diff --git a/AUTHORS b/AUTHORS index 4d9fcaecf4..9e063a3df3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -638,6 +638,7 @@ answer newbie questions, and generally made Django that much better: ryankanno Ryan Kelly Ryan Niemeyer + Ryno Mathee Sam Newman Sander Dijkhuis Sarthak Mehrish diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index af5ec34995..b64cc9ca9e 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -331,11 +331,20 @@ class DateFieldListFilter(FieldListFilter): self.lookup_kwarg_until: str(next_year), }), ) + if field.null: + self.lookup_kwarg_isnull = '%s__isnull' % field_path + self.links += ( + (_('No date'), {self.field_generic + 'isnull': 'True'}), + (_('Has date'), {self.field_generic + 'isnull': 'False'}), + ) super(DateFieldListFilter, self).__init__( field, request, params, model, model_admin, field_path) def expected_parameters(self): - return [self.lookup_kwarg_since, self.lookup_kwarg_until] + params = [self.lookup_kwarg_since, self.lookup_kwarg_until] + if self.field.null: + params.append(self.lookup_kwarg_isnull) + return params def choices(self, changelist): for title, param_dict in self.links: diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index a0419b76bc..7a3643969b 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -59,6 +59,9 @@ Minor features * Selected objects for fields in ``ModelAdmin.raw_id_fields`` now have a link to object's change form. +* Added "No date" and "Has date" choices for ``DateFieldListFilter`` if the + field is nullable. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 570353430d..eb9d1e7fd7 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -402,6 +402,37 @@ class ListFiltersTests(TestCase): ) ) + # Null/not null queries + request = self.request_factory.get('/', {'date_registered__isnull': 'True'}) + changelist = self.get_changelist(request, Book, modeladmin) + + # 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(force_text(filterspec.title), 'date registered') + choice = select_by(filterspec.choices(changelist), 'display', 'No date') + self.assertEqual(choice['selected'], True) + self.assertEqual(choice['query_string'], '?date_registered__isnull=True') + + request = self.request_factory.get('/', {'date_registered__isnull': 'False'}) + changelist = self.get_changelist(request, Book, modeladmin) + + # Make sure the correct queryset is returned + queryset = changelist.get_queryset(request) + self.assertEqual(queryset.count(), 3) + self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) + + # Make sure the correct choice is selected + filterspec = changelist.get_filters(request)[0][4] + self.assertEqual(force_text(filterspec.title), 'date registered') + choice = select_by(filterspec.choices(changelist), 'display', 'Has date') + self.assertEqual(choice['selected'], True) + self.assertEqual(choice['query_string'], '?date_registered__isnull=False') + @unittest.skipIf( sys.platform.startswith('win'), "Windows doesn't support setting a timezone that differs from the "