import datetime import sys import unittest from django.contrib.admin import ( AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter, FieldListFilter, ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site, ) from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured from django.test import RequestFactory, TestCase, override_settings from .models import Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem def select_by(dictlist, key, value): return [x for x in dictlist if x[key] == value][0] class DecadeListFilter(SimpleListFilter): def lookups(self, request, model_admin): return ( ("the 80s", "the 1980's"), ("the 90s", "the 1990's"), ("the 00s", "the 2000's"), ("other", "other decades"), ) def queryset(self, request, queryset): decade = self.value() if decade == "the 80s": return queryset.filter(year__gte=1980, year__lte=1989) if decade == "the 90s": return queryset.filter(year__gte=1990, year__lte=1999) if decade == "the 00s": return queryset.filter(year__gte=2000, year__lte=2009) class NotNinetiesListFilter(SimpleListFilter): title = "Not nineties books" parameter_name = "book_year" def lookups(self, request, model_admin): return (("the 90s", "the 1990's"),) def queryset(self, request, queryset): if self.value() == "the 90s": return queryset.filter(year__gte=1990, year__lte=1999) else: return queryset.exclude(year__gte=1990, year__lte=1999) class DecadeListFilterWithTitleAndParameter(DecadeListFilter): title = "publication decade" parameter_name = "publication-decade" class DecadeListFilterWithoutTitle(DecadeListFilter): parameter_name = "publication-decade" class DecadeListFilterWithoutParameter(DecadeListFilter): title = "publication decade" class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): pass class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter): def queryset(self, request, queryset): raise 1 / 0 class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): qs = model_admin.get_queryset(request) if qs.filter(year__gte=1980, year__lte=1989).exists(): yield ("the 80s", "the 1980's") if qs.filter(year__gte=1990, year__lte=1999).exists(): yield ("the 90s", "the 1990's") if qs.filter(year__gte=2000, year__lte=2009).exists(): yield ("the 00s", "the 2000's") class DecadeListFilterParameterEndsWith__In(DecadeListFilter): title = "publication decade" parameter_name = "decade__in" # Ends with '__in" class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter): title = "publication decade" parameter_name = "decade__isnull" # Ends with '__isnull" class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter): title = "department" parameter_name = "department" def lookups(self, request, model_admin): return sorted( { ( employee.department.id, # Intentionally not a string (Refs #19318) employee.department.code, ) for employee in model_admin.get_queryset(request) } ) def queryset(self, request, queryset): if self.value(): return queryset.filter(department__id=self.value()) class DepartmentListFilterLookupWithUnderscoredParameter( DepartmentListFilterLookupWithNonStringValue ): parameter_name = "department__whatever" class DepartmentListFilterLookupWithDynamicValue(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): if self.value() == "the 80s": return (("the 90s", "the 1990's"),) elif self.value() == "the 90s": return (("the 80s", "the 1980's"),) else: return ( ("the 80s", "the 1980's"), ("the 90s", "the 1990's"), ) class EmployeeNameCustomDividerFilter(FieldListFilter): list_separator = "|" def __init__(self, field, request, params, model, model_admin, field_path): self.lookup_kwarg = "%s__in" % field_path super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): return [self.lookup_kwarg] class CustomUserAdmin(UserAdmin): list_filter = ("books_authored", "books_contributed") class BookAdmin(ModelAdmin): list_filter = ( "year", "author", "contributors", "is_best_seller", "date_registered", "no", "availability", ) ordering = ("-id",) class BookAdminWithTupleBooleanFilter(BookAdmin): list_filter = ( "year", "author", "contributors", ("is_best_seller", BooleanFieldListFilter), "date_registered", "no", ("availability", BooleanFieldListFilter), ) class BookAdminWithUnderscoreLookupAndTuple(BookAdmin): list_filter = ( "year", ("author__email", AllValuesFieldListFilter), "contributors", "is_best_seller", "date_registered", "no", ) class BookAdminWithCustomQueryset(ModelAdmin): def __init__(self, user, *args, **kwargs): self.user = user super().__init__(*args, **kwargs) list_filter = ("year",) def get_queryset(self, request): return super().get_queryset(request).filter(author=self.user) class BookAdminRelatedOnlyFilter(ModelAdmin): list_filter = ( "year", "is_best_seller", "date_registered", "no", ("author", RelatedOnlyFieldListFilter), ("contributors", RelatedOnlyFieldListFilter), ("employee__department", RelatedOnlyFieldListFilter), ) ordering = ("-id",) class DecadeFilterBookAdmin(ModelAdmin): list_filter = ("author", DecadeListFilterWithTitleAndParameter) ordering = ("-id",) class NotNinetiesListFilterAdmin(ModelAdmin): list_filter = (NotNinetiesListFilter,) class DecadeFilterBookAdminWithoutTitle(ModelAdmin): list_filter = (DecadeListFilterWithoutTitle,) class DecadeFilterBookAdminWithoutParameter(ModelAdmin): list_filter = (DecadeListFilterWithoutParameter,) class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin): list_filter = (DecadeListFilterWithNoneReturningLookups,) class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin): list_filter = (DecadeListFilterWithFailingQueryset,) class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin): list_filter = (DecadeListFilterWithQuerysetBasedLookups,) class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin): list_filter = (DecadeListFilterParameterEndsWith__In,) class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin): list_filter = (DecadeListFilterParameterEndsWith__Isnull,) class EmployeeAdmin(ModelAdmin): list_display = ["name", "department"] list_filter = ["department"] class EmployeeCustomDividerFilterAdmin(EmployeeAdmin): list_filter = [ ("name", EmployeeNameCustomDividerFilter), ] class DepartmentFilterEmployeeAdmin(EmployeeAdmin): list_filter = [DepartmentListFilterLookupWithNonStringValue] class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin): list_filter = [DepartmentListFilterLookupWithUnderscoredParameter] class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin): list_filter = [DepartmentListFilterLookupWithDynamicValue] class BookmarkAdminGenericRelation(ModelAdmin): list_filter = ["tags__tag"] class BookAdminWithEmptyFieldListFilter(ModelAdmin): list_filter = [ ("author", EmptyFieldListFilter), ("title", EmptyFieldListFilter), ("improvedbook", EmptyFieldListFilter), ] class DepartmentAdminWithEmptyFieldListFilter(ModelAdmin): list_filter = [ ("description", EmptyFieldListFilter), ("employee", EmptyFieldListFilter), ] class ListFiltersTests(TestCase): request_factory = RequestFactory() @classmethod def setUpTestData(cls): cls.today = datetime.date.today() cls.tomorrow = cls.today + datetime.timedelta(days=1) cls.one_week_ago = cls.today - datetime.timedelta(days=7) if cls.today.month == 12: cls.next_month = cls.today.replace(year=cls.today.year + 1, month=1, day=1) else: cls.next_month = cls.today.replace(month=cls.today.month + 1, day=1) cls.next_year = cls.today.replace(year=cls.today.year + 1, month=1, day=1) # Users cls.alfred = User.objects.create_superuser( "alfred", "alfred@example.com", "password" ) cls.bob = User.objects.create_user("bob", "bob@example.com") cls.lisa = User.objects.create_user("lisa", "lisa@example.com") # Books cls.djangonaut_book = Book.objects.create( title="Djangonaut: an art of living", year=2009, author=cls.alfred, is_best_seller=True, date_registered=cls.today, availability=True, ) cls.bio_book = Book.objects.create( title="Django: a biography", year=1999, author=cls.alfred, is_best_seller=False, no=207, availability=False, ) cls.django_book = Book.objects.create( title="The Django Book", year=None, author=cls.bob, is_best_seller=None, date_registered=cls.today, no=103, availability=True, ) cls.guitar_book = Book.objects.create( title="Guitar for dummies", year=2002, is_best_seller=True, date_registered=cls.one_week_ago, availability=None, ) cls.guitar_book.contributors.set([cls.bob, cls.lisa]) # Departments cls.dev = Department.objects.create(code="DEV", description="Development") cls.design = Department.objects.create(code="DSN", description="Design") # Employees cls.john = Employee.objects.create(name="John Blue", department=cls.dev) cls.jack = Employee.objects.create(name="Jack Red", department=cls.design) def test_choicesfieldlistfilter_has_none_choice(self): """ The last choice is for the None value. """ class BookmarkChoicesAdmin(ModelAdmin): list_display = ["none_or_null"] list_filter = ["none_or_null"] modeladmin = BookmarkChoicesAdmin(Bookmark, site) request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]["display"], "None") self.assertEqual(choices[-1]["query_string"], "?none_or_null__isnull=True") def test_datefieldlistfilter(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist(request) request = self.request_factory.get( "/", {"date_registered__gte": self.today, "date_registered__lt": self.tomorrow}, ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "Today") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?date_registered__gte=%s&date_registered__lt=%s" % ( self.today, self.tomorrow, ), ) request = self.request_factory.get( "/", { "date_registered__gte": self.today.replace(day=1), "date_registered__lt": self.next_month, }, ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) if (self.today.year, self.today.month) == ( self.one_week_ago.year, self.one_week_ago.month, ): # In case one week ago is in the same month. self.assertEqual( list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book], ) else: self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "This month") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?date_registered__gte=%s&date_registered__lt=%s" % ( self.today.replace(day=1), self.next_month, ), ) request = self.request_factory.get( "/", { "date_registered__gte": self.today.replace(month=1, day=1), "date_registered__lt": self.next_year, }, ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) if self.today.year == self.one_week_ago.year: # In case one week ago is in the same year. self.assertEqual( list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book], ) else: self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "This year") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?date_registered__gte=%s&date_registered__lt=%s" % ( self.today.replace(month=1, day=1), self.next_year, ), ) request = self.request_factory.get( "/", { "date_registered__gte": str(self.one_week_ago), "date_registered__lt": str(self.tomorrow), }, ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual( list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book] ) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "Past 7 days") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?date_registered__gte=%s&date_registered__lt=%s" % ( str(self.one_week_ago), str(self.tomorrow), ), ) # Null/not null queries request = self.request_factory.get("/", {"date_registered__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(queryset.count(), 1) self.assertEqual(queryset[0], self.bio_book) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "No date") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?date_registered__isnull=True") request = self.request_factory.get("/", {"date_registered__isnull": "False"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(queryset.count(), 3) self.assertEqual( list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book] ) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] self.assertEqual(filterspec.title, "date registered") choice = select_by(filterspec.choices(changelist), "display", "Has date") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?date_registered__isnull=False") @unittest.skipIf( sys.platform == "win32", "Windows doesn't support setting a timezone that differs from the " "system timezone.", ) @override_settings(USE_TZ=True) def test_datefieldlistfilter_with_time_zone_support(self): # Regression for #17830 self.test_datefieldlistfilter() def test_allvaluesfieldlistfilter(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/", {"year__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book]) # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "year") choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]["selected"], True) self.assertEqual(choices[-1]["query_string"], "?year__isnull=True") request = self.request_factory.get("/", {"year": "2002"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "year") choices = list(filterspec.choices(changelist)) self.assertIs(choices[2]["selected"], True) self.assertEqual(choices[2]["query_string"], "?year=2002") def test_allvaluesfieldlistfilter_custom_qs(self): # Make sure that correct filters are returned with custom querysets modeladmin = BookAdminWithCustomQueryset(self.alfred, Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] choices = list(filterspec.choices(changelist)) # Should have 'All', 1999 and 2009 options i.e. the subset of years of # books written by alfred (which is the filtering criteria set by # BookAdminWithCustomQueryset.get_queryset()) self.assertEqual(3, len(choices)) self.assertEqual(choices[0]["query_string"], "?") self.assertEqual(choices[1]["query_string"], "?year=1999") self.assertEqual(choices[2]["query_string"], "?year=2009") def test_relatedfieldlistfilter_foreignkey(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure that all users are present in the author's list filter filterspec = changelist.get_filters(request)[0][1] expected = [ (self.alfred.pk, "alfred"), (self.bob.pk, "bob"), (self.lisa.pk, "lisa"), ] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) request = self.request_factory.get("/", {"author__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.guitar_book]) # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "Verbose Author") choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]["selected"], True) self.assertEqual(choices[-1]["query_string"], "?author__isnull=True") request = self.request_factory.get("/", {"author__id__exact": self.alfred.pk}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "Verbose Author") # order of choices depends on User model, which has no order choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?author__id__exact=%d" % self.alfred.pk ) def test_relatedfieldlistfilter_foreignkey_ordering(self): """RelatedFieldListFilter ordering respects ModelAdmin.ordering.""" class EmployeeAdminWithOrdering(ModelAdmin): ordering = ("name",) class BookAdmin(ModelAdmin): list_filter = ("employee",) site.register(Employee, EmployeeAdminWithOrdering) self.addCleanup(lambda: site.unregister(Employee)) modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [(self.jack.pk, "Jack Red"), (self.john.pk, "John Blue")] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedfieldlistfilter_foreignkey_ordering_reverse(self): class EmployeeAdminWithOrdering(ModelAdmin): ordering = ("-name",) class BookAdmin(ModelAdmin): list_filter = ("employee",) site.register(Employee, EmployeeAdminWithOrdering) self.addCleanup(lambda: site.unregister(Employee)) modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [(self.john.pk, "John Blue"), (self.jack.pk, "Jack Red")] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedfieldlistfilter_foreignkey_default_ordering(self): """RelatedFieldListFilter ordering respects Model.ordering.""" class BookAdmin(ModelAdmin): list_filter = ("employee",) self.addCleanup(setattr, Employee._meta, "ordering", Employee._meta.ordering) Employee._meta.ordering = ("name",) modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [(self.jack.pk, "Jack Red"), (self.john.pk, "John Blue")] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedfieldlistfilter_manytomany(self): modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure that all users are present in the contrib's list filter filterspec = changelist.get_filters(request)[0][2] expected = [ (self.alfred.pk, "alfred"), (self.bob.pk, "bob"), (self.lisa.pk, "lisa"), ] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) request = self.request_factory.get("/", {"contributors__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual( list(queryset), [self.django_book, self.bio_book, self.djangonaut_book] ) # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][2] self.assertEqual(filterspec.title, "Verbose Contributors") choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]["selected"], True) self.assertEqual(choices[-1]["query_string"], "?contributors__isnull=True") request = self.request_factory.get( "/", {"contributors__id__exact": self.bob.pk} ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][2] self.assertEqual(filterspec.title, "Verbose Contributors") choice = select_by(filterspec.choices(changelist), "display", "bob") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?contributors__id__exact=%d" % self.bob.pk ) def test_relatedfieldlistfilter_reverse_relationships(self): modeladmin = CustomUserAdmin(User, site) # FK relationship ----- request = self.request_factory.get("/", {"books_authored__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.lisa]) # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "book") choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]["selected"], True) self.assertEqual(choices[-1]["query_string"], "?books_authored__isnull=True") request = self.request_factory.get( "/", {"books_authored__id__exact": self.bio_book.pk} ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "book") choice = select_by( filterspec.choices(changelist), "display", self.bio_book.title ) self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?books_authored__id__exact=%d" % self.bio_book.pk ) # M2M relationship ----- request = self.request_factory.get("/", {"books_contributed__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.alfred]) # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "book") choices = list(filterspec.choices(changelist)) self.assertIs(choices[-1]["selected"], True) self.assertEqual(choices[-1]["query_string"], "?books_contributed__isnull=True") request = self.request_factory.get( "/", {"books_contributed__id__exact": self.django_book.pk} ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "book") choice = select_by( filterspec.choices(changelist), "display", self.django_book.title ) self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?books_contributed__id__exact=%d" % self.django_book.pk, ) # With one book, the list filter should appear because there is also a # (None) option. Book.objects.exclude(pk=self.djangonaut_book.pk).delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 2) # With no books remaining, no list filters should appear. Book.objects.all().delete() filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) def test_relatedfieldlistfilter_reverse_relationships_default_ordering(self): self.addCleanup(setattr, Book._meta, "ordering", Book._meta.ordering) Book._meta.ordering = ("title",) modeladmin = CustomUserAdmin(User, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [ (self.bio_book.pk, "Django: a biography"), (self.djangonaut_book.pk, "Djangonaut: an art of living"), (self.guitar_book.pk, "Guitar for dummies"), (self.django_book.pk, "The Django Book"), ] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedonlyfieldlistfilter_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure that only actual authors are present in author's list filter filterspec = changelist.get_filters(request)[0][4] expected = [(self.alfred.pk, "alfred"), (self.bob.pk, "bob")] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) def test_relatedonlyfieldlistfilter_foreignkey_reverse_relationships(self): class EmployeeAdminReverseRelationship(ModelAdmin): list_filter = (("book", RelatedOnlyFieldListFilter),) self.djangonaut_book.employee = self.john self.djangonaut_book.save() self.django_book.employee = self.jack self.django_book.save() modeladmin = EmployeeAdminReverseRelationship(Employee, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] self.assertCountEqual( filterspec.lookup_choices, [ (self.djangonaut_book.pk, "Djangonaut: an art of living"), (self.django_book.pk, "The Django Book"), ], ) def test_relatedonlyfieldlistfilter_manytomany_reverse_relationships(self): class UserAdminReverseRelationship(ModelAdmin): list_filter = (("books_contributed", RelatedOnlyFieldListFilter),) modeladmin = UserAdminReverseRelationship(User, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] self.assertEqual( filterspec.lookup_choices, [(self.guitar_book.pk, "Guitar for dummies")], ) def test_relatedonlyfieldlistfilter_foreignkey_ordering(self): """RelatedOnlyFieldListFilter ordering respects ModelAdmin.ordering.""" class EmployeeAdminWithOrdering(ModelAdmin): ordering = ("name",) class BookAdmin(ModelAdmin): list_filter = (("employee", RelatedOnlyFieldListFilter),) albert = Employee.objects.create(name="Albert Green", department=self.dev) self.djangonaut_book.employee = albert self.djangonaut_book.save() self.bio_book.employee = self.jack self.bio_book.save() site.register(Employee, EmployeeAdminWithOrdering) self.addCleanup(lambda: site.unregister(Employee)) modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [(albert.pk, "Albert Green"), (self.jack.pk, "Jack Red")] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedonlyfieldlistfilter_foreignkey_default_ordering(self): """RelatedOnlyFieldListFilter ordering respects Meta.ordering.""" class BookAdmin(ModelAdmin): list_filter = (("employee", RelatedOnlyFieldListFilter),) albert = Employee.objects.create(name="Albert Green", department=self.dev) self.djangonaut_book.employee = albert self.djangonaut_book.save() self.bio_book.employee = self.jack self.bio_book.save() self.addCleanup(setattr, Employee._meta, "ordering", Employee._meta.ordering) Employee._meta.ordering = ("name",) modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] expected = [(albert.pk, "Albert Green"), (self.jack.pk, "Jack Red")] self.assertEqual(filterspec.lookup_choices, expected) def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self): Department.objects.create(code="TEST", description="Testing") self.djangonaut_book.employee = self.john self.djangonaut_book.save() self.bio_book.employee = self.jack self.bio_book.save() modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Only actual departments should be present in employee__department's # list filter. filterspec = changelist.get_filters(request)[0][6] expected = [ (self.dev.code, str(self.dev)), (self.design.code, str(self.design)), ] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) def test_relatedonlyfieldlistfilter_manytomany(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure that only actual contributors are present in contrib's list filter filterspec = changelist.get_filters(request)[0][5] expected = [(self.bob.pk, "bob"), (self.lisa.pk, "lisa")] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) def test_listfilter_genericrelation(self): django_bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/") python_bookmark = Bookmark.objects.create(url="https://www.python.org/") kernel_bookmark = Bookmark.objects.create(url="https://www.kernel.org/") TaggedItem.objects.create(content_object=django_bookmark, tag="python") TaggedItem.objects.create(content_object=python_bookmark, tag="python") TaggedItem.objects.create(content_object=kernel_bookmark, tag="linux") modeladmin = BookmarkAdminGenericRelation(Bookmark, site) request = self.request_factory.get("/", {"tags__tag": "python"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) expected = [python_bookmark, django_bookmark] self.assertEqual(list(queryset), expected) def test_booleanfieldlistfilter(self): modeladmin = BookAdmin(Book, site) self.verify_booleanfieldlistfilter(modeladmin) def test_booleanfieldlistfilter_tuple(self): modeladmin = BookAdminWithTupleBooleanFilter(Book, site) self.verify_booleanfieldlistfilter(modeladmin) def verify_booleanfieldlistfilter(self, modeladmin): request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) request = self.request_factory.get("/", {"is_best_seller__exact": 0}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] self.assertEqual(filterspec.title, "is best seller") choice = select_by(filterspec.choices(changelist), "display", "No") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?is_best_seller__exact=0") request = self.request_factory.get("/", {"is_best_seller__exact": 1}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] self.assertEqual(filterspec.title, "is best seller") choice = select_by(filterspec.choices(changelist), "display", "Yes") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?is_best_seller__exact=1") request = self.request_factory.get("/", {"is_best_seller__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] self.assertEqual(filterspec.title, "is best seller") choice = select_by(filterspec.choices(changelist), "display", "Unknown") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?is_best_seller__isnull=True") def test_booleanfieldlistfilter_choices(self): modeladmin = BookAdmin(Book, site) self.verify_booleanfieldlistfilter_choices(modeladmin) def test_booleanfieldlistfilter_tuple_choices(self): modeladmin = BookAdminWithTupleBooleanFilter(Book, site) self.verify_booleanfieldlistfilter_choices(modeladmin) def verify_booleanfieldlistfilter_choices(self, modeladmin): # False. request = self.request_factory.get("/", {"availability__exact": 0}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) filterspec = changelist.get_filters(request)[0][6] self.assertEqual(filterspec.title, "availability") choice = select_by(filterspec.choices(changelist), "display", "Paid") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?availability__exact=0") # True. request = self.request_factory.get("/", {"availability__exact": 1}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) filterspec = changelist.get_filters(request)[0][6] self.assertEqual(filterspec.title, "availability") choice = select_by(filterspec.choices(changelist), "display", "Free") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?availability__exact=1") # None. request = self.request_factory.get("/", {"availability__isnull": "True"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.guitar_book]) filterspec = changelist.get_filters(request)[0][6] self.assertEqual(filterspec.title, "availability") choice = select_by(filterspec.choices(changelist), "display", "Obscure") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?availability__isnull=True") # All. request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual( list(queryset), [self.guitar_book, self.django_book, self.bio_book, self.djangonaut_book], ) filterspec = changelist.get_filters(request)[0][6] self.assertEqual(filterspec.title, "availability") choice = select_by(filterspec.choices(changelist), "display", "All") self.assertIs(choice["selected"], True) self.assertEqual(choice["query_string"], "?") def test_fieldlistfilter_underscorelookup_tuple(self): """ Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks when fieldpath contains double underscore in value (#19182). """ modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) request = self.request_factory.get("/", {"author__email": "alfred@example.com"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book]) def test_fieldlistfilter_invalid_lookup_parameters(self): """Filtering by an invalid value.""" modeladmin = BookAdmin(Book, site) request = self.request_factory.get( "/", {"author__id__exact": "StringNotInteger!"} ) request.user = self.alfred with self.assertRaises(IncorrectLookupParameters): modeladmin.get_changelist_instance(request) def test_simplelistfilter(self): modeladmin = DecadeFilterBookAdmin(Book, site) # Make sure that the first option is 'All' --------------------------- request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), list(Book.objects.order_by("-id"))) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[0]["display"], "All") self.assertIs(choices[0]["selected"], True) self.assertEqual(choices[0]["query_string"], "?") # Look for books in the 1980s ---------------------------------------- request = self.request_factory.get("/", {"publication-decade": "the 80s"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), []) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]["display"], "the 1980's") self.assertIs(choices[1]["selected"], True) self.assertEqual(choices[1]["query_string"], "?publication-decade=the+80s") # Look for books in the 1990s ---------------------------------------- request = self.request_factory.get("/", {"publication-decade": "the 90s"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]["display"], "the 1990's") self.assertIs(choices[2]["selected"], True) self.assertEqual(choices[2]["query_string"], "?publication-decade=the+90s") # Look for books in the 2000s ---------------------------------------- request = self.request_factory.get("/", {"publication-decade": "the 00s"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[3]["display"], "the 2000's") self.assertIs(choices[3]["selected"], True) self.assertEqual(choices[3]["query_string"], "?publication-decade=the+00s") # Combine multiple filters ------------------------------------------- request = self.request_factory.get( "/", {"publication-decade": "the 00s", "author__id__exact": self.alfred.pk} ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.djangonaut_book]) # Make sure the correct choices are selected filterspec = changelist.get_filters(request)[0][1] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[3]["display"], "the 2000's") self.assertIs(choices[3]["selected"], True) self.assertEqual( choices[3]["query_string"], "?author__id__exact=%s&publication-decade=the+00s" % self.alfred.pk, ) filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "Verbose Author") choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], "?author__id__exact=%s&publication-decade=the+00s" % self.alfred.pk, ) def test_listfilter_without_title(self): """ Any filter must define a title. """ modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred msg = ( "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'." ) with self.assertRaisesMessage(ImproperlyConfigured, msg): modeladmin.get_changelist_instance(request) def test_simplelistfilter_without_parameter(self): """ Any SimpleListFilter must define a parameter_name. """ modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred msg = ( "The list filter 'DecadeListFilterWithoutParameter' does not specify a " "'parameter_name'." ) with self.assertRaisesMessage(ImproperlyConfigured, msg): modeladmin.get_changelist_instance(request) def test_simplelistfilter_with_none_returning_lookups(self): """ A SimpleListFilter lookups method can return None but disables the filter completely. """ modeladmin = DecadeFilterBookAdminWithNoneReturningLookups(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) def test_filter_with_failing_queryset(self): """ When a filter's queryset method fails, it fails loudly and the corresponding exception doesn't get swallowed (#17828). """ modeladmin = DecadeFilterBookAdminWithFailingQueryset(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred with self.assertRaises(ZeroDivisionError): modeladmin.get_changelist_instance(request) def test_simplelistfilter_with_queryset_based_lookups(self): modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(len(choices), 3) self.assertEqual(choices[0]["display"], "All") self.assertIs(choices[0]["selected"], True) self.assertEqual(choices[0]["query_string"], "?") self.assertEqual(choices[1]["display"], "the 1990's") self.assertIs(choices[1]["selected"], False) self.assertEqual(choices[1]["query_string"], "?publication-decade=the+90s") self.assertEqual(choices[2]["display"], "the 2000's") self.assertIs(choices[2]["selected"], False) self.assertEqual(choices[2]["query_string"], "?publication-decade=the+00s") def test_two_characters_long_field(self): """ list_filter works with two-characters long field names (#16080). """ modeladmin = BookAdmin(Book, site) request = self.request_factory.get("/", {"no": "207"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) filterspec = changelist.get_filters(request)[0][5] self.assertEqual(filterspec.title, "number") choices = list(filterspec.choices(changelist)) self.assertIs(choices[2]["selected"], True) self.assertEqual(choices[2]["query_string"], "?no=207") def test_parameter_ends_with__in__or__isnull(self): """ A SimpleListFilter's parameter name is not mistaken for a model field if it ends with '__isnull' or '__in' (#17091). """ # When it ends with '__in' ----------------------------------------- modeladmin = DecadeFilterBookAdminParameterEndsWith__In(Book, site) request = self.request_factory.get("/", {"decade__in": "the 90s"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]["display"], "the 1990's") self.assertIs(choices[2]["selected"], True) self.assertEqual(choices[2]["query_string"], "?decade__in=the+90s") # When it ends with '__isnull' --------------------------------------- modeladmin = DecadeFilterBookAdminParameterEndsWith__Isnull(Book, site) request = self.request_factory.get("/", {"decade__isnull": "the 90s"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "publication decade") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]["display"], "the 1990's") self.assertIs(choices[2]["selected"], True) self.assertEqual(choices[2]["query_string"], "?decade__isnull=the+90s") def test_lookup_with_non_string_value(self): """ Ensure choices are set the selected class when using non-string values for lookups in SimpleListFilters (#19318). """ modeladmin = DepartmentFilterEmployeeAdmin(Employee, site) request = self.request_factory.get("/", {"department": self.john.department.pk}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] self.assertEqual(filterspec.title, "department") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]["display"], "DEV") self.assertIs(choices[1]["selected"], True) self.assertEqual( choices[1]["query_string"], "?department=%s" % self.john.department.pk ) def test_lookup_with_non_string_value_underscored(self): """ Ensure SimpleListFilter lookups pass lookup_allowed checks when parameter_name attribute contains double-underscore value (#19182). """ modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site) request = self.request_factory.get( "/", {"department__whatever": self.john.department.pk} ) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] self.assertEqual(filterspec.title, "department") choices = list(filterspec.choices(changelist)) self.assertEqual(choices[1]["display"], "DEV") self.assertIs(choices[1]["selected"], True) self.assertEqual( choices[1]["query_string"], "?department__whatever=%s" % self.john.department.pk, ) def test_fk_with_to_field(self): """ A filter on a FK respects the FK's to_field attribute (#17972). """ modeladmin = EmployeeAdmin(Employee, site) request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.jack, self.john]) filterspec = changelist.get_filters(request)[0][-1] self.assertEqual(filterspec.title, "department") choices = [ (choice["display"], choice["selected"], choice["query_string"]) for choice in filterspec.choices(changelist) ] self.assertCountEqual( choices, [ ("All", True, "?"), ("Development", False, "?department__code__exact=DEV"), ("Design", False, "?department__code__exact=DSN"), ], ) # Filter by Department=='Development' -------------------------------- request = self.request_factory.get("/", {"department__code__exact": "DEV"}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] self.assertEqual(filterspec.title, "department") choices = [ (choice["display"], choice["selected"], choice["query_string"]) for choice in filterspec.choices(changelist) ] self.assertCountEqual( choices, [ ("All", False, "?"), ("Development", True, "?department__code__exact=DEV"), ("Design", False, "?department__code__exact=DSN"), ], ) def test_lookup_with_dynamic_value(self): """ Ensure SimpleListFilter can access self.value() inside the lookup. """ modeladmin = DepartmentFilterDynamicValueBookAdmin(Book, site) def _test_choices(request, expected_displays): request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "publication decade") choices = tuple(c["display"] for c in filterspec.choices(changelist)) self.assertEqual(choices, expected_displays) _test_choices( self.request_factory.get("/", {}), ("All", "the 1980's", "the 1990's") ) _test_choices( self.request_factory.get("/", {"publication-decade": "the 80s"}), ("All", "the 1990's"), ) _test_choices( self.request_factory.get("/", {"publication-decade": "the 90s"}), ("All", "the 1980's"), ) def test_list_filter_queryset_filtered_by_default(self): """ A list filter that filters the queryset by default gives the correct full_result_count. """ modeladmin = NotNinetiesListFilterAdmin(Book, site) request = self.request_factory.get("/", {}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) changelist.get_results(request) self.assertEqual(changelist.full_result_count, 4) def test_emptylistfieldfilter(self): empty_description = Department.objects.create(code="EMPT", description="") none_description = Department.objects.create(code="NONE", description=None) empty_title = Book.objects.create(title="", author=self.alfred) department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site) book_admin = BookAdminWithEmptyFieldListFilter(Book, site) tests = [ # Allows nulls and empty strings. ( department_admin, {"description__isempty": "1"}, [empty_description, none_description], ), ( department_admin, {"description__isempty": "0"}, [self.dev, self.design], ), # Allows nulls. (book_admin, {"author__isempty": "1"}, [self.guitar_book]), ( book_admin, {"author__isempty": "0"}, [self.django_book, self.bio_book, self.djangonaut_book, empty_title], ), # Allows empty strings. (book_admin, {"title__isempty": "1"}, [empty_title]), ( book_admin, {"title__isempty": "0"}, [ self.django_book, self.bio_book, self.djangonaut_book, self.guitar_book, ], ), ] for modeladmin, query_string, expected_result in tests: with self.subTest( modeladmin=modeladmin.__class__.__name__, query_string=query_string, ): request = self.request_factory.get("/", query_string) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertCountEqual(queryset, expected_result) def test_emptylistfieldfilter_reverse_relationships(self): class UserAdminReverseRelationship(UserAdmin): list_filter = (("books_contributed", EmptyFieldListFilter),) ImprovedBook.objects.create(book=self.guitar_book) no_employees = Department.objects.create(code="NONE", description=None) book_admin = BookAdminWithEmptyFieldListFilter(Book, site) department_admin = DepartmentAdminWithEmptyFieldListFilter(Department, site) user_admin = UserAdminReverseRelationship(User, site) tests = [ # Reverse one-to-one relationship. ( book_admin, {"improvedbook__isempty": "1"}, [self.django_book, self.bio_book, self.djangonaut_book], ), (book_admin, {"improvedbook__isempty": "0"}, [self.guitar_book]), # Reverse foreign key relationship. (department_admin, {"employee__isempty": "1"}, [no_employees]), (department_admin, {"employee__isempty": "0"}, [self.dev, self.design]), # Reverse many-to-many relationship. (user_admin, {"books_contributed__isempty": "1"}, [self.alfred]), (user_admin, {"books_contributed__isempty": "0"}, [self.bob, self.lisa]), ] for modeladmin, query_string, expected_result in tests: with self.subTest( modeladmin=modeladmin.__class__.__name__, query_string=query_string, ): request = self.request_factory.get("/", query_string) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertCountEqual(queryset, expected_result) def test_emptylistfieldfilter_genericrelation(self): class BookmarkGenericRelation(ModelAdmin): list_filter = (("tags", EmptyFieldListFilter),) modeladmin = BookmarkGenericRelation(Bookmark, site) django_bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/") python_bookmark = Bookmark.objects.create(url="https://www.python.org/") none_tags = Bookmark.objects.create(url="https://www.kernel.org/") TaggedItem.objects.create(content_object=django_bookmark, tag="python") TaggedItem.objects.create(content_object=python_bookmark, tag="python") tests = [ ({"tags__isempty": "1"}, [none_tags]), ({"tags__isempty": "0"}, [django_bookmark, python_bookmark]), ] for query_string, expected_result in tests: with self.subTest(query_string=query_string): request = self.request_factory.get("/", query_string) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) queryset = changelist.get_queryset(request) self.assertCountEqual(queryset, expected_result) def test_emptylistfieldfilter_choices(self): modeladmin = BookAdminWithEmptyFieldListFilter(Book, site) request = self.request_factory.get("/") request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) filterspec = changelist.get_filters(request)[0][0] self.assertEqual(filterspec.title, "Verbose Author") choices = list(filterspec.choices(changelist)) self.assertEqual(len(choices), 3) self.assertEqual(choices[0]["display"], "All") self.assertIs(choices[0]["selected"], True) self.assertEqual(choices[0]["query_string"], "?") self.assertEqual(choices[1]["display"], "Empty") self.assertIs(choices[1]["selected"], False) self.assertEqual(choices[1]["query_string"], "?author__isempty=1") self.assertEqual(choices[2]["display"], "Not empty") self.assertIs(choices[2]["selected"], False) self.assertEqual(choices[2]["query_string"], "?author__isempty=0") def test_emptylistfieldfilter_non_empty_field(self): class EmployeeAdminWithEmptyFieldListFilter(ModelAdmin): list_filter = [("department", EmptyFieldListFilter)] modeladmin = EmployeeAdminWithEmptyFieldListFilter(Employee, site) request = self.request_factory.get("/") request.user = self.alfred msg = ( "The list filter 'EmptyFieldListFilter' cannot be used with field " "'department' which doesn't allow empty strings and nulls." ) with self.assertRaisesMessage(ImproperlyConfigured, msg): modeladmin.get_changelist_instance(request) def test_emptylistfieldfilter_invalid_lookup_parameters(self): modeladmin = BookAdminWithEmptyFieldListFilter(Book, site) request = self.request_factory.get("/", {"author__isempty": 42}) request.user = self.alfred with self.assertRaises(IncorrectLookupParameters): modeladmin.get_changelist_instance(request) def test_lookup_using_custom_divider(self): """ Filter __in lookups with a custom divider. """ jane = Employee.objects.create(name="Jane,Green", department=self.design) modeladmin = EmployeeCustomDividerFilterAdmin(Employee, site) employees = [jane, self.jack] request = self.request_factory.get( "/", {"name__in": "|".join(e.name for e in employees)} ) # test for lookup with custom divider request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), employees) # test for lookup with comma in the lookup string request = self.request_factory.get("/", {"name": jane.name}) request.user = self.alfred changelist = modeladmin.get_changelist_instance(request) # Make sure the correct queryset is returned queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [jane])