Ticket #8528: 8528_filterspec_null_v2.diff
File 8528_filterspec_null_v2.diff, 18.4 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index eab5407..bf706ff 100644
a b class RelatedFilterSpec(FilterSpec): 76 76 self.lookup_title = f.verbose_name # use field name 77 77 rel_name = other_model._meta.pk.name 78 78 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 79 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) 79 80 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 81 self.lookup_val_isnull = request.GET.get( 82 self.lookup_kwarg_isnull, None) 80 83 self.lookup_choices = f.get_choices(include_blank=False) 81 84 82 85 def has_output(self): … … class RelatedFilterSpec(FilterSpec): 86 89 return self.lookup_title 87 90 88 91 def choices(self, cl): 89 yield {'selected': self.lookup_val is None, 90 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 92 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 93 yield {'selected': self.lookup_val is None 94 and not self.lookup_val_isnull, 95 'query_string': cl.get_query_string( 96 {}, 97 [self.lookup_kwarg, self.lookup_kwarg_isnull]), 91 98 'display': _('All')} 92 99 for pk_val, val in self.lookup_choices: 93 100 yield {'selected': self.lookup_val == smart_unicode(pk_val), 94 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), 101 'query_string': cl.get_query_string( 102 {self.lookup_kwarg: pk_val}, 103 [self.lookup_kwarg_isnull]), 95 104 'display': val} 105 if isinstance(self.field, models.related.RelatedObject) \ 106 and self.field.field.null or hasattr(self.field, 'rel') \ 107 and self.field.null: 108 yield {'selected': bool(self.lookup_val_isnull), 109 'query_string': cl.get_query_string( 110 {self.lookup_kwarg_isnull: 'True'}, 111 [self.lookup_kwarg]), 112 'display': EMPTY_CHANGELIST_VALUE} 96 113 97 114 FilterSpec.register(lambda f: ( 98 115 hasattr(f, 'rel') and bool(f.rel) or … … class ChoicesFilterSpec(FilterSpec): 113 130 'display': _('All')} 114 131 for k, v in self.field.flatchoices: 115 132 yield {'selected': smart_unicode(k) == self.lookup_val, 116 'query_string': cl.get_query_string({self.lookup_kwarg: k}), 133 'query_string': cl.get_query_string( 134 {self.lookup_kwarg: k}), 117 135 'display': v} 118 136 119 137 FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) … … class DateFieldFilterSpec(FilterSpec): 127 145 128 146 self.field_generic = '%s__' % self.field_path 129 147 130 self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)]) 148 self.date_params = dict([(k, v) for k, v in params.items() 149 if k.startswith(self.field_generic)]) 131 150 132 151 today = datetime.date.today() 133 152 one_week_ago = today - datetime.timedelta(days=7) 134 today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d') 153 today_str = isinstance(self.field, models.DateTimeField) \ 154 and today.strftime('%Y-%m-%d 23:59:59') \ 155 or today.strftime('%Y-%m-%d') 135 156 136 157 self.links = ( 137 158 (_('Any date'), {}), … … class DateFieldFilterSpec(FilterSpec): 152 173 def choices(self, cl): 153 174 for title, param_dict in self.links: 154 175 yield {'selected': self.date_params == param_dict, 155 'query_string': cl.get_query_string(param_dict, [self.field_generic]), 176 'query_string': cl.get_query_string( 177 param_dict, 178 [self.field_generic]), 156 179 'display': title} 157 180 158 FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) 181 FilterSpec.register(lambda f: isinstance(f, models.DateField), 182 DateFieldFilterSpec) 159 183 160 184 class BooleanFieldFilterSpec(FilterSpec): 161 185 def __init__(self, f, request, params, model, model_admin, … … class BooleanFieldFilterSpec(FilterSpec): 174 198 def choices(self, cl): 175 199 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 176 200 yield {'selected': self.lookup_val == v and not self.lookup_val2, 177 'query_string': cl.get_query_string({self.lookup_kwarg: v}, [self.lookup_kwarg2]), 201 'query_string': cl.get_query_string( 202 {self.lookup_kwarg: v}, 203 [self.lookup_kwarg2]), 178 204 'display': k} 179 205 if isinstance(self.field, models.NullBooleanField): 180 206 yield {'selected': self.lookup_val2 == 'True', 181 'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), 207 'query_string': cl.get_query_string( 208 {self.lookup_kwarg2: 'True'}, 209 [self.lookup_kwarg]), 182 210 'display': _('Unknown')} 183 211 184 FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec) 212 FilterSpec.register(lambda f: isinstance(f, models.BooleanField) 213 or isinstance(f, models.NullBooleanField), 214 BooleanFieldFilterSpec) 185 215 186 216 # This should be registered last, because it's a last resort. For example, 187 217 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much … … class AllValuesFilterSpec(FilterSpec): 192 222 super(AllValuesFilterSpec, self).__init__(f, request, params, model, 193 223 model_admin, 194 224 field_path=field_path) 195 self.lookup_val = request.GET.get(self.field_path, None) 196 parent_model, reverse_path = reverse_field_path(model, field_path) 225 self.lookup_kwarg = self.field_path 226 self.lookup_kwarg_isnull = '%s__isnull' % self.field_path 227 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 228 self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull, 229 None) 230 parent_model, reverse_path = reverse_field_path(model, self.field_path) 197 231 queryset = parent_model._default_manager.all() 198 232 # optional feature: limit choices base on existing relationships 199 233 # queryset = queryset.complex_filter( … … class AllValuesFilterSpec(FilterSpec): 202 236 queryset = queryset.filter(limit_choices_to) 203 237 204 238 self.lookup_choices = \ 205 queryset.distinct().order_by(f.name).values (f.name)239 queryset.distinct().order_by(f.name).values_list(f.name, flat=True) 206 240 207 241 def title(self): 208 242 return self.field.verbose_name 209 243 210 244 def choices(self, cl): 211 yield {'selected': self.lookup_val is None, 212 'query_string': cl.get_query_string({}, [self.field_path]), 245 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 246 yield {'selected': self.lookup_val is None 247 and self.lookup_val_isnull is None, 248 'query_string': cl.get_query_string( 249 {}, 250 [self.lookup_kwarg, self.lookup_kwarg_isnull]), 213 251 'display': _('All')} 252 include_none = False 253 214 254 for val in self.lookup_choices: 215 val = smart_unicode(val[self.field.name]) 255 if val is None: 256 include_none = True 257 continue 258 val = smart_unicode(val) 259 216 260 yield {'selected': self.lookup_val == val, 217 'query_string': cl.get_query_string({self.field_path: val}), 261 'query_string': cl.get_query_string( 262 {self.lookup_kwarg: val}, 263 [self.lookup_kwarg_isnull]), 218 264 'display': val} 265 if include_none: 266 yield {'selected': bool(self.lookup_val_isnull), 267 'query_string': cl.get_query_string( 268 {self.lookup_kwarg_isnull: 'True'}, 269 [self.lookup_kwarg]), 270 'display': EMPTY_CHANGELIST_VALUE} 271 219 272 FilterSpec.register(lambda f: True, AllValuesFilterSpec) -
new file tests/regressiontests/admin_filterspecs/models.py
diff --git a/tests/regressiontests/admin_filterspecs/__init__.py b/tests/regressiontests/admin_filterspecs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/admin_filterspecs/models.py b/tests/regressiontests/admin_filterspecs/models.py new file mode 100644 index 0000000..fe9fdfe
- + 1 from django.db import models 2 from django.contrib.auth.models import User 3 4 class Book(models.Model): 5 title = models.CharField(max_length=25) 6 year = models.PositiveIntegerField(null=True, blank=True) 7 author = models.ForeignKey(User, related_name='books_authored', blank=True, null=True) 8 contributors = models.ManyToManyField(User, related_name='books_contributed', blank=True, null=True) 9 10 def __unicode__(self): 11 return self.title -
new file tests/regressiontests/admin_filterspecs/tests.py
diff --git a/tests/regressiontests/admin_filterspecs/tests.py b/tests/regressiontests/admin_filterspecs/tests.py new file mode 100644 index 0000000..f64b199
- + 1 from django.contrib.auth.admin import UserAdmin 2 from django.test import TestCase 3 from django.test.client import RequestFactory 4 from django.contrib.auth.models import User 5 from django.contrib import admin 6 from django.contrib.admin.views.main import ChangeList 7 from django.utils.encoding import force_unicode 8 9 from models import Book 10 11 class FilterSpecsTests(TestCase): 12 13 def setUp(self): 14 # Users 15 alfred = User.objects.create_user('alfred', 'alfred@example.com') 16 bob = User.objects.create_user('bob', 'alfred@example.com') 17 lisa = User.objects.create_user('lisa', 'lisa@example.com') 18 19 #Books 20 bio_book = Book.objects.create(title='Django: a biography', year=1999, author=alfred) 21 django_book = Book.objects.create(title='The Django Book', year=None, author=bob) 22 gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002) 23 gipsy_book.contributors = [bob, lisa] 24 gipsy_book.save() 25 26 self.request_factory = RequestFactory() 27 28 29 def get_changelist(self, request, model, modeladmin): 30 return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links, 31 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 32 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 33 34 def test_AllValuesFilterSpec(self): 35 modeladmin = BookAdmin(Book, admin.site) 36 37 request = self.request_factory.get('/', {'year__isnull': 'True'}) 38 changelist = self.get_changelist(request, Book, modeladmin) 39 40 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 41 queryset = changelist.get_query_set() 42 43 # Make sure the last choice is None and is selected 44 filterspec = changelist.get_filters(request)[0][0] 45 self.assertEquals(force_unicode(filterspec.title()), u'year') 46 choices = list(filterspec.choices(changelist)) 47 self.assertEquals(choices[3]['selected'], True) 48 self.assertEquals(choices[3]['query_string'], '?year__isnull=True') 49 50 request = self.request_factory.get('/', {'year': '2002'}) 51 changelist = self.get_changelist(request, Book, modeladmin) 52 53 # Make sure the correct choice is selected 54 filterspec = changelist.get_filters(request)[0][0] 55 self.assertEquals(force_unicode(filterspec.title()), u'year') 56 choices = list(filterspec.choices(changelist)) 57 self.assertEquals(choices[2]['selected'], True) 58 self.assertEquals(choices[2]['query_string'], '?year=2002') 59 60 def test_RelatedFilterSpec_ForeignKey(self): 61 modeladmin = BookAdmin(Book, admin.site) 62 63 request = self.request_factory.get('/', {'author__isnull': 'True'}) 64 changelist = ChangeList(request, Book, modeladmin.list_display, modeladmin.list_display_links, 65 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 66 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 67 68 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 69 queryset = changelist.get_query_set() 70 71 # Make sure the last choice is None and is selected 72 filterspec = changelist.get_filters(request)[0][1] 73 self.assertEquals(force_unicode(filterspec.title()), u'author') 74 choices = list(filterspec.choices(changelist)) 75 self.assertEquals(choices[4]['selected'], True) 76 self.assertEquals(choices[4]['query_string'], '?author__isnull=True') 77 78 request = self.request_factory.get('/', {'author__id__exact': '1'}) 79 changelist = self.get_changelist(request, Book, modeladmin) 80 81 # Make sure the correct choice is selected 82 filterspec = changelist.get_filters(request)[0][1] 83 self.assertEquals(force_unicode(filterspec.title()), u'author') 84 choices = list(filterspec.choices(changelist)) 85 self.assertEquals(choices[1]['selected'], True) 86 self.assertEquals(choices[1]['query_string'], '?author__id__exact=1') 87 88 def test_RelatedFilterSpec_ManyToMany(self): 89 modeladmin = BookAdmin(Book, admin.site) 90 91 request = self.request_factory.get('/', {'contributors__isnull': 'True'}) 92 changelist = self.get_changelist(request, Book, modeladmin) 93 94 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 95 queryset = changelist.get_query_set() 96 97 # Make sure the last choice is None and is selected 98 filterspec = changelist.get_filters(request)[0][2] 99 self.assertEquals(force_unicode(filterspec.title()), u'user') 100 choices = list(filterspec.choices(changelist)) 101 self.assertEquals(choices[4]['selected'], True) 102 self.assertEquals(choices[4]['query_string'], '?contributors__isnull=True') 103 104 request = self.request_factory.get('/', {'contributors__id__exact': '2'}) 105 changelist = self.get_changelist(request, Book, modeladmin) 106 107 # Make sure the correct choice is selected 108 filterspec = changelist.get_filters(request)[0][2] 109 self.assertEquals(force_unicode(filterspec.title()), u'user') 110 choices = list(filterspec.choices(changelist)) 111 self.assertEquals(choices[2]['selected'], True) 112 self.assertEquals(choices[2]['query_string'], '?contributors__id__exact=2') 113 114 115 def test_RelatedFilterSpec_reverse_relationships(self): 116 modeladmin = CustomUserAdmin(User, admin.site) 117 118 # FK relationship ----- 119 request = self.request_factory.get('/', {'books_authored__isnull': 'True'}) 120 changelist = self.get_changelist(request, User, modeladmin) 121 122 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 123 queryset = changelist.get_query_set() 124 125 # Make sure the last choice is None and is selected 126 filterspec = changelist.get_filters(request)[0][0] 127 self.assertEquals(force_unicode(filterspec.title()), u'book') 128 choices = list(filterspec.choices(changelist)) 129 self.assertEquals(choices[-1]['selected'], True) 130 self.assertEquals(choices[-1]['query_string'], '?books_authored__isnull=True') 131 132 request = self.request_factory.get('/', {'books_authored__id__exact': '1'}) 133 changelist = self.get_changelist(request, User, modeladmin) 134 135 # Make sure the correct choice is selected 136 filterspec = changelist.get_filters(request)[0][0] 137 self.assertEquals(force_unicode(filterspec.title()), u'book') 138 choices = list(filterspec.choices(changelist)) 139 self.assertEquals(choices[1]['selected'], True) 140 self.assertEquals(choices[1]['query_string'], '?books_authored__id__exact=1') 141 142 # M2M relationship ----- 143 request = self.request_factory.get('/', {'books_contributed__isnull': 'True'}) 144 changelist = self.get_changelist(request, User, modeladmin) 145 146 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 147 queryset = changelist.get_query_set() 148 149 # Make sure the last choice is None and is selected 150 filterspec = changelist.get_filters(request)[0][1] 151 self.assertEquals(force_unicode(filterspec.title()), u'book') 152 choices = list(filterspec.choices(changelist)) 153 self.assertEquals(choices[-1]['selected'], True) 154 self.assertEquals(choices[-1]['query_string'], '?books_contributed__isnull=True') 155 156 request = self.request_factory.get('/', {'books_contributed__id__exact': '2'}) 157 changelist = self.get_changelist(request, User, modeladmin) 158 159 # Make sure the correct choice is selected 160 filterspec = changelist.get_filters(request)[0][1] 161 self.assertEquals(force_unicode(filterspec.title()), u'book') 162 choices = list(filterspec.choices(changelist)) 163 self.assertEquals(choices[2]['selected'], True) 164 self.assertEquals(choices[2]['query_string'], '?books_contributed__id__exact=2') 165 166 class CustomUserAdmin(UserAdmin): 167 list_filter = ('books_authored', 'books_contributed') 168 169 class BookAdmin(admin.ModelAdmin): 170 list_filter = ('year', 'author', 'contributors') 171 order_by = '-id'