summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/admin/filters.py24
-rw-r--r--tests/admin_filters/models.py7
-rw-r--r--tests/admin_filters/tests.py16
3 files changed, 44 insertions, 3 deletions
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py
index f986bca713..d6f260bb61 100644
--- a/django/contrib/admin/filters.py
+++ b/django/contrib/admin/filters.py
@@ -269,25 +269,43 @@ FieldListFilter.register(
class ChoicesFieldListFilter(FieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = '%s__exact' % field_path
+ self.lookup_kwarg_isnull = '%s__isnull' % field_path
self.lookup_val = request.GET.get(self.lookup_kwarg)
+ self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
super(ChoicesFieldListFilter, self).__init__(
field, request, params, model, model_admin, field_path)
def expected_parameters(self):
- return [self.lookup_kwarg]
+ return [self.lookup_kwarg, self.lookup_kwarg_isnull]
def choices(self, changelist):
yield {
'selected': self.lookup_val is None,
- 'query_string': changelist.get_query_string({}, [self.lookup_kwarg]),
+ 'query_string': changelist.get_query_string(
+ {}, [self.lookup_kwarg, self.lookup_kwarg_isnull]
+ ),
'display': _('All')
}
+ none_title = ''
for lookup, title in self.field.flatchoices:
+ if lookup is None:
+ none_title = title
+ continue
yield {
'selected': smart_text(lookup) == self.lookup_val,
- 'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}),
+ 'query_string': changelist.get_query_string(
+ {self.lookup_kwarg: lookup}, [self.lookup_kwarg_isnull]
+ ),
'display': title,
}
+ if none_title:
+ yield {
+ 'selected': bool(self.lookup_val_isnull),
+ 'query_string': changelist.get_query_string({
+ self.lookup_kwarg_isnull: 'True',
+ }, [self.lookup_kwarg]),
+ 'display': none_title,
+ }
FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter)
diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py
index db37958882..8860201d35 100644
--- a/tests/admin_filters/models.py
+++ b/tests/admin_filters/models.py
@@ -75,5 +75,12 @@ class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
+ CHOICES = [
+ ('a', 'A'),
+ (None, 'None'),
+ ('', '-'),
+ ]
+ none_or_null = models.CharField(max_length=20, choices=CHOICES, blank=True, null=True)
+
def __str__(self):
return self.url
diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py
index 3d2f7ab9c6..ba6934f4ed 100644
--- a/tests/admin_filters/tests.py
+++ b/tests/admin_filters/tests.py
@@ -302,6 +302,22 @@ class ListFiltersTests(TestCase):
modeladmin.list_max_show_all, modeladmin.list_editable, modeladmin,
)
+ 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('/', {})
+ changelist = self.get_changelist(request, Bookmark, modeladmin)
+ 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)