tests.py 76 KB

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