tests.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  1. from datetime import date
  2. from django import forms
  3. from django.conf import settings
  4. from django.contrib.admin.options import ModelAdmin, TabularInline, \
  5. HORIZONTAL, VERTICAL
  6. from django.contrib.admin.sites import AdminSite
  7. from django.contrib.admin.validation import validate
  8. from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
  9. from django.core.exceptions import ImproperlyConfigured
  10. from django.forms.models import BaseModelFormSet
  11. from django.forms.widgets import Select
  12. from django.test import TestCase
  13. from django.utils import unittest
  14. from models import Band, Concert, ValidationTestModel, \
  15. ValidationTestInlineModel
  16. # None of the following tests really depend on the content of the request,
  17. # so we'll just pass in None.
  18. request = None
  19. class ModelAdminTests(TestCase):
  20. def setUp(self):
  21. self.band = Band.objects.create(
  22. name='The Doors',
  23. bio='',
  24. sign_date=date(1965, 1, 1),
  25. )
  26. self.site = AdminSite()
  27. # form/fields/fieldsets interaction ##############################
  28. def test_default_fields(self):
  29. ma = ModelAdmin(Band, self.site)
  30. self.assertEqual(ma.get_form(request).base_fields.keys(),
  31. ['name', 'bio', 'sign_date'])
  32. def test_default_fieldsets(self):
  33. # fieldsets_add and fieldsets_change should return a special data structure that
  34. # is used in the templates. They should generate the "right thing" whether we
  35. # have specified a custom form, the fields argument, or nothing at all.
  36. #
  37. # Here's the default case. There are no custom form_add/form_change methods,
  38. # no fields argument, and no fieldsets argument.
  39. ma = ModelAdmin(Band, self.site)
  40. self.assertEqual(ma.get_fieldsets(request),
  41. [(None, {'fields': ['name', 'bio', 'sign_date']})])
  42. self.assertEqual(ma.get_fieldsets(request, self.band),
  43. [(None, {'fields': ['name', 'bio', 'sign_date']})])
  44. def test_field_arguments(self):
  45. # If we specify the fields argument, fieldsets_add and fielsets_change should
  46. # just stick the fields into a formsets structure and return it.
  47. class BandAdmin(ModelAdmin):
  48. fields = ['name']
  49. ma = BandAdmin(Band, self.site)
  50. self.assertEqual( ma.get_fieldsets(request),
  51. [(None, {'fields': ['name']})])
  52. self.assertEqual(ma.get_fieldsets(request, self.band),
  53. [(None, {'fields': ['name']})])
  54. def test_field_arguments_restricted_on_form(self):
  55. # If we specify fields or fieldsets, it should exclude fields on the Form class
  56. # to the fields specified. This may cause errors to be raised in the db layer if
  57. # required model fields arent in fields/fieldsets, but that's preferable to
  58. # ghost errors where you have a field in your Form class that isn't being
  59. # displayed because you forgot to add it to fields/fieldsets
  60. # Using `fields`.
  61. class BandAdmin(ModelAdmin):
  62. fields = ['name']
  63. ma = BandAdmin(Band, self.site)
  64. self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
  65. self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
  66. ['name'])
  67. # Using `fieldsets`.
  68. class BandAdmin(ModelAdmin):
  69. fieldsets = [(None, {'fields': ['name']})]
  70. ma = BandAdmin(Band, self.site)
  71. self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
  72. self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
  73. ['name'])
  74. # Using `exclude`.
  75. class BandAdmin(ModelAdmin):
  76. exclude = ['bio']
  77. ma = BandAdmin(Band, self.site)
  78. self.assertEqual(ma.get_form(request).base_fields.keys(),
  79. ['name', 'sign_date'])
  80. # You can also pass a tuple to `exclude`.
  81. class BandAdmin(ModelAdmin):
  82. exclude = ('bio',)
  83. ma = BandAdmin(Band, self.site)
  84. self.assertEqual(ma.get_form(request).base_fields.keys(),
  85. ['name', 'sign_date'])
  86. # Using `fields` and `exclude`.
  87. class BandAdmin(ModelAdmin):
  88. fields = ['name', 'bio']
  89. exclude = ['bio']
  90. ma = BandAdmin(Band, self.site)
  91. self.assertEqual(ma.get_form(request).base_fields.keys(),
  92. ['name'])
  93. def test_custom_form_validation(self):
  94. # If we specify a form, it should use it allowing custom validation to work
  95. # properly. This won't, however, break any of the admin widgets or media.
  96. class AdminBandForm(forms.ModelForm):
  97. delete = forms.BooleanField()
  98. class Meta:
  99. model = Band
  100. class BandAdmin(ModelAdmin):
  101. form = AdminBandForm
  102. ma = BandAdmin(Band, self.site)
  103. self.assertEqual(ma.get_form(request).base_fields.keys(),
  104. ['name', 'bio', 'sign_date', 'delete'])
  105. self.assertEqual(
  106. type(ma.get_form(request).base_fields['sign_date'].widget),
  107. AdminDateWidget)
  108. def test_queryset_override(self):
  109. # If we need to override the queryset of a ModelChoiceField in our custom form
  110. # make sure that RelatedFieldWidgetWrapper doesn't mess that up.
  111. band2 = Band(name='The Beatles', bio='', sign_date=date(1962, 1, 1))
  112. band2.save()
  113. class ConcertAdmin(ModelAdmin):
  114. pass
  115. ma = ConcertAdmin(Concert, self.site)
  116. form = ma.get_form(request)()
  117. self.assertEqual(str(form["main_band"]),
  118. '<select name="main_band" id="id_main_band">\n'
  119. '<option value="" selected="selected">---------</option>\n'
  120. '<option value="%d">The Doors</option>\n'
  121. '<option value="%d">The Beatles</option>\n'
  122. '</select>' % (self.band.id, band2.id))
  123. class AdminConcertForm(forms.ModelForm):
  124. class Meta:
  125. model = Concert
  126. def __init__(self, *args, **kwargs):
  127. super(AdminConcertForm, self).__init__(*args, **kwargs)
  128. self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')
  129. class ConcertAdmin(ModelAdmin):
  130. form = AdminConcertForm
  131. ma = ConcertAdmin(Concert, self.site)
  132. form = ma.get_form(request)()
  133. self.assertEqual(str(form["main_band"]),
  134. '<select name="main_band" id="id_main_band">\n'
  135. '<option value="" selected="selected">---------</option>\n'
  136. '<option value="%d">The Doors</option>\n'
  137. '</select>' % self.band.id)
  138. # radio_fields behavior ###########################################
  139. def test_default_foreign_key_widget(self):
  140. # First, without any radio_fields specified, the widgets for ForeignKey
  141. # and fields with choices specified ought to be a basic Select widget.
  142. # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
  143. # they need to be handled properly when type checking. For Select fields, all of
  144. # the choices lists have a first entry of dashes.
  145. cma = ModelAdmin(Concert, self.site)
  146. cmafa = cma.get_form(request)
  147. self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
  148. Select)
  149. self.assertEqual(
  150. list(cmafa.base_fields['main_band'].widget.choices),
  151. [(u'', u'---------'), (self.band.id, u'The Doors')])
  152. self.assertEqual(
  153. type(cmafa.base_fields['opening_band'].widget.widget), Select)
  154. self.assertEqual(
  155. list(cmafa.base_fields['opening_band'].widget.choices),
  156. [(u'', u'---------'), (self.band.id, u'The Doors')])
  157. self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
  158. self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
  159. [('', '---------'), (1, 'Fri'), (2, 'Sat')])
  160. self.assertEqual(type(cmafa.base_fields['transport'].widget),
  161. Select)
  162. self.assertEqual(
  163. list(cmafa.base_fields['transport'].widget.choices),
  164. [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
  165. def test_foreign_key_as_radio_field(self):
  166. # Now specify all the fields as radio_fields. Widgets should now be
  167. # RadioSelect, and the choices list should have a first entry of 'None' if
  168. # blank=True for the model field. Finally, the widget should have the
  169. # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
  170. class ConcertAdmin(ModelAdmin):
  171. radio_fields = {
  172. 'main_band': HORIZONTAL,
  173. 'opening_band': VERTICAL,
  174. 'day': VERTICAL,
  175. 'transport': HORIZONTAL,
  176. }
  177. cma = ConcertAdmin(Concert, self.site)
  178. cmafa = cma.get_form(request)
  179. self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
  180. AdminRadioSelect)
  181. self.assertEqual(cmafa.base_fields['main_band'].widget.attrs,
  182. {'class': 'radiolist inline'})
  183. self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices),
  184. [(self.band.id, u'The Doors')])
  185. self.assertEqual(
  186. type(cmafa.base_fields['opening_band'].widget.widget),
  187. AdminRadioSelect)
  188. self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs,
  189. {'class': 'radiolist'})
  190. self.assertEqual(
  191. list(cmafa.base_fields['opening_band'].widget.choices),
  192. [(u'', u'None'), (self.band.id, u'The Doors')])
  193. self.assertEqual(type(cmafa.base_fields['day'].widget),
  194. AdminRadioSelect)
  195. self.assertEqual(cmafa.base_fields['day'].widget.attrs,
  196. {'class': 'radiolist'})
  197. self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
  198. [(1, 'Fri'), (2, 'Sat')])
  199. self.assertEqual(type(cmafa.base_fields['transport'].widget),
  200. AdminRadioSelect)
  201. self.assertEqual(cmafa.base_fields['transport'].widget.attrs,
  202. {'class': 'radiolist inline'})
  203. self.assertEqual(list(cmafa.base_fields['transport'].widget.choices),
  204. [('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
  205. class AdminConcertForm(forms.ModelForm):
  206. class Meta:
  207. model = Concert
  208. exclude = ('transport',)
  209. class ConcertAdmin(ModelAdmin):
  210. form = AdminConcertForm
  211. ma = ConcertAdmin(Concert, self.site)
  212. self.assertEqual(ma.get_form(request).base_fields.keys(),
  213. ['main_band', 'opening_band', 'day'])
  214. class AdminConcertForm(forms.ModelForm):
  215. extra = forms.CharField()
  216. class Meta:
  217. model = Concert
  218. fields = ['extra', 'transport']
  219. class ConcertAdmin(ModelAdmin):
  220. form = AdminConcertForm
  221. ma = ConcertAdmin(Concert, self.site)
  222. self.assertEqual(ma.get_form(request).base_fields.keys(),
  223. ['extra', 'transport'])
  224. class ConcertInline(TabularInline):
  225. form = AdminConcertForm
  226. model = Concert
  227. fk_name = 'main_band'
  228. can_delete = True
  229. class BandAdmin(ModelAdmin):
  230. inlines = [
  231. ConcertInline
  232. ]
  233. ma = BandAdmin(Band, self.site)
  234. self.assertEqual(
  235. list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
  236. ['extra', 'transport', 'id', 'DELETE', 'main_band'])
  237. class ValidationTests(unittest.TestCase):
  238. def test_validation_only_runs_in_debug(self):
  239. # Ensure validation only runs when DEBUG = True
  240. try:
  241. settings.DEBUG = True
  242. class ValidationTestModelAdmin(ModelAdmin):
  243. raw_id_fields = 10
  244. site = AdminSite()
  245. self.assertRaisesRegexp(
  246. ImproperlyConfigured,
  247. "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
  248. site.register,
  249. ValidationTestModel,
  250. ValidationTestModelAdmin,
  251. )
  252. finally:
  253. settings.DEBUG = False
  254. site = AdminSite()
  255. site.register(ValidationTestModel, ValidationTestModelAdmin)
  256. def test_raw_id_fields_validation(self):
  257. class ValidationTestModelAdmin(ModelAdmin):
  258. raw_id_fields = 10
  259. self.assertRaisesRegexp(
  260. ImproperlyConfigured,
  261. "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
  262. validate,
  263. ValidationTestModelAdmin,
  264. ValidationTestModel,
  265. )
  266. class ValidationTestModelAdmin(ModelAdmin):
  267. raw_id_fields = ('non_existent_field',)
  268. self.assertRaisesRegexp(
  269. ImproperlyConfigured,
  270. "'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  271. validate,
  272. ValidationTestModelAdmin,
  273. ValidationTestModel,
  274. )
  275. class ValidationTestModelAdmin(ModelAdmin):
  276. raw_id_fields = ('name',)
  277. self.assertRaisesRegexp(
  278. ImproperlyConfigured,
  279. "'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.",
  280. validate,
  281. ValidationTestModelAdmin,
  282. ValidationTestModel,
  283. )
  284. class ValidationTestModelAdmin(ModelAdmin):
  285. raw_id_fields = ('users',)
  286. validate(ValidationTestModelAdmin, ValidationTestModel)
  287. def test_fieldsets_validation(self):
  288. class ValidationTestModelAdmin(ModelAdmin):
  289. fieldsets = 10
  290. self.assertRaisesRegexp(
  291. ImproperlyConfigured,
  292. "'ValidationTestModelAdmin.fieldsets' must be a list or tuple.",
  293. validate,
  294. ValidationTestModelAdmin,
  295. ValidationTestModel,
  296. )
  297. class ValidationTestModelAdmin(ModelAdmin):
  298. fieldsets = ({},)
  299. self.assertRaisesRegexp(
  300. ImproperlyConfigured,
  301. "'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.",
  302. validate,
  303. ValidationTestModelAdmin,
  304. ValidationTestModel,
  305. )
  306. class ValidationTestModelAdmin(ModelAdmin):
  307. fieldsets = ((),)
  308. self.assertRaisesRegexp(
  309. ImproperlyConfigured,
  310. "'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.",
  311. validate,
  312. ValidationTestModelAdmin,
  313. ValidationTestModel,
  314. )
  315. class ValidationTestModelAdmin(ModelAdmin):
  316. fieldsets = (("General", ()),)
  317. self.assertRaisesRegexp(
  318. ImproperlyConfigured,
  319. "'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.",
  320. validate,
  321. ValidationTestModelAdmin,
  322. ValidationTestModel,
  323. )
  324. class ValidationTestModelAdmin(ModelAdmin):
  325. fieldsets = (("General", {}),)
  326. self.assertRaisesRegexp(
  327. ImproperlyConfigured,
  328. "'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.",
  329. validate,
  330. ValidationTestModelAdmin,
  331. ValidationTestModel,
  332. )
  333. class ValidationTestModelAdmin(ModelAdmin):
  334. fieldsets = (("General", {"fields": ("non_existent_field",)}),)
  335. self.assertRaisesRegexp(
  336. ImproperlyConfigured,
  337. "'ValidationTestModelAdmin.fieldsets\[0\]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.",
  338. validate,
  339. ValidationTestModelAdmin,
  340. ValidationTestModel,
  341. )
  342. class ValidationTestModelAdmin(ModelAdmin):
  343. fieldsets = (("General", {"fields": ("name",)}),)
  344. validate(ValidationTestModelAdmin, ValidationTestModel)
  345. class ValidationTestModelAdmin(ModelAdmin):
  346. fieldsets = (("General", {"fields": ("name",)}),)
  347. fields = ["name",]
  348. self.assertRaisesRegexp(
  349. ImproperlyConfigured,
  350. "Both fieldsets and fields are specified in ValidationTestModelAdmin.",
  351. validate,
  352. ValidationTestModelAdmin,
  353. ValidationTestModel,
  354. )
  355. class ValidationTestModelAdmin(ModelAdmin):
  356. fieldsets = [(None, {'fields': ['name', 'name']})]
  357. self.assertRaisesRegexp(
  358. ImproperlyConfigured,
  359. "There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets",
  360. validate,
  361. ValidationTestModelAdmin,
  362. ValidationTestModel,
  363. )
  364. class ValidationTestModelAdmin(ModelAdmin):
  365. fields = ["name", "name"]
  366. self.assertRaisesRegexp(
  367. ImproperlyConfigured,
  368. "There are duplicate field\(s\) in ValidationTestModelAdmin.fields",
  369. validate,
  370. ValidationTestModelAdmin,
  371. ValidationTestModel,
  372. )
  373. def test_form_validation(self):
  374. class FakeForm(object):
  375. pass
  376. class ValidationTestModelAdmin(ModelAdmin):
  377. form = FakeForm
  378. self.assertRaisesRegexp(
  379. ImproperlyConfigured,
  380. "ValidationTestModelAdmin.form does not inherit from BaseModelForm.",
  381. validate,
  382. ValidationTestModelAdmin,
  383. ValidationTestModel,
  384. )
  385. def test_fieldsets_with_custom_form_validation(self):
  386. class BandAdmin(ModelAdmin):
  387. fieldsets = (
  388. ('Band', {
  389. 'fields': ('non_existent_field',)
  390. }),
  391. )
  392. self.assertRaisesRegexp(
  393. ImproperlyConfigured,
  394. "'BandAdmin.fieldsets\[0\]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.",
  395. validate,
  396. BandAdmin,
  397. Band,
  398. )
  399. class BandAdmin(ModelAdmin):
  400. fieldsets = (
  401. ('Band', {
  402. 'fields': ('name',)
  403. }),
  404. )
  405. validate(BandAdmin, Band)
  406. class AdminBandForm(forms.ModelForm):
  407. class Meta:
  408. model = Band
  409. class BandAdmin(ModelAdmin):
  410. form = AdminBandForm
  411. fieldsets = (
  412. ('Band', {
  413. 'fields': ('non_existent_field',)
  414. }),
  415. )
  416. self.assertRaisesRegexp(
  417. ImproperlyConfigured,
  418. "'BandAdmin.fieldsets\[0]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.",
  419. validate,
  420. BandAdmin,
  421. Band,
  422. )
  423. class AdminBandForm(forms.ModelForm):
  424. delete = forms.BooleanField()
  425. class Meta:
  426. model = Band
  427. class BandAdmin(ModelAdmin):
  428. form = AdminBandForm
  429. fieldsets = (
  430. ('Band', {
  431. 'fields': ('name', 'bio', 'sign_date', 'delete')
  432. }),
  433. )
  434. validate(BandAdmin, Band)
  435. def test_filter_vertical_validation(self):
  436. class ValidationTestModelAdmin(ModelAdmin):
  437. filter_vertical = 10
  438. self.assertRaisesRegexp(
  439. ImproperlyConfigured,
  440. "'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.",
  441. validate,
  442. ValidationTestModelAdmin,
  443. ValidationTestModel,
  444. )
  445. class ValidationTestModelAdmin(ModelAdmin):
  446. filter_vertical = ("non_existent_field",)
  447. self.assertRaisesRegexp(
  448. ImproperlyConfigured,
  449. "'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  450. validate,
  451. ValidationTestModelAdmin,
  452. ValidationTestModel,
  453. )
  454. class ValidationTestModelAdmin(ModelAdmin):
  455. filter_vertical = ("name",)
  456. self.assertRaisesRegexp(
  457. ImproperlyConfigured,
  458. "'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.",
  459. validate,
  460. ValidationTestModelAdmin,
  461. ValidationTestModel,
  462. )
  463. class ValidationTestModelAdmin(ModelAdmin):
  464. filter_vertical = ("users",)
  465. validate(ValidationTestModelAdmin, ValidationTestModel)
  466. def test_filter_horizontal_validation(self):
  467. class ValidationTestModelAdmin(ModelAdmin):
  468. filter_horizontal = 10
  469. self.assertRaisesRegexp(
  470. ImproperlyConfigured,
  471. "'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.",
  472. validate,
  473. ValidationTestModelAdmin,
  474. ValidationTestModel,
  475. )
  476. class ValidationTestModelAdmin(ModelAdmin):
  477. filter_horizontal = ("non_existent_field",)
  478. self.assertRaisesRegexp(
  479. ImproperlyConfigured,
  480. "'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  481. validate,
  482. ValidationTestModelAdmin,
  483. ValidationTestModel,
  484. )
  485. class ValidationTestModelAdmin(ModelAdmin):
  486. filter_horizontal = ("name",)
  487. self.assertRaisesRegexp(
  488. ImproperlyConfigured,
  489. "'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.",
  490. validate,
  491. ValidationTestModelAdmin,
  492. ValidationTestModel,
  493. )
  494. class ValidationTestModelAdmin(ModelAdmin):
  495. filter_horizontal = ("users",)
  496. validate(ValidationTestModelAdmin, ValidationTestModel)
  497. def test_radio_fields_validation(self):
  498. class ValidationTestModelAdmin(ModelAdmin):
  499. radio_fields = ()
  500. self.assertRaisesRegexp(
  501. ImproperlyConfigured,
  502. "'ValidationTestModelAdmin.radio_fields' must be a dictionary.",
  503. validate,
  504. ValidationTestModelAdmin,
  505. ValidationTestModel,
  506. )
  507. class ValidationTestModelAdmin(ModelAdmin):
  508. radio_fields = {"non_existent_field": None}
  509. self.assertRaisesRegexp(
  510. ImproperlyConfigured,
  511. "'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  512. validate,
  513. ValidationTestModelAdmin,
  514. ValidationTestModel,
  515. )
  516. class ValidationTestModelAdmin(ModelAdmin):
  517. radio_fields = {"name": None}
  518. self.assertRaisesRegexp(
  519. ImproperlyConfigured,
  520. "'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.",
  521. validate,
  522. ValidationTestModelAdmin,
  523. ValidationTestModel,
  524. )
  525. class ValidationTestModelAdmin(ModelAdmin):
  526. radio_fields = {"state": None}
  527. self.assertRaisesRegexp(
  528. ImproperlyConfigured,
  529. "'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.",
  530. validate,
  531. ValidationTestModelAdmin,
  532. ValidationTestModel,
  533. )
  534. class ValidationTestModelAdmin(ModelAdmin):
  535. radio_fields = {"state": VERTICAL}
  536. validate(ValidationTestModelAdmin, ValidationTestModel)
  537. def test_prepopulated_fields_validation(self):
  538. class ValidationTestModelAdmin(ModelAdmin):
  539. prepopulated_fields = ()
  540. self.assertRaisesRegexp(
  541. ImproperlyConfigured,
  542. "'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.",
  543. validate,
  544. ValidationTestModelAdmin,
  545. ValidationTestModel,
  546. )
  547. class ValidationTestModelAdmin(ModelAdmin):
  548. prepopulated_fields = {"non_existent_field": None}
  549. self.assertRaisesRegexp(
  550. ImproperlyConfigured,
  551. "'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  552. validate,
  553. ValidationTestModelAdmin,
  554. ValidationTestModel,
  555. )
  556. class ValidationTestModelAdmin(ModelAdmin):
  557. prepopulated_fields = {"slug": ("non_existent_field",)}
  558. self.assertRaisesRegexp(
  559. ImproperlyConfigured,
  560. "'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  561. validate,
  562. ValidationTestModelAdmin,
  563. ValidationTestModel,
  564. )
  565. class ValidationTestModelAdmin(ModelAdmin):
  566. prepopulated_fields = {"users": ("name",)}
  567. self.assertRaisesRegexp(
  568. ImproperlyConfigured,
  569. "'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.",
  570. validate,
  571. ValidationTestModelAdmin,
  572. ValidationTestModel,
  573. )
  574. class ValidationTestModelAdmin(ModelAdmin):
  575. prepopulated_fields = {"slug": ("name",)}
  576. validate(ValidationTestModelAdmin, ValidationTestModel)
  577. def test_list_display_validation(self):
  578. class ValidationTestModelAdmin(ModelAdmin):
  579. list_display = 10
  580. self.assertRaisesRegexp(
  581. ImproperlyConfigured,
  582. "'ValidationTestModelAdmin.list_display' must be a list or tuple.",
  583. validate,
  584. ValidationTestModelAdmin,
  585. ValidationTestModel,
  586. )
  587. class ValidationTestModelAdmin(ModelAdmin):
  588. list_display = ('non_existent_field',)
  589. self.assertRaisesRegexp(
  590. ImproperlyConfigured,
  591. "ValidationTestModelAdmin.list_display\[0\], 'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'.",
  592. validate,
  593. ValidationTestModelAdmin,
  594. ValidationTestModel,
  595. )
  596. class ValidationTestModelAdmin(ModelAdmin):
  597. list_display = ('users',)
  598. self.assertRaisesRegexp(
  599. ImproperlyConfigured,
  600. "'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.",
  601. validate,
  602. ValidationTestModelAdmin,
  603. ValidationTestModel,
  604. )
  605. def a_callable(obj):
  606. pass
  607. class ValidationTestModelAdmin(ModelAdmin):
  608. def a_method(self, obj):
  609. pass
  610. list_display = ('name', 'decade_published_in', 'a_method', a_callable)
  611. validate(ValidationTestModelAdmin, ValidationTestModel)
  612. def test_list_display_links_validation(self):
  613. class ValidationTestModelAdmin(ModelAdmin):
  614. list_display_links = 10
  615. self.assertRaisesRegexp(
  616. ImproperlyConfigured,
  617. "'ValidationTestModelAdmin.list_display_links' must be a list or tuple.",
  618. validate,
  619. ValidationTestModelAdmin,
  620. ValidationTestModel,
  621. )
  622. class ValidationTestModelAdmin(ModelAdmin):
  623. list_display_links = ('non_existent_field',)
  624. self.assertRaisesRegexp(
  625. ImproperlyConfigured,
  626. "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.",
  627. validate,
  628. ValidationTestModelAdmin,
  629. ValidationTestModel,
  630. )
  631. class ValidationTestModelAdmin(ModelAdmin):
  632. list_display_links = ('name',)
  633. self.assertRaisesRegexp(
  634. ImproperlyConfigured,
  635. "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.",
  636. validate,
  637. ValidationTestModelAdmin,
  638. ValidationTestModel,
  639. )
  640. def a_callable(obj):
  641. pass
  642. class ValidationTestModelAdmin(ModelAdmin):
  643. def a_method(self, obj):
  644. pass
  645. list_display = ('name', 'decade_published_in', 'a_method', a_callable)
  646. list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)
  647. validate(ValidationTestModelAdmin, ValidationTestModel)
  648. def test_list_filter_validation(self):
  649. class ValidationTestModelAdmin(ModelAdmin):
  650. list_filter = 10
  651. self.assertRaisesRegexp(
  652. ImproperlyConfigured,
  653. "'ValidationTestModelAdmin.list_filter' must be a list or tuple.",
  654. validate,
  655. ValidationTestModelAdmin,
  656. ValidationTestModel,
  657. )
  658. class ValidationTestModelAdmin(ModelAdmin):
  659. list_filter = ('non_existent_field',)
  660. self.assertRaisesRegexp(
  661. ImproperlyConfigured,
  662. "'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.",
  663. validate,
  664. ValidationTestModelAdmin,
  665. ValidationTestModel,
  666. )
  667. class ValidationTestModelAdmin(ModelAdmin):
  668. list_filter = ('is_active',)
  669. validate(ValidationTestModelAdmin, ValidationTestModel)
  670. def test_list_per_page_validation(self):
  671. class ValidationTestModelAdmin(ModelAdmin):
  672. list_per_page = 'hello'
  673. self.assertRaisesRegexp(
  674. ImproperlyConfigured,
  675. "'ValidationTestModelAdmin.list_per_page' should be a integer.",
  676. validate,
  677. ValidationTestModelAdmin,
  678. ValidationTestModel,
  679. )
  680. class ValidationTestModelAdmin(ModelAdmin):
  681. list_per_page = 100
  682. validate(ValidationTestModelAdmin, ValidationTestModel)
  683. def test_search_fields_validation(self):
  684. class ValidationTestModelAdmin(ModelAdmin):
  685. search_fields = 10
  686. self.assertRaisesRegexp(
  687. ImproperlyConfigured,
  688. "'ValidationTestModelAdmin.search_fields' must be a list or tuple.",
  689. validate,
  690. ValidationTestModelAdmin,
  691. ValidationTestModel,
  692. )
  693. def test_date_hierarchy_validation(self):
  694. class ValidationTestModelAdmin(ModelAdmin):
  695. date_hierarchy = 'non_existent_field'
  696. self.assertRaisesRegexp(
  697. ImproperlyConfigured,
  698. "'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  699. validate,
  700. ValidationTestModelAdmin,
  701. ValidationTestModel,
  702. )
  703. class ValidationTestModelAdmin(ModelAdmin):
  704. date_hierarchy = 'name'
  705. self.assertRaisesRegexp(
  706. ImproperlyConfigured,
  707. "'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.",
  708. validate,
  709. ValidationTestModelAdmin,
  710. ValidationTestModel,
  711. )
  712. class ValidationTestModelAdmin(ModelAdmin):
  713. date_hierarchy = 'pub_date'
  714. validate(ValidationTestModelAdmin, ValidationTestModel)
  715. def test_ordering_validation(self):
  716. class ValidationTestModelAdmin(ModelAdmin):
  717. ordering = 10
  718. self.assertRaisesRegexp(
  719. ImproperlyConfigured,
  720. "'ValidationTestModelAdmin.ordering' must be a list or tuple.",
  721. validate,
  722. ValidationTestModelAdmin,
  723. ValidationTestModel,
  724. )
  725. class ValidationTestModelAdmin(ModelAdmin):
  726. ordering = ('non_existent_field',)
  727. self.assertRaisesRegexp(
  728. ImproperlyConfigured,
  729. "'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
  730. validate,
  731. ValidationTestModelAdmin,
  732. ValidationTestModel,
  733. )
  734. class ValidationTestModelAdmin(ModelAdmin):
  735. ordering = ('?', 'name')
  736. self.assertRaisesRegexp(
  737. ImproperlyConfigured,
  738. "'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.",
  739. validate,
  740. ValidationTestModelAdmin,
  741. ValidationTestModel,
  742. )
  743. class ValidationTestModelAdmin(ModelAdmin):
  744. ordering = ('?',)
  745. validate(ValidationTestModelAdmin, ValidationTestModel)
  746. class ValidationTestModelAdmin(ModelAdmin):
  747. ordering = ('band__name',)
  748. validate(ValidationTestModelAdmin, ValidationTestModel)
  749. class ValidationTestModelAdmin(ModelAdmin):
  750. ordering = ('name',)
  751. validate(ValidationTestModelAdmin, ValidationTestModel)
  752. def test_list_select_related_validation(self):
  753. class ValidationTestModelAdmin(ModelAdmin):
  754. list_select_related = 1
  755. self.assertRaisesRegexp(
  756. ImproperlyConfigured,
  757. "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
  758. validate,
  759. ValidationTestModelAdmin,
  760. ValidationTestModel,
  761. )
  762. class ValidationTestModelAdmin(ModelAdmin):
  763. list_select_related = False
  764. validate(ValidationTestModelAdmin, ValidationTestModel)
  765. def test_save_as_validation(self):
  766. class ValidationTestModelAdmin(ModelAdmin):
  767. save_as = 1
  768. self.assertRaisesRegexp(
  769. ImproperlyConfigured,
  770. "'ValidationTestModelAdmin.save_as' should be a boolean.",
  771. validate,
  772. ValidationTestModelAdmin,
  773. ValidationTestModel,
  774. )
  775. class ValidationTestModelAdmin(ModelAdmin):
  776. save_as = True
  777. validate(ValidationTestModelAdmin, ValidationTestModel)
  778. def test_save_on_top_validation(self):
  779. class ValidationTestModelAdmin(ModelAdmin):
  780. save_on_top = 1
  781. self.assertRaisesRegexp(
  782. ImproperlyConfigured,
  783. "'ValidationTestModelAdmin.save_on_top' should be a boolean.",
  784. validate,
  785. ValidationTestModelAdmin,
  786. ValidationTestModel,
  787. )
  788. class ValidationTestModelAdmin(ModelAdmin):
  789. save_on_top = True
  790. validate(ValidationTestModelAdmin, ValidationTestModel)
  791. def test_inlines_validation(self):
  792. class ValidationTestModelAdmin(ModelAdmin):
  793. inlines = 10
  794. self.assertRaisesRegexp(
  795. ImproperlyConfigured,
  796. "'ValidationTestModelAdmin.inlines' must be a list or tuple.",
  797. validate,
  798. ValidationTestModelAdmin,
  799. ValidationTestModel,
  800. )
  801. class ValidationTestInline(object):
  802. pass
  803. class ValidationTestModelAdmin(ModelAdmin):
  804. inlines = [ValidationTestInline]
  805. self.assertRaisesRegexp(
  806. ImproperlyConfigured,
  807. "'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.",
  808. validate,
  809. ValidationTestModelAdmin,
  810. ValidationTestModel,
  811. )
  812. class ValidationTestInline(TabularInline):
  813. pass
  814. class ValidationTestModelAdmin(ModelAdmin):
  815. inlines = [ValidationTestInline]
  816. self.assertRaisesRegexp(
  817. ImproperlyConfigured,
  818. "'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.",
  819. validate,
  820. ValidationTestModelAdmin,
  821. ValidationTestModel,
  822. )
  823. class SomethingBad(object):
  824. pass
  825. class ValidationTestInline(TabularInline):
  826. model = SomethingBad
  827. class ValidationTestModelAdmin(ModelAdmin):
  828. inlines = [ValidationTestInline]
  829. self.assertRaisesRegexp(
  830. ImproperlyConfigured,
  831. "'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.",
  832. validate,
  833. ValidationTestModelAdmin,
  834. ValidationTestModel,
  835. )
  836. class ValidationTestInline(TabularInline):
  837. model = ValidationTestInlineModel
  838. class ValidationTestModelAdmin(ModelAdmin):
  839. inlines = [ValidationTestInline]
  840. validate(ValidationTestModelAdmin, ValidationTestModel)
  841. def test_fields_validation(self):
  842. class ValidationTestInline(TabularInline):
  843. model = ValidationTestInlineModel
  844. fields = 10
  845. class ValidationTestModelAdmin(ModelAdmin):
  846. inlines = [ValidationTestInline]
  847. self.assertRaisesRegexp(
  848. ImproperlyConfigured,
  849. "'ValidationTestInline.fields' must be a list or tuple.",
  850. validate,
  851. ValidationTestModelAdmin,
  852. ValidationTestModel,
  853. )
  854. class ValidationTestInline(TabularInline):
  855. model = ValidationTestInlineModel
  856. fields = ("non_existent_field",)
  857. class ValidationTestModelAdmin(ModelAdmin):
  858. inlines = [ValidationTestInline]
  859. self.assertRaisesRegexp(
  860. ImproperlyConfigured,
  861. "'ValidationTestInline.fields' refers to field 'non_existent_field' that is missing from the form.",
  862. validate,
  863. ValidationTestModelAdmin,
  864. ValidationTestModel,
  865. )
  866. def test_fk_name_validation(self):
  867. class ValidationTestInline(TabularInline):
  868. model = ValidationTestInlineModel
  869. fk_name = "non_existent_field"
  870. class ValidationTestModelAdmin(ModelAdmin):
  871. inlines = [ValidationTestInline]
  872. self.assertRaisesRegexp(
  873. ImproperlyConfigured,
  874. "'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'ValidationTestInlineModel'.",
  875. validate,
  876. ValidationTestModelAdmin,
  877. ValidationTestModel,
  878. )
  879. class ValidationTestInline(TabularInline):
  880. model = ValidationTestInlineModel
  881. fk_name = "parent"
  882. class ValidationTestModelAdmin(ModelAdmin):
  883. inlines = [ValidationTestInline]
  884. validate(ValidationTestModelAdmin, ValidationTestModel)
  885. def test_extra_validation(self):
  886. class ValidationTestInline(TabularInline):
  887. model = ValidationTestInlineModel
  888. extra = "hello"
  889. class ValidationTestModelAdmin(ModelAdmin):
  890. inlines = [ValidationTestInline]
  891. self.assertRaisesRegexp(
  892. ImproperlyConfigured,
  893. "'ValidationTestInline.extra' should be a integer.",
  894. validate,
  895. ValidationTestModelAdmin,
  896. ValidationTestModel,
  897. )
  898. class ValidationTestInline(TabularInline):
  899. model = ValidationTestInlineModel
  900. extra = 2
  901. class ValidationTestModelAdmin(ModelAdmin):
  902. inlines = [ValidationTestInline]
  903. validate(ValidationTestModelAdmin, ValidationTestModel)
  904. def test_max_num_validation(self):
  905. class ValidationTestInline(TabularInline):
  906. model = ValidationTestInlineModel
  907. max_num = "hello"
  908. class ValidationTestModelAdmin(ModelAdmin):
  909. inlines = [ValidationTestInline]
  910. self.assertRaisesRegexp(
  911. ImproperlyConfigured,
  912. "'ValidationTestInline.max_num' should be an integer or None \(default\).",
  913. validate,
  914. ValidationTestModelAdmin,
  915. ValidationTestModel,
  916. )
  917. class ValidationTestInline(TabularInline):
  918. model = ValidationTestInlineModel
  919. max_num = 2
  920. class ValidationTestModelAdmin(ModelAdmin):
  921. inlines = [ValidationTestInline]
  922. validate(ValidationTestModelAdmin, ValidationTestModel)
  923. def test_formset_validation(self):
  924. class FakeFormSet(object):
  925. pass
  926. class ValidationTestInline(TabularInline):
  927. model = ValidationTestInlineModel
  928. formset = FakeFormSet
  929. class ValidationTestModelAdmin(ModelAdmin):
  930. inlines = [ValidationTestInline]
  931. self.assertRaisesRegexp(
  932. ImproperlyConfigured,
  933. "'ValidationTestInline.formset' does not inherit from BaseModelFormSet.",
  934. validate,
  935. ValidationTestModelAdmin,
  936. ValidationTestModel,
  937. )
  938. class RealModelFormSet(BaseModelFormSet):
  939. pass
  940. class ValidationTestInline(TabularInline):
  941. model = ValidationTestInlineModel
  942. formset = RealModelFormSet
  943. class ValidationTestModelAdmin(ModelAdmin):
  944. inlines = [ValidationTestInline]
  945. validate(ValidationTestModelAdmin, ValidationTestModel)