Hello site
[auf_framonde.git] / eggs / Django-1.4.5-py2.7.egg / django / contrib / admin / validation.py
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))