root / env / lib / python2.7 / site-packages / django / contrib / admin / validation.py @ 1a305335
History | View | Annotate | Download (20.1 KB)
1 |
from django.core.exceptions import ImproperlyConfigured |
---|---|
2 |
from django.db import models |
3 |
from django.db.models.fields import FieldDoesNotExist |
4 |
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, |
5 |
_get_foreign_key) |
6 |
from django.contrib.admin import ListFilter, FieldListFilter |
7 |
from django.contrib.admin.util import get_fields_from_path, NotRelationField |
8 |
from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, |
9 |
HORIZONTAL, VERTICAL) |
10 |
|
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 |
if field not in cls.list_display: |
51 |
raise ImproperlyConfigured("'%s.list_display_links[%d]' " |
52 |
"refers to '%s' which is not defined in 'list_display'."
|
53 |
% (cls.__name__, idx, field)) |
54 |
|
55 |
# list_filter
|
56 |
if hasattr(cls, 'list_filter'): |
57 |
check_isseq(cls, 'list_filter', cls.list_filter)
|
58 |
for idx, item in enumerate(cls.list_filter): |
59 |
# There are three options for specifying a filter:
|
60 |
# 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
|
61 |
# 2: ('field', SomeFieldListFilter) - a field-based list filter class
|
62 |
# 3: SomeListFilter - a non-field list filter class
|
63 |
if callable(item) and not isinstance(item, models.Field): |
64 |
# If item is option 3, it should be a ListFilter...
|
65 |
if not issubclass(item, ListFilter): |
66 |
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" |
67 |
" which is not a descendant of ListFilter."
|
68 |
% (cls.__name__, idx, item.__name__)) |
69 |
# ... but not a FieldListFilter.
|
70 |
if issubclass(item, FieldListFilter): |
71 |
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" |
72 |
" which is of type FieldListFilter but is not"
|
73 |
" associated with a field name."
|
74 |
% (cls.__name__, idx, item.__name__)) |
75 |
else:
|
76 |
if isinstance(item, (tuple, list)): |
77 |
# item is option #2
|
78 |
field, list_filter_class = item |
79 |
if not issubclass(list_filter_class, FieldListFilter): |
80 |
raise ImproperlyConfigured("'%s.list_filter[%d][1]'" |
81 |
" is '%s' which is not of type FieldListFilter."
|
82 |
% (cls.__name__, idx, list_filter_class.__name__)) |
83 |
else:
|
84 |
# item is option #1
|
85 |
field = item |
86 |
# Validate the field string
|
87 |
try:
|
88 |
get_fields_from_path(model, field) |
89 |
except (NotRelationField, FieldDoesNotExist):
|
90 |
raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" |
91 |
" which does not refer to a Field."
|
92 |
% (cls.__name__, idx, field)) |
93 |
|
94 |
# list_per_page = 100
|
95 |
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): |
96 |
raise ImproperlyConfigured("'%s.list_per_page' should be a integer." |
97 |
% cls.__name__) |
98 |
|
99 |
# list_max_show_all
|
100 |
if hasattr(cls, 'list_max_show_all') and not isinstance(cls.list_max_show_all, int): |
101 |
raise ImproperlyConfigured("'%s.list_max_show_all' should be an integer." |
102 |
% cls.__name__) |
103 |
|
104 |
# list_editable
|
105 |
if hasattr(cls, 'list_editable') and cls.list_editable: |
106 |
check_isseq(cls, 'list_editable', cls.list_editable)
|
107 |
for idx, field_name in enumerate(cls.list_editable): |
108 |
try:
|
109 |
field = opts.get_field_by_name(field_name)[0]
|
110 |
except models.FieldDoesNotExist:
|
111 |
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a " |
112 |
"field, '%s', not defined on %s.%s."
|
113 |
% (cls.__name__, idx, field_name, model._meta.app_label, model.__name__)) |
114 |
if field_name not in cls.list_display: |
115 |
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to " |
116 |
"'%s' which is not defined in 'list_display'."
|
117 |
% (cls.__name__, idx, field_name)) |
118 |
if field_name in cls.list_display_links: |
119 |
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'" |
120 |
" and '%s.list_display_links'"
|
121 |
% (field_name, cls.__name__, cls.__name__)) |
122 |
if not cls.list_display_links and cls.list_display[0] in cls.list_editable: |
123 |
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to" |
124 |
" the first field in list_display, '%s', which can't be"
|
125 |
" used unless list_display_links is set."
|
126 |
% (cls.__name__, idx, cls.list_display[0]))
|
127 |
if not field.editable: |
128 |
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a " |
129 |
"field, '%s', which isn't editable through the admin."
|
130 |
% (cls.__name__, idx, field_name)) |
131 |
|
132 |
# search_fields = ()
|
133 |
if hasattr(cls, 'search_fields'): |
134 |
check_isseq(cls, 'search_fields', cls.search_fields)
|
135 |
|
136 |
# date_hierarchy = None
|
137 |
if cls.date_hierarchy:
|
138 |
f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
|
139 |
if not isinstance(f, (models.DateField, models.DateTimeField)): |
140 |
raise ImproperlyConfigured("'%s.date_hierarchy is " |
141 |
"neither an instance of DateField nor DateTimeField."
|
142 |
% cls.__name__) |
143 |
|
144 |
# ordering = None
|
145 |
if cls.ordering:
|
146 |
check_isseq(cls, 'ordering', cls.ordering)
|
147 |
for idx, field in enumerate(cls.ordering): |
148 |
if field == '?' and len(cls.ordering) != 1: |
149 |
raise ImproperlyConfigured("'%s.ordering' has the random " |
150 |
"ordering marker '?', but contains other fields as "
|
151 |
"well. Please either remove '?' or the other fields."
|
152 |
% cls.__name__) |
153 |
if field == '?': |
154 |
continue
|
155 |
if field.startswith('-'): |
156 |
field = field[1:]
|
157 |
# Skip ordering in the format field1__field2 (FIXME: checking
|
158 |
# this format would be nice, but it's a little fiddly).
|
159 |
if '__' in field: |
160 |
continue
|
161 |
get_field(cls, model, opts, 'ordering[%d]' % idx, field)
|
162 |
|
163 |
if hasattr(cls, "readonly_fields"): |
164 |
check_readonly_fields(cls, model, opts) |
165 |
|
166 |
# list_select_related = False
|
167 |
# save_as = False
|
168 |
# save_on_top = False
|
169 |
for attr in ('list_select_related', 'save_as', 'save_on_top'): |
170 |
if not isinstance(getattr(cls, attr), bool): |
171 |
raise ImproperlyConfigured("'%s.%s' should be a boolean." |
172 |
% (cls.__name__, attr)) |
173 |
|
174 |
|
175 |
# inlines = []
|
176 |
if hasattr(cls, 'inlines'): |
177 |
check_isseq(cls, 'inlines', cls.inlines)
|
178 |
for idx, inline in enumerate(cls.inlines): |
179 |
if not issubclass(inline, BaseModelAdmin): |
180 |
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit " |
181 |
"from BaseModelAdmin." % (cls.__name__, idx))
|
182 |
if not inline.model: |
183 |
raise ImproperlyConfigured("'model' is a required attribute " |
184 |
"of '%s.inlines[%d]'." % (cls.__name__, idx))
|
185 |
if not issubclass(inline.model, models.Model): |
186 |
raise ImproperlyConfigured("'%s.inlines[%d].model' does not " |
187 |
"inherit from models.Model." % (cls.__name__, idx))
|
188 |
validate_base(inline, inline.model) |
189 |
validate_inline(inline, cls, model) |
190 |
|
191 |
def validate_inline(cls, parent, parent_model): |
192 |
|
193 |
# model is already verified to exist and be a Model
|
194 |
if cls.fk_name: # default value is None |
195 |
f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
|
196 |
if not isinstance(f, models.ForeignKey): |
197 |
raise ImproperlyConfigured("'%s.fk_name is not an instance of " |
198 |
"models.ForeignKey." % cls.__name__)
|
199 |
|
200 |
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
|
201 |
|
202 |
# extra = 3
|
203 |
if not isinstance(cls.extra, int): |
204 |
raise ImproperlyConfigured("'%s.extra' should be a integer." |
205 |
% cls.__name__) |
206 |
|
207 |
# max_num = None
|
208 |
max_num = getattr(cls, 'max_num', None) |
209 |
if max_num is not None and not isinstance(max_num, int): |
210 |
raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)." |
211 |
% cls.__name__) |
212 |
|
213 |
# formset
|
214 |
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet): |
215 |
raise ImproperlyConfigured("'%s.formset' does not inherit from " |
216 |
"BaseModelFormSet." % cls.__name__)
|
217 |
|
218 |
# exclude
|
219 |
if hasattr(cls, 'exclude') and cls.exclude: |
220 |
if fk and fk.name in cls.exclude: |
221 |
raise ImproperlyConfigured("%s cannot exclude the field " |
222 |
"'%s' - this is the foreign key to the parent model "
|
223 |
"%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
|
224 |
|
225 |
if hasattr(cls, "readonly_fields"): |
226 |
check_readonly_fields(cls, cls.model, cls.model._meta) |
227 |
|
228 |
def validate_fields_spec(cls, model, opts, flds, label): |
229 |
"""
|
230 |
Validate the fields specification in `flds` from a ModelAdmin subclass
|
231 |
`cls` for the `model` model. `opts` is `model`'s Meta inner class.
|
232 |
Use `label` for reporting problems to the user.
|
233 |
|
234 |
The fields specification can be a ``fields`` option or a ``fields``
|
235 |
sub-option from a ``fieldsets`` option component.
|
236 |
"""
|
237 |
for fields in flds: |
238 |
# The entry in fields might be a tuple. If it is a standalone
|
239 |
# field, make it into a tuple to make processing easier.
|
240 |
if type(fields) != tuple: |
241 |
fields = (fields,) |
242 |
for field in fields: |
243 |
if field in cls.readonly_fields: |
244 |
# Stuff can be put in fields that isn't actually a
|
245 |
# model field if it's in readonly_fields,
|
246 |
# readonly_fields will handle the validation of such
|
247 |
# things.
|
248 |
continue
|
249 |
check_formfield(cls, model, opts, label, field) |
250 |
try:
|
251 |
f = opts.get_field(field) |
252 |
except models.FieldDoesNotExist:
|
253 |
# If we can't find a field on the model that matches, it could be an
|
254 |
# extra field on the form; nothing to check so move on to the next field.
|
255 |
continue
|
256 |
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created: |
257 |
raise ImproperlyConfigured("'%s.%s' " |
258 |
"can't include the ManyToManyField field '%s' because "
|
259 |
"'%s' manually specifies a 'through' model." % (
|
260 |
cls.__name__, label, field, field)) |
261 |
|
262 |
def validate_base(cls, model): |
263 |
opts = model._meta |
264 |
|
265 |
# raw_id_fields
|
266 |
if hasattr(cls, 'raw_id_fields'): |
267 |
check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
|
268 |
for idx, field in enumerate(cls.raw_id_fields): |
269 |
f = get_field(cls, model, opts, 'raw_id_fields', field)
|
270 |
if not isinstance(f, (models.ForeignKey, models.ManyToManyField)): |
271 |
raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must " |
272 |
"be either a ForeignKey or ManyToManyField."
|
273 |
% (cls.__name__, idx, field)) |
274 |
|
275 |
# fields
|
276 |
if cls.fields: # default value is None |
277 |
check_isseq(cls, 'fields', cls.fields)
|
278 |
validate_fields_spec(cls, model, opts, cls.fields, 'fields')
|
279 |
if cls.fieldsets:
|
280 |
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__) |
281 |
if len(cls.fields) > len(set(cls.fields)): |
282 |
raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__) |
283 |
|
284 |
# fieldsets
|
285 |
if cls.fieldsets: # default value is None |
286 |
check_isseq(cls, 'fieldsets', cls.fieldsets)
|
287 |
for idx, fieldset in enumerate(cls.fieldsets): |
288 |
check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
|
289 |
if len(fieldset) != 2: |
290 |
raise ImproperlyConfigured("'%s.fieldsets[%d]' does not " |
291 |
"have exactly two elements." % (cls.__name__, idx))
|
292 |
check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1]) |
293 |
if 'fields' not in fieldset[1]: |
294 |
raise ImproperlyConfigured("'fields' key is required in " |
295 |
"%s.fieldsets[%d][1] field options dict."
|
296 |
% (cls.__name__, idx)) |
297 |
validate_fields_spec(cls, model, opts, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx) |
298 |
flattened_fieldsets = flatten_fieldsets(cls.fieldsets) |
299 |
if len(flattened_fieldsets) > len(set(flattened_fieldsets)): |
300 |
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__) |
301 |
|
302 |
# exclude
|
303 |
if cls.exclude: # default value is None |
304 |
check_isseq(cls, 'exclude', cls.exclude)
|
305 |
for field in cls.exclude: |
306 |
check_formfield(cls, model, opts, 'exclude', field)
|
307 |
try:
|
308 |
f = opts.get_field(field) |
309 |
except models.FieldDoesNotExist:
|
310 |
# If we can't find a field on the model that matches,
|
311 |
# it could be an extra field on the form.
|
312 |
continue
|
313 |
if len(cls.exclude) > len(set(cls.exclude)): |
314 |
raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__) |
315 |
|
316 |
# form
|
317 |
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm): |
318 |
raise ImproperlyConfigured("%s.form does not inherit from " |
319 |
"BaseModelForm." % cls.__name__)
|
320 |
|
321 |
# filter_vertical
|
322 |
if hasattr(cls, 'filter_vertical'): |
323 |
check_isseq(cls, 'filter_vertical', cls.filter_vertical)
|
324 |
for idx, field in enumerate(cls.filter_vertical): |
325 |
f = get_field(cls, model, opts, 'filter_vertical', field)
|
326 |
if not isinstance(f, models.ManyToManyField): |
327 |
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be " |
328 |
"a ManyToManyField." % (cls.__name__, idx))
|
329 |
|
330 |
# filter_horizontal
|
331 |
if hasattr(cls, 'filter_horizontal'): |
332 |
check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
|
333 |
for idx, field in enumerate(cls.filter_horizontal): |
334 |
f = get_field(cls, model, opts, 'filter_horizontal', field)
|
335 |
if not isinstance(f, models.ManyToManyField): |
336 |
raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be " |
337 |
"a ManyToManyField." % (cls.__name__, idx))
|
338 |
|
339 |
# radio_fields
|
340 |
if hasattr(cls, 'radio_fields'): |
341 |
check_isdict(cls, 'radio_fields', cls.radio_fields)
|
342 |
for field, val in cls.radio_fields.items(): |
343 |
f = get_field(cls, model, opts, 'radio_fields', field)
|
344 |
if not (isinstance(f, models.ForeignKey) or f.choices): |
345 |
raise ImproperlyConfigured("'%s.radio_fields['%s']' " |
346 |
"is neither an instance of ForeignKey nor does "
|
347 |
"have choices set." % (cls.__name__, field))
|
348 |
if not val in (HORIZONTAL, VERTICAL): |
349 |
raise ImproperlyConfigured("'%s.radio_fields['%s']' " |
350 |
"is neither admin.HORIZONTAL nor admin.VERTICAL."
|
351 |
% (cls.__name__, field)) |
352 |
|
353 |
# prepopulated_fields
|
354 |
if hasattr(cls, 'prepopulated_fields'): |
355 |
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
|
356 |
for field, val in cls.prepopulated_fields.items(): |
357 |
f = get_field(cls, model, opts, 'prepopulated_fields', field)
|
358 |
if isinstance(f, (models.DateTimeField, models.ForeignKey, |
359 |
models.ManyToManyField)): |
360 |
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' " |
361 |
"is either a DateTimeField, ForeignKey or "
|
362 |
"ManyToManyField. This isn't allowed."
|
363 |
% (cls.__name__, field)) |
364 |
check_isseq(cls, "prepopulated_fields['%s']" % field, val)
|
365 |
for idx, f in enumerate(val): |
366 |
get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
|
367 |
|
368 |
def check_isseq(cls, label, obj): |
369 |
if not isinstance(obj, (list, tuple)): |
370 |
raise ImproperlyConfigured("'%s.%s' must be a list or tuple." % (cls.__name__, label)) |
371 |
|
372 |
def check_isdict(cls, label, obj): |
373 |
if not isinstance(obj, dict): |
374 |
raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label)) |
375 |
|
376 |
def get_field(cls, model, opts, label, field): |
377 |
try:
|
378 |
return opts.get_field(field)
|
379 |
except models.FieldDoesNotExist:
|
380 |
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s.%s'." |
381 |
% (cls.__name__, label, field, model._meta.app_label, model.__name__)) |
382 |
|
383 |
def check_formfield(cls, model, opts, label, field): |
384 |
if getattr(cls.form, 'base_fields', None): |
385 |
try:
|
386 |
cls.form.base_fields[field] |
387 |
except KeyError: |
388 |
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that " |
389 |
"is missing from the form." % (cls.__name__, label, field))
|
390 |
else:
|
391 |
fields = fields_for_model(model) |
392 |
try:
|
393 |
fields[field] |
394 |
except KeyError: |
395 |
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that " |
396 |
"is missing from the form." % (cls.__name__, label, field))
|
397 |
|
398 |
def fetch_attr(cls, model, opts, label, field): |
399 |
try:
|
400 |
return opts.get_field(field)
|
401 |
except models.FieldDoesNotExist:
|
402 |
pass
|
403 |
try:
|
404 |
return getattr(model, field) |
405 |
except AttributeError: |
406 |
raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s.%s'." |
407 |
% (cls.__name__, label, field, model._meta.app_label, model.__name__)) |
408 |
|
409 |
def check_readonly_fields(cls, model, opts): |
410 |
check_isseq(cls, "readonly_fields", cls.readonly_fields)
|
411 |
for idx, field in enumerate(cls.readonly_fields): |
412 |
if not callable(field): |
413 |
if not hasattr(cls, field): |
414 |
if not hasattr(model, field): |
415 |
try:
|
416 |
opts.get_field(field) |
417 |
except models.FieldDoesNotExist:
|
418 |
raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r." |
419 |
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) |