web/lib/django/core/management/validation.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import sys
       
     2 from django.core.management.color import color_style
       
     3 from django.utils.itercompat import is_iterable
       
     4 
       
     5 class ModelErrorCollection:
       
     6     def __init__(self, outfile=sys.stdout):
       
     7         self.errors = []
       
     8         self.outfile = outfile
       
     9         self.style = color_style()
       
    10 
       
    11     def add(self, context, error):
       
    12         self.errors.append((context, error))
       
    13         self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error)))
       
    14 
       
    15 def get_validation_errors(outfile, app=None):
       
    16     """
       
    17     Validates all models that are part of the specified app. If no app name is provided,
       
    18     validates all models of all installed apps. Writes errors, if any, to outfile.
       
    19     Returns number of errors.
       
    20     """
       
    21     from django.conf import settings
       
    22     from django.db import models, connection
       
    23     from django.db.models.loading import get_app_errors
       
    24     from django.db.models.fields.related import RelatedObject
       
    25 
       
    26     e = ModelErrorCollection(outfile)
       
    27 
       
    28     for (app_name, error) in get_app_errors().items():
       
    29         e.add(app_name, error)
       
    30 
       
    31     for cls in models.get_models(app):
       
    32         opts = cls._meta
       
    33 
       
    34         # Do field-specific validation.
       
    35         for f in opts.local_fields:
       
    36             if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
       
    37                 e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
       
    38             if f.name.endswith('_'):
       
    39                 e.add(opts, '"%s": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.' % f.name)
       
    40             if isinstance(f, models.CharField) and f.max_length in (None, 0):
       
    41                 e.add(opts, '"%s": CharFields require a "max_length" attribute.' % f.name)
       
    42             if isinstance(f, models.DecimalField):
       
    43                 if f.decimal_places is None:
       
    44                     e.add(opts, '"%s": DecimalFields require a "decimal_places" attribute.' % f.name)
       
    45                 if f.max_digits is None:
       
    46                     e.add(opts, '"%s": DecimalFields require a "max_digits" attribute.' % f.name)
       
    47             if isinstance(f, models.FileField) and not f.upload_to:
       
    48                 e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
       
    49             if isinstance(f, models.ImageField):
       
    50                 try:
       
    51                     from PIL import Image
       
    52                 except ImportError:
       
    53                     e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
       
    54             if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
       
    55                 e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
       
    56             if f.choices:
       
    57                 if isinstance(f.choices, basestring) or not is_iterable(f.choices):
       
    58                     e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
       
    59                 else:
       
    60                     for c in f.choices:
       
    61                         if not type(c) in (tuple, list) or len(c) != 2:
       
    62                             e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
       
    63             if f.db_index not in (None, True, False):
       
    64                 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
       
    65 
       
    66             # Perform any backend-specific field validation.
       
    67             connection.validation.validate_field(e, opts, f)
       
    68 
       
    69             # Check to see if the related field will clash with any existing
       
    70             # fields, m2m fields, m2m related objects or related objects
       
    71             if f.rel:
       
    72                 if f.rel.to not in models.get_models():
       
    73                     e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
       
    74                 # it is a string and we could not find the model it refers to
       
    75                 # so skip the next section
       
    76                 if isinstance(f.rel.to, (str, unicode)):
       
    77                     continue
       
    78 
       
    79                 rel_opts = f.rel.to._meta
       
    80                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
    81                 rel_query_name = f.related_query_name()
       
    82                 for r in rel_opts.fields:
       
    83                     if r.name == rel_name:
       
    84                         e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
    85                     if r.name == rel_query_name:
       
    86                         e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
    87                 for r in rel_opts.local_many_to_many:
       
    88                     if r.name == rel_name:
       
    89                         e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
    90                     if r.name == rel_query_name:
       
    91                         e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
    92                 for r in rel_opts.get_all_related_many_to_many_objects():
       
    93                     if r.get_accessor_name() == rel_name:
       
    94                         e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
    95                     if r.get_accessor_name() == rel_query_name:
       
    96                         e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
    97                 for r in rel_opts.get_all_related_objects():
       
    98                     if r.field is not f:
       
    99                         if r.get_accessor_name() == rel_name:
       
   100                             e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   101                         if r.get_accessor_name() == rel_query_name:
       
   102                             e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   103 
       
   104         seen_intermediary_signatures = []
       
   105         for i, f in enumerate(opts.local_many_to_many):
       
   106             # Check to see if the related m2m field will clash with any
       
   107             # existing fields, m2m fields, m2m related objects or related
       
   108             # objects
       
   109             if f.rel.to not in models.get_models():
       
   110                 e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
       
   111                 # it is a string and we could not find the model it refers to
       
   112                 # so skip the next section
       
   113                 if isinstance(f.rel.to, (str, unicode)):
       
   114                     continue
       
   115 
       
   116             # Check that the field is not set to unique.  ManyToManyFields do not support unique.
       
   117             if f.unique:
       
   118                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
       
   119 
       
   120             if getattr(f.rel, 'through', None) is not None:
       
   121                 if hasattr(f.rel, 'through_model'):
       
   122                     from_model, to_model = cls, f.rel.to
       
   123                     if from_model == to_model and f.rel.symmetrical:
       
   124                         e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
       
   125                     seen_from, seen_to, seen_self = False, False, 0
       
   126                     for inter_field in f.rel.through_model._meta.fields:
       
   127                         rel_to = getattr(inter_field.rel, 'to', None)
       
   128                         if from_model == to_model: # relation to self
       
   129                             if rel_to == from_model:
       
   130                                 seen_self += 1
       
   131                             if seen_self > 2:
       
   132                                 e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
       
   133                         else:
       
   134                             if rel_to == from_model:
       
   135                                 if seen_from:
       
   136                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
       
   137                                 else:
       
   138                                     seen_from = True
       
   139                             elif rel_to == to_model:
       
   140                                 if seen_to:
       
   141                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
       
   142                                 else:
       
   143                                     seen_to = True
       
   144                     if f.rel.through_model not in models.get_models():
       
   145                         e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
       
   146                     signature = (f.rel.to, cls, f.rel.through_model)
       
   147                     if signature in seen_intermediary_signatures:
       
   148                         e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
       
   149                     else:
       
   150                         seen_intermediary_signatures.append(signature)
       
   151                     seen_related_fk, seen_this_fk = False, False
       
   152                     for field in f.rel.through_model._meta.fields:
       
   153                         if field.rel:
       
   154                             if not seen_related_fk and field.rel.to == f.rel.to:
       
   155                                 seen_related_fk = True
       
   156                             elif field.rel.to == cls:
       
   157                                 seen_this_fk = True
       
   158                     if not seen_related_fk or not seen_this_fk:
       
   159                         e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
       
   160                 else:
       
   161                     e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
       
   162 
       
   163             rel_opts = f.rel.to._meta
       
   164             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
   165             rel_query_name = f.related_query_name()
       
   166             # If rel_name is none, there is no reverse accessor (this only
       
   167             # occurs for symmetrical m2m relations to self). If this is the
       
   168             # case, there are no clashes to check for this field, as there are
       
   169             # no reverse descriptors for this field.
       
   170             if rel_name is not None:
       
   171                 for r in rel_opts.fields:
       
   172                     if r.name == rel_name:
       
   173                         e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
   174                     if r.name == rel_query_name:
       
   175                         e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
   176                 for r in rel_opts.local_many_to_many:
       
   177                     if r.name == rel_name:
       
   178                         e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
   179                     if r.name == rel_query_name:
       
   180                         e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
       
   181                 for r in rel_opts.get_all_related_many_to_many_objects():
       
   182                     if r.field is not f:
       
   183                         if r.get_accessor_name() == rel_name:
       
   184                             e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   185                         if r.get_accessor_name() == rel_query_name:
       
   186                             e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   187                 for r in rel_opts.get_all_related_objects():
       
   188                     if r.get_accessor_name() == rel_name:
       
   189                         e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   190                     if r.get_accessor_name() == rel_query_name:
       
   191                         e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
       
   192 
       
   193         # Check ordering attribute.
       
   194         if opts.ordering:
       
   195             for field_name in opts.ordering:
       
   196                 if field_name == '?': continue
       
   197                 if field_name.startswith('-'):
       
   198                     field_name = field_name[1:]
       
   199                 if opts.order_with_respect_to and field_name == '_order':
       
   200                     continue
       
   201                 # Skip ordering in the format field1__field2 (FIXME: checking
       
   202                 # this format would be nice, but it's a little fiddly).
       
   203                 if '_' in field_name:
       
   204                     continue
       
   205                 try:
       
   206                     opts.get_field(field_name, many_to_many=False)
       
   207                 except models.FieldDoesNotExist:
       
   208                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
       
   209 
       
   210         # Check unique_together.
       
   211         for ut in opts.unique_together:
       
   212             for field_name in ut:
       
   213                 try:
       
   214                     f = opts.get_field(field_name, many_to_many=True)
       
   215                 except models.FieldDoesNotExist:
       
   216                     e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
       
   217                 else:
       
   218                     if isinstance(f.rel, models.ManyToManyRel):
       
   219                         e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
       
   220                     if f not in opts.local_fields:
       
   221                         e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
       
   222 
       
   223     return len(e.errors)