web/lib/django/contrib/admin/validation.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 try:
       
     2     set
       
     3 except NameError:
       
     4     from sets import Set as set   # Python 2.3 fallback
       
     5 
       
     6 from django.core.exceptions import ImproperlyConfigured
       
     7 from django.db import models
       
     8 from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model, _get_foreign_key
       
     9 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
       
    10 from django.contrib.admin.options import HORIZONTAL, VERTICAL
       
    11 
       
    12 __all__ = ['validate']
       
    13 
       
    14 def validate(cls, model):
       
    15     """
       
    16     Does basic ModelAdmin option validation. Calls custom validation
       
    17     classmethod in the end if it is provided in cls. The signature of the
       
    18     custom validation classmethod should be: def validate(cls, model).
       
    19     """
       
    20     # Before we can introspect models, they need to be fully loaded so that
       
    21     # inter-relations are set up correctly. We force that here.
       
    22     models.get_apps()
       
    23 
       
    24     opts = model._meta
       
    25     validate_base(cls, model)
       
    26 
       
    27     # list_display
       
    28     if hasattr(cls, 'list_display'):
       
    29         check_isseq(cls, 'list_display', cls.list_display)
       
    30         for idx, field in enumerate(cls.list_display):
       
    31             if not callable(field):
       
    32                 if not hasattr(cls, field):
       
    33                     if not hasattr(model, field):
       
    34                         try:
       
    35                             opts.get_field(field)
       
    36                         except models.FieldDoesNotExist:
       
    37                             raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
       
    38                                 % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
       
    39                     else:
       
    40                         # getattr(model, field) could be an X_RelatedObjectsDescriptor
       
    41                         f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
       
    42                         if isinstance(f, models.ManyToManyField):
       
    43                             raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
       
    44                                 % (cls.__name__, idx, field))
       
    45 
       
    46     # list_display_links
       
    47     if hasattr(cls, 'list_display_links'):
       
    48         check_isseq(cls, 'list_display_links', cls.list_display_links)
       
    49         for idx, field in enumerate(cls.list_display_links):
       
    50             fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field)
       
    51             if field not in cls.list_display:
       
    52                 raise ImproperlyConfigured("'%s.list_display_links[%d]'"
       
    53                         "refers to '%s' which is not defined in 'list_display'."
       
    54                         % (cls.__name__, idx, field))
       
    55 
       
    56     # list_filter
       
    57     if hasattr(cls, 'list_filter'):
       
    58         check_isseq(cls, 'list_filter', cls.list_filter)
       
    59         for idx, field in enumerate(cls.list_filter):
       
    60             get_field(cls, model, opts, 'list_filter[%d]' % idx, field)
       
    61 
       
    62     # list_per_page = 100
       
    63     if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
       
    64         raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
       
    65                 % cls.__name__)
       
    66 
       
    67     # list_editable
       
    68     if hasattr(cls, 'list_editable') and cls.list_editable:
       
    69         check_isseq(cls, 'list_editable', cls.list_editable)
       
    70         for idx, field_name in enumerate(cls.list_editable):
       
    71             try:
       
    72                 field = opts.get_field_by_name(field_name)[0]
       
    73             except models.FieldDoesNotExist:
       
    74                 raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
       
    75                     "field, '%s', not defined on %s."
       
    76                     % (cls.__name__, idx, field_name, model.__name__))
       
    77             if field_name not in cls.list_display:
       
    78                 raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
       
    79                     "'%s' which is not defined in 'list_display'."
       
    80                     % (cls.__name__, idx, field_name))
       
    81             if field_name in cls.list_display_links:
       
    82                 raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
       
    83                     " and '%s.list_display_links'"
       
    84                     % (field_name, cls.__name__, cls.__name__))
       
    85             if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
       
    86                 raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
       
    87                     " the first field in list_display, '%s', which can't be"
       
    88                     " used unless list_display_links is set."
       
    89                     % (cls.__name__, idx, cls.list_display[0]))
       
    90             if not field.editable:
       
    91                 raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
       
    92                     "field, '%s', which isn't editable through the admin."
       
    93                     % (cls.__name__, idx, field_name))
       
    94 
       
    95     # search_fields = ()
       
    96     if hasattr(cls, 'search_fields'):
       
    97         check_isseq(cls, 'search_fields', cls.search_fields)
       
    98 
       
    99     # date_hierarchy = None
       
   100     if cls.date_hierarchy:
       
   101         f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
       
   102         if not isinstance(f, (models.DateField, models.DateTimeField)):
       
   103             raise ImproperlyConfigured("'%s.date_hierarchy is "
       
   104                     "neither an instance of DateField nor DateTimeField."
       
   105                     % cls.__name__)
       
   106 
       
   107     # ordering = None
       
   108     if cls.ordering:
       
   109         check_isseq(cls, 'ordering', cls.ordering)
       
   110         for idx, field in enumerate(cls.ordering):
       
   111             if field == '?' and len(cls.ordering) != 1:
       
   112                 raise ImproperlyConfigured("'%s.ordering' has the random "
       
   113                         "ordering marker '?', but contains other fields as "
       
   114                         "well. Please either remove '?' or the other fields."
       
   115                         % cls.__name__)
       
   116             if field == '?':
       
   117                 continue
       
   118             if field.startswith('-'):
       
   119                 field = field[1:]
       
   120             # Skip ordering in the format field1__field2 (FIXME: checking
       
   121             # this format would be nice, but it's a little fiddly).
       
   122             if '__' in field:
       
   123                 continue
       
   124             get_field(cls, model, opts, 'ordering[%d]' % idx, field)
       
   125 
       
   126     # list_select_related = False
       
   127     # save_as = False
       
   128     # save_on_top = False
       
   129     for attr in ('list_select_related', 'save_as', 'save_on_top'):
       
   130         if not isinstance(getattr(cls, attr), bool):
       
   131             raise ImproperlyConfigured("'%s.%s' should be a boolean."
       
   132                     % (cls.__name__, attr))
       
   133 
       
   134 
       
   135     # inlines = []
       
   136     if hasattr(cls, 'inlines'):
       
   137         check_isseq(cls, 'inlines', cls.inlines)
       
   138         for idx, inline in enumerate(cls.inlines):
       
   139             if not issubclass(inline, BaseModelAdmin):
       
   140                 raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
       
   141                         "from BaseModelAdmin." % (cls.__name__, idx))
       
   142             if not inline.model:
       
   143                 raise ImproperlyConfigured("'model' is a required attribute "
       
   144                         "of '%s.inlines[%d]'." % (cls.__name__, idx))
       
   145             if not issubclass(inline.model, models.Model):
       
   146                 raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
       
   147                         "inherit from models.Model." % (cls.__name__, idx))
       
   148             validate_base(inline, inline.model)
       
   149             validate_inline(inline, cls, model)
       
   150 
       
   151 def validate_inline(cls, parent, parent_model):
       
   152     # model is already verified to exist and be a Model
       
   153     if cls.fk_name: # default value is None
       
   154         f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
       
   155         if not isinstance(f, models.ForeignKey):
       
   156             raise ImproperlyConfigured("'%s.fk_name is not an instance of "
       
   157                     "models.ForeignKey." % cls.__name__)
       
   158     # extra = 3
       
   159     # max_num = 0
       
   160     for attr in ('extra', 'max_num'):
       
   161         if not isinstance(getattr(cls, attr), int):
       
   162             raise ImproperlyConfigured("'%s.%s' should be a integer."
       
   163                     % (cls.__name__, attr))
       
   164 
       
   165     # formset
       
   166     if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
       
   167         raise ImproperlyConfigured("'%s.formset' does not inherit from "
       
   168                 "BaseModelFormSet." % cls.__name__)
       
   169 
       
   170     # exclude
       
   171     if hasattr(cls, 'exclude') and cls.exclude:
       
   172         fk = _get_foreign_key(parent_model, cls.model, can_fail=True)
       
   173         if fk and fk.name in cls.exclude:
       
   174             raise ImproperlyConfigured("%s cannot exclude the field "
       
   175                     "'%s' - this is the foreign key to the parent model "
       
   176                     "%s." % (cls.__name__, fk.name, parent_model.__name__))
       
   177 
       
   178 def validate_base(cls, model):
       
   179     opts = model._meta
       
   180 
       
   181     # raw_id_fields
       
   182     if hasattr(cls, 'raw_id_fields'):
       
   183         check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
       
   184         for idx, field in enumerate(cls.raw_id_fields):
       
   185             f = get_field(cls, model, opts, 'raw_id_fields', field)
       
   186             if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
       
   187                 raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
       
   188                         "be either a ForeignKey or ManyToManyField."
       
   189                         % (cls.__name__, idx, field))
       
   190 
       
   191     # fields
       
   192     if cls.fields: # default value is None
       
   193         check_isseq(cls, 'fields', cls.fields)
       
   194         for field in cls.fields:
       
   195             check_formfield(cls, model, opts, 'fields', field)
       
   196         if cls.fieldsets:
       
   197             raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
       
   198         if len(cls.fields) > len(set(cls.fields)):
       
   199             raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
       
   200 
       
   201     # fieldsets
       
   202     if cls.fieldsets: # default value is None
       
   203         check_isseq(cls, 'fieldsets', cls.fieldsets)
       
   204         for idx, fieldset in enumerate(cls.fieldsets):
       
   205             check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
       
   206             if len(fieldset) != 2:
       
   207                 raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
       
   208                         "have exactly two elements." % (cls.__name__, idx))
       
   209             check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
       
   210             if 'fields' not in fieldset[1]:
       
   211                 raise ImproperlyConfigured("'fields' key is required in "
       
   212                         "%s.fieldsets[%d][1] field options dict."
       
   213                         % (cls.__name__, idx))
       
   214         flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
       
   215         if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
       
   216             raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
       
   217         for field in flattened_fieldsets:
       
   218             check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
       
   219 
       
   220     # form
       
   221     if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
       
   222         raise ImproperlyConfigured("%s.form does not inherit from "
       
   223                 "BaseModelForm." % cls.__name__)
       
   224 
       
   225     # filter_vertical
       
   226     if hasattr(cls, 'filter_vertical'):
       
   227         check_isseq(cls, 'filter_vertical', cls.filter_vertical)
       
   228         for idx, field in enumerate(cls.filter_vertical):
       
   229             f = get_field(cls, model, opts, 'filter_vertical', field)
       
   230             if not isinstance(f, models.ManyToManyField):
       
   231                 raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
       
   232                     "a ManyToManyField." % (cls.__name__, idx))
       
   233 
       
   234     # filter_horizontal
       
   235     if hasattr(cls, 'filter_horizontal'):
       
   236         check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
       
   237         for idx, field in enumerate(cls.filter_horizontal):
       
   238             f = get_field(cls, model, opts, 'filter_horizontal', field)
       
   239             if not isinstance(f, models.ManyToManyField):
       
   240                 raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
       
   241                     "a ManyToManyField." % (cls.__name__, idx))
       
   242 
       
   243     # radio_fields
       
   244     if hasattr(cls, 'radio_fields'):
       
   245         check_isdict(cls, 'radio_fields', cls.radio_fields)
       
   246         for field, val in cls.radio_fields.items():
       
   247             f = get_field(cls, model, opts, 'radio_fields', field)
       
   248             if not (isinstance(f, models.ForeignKey) or f.choices):
       
   249                 raise ImproperlyConfigured("'%s.radio_fields['%s']' "
       
   250                         "is neither an instance of ForeignKey nor does "
       
   251                         "have choices set." % (cls.__name__, field))
       
   252             if not val in (HORIZONTAL, VERTICAL):
       
   253                 raise ImproperlyConfigured("'%s.radio_fields['%s']' "
       
   254                         "is neither admin.HORIZONTAL nor admin.VERTICAL."
       
   255                         % (cls.__name__, field))
       
   256 
       
   257     # prepopulated_fields
       
   258     if hasattr(cls, 'prepopulated_fields'):
       
   259         check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
       
   260         for field, val in cls.prepopulated_fields.items():
       
   261             f = get_field(cls, model, opts, 'prepopulated_fields', field)
       
   262             if isinstance(f, (models.DateTimeField, models.ForeignKey,
       
   263                 models.ManyToManyField)):
       
   264                 raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
       
   265                         "is either a DateTimeField, ForeignKey or "
       
   266                         "ManyToManyField. This isn't allowed."
       
   267                         % (cls.__name__, field))
       
   268             check_isseq(cls, "prepopulated_fields['%s']" % field, val)
       
   269             for idx, f in enumerate(val):
       
   270                 get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
       
   271 
       
   272 def check_isseq(cls, label, obj):
       
   273     if not isinstance(obj, (list, tuple)):
       
   274         raise ImproperlyConfigured("'%s.%s' must be a list or tuple." % (cls.__name__, label))
       
   275 
       
   276 def check_isdict(cls, label, obj):
       
   277     if not isinstance(obj, dict):
       
   278         raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
       
   279 
       
   280 def get_field(cls, model, opts, label, field):
       
   281     try:
       
   282         return opts.get_field(field)
       
   283     except models.FieldDoesNotExist:
       
   284         raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s'."
       
   285                 % (cls.__name__, label, field, model.__name__))
       
   286 
       
   287 def check_formfield(cls, model, opts, label, field):
       
   288     if getattr(cls.form, 'base_fields', None):
       
   289         try:
       
   290             cls.form.base_fields[field]
       
   291         except KeyError:
       
   292             raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
       
   293                 "is missing from the form." % (cls.__name__, label, field))
       
   294     else:
       
   295         fields = fields_for_model(model)
       
   296         try:
       
   297             fields[field]
       
   298         except KeyError:
       
   299             raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
       
   300                 "is missing from the form." % (cls.__name__, label, field))
       
   301 
       
   302 def fetch_attr(cls, model, opts, label, field):
       
   303     try:
       
   304         return opts.get_field(field)
       
   305     except models.FieldDoesNotExist:
       
   306         pass
       
   307     try:
       
   308         return getattr(model, field)
       
   309     except AttributeError:
       
   310         raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s'."
       
   311             % (cls.__name__, label, field, model.__name__))