tests.py 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549
  1. import datetime
  2. import sys
  3. import unittest
  4. from django.contrib.admin import (
  5. AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter,
  6. ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site,
  7. )
  8. from django.contrib.admin.options import IncorrectLookupParameters
  9. from django.contrib.auth.admin import UserAdmin
  10. from django.contrib.auth.models import User
  11. from django.core.exceptions import ImproperlyConfigured
  12. from django.test import RequestFactory, TestCase, override_settings
  13. from .models import (
  14. Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem,
  15. )
  16. def select_by(dictlist, key, value):
  17. return [x for x in dictlist if x[key] == value][0]
  18. class DecadeListFilter(SimpleListFilter):
  19. def lookups(self, request, model_admin):
  20. return (
  21. ('the 80s', "the 1980's"),
  22. ('the 90s', "the 1990's"),
  23. ('the 00s', "the 2000's"),
  24. ('other', "other decades"),
  25. )
  26. def queryset(self, request, queryset):
  27. decade = self.value()
  28. if decade == 'the 80s':
  29. return queryset.filter(year__gte=1980, year__lte=1989)
  30. if decade == 'the 90s':
  31. return queryset.filter(year__gte=1990, year__lte=1999)
  32. if decade == 'the 00s':
  33. return queryset.filter(year__gte=2000, year__lte=2009)
  34. class NotNinetiesListFilter(SimpleListFilter):
  35. title = "Not nineties books"
  36. parameter_name = "book_year"
  37. def lookups(self, request, model_admin):
  38. return (
  39. ('the 90s', "the 1990's"),
  40. )
  41. def queryset(self, request, queryset):
  42. if self.value() == 'the 90s':
  43. return queryset.filter(year__gte=1990, year__lte=1999)
  44. else:
  45. return queryset.exclude(year__gte=1990, year__lte=1999)
  46. class DecadeListFilterWithTitleAndParameter(DecadeListFilter):
  47. title = 'publication decade'
  48. parameter_name = 'publication-decade'
  49. class DecadeListFilterWithoutTitle(DecadeListFilter):
  50. parameter_name = 'publication-decade'
  51. class DecadeListFilterWithoutParameter(DecadeListFilter):
  52. title = 'publication decade'
  53. class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
  54. def lookups(self, request, model_admin):
  55. pass
  56. class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter):
  57. def queryset(self, request, queryset):
  58. raise 1 / 0
  59. class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter):
  60. def lookups(self, request, model_admin):
  61. qs = model_admin.get_queryset(request)
  62. if qs.filter(year__gte=1980, year__lte=1989).exists():
  63. yield ('the 80s', "the 1980's")
  64. if qs.filter(year__gte=1990, year__lte=1999).exists():
  65. yield ('the 90s', "the 1990's")
  66. if qs.filter(year__gte=2000, year__lte=2009).exists():
  67. yield ('the 00s', "the 2000's")
  68. class DecadeListFilterParameterEndsWith__In(DecadeListFilter):
  69. title = 'publication decade'
  70. parameter_name = 'decade__in' # Ends with '__in"
  71. class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter):
  72. title = 'publication decade'
  73. parameter_name = 'decade__isnull' # Ends with '__isnull"
  74. class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter):
  75. title = 'department'
  76. parameter_name = 'department'
  77. def lookups(self, request, model_admin):
  78. return sorted({
  79. (employee.department.id, # Intentionally not a string (Refs #19318)
  80. employee.department.code)
  81. for employee in model_admin.get_queryset(request).all()
  82. })
  83. def queryset(self, request, queryset):
  84. if self.value():
  85. return queryset.filter(department__id=self.value())
  86. class DepartmentListFilterLookupWithUnderscoredParameter(DepartmentListFilterLookupWithNonStringValue):
  87. parameter_name = 'department__whatever'
  88. class DepartmentListFilterLookupWithDynamicValue(DecadeListFilterWithTitleAndParameter):
  89. def lookups(self, request, model_admin):
  90. if self.value() == 'the 80s':
  91. return (('the 90s', "the 1990's"),)
  92. elif self.value() == 'the 90s':
  93. return (('the 80s', "the 1980's"),)
  94. else:
  95. return (('the 80s', "the 1980's"), ('the 90s', "the 1990's"),)
  96. class CustomUserAdmin(UserAdmin):
  97. list_filter = ('books_authored', 'books_contributed')
  98. class BookAdmin(ModelAdmin):
  99. list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no', 'availability')
  100. ordering = ('-id',)
  101. class BookAdminWithTupleBooleanFilter(BookAdmin):
  102. list_filter = (
  103. 'year',
  104. 'author',
  105. 'contributors',
  106. ('is_best_seller', BooleanFieldListFilter),
  107. 'date_registered',
  108. 'no',
  109. ('availability', BooleanFieldListFilter),
  110. )
  111. class BookAdminWithUnderscoreLookupAndTuple(BookAdmin):
  112. list_filter = (
  113. 'year',
  114. ('author__email', AllValuesFieldListFilter),
  115. 'contributors',
  116. 'is_best_seller',
  117. 'date_registered',
  118. 'no',
  119. )
  120. class BookAdminWithCustomQueryset(ModelAdmin):
  121. def __init__(self, user, *args, **kwargs):
  122. self.user = user
  123. super().__init__(*args, **kwargs)
  124. list_filter = ('year',)
  125. def get_queryset(self, request):
  126. return super().get_queryset(request).filter(author=self.user)
  127. class BookAdminRelatedOnlyFilter(ModelAdmin):
  128. list_filter = (
  129. 'year', 'is_best_seller', 'date_registered', 'no',
  130. ('author', RelatedOnlyFieldListFilter),
  131. ('contributors', RelatedOnlyFieldListFilter),
  132. ('employee__department', RelatedOnlyFieldListFilter),
  133. )
  134. ordering = ('-id',)
  135. class DecadeFilterBookAdmin(ModelAdmin):
  136. list_filter = ('author', DecadeListFilterWithTitleAndParameter)
  137. ordering = ('-id',)
  138. class NotNinetiesListFilterAdmin(ModelAdmin):
  139. list_filter = (NotNinetiesListFilter,)
  140. class DecadeFilterBookAdminWithoutTitle(ModelAdmin):
  141. list_filter = (DecadeListFilterWithoutTitle,)
  142. class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
  143. list_filter = (DecadeListFilterWithoutParameter,)
  144. class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
  145. list_filter = (DecadeListFilterWithNoneReturningLookups,)
  146. class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin):
  147. list_filter = (DecadeListFilterWithFailingQueryset,)
  148. class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
  149. list_filter = (DecadeListFilterWithQuerysetBasedLookups,)
  150. class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin):
  151. list_filter = (DecadeListFilterParameterEndsWith__In,)
  152. class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin):
  153. list_filter = (DecadeListFilterParameterEndsWith__Isnull,)
  154. class EmployeeAdmin(ModelAdmin):
  155. list_display = ['name', 'department']
  156. list_filter = ['department']
  157. class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
  158. list_filter = [DepartmentListFilterLookupWithNonStringValue]
  159. class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin):
  160. list_filter = [DepartmentListFilterLookupWithUnderscoredParameter]
  161. class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin):
  162. list_filter = [DepartmentListFilterLookupWithDynamicValue]
  163. class BookmarkAdminGenericRelation(ModelAdmin):
  164. list_filter = ['tags__tag']
  165. class BookAdminWithEmptyFieldListFilter(ModelAdmin):
  166. list_filter = [
  167. ('author', EmptyFieldListFilter),
  168. ('title', EmptyFieldListFilter),
  169. ('improvedbook', EmptyFieldListFilter),
  170. ]
  171. class DepartmentAdminWithEmptyFieldListFilter(ModelAdmin):
  172. list_filter = [
  173. ('description', EmptyFieldListFilter),
  174. ('employee', EmptyFieldListFilter),
  175. ]
  176. class ListFiltersTests(TestCase):
  177. request_factory = RequestFactory()
  178. @classmethod
  179. def setUpTestData(cls):
  180. cls.today = datetime.date.today()
  181. cls.tomorrow = cls.today + datetime.timedelta(days=1)
  182. cls.one_week_ago = cls.today - datetime.timedelta(days=7)
  183. if cls.today.month == 12:
  184. cls.next_month = cls.today.replace(year=cls.today.year + 1, month=1, day=1)
  185. else:
  186. cls.next_month = cls.today.replace(month=cls.today.month + 1, day=1)
  187. cls.next_year = cls.today.replace(year=cls.today.year + 1, month=1, day=1)
  188. # Users
  189. cls.alfred = User.objects.create_superuser('alfred', 'alfred@example.com', 'password')
  190. cls.bob = User.objects.create_user('bob', 'bob@example.com')
  191. cls.lisa = User.objects.create_user('lisa', 'lisa@example.com')
  192. # Books
  193. cls.djangonaut_book = Book.objects.create(
  194. title='Djangonaut: an art of living', year=2009,
  195. author=cls.alfred, is_best_seller=True, date_registered=cls.today,
  196. availability=True,
  197. )
  198. cls.bio_book = Book.objects.create(
  199. title='Django: a biography', year=1999, author=cls.alfred,
  200. is_best_seller=False, no=207,
  201. availability=False,
  202. )
  203. cls.django_book = Book.objects.create(
  204. title='The Django Book', year=None, author=cls.bob,
  205. is_best_seller=None, date_registered=cls.today, no=103,
  206. availability=True,
  207. )
  208. cls.guitar_book = Book.objects.create(
  209. title='Guitar for dummies', year=2002, is_best_seller=True,
  210. date_registered=cls.one_week_ago,
  211. availability=None,
  212. )
  213. cls.guitar_book.contributors.set([cls.bob, cls.lisa])
  214. # Departments
  215. cls.dev = Department.objects.create(code='DEV', description='Development')
  216. cls.design = Department.objects.create(code='DSN', description='Design')
  217. # Employees
  218. cls.john = Employee.objects.create(name='John Blue', department=cls.dev)
  219. cls.jack = Employee.objects.create(name='Jack Red', department=cls.design)
  220. def test_choicesfieldlistfilter_has_none_choice(self):
  221. """
  222. The last choice is for the None value.
  223. """
  224. class BookmarkChoicesAdmin(ModelAdmin):
  225. list_display = ['none_or_null']
  226. list_filter = ['none_or_null']
  227. modeladmin = BookmarkChoicesAdmin(Bookmark, site)
  228. request = self.request_factory.get('/', {})
  229. request.user = self.alfred
  230. changelist = modeladmin.get_changelist_instance(request)
  231. filterspec = changelist.get_filters(request)[0][0]
  232. choices = list(filterspec.choices(changelist))
  233. self.assertEqual(choices[-1]['display'], 'None')
  234. self.assertEqual(choices[-1]['query_string'], '?none_or_null__isnull=True')
  235. def test_datefieldlistfilter(self):
  236. modeladmin = BookAdmin(Book, site)
  237. request = self.request_factory.get('/')
  238. request.user = self.alfred
  239. changelist = modeladmin.get_changelist(request)
  240. request = self.request_factory.get('/', {
  241. 'date_registered__gte': self.today,
  242. 'date_registered__lt': self.tomorrow},
  243. )
  244. request.user = self.alfred
  245. changelist = modeladmin.get_changelist_instance(request)
  246. # Make sure the correct queryset is returned
  247. queryset = changelist.get_queryset(request)
  248. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  249. # Make sure the correct choice is selected
  250. filterspec = changelist.get_filters(request)[0][4]
  251. self.assertEqual(filterspec.title, 'date registered')
  252. choice = select_by(filterspec.choices(changelist), "display", "Today")
  253. self.assertIs(choice['selected'], True)
  254. self.assertEqual(
  255. choice['query_string'],
  256. '?date_registered__gte=%s&date_registered__lt=%s' % (
  257. self.today,
  258. self.tomorrow,
  259. )
  260. )
  261. request = self.request_factory.get('/', {
  262. 'date_registered__gte': self.today.replace(day=1),
  263. 'date_registered__lt': self.next_month},
  264. )
  265. request.user = self.alfred
  266. changelist = modeladmin.get_changelist_instance(request)
  267. # Make sure the correct queryset is returned
  268. queryset = changelist.get_queryset(request)
  269. if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month):
  270. # In case one week ago is in the same month.
  271. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  272. else:
  273. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  274. # Make sure the correct choice is selected
  275. filterspec = changelist.get_filters(request)[0][4]
  276. self.assertEqual(filterspec.title, 'date registered')
  277. choice = select_by(filterspec.choices(changelist), "display", "This month")
  278. self.assertIs(choice['selected'], True)
  279. self.assertEqual(
  280. choice['query_string'],
  281. '?date_registered__gte=%s&date_registered__lt=%s' % (
  282. self.today.replace(day=1),
  283. self.next_month,
  284. )
  285. )
  286. request = self.request_factory.get('/', {
  287. 'date_registered__gte': self.today.replace(month=1, day=1),
  288. 'date_registered__lt': self.next_year},
  289. )
  290. request.user = self.alfred
  291. changelist = modeladmin.get_changelist_instance(request)
  292. # Make sure the correct queryset is returned
  293. queryset = changelist.get_queryset(request)
  294. if self.today.year == self.one_week_ago.year:
  295. # In case one week ago is in the same year.
  296. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  297. else:
  298. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  299. # Make sure the correct choice is selected
  300. filterspec = changelist.get_filters(request)[0][4]
  301. self.assertEqual(filterspec.title, 'date registered')
  302. choice = select_by(filterspec.choices(changelist), "display", "This year")
  303. self.assertIs(choice['selected'], True)
  304. self.assertEqual(
  305. choice['query_string'],
  306. '?date_registered__gte=%s&date_registered__lt=%s' % (
  307. self.today.replace(month=1, day=1),
  308. self.next_year,
  309. )
  310. )
  311. request = self.request_factory.get('/', {
  312. 'date_registered__gte': str(self.one_week_ago),
  313. 'date_registered__lt': str(self.tomorrow),
  314. })
  315. request.user = self.alfred
  316. changelist = modeladmin.get_changelist_instance(request)
  317. # Make sure the correct queryset is returned
  318. queryset = changelist.get_queryset(request)
  319. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  320. # Make sure the correct choice is selected
  321. filterspec = changelist.get_filters(request)[0][4]
  322. self.assertEqual(filterspec.title, 'date registered')
  323. choice = select_by(filterspec.choices(changelist), "display", "Past 7 days")
  324. self.assertIs(choice['selected'], True)
  325. self.assertEqual(
  326. choice['query_string'],
  327. '?date_registered__gte=%s&date_registered__lt=%s' % (
  328. str(self.one_week_ago),
  329. str(self.tomorrow),
  330. )
  331. )
  332. # Null/not null queries
  333. request = self.request_factory.get('/', {'date_registered__isnull': 'True'})
  334. request.user = self.alfred
  335. changelist = modeladmin.get_changelist_instance(request)
  336. # Make sure the correct queryset is returned
  337. queryset = changelist.get_queryset(request)
  338. self.assertEqual(queryset.count(), 1)
  339. self.assertEqual(queryset[0], self.bio_book)
  340. # Make sure the correct choice is selected
  341. filterspec = changelist.get_filters(request)[0][4]
  342. self.assertEqual(filterspec.title, 'date registered')
  343. choice = select_by(filterspec.choices(changelist), 'display', 'No date')
  344. self.assertIs(choice['selected'], True)
  345. self.assertEqual(choice['query_string'], '?date_registered__isnull=True')
  346. request = self.request_factory.get('/', {'date_registered__isnull': 'False'})
  347. request.user = self.alfred
  348. changelist = modeladmin.get_changelist_instance(request)
  349. # Make sure the correct queryset is returned
  350. queryset = changelist.get_queryset(request)
  351. self.assertEqual(queryset.count(), 3)
  352. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  353. # Make sure the correct choice is selected
  354. filterspec = changelist.get_filters(request)[0][4]
  355. self.assertEqual(filterspec.title, 'date registered')
  356. choice = select_by(filterspec.choices(changelist), 'display', 'Has date')
  357. self.assertIs(choice['selected'], True)
  358. self.assertEqual(choice['query_string'], '?date_registered__isnull=False')
  359. @unittest.skipIf(
  360. sys.platform == 'win32',
  361. "Windows doesn't support setting a timezone that differs from the "
  362. "system timezone."
  363. )
  364. @override_settings(USE_TZ=True)
  365. def test_datefieldlistfilter_with_time_zone_support(self):
  366. # Regression for #17830
  367. self.test_datefieldlistfilter()
  368. def test_allvaluesfieldlistfilter(self):
  369. modeladmin = BookAdmin(Book, site)
  370. request = self.request_factory.get('/', {'year__isnull': 'True'})
  371. request.user = self.alfred
  372. changelist = modeladmin.get_changelist_instance(request)
  373. # Make sure the correct queryset is returned
  374. queryset = changelist.get_queryset(request)
  375. self.assertEqual(list(queryset), [self.django_book])
  376. # Make sure the last choice is None and is selected
  377. filterspec = changelist.get_filters(request)[0][0]
  378. self.assertEqual(filterspec.title, 'year')
  379. choices = list(filterspec.choices(changelist))
  380. self.assertIs(choices[-1]['selected'], True)
  381. self.assertEqual(choices[-1]['query_string'], '?year__isnull=True')
  382. request = self.request_factory.get('/', {'year': '2002'})
  383. request.user = self.alfred
  384. changelist = modeladmin.get_changelist_instance(request)
  385. # Make sure the correct choice is selected
  386. filterspec = changelist.get_filters(request)[0][0]
  387. self.assertEqual(filterspec.title, 'year')
  388. choices = list(filterspec.choices(changelist))
  389. self.assertIs(choices[2]['selected'], True)
  390. self.assertEqual(choices[2]['query_string'], '?year=2002')
  391. def test_allvaluesfieldlistfilter_custom_qs(self):
  392. # Make sure that correct filters are returned with custom querysets
  393. modeladmin = BookAdminWithCustomQueryset(self.alfred, Book, site)
  394. request = self.request_factory.get('/')
  395. request.user = self.alfred
  396. changelist = modeladmin.get_changelist_instance(request)
  397. filterspec = changelist.get_filters(request)[0][0]
  398. choices = list(filterspec.choices(changelist))
  399. # Should have 'All', 1999 and 2009 options i.e. the subset of years of
  400. # books written by alfred (which is the filtering criteria set by
  401. # BookAdminWithCustomQueryset.get_queryset())
  402. self.assertEqual(3, len(choices))
  403. self.assertEqual(choices[0]['query_string'], '?')
  404. self.assertEqual(choices[1]['query_string'], '?year=1999')
  405. self.assertEqual(choices[2]['query_string'], '?year=2009')
  406. def test_relatedfieldlistfilter_foreignkey(self):
  407. modeladmin = BookAdmin(Book, site)
  408. request = self.request_factory.get('/')
  409. request.user = self.alfred
  410. changelist = modeladmin.get_changelist_instance(request)
  411. # Make sure that all users are present in the author's list filter
  412. filterspec = changelist.get_filters(request)[0][1]
  413. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  414. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  415. request = self.request_factory.get('/', {'author__isnull': 'True'})
  416. request.user = self.alfred
  417. changelist = modeladmin.get_changelist_instance(request)
  418. # Make sure the correct queryset is returned
  419. queryset = changelist.get_queryset(request)
  420. self.assertEqual(list(queryset), [self.guitar_book])
  421. # Make sure the last choice is None and is selected
  422. filterspec = changelist.get_filters(request)[0][1]
  423. self.assertEqual(filterspec.title, 'Verbose Author')
  424. choices = list(filterspec.choices(changelist))
  425. self.assertIs(choices[-1]['selected'], True)
  426. self.assertEqual(choices[-1]['query_string'], '?author__isnull=True')
  427. request = self.request_factory.get('/', {'author__id__exact': self.alfred.pk})
  428. request.user = self.alfred
  429. changelist = modeladmin.get_changelist_instance(request)
  430. # Make sure the correct choice is selected
  431. filterspec = changelist.get_filters(request)[0][1]
  432. self.assertEqual(filterspec.title, 'Verbose Author')
  433. # order of choices depends on User model, which has no order
  434. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  435. self.assertIs(choice['selected'], True)
  436. self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk)
  437. def test_relatedfieldlistfilter_foreignkey_ordering(self):
  438. """RelatedFieldListFilter ordering respects ModelAdmin.ordering."""
  439. class EmployeeAdminWithOrdering(ModelAdmin):
  440. ordering = ('name',)
  441. class BookAdmin(ModelAdmin):
  442. list_filter = ('employee',)
  443. site.register(Employee, EmployeeAdminWithOrdering)
  444. self.addCleanup(lambda: site.unregister(Employee))
  445. modeladmin = BookAdmin(Book, site)
  446. request = self.request_factory.get('/')
  447. request.user = self.alfred
  448. changelist = modeladmin.get_changelist_instance(request)
  449. filterspec = changelist.get_filters(request)[0][0]
  450. expected = [(self.jack.pk, 'Jack Red'), (self.john.pk, 'John Blue')]
  451. self.assertEqual(filterspec.lookup_choices, expected)
  452. def test_relatedfieldlistfilter_foreignkey_ordering_reverse(self):
  453. class EmployeeAdminWithOrdering(ModelAdmin):
  454. ordering = ('-name',)
  455. class BookAdmin(ModelAdmin):
  456. list_filter = ('employee',)
  457. site.register(Employee, EmployeeAdminWithOrdering)
  458. self.addCleanup(lambda: site.unregister(Employee))
  459. modeladmin = BookAdmin(Book, site)
  460. request = self.request_factory.get('/')
  461. request.user = self.alfred
  462. changelist = modeladmin.get_changelist_instance(request)
  463. filterspec = changelist.get_filters(request)[0][0]
  464. expected = [(self.john.pk, 'John Blue'), (self.jack.pk, 'Jack Red')]
  465. self.assertEqual(filterspec.lookup_choices, expected)
  466. def test_relatedfieldlistfilter_foreignkey_default_ordering(self):
  467. """RelatedFieldListFilter ordering respects Model.ordering."""
  468. class BookAdmin(ModelAdmin):
  469. list_filter = ('employee',)
  470. self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering)
  471. Employee._meta.ordering = ('name',)
  472. modeladmin = BookAdmin(Book, site)
  473. request = self.request_factory.get('/')
  474. request.user = self.alfred
  475. changelist = modeladmin.get_changelist_instance(request)
  476. filterspec = changelist.get_filters(request)[0][0]
  477. expected = [(self.jack.pk, 'Jack Red'), (self.john.pk, 'John Blue')]
  478. self.assertEqual(filterspec.lookup_choices, expected)
  479. def test_relatedfieldlistfilter_manytomany(self):
  480. modeladmin = BookAdmin(Book, site)
  481. request = self.request_factory.get('/')
  482. request.user = self.alfred
  483. changelist = modeladmin.get_changelist_instance(request)
  484. # Make sure that all users are present in the contrib's list filter
  485. filterspec = changelist.get_filters(request)[0][2]
  486. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  487. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  488. request = self.request_factory.get('/', {'contributors__isnull': 'True'})
  489. request.user = self.alfred
  490. changelist = modeladmin.get_changelist_instance(request)
  491. # Make sure the correct queryset is returned
  492. queryset = changelist.get_queryset(request)
  493. self.assertEqual(list(queryset), [self.django_book, self.bio_book, self.djangonaut_book])
  494. # Make sure the last choice is None and is selected
  495. filterspec = changelist.get_filters(request)[0][2]
  496. self.assertEqual(filterspec.title, 'Verbose Contributors')
  497. choices = list(filterspec.choices(changelist))
  498. self.assertIs(choices[-1]['selected'], True)
  499. self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True')
  500. request = self.request_factory.get('/', {'contributors__id__exact': self.bob.pk})
  501. request.user = self.alfred
  502. changelist = modeladmin.get_changelist_instance(request)
  503. # Make sure the correct choice is selected
  504. filterspec = changelist.get_filters(request)[0][2]
  505. self.assertEqual(filterspec.title, 'Verbose Contributors')
  506. choice = select_by(filterspec.choices(changelist), "display", "bob")
  507. self.assertIs(choice['selected'], True)
  508. self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk)
  509. def test_relatedfieldlistfilter_reverse_relationships(self):
  510. modeladmin = CustomUserAdmin(User, site)
  511. # FK relationship -----
  512. request = self.request_factory.get('/', {'books_authored__isnull': 'True'})
  513. request.user = self.alfred
  514. changelist = modeladmin.get_changelist_instance(request)
  515. # Make sure the correct queryset is returned
  516. queryset = changelist.get_queryset(request)
  517. self.assertEqual(list(queryset), [self.lisa])
  518. # Make sure the last choice is None and is selected
  519. filterspec = changelist.get_filters(request)[0][0]
  520. self.assertEqual(filterspec.title, 'book')
  521. choices = list(filterspec.choices(changelist))
  522. self.assertIs(choices[-1]['selected'], True)
  523. self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True')
  524. request = self.request_factory.get('/', {'books_authored__id__exact': self.bio_book.pk})
  525. request.user = self.alfred
  526. changelist = modeladmin.get_changelist_instance(request)
  527. # Make sure the correct choice is selected
  528. filterspec = changelist.get_filters(request)[0][0]
  529. self.assertEqual(filterspec.title, 'book')
  530. choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title)
  531. self.assertIs(choice['selected'], True)
  532. self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk)
  533. # M2M relationship -----
  534. request = self.request_factory.get('/', {'books_contributed__isnull': 'True'})
  535. request.user = self.alfred
  536. changelist = modeladmin.get_changelist_instance(request)
  537. # Make sure the correct queryset is returned
  538. queryset = changelist.get_queryset(request)
  539. self.assertEqual(list(queryset), [self.alfred])
  540. # Make sure the last choice is None and is selected
  541. filterspec = changelist.get_filters(request)[0][1]
  542. self.assertEqual(filterspec.title, 'book')
  543. choices = list(filterspec.choices(changelist))
  544. self.assertIs(choices[-1]['selected'], True)
  545. self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True')
  546. request = self.request_factory.get('/', {'books_contributed__id__exact': self.django_book.pk})
  547. request.user = self.alfred
  548. changelist = modeladmin.get_changelist_instance(request)
  549. # Make sure the correct choice is selected
  550. filterspec = changelist.get_filters(request)[0][1]
  551. self.assertEqual(filterspec.title, 'book')
  552. choice = select_by(filterspec.choices(changelist), "display", self.django_book.title)
  553. self.assertIs(choice['selected'], True)
  554. self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk)
  555. # With one book, the list filter should appear because there is also a
  556. # (None) option.
  557. Book.objects.exclude(pk=self.djangonaut_book.pk).delete()
  558. filterspec = changelist.get_filters(request)[0]
  559. self.assertEqual(len(filterspec), 2)
  560. # With no books remaining, no list filters should appear.
  561. Book.objects.all().delete()
  562. filterspec = changelist.get_filters(request)[0]
  563. self.assertEqual(len(filterspec), 0)
  564. def test_relatedfieldlistfilter_reverse_relationships_default_ordering(self):
  565. self.addCleanup(setattr, Book._meta, 'ordering', Book._meta.ordering)
  566. Book._meta.ordering = ('title',)
  567. modeladmin = CustomUserAdmin(User, site)
  568. request = self.request_factory.get('/')
  569. request.user = self.alfred
  570. changelist = modeladmin.get_changelist_instance(request)
  571. filterspec = changelist.get_filters(request)[0][0]
  572. expected = [
  573. (self.bio_book.pk, 'Django: a biography'),
  574. (self.djangonaut_book.pk, 'Djangonaut: an art of living'),
  575. (self.guitar_book.pk, 'Guitar for dummies'),
  576. (self.django_book.pk, 'The Django Book')
  577. ]
  578. self.assertEqual(filterspec.lookup_choices, expected)
  579. def test_relatedonlyfieldlistfilter_foreignkey(self):
  580. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  581. request = self.request_factory.get('/')
  582. request.user = self.alfred
  583. changelist = modeladmin.get_changelist_instance(request)
  584. # Make sure that only actual authors are present in author's list filter
  585. filterspec = changelist.get_filters(request)[0][4]
  586. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob')]
  587. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  588. def test_relatedonlyfieldlistfilter_foreignkey_reverse_relationships(self):
  589. class EmployeeAdminReverseRelationship(ModelAdmin):
  590. list_filter = (
  591. ('book', RelatedOnlyFieldListFilter),
  592. )
  593. self.djangonaut_book.employee = self.john
  594. self.djangonaut_book.save()
  595. self.django_book.employee = self.jack
  596. self.django_book.save()
  597. modeladmin = EmployeeAdminReverseRelationship(Employee, site)
  598. request = self.request_factory.get('/')
  599. request.user = self.alfred
  600. changelist = modeladmin.get_changelist_instance(request)
  601. filterspec = changelist.get_filters(request)[0][0]
  602. self.assertEqual(filterspec.lookup_choices, [
  603. (self.djangonaut_book.pk, 'Djangonaut: an art of living'),
  604. (self.django_book.pk, 'The Django Book'),
  605. ])
  606. def test_relatedonlyfieldlistfilter_manytomany_reverse_relationships(self):
  607. class UserAdminReverseRelationship(ModelAdmin):
  608. list_filter = (
  609. ('books_contributed', RelatedOnlyFieldListFilter),
  610. )
  611. modeladmin = UserAdminReverseRelationship(User, site)
  612. request = self.request_factory.get('/')
  613. request.user = self.alfred
  614. changelist = modeladmin.get_changelist_instance(request)
  615. filterspec = changelist.get_filters(request)[0][0]
  616. self.assertEqual(
  617. filterspec.lookup_choices,
  618. [(self.guitar_book.pk, 'Guitar for dummies')],
  619. )
  620. def test_relatedonlyfieldlistfilter_foreignkey_ordering(self):
  621. """RelatedOnlyFieldListFilter ordering respects ModelAdmin.ordering."""
  622. class EmployeeAdminWithOrdering(ModelAdmin):
  623. ordering = ('name',)
  624. class BookAdmin(ModelAdmin):
  625. list_filter = (
  626. ('employee', RelatedOnlyFieldListFilter),
  627. )
  628. albert = Employee.objects.create(name='Albert Green', department=self.dev)
  629. self.djangonaut_book.employee = albert
  630. self.djangonaut_book.save()
  631. self.bio_book.employee = self.jack
  632. self.bio_book.save()
  633. site.register(Employee, EmployeeAdminWithOrdering)
  634. self.addCleanup(lambda: site.unregister(Employee))
  635. modeladmin = BookAdmin(Book, site)
  636. request = self.request_factory.get('/')
  637. request.user = self.alfred
  638. changelist = modeladmin.get_changelist_instance(request)
  639. filterspec = changelist.get_filters(request)[0][0]
  640. expected = [(albert.pk, 'Albert Green'), (self.jack.pk, 'Jack Red')]
  641. self.assertEqual(filterspec.lookup_choices, expected)
  642. def test_relatedonlyfieldlistfilter_foreignkey_default_ordering(self):
  643. """RelatedOnlyFieldListFilter ordering respects Meta.ordering."""
  644. class BookAdmin(ModelAdmin):
  645. list_filter = (
  646. ('employee', RelatedOnlyFieldListFilter),
  647. )
  648. albert = Employee.objects.create(name='Albert Green', department=self.dev)
  649. self.djangonaut_book.employee = albert
  650. self.djangonaut_book.save()
  651. self.bio_book.employee = self.jack
  652. self.bio_book.save()
  653. self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering)
  654. Employee._meta.ordering = ('name',)
  655. modeladmin = BookAdmin(Book, site)
  656. request = self.request_factory.get('/')
  657. request.user = self.alfred
  658. changelist = modeladmin.get_changelist_instance(request)
  659. filterspec = changelist.get_filters(request)[0][0]
  660. expected = [(albert.pk, 'Albert Green'), (self.jack.pk, 'Jack Red')]
  661. self.assertEqual(filterspec.lookup_choices, expected)
  662. def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self):
  663. Department.objects.create(code='TEST', description='Testing')
  664. self.djangonaut_book.employee = self.john
  665. self.djangonaut_book.save()
  666. self.bio_book.employee = self.jack
  667. self.bio_book.save()
  668. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  669. request = self.request_factory.get('/')
  670. request.user = self.alfred
  671. changelist = modeladmin.get_changelist_instance(request)
  672. # Only actual departments should be present in employee__department's
  673. # list filter.
  674. filterspec = changelist.get_filters(request)[0][6]
  675. expected = [
  676. (self.dev.code, str(self.dev)),
  677. (self.design.code, str(self.design)),
  678. ]
  679. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  680. def test_relatedonlyfieldlistfilter_manytomany(self):
  681. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  682. request = self.request_factory.get('/')
  683. request.user = self.alfred
  684. changelist = modeladmin.get_changelist_instance(request)
  685. # Make sure that only actual contributors are present in contrib's list filter
  686. filterspec = changelist.get_filters(request)[0][5]
  687. expected = [(self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  688. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  689. def test_listfilter_genericrelation(self):
  690. django_bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
  691. python_bookmark = Bookmark.objects.create(url='https://www.python.org/')
  692. kernel_bookmark = Bookmark.objects.create(url='https://www.kernel.org/')
  693. TaggedItem.objects.create(content_object=django_bookmark, tag='python')
  694. TaggedItem.objects.create(content_object=python_bookmark, tag='python')
  695. TaggedItem.objects.create(content_object=kernel_bookmark, tag='linux')
  696. modeladmin = BookmarkAdminGenericRelation(Bookmark, site)
  697. request = self.request_factory.get('/', {'tags__tag': 'python'})
  698. request.user = self.alfred
  699. changelist = modeladmin.get_changelist_instance(request)
  700. queryset = changelist.get_queryset(request)
  701. expected = [python_bookmark, django_bookmark]
  702. self.assertEqual(list(queryset), expected)
  703. def test_booleanfieldlistfilter(self):
  704. modeladmin = BookAdmin(Book, site)
  705. self.verify_booleanfieldlistfilter(modeladmin)
  706. def test_booleanfieldlistfilter_tuple(self):
  707. modeladmin = BookAdminWithTupleBooleanFilter(Book, site)
  708. self.verify_booleanfieldlistfilter(modeladmin)
  709. def verify_booleanfieldlistfilter(self, modeladmin):
  710. request = self.request_factory.get('/')
  711. request.user = self.alfred
  712. changelist = modeladmin.get_changelist_instance(request)
  713. request = self.request_factory.get('/', {'is_best_seller__exact': 0})
  714. request.user = self.alfred
  715. changelist = modeladmin.get_changelist_instance(request)
  716. # Make sure the correct queryset is returned
  717. queryset = changelist.get_queryset(request)
  718. self.assertEqual(list(queryset), [self.bio_book])
  719. # Make sure the correct choice is selected
  720. filterspec = changelist.get_filters(request)[0][3]
  721. self.assertEqual(filterspec.title, 'is best seller')
  722. choice = select_by(filterspec.choices(changelist), "display", "No")
  723. self.assertIs(choice['selected'], True)
  724. self.assertEqual(choice['query_string'], '?is_best_seller__exact=0')
  725. request = self.request_factory.get('/', {'is_best_seller__exact': 1})
  726. request.user = self.alfred
  727. changelist = modeladmin.get_changelist_instance(request)
  728. # Make sure the correct queryset is returned
  729. queryset = changelist.get_queryset(request)
  730. self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book])
  731. # Make sure the correct choice is selected
  732. filterspec = changelist.get_filters(request)[0][3]
  733. self.assertEqual(filterspec.title, 'is best seller')
  734. choice = select_by(filterspec.choices(changelist), "display", "Yes")
  735. self.assertIs(choice['selected'], True)
  736. self.assertEqual(choice['query_string'], '?is_best_seller__exact=1')
  737. request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'})
  738. request.user = self.alfred
  739. changelist = modeladmin.get_changelist_instance(request)
  740. # Make sure the correct queryset is returned
  741. queryset = changelist.get_queryset(request)
  742. self.assertEqual(list(queryset), [self.django_book])
  743. # Make sure the correct choice is selected
  744. filterspec = changelist.get_filters(request)[0][3]
  745. self.assertEqual(filterspec.title, 'is best seller')
  746. choice = select_by(filterspec.choices(changelist), "display", "Unknown")
  747. self.assertIs(choice['selected'], True)
  748. self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True')
  749. def test_booleanfieldlistfilter_choices(self):
  750. modeladmin = BookAdmin(Book, site)
  751. self.verify_booleanfieldlistfilter_choices(modeladmin)
  752. def test_booleanfieldlistfilter_tuple_choices(self):
  753. modeladmin = BookAdminWithTupleBooleanFilter(Book, site)
  754. self.verify_booleanfieldlistfilter_choices(modeladmin)
  755. def verify_booleanfieldlistfilter_choices(self, modeladmin):
  756. # False.
  757. request = self.request_factory.get('/', {'availability__exact': 0})
  758. request.user = self.alfred
  759. changelist = modeladmin.get_changelist_instance(request)
  760. queryset = changelist.get_queryset(request)
  761. self.assertEqual(list(queryset), [self.bio_book])
  762. filterspec = changelist.get_filters(request)[0][6]
  763. self.assertEqual(filterspec.title, 'availability')
  764. choice = select_by(filterspec.choices(changelist), 'display', 'Paid')
  765. self.assertIs(choice['selected'], True)
  766. self.assertEqual(choice['query_string'], '?availability__exact=0')
  767. # True.
  768. request = self.request_factory.get('/', {'availability__exact': 1})
  769. request.user = self.alfred
  770. changelist = modeladmin.get_changelist_instance(request)
  771. queryset = changelist.get_queryset(request)
  772. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  773. filterspec = changelist.get_filters(request)[0][6]
  774. self.assertEqual(filterspec.title, 'availability')
  775. choice = select_by(filterspec.choices(changelist), 'display', 'Free')
  776. self.assertIs(choice['selected'], True)
  777. self.assertEqual(choice['query_string'], '?availability__exact=1')
  778. # None.
  779. request = self.request_factory.get('/', {'availability__isnull': 'True'})
  780. request.user = self.alfred
  781. changelist = modeladmin.get_changelist_instance(request)
  782. queryset = changelist.get_queryset(request)
  783. self.assertEqual(list(queryset), [self.guitar_book])
  784. filterspec = changelist.get_filters(request)[0][6]
  785. self.assertEqual(filterspec.title, 'availability')
  786. choice = select_by(filterspec.choices(changelist), 'display', 'Obscure')
  787. self.assertIs(choice['selected'], True)
  788. self.assertEqual(choice['query_string'], '?availability__isnull=True')
  789. # All.
  790. request = self.request_factory.get('/')
  791. request.user = self.alfred
  792. changelist = modeladmin.get_changelist_instance(request)
  793. queryset = changelist.get_queryset(request)
  794. self.assertEqual(
  795. list(queryset),
  796. [self.guitar_book, self.django_book, self.bio_book, self.djangonaut_book],
  797. )
  798. filterspec = changelist.get_filters(request)[0][6]
  799. self.assertEqual(filterspec.title, 'availability')
  800. choice = select_by(filterspec.choices(changelist), 'display', 'All')
  801. self.assertIs(choice['selected'], True)
  802. self.assertEqual(choice['query_string'], '?')
  803. def test_fieldlistfilter_underscorelookup_tuple(self):
  804. """
  805. Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks
  806. when fieldpath contains double underscore in value (#19182).
  807. """
  808. modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site)
  809. request = self.request_factory.get('/')
  810. request.user = self.alfred
  811. changelist = modeladmin.get_changelist_instance(request)
  812. request = self.request_factory.get('/', {'author__email': 'alfred@example.com'})
  813. request.user = self.alfred
  814. changelist = modeladmin.get_changelist_instance(request)
  815. # Make sure the correct queryset is returned
  816. queryset = changelist.get_queryset(request)
  817. self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book])
  818. def test_fieldlistfilter_invalid_lookup_parameters(self):
  819. """Filtering by an invalid value."""
  820. modeladmin = BookAdmin(Book, site)
  821. request = self.request_factory.get('/', {'author__id__exact': 'StringNotInteger!'})
  822. request.user = self.alfred
  823. with self.assertRaises(IncorrectLookupParameters):
  824. modeladmin.get_changelist_instance(request)
  825. def test_simplelistfilter(self):
  826. modeladmin = DecadeFilterBookAdmin(Book, site)
  827. # Make sure that the first option is 'All' ---------------------------
  828. request = self.request_factory.get('/', {})
  829. request.user = self.alfred
  830. changelist = modeladmin.get_changelist_instance(request)
  831. # Make sure the correct queryset is returned
  832. queryset = changelist.get_queryset(request)
  833. self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id')))
  834. # Make sure the correct choice is selected
  835. filterspec = changelist.get_filters(request)[0][1]
  836. self.assertEqual(filterspec.title, 'publication decade')
  837. choices = list(filterspec.choices(changelist))
  838. self.assertEqual(choices[0]['display'], 'All')
  839. self.assertIs(choices[0]['selected'], True)
  840. self.assertEqual(choices[0]['query_string'], '?')
  841. # Look for books in the 1980s ----------------------------------------
  842. request = self.request_factory.get('/', {'publication-decade': 'the 80s'})
  843. request.user = self.alfred
  844. changelist = modeladmin.get_changelist_instance(request)
  845. # Make sure the correct queryset is returned
  846. queryset = changelist.get_queryset(request)
  847. self.assertEqual(list(queryset), [])
  848. # Make sure the correct choice is selected
  849. filterspec = changelist.get_filters(request)[0][1]
  850. self.assertEqual(filterspec.title, 'publication decade')
  851. choices = list(filterspec.choices(changelist))
  852. self.assertEqual(choices[1]['display'], 'the 1980\'s')
  853. self.assertIs(choices[1]['selected'], True)
  854. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+80s')
  855. # Look for books in the 1990s ----------------------------------------
  856. request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
  857. request.user = self.alfred
  858. changelist = modeladmin.get_changelist_instance(request)
  859. # Make sure the correct queryset is returned
  860. queryset = changelist.get_queryset(request)
  861. self.assertEqual(list(queryset), [self.bio_book])
  862. # Make sure the correct choice is selected
  863. filterspec = changelist.get_filters(request)[0][1]
  864. self.assertEqual(filterspec.title, 'publication decade')
  865. choices = list(filterspec.choices(changelist))
  866. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  867. self.assertIs(choices[2]['selected'], True)
  868. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s')
  869. # Look for books in the 2000s ----------------------------------------
  870. request = self.request_factory.get('/', {'publication-decade': 'the 00s'})
  871. request.user = self.alfred
  872. changelist = modeladmin.get_changelist_instance(request)
  873. # Make sure the correct queryset is returned
  874. queryset = changelist.get_queryset(request)
  875. self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book])
  876. # Make sure the correct choice is selected
  877. filterspec = changelist.get_filters(request)[0][1]
  878. self.assertEqual(filterspec.title, 'publication decade')
  879. choices = list(filterspec.choices(changelist))
  880. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  881. self.assertIs(choices[3]['selected'], True)
  882. self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s')
  883. # Combine multiple filters -------------------------------------------
  884. request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk})
  885. request.user = self.alfred
  886. changelist = modeladmin.get_changelist_instance(request)
  887. # Make sure the correct queryset is returned
  888. queryset = changelist.get_queryset(request)
  889. self.assertEqual(list(queryset), [self.djangonaut_book])
  890. # Make sure the correct choices are selected
  891. filterspec = changelist.get_filters(request)[0][1]
  892. self.assertEqual(filterspec.title, 'publication decade')
  893. choices = list(filterspec.choices(changelist))
  894. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  895. self.assertIs(choices[3]['selected'], True)
  896. self.assertEqual(
  897. choices[3]['query_string'],
  898. '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk
  899. )
  900. filterspec = changelist.get_filters(request)[0][0]
  901. self.assertEqual(filterspec.title, 'Verbose Author')
  902. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  903. self.assertIs(choice['selected'], True)
  904. self.assertEqual(choice['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk)
  905. def test_listfilter_without_title(self):
  906. """
  907. Any filter must define a title.
  908. """
  909. modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
  910. request = self.request_factory.get('/', {})
  911. request.user = self.alfred
  912. msg = "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'."
  913. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  914. modeladmin.get_changelist_instance(request)
  915. def test_simplelistfilter_without_parameter(self):
  916. """
  917. Any SimpleListFilter must define a parameter_name.
  918. """
  919. modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
  920. request = self.request_factory.get('/', {})
  921. request.user = self.alfred
  922. msg = "The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'."
  923. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  924. modeladmin.get_changelist_instance(request)
  925. def test_simplelistfilter_with_none_returning_lookups(self):
  926. """
  927. A SimpleListFilter lookups method can return None but disables the
  928. filter completely.
  929. """
  930. modeladmin = DecadeFilterBookAdminWithNoneReturningLookups(Book, site)
  931. request = self.request_factory.get('/', {})
  932. request.user = self.alfred
  933. changelist = modeladmin.get_changelist_instance(request)
  934. filterspec = changelist.get_filters(request)[0]
  935. self.assertEqual(len(filterspec), 0)
  936. def test_filter_with_failing_queryset(self):
  937. """
  938. When a filter's queryset method fails, it fails loudly and
  939. the corresponding exception doesn't get swallowed (#17828).
  940. """
  941. modeladmin = DecadeFilterBookAdminWithFailingQueryset(Book, site)
  942. request = self.request_factory.get('/', {})
  943. request.user = self.alfred
  944. with self.assertRaises(ZeroDivisionError):
  945. modeladmin.get_changelist_instance(request)
  946. def test_simplelistfilter_with_queryset_based_lookups(self):
  947. modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site)
  948. request = self.request_factory.get('/', {})
  949. request.user = self.alfred
  950. changelist = modeladmin.get_changelist_instance(request)
  951. filterspec = changelist.get_filters(request)[0][0]
  952. self.assertEqual(filterspec.title, 'publication decade')
  953. choices = list(filterspec.choices(changelist))
  954. self.assertEqual(len(choices), 3)
  955. self.assertEqual(choices[0]['display'], 'All')
  956. self.assertIs(choices[0]['selected'], True)
  957. self.assertEqual(choices[0]['query_string'], '?')
  958. self.assertEqual(choices[1]['display'], 'the 1990\'s')
  959. self.assertIs(choices[1]['selected'], False)
  960. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
  961. self.assertEqual(choices[2]['display'], 'the 2000\'s')
  962. self.assertIs(choices[2]['selected'], False)
  963. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
  964. def test_two_characters_long_field(self):
  965. """
  966. list_filter works with two-characters long field names (#16080).
  967. """
  968. modeladmin = BookAdmin(Book, site)
  969. request = self.request_factory.get('/', {'no': '207'})
  970. request.user = self.alfred
  971. changelist = modeladmin.get_changelist_instance(request)
  972. # Make sure the correct queryset is returned
  973. queryset = changelist.get_queryset(request)
  974. self.assertEqual(list(queryset), [self.bio_book])
  975. filterspec = changelist.get_filters(request)[0][5]
  976. self.assertEqual(filterspec.title, 'number')
  977. choices = list(filterspec.choices(changelist))
  978. self.assertIs(choices[2]['selected'], True)
  979. self.assertEqual(choices[2]['query_string'], '?no=207')
  980. def test_parameter_ends_with__in__or__isnull(self):
  981. """
  982. A SimpleListFilter's parameter name is not mistaken for a model field
  983. if it ends with '__isnull' or '__in' (#17091).
  984. """
  985. # When it ends with '__in' -----------------------------------------
  986. modeladmin = DecadeFilterBookAdminParameterEndsWith__In(Book, site)
  987. request = self.request_factory.get('/', {'decade__in': 'the 90s'})
  988. request.user = self.alfred
  989. changelist = modeladmin.get_changelist_instance(request)
  990. # Make sure the correct queryset is returned
  991. queryset = changelist.get_queryset(request)
  992. self.assertEqual(list(queryset), [self.bio_book])
  993. # Make sure the correct choice is selected
  994. filterspec = changelist.get_filters(request)[0][0]
  995. self.assertEqual(filterspec.title, 'publication decade')
  996. choices = list(filterspec.choices(changelist))
  997. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  998. self.assertIs(choices[2]['selected'], True)
  999. self.assertEqual(choices[2]['query_string'], '?decade__in=the+90s')
  1000. # When it ends with '__isnull' ---------------------------------------
  1001. modeladmin = DecadeFilterBookAdminParameterEndsWith__Isnull(Book, site)
  1002. request = self.request_factory.get('/', {'decade__isnull': 'the 90s'})
  1003. request.user = self.alfred
  1004. changelist = modeladmin.get_changelist_instance(request)
  1005. # Make sure the correct queryset is returned
  1006. queryset = changelist.get_queryset(request)
  1007. self.assertEqual(list(queryset), [self.bio_book])
  1008. # Make sure the correct choice is selected
  1009. filterspec = changelist.get_filters(request)[0][0]
  1010. self.assertEqual(filterspec.title, 'publication decade')
  1011. choices = list(filterspec.choices(changelist))
  1012. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  1013. self.assertIs(choices[2]['selected'], True)
  1014. self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s')
  1015. def test_lookup_with_non_string_value(self):
  1016. """
  1017. Ensure choices are set the selected class when using non-string values
  1018. for lookups in SimpleListFilters (#19318).
  1019. """
  1020. modeladmin = DepartmentFilterEmployeeAdmin(Employee, site)
  1021. request = self.request_factory.get('/', {'department': self.john.department.pk})
  1022. request.user = self.alfred
  1023. changelist = modeladmin.get_changelist_instance(request)
  1024. queryset = changelist.get_queryset(request)
  1025. self.assertEqual(list(queryset), [self.john])
  1026. filterspec = changelist.get_filters(request)[0][-1]
  1027. self.assertEqual(filterspec.title, 'department')
  1028. choices = list(filterspec.choices(changelist))
  1029. self.assertEqual(choices[1]['display'], 'DEV')
  1030. self.assertIs(choices[1]['selected'], True)
  1031. self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.department.pk)
  1032. def test_lookup_with_non_string_value_underscored(self):
  1033. """
  1034. Ensure SimpleListFilter lookups pass lookup_allowed checks when
  1035. parameter_name attribute contains double-underscore value (#19182).
  1036. """
  1037. modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site)
  1038. request = self.request_factory.get('/', {'department__whatever': self.john.department.pk})
  1039. request.user = self.alfred
  1040. changelist = modeladmin.get_changelist_instance(request)
  1041. queryset = changelist.get_queryset(request)
  1042. self.assertEqual(list(queryset), [self.john])
  1043. filterspec = changelist.get_filters(request)[0][-1]
  1044. self.assertEqual(filterspec.title, 'department')
  1045. choices = list(filterspec.choices(changelist))
  1046. self.assertEqual(choices[1]['display'], 'DEV')
  1047. self.assertIs(choices[1]['selected'], True)
  1048. self.assertEqual(choices[1]['query_string'], '?department__whatever=%s' % self.john.department.pk)
  1049. def test_fk_with_to_field(self):
  1050. """
  1051. A filter on a FK respects the FK's to_field attribute (#17972).
  1052. """
  1053. modeladmin = EmployeeAdmin(Employee, site)
  1054. request = self.request_factory.get('/', {})
  1055. request.user = self.alfred
  1056. changelist = modeladmin.get_changelist_instance(request)
  1057. # Make sure the correct queryset is returned
  1058. queryset = changelist.get_queryset(request)
  1059. self.assertEqual(list(queryset), [self.jack, self.john])
  1060. filterspec = changelist.get_filters(request)[0][-1]
  1061. self.assertEqual(filterspec.title, 'department')
  1062. choices = list(filterspec.choices(changelist))
  1063. self.assertEqual(choices[0]['display'], 'All')
  1064. self.assertIs(choices[0]['selected'], True)
  1065. self.assertEqual(choices[0]['query_string'], '?')
  1066. self.assertEqual(choices[1]['display'], 'Development')
  1067. self.assertIs(choices[1]['selected'], False)
  1068. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  1069. self.assertEqual(choices[2]['display'], 'Design')
  1070. self.assertIs(choices[2]['selected'], False)
  1071. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  1072. # Filter by Department=='Development' --------------------------------
  1073. request = self.request_factory.get('/', {'department__code__exact': 'DEV'})
  1074. request.user = self.alfred
  1075. changelist = modeladmin.get_changelist_instance(request)
  1076. # Make sure the correct queryset is returned
  1077. queryset = changelist.get_queryset(request)
  1078. self.assertEqual(list(queryset), [self.john])
  1079. filterspec = changelist.get_filters(request)[0][-1]
  1080. self.assertEqual(filterspec.title, 'department')
  1081. choices = list(filterspec.choices(changelist))
  1082. self.assertEqual(choices[0]['display'], 'All')
  1083. self.assertIs(choices[0]['selected'], False)
  1084. self.assertEqual(choices[0]['query_string'], '?')
  1085. self.assertEqual(choices[1]['display'], 'Development')
  1086. self.assertIs(choices[1]['selected'], True)
  1087. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  1088. self.assertEqual(choices[2]['display'], 'Design')
  1089. self.assertIs(choices[2]['selected'], False)
  1090. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  1091. def test_lookup_with_dynamic_value(self):
  1092. """
  1093. Ensure SimpleListFilter can access self.value() inside the lookup.
  1094. """
  1095. modeladmin = DepartmentFilterDynamicValueBookAdmin(Book, site)
  1096. def _test_choices(request, expected_displays):
  1097. request.user = self.alfred
  1098. changelist = modeladmin.get_changelist_instance(request)
  1099. filterspec = changelist.get_filters(request)[0][0]
  1100. self.assertEqual(filterspec.title, 'publication decade')
  1101. choices = tuple(c['display'] for c in filterspec.choices(changelist))
  1102. self.assertEqual(choices, expected_displays)
  1103. _test_choices(self.request_factory.get('/', {}),
  1104. ("All", "the 1980's", "the 1990's"))
  1105. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 80s'}),
  1106. ("All", "the 1990's"))
  1107. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 90s'}),
  1108. ("All", "the 1980's"))
  1109. def test_list_filter_queryset_filtered_by_default(self):
  1110. """
  1111. A list filter that filters the queryset by default gives the correct
  1112. full_result_count.
  1113. """
  1114. modeladmin = NotNinetiesListFilterAdmin(Book, site)
  1115. request = self.request_factory.get('/', {})
  1116. request.user = self.alfred
  1117. changelist = modeladmin.get_changelist_instance(request)
  1118. changelist.get_results(request)
  1119. self.assertEqual(changelist.full_result_count, 4)
  1120. def test_emptylistfieldfilter(self):
  1121. empty_description = Department.objects.create(code='EMPT', description='')
  1122. none_description = Department.objects.create(code='NONE', description=None)
  1123. empty_title = Book.objects.create(title='', author=self.alfred)
  1124. department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site)
  1125. book_admin = BookAdminWithEmptyFieldListFilter(Book, site)
  1126. tests = [
  1127. # Allows nulls and empty strings.
  1128. (
  1129. department_admin,
  1130. {'description__isempty': '1'},
  1131. [empty_description, none_description],
  1132. ),
  1133. (
  1134. department_admin,
  1135. {'description__isempty': '0'},
  1136. [self.dev, self.design],
  1137. ),
  1138. # Allows nulls.
  1139. (book_admin, {'author__isempty': '1'}, [self.guitar_book]),
  1140. (
  1141. book_admin,
  1142. {'author__isempty': '0'},
  1143. [self.django_book, self.bio_book, self.djangonaut_book, empty_title],
  1144. ),
  1145. # Allows empty strings.
  1146. (book_admin, {'title__isempty': '1'}, [empty_title]),
  1147. (
  1148. book_admin,
  1149. {'title__isempty': '0'},
  1150. [self.django_book, self.bio_book, self.djangonaut_book, self.guitar_book],
  1151. ),
  1152. ]
  1153. for modeladmin, query_string, expected_result in tests:
  1154. with self.subTest(
  1155. modeladmin=modeladmin.__class__.__name__,
  1156. query_string=query_string,
  1157. ):
  1158. request = self.request_factory.get('/', query_string)
  1159. request.user = self.alfred
  1160. changelist = modeladmin.get_changelist_instance(request)
  1161. queryset = changelist.get_queryset(request)
  1162. self.assertCountEqual(queryset, expected_result)
  1163. def test_emptylistfieldfilter_reverse_relationships(self):
  1164. class UserAdminReverseRelationship(UserAdmin):
  1165. list_filter = (
  1166. ('books_contributed', EmptyFieldListFilter),
  1167. )
  1168. ImprovedBook.objects.create(book=self.guitar_book)
  1169. no_employees = Department.objects.create(code='NONE', description=None)
  1170. book_admin = BookAdminWithEmptyFieldListFilter(Book, site)
  1171. department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site)
  1172. user_admin = UserAdminReverseRelationship(User, site)
  1173. tests = [
  1174. # Reverse one-to-one relationship.
  1175. (
  1176. book_admin,
  1177. {'improvedbook__isempty': '1'},
  1178. [self.django_book, self.bio_book, self.djangonaut_book],
  1179. ),
  1180. (book_admin, {'improvedbook__isempty': '0'}, [self.guitar_book]),
  1181. # Reverse foreign key relationship.
  1182. (department_admin, {'employee__isempty': '1'}, [no_employees]),
  1183. (department_admin, {'employee__isempty': '0'}, [self.dev, self.design]),
  1184. # Reverse many-to-many relationship.
  1185. (user_admin, {'books_contributed__isempty': '1'}, [self.alfred]),
  1186. (user_admin, {'books_contributed__isempty': '0'}, [self.bob, self.lisa]),
  1187. ]
  1188. for modeladmin, query_string, expected_result in tests:
  1189. with self.subTest(
  1190. modeladmin=modeladmin.__class__.__name__,
  1191. query_string=query_string,
  1192. ):
  1193. request = self.request_factory.get('/', query_string)
  1194. request.user = self.alfred
  1195. changelist = modeladmin.get_changelist_instance(request)
  1196. queryset = changelist.get_queryset(request)
  1197. self.assertCountEqual(queryset, expected_result)
  1198. def test_emptylistfieldfilter_genericrelation(self):
  1199. class BookmarkGenericRelation(ModelAdmin):
  1200. list_filter = (
  1201. ('tags', EmptyFieldListFilter),
  1202. )
  1203. modeladmin = BookmarkGenericRelation(Bookmark, site)
  1204. django_bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
  1205. python_bookmark = Bookmark.objects.create(url='https://www.python.org/')
  1206. none_tags = Bookmark.objects.create(url='https://www.kernel.org/')
  1207. TaggedItem.objects.create(content_object=django_bookmark, tag='python')
  1208. TaggedItem.objects.create(content_object=python_bookmark, tag='python')
  1209. tests = [
  1210. ({'tags__isempty': '1'}, [none_tags]),
  1211. ({'tags__isempty': '0'}, [django_bookmark, python_bookmark]),
  1212. ]
  1213. for query_string, expected_result in tests:
  1214. with self.subTest(query_string=query_string):
  1215. request = self.request_factory.get('/', query_string)
  1216. request.user = self.alfred
  1217. changelist = modeladmin.get_changelist_instance(request)
  1218. queryset = changelist.get_queryset(request)
  1219. self.assertCountEqual(queryset, expected_result)
  1220. def test_emptylistfieldfilter_choices(self):
  1221. modeladmin = BookAdminWithEmptyFieldListFilter(Book, site)
  1222. request = self.request_factory.get('/')
  1223. request.user = self.alfred
  1224. changelist = modeladmin.get_changelist_instance(request)
  1225. filterspec = changelist.get_filters(request)[0][0]
  1226. self.assertEqual(filterspec.title, 'Verbose Author')
  1227. choices = list(filterspec.choices(changelist))
  1228. self.assertEqual(len(choices), 3)
  1229. self.assertEqual(choices[0]['display'], 'All')
  1230. self.assertIs(choices[0]['selected'], True)
  1231. self.assertEqual(choices[0]['query_string'], '?')
  1232. self.assertEqual(choices[1]['display'], 'Empty')
  1233. self.assertIs(choices[1]['selected'], False)
  1234. self.assertEqual(choices[1]['query_string'], '?author__isempty=1')
  1235. self.assertEqual(choices[2]['display'], 'Not empty')
  1236. self.assertIs(choices[2]['selected'], False)
  1237. self.assertEqual(choices[2]['query_string'], '?author__isempty=0')
  1238. def test_emptylistfieldfilter_non_empty_field(self):
  1239. class EmployeeAdminWithEmptyFieldListFilter(ModelAdmin):
  1240. list_filter = [('department', EmptyFieldListFilter)]
  1241. modeladmin = EmployeeAdminWithEmptyFieldListFilter(Employee, site)
  1242. request = self.request_factory.get('/')
  1243. request.user = self.alfred
  1244. msg = (
  1245. "The list filter 'EmptyFieldListFilter' cannot be used with field "
  1246. "'department' which doesn't allow empty strings and nulls."
  1247. )
  1248. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1249. modeladmin.get_changelist_instance(request)
  1250. def test_emptylistfieldfilter_invalid_lookup_parameters(self):
  1251. modeladmin = BookAdminWithEmptyFieldListFilter(Book, site)
  1252. request = self.request_factory.get('/', {'author__isempty': 42})
  1253. request.user = self.alfred
  1254. with self.assertRaises(IncorrectLookupParameters):
  1255. modeladmin.get_changelist_instance(request)