tests.py 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660
  1. from __future__ import unicode_literals
  2. from datetime import date
  3. from django import forms
  4. from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter
  5. from django.contrib.admin.options import (
  6. HORIZONTAL, VERTICAL, ModelAdmin, TabularInline,
  7. )
  8. from django.contrib.admin.sites import AdminSite
  9. from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
  10. from django.core.checks import Error
  11. from django.forms.models import BaseModelFormSet
  12. from django.forms.widgets import Select
  13. from django.test import SimpleTestCase, TestCase
  14. from django.utils import six
  15. from .models import (
  16. Band, Concert, ValidationTestInlineModel, ValidationTestModel,
  17. )
  18. class MockRequest(object):
  19. pass
  20. class MockSuperUser(object):
  21. def has_perm(self, perm):
  22. return True
  23. request = MockRequest()
  24. request.user = MockSuperUser()
  25. class ModelAdminTests(TestCase):
  26. def setUp(self):
  27. self.band = Band.objects.create(
  28. name='The Doors',
  29. bio='',
  30. sign_date=date(1965, 1, 1),
  31. )
  32. self.site = AdminSite()
  33. # form/fields/fieldsets interaction ##############################
  34. def test_default_fields(self):
  35. ma = ModelAdmin(Band, self.site)
  36. self.assertEqual(list(ma.get_form(request).base_fields),
  37. ['name', 'bio', 'sign_date'])
  38. self.assertEqual(list(ma.get_fields(request)),
  39. ['name', 'bio', 'sign_date'])
  40. self.assertEqual(list(ma.get_fields(request, self.band)),
  41. ['name', 'bio', 'sign_date'])
  42. def test_default_fieldsets(self):
  43. # fieldsets_add and fieldsets_change should return a special data structure that
  44. # is used in the templates. They should generate the "right thing" whether we
  45. # have specified a custom form, the fields argument, or nothing at all.
  46. #
  47. # Here's the default case. There are no custom form_add/form_change methods,
  48. # no fields argument, and no fieldsets argument.
  49. ma = ModelAdmin(Band, self.site)
  50. self.assertEqual(ma.get_fieldsets(request),
  51. [(None, {'fields': ['name', 'bio', 'sign_date']})])
  52. self.assertEqual(ma.get_fieldsets(request, self.band),
  53. [(None, {'fields': ['name', 'bio', 'sign_date']})])
  54. def test_get_fieldsets(self):
  55. # Test that get_fieldsets is called when figuring out form fields.
  56. # Refs #18681.
  57. class BandAdmin(ModelAdmin):
  58. def get_fieldsets(self, request, obj=None):
  59. return [(None, {'fields': ['name', 'bio']})]
  60. ma = BandAdmin(Band, self.site)
  61. form = ma.get_form(None)
  62. self.assertEqual(form._meta.fields, ['name', 'bio'])
  63. class InlineBandAdmin(TabularInline):
  64. model = Concert
  65. fk_name = 'main_band'
  66. can_delete = False
  67. def get_fieldsets(self, request, obj=None):
  68. return [(None, {'fields': ['day', 'transport']})]
  69. ma = InlineBandAdmin(Band, self.site)
  70. form = ma.get_formset(None).form
  71. self.assertEqual(form._meta.fields, ['day', 'transport'])
  72. def test_lookup_allowed_allows_nonexistent_lookup(self):
  73. """
  74. Ensure that a lookup_allowed allows a parameter
  75. whose field lookup doesn't exist.
  76. Refs #21129.
  77. """
  78. class BandAdmin(ModelAdmin):
  79. fields = ['name']
  80. ma = BandAdmin(Band, self.site)
  81. self.assertTrue(ma.lookup_allowed('name__nonexistent', 'test_value'))
  82. def test_field_arguments(self):
  83. # If we specify the fields argument, fieldsets_add and fieldsets_change should
  84. # just stick the fields into a formsets structure and return it.
  85. class BandAdmin(ModelAdmin):
  86. fields = ['name']
  87. ma = BandAdmin(Band, self.site)
  88. self.assertEqual(list(ma.get_fields(request)), ['name'])
  89. self.assertEqual(list(ma.get_fields(request, self.band)), ['name'])
  90. self.assertEqual(ma.get_fieldsets(request),
  91. [(None, {'fields': ['name']})])
  92. self.assertEqual(ma.get_fieldsets(request, self.band),
  93. [(None, {'fields': ['name']})])
  94. def test_field_arguments_restricted_on_form(self):
  95. # If we specify fields or fieldsets, it should exclude fields on the Form class
  96. # to the fields specified. This may cause errors to be raised in the db layer if
  97. # required model fields aren't in fields/fieldsets, but that's preferable to
  98. # ghost errors where you have a field in your Form class that isn't being
  99. # displayed because you forgot to add it to fields/fieldsets
  100. # Using `fields`.
  101. class BandAdmin(ModelAdmin):
  102. fields = ['name']
  103. ma = BandAdmin(Band, self.site)
  104. self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
  105. self.assertEqual(list(ma.get_form(request, self.band).base_fields),
  106. ['name'])
  107. # Using `fieldsets`.
  108. class BandAdmin(ModelAdmin):
  109. fieldsets = [(None, {'fields': ['name']})]
  110. ma = BandAdmin(Band, self.site)
  111. self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
  112. self.assertEqual(list(ma.get_form(request, self.band).base_fields),
  113. ['name'])
  114. # Using `exclude`.
  115. class BandAdmin(ModelAdmin):
  116. exclude = ['bio']
  117. ma = BandAdmin(Band, self.site)
  118. self.assertEqual(list(ma.get_form(request).base_fields),
  119. ['name', 'sign_date'])
  120. # You can also pass a tuple to `exclude`.
  121. class BandAdmin(ModelAdmin):
  122. exclude = ('bio',)
  123. ma = BandAdmin(Band, self.site)
  124. self.assertEqual(list(ma.get_form(request).base_fields),
  125. ['name', 'sign_date'])
  126. # Using `fields` and `exclude`.
  127. class BandAdmin(ModelAdmin):
  128. fields = ['name', 'bio']
  129. exclude = ['bio']
  130. ma = BandAdmin(Band, self.site)
  131. self.assertEqual(list(ma.get_form(request).base_fields),
  132. ['name'])
  133. def test_custom_form_meta_exclude_with_readonly(self):
  134. """
  135. Ensure that the custom ModelForm's `Meta.exclude` is respected when
  136. used in conjunction with `ModelAdmin.readonly_fields` and when no
  137. `ModelAdmin.exclude` is defined.
  138. Refs #14496.
  139. """
  140. # First, with `ModelAdmin` -----------------------
  141. class AdminBandForm(forms.ModelForm):
  142. class Meta:
  143. model = Band
  144. exclude = ['bio']
  145. class BandAdmin(ModelAdmin):
  146. readonly_fields = ['name']
  147. form = AdminBandForm
  148. ma = BandAdmin(Band, self.site)
  149. self.assertEqual(list(ma.get_form(request).base_fields),
  150. ['sign_date'])
  151. # Then, with `InlineModelAdmin` -----------------
  152. class AdminConcertForm(forms.ModelForm):
  153. class Meta:
  154. model = Concert
  155. exclude = ['day']
  156. class ConcertInline(TabularInline):
  157. readonly_fields = ['transport']
  158. form = AdminConcertForm
  159. fk_name = 'main_band'
  160. model = Concert
  161. class BandAdmin(ModelAdmin):
  162. inlines = [
  163. ConcertInline
  164. ]
  165. ma = BandAdmin(Band, self.site)
  166. self.assertEqual(
  167. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  168. ['main_band', 'opening_band', 'id', 'DELETE'])
  169. def test_custom_formfield_override_readonly(self):
  170. class AdminBandForm(forms.ModelForm):
  171. name = forms.CharField()
  172. class Meta:
  173. exclude = tuple()
  174. model = Band
  175. class BandAdmin(ModelAdmin):
  176. form = AdminBandForm
  177. readonly_fields = ['name']
  178. ma = BandAdmin(Band, self.site)
  179. # `name` shouldn't appear in base_fields because it's part of
  180. # readonly_fields.
  181. self.assertEqual(
  182. list(ma.get_form(request).base_fields),
  183. ['bio', 'sign_date']
  184. )
  185. # But it should appear in get_fields()/fieldsets() so it can be
  186. # displayed as read-only.
  187. self.assertEqual(
  188. list(ma.get_fields(request)),
  189. ['bio', 'sign_date', 'name']
  190. )
  191. self.assertEqual(
  192. list(ma.get_fieldsets(request)),
  193. [(None, {'fields': ['bio', 'sign_date', 'name']})]
  194. )
  195. def test_custom_form_meta_exclude(self):
  196. """
  197. Ensure that the custom ModelForm's `Meta.exclude` is overridden if
  198. `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined.
  199. Refs #14496.
  200. """
  201. # First, with `ModelAdmin` -----------------------
  202. class AdminBandForm(forms.ModelForm):
  203. class Meta:
  204. model = Band
  205. exclude = ['bio']
  206. class BandAdmin(ModelAdmin):
  207. exclude = ['name']
  208. form = AdminBandForm
  209. ma = BandAdmin(Band, self.site)
  210. self.assertEqual(list(ma.get_form(request).base_fields),
  211. ['bio', 'sign_date'])
  212. # Then, with `InlineModelAdmin` -----------------
  213. class AdminConcertForm(forms.ModelForm):
  214. class Meta:
  215. model = Concert
  216. exclude = ['day']
  217. class ConcertInline(TabularInline):
  218. exclude = ['transport']
  219. form = AdminConcertForm
  220. fk_name = 'main_band'
  221. model = Concert
  222. class BandAdmin(ModelAdmin):
  223. inlines = [
  224. ConcertInline
  225. ]
  226. ma = BandAdmin(Band, self.site)
  227. self.assertEqual(
  228. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  229. ['main_band', 'opening_band', 'day', 'id', 'DELETE'])
  230. def test_custom_form_validation(self):
  231. # If we specify a form, it should use it allowing custom validation to work
  232. # properly. This won't, however, break any of the admin widgets or media.
  233. class AdminBandForm(forms.ModelForm):
  234. delete = forms.BooleanField()
  235. class BandAdmin(ModelAdmin):
  236. form = AdminBandForm
  237. ma = BandAdmin(Band, self.site)
  238. self.assertEqual(list(ma.get_form(request).base_fields),
  239. ['name', 'bio', 'sign_date', 'delete'])
  240. self.assertEqual(
  241. type(ma.get_form(request).base_fields['sign_date'].widget),
  242. AdminDateWidget)
  243. def test_form_exclude_kwarg_override(self):
  244. """
  245. Ensure that the `exclude` kwarg passed to `ModelAdmin.get_form()`
  246. overrides all other declarations. Refs #8999.
  247. """
  248. class AdminBandForm(forms.ModelForm):
  249. class Meta:
  250. model = Band
  251. exclude = ['name']
  252. class BandAdmin(ModelAdmin):
  253. exclude = ['sign_date']
  254. form = AdminBandForm
  255. def get_form(self, request, obj=None, **kwargs):
  256. kwargs['exclude'] = ['bio']
  257. return super(BandAdmin, self).get_form(request, obj, **kwargs)
  258. ma = BandAdmin(Band, self.site)
  259. self.assertEqual(list(ma.get_form(request).base_fields),
  260. ['name', 'sign_date'])
  261. def test_formset_exclude_kwarg_override(self):
  262. """
  263. Ensure that the `exclude` kwarg passed to `InlineModelAdmin.get_formset()`
  264. overrides all other declarations. Refs #8999.
  265. """
  266. class AdminConcertForm(forms.ModelForm):
  267. class Meta:
  268. model = Concert
  269. exclude = ['day']
  270. class ConcertInline(TabularInline):
  271. exclude = ['transport']
  272. form = AdminConcertForm
  273. fk_name = 'main_band'
  274. model = Concert
  275. def get_formset(self, request, obj=None, **kwargs):
  276. kwargs['exclude'] = ['opening_band']
  277. return super(ConcertInline, self).get_formset(request, obj, **kwargs)
  278. class BandAdmin(ModelAdmin):
  279. inlines = [
  280. ConcertInline
  281. ]
  282. ma = BandAdmin(Band, self.site)
  283. self.assertEqual(
  284. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  285. ['main_band', 'day', 'transport', 'id', 'DELETE'])
  286. def test_queryset_override(self):
  287. # If we need to override the queryset of a ModelChoiceField in our custom form
  288. # make sure that RelatedFieldWidgetWrapper doesn't mess that up.
  289. band2 = Band(name='The Beatles', bio='', sign_date=date(1962, 1, 1))
  290. band2.save()
  291. class ConcertAdmin(ModelAdmin):
  292. pass
  293. ma = ConcertAdmin(Concert, self.site)
  294. form = ma.get_form(request)()
  295. self.assertHTMLEqual(str(form["main_band"]),
  296. '<div class="related-widget-wrapper">'
  297. '<select name="main_band" id="id_main_band">'
  298. '<option value="" selected="selected">---------</option>'
  299. '<option value="%d">The Beatles</option>'
  300. '<option value="%d">The Doors</option>'
  301. '</select></div>' % (band2.id, self.band.id))
  302. class AdminConcertForm(forms.ModelForm):
  303. def __init__(self, *args, **kwargs):
  304. super(AdminConcertForm, self).__init__(*args, **kwargs)
  305. self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')
  306. class ConcertAdminWithForm(ModelAdmin):
  307. form = AdminConcertForm
  308. ma = ConcertAdminWithForm(Concert, self.site)
  309. form = ma.get_form(request)()
  310. self.assertHTMLEqual(str(form["main_band"]),
  311. '<div class="related-widget-wrapper">'
  312. '<select name="main_band" id="id_main_band">'
  313. '<option value="" selected="selected">---------</option>'
  314. '<option value="%d">The Doors</option>'
  315. '</select></div>' % self.band.id)
  316. def test_regression_for_ticket_15820(self):
  317. """
  318. Ensure that `obj` is passed from `InlineModelAdmin.get_fieldsets()` to
  319. `InlineModelAdmin.get_formset()`.
  320. """
  321. class CustomConcertForm(forms.ModelForm):
  322. class Meta:
  323. model = Concert
  324. fields = ['day']
  325. class ConcertInline(TabularInline):
  326. model = Concert
  327. fk_name = 'main_band'
  328. def get_formset(self, request, obj=None, **kwargs):
  329. if obj:
  330. kwargs['form'] = CustomConcertForm
  331. return super(ConcertInline, self).get_formset(request, obj, **kwargs)
  332. class BandAdmin(ModelAdmin):
  333. inlines = [
  334. ConcertInline
  335. ]
  336. Concert.objects.create(main_band=self.band, opening_band=self.band, day=1)
  337. ma = BandAdmin(Band, self.site)
  338. inline_instances = ma.get_inline_instances(request)
  339. fieldsets = list(inline_instances[0].get_fieldsets(request))
  340. self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport'])
  341. fieldsets = list(inline_instances[0].get_fieldsets(request, inline_instances[0].model))
  342. self.assertEqual(fieldsets[0][1]['fields'], ['day'])
  343. # radio_fields behavior ###########################################
  344. def test_default_foreign_key_widget(self):
  345. # First, without any radio_fields specified, the widgets for ForeignKey
  346. # and fields with choices specified ought to be a basic Select widget.
  347. # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
  348. # they need to be handled properly when type checking. For Select fields, all of
  349. # the choices lists have a first entry of dashes.
  350. cma = ModelAdmin(Concert, self.site)
  351. cmafa = cma.get_form(request)
  352. self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
  353. Select)
  354. self.assertEqual(
  355. list(cmafa.base_fields['main_band'].widget.choices),
  356. [('', '---------'), (self.band.id, 'The Doors')])
  357. self.assertEqual(
  358. type(cmafa.base_fields['opening_band'].widget.widget), Select)
  359. self.assertEqual(
  360. list(cmafa.base_fields['opening_band'].widget.choices),
  361. [('', '---------'), (self.band.id, 'The Doors')])
  362. self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
  363. self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
  364. [('', '---------'), (1, 'Fri'), (2, 'Sat')])
  365. self.assertEqual(type(cmafa.base_fields['transport'].widget),
  366. Select)
  367. self.assertEqual(
  368. list(cmafa.base_fields['transport'].widget.choices),
  369. [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
  370. def test_foreign_key_as_radio_field(self):
  371. # Now specify all the fields as radio_fields. Widgets should now be
  372. # RadioSelect, and the choices list should have a first entry of 'None' if
  373. # blank=True for the model field. Finally, the widget should have the
  374. # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
  375. class ConcertAdmin(ModelAdmin):
  376. radio_fields = {
  377. 'main_band': HORIZONTAL,
  378. 'opening_band': VERTICAL,
  379. 'day': VERTICAL,
  380. 'transport': HORIZONTAL,
  381. }
  382. cma = ConcertAdmin(Concert, self.site)
  383. cmafa = cma.get_form(request)
  384. self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
  385. AdminRadioSelect)
  386. self.assertEqual(cmafa.base_fields['main_band'].widget.attrs,
  387. {'class': 'radiolist inline'})
  388. self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices),
  389. [(self.band.id, 'The Doors')])
  390. self.assertEqual(
  391. type(cmafa.base_fields['opening_band'].widget.widget),
  392. AdminRadioSelect)
  393. self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs,
  394. {'class': 'radiolist'})
  395. self.assertEqual(
  396. list(cmafa.base_fields['opening_band'].widget.choices),
  397. [('', 'None'), (self.band.id, 'The Doors')])
  398. self.assertEqual(type(cmafa.base_fields['day'].widget),
  399. AdminRadioSelect)
  400. self.assertEqual(cmafa.base_fields['day'].widget.attrs,
  401. {'class': 'radiolist'})
  402. self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
  403. [(1, 'Fri'), (2, 'Sat')])
  404. self.assertEqual(type(cmafa.base_fields['transport'].widget),
  405. AdminRadioSelect)
  406. self.assertEqual(cmafa.base_fields['transport'].widget.attrs,
  407. {'class': 'radiolist inline'})
  408. self.assertEqual(list(cmafa.base_fields['transport'].widget.choices),
  409. [('', 'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
  410. class AdminConcertForm(forms.ModelForm):
  411. class Meta:
  412. model = Concert
  413. exclude = ('transport',)
  414. class ConcertAdmin(ModelAdmin):
  415. form = AdminConcertForm
  416. ma = ConcertAdmin(Concert, self.site)
  417. self.assertEqual(list(ma.get_form(request).base_fields),
  418. ['main_band', 'opening_band', 'day'])
  419. class AdminConcertForm(forms.ModelForm):
  420. extra = forms.CharField()
  421. class Meta:
  422. model = Concert
  423. fields = ['extra', 'transport']
  424. class ConcertAdmin(ModelAdmin):
  425. form = AdminConcertForm
  426. ma = ConcertAdmin(Concert, self.site)
  427. self.assertEqual(list(ma.get_form(request).base_fields),
  428. ['extra', 'transport'])
  429. class ConcertInline(TabularInline):
  430. form = AdminConcertForm
  431. model = Concert
  432. fk_name = 'main_band'
  433. can_delete = True
  434. class BandAdmin(ModelAdmin):
  435. inlines = [
  436. ConcertInline
  437. ]
  438. ma = BandAdmin(Band, self.site)
  439. self.assertEqual(
  440. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  441. ['extra', 'transport', 'id', 'DELETE', 'main_band'])
  442. class CheckTestCase(SimpleTestCase):
  443. def assertIsInvalid(self, model_admin, model, msg,
  444. id=None, hint=None, invalid_obj=None):
  445. invalid_obj = invalid_obj or model_admin
  446. admin_obj = model_admin(model, AdminSite())
  447. errors = admin_obj.check()
  448. expected = [
  449. Error(
  450. msg,
  451. hint=hint,
  452. obj=invalid_obj,
  453. id=id,
  454. )
  455. ]
  456. self.assertEqual(errors, expected)
  457. def assertIsInvalidRegexp(self, model_admin, model, msg,
  458. id=None, hint=None, invalid_obj=None):
  459. """
  460. Same as assertIsInvalid but treats the given msg as a regexp.
  461. """
  462. invalid_obj = invalid_obj or model_admin
  463. admin_obj = model_admin(model, AdminSite())
  464. errors = admin_obj.check()
  465. self.assertEqual(len(errors), 1)
  466. error = errors[0]
  467. self.assertEqual(error.hint, hint)
  468. self.assertEqual(error.obj, invalid_obj)
  469. self.assertEqual(error.id, id)
  470. six.assertRegex(self, error.msg, msg)
  471. def assertIsValid(self, model_admin, model):
  472. admin_obj = model_admin(model, AdminSite())
  473. errors = admin_obj.check()
  474. expected = []
  475. self.assertEqual(errors, expected)
  476. class RawIdCheckTests(CheckTestCase):
  477. def test_not_iterable(self):
  478. class ValidationTestModelAdmin(ModelAdmin):
  479. raw_id_fields = 10
  480. self.assertIsInvalid(
  481. ValidationTestModelAdmin, ValidationTestModel,
  482. "The value of 'raw_id_fields' must be a list or tuple.",
  483. 'admin.E001')
  484. def test_missing_field(self):
  485. class ValidationTestModelAdmin(ModelAdmin):
  486. raw_id_fields = ('non_existent_field',)
  487. self.assertIsInvalid(
  488. ValidationTestModelAdmin, ValidationTestModel,
  489. ("The value of 'raw_id_fields[0]' refers to 'non_existent_field', "
  490. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  491. 'admin.E002')
  492. def test_invalid_field_type(self):
  493. class ValidationTestModelAdmin(ModelAdmin):
  494. raw_id_fields = ('name',)
  495. self.assertIsInvalid(
  496. ValidationTestModelAdmin, ValidationTestModel,
  497. "The value of 'raw_id_fields[0]' must be a ForeignKey or ManyToManyField.",
  498. 'admin.E003')
  499. def test_valid_case(self):
  500. class ValidationTestModelAdmin(ModelAdmin):
  501. raw_id_fields = ('users',)
  502. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  503. class FieldsetsCheckTests(CheckTestCase):
  504. def test_valid_case(self):
  505. class ValidationTestModelAdmin(ModelAdmin):
  506. fieldsets = (("General", {'fields': ('name',)}),)
  507. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  508. def test_not_iterable(self):
  509. class ValidationTestModelAdmin(ModelAdmin):
  510. fieldsets = 10
  511. self.assertIsInvalid(
  512. ValidationTestModelAdmin, ValidationTestModel,
  513. "The value of 'fieldsets' must be a list or tuple.",
  514. 'admin.E007')
  515. def test_non_iterable_item(self):
  516. class ValidationTestModelAdmin(ModelAdmin):
  517. fieldsets = ({},)
  518. self.assertIsInvalid(
  519. ValidationTestModelAdmin, ValidationTestModel,
  520. "The value of 'fieldsets[0]' must be a list or tuple.",
  521. 'admin.E008')
  522. def test_item_not_a_pair(self):
  523. class ValidationTestModelAdmin(ModelAdmin):
  524. fieldsets = ((),)
  525. self.assertIsInvalid(
  526. ValidationTestModelAdmin, ValidationTestModel,
  527. "The value of 'fieldsets[0]' must be of length 2.",
  528. 'admin.E009')
  529. def test_second_element_of_item_not_a_dict(self):
  530. class ValidationTestModelAdmin(ModelAdmin):
  531. fieldsets = (("General", ()),)
  532. self.assertIsInvalid(
  533. ValidationTestModelAdmin, ValidationTestModel,
  534. "The value of 'fieldsets[0][1]' must be a dictionary.",
  535. 'admin.E010')
  536. def test_missing_fields_key(self):
  537. class ValidationTestModelAdmin(ModelAdmin):
  538. fieldsets = (("General", {}),)
  539. self.assertIsInvalid(
  540. ValidationTestModelAdmin, ValidationTestModel,
  541. "The value of 'fieldsets[0][1]' must contain the key 'fields'.",
  542. 'admin.E011')
  543. class ValidationTestModelAdmin(ModelAdmin):
  544. fieldsets = (("General", {'fields': ('name',)}),)
  545. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  546. def test_specified_both_fields_and_fieldsets(self):
  547. class ValidationTestModelAdmin(ModelAdmin):
  548. fieldsets = (("General", {'fields': ('name',)}),)
  549. fields = ['name']
  550. self.assertIsInvalid(
  551. ValidationTestModelAdmin, ValidationTestModel,
  552. "Both 'fieldsets' and 'fields' are specified.",
  553. 'admin.E005')
  554. def test_duplicate_fields(self):
  555. class ValidationTestModelAdmin(ModelAdmin):
  556. fieldsets = [(None, {'fields': ['name', 'name']})]
  557. self.assertIsInvalid(
  558. ValidationTestModelAdmin, ValidationTestModel,
  559. "There are duplicate field(s) in 'fieldsets[0][1]'.",
  560. 'admin.E012')
  561. def test_fieldsets_with_custom_form_validation(self):
  562. class BandAdmin(ModelAdmin):
  563. fieldsets = (
  564. ('Band', {
  565. 'fields': ('name',)
  566. }),
  567. )
  568. self.assertIsValid(BandAdmin, Band)
  569. class FieldsCheckTests(CheckTestCase):
  570. def test_duplicate_fields_in_fields(self):
  571. class ValidationTestModelAdmin(ModelAdmin):
  572. fields = ['name', 'name']
  573. self.assertIsInvalid(
  574. ValidationTestModelAdmin, ValidationTestModel,
  575. "The value of 'fields' contains duplicate field(s).",
  576. 'admin.E006')
  577. def test_inline(self):
  578. class ValidationTestInline(TabularInline):
  579. model = ValidationTestInlineModel
  580. fields = 10
  581. class ValidationTestModelAdmin(ModelAdmin):
  582. inlines = [ValidationTestInline]
  583. self.assertIsInvalid(
  584. ValidationTestModelAdmin, ValidationTestModel,
  585. "The value of 'fields' must be a list or tuple.",
  586. 'admin.E004',
  587. invalid_obj=ValidationTestInline)
  588. class FormCheckTests(CheckTestCase):
  589. def test_invalid_type(self):
  590. class FakeForm(object):
  591. pass
  592. class ValidationTestModelAdmin(ModelAdmin):
  593. form = FakeForm
  594. self.assertIsInvalid(
  595. ValidationTestModelAdmin, ValidationTestModel,
  596. "The value of 'form' must inherit from 'BaseModelForm'.",
  597. 'admin.E016')
  598. def test_fieldsets_with_custom_form_validation(self):
  599. class BandAdmin(ModelAdmin):
  600. fieldsets = (
  601. ('Band', {
  602. 'fields': ('name',)
  603. }),
  604. )
  605. self.assertIsValid(BandAdmin, Band)
  606. def test_valid_case(self):
  607. class AdminBandForm(forms.ModelForm):
  608. delete = forms.BooleanField()
  609. class BandAdmin(ModelAdmin):
  610. form = AdminBandForm
  611. fieldsets = (
  612. ('Band', {
  613. 'fields': ('name', 'bio', 'sign_date', 'delete')
  614. }),
  615. )
  616. self.assertIsValid(BandAdmin, Band)
  617. class FilterVerticalCheckTests(CheckTestCase):
  618. def test_not_iterable(self):
  619. class ValidationTestModelAdmin(ModelAdmin):
  620. filter_vertical = 10
  621. self.assertIsInvalid(
  622. ValidationTestModelAdmin, ValidationTestModel,
  623. "The value of 'filter_vertical' must be a list or tuple.",
  624. 'admin.E017')
  625. def test_missing_field(self):
  626. class ValidationTestModelAdmin(ModelAdmin):
  627. filter_vertical = ('non_existent_field',)
  628. self.assertIsInvalid(
  629. ValidationTestModelAdmin, ValidationTestModel,
  630. ("The value of 'filter_vertical[0]' refers to 'non_existent_field', "
  631. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  632. 'admin.E019')
  633. def test_invalid_field_type(self):
  634. class ValidationTestModelAdmin(ModelAdmin):
  635. filter_vertical = ('name',)
  636. self.assertIsInvalid(
  637. ValidationTestModelAdmin, ValidationTestModel,
  638. "The value of 'filter_vertical[0]' must be a ManyToManyField.",
  639. 'admin.E020')
  640. def test_valid_case(self):
  641. class ValidationTestModelAdmin(ModelAdmin):
  642. filter_vertical = ("users",)
  643. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  644. class FilterHorizontalCheckTests(CheckTestCase):
  645. def test_not_iterable(self):
  646. class ValidationTestModelAdmin(ModelAdmin):
  647. filter_horizontal = 10
  648. self.assertIsInvalid(
  649. ValidationTestModelAdmin, ValidationTestModel,
  650. "The value of 'filter_horizontal' must be a list or tuple.",
  651. 'admin.E018')
  652. def test_missing_field(self):
  653. class ValidationTestModelAdmin(ModelAdmin):
  654. filter_horizontal = ('non_existent_field',)
  655. self.assertIsInvalid(
  656. ValidationTestModelAdmin, ValidationTestModel,
  657. ("The value of 'filter_horizontal[0]' refers to 'non_existent_field', "
  658. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  659. 'admin.E019')
  660. def test_invalid_field_type(self):
  661. class ValidationTestModelAdmin(ModelAdmin):
  662. filter_horizontal = ('name',)
  663. self.assertIsInvalid(
  664. ValidationTestModelAdmin, ValidationTestModel,
  665. "The value of 'filter_horizontal[0]' must be a ManyToManyField.",
  666. 'admin.E020')
  667. def test_valid_case(self):
  668. class ValidationTestModelAdmin(ModelAdmin):
  669. filter_horizontal = ("users",)
  670. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  671. class RadioFieldsCheckTests(CheckTestCase):
  672. def test_not_dictionary(self):
  673. class ValidationTestModelAdmin(ModelAdmin):
  674. radio_fields = ()
  675. self.assertIsInvalid(
  676. ValidationTestModelAdmin, ValidationTestModel,
  677. "The value of 'radio_fields' must be a dictionary.",
  678. 'admin.E021')
  679. def test_missing_field(self):
  680. class ValidationTestModelAdmin(ModelAdmin):
  681. radio_fields = {'non_existent_field': VERTICAL}
  682. self.assertIsInvalid(
  683. ValidationTestModelAdmin, ValidationTestModel,
  684. ("The value of 'radio_fields' refers to 'non_existent_field', "
  685. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  686. 'admin.E022')
  687. def test_invalid_field_type(self):
  688. class ValidationTestModelAdmin(ModelAdmin):
  689. radio_fields = {'name': VERTICAL}
  690. self.assertIsInvalid(
  691. ValidationTestModelAdmin, ValidationTestModel,
  692. ("The value of 'radio_fields' refers to 'name', which is not an instance "
  693. "of ForeignKey, and does not have a 'choices' definition."),
  694. 'admin.E023')
  695. def test_invalid_value(self):
  696. class ValidationTestModelAdmin(ModelAdmin):
  697. radio_fields = {"state": None}
  698. self.assertIsInvalid(
  699. ValidationTestModelAdmin, ValidationTestModel,
  700. "The value of 'radio_fields[\"state\"]' must be either admin.HORIZONTAL or admin.VERTICAL.",
  701. 'admin.E024')
  702. def test_valid_case(self):
  703. class ValidationTestModelAdmin(ModelAdmin):
  704. radio_fields = {"state": VERTICAL}
  705. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  706. class PrepopulatedFieldsCheckTests(CheckTestCase):
  707. def test_not_dictionary(self):
  708. class ValidationTestModelAdmin(ModelAdmin):
  709. prepopulated_fields = ()
  710. self.assertIsInvalid(
  711. ValidationTestModelAdmin, ValidationTestModel,
  712. "The value of 'prepopulated_fields' must be a dictionary.",
  713. 'admin.E026')
  714. def test_missing_field(self):
  715. class ValidationTestModelAdmin(ModelAdmin):
  716. prepopulated_fields = {'non_existent_field': ("slug",)}
  717. self.assertIsInvalid(
  718. ValidationTestModelAdmin, ValidationTestModel,
  719. ("The value of 'prepopulated_fields' refers to 'non_existent_field', "
  720. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  721. 'admin.E027')
  722. def test_missing_field_again(self):
  723. class ValidationTestModelAdmin(ModelAdmin):
  724. prepopulated_fields = {"slug": ('non_existent_field',)}
  725. self.assertIsInvalid(
  726. ValidationTestModelAdmin, ValidationTestModel,
  727. ("The value of 'prepopulated_fields[\"slug\"][0]' refers to 'non_existent_field', "
  728. "which is not an attribute of 'modeladmin.ValidationTestModel'."),
  729. 'admin.E030')
  730. def test_invalid_field_type(self):
  731. class ValidationTestModelAdmin(ModelAdmin):
  732. prepopulated_fields = {"users": ('name',)}
  733. self.assertIsInvalid(
  734. ValidationTestModelAdmin, ValidationTestModel,
  735. ("The value of 'prepopulated_fields' refers to 'users', which must not be "
  736. "a DateTimeField, ForeignKey or ManyToManyField."),
  737. 'admin.E028')
  738. def test_valid_case(self):
  739. class ValidationTestModelAdmin(ModelAdmin):
  740. prepopulated_fields = {"slug": ('name',)}
  741. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  742. class ListDisplayTests(CheckTestCase):
  743. def test_not_iterable(self):
  744. class ValidationTestModelAdmin(ModelAdmin):
  745. list_display = 10
  746. self.assertIsInvalid(
  747. ValidationTestModelAdmin, ValidationTestModel,
  748. "The value of 'list_display' must be a list or tuple.",
  749. 'admin.E107')
  750. def test_missing_field(self):
  751. class ValidationTestModelAdmin(ModelAdmin):
  752. list_display = ('non_existent_field',)
  753. self.assertIsInvalid(
  754. ValidationTestModelAdmin, ValidationTestModel,
  755. ("The value of 'list_display[0]' refers to 'non_existent_field', which is not a callable, an attribute "
  756. "of 'ValidationTestModelAdmin', or an attribute or method on 'modeladmin.ValidationTestModel'."),
  757. 'admin.E108')
  758. def test_invalid_field_type(self):
  759. class ValidationTestModelAdmin(ModelAdmin):
  760. list_display = ('users',)
  761. self.assertIsInvalid(
  762. ValidationTestModelAdmin, ValidationTestModel,
  763. "The value of 'list_display[0]' must not be a ManyToManyField.",
  764. 'admin.E109')
  765. def test_valid_case(self):
  766. def a_callable(obj):
  767. pass
  768. class ValidationTestModelAdmin(ModelAdmin):
  769. def a_method(self, obj):
  770. pass
  771. list_display = ('name', 'decade_published_in', 'a_method', a_callable)
  772. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  773. class ListDisplayLinksCheckTests(CheckTestCase):
  774. def test_not_iterable(self):
  775. class ValidationTestModelAdmin(ModelAdmin):
  776. list_display_links = 10
  777. self.assertIsInvalid(
  778. ValidationTestModelAdmin, ValidationTestModel,
  779. "The value of 'list_display_links' must be a list, a tuple, or None.",
  780. 'admin.E110')
  781. def test_missing_field(self):
  782. class ValidationTestModelAdmin(ModelAdmin):
  783. list_display_links = ('non_existent_field',)
  784. self.assertIsInvalid(
  785. ValidationTestModelAdmin, ValidationTestModel, (
  786. "The value of 'list_display_links[0]' refers to "
  787. "'non_existent_field', which is not defined in 'list_display'."
  788. ), 'admin.E111'
  789. )
  790. def test_missing_in_list_display(self):
  791. class ValidationTestModelAdmin(ModelAdmin):
  792. list_display_links = ('name',)
  793. self.assertIsInvalid(
  794. ValidationTestModelAdmin, ValidationTestModel,
  795. "The value of 'list_display_links[0]' refers to 'name', which is not defined in 'list_display'.",
  796. 'admin.E111')
  797. def test_valid_case(self):
  798. def a_callable(obj):
  799. pass
  800. class ValidationTestModelAdmin(ModelAdmin):
  801. def a_method(self, obj):
  802. pass
  803. list_display = ('name', 'decade_published_in', 'a_method', a_callable)
  804. list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)
  805. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  806. def test_None_is_valid_case(self):
  807. class ValidationTestModelAdmin(ModelAdmin):
  808. list_display_links = None
  809. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  810. class ListFilterTests(CheckTestCase):
  811. def test_list_filter_validation(self):
  812. class ValidationTestModelAdmin(ModelAdmin):
  813. list_filter = 10
  814. self.assertIsInvalid(
  815. ValidationTestModelAdmin, ValidationTestModel,
  816. "The value of 'list_filter' must be a list or tuple.",
  817. 'admin.E112')
  818. def test_missing_field(self):
  819. class ValidationTestModelAdmin(ModelAdmin):
  820. list_filter = ('non_existent_field',)
  821. self.assertIsInvalid(
  822. ValidationTestModelAdmin, ValidationTestModel,
  823. "The value of 'list_filter[0]' refers to 'non_existent_field', which does not refer to a Field.",
  824. 'admin.E116')
  825. def test_not_filter(self):
  826. class RandomClass(object):
  827. pass
  828. class ValidationTestModelAdmin(ModelAdmin):
  829. list_filter = (RandomClass,)
  830. self.assertIsInvalid(
  831. ValidationTestModelAdmin, ValidationTestModel,
  832. "The value of 'list_filter[0]' must inherit from 'ListFilter'.",
  833. 'admin.E113')
  834. def test_not_filter_again(self):
  835. class RandomClass(object):
  836. pass
  837. class ValidationTestModelAdmin(ModelAdmin):
  838. list_filter = (('is_active', RandomClass),)
  839. self.assertIsInvalid(
  840. ValidationTestModelAdmin, ValidationTestModel,
  841. "The value of 'list_filter[0][1]' must inherit from 'FieldListFilter'.",
  842. 'admin.E115')
  843. def test_not_filter_again_again(self):
  844. class AwesomeFilter(SimpleListFilter):
  845. def get_title(self):
  846. return 'awesomeness'
  847. def get_choices(self, request):
  848. return (('bit', 'A bit awesome'), ('very', 'Very awesome'), )
  849. def get_queryset(self, cl, qs):
  850. return qs
  851. class ValidationTestModelAdmin(ModelAdmin):
  852. list_filter = (('is_active', AwesomeFilter),)
  853. self.assertIsInvalid(
  854. ValidationTestModelAdmin, ValidationTestModel,
  855. "The value of 'list_filter[0][1]' must inherit from 'FieldListFilter'.",
  856. 'admin.E115')
  857. def test_not_associated_with_field_name(self):
  858. class ValidationTestModelAdmin(ModelAdmin):
  859. list_filter = (BooleanFieldListFilter,)
  860. self.assertIsInvalid(
  861. ValidationTestModelAdmin, ValidationTestModel,
  862. "The value of 'list_filter[0]' must not inherit from 'FieldListFilter'.",
  863. 'admin.E114')
  864. def test_valid_case(self):
  865. class AwesomeFilter(SimpleListFilter):
  866. def get_title(self):
  867. return 'awesomeness'
  868. def get_choices(self, request):
  869. return (('bit', 'A bit awesome'), ('very', 'Very awesome'), )
  870. def get_queryset(self, cl, qs):
  871. return qs
  872. class ValidationTestModelAdmin(ModelAdmin):
  873. list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no')
  874. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  875. class ListPerPageCheckTests(CheckTestCase):
  876. def test_not_integer(self):
  877. class ValidationTestModelAdmin(ModelAdmin):
  878. list_per_page = 'hello'
  879. self.assertIsInvalid(
  880. ValidationTestModelAdmin, ValidationTestModel,
  881. "The value of 'list_per_page' must be an integer.",
  882. 'admin.E118')
  883. def test_valid_case(self):
  884. class ValidationTestModelAdmin(ModelAdmin):
  885. list_per_page = 100
  886. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  887. class ListMaxShowAllCheckTests(CheckTestCase):
  888. def test_not_integer(self):
  889. class ValidationTestModelAdmin(ModelAdmin):
  890. list_max_show_all = 'hello'
  891. self.assertIsInvalid(
  892. ValidationTestModelAdmin, ValidationTestModel,
  893. "The value of 'list_max_show_all' must be an integer.",
  894. 'admin.E119')
  895. def test_valid_case(self):
  896. class ValidationTestModelAdmin(ModelAdmin):
  897. list_max_show_all = 200
  898. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  899. class SearchFieldsCheckTests(CheckTestCase):
  900. def test_not_iterable(self):
  901. class ValidationTestModelAdmin(ModelAdmin):
  902. search_fields = 10
  903. self.assertIsInvalid(
  904. ValidationTestModelAdmin, ValidationTestModel,
  905. "The value of 'search_fields' must be a list or tuple.",
  906. 'admin.E126')
  907. class DateHierarchyCheckTests(CheckTestCase):
  908. def test_missing_field(self):
  909. class ValidationTestModelAdmin(ModelAdmin):
  910. date_hierarchy = 'non_existent_field'
  911. self.assertIsInvalid(
  912. ValidationTestModelAdmin, ValidationTestModel,
  913. ("The value of 'date_hierarchy' refers to 'non_existent_field', which "
  914. "is not an attribute of 'modeladmin.ValidationTestModel'."),
  915. 'admin.E127')
  916. def test_invalid_field_type(self):
  917. class ValidationTestModelAdmin(ModelAdmin):
  918. date_hierarchy = 'name'
  919. self.assertIsInvalid(
  920. ValidationTestModelAdmin, ValidationTestModel,
  921. "The value of 'date_hierarchy' must be a DateField or DateTimeField.",
  922. 'admin.E128')
  923. def test_valid_case(self):
  924. class ValidationTestModelAdmin(ModelAdmin):
  925. date_hierarchy = 'pub_date'
  926. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  927. class OrderingCheckTests(CheckTestCase):
  928. def test_not_iterable(self):
  929. class ValidationTestModelAdmin(ModelAdmin):
  930. ordering = 10
  931. self.assertIsInvalid(
  932. ValidationTestModelAdmin, ValidationTestModel,
  933. "The value of 'ordering' must be a list or tuple.",
  934. 'admin.E031'
  935. )
  936. class ValidationTestModelAdmin(ModelAdmin):
  937. ordering = ('non_existent_field',)
  938. self.assertIsInvalid(
  939. ValidationTestModelAdmin, ValidationTestModel,
  940. "The value of 'ordering[0]' refers to 'non_existent_field', "
  941. "which is not an attribute of 'modeladmin.ValidationTestModel'.",
  942. 'admin.E033'
  943. )
  944. def test_random_marker_not_alone(self):
  945. class ValidationTestModelAdmin(ModelAdmin):
  946. ordering = ('?', 'name')
  947. self.assertIsInvalid(
  948. ValidationTestModelAdmin, ValidationTestModel,
  949. "The value of 'ordering' has the random ordering marker '?', but contains "
  950. "other fields as well.",
  951. 'admin.E032',
  952. hint='Either remove the "?", or remove the other fields.'
  953. )
  954. def test_valid_random_marker_case(self):
  955. class ValidationTestModelAdmin(ModelAdmin):
  956. ordering = ('?',)
  957. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  958. def test_valid_complex_case(self):
  959. class ValidationTestModelAdmin(ModelAdmin):
  960. ordering = ('band__name',)
  961. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  962. def test_valid_case(self):
  963. class ValidationTestModelAdmin(ModelAdmin):
  964. ordering = ('name',)
  965. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  966. class ListSelectRelatedCheckTests(CheckTestCase):
  967. def test_invalid_type(self):
  968. class ValidationTestModelAdmin(ModelAdmin):
  969. list_select_related = 1
  970. self.assertIsInvalid(
  971. ValidationTestModelAdmin, ValidationTestModel,
  972. "The value of 'list_select_related' must be a boolean, tuple or list.",
  973. 'admin.E117')
  974. def test_valid_case(self):
  975. class ValidationTestModelAdmin(ModelAdmin):
  976. list_select_related = False
  977. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  978. class SaveAsCheckTests(CheckTestCase):
  979. def test_not_boolean(self):
  980. class ValidationTestModelAdmin(ModelAdmin):
  981. save_as = 1
  982. self.assertIsInvalid(
  983. ValidationTestModelAdmin, ValidationTestModel,
  984. "The value of 'save_as' must be a boolean.",
  985. 'admin.E101')
  986. def test_valid_case(self):
  987. class ValidationTestModelAdmin(ModelAdmin):
  988. save_as = True
  989. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  990. class SaveOnTopCheckTests(CheckTestCase):
  991. def test_not_boolean(self):
  992. class ValidationTestModelAdmin(ModelAdmin):
  993. save_on_top = 1
  994. self.assertIsInvalid(
  995. ValidationTestModelAdmin, ValidationTestModel,
  996. "The value of 'save_on_top' must be a boolean.",
  997. 'admin.E102')
  998. def test_valid_case(self):
  999. class ValidationTestModelAdmin(ModelAdmin):
  1000. save_on_top = True
  1001. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1002. class InlinesCheckTests(CheckTestCase):
  1003. def test_not_iterable(self):
  1004. class ValidationTestModelAdmin(ModelAdmin):
  1005. inlines = 10
  1006. self.assertIsInvalid(
  1007. ValidationTestModelAdmin, ValidationTestModel,
  1008. "The value of 'inlines' must be a list or tuple.",
  1009. 'admin.E103')
  1010. def test_not_model_admin(self):
  1011. class ValidationTestInline(object):
  1012. pass
  1013. class ValidationTestModelAdmin(ModelAdmin):
  1014. inlines = [ValidationTestInline]
  1015. self.assertIsInvalidRegexp(
  1016. ValidationTestModelAdmin, ValidationTestModel,
  1017. r"'.*\.ValidationTestInline' must inherit from 'BaseModelAdmin'\.",
  1018. 'admin.E104')
  1019. def test_missing_model_field(self):
  1020. class ValidationTestInline(TabularInline):
  1021. pass
  1022. class ValidationTestModelAdmin(ModelAdmin):
  1023. inlines = [ValidationTestInline]
  1024. self.assertIsInvalidRegexp(
  1025. ValidationTestModelAdmin, ValidationTestModel,
  1026. r"'.*\.ValidationTestInline' must have a 'model' attribute\.",
  1027. 'admin.E105')
  1028. def test_invalid_model_type(self):
  1029. """ Test if `model` attribute on inline model admin is a models.Model.
  1030. """
  1031. class SomethingBad(object):
  1032. pass
  1033. class ValidationTestInline(TabularInline):
  1034. model = SomethingBad
  1035. class ValidationTestModelAdmin(ModelAdmin):
  1036. inlines = [ValidationTestInline]
  1037. self.assertIsInvalidRegexp(
  1038. ValidationTestModelAdmin, ValidationTestModel,
  1039. r"The value of '.*\.ValidationTestInline.model' must be a Model\.",
  1040. 'admin.E106')
  1041. def test_valid_case(self):
  1042. class ValidationTestInline(TabularInline):
  1043. model = ValidationTestInlineModel
  1044. class ValidationTestModelAdmin(ModelAdmin):
  1045. inlines = [ValidationTestInline]
  1046. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1047. class FkNameCheckTests(CheckTestCase):
  1048. def test_missing_field(self):
  1049. class ValidationTestInline(TabularInline):
  1050. model = ValidationTestInlineModel
  1051. fk_name = 'non_existent_field'
  1052. class ValidationTestModelAdmin(ModelAdmin):
  1053. inlines = [ValidationTestInline]
  1054. self.assertIsInvalid(
  1055. ValidationTestModelAdmin, ValidationTestModel,
  1056. "'modeladmin.ValidationTestInlineModel' has no field named 'non_existent_field'.",
  1057. 'admin.E202',
  1058. invalid_obj=ValidationTestInline)
  1059. def test_valid_case(self):
  1060. class ValidationTestInline(TabularInline):
  1061. model = ValidationTestInlineModel
  1062. fk_name = "parent"
  1063. class ValidationTestModelAdmin(ModelAdmin):
  1064. inlines = [ValidationTestInline]
  1065. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1066. class ExtraCheckTests(CheckTestCase):
  1067. def test_not_integer(self):
  1068. class ValidationTestInline(TabularInline):
  1069. model = ValidationTestInlineModel
  1070. extra = "hello"
  1071. class ValidationTestModelAdmin(ModelAdmin):
  1072. inlines = [ValidationTestInline]
  1073. self.assertIsInvalid(
  1074. ValidationTestModelAdmin, ValidationTestModel,
  1075. "The value of 'extra' must be an integer.",
  1076. 'admin.E203',
  1077. invalid_obj=ValidationTestInline)
  1078. def test_valid_case(self):
  1079. class ValidationTestInline(TabularInline):
  1080. model = ValidationTestInlineModel
  1081. extra = 2
  1082. class ValidationTestModelAdmin(ModelAdmin):
  1083. inlines = [ValidationTestInline]
  1084. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1085. class MaxNumCheckTests(CheckTestCase):
  1086. def test_not_integer(self):
  1087. class ValidationTestInline(TabularInline):
  1088. model = ValidationTestInlineModel
  1089. max_num = "hello"
  1090. class ValidationTestModelAdmin(ModelAdmin):
  1091. inlines = [ValidationTestInline]
  1092. self.assertIsInvalid(
  1093. ValidationTestModelAdmin, ValidationTestModel,
  1094. "The value of 'max_num' must be an integer.",
  1095. 'admin.E204',
  1096. invalid_obj=ValidationTestInline)
  1097. def test_valid_case(self):
  1098. class ValidationTestInline(TabularInline):
  1099. model = ValidationTestInlineModel
  1100. max_num = 2
  1101. class ValidationTestModelAdmin(ModelAdmin):
  1102. inlines = [ValidationTestInline]
  1103. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1104. class MinNumCheckTests(CheckTestCase):
  1105. def test_not_integer(self):
  1106. class ValidationTestInline(TabularInline):
  1107. model = ValidationTestInlineModel
  1108. min_num = "hello"
  1109. class ValidationTestModelAdmin(ModelAdmin):
  1110. inlines = [ValidationTestInline]
  1111. self.assertIsInvalid(
  1112. ValidationTestModelAdmin, ValidationTestModel,
  1113. "The value of 'min_num' must be an integer.",
  1114. 'admin.E205',
  1115. invalid_obj=ValidationTestInline)
  1116. def test_valid_case(self):
  1117. class ValidationTestInline(TabularInline):
  1118. model = ValidationTestInlineModel
  1119. min_num = 2
  1120. class ValidationTestModelAdmin(ModelAdmin):
  1121. inlines = [ValidationTestInline]
  1122. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1123. class FormsetCheckTests(CheckTestCase):
  1124. def test_invalid_type(self):
  1125. class FakeFormSet(object):
  1126. pass
  1127. class ValidationTestInline(TabularInline):
  1128. model = ValidationTestInlineModel
  1129. formset = FakeFormSet
  1130. class ValidationTestModelAdmin(ModelAdmin):
  1131. inlines = [ValidationTestInline]
  1132. self.assertIsInvalid(
  1133. ValidationTestModelAdmin, ValidationTestModel,
  1134. "The value of 'formset' must inherit from 'BaseModelFormSet'.",
  1135. 'admin.E206',
  1136. invalid_obj=ValidationTestInline)
  1137. def test_valid_case(self):
  1138. class RealModelFormSet(BaseModelFormSet):
  1139. pass
  1140. class ValidationTestInline(TabularInline):
  1141. model = ValidationTestInlineModel
  1142. formset = RealModelFormSet
  1143. class ValidationTestModelAdmin(ModelAdmin):
  1144. inlines = [ValidationTestInline]
  1145. self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
  1146. class ListDisplayEditableTests(CheckTestCase):
  1147. def test_list_display_links_is_none(self):
  1148. """
  1149. list_display and list_editable can contain the same values
  1150. when list_display_links is None
  1151. """
  1152. class ProductAdmin(ModelAdmin):
  1153. list_display = ['name', 'slug', 'pub_date']
  1154. list_editable = list_display
  1155. list_display_links = None
  1156. self.assertIsValid(ProductAdmin, ValidationTestModel)
  1157. def test_list_display_same_as_list_editable(self):
  1158. """
  1159. The first item in list_display can be the same as the first
  1160. in list_editable
  1161. """
  1162. class ProductAdmin(ModelAdmin):
  1163. list_display = ['name', 'slug', 'pub_date']
  1164. list_editable = ['name', 'slug']
  1165. list_display_links = ['pub_date']
  1166. self.assertIsValid(ProductAdmin, ValidationTestModel)
  1167. class ModelAdminPermissionTests(SimpleTestCase):
  1168. class MockUser(object):
  1169. def has_module_perms(self, app_label):
  1170. if app_label == "modeladmin":
  1171. return True
  1172. return False
  1173. class MockAddUser(MockUser):
  1174. def has_perm(self, perm):
  1175. if perm == "modeladmin.add_band":
  1176. return True
  1177. return False
  1178. class MockChangeUser(MockUser):
  1179. def has_perm(self, perm):
  1180. if perm == "modeladmin.change_band":
  1181. return True
  1182. return False
  1183. class MockDeleteUser(MockUser):
  1184. def has_perm(self, perm):
  1185. if perm == "modeladmin.delete_band":
  1186. return True
  1187. return False
  1188. def test_has_add_permission(self):
  1189. """
  1190. Ensure that has_add_permission returns True for users who can add
  1191. objects and False for users who can't.
  1192. """
  1193. ma = ModelAdmin(Band, AdminSite())
  1194. request = MockRequest()
  1195. request.user = self.MockAddUser()
  1196. self.assertTrue(ma.has_add_permission(request))
  1197. request.user = self.MockChangeUser()
  1198. self.assertFalse(ma.has_add_permission(request))
  1199. request.user = self.MockDeleteUser()
  1200. self.assertFalse(ma.has_add_permission(request))
  1201. def test_has_change_permission(self):
  1202. """
  1203. Ensure that has_change_permission returns True for users who can edit
  1204. objects and False for users who can't.
  1205. """
  1206. ma = ModelAdmin(Band, AdminSite())
  1207. request = MockRequest()
  1208. request.user = self.MockAddUser()
  1209. self.assertFalse(ma.has_change_permission(request))
  1210. request.user = self.MockChangeUser()
  1211. self.assertTrue(ma.has_change_permission(request))
  1212. request.user = self.MockDeleteUser()
  1213. self.assertFalse(ma.has_change_permission(request))
  1214. def test_has_delete_permission(self):
  1215. """
  1216. Ensure that has_delete_permission returns True for users who can delete
  1217. objects and False for users who can't.
  1218. """
  1219. ma = ModelAdmin(Band, AdminSite())
  1220. request = MockRequest()
  1221. request.user = self.MockAddUser()
  1222. self.assertFalse(ma.has_delete_permission(request))
  1223. request.user = self.MockChangeUser()
  1224. self.assertFalse(ma.has_delete_permission(request))
  1225. request.user = self.MockDeleteUser()
  1226. self.assertTrue(ma.has_delete_permission(request))
  1227. def test_has_module_permission(self):
  1228. """
  1229. Ensure that has_module_permission returns True for users who have any
  1230. permission for the module and False for users who don't.
  1231. """
  1232. ma = ModelAdmin(Band, AdminSite())
  1233. request = MockRequest()
  1234. request.user = self.MockAddUser()
  1235. self.assertTrue(ma.has_module_permission(request))
  1236. request.user = self.MockChangeUser()
  1237. self.assertTrue(ma.has_module_permission(request))
  1238. request.user = self.MockDeleteUser()
  1239. self.assertTrue(ma.has_module_permission(request))
  1240. original_app_label = ma.opts.app_label
  1241. ma.opts.app_label = 'anotherapp'
  1242. try:
  1243. request.user = self.MockAddUser()
  1244. self.assertFalse(ma.has_module_permission(request))
  1245. request.user = self.MockChangeUser()
  1246. self.assertFalse(ma.has_module_permission(request))
  1247. request.user = self.MockDeleteUser()
  1248. self.assertFalse(ma.has_module_permission(request))
  1249. finally:
  1250. ma.opts.app_label = original_app_label