app/django/core/management/validation.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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 f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple):
       
    55                 e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % 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             # Check that max_length <= 255 if using older MySQL versions.
       
    67             if settings.DATABASE_ENGINE == 'mysql':
       
    68                 db_version = connection.get_server_version()
       
    69                 if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.max_length > 255:
       
    70                     e.add(opts, '"%s": %s cannot have a "max_length" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]])))
       
    71 
       
    72             # Check to see if the related field will clash with any existing
       
    73             # fields, m2m fields, m2m related objects or related objects
       
    74             if f.rel:
       
    75                 if f.rel.to not in models.get_models():
       
    76                     e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, f.rel.to))
       
    77                 # it is a string and we could not find the model it refers to
       
    78                 # so skip the next section
       
    79                 if isinstance(f.rel.to, (str, unicode)):
       
    80                     continue
       
    81 
       
    82                 rel_opts = f.rel.to._meta
       
    83                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
    84                 rel_query_name = f.related_query_name()
       
    85                 for r in rel_opts.fields:
       
    86                     if r.name == rel_name:
       
    87                         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))
       
    88                     if r.name == rel_query_name:
       
    89                         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))
       
    90                 for r in rel_opts.local_many_to_many:
       
    91                     if r.name == rel_name:
       
    92                         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))
       
    93                     if r.name == rel_query_name:
       
    94                         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))
       
    95                 for r in rel_opts.get_all_related_many_to_many_objects():
       
    96                     if r.get_accessor_name() == rel_name:
       
    97                         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))
       
    98                     if r.get_accessor_name() == rel_query_name:
       
    99                         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))
       
   100                 for r in rel_opts.get_all_related_objects():
       
   101                     if r.field is not f:
       
   102                         if r.get_accessor_name() == rel_name:
       
   103                             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))
       
   104                         if r.get_accessor_name() == rel_query_name:
       
   105                             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))
       
   106 
       
   107         for i, f in enumerate(opts.local_many_to_many):
       
   108             # Check to see if the related m2m field will clash with any
       
   109             # existing fields, m2m fields, m2m related objects or related
       
   110             # objects
       
   111             if f.rel.to not in models.get_models():
       
   112                 e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, f.rel.to))
       
   113                 # it is a string and we could not find the model it refers to
       
   114                 # so skip the next section
       
   115                 if isinstance(f.rel.to, (str, unicode)):
       
   116                     continue
       
   117 
       
   118             rel_opts = f.rel.to._meta
       
   119             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
       
   120             rel_query_name = f.related_query_name()
       
   121             # If rel_name is none, there is no reverse accessor (this only
       
   122             # occurs for symmetrical m2m relations to self). If this is the
       
   123             # case, there are no clashes to check for this field, as there are
       
   124             # no reverse descriptors for this field.
       
   125             if rel_name is not None:
       
   126                 for r in rel_opts.fields:
       
   127                     if r.name == rel_name:
       
   128                         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))
       
   129                     if r.name == rel_query_name:
       
   130                         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))
       
   131                 for r in rel_opts.local_many_to_many:
       
   132                     if r.name == rel_name:
       
   133                         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))
       
   134                     if r.name == rel_query_name:
       
   135                         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))
       
   136                 for r in rel_opts.get_all_related_many_to_many_objects():
       
   137                     if r.field is not f:
       
   138                         if r.get_accessor_name() == rel_name:
       
   139                             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))
       
   140                         if r.get_accessor_name() == rel_query_name:
       
   141                             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))
       
   142                 for r in rel_opts.get_all_related_objects():
       
   143                     if r.get_accessor_name() == rel_name:
       
   144                         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))
       
   145                     if r.get_accessor_name() == rel_query_name:
       
   146                         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))
       
   147 
       
   148         # Check admin attribute.
       
   149         if opts.admin is not None:
       
   150             if not isinstance(opts.admin, models.AdminOptions):
       
   151                 e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.')
       
   152             else:
       
   153                 # list_display
       
   154                 if not isinstance(opts.admin.list_display, (list, tuple)):
       
   155                     e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
       
   156                 else:
       
   157                     for fn in opts.admin.list_display:
       
   158                         try:
       
   159                             f = opts.get_field(fn)
       
   160                         except models.FieldDoesNotExist:
       
   161                             if not hasattr(cls, fn):
       
   162                                 e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
       
   163                         else:
       
   164                             if isinstance(f, models.ManyToManyField):
       
   165                                 e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
       
   166                 # list_display_links
       
   167                 if opts.admin.list_display_links and not opts.admin.list_display:
       
   168                     e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.')
       
   169                 if not isinstance(opts.admin.list_display_links, (list, tuple)):
       
   170                     e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.')
       
   171                 else:
       
   172                     for fn in opts.admin.list_display_links:
       
   173                         try:
       
   174                             f = opts.get_field(fn)
       
   175                         except models.FieldDoesNotExist:
       
   176                             if not hasattr(cls, fn):
       
   177                                 e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
       
   178                         if fn not in opts.admin.list_display:
       
   179                             e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
       
   180                 # list_filter
       
   181                 if not isinstance(opts.admin.list_filter, (list, tuple)):
       
   182                     e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.')
       
   183                 else:
       
   184                     for fn in opts.admin.list_filter:
       
   185                         try:
       
   186                             f = opts.get_field(fn)
       
   187                         except models.FieldDoesNotExist:
       
   188                             e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
       
   189                 # date_hierarchy
       
   190                 if opts.admin.date_hierarchy:
       
   191                     try:
       
   192                         f = opts.get_field(opts.admin.date_hierarchy)
       
   193                     except models.FieldDoesNotExist:
       
   194                         e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy)
       
   195 
       
   196         # Check ordering attribute.
       
   197         if opts.ordering:
       
   198             for field_name in opts.ordering:
       
   199                 if field_name == '?': continue
       
   200                 if field_name.startswith('-'):
       
   201                     field_name = field_name[1:]
       
   202                 if opts.order_with_respect_to and field_name == '_order':
       
   203                     continue
       
   204                 # Skip ordering in the format field1__field2 (FIXME: checking
       
   205                 # this format would be nice, but it's a little fiddly).
       
   206                 if '_' in field_name:
       
   207                     continue
       
   208                 try:
       
   209                     opts.get_field(field_name, many_to_many=False)
       
   210                 except models.FieldDoesNotExist:
       
   211                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
       
   212 
       
   213         # Check core=True, if needed.
       
   214         for related in opts.get_followed_related_objects():
       
   215             if not related.edit_inline:
       
   216                 continue
       
   217             try:
       
   218                 for f in related.opts.fields:
       
   219                     if f.core:
       
   220                         raise StopIteration
       
   221                 e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
       
   222             except StopIteration:
       
   223                 pass
       
   224 
       
   225         # Check unique_together.
       
   226         for ut in opts.unique_together:
       
   227             for field_name in ut:
       
   228                 try:
       
   229                     f = opts.get_field(field_name, many_to_many=True)
       
   230                 except models.FieldDoesNotExist:
       
   231                     e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
       
   232                 else:
       
   233                     if isinstance(f.rel, models.ManyToManyRel):
       
   234                         e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
       
   235                     if f not in opts.local_fields:
       
   236                         e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
       
   237 
       
   238     return len(e.errors)