PageRenderTime 38ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/admin/validation.py

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