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