Django

Code

root/django/trunk/django/contrib/admin/validation.py

Revision 9002, 12.5 kB (checked in by adrian, 2 months ago)

Fixed #8957 -- Fixed incorrect error message when Admin prepopulated_fields refers to a field that does not exist. Thanks, charmless

Line 
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
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                         f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
40                         if isinstance(f, models.ManyToManyField):
41                             raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
42                                 % (cls.__name__, idx, field))
43
44     # list_display_links
45     if hasattr(cls, 'list_display_links'):
46         check_isseq(cls, 'list_display_links', cls.list_display_links)
47         for idx, field in enumerate(cls.list_display_links):
48             fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field)
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, field in enumerate(cls.list_filter):
58             get_field(cls, model, opts, 'list_filter[%d]' % idx, field)
59
60     # list_per_page = 100
61     if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
62         raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
63                 % cls.__name__)
64
65     # search_fields = ()
66     if hasattr(cls, 'search_fields'):
67         check_isseq(cls, 'search_fields', cls.search_fields)
68
69     # date_hierarchy = None
70     if cls.date_hierarchy:
71         f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
72         if not isinstance(f, (models.DateField, models.DateTimeField)):
73             raise ImproperlyConfigured("'%s.date_hierarchy is "
74                     "neither an instance of DateField nor DateTimeField."
75                     % cls.__name__)
76
77     # ordering = None
78     if cls.ordering:
79         check_isseq(cls, 'ordering', cls.ordering)
80         for idx, field in enumerate(cls.ordering):
81             if field == '?' and len(cls.ordering) != 1:
82                 raise ImproperlyConfigured("'%s.ordering' has the random "
83                         "ordering marker '?', but contains other fields as "
84                         "well. Please either remove '?' or the other fields."
85                         % cls.__name__)
86             if field == '?':
87                 continue
88             if field.startswith('-'):
89                 field = field[1:]
90             # Skip ordering in the format field1__field2 (FIXME: checking
91             # this format would be nice, but it's a little fiddly).
92             if '__' in field:
93                 continue
94             get_field(cls, model, opts, 'ordering[%d]' % idx, field)
95
96     # list_select_related = False
97     # save_as = False
98     # save_on_top = False
99     for attr in ('list_select_related', 'save_as', 'save_on_top'):
100         if not isinstance(getattr(cls, attr), bool):
101             raise ImproperlyConfigured("'%s.%s' should be a boolean."
102                     % (cls.__name__, attr))
103
104     # inlines = []
105     if hasattr(cls, 'inlines'):
106         check_isseq(cls, 'inlines', cls.inlines)
107         for idx, inline in enumerate(cls.inlines):
108             if not issubclass(inline, BaseModelAdmin):
109                 raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
110                         "from BaseModelAdmin." % (cls.__name__, idx))
111             if not inline.model:
112                 raise ImproperlyConfigured("'model' is a required attribute "
113                         "of '%s.inlines[%d]'." % (cls.__name__, idx))
114             if not issubclass(inline.model, models.Model):
115                 raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
116                         "inherit from models.Model." % (cls.__name__, idx))
117             validate_base(inline, inline.model)
118             validate_inline(inline)
119
120 def validate_inline(cls):
121     # model is already verified to exist and be a Model
122     if cls.fk_name: # default value is None
123         f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
124         if not isinstance(f, models.ForeignKey):
125             raise ImproperlyConfigured("'%s.fk_name is not an instance of "
126                     "models.ForeignKey." % cls.__name__)
127     # extra = 3
128     # max_num = 0
129     for attr in ('extra', 'max_num'):
130         if not isinstance(getattr(cls, attr), int):
131             raise ImproperlyConfigured("'%s.%s' should be a integer."
132                     % (cls.__name__, attr))
133
134     # formset
135     if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
136         raise ImproperlyConfigured("'%s.formset' does not inherit from "
137                 "BaseModelFormSet." % cls.__name__)
138
139 def validate_base(cls, model):
140     opts = model._meta
141
142     # raw_id_fields
143     if hasattr(cls, 'raw_id_fields'):
144         check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
145         for idx, field in enumerate(cls.raw_id_fields):
146             f = get_field(cls, model, opts, 'raw_id_fields', field)
147             if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
148                 raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
149                         "be either a ForeignKey or ManyToManyField."
150                         % (cls.__name__, idx, field))
151
152     # fields
153     if cls.fields: # default value is None
154         check_isseq(cls, 'fields', cls.fields)
155         for field in cls.fields:
156             check_formfield(cls, model, opts, 'fields', field)
157         if cls.fieldsets:
158             raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
159         if len(cls.fields) > len(set(cls.fields)):
160             raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
161
162     # fieldsets
163     if cls.fieldsets: # default value is None
164         check_isseq(cls, 'fieldsets', cls.fieldsets)
165         for idx, fieldset in enumerate(cls.fieldsets):
166             check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
167             if len(fieldset) != 2:
168                 raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
169                         "have exactly two elements." % (cls.__name__, idx))
170             check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
171             if 'fields' not in fieldset[1]:
172                 raise ImproperlyConfigured("'fields' key is required in "
173                         "%s.fieldsets[%d][1] field options dict."
174                         % (cls.__name__, idx))
175         flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
176         if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
177             raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
178         for field in flattened_fieldsets:
179             check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
180
181     # form
182     if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
183         raise ImproperlyConfigured("%s.form does not inherit from "
184                 "BaseModelForm." % cls.__name__)
185
186     # filter_vertical
187     if hasattr(cls, 'filter_vertical'):
188         check_isseq(cls, 'filter_vertical', cls.filter_vertical)
189         for idx, field in enumerate(cls.filter_vertical):
190             f = get_field(cls, model, opts, 'filter_vertical', field)
191             if not isinstance(f, models.ManyToManyField):
192                 raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
193                     "a ManyToManyField." % (cls.__name__, idx))
194
195     # filter_horizontal
196     if hasattr(cls, 'filter_horizontal'):
197         check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
198         for idx, field in enumerate(cls.filter_horizontal):
199             f = get_field(cls, model, opts, 'filter_horizontal', field)
200             if not isinstance(f, models.ManyToManyField):
201                 raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
202                     "a ManyToManyField." % (cls.__name__, idx))
203
204     # radio_fields
205     if hasattr(cls, 'radio_fields'):
206         check_isdict(cls, 'radio_fields', cls.radio_fields)
207         for field, val in cls.radio_fields.items():
208             f = get_field(cls, model, opts, 'radio_fields', field)
209             if not (isinstance(f, models.ForeignKey) or f.choices):
210                 raise ImproperlyConfigured("'%s.radio_fields['%s']' "
211                         "is neither an instance of ForeignKey nor does "
212                         "have choices set." % (cls.__name__, field))
213             if not val in (HORIZONTAL, VERTICAL):
214                 raise ImproperlyConfigured("'%s.radio_fields['%s']' "
215                         "is neither admin.HORIZONTAL nor admin.VERTICAL."
216                         % (cls.__name__, field))
217
218     # prepopulated_fields
219     if hasattr(cls, 'prepopulated_fields'):
220         check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
221         for field, val in cls.prepopulated_fields.items():
222             f = get_field(cls, model, opts, 'prepopulated_fields', field)
223             if isinstance(f, (models.DateTimeField, models.ForeignKey,
224                 models.ManyToManyField)):
225                 raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
226                         "is either a DateTimeField, ForeignKey or "
227                         "ManyToManyField. This isn't allowed."
228                         % (cls.__name__, field))
229             check_isseq(cls, "prepopulated_fields['%s']" % field, val)
230             for idx, f in enumerate(val):
231                 get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
232
233 def check_isseq(cls, label, obj):
234     if not isinstance(obj, (list, tuple)):
235         raise ImproperlyConfigured("'%s.%s' must be a list or tuple." % (cls.__name__, label))
236
237 def check_isdict(cls, label, obj):
238     if not isinstance(obj, dict):
239         raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
240
241 def get_field(cls, model, opts, label, field):
242     try:
243         return opts.get_field(field)
244     except models.FieldDoesNotExist:
245         raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s'."
246                 % (cls.__name__, label, field, model.__name__))
247
248 def check_formfield(cls, model, opts, label, field):
249     if getattr(cls.form, 'base_fields', None):
250         try:
251             cls.form.base_fields[field]
252         except KeyError:
253             raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
254                 "is missing from the form." % (cls.__name__, label, field))
255     else:
256         fields = fields_for_model(model)
257         try:
258             fields[field]
259         except KeyError:
260             raise ImproperlyConfigured("'%s.%s' refers to field '%s' that "
261                 "is missing from the form." % (cls.__name__, label, field))
262
263 def fetch_attr(cls, model, opts, label, field):
264     try:
265         return opts.get_field(field)
266     except models.FieldDoesNotExist:
267         pass
268     try:
269         return getattr(model, field)
270     except AttributeError:
271         raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s'."
272             % (cls.__name__, label, field, model.__name__))
Note: See TracBrowser for help on using the browser.