You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

974 lines
38 KiB

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from itertools import chain
  4. from django.contrib.admin.utils import (
  5. NotRelationField, flatten, get_fields_from_path,
  6. )
  7. from django.core import checks
  8. from django.core.exceptions import FieldDoesNotExist
  9. from django.db import models
  10. from django.forms.models import (
  11. BaseModelForm, BaseModelFormSet, _get_foreign_key,
  12. )
  13. def check_admin_app(**kwargs):
  14. from django.contrib.admin.sites import system_check_errors
  15. return system_check_errors
  16. class BaseModelAdminChecks(object):
  17. def check(self, admin_obj, **kwargs):
  18. errors = []
  19. errors.extend(self._check_raw_id_fields(admin_obj))
  20. errors.extend(self._check_fields(admin_obj))
  21. errors.extend(self._check_fieldsets(admin_obj))
  22. errors.extend(self._check_exclude(admin_obj))
  23. errors.extend(self._check_form(admin_obj))
  24. errors.extend(self._check_filter_vertical(admin_obj))
  25. errors.extend(self._check_filter_horizontal(admin_obj))
  26. errors.extend(self._check_radio_fields(admin_obj))
  27. errors.extend(self._check_prepopulated_fields(admin_obj))
  28. errors.extend(self._check_view_on_site_url(admin_obj))
  29. errors.extend(self._check_ordering(admin_obj))
  30. errors.extend(self._check_readonly_fields(admin_obj))
  31. return errors
  32. def _check_raw_id_fields(self, obj):
  33. """ Check that `raw_id_fields` only contains field names that are listed
  34. on the model. """
  35. if not isinstance(obj.raw_id_fields, (list, tuple)):
  36. return must_be('a list or tuple', option='raw_id_fields', obj=obj, id='admin.E001')
  37. else:
  38. return list(chain(*[
  39. self._check_raw_id_fields_item(obj, obj.model, field_name, 'raw_id_fields[%d]' % index)
  40. for index, field_name in enumerate(obj.raw_id_fields)
  41. ]))
  42. def _check_raw_id_fields_item(self, obj, model, field_name, label):
  43. """ Check an item of `raw_id_fields`, i.e. check that field named
  44. `field_name` exists in model `model` and is a ForeignKey or a
  45. ManyToManyField. """
  46. try:
  47. field = model._meta.get_field(field_name)
  48. except FieldDoesNotExist:
  49. return refer_to_missing_field(field=field_name, option=label,
  50. model=model, obj=obj, id='admin.E002')
  51. else:
  52. if not isinstance(field, (models.ForeignKey, models.ManyToManyField)):
  53. return must_be('a ForeignKey or ManyToManyField',
  54. option=label, obj=obj, id='admin.E003')
  55. else:
  56. return []
  57. def _check_fields(self, obj):
  58. """ Check that `fields` only refer to existing fields, doesn't contain
  59. duplicates. Check if at most one of `fields` and `fieldsets` is defined.
  60. """
  61. if obj.fields is None:
  62. return []
  63. elif not isinstance(obj.fields, (list, tuple)):
  64. return must_be('a list or tuple', option='fields', obj=obj, id='admin.E004')
  65. elif obj.fieldsets:
  66. return [
  67. checks.Error(
  68. "Both 'fieldsets' and 'fields' are specified.",
  69. hint=None,
  70. obj=obj.__class__,
  71. id='admin.E005',
  72. )
  73. ]
  74. fields = flatten(obj.fields)
  75. if len(fields) != len(set(fields)):
  76. return [
  77. checks.Error(
  78. "The value of 'fields' contains duplicate field(s).",
  79. hint=None,
  80. obj=obj.__class__,
  81. id='admin.E006',
  82. )
  83. ]
  84. return list(chain(*[
  85. self._check_field_spec(obj, obj.model, field_name, 'fields')
  86. for field_name in obj.fields
  87. ]))
  88. def _check_fieldsets(self, obj):
  89. """ Check that fieldsets is properly formatted and doesn't contain
  90. duplicates. """
  91. if obj.fieldsets is None:
  92. return []
  93. elif not isinstance(obj.fieldsets, (list, tuple)):
  94. return must_be('a list or tuple', option='fieldsets', obj=obj, id='admin.E007')
  95. else:
  96. return list(chain(*[
  97. self._check_fieldsets_item(obj, obj.model, fieldset, 'fieldsets[%d]' % index)
  98. for index, fieldset in enumerate(obj.fieldsets)
  99. ]))
  100. def _check_fieldsets_item(self, obj, model, fieldset, label):
  101. """ Check an item of `fieldsets`, i.e. check that this is a pair of a
  102. set name and a dictionary containing "fields" key. """
  103. if not isinstance(fieldset, (list, tuple)):
  104. return must_be('a list or tuple', option=label, obj=obj, id='admin.E008')
  105. elif len(fieldset) != 2:
  106. return must_be('of length 2', option=label, obj=obj, id='admin.E009')
  107. elif not isinstance(fieldset[1], dict):
  108. return must_be('a dictionary', option='%s[1]' % label, obj=obj, id='admin.E010')
  109. elif 'fields' not in fieldset[1]:
  110. return [
  111. checks.Error(
  112. "The value of '%s[1]' must contain the key 'fields'." % label,
  113. hint=None,
  114. obj=obj.__class__,
  115. id='admin.E011',
  116. )
  117. ]
  118. elif not isinstance(fieldset[1]['fields'], (list, tuple)):
  119. return must_be('a list or tuple', option="%s[1]['fields']" % label, obj=obj, id='admin.E008')
  120. fields = flatten(fieldset[1]['fields'])
  121. if len(fields) != len(set(fields)):
  122. return [
  123. checks.Error(
  124. "There are duplicate field(s) in '%s[1]'." % label,
  125. hint=None,
  126. obj=obj.__class__,
  127. id='admin.E012',
  128. )
  129. ]
  130. return list(chain(*[
  131. self._check_field_spec(obj, model, fieldset_fields, '%s[1]["fields"]' % label)
  132. for fieldset_fields in fieldset[1]['fields']
  133. ]))
  134. def _check_field_spec(self, obj, model, fields, label):
  135. """ `fields` should be an item of `fields` or an item of
  136. fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a
  137. field name or a tuple of field names. """
  138. if isinstance(fields, tuple):
  139. return list(chain(*[
  140. self._check_field_spec_item(obj, model, field_name, "%s[%d]" % (label, index))
  141. for index, field_name in enumerate(fields)
  142. ]))
  143. else:
  144. return self._check_field_spec_item(obj, model, fields, label)
  145. def _check_field_spec_item(self, obj, model, field_name, label):
  146. if field_name in obj.readonly_fields:
  147. # Stuff can be put in fields that isn't actually a model field if
  148. # it's in readonly_fields, readonly_fields will handle the
  149. # validation of such things.
  150. return []
  151. else:
  152. try:
  153. field = model._meta.get_field(field_name)
  154. except FieldDoesNotExist:
  155. # If we can't find a field on the model that matches, it could
  156. # be an extra field on the form.
  157. return []
  158. else:
  159. if (isinstance(field, models.ManyToManyField) and
  160. not field.remote_field.through._meta.auto_created):
  161. return [
  162. checks.Error(
  163. ("The value of '%s' cannot include the ManyToManyField '%s', "
  164. "because that field manually specifies a relationship model.")
  165. % (label, field_name),
  166. hint=None,
  167. obj=obj.__class__,
  168. id='admin.E013',
  169. )
  170. ]
  171. else:
  172. return []
  173. def _check_exclude(self, obj):
  174. """ Check that exclude is a sequence without duplicates. """
  175. if obj.exclude is None: # default value is None
  176. return []
  177. elif not isinstance(obj.exclude, (list, tuple)):
  178. return must_be('a list or tuple', option='exclude', obj=obj, id='admin.E014')
  179. elif len(obj.exclude) > len(set(obj.exclude)):
  180. return [
  181. checks.Error(
  182. "The value of 'exclude' contains duplicate field(s).",
  183. hint=None,
  184. obj=obj.__class__,
  185. id='admin.E015',
  186. )
  187. ]
  188. else:
  189. return []
  190. def _check_form(self, obj):
  191. """ Check that form subclasses BaseModelForm. """
  192. if hasattr(obj, 'form') and not issubclass(obj.form, BaseModelForm):
  193. return must_inherit_from(parent='BaseModelForm', option='form',
  194. obj=obj, id='admin.E016')
  195. else:
  196. return []
  197. def _check_filter_vertical(self, obj):
  198. """ Check that filter_vertical is a sequence of field names. """
  199. if not hasattr(obj, 'filter_vertical'):
  200. return []
  201. elif not isinstance(obj.filter_vertical, (list, tuple)):
  202. return must_be('a list or tuple', option='filter_vertical', obj=obj, id='admin.E017')
  203. else:
  204. return list(chain(*[
  205. self._check_filter_item(obj, obj.model, field_name, "filter_vertical[%d]" % index)
  206. for index, field_name in enumerate(obj.filter_vertical)
  207. ]))
  208. def _check_filter_horizontal(self, obj):
  209. """ Check that filter_horizontal is a sequence of field names. """
  210. if not hasattr(obj, 'filter_horizontal'):
  211. return []
  212. elif not isinstance(obj.filter_horizontal, (list, tuple)):
  213. return must_be('a list or tuple', option='filter_horizontal', obj=obj, id='admin.E018')
  214. else:
  215. return list(chain(*[
  216. self._check_filter_item(obj, obj.model, field_name, "filter_horizontal[%d]" % index)
  217. for index, field_name in enumerate(obj.filter_horizontal)
  218. ]))
  219. def _check_filter_item(self, obj, model, field_name, label):
  220. """ Check one item of `filter_vertical` or `filter_horizontal`, i.e.
  221. check that given field exists and is a ManyToManyField. """
  222. try:
  223. field = model._meta.get_field(field_name)
  224. except FieldDoesNotExist:
  225. return refer_to_missing_field(field=field_name, option=label,
  226. model=model, obj=obj, id='admin.E019')
  227. else:
  228. if not isinstance(field, models.ManyToManyField):
  229. return must_be('a ManyToManyField', option=label, obj=obj, id='admin.E020')
  230. else:
  231. return []
  232. def _check_radio_fields(self, obj):
  233. """ Check that `radio_fields` is a dictionary. """
  234. if not hasattr(obj, 'radio_fields'):
  235. return []
  236. elif not isinstance(obj.radio_fields, dict):
  237. return must_be('a dictionary', option='radio_fields', obj=obj, id='admin.E021')
  238. else:
  239. return list(chain(*[
  240. self._check_radio_fields_key(obj, obj.model, field_name, 'radio_fields') +
  241. self._check_radio_fields_value(obj, val, 'radio_fields["%s"]' % field_name)
  242. for field_name, val in obj.radio_fields.items()
  243. ]))
  244. def _check_radio_fields_key(self, obj, model, field_name, label):
  245. """ Check that a key of `radio_fields` dictionary is name of existing
  246. field and that the field is a ForeignKey or has `choices` defined. """
  247. try:
  248. field = model._meta.get_field(field_name)
  249. except FieldDoesNotExist:
  250. return refer_to_missing_field(field=field_name, option=label,
  251. model=model, obj=obj, id='admin.E022')
  252. else:
  253. if not (isinstance(field, models.ForeignKey) or field.choices):
  254. return [
  255. checks.Error(
  256. "The value of '%s' refers to '%s', which is not an "
  257. "instance of ForeignKey, and does not have a 'choices' definition." % (
  258. label, field_name
  259. ),
  260. hint=None,
  261. obj=obj.__class__,
  262. id='admin.E023',
  263. )
  264. ]
  265. else:
  266. return []
  267. def _check_radio_fields_value(self, obj, val, label):
  268. """ Check type of a value of `radio_fields` dictionary. """
  269. from django.contrib.admin.options import HORIZONTAL, VERTICAL
  270. if val not in (HORIZONTAL, VERTICAL):
  271. return [
  272. checks.Error(
  273. "The value of '%s' must be either admin.HORIZONTAL or admin.VERTICAL." % label,
  274. hint=None,
  275. obj=obj.__class__,
  276. id='admin.E024',
  277. )
  278. ]
  279. else:
  280. return []
  281. def _check_view_on_site_url(self, obj):
  282. if hasattr(obj, 'view_on_site'):
  283. if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool):
  284. return [
  285. checks.Error(
  286. "The value of 'view_on_site' must be a callable or a boolean value.",
  287. hint=None,
  288. obj=obj.__class__,
  289. id='admin.E025',
  290. )
  291. ]
  292. else:
  293. return []
  294. else:
  295. return []
  296. def _check_prepopulated_fields(self, obj):
  297. """ Check that `prepopulated_fields` is a dictionary containing allowed
  298. field types. """
  299. if not hasattr(obj, 'prepopulated_fields'):
  300. return []
  301. elif not isinstance(obj.prepopulated_fields, dict):
  302. return must_be('a dictionary', option='prepopulated_fields', obj=obj, id='admin.E026')
  303. else:
  304. return list(chain(*[
  305. self._check_prepopulated_fields_key(obj, obj.model, field_name, 'prepopulated_fields') +
  306. self._check_prepopulated_fields_value(obj, obj.model, val, 'prepopulated_fields["%s"]' % field_name)
  307. for field_name, val in obj.prepopulated_fields.items()
  308. ]))
  309. def _check_prepopulated_fields_key(self, obj, model, field_name, label):
  310. """ Check a key of `prepopulated_fields` dictionary, i.e. check that it
  311. is a name of existing field and the field is one of the allowed types.
  312. """
  313. forbidden_field_types = (
  314. models.DateTimeField,
  315. models.ForeignKey,
  316. models.ManyToManyField
  317. )
  318. try:
  319. field = model._meta.get_field(field_name)
  320. except FieldDoesNotExist:
  321. return refer_to_missing_field(field=field_name, option=label,
  322. model=model, obj=obj, id='admin.E027')
  323. else:
  324. if isinstance(field, forbidden_field_types):
  325. return [
  326. checks.Error(
  327. "The value of '%s' refers to '%s', which must not be a DateTimeField, "
  328. "ForeignKey or ManyToManyField." % (
  329. label, field_name
  330. ),
  331. hint=None,
  332. obj=obj.__class__,
  333. id='admin.E028',
  334. )
  335. ]
  336. else:
  337. return []
  338. def _check_prepopulated_fields_value(self, obj, model, val, label):
  339. """ Check a value of `prepopulated_fields` dictionary, i.e. it's an
  340. iterable of existing fields. """
  341. if not isinstance(val, (list, tuple)):
  342. return must_be('a list or tuple', option=label, obj=obj, id='admin.E029')
  343. else:
  344. return list(chain(*[
  345. self._check_prepopulated_fields_value_item(obj, model, subfield_name, "%s[%r]" % (label, index))
  346. for index, subfield_name in enumerate(val)
  347. ]))
  348. def _check_prepopulated_fields_value_item(self, obj, model, field_name, label):
  349. """ For `prepopulated_fields` equal to {"slug": ("title",)},
  350. `field_name` is "title". """
  351. try:
  352. model._meta.get_field(field_name)
  353. except FieldDoesNotExist:
  354. return refer_to_missing_field(field=field_name, option=label,
  355. model=model, obj=obj, id='admin.E030')
  356. else:
  357. return []
  358. def _check_ordering(self, obj):
  359. """ Check that ordering refers to existing fields or is random. """
  360. # ordering = None
  361. if obj.ordering is None: # The default value is None
  362. return []
  363. elif not isinstance(obj.ordering, (list, tuple)):
  364. return must_be('a list or tuple', option='ordering', obj=obj, id='admin.E031')
  365. else:
  366. return list(chain(*[
  367. self._check_ordering_item(obj, obj.model, field_name, 'ordering[%d]' % index)
  368. for index, field_name in enumerate(obj.ordering)
  369. ]))
  370. def _check_ordering_item(self, obj, model, field_name, label):
  371. """ Check that `ordering` refers to existing fields. """
  372. if field_name == '?' and len(obj.ordering) != 1:
  373. return [
  374. checks.Error(
  375. ("The value of 'ordering' has the random ordering marker '?', "
  376. "but contains other fields as well."),
  377. hint='Either remove the "?", or remove the other fields.',
  378. obj=obj.__class__,
  379. id='admin.E032',
  380. )
  381. ]
  382. elif field_name == '?':
  383. return []
  384. elif '__' in field_name:
  385. # Skip ordering in the format field1__field2 (FIXME: checking
  386. # this format would be nice, but it's a little fiddly).
  387. return []
  388. else:
  389. if field_name.startswith('-'):
  390. field_name = field_name[1:]
  391. try:
  392. model._meta.get_field(field_name)
  393. except FieldDoesNotExist:
  394. return refer_to_missing_field(field=field_name, option=label,
  395. model=model, obj=obj, id='admin.E033')
  396. else:
  397. return []
  398. def _check_readonly_fields(self, obj):
  399. """ Check that readonly_fields refers to proper attribute or field. """
  400. if obj.readonly_fields == ():
  401. return []
  402. elif not isinstance(obj.readonly_fields, (list, tuple)):
  403. return must_be('a list or tuple', option='readonly_fields', obj=obj, id='admin.E034')
  404. else:
  405. return list(chain(*[
  406. self._check_readonly_fields_item(obj, obj.model, field_name, "readonly_fields[%d]" % index)
  407. for index, field_name in enumerate(obj.readonly_fields)
  408. ]))
  409. def _check_readonly_fields_item(self, obj, model, field_name, label):
  410. if callable(field_name):
  411. return []
  412. elif hasattr(obj, field_name):
  413. return []
  414. elif hasattr(model, field_name):
  415. return []
  416. else:
  417. try:
  418. model._meta.get_field(field_name)
  419. except FieldDoesNotExist:
  420. return [
  421. checks.Error(
  422. "The value of '%s' is not a callable, an attribute of '%s', or an attribute of '%s.%s'." % (
  423. label, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
  424. ),
  425. hint=None,
  426. obj=obj.__class__,
  427. id='admin.E035',
  428. )
  429. ]
  430. else:
  431. return []
  432. class ModelAdminChecks(BaseModelAdminChecks):
  433. def check(self, admin_obj, **kwargs):
  434. errors = super(ModelAdminChecks, self).check(admin_obj)
  435. errors.extend(self._check_save_as(admin_obj))
  436. errors.extend(self._check_save_on_top(admin_obj))
  437. errors.extend(self._check_inlines(admin_obj))
  438. errors.extend(self._check_list_display(admin_obj))
  439. errors.extend(self._check_list_display_links(admin_obj))
  440. errors.extend(self._check_list_filter(admin_obj))
  441. errors.extend(self._check_list_select_related(admin_obj))
  442. errors.extend(self._check_list_per_page(admin_obj))
  443. errors.extend(self._check_list_max_show_all(admin_obj))
  444. errors.extend(self._check_list_editable(admin_obj))
  445. errors.extend(self._check_search_fields(admin_obj))
  446. errors.extend(self._check_date_hierarchy(admin_obj))
  447. return errors
  448. def _check_save_as(self, obj):
  449. """ Check save_as is a boolean. """
  450. if not isinstance(obj.save_as, bool):
  451. return must_be('a boolean', option='save_as',
  452. obj=obj, id='admin.E101')
  453. else:
  454. return []
  455. def _check_save_on_top(self, obj):
  456. """ Check save_on_top is a boolean. """
  457. if not isinstance(obj.save_on_top, bool):
  458. return must_be('a boolean', option='save_on_top',
  459. obj=obj, id='admin.E102')
  460. else:
  461. return []
  462. def _check_inlines(self, obj):
  463. """ Check all inline model admin classes. """
  464. if not isinstance(obj.inlines, (list, tuple)):
  465. return must_be('a list or tuple', option='inlines', obj=obj, id='admin.E103')
  466. else:
  467. return list(chain(*[
  468. self._check_inlines_item(obj, obj.model, item, "inlines[%d]" % index)
  469. for index, item in enumerate(obj.inlines)
  470. ]))
  471. def _check_inlines_item(self, obj, model, inline, label):
  472. """ Check one inline model admin. """
  473. inline_label = '.'.join([inline.__module__, inline.__name__])
  474. from django.contrib.admin.options import BaseModelAdmin
  475. if not issubclass(inline, BaseModelAdmin):
  476. return [
  477. checks.Error(
  478. "'%s' must inherit from 'BaseModelAdmin'." % inline_label,
  479. hint=None,
  480. obj=obj.__class__,
  481. id='admin.E104',
  482. )
  483. ]
  484. elif not inline.model:
  485. return [
  486. checks.Error(
  487. "'%s' must have a 'model' attribute." % inline_label,
  488. hint=None,
  489. obj=obj.__class__,
  490. id='admin.E105',
  491. )
  492. ]
  493. elif not issubclass(inline.model, models.Model):
  494. return must_be('a Model', option='%s.model' % inline_label,
  495. obj=obj, id='admin.E106')
  496. else:
  497. return inline(model, obj.admin_site).check()
  498. def _check_list_display(self, obj):
  499. """ Check that list_display only contains fields or usable attributes.
  500. """
  501. if not isinstance(obj.list_display, (list, tuple)):
  502. return must_be('a list or tuple', option='list_display', obj=obj, id='admin.E107')
  503. else:
  504. return list(chain(*[
  505. self._check_list_display_item(obj, obj.model, item, "list_display[%d]" % index)
  506. for index, item in enumerate(obj.list_display)
  507. ]))
  508. def _check_list_display_item(self, obj, model, item, label):
  509. if callable(item):
  510. return []
  511. elif hasattr(obj, item):
  512. return []
  513. elif hasattr(model, item):
  514. # getattr(model, item) could be an X_RelatedObjectsDescriptor
  515. try:
  516. field = model._meta.get_field(item)
  517. except FieldDoesNotExist:
  518. try:
  519. field = getattr(model, item)
  520. except AttributeError:
  521. field = None
  522. if field is None:
  523. return [
  524. checks.Error(
  525. "The value of '%s' refers to '%s', which is not a "
  526. "callable, an attribute of '%s', or an attribute or method on '%s.%s'." % (
  527. label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
  528. ),
  529. hint=None,
  530. obj=obj.__class__,
  531. id='admin.E108',
  532. )
  533. ]
  534. elif isinstance(field, models.ManyToManyField):
  535. return [
  536. checks.Error(
  537. "The value of '%s' must not be a ManyToManyField." % label,
  538. hint=None,
  539. obj=obj.__class__,
  540. id='admin.E109',
  541. )
  542. ]
  543. else:
  544. return []
  545. else:
  546. try:
  547. model._meta.get_field(item)
  548. except FieldDoesNotExist:
  549. return [
  550. # This is a deliberate repeat of E108; there's more than one path
  551. # required to test this condition.
  552. checks.Error(
  553. "The value of '%s' refers to '%s', which is not a callable, "
  554. "an attribute of '%s', or an attribute or method on '%s.%s'." % (
  555. label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
  556. ),
  557. hint=None,
  558. obj=obj.__class__,
  559. id='admin.E108',
  560. )
  561. ]
  562. else:
  563. return []
  564. def _check_list_display_links(self, obj):
  565. """ Check that list_display_links is a unique subset of list_display.
  566. """
  567. if obj.list_display_links is None:
  568. return []
  569. elif not isinstance(obj.list_display_links, (list, tuple)):
  570. return must_be('a list, a tuple, or None', option='list_display_links', obj=obj, id='admin.E110')
  571. else:
  572. return list(chain(*[
  573. self._check_list_display_links_item(obj, field_name, "list_display_links[%d]" % index)
  574. for index, field_name in enumerate(obj.list_display_links)
  575. ]))
  576. def _check_list_display_links_item(self, obj, field_name, label):
  577. if field_name not in obj.list_display:
  578. return [
  579. checks.Error(
  580. "The value of '%s' refers to '%s', which is not defined in 'list_display'." % (
  581. label, field_name
  582. ),
  583. hint=None,
  584. obj=obj.__class__,
  585. id='admin.E111',
  586. )
  587. ]
  588. else:
  589. return []
  590. def _check_list_filter(self, obj):
  591. if not isinstance(obj.list_filter, (list, tuple)):
  592. return must_be('a list or tuple', option='list_filter', obj=obj, id='admin.E112')
  593. else:
  594. return list(chain(*[
  595. self._check_list_filter_item(obj, obj.model, item, "list_filter[%d]" % index)
  596. for index, item in enumerate(obj.list_filter)
  597. ]))
  598. def _check_list_filter_item(self, obj, model, item, label):
  599. """
  600. Check one item of `list_filter`, i.e. check if it is one of three options:
  601. 1. 'field' -- a basic field filter, possibly w/ relationships (e.g.
  602. 'field__rel')
  603. 2. ('field', SomeFieldListFilter) - a field-based list filter class
  604. 3. SomeListFilter - a non-field list filter class
  605. """
  606. from django.contrib.admin import ListFilter, FieldListFilter
  607. if callable(item) and not isinstance(item, models.Field):
  608. # If item is option 3, it should be a ListFilter...
  609. if not issubclass(item, ListFilter):
  610. return must_inherit_from(parent='ListFilter', option=label,
  611. obj=obj, id='admin.E113')
  612. # ... but not a FieldListFilter.
  613. elif issubclass(item, FieldListFilter):
  614. return [
  615. checks.Error(
  616. "The value of '%s' must not inherit from 'FieldListFilter'." % label,
  617. hint=None,
  618. obj=obj.__class__,
  619. id='admin.E114',
  620. )
  621. ]
  622. else:
  623. return []
  624. elif isinstance(item, (tuple, list)):
  625. # item is option #2
  626. field, list_filter_class = item
  627. if not issubclass(list_filter_class, FieldListFilter):
  628. return must_inherit_from(parent='FieldListFilter', option='%s[1]' % label,
  629. obj=obj, id='admin.E115')
  630. else:
  631. return []
  632. else:
  633. # item is option #1
  634. field = item
  635. # Validate the field string
  636. try:
  637. get_fields_from_path(model, field)
  638. except (NotRelationField, FieldDoesNotExist):
  639. return [
  640. checks.Error(
  641. "The value of '%s' refers to '%s', which does not refer to a Field." % (label, field),
  642. hint=None,
  643. obj=obj.__class__,
  644. id='admin.E116',
  645. )
  646. ]
  647. else:
  648. return []
  649. def _check_list_select_related(self, obj):
  650. """ Check that list_select_related is a boolean, a list or a tuple. """
  651. if not isinstance(obj.list_select_related, (bool, list, tuple)):
  652. return must_be('a boolean, tuple or list', option='list_select_related',
  653. obj=obj, id='admin.E117')
  654. else:
  655. return []
  656. def _check_list_per_page(self, obj):
  657. """ Check that list_per_page is an integer. """
  658. if not isinstance(obj.list_per_page, int):
  659. return must_be('an integer', option='list_per_page', obj=obj, id='admin.E118')
  660. else:
  661. return []
  662. def _check_list_max_show_all(self, obj):
  663. """ Check that list_max_show_all is an integer. """
  664. if not isinstance(obj.list_max_show_all, int):
  665. return must_be('an integer', option='list_max_show_all', obj=obj, id='admin.E119')
  666. else:
  667. return []
  668. def _check_list_editable(self, obj):
  669. """ Check that list_editable is a sequence of editable fields from
  670. list_display without first element. """
  671. if not isinstance(obj.list_editable, (list, tuple)):
  672. return must_be('a list or tuple', option='list_editable', obj=obj, id='admin.E120')
  673. else:
  674. return list(chain(*[
  675. self._check_list_editable_item(obj, obj.model, item, "list_editable[%d]" % index)
  676. for index, item in enumerate(obj.list_editable)
  677. ]))
  678. def _check_list_editable_item(self, obj, model, field_name, label):
  679. try:
  680. field = model._meta.get_field(field_name)
  681. except FieldDoesNotExist:
  682. return refer_to_missing_field(field=field_name, option=label,
  683. model=model, obj=obj, id='admin.E121')
  684. else:
  685. if field_name not in obj.list_display:
  686. return [
  687. checks.Error(
  688. "The value of '%s' refers to '%s', which is not "
  689. "contained in 'list_display'." % (label, field_name),
  690. hint=None,
  691. obj=obj.__class__,
  692. id='admin.E122',
  693. )
  694. ]
  695. elif obj.list_display_links and field_name in obj.list_display_links:
  696. return [
  697. checks.Error(
  698. "The value of '%s' cannot be in both 'list_editable' and 'list_display_links'." % field_name,
  699. hint=None,
  700. obj=obj.__class__,
  701. id='admin.E123',
  702. )
  703. ]
  704. # Check that list_display_links is set, and that the first values of list_editable and list_display are
  705. # not the same. See ticket #22792 for the use case relating to this.
  706. elif (obj.list_display[0] in obj.list_editable and obj.list_display[0] != obj.list_editable[0] and
  707. obj.list_display_links is not None):
  708. return [
  709. checks.Error(
  710. "The value of '%s' refers to the first field in 'list_display' ('%s'), "
  711. "which cannot be used unless 'list_display_links' is set." % (
  712. label, obj.list_display[0]
  713. ),
  714. hint=None,
  715. obj=obj.__class__,
  716. id='admin.E124',
  717. )
  718. ]
  719. elif not field.editable:
  720. return [
  721. checks.Error(
  722. "The value of '%s' refers to '%s', which is not editable through the admin." % (
  723. label, field_name
  724. ),
  725. hint=None,
  726. obj=obj.__class__,
  727. id='admin.E125',
  728. )
  729. ]
  730. else:
  731. return []
  732. def _check_search_fields(self, obj):
  733. """ Check search_fields is a sequence. """
  734. if not isinstance(obj.search_fields, (list, tuple)):
  735. return must_be('a list or tuple', option='search_fields', obj=obj, id='admin.E126')
  736. else:
  737. return []
  738. def _check_date_hierarchy(self, obj):
  739. """ Check that date_hierarchy refers to DateField or DateTimeField. """
  740. if obj.date_hierarchy is None:
  741. return []
  742. else:
  743. try:
  744. field = obj.model._meta.get_field(obj.date_hierarchy)
  745. except FieldDoesNotExist:
  746. return refer_to_missing_field(option='date_hierarchy',
  747. field=obj.date_hierarchy,
  748. model=obj.model, obj=obj, id='admin.E127')
  749. else:
  750. if not isinstance(field, (models.DateField, models.DateTimeField)):
  751. return must_be('a DateField or DateTimeField', option='date_hierarchy',
  752. obj=obj, id='admin.E128')
  753. else:
  754. return []
  755. class InlineModelAdminChecks(BaseModelAdminChecks):
  756. def check(self, inline_obj, **kwargs):
  757. errors = super(InlineModelAdminChecks, self).check(inline_obj)
  758. parent_model = inline_obj.parent_model
  759. errors.extend(self._check_relation(inline_obj, parent_model))
  760. errors.extend(self._check_exclude_of_parent_model(inline_obj, parent_model))
  761. errors.extend(self._check_extra(inline_obj))
  762. errors.extend(self._check_max_num(inline_obj))
  763. errors.extend(self._check_min_num(inline_obj))
  764. errors.extend(self._check_formset(inline_obj))
  765. return errors
  766. def _check_exclude_of_parent_model(self, obj, parent_model):
  767. # Do not perform more specific checks if the base checks result in an
  768. # error.
  769. errors = super(InlineModelAdminChecks, self)._check_exclude(obj)
  770. if errors:
  771. return []
  772. # Skip if `fk_name` is invalid.
  773. if self._check_relation(obj, parent_model):
  774. return []
  775. if obj.exclude is None:
  776. return []
  777. fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
  778. if fk.name in obj.exclude:
  779. return [
  780. checks.Error(
  781. "Cannot exclude the field '%s', because it is the foreign key "
  782. "to the parent model '%s.%s'." % (
  783. fk.name, parent_model._meta.app_label, parent_model._meta.object_name
  784. ),
  785. hint=None,
  786. obj=obj.__class__,
  787. id='admin.E201',
  788. )
  789. ]
  790. else:
  791. return []
  792. def _check_relation(self, obj, parent_model):
  793. try:
  794. _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
  795. except ValueError as e:
  796. return [checks.Error(e.args[0], hint=None, obj=obj.__class__, id='admin.E202')]
  797. else:
  798. return []
  799. def _check_extra(self, obj):
  800. """ Check that extra is an integer. """
  801. if not isinstance(obj.extra, int):
  802. return must_be('an integer', option='extra', obj=obj, id='admin.E203')
  803. else:
  804. return []
  805. def _check_max_num(self, obj):
  806. """ Check that max_num is an integer. """
  807. if obj.max_num is None:
  808. return []
  809. elif not isinstance(obj.max_num, int):
  810. return must_be('an integer', option='max_num', obj=obj, id='admin.E204')
  811. else:
  812. return []
  813. def _check_min_num(self, obj):
  814. """ Check that min_num is an integer. """
  815. if obj.min_num is None:
  816. return []
  817. elif not isinstance(obj.min_num, int):
  818. return must_be('an integer', option='min_num', obj=obj, id='admin.E205')
  819. else:
  820. return []
  821. def _check_formset(self, obj):
  822. """ Check formset is a subclass of BaseModelFormSet. """
  823. if not issubclass(obj.formset, BaseModelFormSet):
  824. return must_inherit_from(parent='BaseModelFormSet', option='formset',
  825. obj=obj, id='admin.E206')
  826. else:
  827. return []
  828. def must_be(type, option, obj, id):
  829. return [
  830. checks.Error(
  831. "The value of '%s' must be %s." % (option, type),
  832. hint=None,
  833. obj=obj.__class__,
  834. id=id,
  835. ),
  836. ]
  837. def must_inherit_from(parent, option, obj, id):
  838. return [
  839. checks.Error(
  840. "The value of '%s' must inherit from '%s'." % (option, parent),
  841. hint=None,
  842. obj=obj.__class__,
  843. id=id,
  844. ),
  845. ]
  846. def refer_to_missing_field(field, option, model, obj, id):
  847. return [
  848. checks.Error(
  849. "The value of '%s' refers to '%s', which is not an attribute of '%s.%s'." % (
  850. option, field, model._meta.app_label, model._meta.object_name
  851. ),
  852. hint=None,
  853. obj=obj.__class__,
  854. id=id,
  855. ),
  856. ]