PageRenderTime 108ms CodeModel.GetById 30ms app.highlight 57ms RepoModel.GetById 14ms app.codeStats 0ms

/django/contrib/admin/validation.py

https://code.google.com/p/mango-py/
Python | 396 lines | 304 code | 40 blank | 52 comment | 169 complexity | dc4b832a3332cd023fe6d7490c5c3234 MD5 | raw file
  1from django.core.exceptions import ImproperlyConfigured
  2from django.db import models
  3from django.db.models.fields import FieldDoesNotExist
  4from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
  5    _get_foreign_key)
  6from django.contrib.admin.util import get_fields_from_path, NotRelationField
  7from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin,
  8    HORIZONTAL, VERTICAL)
  9
 10
 11__all__ = ['validate']
 12
 13def validate(cls, model):
 14    """
 15    Does basic ModelAdmin option validation. Calls custom validation
 16    classmethod in the end if it is provided in cls. The signature of the
 17    custom validation classmethod should be: def validate(cls, model).
 18    """
 19    # Before we can introspect models, they need to be fully loaded so that
 20    # inter-relations are set up correctly. We force that here.
 21    models.get_apps()
 22
 23    opts = model._meta
 24    validate_base(cls, model)
 25
 26    # list_display
 27    if hasattr(cls, 'list_display'):
 28        check_isseq(cls, 'list_display', cls.list_display)
 29        for idx, field in enumerate(cls.list_display):
 30            if not callable(field):
 31                if not hasattr(cls, field):
 32                    if not hasattr(model, field):
 33                        try:
 34                            opts.get_field(field)
 35                        except models.FieldDoesNotExist:
 36                            raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
 37                                % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
 38                    else:
 39                        # getattr(model, field) could be an X_RelatedObjectsDescriptor
 40                        f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
 41                        if isinstance(f, models.ManyToManyField):
 42                            raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
 43                                % (cls.__name__, idx, field))
 44
 45    # list_display_links
 46    if hasattr(cls, 'list_display_links'):
 47        check_isseq(cls, 'list_display_links', cls.list_display_links)
 48        for idx, field in enumerate(cls.list_display_links):
 49            if field not in cls.list_display:
 50                raise ImproperlyConfigured("'%s.list_display_links[%d]' "
 51                        "refers to '%s' which is not defined in 'list_display'."
 52                        % (cls.__name__, idx, field))
 53
 54    # list_filter
 55    if hasattr(cls, 'list_filter'):
 56        check_isseq(cls, 'list_filter', cls.list_filter)
 57        for idx, fpath in enumerate(cls.list_filter):
 58            try:
 59                get_fields_from_path(model, fpath)
 60            except (NotRelationField, FieldDoesNotExist), e:
 61                if ':' in fpath:
 62                    fpath = fpath.split(':',1)[1]   
 63                if '__' in fpath:
 64                    get_field(cls, model, opts, 'list_filter[%d]' % idx, fpath.split("__")[0])
 65                else:
 66                    get_field(cls, model, opts, 'list_filter[%d]' % idx, fpath)
 67
 68    # list_per_page = 100
 69    if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
 70        raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
 71                % cls.__name__)
 72
 73    # list_editable
 74    if hasattr(cls, 'list_editable') and cls.list_editable:
 75        check_isseq(cls, 'list_editable', cls.list_editable)
 76        for idx, field_name in enumerate(cls.list_editable):
 77            try:
 78                field = opts.get_field_by_name(field_name)[0]
 79            except models.FieldDoesNotExist:
 80                raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
 81                    "field, '%s', not defined on %s."
 82                    % (cls.__name__, idx, field_name, model.__name__))
 83            if field_name not in cls.list_display:
 84                raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
 85                    "'%s' which is not defined in 'list_display'."
 86                    % (cls.__name__, idx, field_name))
 87            if field_name in cls.list_display_links:
 88                raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
 89                    " and '%s.list_display_links'"
 90                    % (field_name, cls.__name__, cls.__name__))
 91            if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
 92                raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
 93                    " the first field in list_display, '%s', which can't be"
 94                    " used unless list_display_links is set."
 95                    % (cls.__name__, idx, cls.list_display[0]))
 96            if not field.editable:
 97                raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
 98                    "field, '%s', which isn't editable through the admin."
 99                    % (cls.__name__, idx, field_name))
100
101    # search_fields = ()
102    if hasattr(cls, 'search_fields'):
103        check_isseq(cls, 'search_fields', cls.search_fields)
104
105    # date_hierarchy = None
106    if cls.date_hierarchy:
107        f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
108        if not isinstance(f, (models.DateField, models.DateTimeField)):
109            raise ImproperlyConfigured("'%s.date_hierarchy is "
110                    "neither an instance of DateField nor DateTimeField."
111                    % cls.__name__)
112
113    # ordering = None
114    if cls.ordering:
115        check_isseq(cls, 'ordering', cls.ordering)
116        for idx, field in enumerate(cls.ordering):
117            if field == '?' and len(cls.ordering) != 1:
118                raise ImproperlyConfigured("'%s.ordering' has the random "
119                        "ordering marker '?', but contains other fields as "
120                        "well. Please either remove '?' or the other fields."
121                        % cls.__name__)
122            if field == '?':
123                continue
124            if field.startswith('-'):
125                field = field[1:]
126            # Skip ordering in the format field1__field2 (FIXME: checking
127            # this format would be nice, but it's a little fiddly).
128            if '__' in field:
129                continue
130            get_field(cls, model, opts, 'ordering[%d]' % idx, field)
131
132    if hasattr(cls, "readonly_fields"):
133        check_readonly_fields(cls, model, opts)
134
135    # list_select_related = False
136    # save_as = False
137    # save_on_top = False
138    for attr in ('list_select_related', 'save_as', 'save_on_top'):
139        if not isinstance(getattr(cls, attr), bool):
140            raise ImproperlyConfigured("'%s.%s' should be a boolean."
141                    % (cls.__name__, attr))
142
143
144    # inlines = []
145    if hasattr(cls, 'inlines'):
146        check_isseq(cls, 'inlines', cls.inlines)
147        for idx, inline in enumerate(cls.inlines):
148            if not issubclass(inline, BaseModelAdmin):
149                raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
150                        "from BaseModelAdmin." % (cls.__name__, idx))
151            if not inline.model:
152                raise ImproperlyConfigured("'model' is a required attribute "
153                        "of '%s.inlines[%d]'." % (cls.__name__, idx))
154            if not issubclass(inline.model, models.Model):
155                raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
156                        "inherit from models.Model." % (cls.__name__, idx))
157            validate_base(inline, inline.model)
158            validate_inline(inline, cls, model)
159
160def validate_inline(cls, parent, parent_model):
161
162    # model is already verified to exist and be a Model
163    if cls.fk_name: # default value is None
164        f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
165        if not isinstance(f, models.ForeignKey):
166            raise ImproperlyConfigured("'%s.fk_name is not an instance of "
167                    "models.ForeignKey." % cls.__name__)
168
169    fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
170
171    # extra = 3
172    if not isinstance(cls.extra, int):
173        raise ImproperlyConfigured("'%s.extra' should be a integer."
174                % cls.__name__)
175
176    # max_num = None
177    max_num = getattr(cls, 'max_num', None)
178    if max_num is not None and not isinstance(max_num, int):
179        raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)."
180                % cls.__name__)
181
182    # formset
183    if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
184        raise ImproperlyConfigured("'%s.formset' does not inherit from "
185                "BaseModelFormSet." % cls.__name__)
186
187    # exclude
188    if hasattr(cls, 'exclude') and cls.exclude:
189        if fk and fk.name in cls.exclude:
190            raise ImproperlyConfigured("%s cannot exclude the field "
191                    "'%s' - this is the foreign key to the parent model "
192                    "%s." % (cls.__name__, fk.name, parent_model.__name__))
193
194    if hasattr(cls, "readonly_fields"):
195        check_readonly_fields(cls, cls.model, cls.model._meta)
196
197def validate_base(cls, model):
198    opts = model._meta
199
200    # raw_id_fields
201    if hasattr(cls, 'raw_id_fields'):
202        check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
203        for idx, field in enumerate(cls.raw_id_fields):
204            f = get_field(cls, model, opts, 'raw_id_fields', field)
205            if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
206                raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
207                        "be either a ForeignKey or ManyToManyField."
208                        % (cls.__name__, idx, field))
209
210    # fields
211    if cls.fields: # default value is None
212        check_isseq(cls, 'fields', cls.fields)
213        for field in cls.fields:
214            if field in cls.readonly_fields:
215                # Stuff can be put in fields that isn't actually a model field
216                # if it's in readonly_fields, readonly_fields will handle the
217                # validation of such things.
218                continue
219            check_formfield(cls, model, opts, 'fields', field)
220            try:
221                f = opts.get_field(field)
222            except models.FieldDoesNotExist:
223                # If we can't find a field on the model that matches,
224                # it could be an extra field on the form.
225                continue
226            if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
227                raise ImproperlyConfigured("'%s.fields' can't include the ManyToManyField "
228                    "field '%s' because '%s' manually specifies "
229                    "a 'through' model." % (cls.__name__, field, field))
230        if cls.fieldsets:
231            raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
232        if len(cls.fields) > len(set(cls.fields)):
233            raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
234
235    # fieldsets
236    if cls.fieldsets: # default value is None
237        check_isseq(cls, 'fieldsets', cls.fieldsets)
238        for idx, fieldset in enumerate(cls.fieldsets):
239            check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
240            if len(fieldset) != 2:
241                raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
242                        "have exactly two elements." % (cls.__name__, idx))
243            check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
244            if 'fields' not in fieldset[1]:
245                raise ImproperlyConfigured("'fields' key is required in "
246                        "%s.fieldsets[%d][1] field options dict."
247                        % (cls.__name__, idx))
248            for fields in fieldset[1]['fields']:
249                # The entry in fields might be a tuple. If it is a standalone
250                # field, make it into a tuple to make processing easier.
251                if type(fields) != tuple:
252                    fields = (fields,)
253                for field in fields:
254                    if field in cls.readonly_fields:
255                        # Stuff can be put in fields that isn't actually a
256                        # model field if it's in readonly_fields,
257                        # readonly_fields will handle the validation of such
258                        # things.
259                        continue
260                    if getattr(cls,'inlines') and field in [X.__name__ for X in cls.inlines]:
261                        # Inline validation skipped
262                        continue
263                    check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
264                    try:
265                        f = opts.get_field(field)
266                        if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
267                            raise ImproperlyConfigured("'%s.fieldsets[%d][1]['fields']' "
268                                "can't include the ManyToManyField field '%s' because "
269                                "'%s' manually specifies a 'through' model." % (
270                                    cls.__name__, idx, field, field))
271                    except models.FieldDoesNotExist:
272                        # If we can't find a field on the model that matches,
273                        # it could be an extra field on the form.
274                        pass
275        flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
276        if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
277            raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
278
279    # exclude
280    if cls.exclude: # default value is None
281        check_isseq(cls, 'exclude', cls.exclude)
282        for field in cls.exclude:
283            check_formfield(cls, model, opts, 'exclude', field)
284            try:
285                f = opts.get_field(field)
286            except models.FieldDoesNotExist:
287                # If we can't find a field on the model that matches,
288                # it could be an extra field on the form.
289                continue
290        if len(cls.exclude) > len(set(cls.exclude)):
291            raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
292
293    # form
294    if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
295        raise ImproperlyConfigured("%s.form does not inherit from "
296                "BaseModelForm." % cls.__name__)
297
298    # filter_vertical
299    if hasattr(cls, 'filter_vertical'):
300        check_isseq(cls, 'filter_vertical', cls.filter_vertical)
301        for idx, field in enumerate(cls.filter_vertical):
302            f = get_field(cls, model, opts, 'filter_vertical', field)
303            if not isinstance(f, models.ManyToManyField):
304                raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
305                    "a ManyToManyField." % (cls.__name__, idx))
306
307    # filter_horizontal
308    if hasattr(cls, 'filter_horizontal'):
309        check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
310        for idx, field in enumerate(cls.filter_horizontal):
311            f = get_field(cls, model, opts, 'filter_horizontal', field)
312            if not isinstance(f, models.ManyToManyField):
313                raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
314                    "a ManyToManyField." % (cls.__name__, idx))
315
316    # radio_fields
317    if hasattr(cls, 'radio_fields'):
318        check_isdict(cls, 'radio_fields', cls.radio_fields)
319        for field, val in cls.radio_fields.items():
320            f = get_field(cls, model, opts, 'radio_fields', field)
321            if not (isinstance(f, models.ForeignKey) or f.choices):
322                raise ImproperlyConfigured("'%s.radio_fields['%s']' "
323                        "is neither an instance of ForeignKey nor does "
324                        "have choices set." % (cls.__name__, field))
325            if not val in (HORIZONTAL, VERTICAL):
326                raise ImproperlyConfigured("'%s.radio_fields['%s']' "
327                        "is neither admin.HORIZONTAL nor admin.VERTICAL."
328                        % (cls.__name__, field))
329
330    # prepopulated_fields
331    if hasattr(cls, 'prepopulated_fields'):
332        check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
333        for field, val in cls.prepopulated_fields.items():
334            f = get_field(cls, model, opts, 'prepopulated_fields', field)
335            if isinstance(f, (models.DateTimeField, models.ForeignKey,
336                models.ManyToManyField)):
337                raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
338                        "is either a DateTimeField, ForeignKey or "
339                        "ManyToManyField. This isn't allowed."
340                        % (cls.__name__, field))
341            check_isseq(cls, "prepopulated_fields['%s']" % field, val)
342            for idx, f in enumerate(val):
343                get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
344
345def check_isseq(cls, label, obj):
346    if not isinstance(obj, (list, tuple)):
347        raise ImproperlyConfigured("'%s.%s' must be a list or tuple." % (cls.__name__, label))
348
349def check_isdict(cls, label, obj):
350    if not isinstance(obj, dict):
351        raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
352
353def get_field(cls, model, opts, label, field):
354    try:
355        return opts.get_field(field)
356    except models.FieldDoesNotExist:
357        raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s'."
358                % (cls.__name__, label, field, model.__name__))
359
360def check_formfield(cls, model, opts, label, field):
361    if getattr(cls.form, 'base_fields', None):
362        try:
363            cls.form.base_fields[field]
364        except KeyError:
365            raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
366                "is missing from the form." % (cls.__name__, label, field))
367    else:
368        fields = fields_for_model(model)
369        try:
370            fields[field]
371        except KeyError:
372            raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
373                "is missing from the form." % (cls.__name__, label, field))
374
375def fetch_attr(cls, model, opts, label, field):
376    try:
377        return opts.get_field(field)
378    except models.FieldDoesNotExist:
379        pass
380    try:
381        return getattr(model, field)
382    except AttributeError:
383        raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s'."
384            % (cls.__name__, label, field, model.__name__))
385
386def check_readonly_fields(cls, model, opts):
387    check_isseq(cls, "readonly_fields", cls.readonly_fields)
388    for idx, field in enumerate(cls.readonly_fields):
389        if not callable(field):
390            if not hasattr(cls, field):
391                if not hasattr(model, field):
392                    try:
393                        opts.get_field(field)
394                    except models.FieldDoesNotExist:
395                        raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
396                            % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))