tests.py 68 KB

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