1 | Index: django/core/management/validation.py
|
---|
2 | ===================================================================
|
---|
3 | --- django/core/management/validation.py (revision 7884)
|
---|
4 | +++ django/core/management/validation.py (working copy)
|
---|
5 | @@ -143,59 +143,6 @@
|
---|
6 | if r.get_accessor_name() == rel_query_name:
|
---|
7 | e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
|
---|
8 |
|
---|
9 | - # Check admin attribute.
|
---|
10 | - if opts.admin is not None:
|
---|
11 | - # prepopulated_fields
|
---|
12 | - if not isinstance(opts.admin.prepopulated_fields, dict):
|
---|
13 | - e.add(opts, '"%s": prepopulated_fields should be a dictionary.' % f.name)
|
---|
14 | - else:
|
---|
15 | - for field_name, field_list in opts.admin.prepopulated_fields.items():
|
---|
16 | - if not isinstance(field_list, (list, tuple)):
|
---|
17 | - e.add(opts, '"%s": prepopulated_fields "%s" value should be a list or tuple.' % (f.name, field_name))
|
---|
18 | -
|
---|
19 | - # list_display
|
---|
20 | - if not isinstance(opts.admin.list_display, (list, tuple)):
|
---|
21 | - e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
|
---|
22 | - else:
|
---|
23 | - for fn in opts.admin.list_display:
|
---|
24 | - try:
|
---|
25 | - f = opts.get_field(fn)
|
---|
26 | - except models.FieldDoesNotExist:
|
---|
27 | - if not hasattr(cls, fn):
|
---|
28 | - e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
|
---|
29 | - else:
|
---|
30 | - if isinstance(f, models.ManyToManyField):
|
---|
31 | - e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
|
---|
32 | - # list_display_links
|
---|
33 | - if opts.admin.list_display_links and not opts.admin.list_display:
|
---|
34 | - e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.')
|
---|
35 | - if not isinstance(opts.admin.list_display_links, (list, tuple)):
|
---|
36 | - e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.')
|
---|
37 | - else:
|
---|
38 | - for fn in opts.admin.list_display_links:
|
---|
39 | - try:
|
---|
40 | - f = opts.get_field(fn)
|
---|
41 | - except models.FieldDoesNotExist:
|
---|
42 | - if not hasattr(cls, fn):
|
---|
43 | - e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
|
---|
44 | - if fn not in opts.admin.list_display:
|
---|
45 | - e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
|
---|
46 | - # list_filter
|
---|
47 | - if not isinstance(opts.admin.list_filter, (list, tuple)):
|
---|
48 | - e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.')
|
---|
49 | - else:
|
---|
50 | - for fn in opts.admin.list_filter:
|
---|
51 | - try:
|
---|
52 | - f = opts.get_field(fn)
|
---|
53 | - except models.FieldDoesNotExist:
|
---|
54 | - e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
|
---|
55 | - # date_hierarchy
|
---|
56 | - if opts.admin.date_hierarchy:
|
---|
57 | - try:
|
---|
58 | - f = opts.get_field(opts.admin.date_hierarchy)
|
---|
59 | - except models.FieldDoesNotExist:
|
---|
60 | - e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy)
|
---|
61 | -
|
---|
62 | # Check ordering attribute.
|
---|
63 | if opts.ordering:
|
---|
64 | for field_name in opts.ordering:
|
---|
65 | @@ -213,18 +160,6 @@
|
---|
66 | except models.FieldDoesNotExist:
|
---|
67 | e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
|
---|
68 |
|
---|
69 | - # Check core=True, if needed.
|
---|
70 | - for related in opts.get_followed_related_objects():
|
---|
71 | - if not related.edit_inline:
|
---|
72 | - continue
|
---|
73 | - try:
|
---|
74 | - for f in related.opts.fields:
|
---|
75 | - if f.core:
|
---|
76 | - raise StopIteration
|
---|
77 | - e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
|
---|
78 | - except StopIteration:
|
---|
79 | - pass
|
---|
80 | -
|
---|
81 | # Check unique_together.
|
---|
82 | for ut in opts.unique_together:
|
---|
83 | for field_name in ut:
|
---|
84 | Index: django/contrib/admin/validation.py
|
---|
85 | ===================================================================
|
---|
86 | --- django/contrib/admin/validation.py (revision 0)
|
---|
87 | +++ django/contrib/admin/validation.py (revision 0)
|
---|
88 | @@ -0,0 +1,271 @@
|
---|
89 | +# Possible improvements: gather all errors and raise in the end instead of
|
---|
90 | +# bailing out on the first one (like core/management/validation.py does)
|
---|
91 | +
|
---|
92 | +from django.core.exceptions import ImproperlyConfigured
|
---|
93 | +from django.db import models
|
---|
94 | +from django import newforms as forms
|
---|
95 | +from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
|
---|
96 | +from django.contrib.admin.options import HORIZONTAL, VERTICAL
|
---|
97 | +
|
---|
98 | +def validate(cls, model):
|
---|
99 | + """
|
---|
100 | + Does basic ModelAdmin option validation. Calls custom validation
|
---|
101 | + classmethod in the end if it is provided in cls. The signature of the
|
---|
102 | + custom validation classmethod should be: def validate(cls, model).
|
---|
103 | + """
|
---|
104 | + opts = model._meta
|
---|
105 | + _validate_base(cls, model)
|
---|
106 | +
|
---|
107 | + # currying is expensive, use wrappers instead
|
---|
108 | + def _check_istuplew(label, obj):
|
---|
109 | + _check_istuple(cls, label, obj)
|
---|
110 | +
|
---|
111 | + def _check_isdictw(label, obj):
|
---|
112 | + _check_isdict(cls, label, obj)
|
---|
113 | +
|
---|
114 | + def _check_field_existsw(label, field):
|
---|
115 | + return _check_field_exists(cls, model, opts, label, field)
|
---|
116 | +
|
---|
117 | + def _check_attr_existsw(label, field):
|
---|
118 | + _check_attr_exists(cls, model, opts, label, field)
|
---|
119 | +
|
---|
120 | + # list_display
|
---|
121 | + if not cls.list_display:
|
---|
122 | + raise ImproperlyConfigured("%s.list_display can not be empty." %
|
---|
123 | + cls.__name__)
|
---|
124 | + _check_istuplew('list_display', cls.list_display)
|
---|
125 | + for idx, field in enumerate(cls.list_display):
|
---|
126 | + f = _check_attr_existsw("list_display['%d']" % idx, field)
|
---|
127 | + if isinstance(f, models.ManyToManyField):
|
---|
128 | + raise ImproperlyConfigured("`%s.list_display['%d']`, `%s` is a "
|
---|
129 | + "ManyToManyField which is not supported."
|
---|
130 | + % (cls.__name__, idx, field))
|
---|
131 | +
|
---|
132 | + # list_display_links
|
---|
133 | + if cls.list_display_links:
|
---|
134 | + _check_istuplew('list_display_links', cls.list_display_links)
|
---|
135 | + for idx, field in enumerate(cls.list_display_links):
|
---|
136 | + _check_attr_existsw('list_display_links[%d]' % idx, field)
|
---|
137 | + if field not in cls.list_display:
|
---|
138 | + raise ImproperlyConfigured("`%s.list_display_links['%d']`"
|
---|
139 | + "refers to `%s` which is not defined in `list_display`."
|
---|
140 | + % (cls.__name__, idx, field))
|
---|
141 | +
|
---|
142 | + # list_filter
|
---|
143 | + if cls.list_filter:
|
---|
144 | + _check_istuplew('list_filter', cls.list_filter)
|
---|
145 | + for idx, field in enumerate(cls.list_filter):
|
---|
146 | + _check_field_existsw('list_filter[%d]' % idx, field)
|
---|
147 | +
|
---|
148 | + # list_per_page = 100
|
---|
149 | + if not isinstance(cls.list_per_page, int):
|
---|
150 | + raise ImproperlyConfigured("`%s.list_per_page` should be a integer."
|
---|
151 | + % cls.__name__)
|
---|
152 | +
|
---|
153 | + # search_fields = ()
|
---|
154 | + if cls.search_fields:
|
---|
155 | + _check_istuplew('search_fields', cls.search_fields)
|
---|
156 | + # TODO: search field validation is quite complex (restrictions,
|
---|
157 | + # follow fields etc), will skip it as of now
|
---|
158 | + # for idx, field in enumerate(cls.search_fields):
|
---|
159 | + # _check_field_existsw('search_fields[%d]' % idx, field)
|
---|
160 | +
|
---|
161 | + # date_hierarchy = None
|
---|
162 | + if cls.date_hierarchy:
|
---|
163 | + f = _check_field_existsw('date_hierarchy', cls.date_hierarchy)
|
---|
164 | + if not isinstance(f, (models.DateField, models.DateTimeField)):
|
---|
165 | + raise ImproperlyConfigured("`%s.date_hierarchy is "
|
---|
166 | + "neither an instance of DateField nor DateTimeField."
|
---|
167 | + % cls.__name__)
|
---|
168 | +
|
---|
169 | + # ordering = None
|
---|
170 | + if cls.ordering:
|
---|
171 | + _check_istuplew('ordering', cls.ordering)
|
---|
172 | + for idx, field in enumerate(cls.ordering):
|
---|
173 | + if field == '?' and len(cls.ordering) != 1:
|
---|
174 | + raise ImproperlyConfigured("`%s.ordering` has the random "
|
---|
175 | + "ordering marker `?`, but contains other fields as "
|
---|
176 | + "well. Please either remove `?` or the other fields."
|
---|
177 | + % cls.__name__)
|
---|
178 | + if field.startswith('-'):
|
---|
179 | + field = field[1:]
|
---|
180 | + _check_field_existsw('ordering[%d]' % idx, field)
|
---|
181 | +
|
---|
182 | + # list_select_related = False
|
---|
183 | + # save_as = False
|
---|
184 | + # save_on_top = False
|
---|
185 | + for attr in ('list_select_related', 'save_as', 'save_on_top'):
|
---|
186 | + if not isinstance(getattr(cls, attr), bool):
|
---|
187 | + raise ImproperlyConfigured("`%s.%s` should be a boolean."
|
---|
188 | + % (cls.__name__, attr))
|
---|
189 | +
|
---|
190 | + # inlines = []
|
---|
191 | + if cls.inlines:
|
---|
192 | + _check_istuplew('inlines', cls.inlines)
|
---|
193 | + for idx, inline in enumerate(cls.inlines):
|
---|
194 | + if not issubclass(inline, BaseModelAdmin):
|
---|
195 | + raise ImproperlyConfigured("`%s.inlines[%d]` does not inherit "
|
---|
196 | + "from BaseModelAdmin." % (cls.__name__, idx))
|
---|
197 | + if not inline.model:
|
---|
198 | + raise ImproperlyConfigured("`model` is a required attribute "
|
---|
199 | + "of `%s.inlines[%d]`." % (cls.__name__, idx))
|
---|
200 | + if not issubclass(inline.model, models.Model):
|
---|
201 | + raise ImproperlyConfigured("`%s.inlines[%d].model` does not "
|
---|
202 | + "inherit from models.Model." % (cls.__name__, idx))
|
---|
203 | + _validate_base(inline, inline.model)
|
---|
204 | + _validate_inline(inline)
|
---|
205 | +
|
---|
206 | + # TODO: check that the templates exist if given
|
---|
207 | + # change_form_template = None
|
---|
208 | + # change_list_template = None
|
---|
209 | + # delete_confirmation_template = None
|
---|
210 | + # object_history_template = None
|
---|
211 | +
|
---|
212 | + # hook for custom validation
|
---|
213 | + _call_validation_hook(cls, model)
|
---|
214 | +
|
---|
215 | +def _validate_inline(cls):
|
---|
216 | + # model is already verified to exist and be a Model
|
---|
217 | + if cls.fk_name:
|
---|
218 | + f = _check_field_exists(cls, cls.model, cls.model._meta,
|
---|
219 | + 'fk_name', cls.fk_name)
|
---|
220 | + if not isinstance(f, models.ForeignKey):
|
---|
221 | + raise ImproperlyConfigured("`%s.fk_name is not an instance of "
|
---|
222 | + "models.ForeignKey." % cls.__name__)
|
---|
223 | + # extra = 3
|
---|
224 | + # max_num = 0
|
---|
225 | + for attr in ('extra', 'max_num'):
|
---|
226 | + if not isinstance(getattr(cls, attr), int):
|
---|
227 | + raise ImproperlyConfigured("`%s.%s` should be a integer."
|
---|
228 | + % (cls.__name__, attr))
|
---|
229 | +
|
---|
230 | + #TODO: check the following
|
---|
231 | + # formset = BaseInlineFormset
|
---|
232 | + # template = None
|
---|
233 | + # verbose_name = None
|
---|
234 | + # verbose_name_plural = None
|
---|
235 | +
|
---|
236 | + # hook for custom validation
|
---|
237 | + _call_validation_hook(cls, cls.model)
|
---|
238 | +
|
---|
239 | +def _validate_base(cls, model):
|
---|
240 | + opts = model._meta
|
---|
241 | + # currying is expensive, use wrappers instead
|
---|
242 | + def _check_istuplew(label, obj):
|
---|
243 | + _check_istuple(cls, label, obj)
|
---|
244 | +
|
---|
245 | + def _check_isdictw(label, obj):
|
---|
246 | + _check_isdict(cls, label, obj)
|
---|
247 | +
|
---|
248 | + def _check_field_existsw(label, field):
|
---|
249 | + return _check_field_exists(cls, model, opts, label, field)
|
---|
250 | +
|
---|
251 | + # raw_id_fields
|
---|
252 | + if cls.raw_id_fields:
|
---|
253 | + _check_istuplew('raw_id_fields', cls.raw_id_fields)
|
---|
254 | + for field in cls.raw_id_fields:
|
---|
255 | + _check_field_existsw('raw_id_fields', field)
|
---|
256 | +
|
---|
257 | + # fields
|
---|
258 | + if cls.fields:
|
---|
259 | + for field in cls.fields:
|
---|
260 | + _check_field_existsw('fields', field)
|
---|
261 | +
|
---|
262 | + # fieldsets
|
---|
263 | + if cls.fieldsets:
|
---|
264 | + _check_istuplew('fieldsets', cls.fieldsets)
|
---|
265 | + for idx, fieldset in enumerate(cls.fieldsets):
|
---|
266 | + _check_istuplew('fieldsets[%d]' % idx, fieldset)
|
---|
267 | + if len(fieldset) != 2:
|
---|
268 | + raise ImproperlyConfigured("`%s.fieldsets[%d]` does not "
|
---|
269 | + "have exactly two elements." % (cls.__name__, idx))
|
---|
270 | + _check_isdictw('fieldsets[%d][1]' % idx, fieldset[1])
|
---|
271 | + if 'fields' not in fieldset[1]:
|
---|
272 | + raise ImproperlyConfigured("`fields` key is required in "
|
---|
273 | + "%s.fieldsets[%d][1] field options dict."
|
---|
274 | + % (cls.__name__, idx))
|
---|
275 | + for field in flatten_fieldsets(cls.fieldsets):
|
---|
276 | + _check_field_existsw("fieldsets[%d][1]['fields']" % idx, field)
|
---|
277 | +
|
---|
278 | + # form FIXME: brosner, is this correct?
|
---|
279 | + # if cls.form and not isinstance(cls.form, forms.BaseForm):
|
---|
280 | + # raise ImproperlyConfigured("%s.form is not a form instance."
|
---|
281 | + # % cls.__name__)
|
---|
282 | +
|
---|
283 | + # filter_vertical
|
---|
284 | + if cls.filter_vertical:
|
---|
285 | + _check_istuplew('filter_vertical', cls.filter_vertical)
|
---|
286 | + for field in cls.filter_vertical:
|
---|
287 | + _check_field_existsw('filter_vertical', field)
|
---|
288 | +
|
---|
289 | + # filter_horizontal
|
---|
290 | + if cls.filter_horizontal:
|
---|
291 | + _check_istuplew('filter_horizontal', cls.filter_horizontal)
|
---|
292 | + for field in cls.filter_horizontal:
|
---|
293 | + _check_field_existsw('filter_horizontal', field)
|
---|
294 | +
|
---|
295 | + # radio_fields
|
---|
296 | + if cls.radio_fields:
|
---|
297 | + _check_isdictw('radio_fields', cls.radio_fields)
|
---|
298 | + for field, val in cls.radio_fields.items():
|
---|
299 | + f = _check_field_existsw('radio_fields', field)
|
---|
300 | + if not (isinstance(f, models.ForeignKey) or f.choices):
|
---|
301 | + raise ImproperlyConfigured("`%s.radio_fields['%s']` "
|
---|
302 | + "is neither an instance of ForeignKey nor does "
|
---|
303 | + "have choices set." % (cls.__name__, field))
|
---|
304 | + if not val in (HORIZONTAL, VERTICAL):
|
---|
305 | + raise ImproperlyConfigured("`%s.radio_fields['%s']` "
|
---|
306 | + "is neither admin.HORIZONTAL nor admin.VERTICAL."
|
---|
307 | + % (cls.__name__, field))
|
---|
308 | +
|
---|
309 | + # prepopulated_fields
|
---|
310 | + if cls.prepopulated_fields:
|
---|
311 | + _check_isdictw('prepopulated_fields', cls.prepopulated_fields)
|
---|
312 | + for field, val in cls.prepopulated_fields.items():
|
---|
313 | + f = _check_field_existsw('prepopulated_fields', field)
|
---|
314 | + if isinstance(f, (models.DateTimeField, models.ForeignKey,
|
---|
315 | + models.ManyToManyField)):
|
---|
316 | + raise ImproperlyConfigured("`%s.prepopulated_fields['%s']` "
|
---|
317 | + "is either a DateTimeField, ForeignKey or "
|
---|
318 | + "ManyToManyField. This isn't allowed."
|
---|
319 | + % (cls.__name__, field))
|
---|
320 | + _check_istuplew("prepopulated_fields['%s']" % field, val)
|
---|
321 | + for idx, f in enumerate(val):
|
---|
322 | + _check_field_existsw("prepopulated_fields['%s'][%d]"
|
---|
323 | + % (f, idx), f)
|
---|
324 | +
|
---|
325 | +def _call_validation_hook(cls, model):
|
---|
326 | + if hasattr(cls, 'validate'):
|
---|
327 | + if not callable(cls.validate):
|
---|
328 | + raise ImproperlyConfigured("`%s.validate` should be a callable "
|
---|
329 | + "(class method)." % cls.__name__)
|
---|
330 | + cls.validate(model)
|
---|
331 | +
|
---|
332 | +def _check_istuple(cls, label, obj):
|
---|
333 | + if not isinstance(obj, (list, tuple)):
|
---|
334 | + raise ImproperlyConfigured("`%s.%s` must be a "
|
---|
335 | + "list or tuple." % (cls.__name__, label))
|
---|
336 | +
|
---|
337 | +def _check_isdict(cls, label, obj):
|
---|
338 | + if not isinstance(obj, dict):
|
---|
339 | + raise ImproperlyConfigured("`%s.%s` must be a dictionary."
|
---|
340 | + % (cls.__name__, label))
|
---|
341 | +
|
---|
342 | +def _check_field_exists(cls, model, opts, label, field):
|
---|
343 | + try:
|
---|
344 | + return opts.get_field(field)
|
---|
345 | + except models.FieldDoesNotExist:
|
---|
346 | + raise ImproperlyConfigured("`%s.%s` refers to "
|
---|
347 | + "field `%s` that is missing from model `%s`."
|
---|
348 | + % (cls.__name__, label, field, model.__name__))
|
---|
349 | +
|
---|
350 | +def _check_attr_exists(cls, model, opts, label, field):
|
---|
351 | + try:
|
---|
352 | + return opts.get_field(field)
|
---|
353 | + except models.FieldDoesNotExist:
|
---|
354 | + if not hasattr(model, field):
|
---|
355 | + raise ImproperlyConfigured("`%s.%s` refers to "
|
---|
356 | + "`%s` that is neither a field, method or property "
|
---|
357 | + "of model `%s`."
|
---|
358 | + % (cls.__name__, label, field, model.__name__))
|
---|
359 | + return getattr(model, field)
|
---|
360 | Index: django/contrib/admin/sites.py
|
---|
361 | ===================================================================
|
---|
362 | --- django/contrib/admin/sites.py (revision 7884)
|
---|
363 | +++ django/contrib/admin/sites.py (working copy)
|
---|
364 | @@ -7,6 +7,7 @@
|
---|
365 | from django.utils.text import capfirst
|
---|
366 | from django.utils.translation import ugettext_lazy, ugettext as _
|
---|
367 | from django.views.decorators.cache import never_cache
|
---|
368 | +from django.conf import settings
|
---|
369 | import base64
|
---|
370 | import cPickle as pickle
|
---|
371 | import datetime
|
---|
372 | @@ -65,6 +66,10 @@
|
---|
373 |
|
---|
374 | If a model is already registered, this will raise AlreadyRegistered.
|
---|
375 | """
|
---|
376 | + do_validate = admin_class and settings.DEBUG
|
---|
377 | + if do_validate:
|
---|
378 | + # don't import the humongous validation code unless necessary
|
---|
379 | + from django.contrib.admin.validation import validate
|
---|
380 | admin_class = admin_class or ModelAdmin
|
---|
381 | # TODO: Handle options
|
---|
382 | if isinstance(model_or_iterable, ModelBase):
|
---|
383 | @@ -72,6 +77,8 @@
|
---|
384 | for model in model_or_iterable:
|
---|
385 | if model in self._registry:
|
---|
386 | raise AlreadyRegistered('The model %s is already registered' % model.__name__)
|
---|
387 | + if do_validate:
|
---|
388 | + validate(admin_class, model)
|
---|
389 | self._registry[model] = admin_class(model, self)
|
---|
390 |
|
---|
391 | def unregister(self, model_or_iterable):
|
---|