web/lib/django/core/management/validation.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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):
       
    41                 try:
       
    42                     max_length = int(f.max_length)
       
    43                     if max_length <= 0:
       
    44                         e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
       
    45                 except (ValueError, TypeError):
       
    46                     e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
       
    47             if isinstance(f, models.DecimalField):
       
    48                 decimalp_msg ='"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
       
    49                 try:
       
    50                     decimal_places = int(f.decimal_places)
       
    51                     if decimal_places < 0:
       
    52                         e.add(opts, decimalp_msg % f.name)
       
    53                 except (ValueError, TypeError):
       
    54                     e.add(opts, decimalp_msg % f.name)
       
    55                 mdigits_msg = '"%s": DecimalFields require a "max_digits" attribute that is a positive integer.'
       
    56                 try:
       
    57                     max_digits = int(f.max_digits)
       
    58                     if max_digits <= 0:
       
    59                         e.add(opts,  mdigits_msg % f.name)
       
    60                 except (ValueError, TypeError):
       
    61                     e.add(opts, mdigits_msg % f.name)
       
    62             if isinstance(f, models.FileField) and not f.upload_to:
       
    63                 e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
       
    64             if isinstance(f, models.ImageField):
       
    65                 # Try to import PIL in either of the two ways it can end up installed.
       
    66                 try:
       
    67                     from PIL import Image
       
    68                 except ImportError:
       
    69                     try:
       
    70                         import Image
       
    71                     except ImportError:
       
    72                         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)
       
    73             if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
       
    74                 e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
       
    75             if f.choices:
       
    76                 if isinstance(f.choices, basestring) or not is_iterable(f.choices):
       
    77                     e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
       
    78                 else:
       
    79                     for c in f.choices:
       
    80                         if not isinstance(c, (list, tuple)) or len(c) != 2:
       
    81                             e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
       
    82             if f.db_index not in (None, True, False):
       
    83                 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
       
    84 
       
    85             # Perform any backend-specific field validation.
       
    86             connection.validation.validate_field(e, opts, f)
       
    87 
       
    88             # Check to see if the related field will clash with any existing
       
    89             # fields, m2m fields, m2m related objects or related objects
       
    90             if f.rel:
       
    91                 if f.rel.to not in models.get_models():
       
    92                     e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
       
    93                 # it is a string and we could not find the model it refers to
       
    94                 # so skip the next section
       
    95                 if isinstance(f.rel.to, (str, unicode)):
       
    96                     continue
       
    97 
       
    98                 # Make sure the related field specified by a ForeignKey is unique
       
    99                 if not f.rel.to._meta.get_field(f.rel.field_name).unique:
       
   100                     e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
       
   101 
       
   102                 rel_opts = f.rel.to._meta
       
   103                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
   104                 rel_query_name = f.related_query_name()
       
   105                 if not f.rel.is_hidden():
       
   106                     for r in rel_opts.fields:
       
   107                         if r.name == rel_name:
       
   108                             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))
       
   109                         if r.name == rel_query_name:
       
   110                             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))
       
   111                     for r in rel_opts.local_many_to_many:
       
   112                         if r.name == rel_name:
       
   113                             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))
       
   114                         if r.name == rel_query_name:
       
   115                             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))
       
   116                     for r in rel_opts.get_all_related_many_to_many_objects():
       
   117                         if r.get_accessor_name() == rel_name:
       
   118                             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))
       
   119                         if r.get_accessor_name() == rel_query_name:
       
   120                             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))
       
   121                     for r in rel_opts.get_all_related_objects():
       
   122                         if r.field is not f:
       
   123                             if r.get_accessor_name() == rel_name:
       
   124                                 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))
       
   125                             if r.get_accessor_name() == rel_query_name:
       
   126                                 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))
       
   127 
       
   128         seen_intermediary_signatures = []
       
   129         for i, f in enumerate(opts.local_many_to_many):
       
   130             # Check to see if the related m2m field will clash with any
       
   131             # existing fields, m2m fields, m2m related objects or related
       
   132             # objects
       
   133             if f.rel.to not in models.get_models():
       
   134                 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))
       
   135                 # it is a string and we could not find the model it refers to
       
   136                 # so skip the next section
       
   137                 if isinstance(f.rel.to, (str, unicode)):
       
   138                     continue
       
   139 
       
   140             # Check that the field is not set to unique.  ManyToManyFields do not support unique.
       
   141             if f.unique:
       
   142                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
       
   143 
       
   144             if f.rel.through is not None and not isinstance(f.rel.through, basestring):
       
   145                 from_model, to_model = cls, f.rel.to
       
   146                 if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
       
   147                     e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
       
   148                 seen_from, seen_to, seen_self = False, False, 0
       
   149                 for inter_field in f.rel.through._meta.fields:
       
   150                     rel_to = getattr(inter_field.rel, 'to', None)
       
   151                     if from_model == to_model: # relation to self
       
   152                         if rel_to == from_model:
       
   153                             seen_self += 1
       
   154                         if seen_self > 2:
       
   155                             e.add(opts, "Intermediary model %s has more than "
       
   156                                 "two foreign keys to %s, which is ambiguous "
       
   157                                 "and is not permitted." % (
       
   158                                     f.rel.through._meta.object_name,
       
   159                                     from_model._meta.object_name
       
   160                                 )
       
   161                             )
       
   162                     else:
       
   163                         if rel_to == from_model:
       
   164                             if seen_from:
       
   165                                 e.add(opts, "Intermediary model %s has more "
       
   166                                     "than one foreign key to %s, which is "
       
   167                                     "ambiguous and is not permitted." % (
       
   168                                         f.rel.through._meta.object_name,
       
   169                                          from_model._meta.object_name
       
   170                                      )
       
   171                                  )
       
   172                             else:
       
   173                                 seen_from = True
       
   174                         elif rel_to == to_model:
       
   175                             if seen_to:
       
   176                                 e.add(opts, "Intermediary model %s has more "
       
   177                                     "than one foreign key to %s, which is "
       
   178                                     "ambiguous and is not permitted." % (
       
   179                                         f.rel.through._meta.object_name,
       
   180                                         rel_to._meta.object_name
       
   181                                     )
       
   182                                 )
       
   183                             else:
       
   184                                 seen_to = True
       
   185                 if f.rel.through not in models.get_models(include_auto_created=True):
       
   186                     e.add(opts, "'%s' specifies an m2m relation through model "
       
   187                         "%s, which has not been installed." % (f.name, f.rel.through)
       
   188                     )
       
   189                 signature = (f.rel.to, cls, f.rel.through)
       
   190                 if signature in seen_intermediary_signatures:
       
   191                     e.add(opts, "The model %s has two manually-defined m2m "
       
   192                         "relations through the model %s, which is not "
       
   193                         "permitted. Please consider using an extra field on "
       
   194                         "your intermediary model instead." % (
       
   195                             cls._meta.object_name,
       
   196                             f.rel.through._meta.object_name
       
   197                         )
       
   198                     )
       
   199                 else:
       
   200                     seen_intermediary_signatures.append(signature)
       
   201                 if not f.rel.through._meta.auto_created:
       
   202                     seen_related_fk, seen_this_fk = False, False
       
   203                     for field in f.rel.through._meta.fields:
       
   204                         if field.rel:
       
   205                             if not seen_related_fk and field.rel.to == f.rel.to:
       
   206                                 seen_related_fk = True
       
   207                             elif field.rel.to == cls:
       
   208                                 seen_this_fk = True
       
   209                     if not seen_related_fk or not seen_this_fk:
       
   210                         e.add(opts, "'%s' is a manually-defined m2m relation "
       
   211                             "through model %s, which does not have foreign keys "
       
   212                             "to %s and %s" % (f.name, f.rel.through._meta.object_name,
       
   213                                 f.rel.to._meta.object_name, cls._meta.object_name)
       
   214                         )
       
   215             elif isinstance(f.rel.through, basestring):
       
   216                 e.add(opts, "'%s' specifies an m2m relation through model %s, "
       
   217                     "which has not been installed" % (f.name, f.rel.through)
       
   218                 )
       
   219 
       
   220             rel_opts = f.rel.to._meta
       
   221             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
   222             rel_query_name = f.related_query_name()
       
   223             # If rel_name is none, there is no reverse accessor (this only
       
   224             # occurs for symmetrical m2m relations to self). If this is the
       
   225             # case, there are no clashes to check for this field, as there are
       
   226             # no reverse descriptors for this field.
       
   227             if rel_name is not None:
       
   228                 for r in rel_opts.fields:
       
   229                     if r.name == rel_name:
       
   230                         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))
       
   231                     if r.name == rel_query_name:
       
   232                         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))
       
   233                 for r in rel_opts.local_many_to_many:
       
   234                     if r.name == rel_name:
       
   235                         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))
       
   236                     if r.name == rel_query_name:
       
   237                         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))
       
   238                 for r in rel_opts.get_all_related_many_to_many_objects():
       
   239                     if r.field is not f:
       
   240                         if r.get_accessor_name() == rel_name:
       
   241                             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))
       
   242                         if r.get_accessor_name() == rel_query_name:
       
   243                             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))
       
   244                 for r in rel_opts.get_all_related_objects():
       
   245                     if r.get_accessor_name() == rel_name:
       
   246                         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))
       
   247                     if r.get_accessor_name() == rel_query_name:
       
   248                         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))
       
   249 
       
   250         # Check ordering attribute.
       
   251         if opts.ordering:
       
   252             for field_name in opts.ordering:
       
   253                 if field_name == '?': continue
       
   254                 if field_name.startswith('-'):
       
   255                     field_name = field_name[1:]
       
   256                 if opts.order_with_respect_to and field_name == '_order':
       
   257                     continue
       
   258                 # Skip ordering in the format field1__field2 (FIXME: checking
       
   259                 # this format would be nice, but it's a little fiddly).
       
   260                 if '_' in field_name:
       
   261                     continue
       
   262                 try:
       
   263                     opts.get_field(field_name, many_to_many=False)
       
   264                 except models.FieldDoesNotExist:
       
   265                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
       
   266 
       
   267         # Check unique_together.
       
   268         for ut in opts.unique_together:
       
   269             for field_name in ut:
       
   270                 try:
       
   271                     f = opts.get_field(field_name, many_to_many=True)
       
   272                 except models.FieldDoesNotExist:
       
   273                     e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
       
   274                 else:
       
   275                     if isinstance(f.rel, models.ManyToManyRel):
       
   276                         e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
       
   277                     if f not in opts.local_fields:
       
   278                         e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
       
   279 
       
   280     return len(e.errors)