Code

Ticket #8528: AllValuesFilterSpec_Null.diff

File AllValuesFilterSpec_Null.diff, 5.0 KB (added by oyvind, 4 years ago)

Patch from #14467 with tests

Line 
1Index: django/contrib/admin/filterspecs.py
2===================================================================
3--- django/contrib/admin/filterspecs.py (revision 14260)
4+++ django/contrib/admin/filterspecs.py (working copy)
5@@ -161,19 +161,31 @@
6 class AllValuesFilterSpec(FilterSpec):
7     def __init__(self, f, request, params, model, model_admin):
8         super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin)
9-        self.lookup_val = request.GET.get(f.name, None)
10-        self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name)
11+        self.lookup_kwarg = f.name
12+        self.lookup_kwarg_isnull = '%s__isnull' % f.name
13+        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
14+        self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull, None)
15+        self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values_list(f.name, flat=True)
16 
17     def title(self):
18         return self.field.verbose_name
19 
20     def choices(self, cl):
21-        yield {'selected': self.lookup_val is None,
22-               'query_string': cl.get_query_string({}, [self.field.name]),
23+        yield {'selected': self.lookup_val is None and self.lookup_val_isnull is None,
24+               'query_string': cl.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]),
25                'display': _('All')}
26+        include_none = False
27         for val in self.lookup_choices:
28-            val = smart_unicode(val[self.field.name])
29+            if val is None:
30+                include_none = True
31+                continue
32+            val = smart_unicode(val)
33             yield {'selected': self.lookup_val == val,
34-                   'query_string': cl.get_query_string({self.field.name: val}),
35+                   'query_string': cl.get_query_string({self.lookup_kwarg: val}, [self.lookup_kwarg_isnull]),
36                    'display': val}
37+        if include_none:
38+            yield {'selected': self.lookup_val_isnull is not None,
39+                    'query_string': cl.get_query_string({self.lookup_kwarg_isnull: 'True'}, [self.lookup_kwarg]),
40+                    'display': _('None')}
41+
42 FilterSpec.register(lambda f: True, AllValuesFilterSpec)
43Index: tests/regressiontests/admin_filterspecs/__init__.py
44===================================================================
45Index: tests/regressiontests/admin_filterspecs/tests.py
46===================================================================
47--- tests/regressiontests/admin_filterspecs/tests.py    (revision 0)
48+++ tests/regressiontests/admin_filterspecs/tests.py    (revision 0)
49@@ -0,0 +1,47 @@
50+from django.utils import unittest
51+from django.test.client import RequestFactory
52+
53+from django.contrib import admin
54+from django.contrib.admin.views.main import ChangeList
55+
56+from models import OptionalAge
57+
58+class AllValuesFilterSpecTest(unittest.TestCase):
59+   
60+    def setUp(self):
61+        OptionalAge.objects.create(name='Sam', age=35)
62+        OptionalAge.objects.create(name='Tilk', age=135)
63+        OptionalAge.objects.create(name='Oneill', age=None)
64+        self.request_factory = RequestFactory()
65+
66+    def testAllValuesFilterSpecTest(self):
67+        m = OptionalAgeAdmin(OptionalAge, admin.site)
68+       
69+        r = self.request_factory.get('/', {'age__isnull': 'True'})
70+        cl = ChangeList(r, OptionalAge, m.list_display, m.list_display_links,
71+            m.list_filter, m.date_hierarchy, m.search_fields,
72+            m.list_select_related, m.list_per_page, m.list_editable, m)
73+           
74+        # Make sure cl.get_query_set() does not raise IncorrectLookupParameters
75+        queryset = cl.get_query_set()
76+
77+        # Make sure the last choice is None and is selected
78+        filterspec = cl.get_filters(r)[0][0]
79+        choices = list(filterspec.choices(cl))
80+        self.assertEquals(choices[3]['selected'], True)
81+        self.assertEquals(choices[3]['query_string'], '?age__isnull=True')
82+       
83+        r = self.request_factory.get('/', {'age': '135'})
84+        cl = ChangeList(r, OptionalAge, m.list_display, m.list_display_links,
85+            m.list_filter, m.date_hierarchy, m.search_fields,
86+            m.list_select_related, m.list_per_page, m.list_editable, m)
87+           
88+        # Make sure the correct choice is selected   
89+        filterspec = cl.get_filters(r)[0][0]
90+        choices = list(filterspec.choices(cl))
91+        self.assertEquals(choices[2]['selected'], True)
92+        self.assertEquals(choices[2]['query_string'], '?age=135')
93+
94+class OptionalAgeAdmin(admin.ModelAdmin):
95+    list_filter = ('age',)
96+    order_by = '-id'
97Index: tests/regressiontests/admin_filterspecs/models.py
98===================================================================
99--- tests/regressiontests/admin_filterspecs/models.py   (revision 0)
100+++ tests/regressiontests/admin_filterspecs/models.py   (revision 0)
101@@ -0,0 +1,5 @@
102+from django.db import models
103+
104+class OptionalAge(models.Model):
105+    name = models.CharField(max_length=25)
106+    age = models.PositiveIntegerField(null=True, blank=True)