Ticket #5833: 5833.custom-filterspecs.2.diff
File 5833.custom-filterspecs.2.diff, 34.6 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/__init__.py
diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py index f8e634e..77c1143 100644
a b 1 1 # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here 2 2 # has been referenced in documentation. 3 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 3 4 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 4 5 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 5 6 from django.contrib.admin.options import StackedInline, TabularInline -
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 965b32b..12322e0 100644
a b from django.utils.html import escape 13 13 from django.utils.safestring import mark_safe 14 14 from django.contrib.admin.util import get_model_from_relation, \ 15 15 reverse_field_path, get_limit_choices_to_from_path 16 from django.template.defaultfilters import slugify 16 17 import datetime 17 18 18 19 class FilterSpec(object): 19 filter_specs = []20 def __init__(self, f, request, params, model, model_admin,21 field_path=None):22 self.field = f20 _filter_specs = [] 21 _high_priority_index = 0 22 23 def __init__(self, request, params, model, model_admin): 23 24 self.params = params 24 self.field_path = field_path25 if field_path is None:26 if isinstance(f, models.related.RelatedObject):27 self.field_path = f.var_name28 else:29 self.field_path = f.name30 31 def register(cls, test, factory):32 cls.filter_specs.append((test, factory))33 register = classmethod(register)34 35 def create(cls, f, request, params, model, model_admin, field_path=None):36 for test, factory in cls.filter_specs:37 if test(f):38 return factory(f, request, params, model, model_admin,39 field_path=field_path)40 create = classmethod(create)41 25 42 26 def has_output(self): 43 27 return True 44 28 45 def choices(self, cl): 46 raise NotImplementedError() 47 48 def title(self): 49 return self.field.verbose_name 29 def _choices(self, cl): 30 raise NotImplementedError 31 32 def get_title(self): 33 raise NotImplementedError 34 35 def get_query_set(self, cl, qs): 36 return False 37 38 def _consumed_params(self): 39 """ 40 Return a list of parameters to consume from the change list 41 querystring. 42 43 Override this for non-field based FilterSpecs subclasses in order 44 to consume custom GET parameters, as any GET parameters that are not 45 consumed and are not a field name raises an exception. 46 """ 47 return [] 50 48 51 49 def output(self, cl): 52 50 t = [] 53 51 if self.has_output(): 54 t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self. title()))52 t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.get_title())) 55 53 56 for choice in self. choices(cl):54 for choice in self._choices(cl): 57 55 t.append(u'<li%s><a href="%s">%s</a></li>\n' % \ 58 56 ((choice['selected'] and ' class="selected"' or ''), 59 57 iri_to_uri(choice['query_string']), 60 58 choice['display'])) 61 59 t.append('</ul>\n\n') 62 60 return mark_safe("".join(t)) 61 62 @classmethod 63 def register(cls, test, factory, high_priority=True): 64 if high_priority: 65 cls._filter_specs.insert(cls._high_priority_index, (test, factory)) 66 cls._high_priority_index += 1 67 else: 68 cls._filter_specs.append((test, factory)) 69 70 71 72 class SimpleFilterSpec(FilterSpec): 73 74 def __init__(self, request, params, model, model_admin): 75 super(SimpleFilterSpec, self).__init__(request, params, model, model_admin) 76 self.lookup_kwarg = self.get_lookup_parameter() 77 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 78 self.lookup_choices = self.get_choices(request) 79 80 def has_output(self): 81 return len(self.lookup_choices) > 0 82 83 def get_lookup_parameter(self): 84 """ 85 Returns the parameter that should be used in the query string 86 for that filter. Defaults to the title, slugified. 87 """ 88 return slugify(self.get_title()) 89 90 def get_value(self): 91 """ 92 Returns the value given in the query string for this filter, 93 if any. Returns None otherwise. 94 """ 95 return self.params.get(self.lookup_kwarg, None) 96 97 def get_choices(self, request): 98 """ 99 Must be overriden to return a list of tuples (value, verbose value) 100 """ 101 raise NotImplementedError 102 103 def _consumed_params(self): 104 return [self.lookup_kwarg] 105 106 def _choices(self, cl): 107 yield {'selected': self.lookup_val is None, 108 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 109 'display': _('All')} 110 for k, v in self.lookup_choices: 111 yield {'selected': self.lookup_val == k, 112 'query_string': cl.get_query_string( 113 {self.lookup_kwarg: k}, 114 []), 115 'display': v} 63 116 64 class RelatedFilterSpec(FilterSpec): 65 def __init__(self, f, request, params, model, model_admin, 117 118 119 class FieldFilterSpec(FilterSpec): 120 121 def __init__(self, field, request, params, model, model_admin, \ 122 field_path=None): 123 super(FieldFilterSpec, self).__init__(request, params, model, \ 124 model_admin) 125 self.field = field 126 if field_path is None: 127 if isinstance(field, models.related.RelatedObject): 128 self.field_path = field.var_name 129 else: 130 self.field_path = field.name 131 else: 132 self.field_path = field_path 133 134 def get_title(self): 135 return self.field.verbose_name 136 137 @classmethod 138 def create(cls, field, request, params, model, model_admin, field_path=None): 139 for test, factory in cls._filter_specs: 140 if test(field): 141 return factory(field, request, params, model, model_admin, 142 field_path=field_path) 143 144 145 class RelatedFilterSpec(FieldFilterSpec): 146 def __init__(self, field, request, params, model, model_admin, 66 147 field_path=None): 67 148 super(RelatedFilterSpec, self).__init__( 68 f , request, params, model, model_admin, field_path=field_path)149 field, request, params, model, model_admin, field_path=field_path) 69 150 70 other_model = get_model_from_relation(f )71 if isinstance(f , (models.ManyToManyField,151 other_model = get_model_from_relation(field) 152 if isinstance(field, (models.ManyToManyField, 72 153 models.related.RelatedObject)): 73 154 # no direct field on this model, get name from other model 74 155 self.lookup_title = other_model._meta.verbose_name 75 156 else: 76 self.lookup_title = f .verbose_name # use field name157 self.lookup_title = field.verbose_name # use field name 77 158 rel_name = other_model._meta.pk.name 78 159 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 79 160 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) 80 161 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 81 162 self.lookup_val_isnull = request.GET.get( 82 163 self.lookup_kwarg_isnull, None) 83 self.lookup_choices = f .get_choices(include_blank=False)164 self.lookup_choices = field.get_choices(include_blank=False) 84 165 85 166 def has_output(self): 86 167 if isinstance(self.field, models.related.RelatedObject) \ … … class RelatedFilterSpec(FilterSpec): 91 172 extra = 0 92 173 return len(self.lookup_choices) + extra > 1 93 174 94 def title(self):175 def get_title(self): 95 176 return self.lookup_title 96 177 97 def choices(self, cl):178 def _choices(self, cl): 98 179 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 99 180 yield {'selected': self.lookup_val is None 100 181 and not self.lookup_val_isnull, … … class RelatedFilterSpec(FilterSpec): 117 198 [self.lookup_kwarg]), 118 199 'display': EMPTY_CHANGELIST_VALUE} 119 200 120 Fi lterSpec.register(lambda f: (201 FieldFilterSpec.register(lambda f: ( 121 202 hasattr(f, 'rel') and bool(f.rel) or 122 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec )203 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec, False) 123 204 124 class BooleanFieldFilterSpec(Fi lterSpec):205 class BooleanFieldFilterSpec(FieldFilterSpec): 125 206 def __init__(self, f, request, params, model, model_admin, 126 207 field_path=None): 127 208 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, … … class BooleanFieldFilterSpec(FilterSpec): 132 213 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 133 214 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 134 215 135 def title(self): 136 return self.field.verbose_name 137 138 def choices(self, cl): 216 def _choices(self, cl): 139 217 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 140 218 yield {'selected': self.lookup_val == v and not self.lookup_val2, 141 219 'query_string': cl.get_query_string( … … class BooleanFieldFilterSpec(FilterSpec): 149 227 [self.lookup_kwarg]), 150 228 'display': _('Unknown')} 151 229 152 Fi lterSpec.register(lambda f: isinstance(f, models.BooleanField)230 FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField) 153 231 or isinstance(f, models.NullBooleanField), 154 BooleanFieldFilterSpec )232 BooleanFieldFilterSpec, False) 155 233 156 class ChoicesFilterSpec(Fi lterSpec):234 class ChoicesFilterSpec(FieldFilterSpec): 157 235 def __init__(self, f, request, params, model, model_admin, 158 236 field_path=None): 159 237 super(ChoicesFilterSpec, self).__init__(f, request, params, model, … … class ChoicesFilterSpec(FilterSpec): 162 240 self.lookup_kwarg = '%s__exact' % self.field_path 163 241 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 164 242 165 def choices(self, cl):243 def _choices(self, cl): 166 244 yield {'selected': self.lookup_val is None, 167 245 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 168 246 'display': _('All')} … … class ChoicesFilterSpec(FilterSpec): 172 250 {self.lookup_kwarg: k}), 173 251 'display': v} 174 252 175 Fi lterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)253 FieldFilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec, False) 176 254 177 class DateFieldFilterSpec(Fi lterSpec):255 class DateFieldFilterSpec(FieldFilterSpec): 178 256 def __init__(self, f, request, params, model, model_admin, 179 257 field_path=None): 180 258 super(DateFieldFilterSpec, self).__init__(f, request, params, model, … … class DateFieldFilterSpec(FilterSpec): 205 283 (_('This year'), {'%s__year' % self.field_path: str(today.year)}) 206 284 ) 207 285 208 def title(self): 209 return self.field.verbose_name 210 211 def choices(self, cl): 286 def _choices(self, cl): 212 287 for title, param_dict in self.links: 213 288 yield {'selected': self.date_params == param_dict, 214 289 'query_string': cl.get_query_string( … … class DateFieldFilterSpec(FilterSpec): 216 291 [self.field_generic]), 217 292 'display': title} 218 293 219 Fi lterSpec.register(lambda f: isinstance(f, models.DateField),220 DateFieldFilterSpec )294 FieldFilterSpec.register(lambda f: isinstance(f, models.DateField), 295 DateFieldFilterSpec, False) 221 296 222 297 223 298 # This should be registered last, because it's a last resort. For example, 224 299 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much 225 300 # more appropriate, and the AllValuesFilterSpec won't get used for it. 226 class AllValuesFilterSpec(Fi lterSpec):301 class AllValuesFilterSpec(FieldFilterSpec): 227 302 def __init__(self, f, request, params, model, model_admin, 228 303 field_path=None): 229 304 super(AllValuesFilterSpec, self).__init__(f, request, params, model, … … class AllValuesFilterSpec(FilterSpec): 245 320 self.lookup_choices = \ 246 321 queryset.distinct().order_by(f.name).values_list(f.name, flat=True) 247 322 248 def title(self): 249 return self.field.verbose_name 250 251 def choices(self, cl): 323 def _choices(self, cl): 252 324 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 253 325 yield {'selected': self.lookup_val is None 254 326 and self.lookup_val_isnull is None, … … class AllValuesFilterSpec(FilterSpec): 276 348 [self.lookup_kwarg]), 277 349 'display': EMPTY_CHANGELIST_VALUE} 278 350 279 Fi lterSpec.register(lambda f: True, AllValuesFilterSpec)351 FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False) -
django/contrib/admin/templatetags/admin_list.py
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index fdf082b..6bac03b 100644
a b def search_form(cl): 317 317 search_form = register.inclusion_tag('admin/search_form.html')(search_form) 318 318 319 319 def admin_list_filter(cl, spec): 320 return {'title': spec. title(), 'choices' : list(spec.choices(cl))}320 return {'title': spec.get_title(), 'choices' : list(spec._choices(cl))} 321 321 admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) 322 322 323 323 def admin_actions(context): -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 159afa4..4fdb8a1 100644
a b from django.db import models 3 3 from django.db.models.fields import FieldDoesNotExist 4 4 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, 5 5 _get_foreign_key) 6 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 6 7 from django.contrib.admin.util import get_fields_from_path, NotRelationField 7 8 from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, 8 9 HORIZONTAL, VERTICAL) … … def validate(cls, model): 54 55 # list_filter 55 56 if hasattr(cls, 'list_filter'): 56 57 check_isseq(cls, 'list_filter', cls.list_filter) 57 for idx, fpath in enumerate(cls.list_filter): 58 try: 59 get_fields_from_path(model, fpath) 60 except (NotRelationField, FieldDoesNotExist), e: 61 raise ImproperlyConfigured( 62 "'%s.list_filter[%d]' refers to '%s' which does not refer to a Field." % ( 63 cls.__name__, idx, fpath 64 ) 65 ) 58 for idx, item in enumerate(cls.list_filter): 59 # There are three methods of specifying a filter: 60 # 1: 'field' - a simple field filter, poss. w/ relationships (eg, 'field__rel') 61 # 2: ('field', SomeFieldFilterSpec) - a field-based filter spec 62 # 3: SomeFilterSpec - a non-field filter spec 63 if callable(item) and not isinstance(item, models.Field): 64 # If item is option 3, it should be a FilterSpec, but not a FieldFilterSpec 65 if not issubclass(item, FilterSpec) or issubclass(item, FieldFilterSpec): 66 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 67 " which is not of type FilterSpec." 68 % (cls.__name__, idx, item)) 69 else: 70 try: 71 # Check for option #2 (tuple) 72 field, factory = item 73 except (TypeError, ValueError): 74 # item is option #1 75 field = item 76 else: 77 # item is option #2 78 if not issubclass(factory, FieldFilterSpec): 79 raise ImproperlyConfigured("'%s.list_filter[%d][1]'" 80 " refers to '%s' which is not of type FieldFilterSpec." 81 % (cls.__name__, idx, factory.__name__)) 82 # Validate the field string 83 try: 84 get_fields_from_path(model, field) 85 except (NotRelationField, FieldDoesNotExist), e: 86 raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" 87 " which does not refer to a Field." 88 % (cls.__name__, idx, field)) 66 89 67 90 # list_per_page = 100 68 91 if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 170d168..77a52d8 100644
a b 1 from django.contrib.admin.filterspecs import FilterSpec 1 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 2 2 from django.contrib.admin.options import IncorrectLookupParameters 3 3 from django.contrib.admin.util import quote, get_fields_from_path 4 4 from django.core.exceptions import SuspiciousOperation … … class ChangeList(object): 59 59 self.list_editable = () 60 60 else: 61 61 self.list_editable = list_editable 62 self.filter_specs, self.has_filters = self.get_filters(request) 62 63 self.order_field, self.order_type = self.get_ordering() 63 64 self.query = request.GET.get(SEARCH_VAR, '') 64 65 self.query_set = self.get_query_set() 65 66 self.get_results(request) 66 67 self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name)) 67 self.filter_specs, self.has_filters = self.get_filters(request)68 68 self.pk_attname = self.lookup_opts.pk.attname 69 69 70 70 def get_filters(self, request): 71 71 filter_specs = [] 72 72 if self.list_filter: 73 for filter_name in self.list_filter: 74 field = get_fields_from_path(self.model, filter_name)[-1] 75 spec = FilterSpec.create(field, request, self.params, 76 self.model, self.model_admin, 77 field_path=filter_name) 73 for item in self.list_filter: 74 if callable(item): 75 # This is simply a custom FilterSpec class. 76 spec = item(request, self.params, self.model, self.model_admin) 77 else: 78 field_path = None 79 try: 80 # This is custom FilterSpec class for a given field. 81 field, factory = item 82 except (TypeError, ValueError): 83 # This simply a field name, so use the default 84 # 'create' factory. 85 field, factory = item, FieldFilterSpec.create 86 if not isinstance(field, models.Field): 87 field_path = field 88 field = get_fields_from_path(self.model, field_path)[-1] 89 spec = factory(field, request, self.params, self.model, 90 self.model_admin, field_path=field_path) 78 91 if spec and spec.has_output(): 79 92 filter_specs.append(spec) 80 93 return filter_specs, bool(filter_specs) … … class ChangeList(object): 166 179 order_type = params[ORDER_TYPE_VAR] 167 180 return order_field, order_type 168 181 182 def apply_filter_specs(self, qs, lookup_params): 183 for filter_spec in self.filter_specs: 184 new_qs = filter_spec.get_query_set(self, qs) 185 if new_qs is not None and new_qs is not False: 186 qs = new_qs 187 # Only consume params if we got a new queryset 188 for param in filter_spec._consumed_params(): 189 try: 190 del lookup_params[param] 191 except KeyError: 192 pass 193 return qs 194 169 195 def get_query_set(self): 170 196 use_distinct = False 171 197 … … class ChangeList(object): 187 213 field_name = key.split('__', 1)[0] 188 214 try: 189 215 f = self.lookup_opts.get_field_by_name(field_name)[0] 216 if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): 217 use_distinct = True 190 218 except models.FieldDoesNotExist: 191 raise IncorrectLookupParameters 192 if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): 193 use_distinct = True 219 # It might be for a non-field custom filter specs. 220 pass 194 221 195 222 # if key ends with __in, split parameter into separate values 196 223 if key.endswith('__in'): … … class ChangeList(object): 209 236 raise SuspiciousOperation( 210 237 "Filtering by %s not allowed" % key 211 238 ) 212 239 # Let every filter spec modify the qs and params to its liking 240 qs = self.apply_filter_specs(qs, lookup_params) 241 213 242 # Apply lookup parameters from the query string. 214 243 try: 215 244 qs = qs.filter(**lookup_params) -
tests/regressiontests/admin_filterspecs/tests.py
diff --git a/tests/regressiontests/admin_filterspecs/tests.py b/tests/regressiontests/admin_filterspecs/tests.py index 8b9e734..a39db9b 100644
a b from django.contrib.auth.models import User 5 5 from django.contrib import admin 6 6 from django.contrib.admin.views.main import ChangeList 7 7 from django.utils.encoding import force_unicode 8 from django.contrib.admin.filterspecs import SimpleFilterSpec 8 9 9 10 from models import Book, BoolTest 10 11 11 12 def select_by(dictlist, key, value): 12 13 return [x for x in dictlist if x[key] == value][0] 13 14 15 16 17 class DecadeFilterSpec(SimpleFilterSpec): 18 19 def get_title(self): 20 return u'publication decade' 21 22 def get_choices(self, request): 23 return ( 24 (u'90s', u'1990\'s'), 25 (u'00s', u'2000\'s'), 26 (u'other', u'other'), 27 ) 28 29 def get_query_set(self, cl, qs): 30 decade = self.get_value() 31 if decade == u'90s': 32 return qs.filter(year__gte=1990, year__lte=1999) 33 if decade == u'00s': 34 return qs.filter(year__gte=2000, year__lte=2009) 35 return qs 36 37 14 38 class FilterSpecsTests(TestCase): 15 39 16 40 def setUp(self): 17 41 # Users 18 42 self.alfred = User.objects.create_user('alfred', 'alfred@example.com') 19 43 self.bob = User.objects.create_user('bob', 'bob@example.com') 20 lisa = User.objects.create_user('lisa', 'lisa@example.com')44 self.lisa = User.objects.create_user('lisa', 'lisa@example.com') 21 45 22 46 #Books 47 self.djangonaut_book = Book.objects.create(title='Djangonaut: an art of living', year=2009, author=self.alfred) 23 48 self.bio_book = Book.objects.create(title='Django: a biography', year=1999, author=self.alfred) 24 49 self.django_book = Book.objects.create(title='The Django Book', year=None, author=self.bob) 25 gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002)26 gipsy_book.contributors = [self.bob,lisa]27 gipsy_book.save()50 self.gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002) 51 self.gipsy_book.contributors = [self.bob, self.lisa] 52 self.gipsy_book.save() 28 53 29 54 # BoolTests 30 55 self.trueTest = BoolTest.objects.create(completed=True) … … class FilterSpecsTests(TestCase): 49 74 50 75 # Make sure the last choice is None and is selected 51 76 filterspec = changelist.get_filters(request)[0][0] 52 self.assertEqual(force_unicode(filterspec. title()), u'year')53 choices = list(filterspec. choices(changelist))77 self.assertEqual(force_unicode(filterspec.get_title()), u'year') 78 choices = list(filterspec._choices(changelist)) 54 79 self.assertEqual(choices[-1]['selected'], True) 55 80 self.assertEqual(choices[-1]['query_string'], '?year__isnull=True') 56 81 … … class FilterSpecsTests(TestCase): 59 84 60 85 # Make sure the correct choice is selected 61 86 filterspec = changelist.get_filters(request)[0][0] 62 self.assertEqual(force_unicode(filterspec. title()), u'year')63 choices = list(filterspec. choices(changelist))87 self.assertEqual(force_unicode(filterspec.get_title()), u'year') 88 choices = list(filterspec._choices(changelist)) 64 89 self.assertEqual(choices[2]['selected'], True) 65 90 self.assertEqual(choices[2]['query_string'], '?year=2002') 66 91 … … class FilterSpecsTests(TestCase): 77 102 78 103 # Make sure the last choice is None and is selected 79 104 filterspec = changelist.get_filters(request)[0][1] 80 self.assertEqual(force_unicode(filterspec. title()), u'author')81 choices = list(filterspec. choices(changelist))105 self.assertEqual(force_unicode(filterspec.get_title()), u'author') 106 choices = list(filterspec._choices(changelist)) 82 107 self.assertEqual(choices[-1]['selected'], True) 83 108 self.assertEqual(choices[-1]['query_string'], '?author__isnull=True') 84 109 … … class FilterSpecsTests(TestCase): 87 112 88 113 # Make sure the correct choice is selected 89 114 filterspec = changelist.get_filters(request)[0][1] 90 self.assertEqual(force_unicode(filterspec. title()), u'author')115 self.assertEqual(force_unicode(filterspec.get_title()), u'author') 91 116 # order of choices depends on User model, which has no order 92 choice = select_by(filterspec. choices(changelist), "display", "alfred")117 choice = select_by(filterspec._choices(changelist), "display", "alfred") 93 118 self.assertEqual(choice['selected'], True) 94 119 self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk) 95 120 … … class FilterSpecsTests(TestCase): 104 129 105 130 # Make sure the last choice is None and is selected 106 131 filterspec = changelist.get_filters(request)[0][2] 107 self.assertEqual(force_unicode(filterspec. title()), u'user')108 choices = list(filterspec. choices(changelist))132 self.assertEqual(force_unicode(filterspec.get_title()), u'user') 133 choices = list(filterspec._choices(changelist)) 109 134 self.assertEqual(choices[-1]['selected'], True) 110 135 self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True') 111 136 … … class FilterSpecsTests(TestCase): 114 139 115 140 # Make sure the correct choice is selected 116 141 filterspec = changelist.get_filters(request)[0][2] 117 self.assertEqual(force_unicode(filterspec. title()), u'user')118 choice = select_by(filterspec. choices(changelist), "display", "bob")142 self.assertEqual(force_unicode(filterspec.get_title()), u'user') 143 choice = select_by(filterspec._choices(changelist), "display", "bob") 119 144 self.assertEqual(choice['selected'], True) 120 145 self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk) 121 146 … … class FilterSpecsTests(TestCase): 132 157 133 158 # Make sure the last choice is None and is selected 134 159 filterspec = changelist.get_filters(request)[0][0] 135 self.assertEqual(force_unicode(filterspec. title()), u'book')136 choices = list(filterspec. choices(changelist))160 self.assertEqual(force_unicode(filterspec.get_title()), u'book') 161 choices = list(filterspec._choices(changelist)) 137 162 self.assertEqual(choices[-1]['selected'], True) 138 163 self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True') 139 164 … … class FilterSpecsTests(TestCase): 142 167 143 168 # Make sure the correct choice is selected 144 169 filterspec = changelist.get_filters(request)[0][0] 145 self.assertEqual(force_unicode(filterspec. title()), u'book')146 choice = select_by(filterspec. choices(changelist), "display", self.bio_book.title)170 self.assertEqual(force_unicode(filterspec.get_title()), u'book') 171 choice = select_by(filterspec._choices(changelist), "display", self.bio_book.title) 147 172 self.assertEqual(choice['selected'], True) 148 173 self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk) 149 174 … … class FilterSpecsTests(TestCase): 156 181 157 182 # Make sure the last choice is None and is selected 158 183 filterspec = changelist.get_filters(request)[0][1] 159 self.assertEqual(force_unicode(filterspec. title()), u'book')160 choices = list(filterspec. choices(changelist))184 self.assertEqual(force_unicode(filterspec.get_title()), u'book') 185 choices = list(filterspec._choices(changelist)) 161 186 self.assertEqual(choices[-1]['selected'], True) 162 187 self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True') 163 188 … … class FilterSpecsTests(TestCase): 166 191 167 192 # Make sure the correct choice is selected 168 193 filterspec = changelist.get_filters(request)[0][1] 169 self.assertEqual(force_unicode(filterspec. title()), u'book')170 choice = select_by(filterspec. choices(changelist), "display", self.django_book.title)194 self.assertEqual(force_unicode(filterspec.get_title()), u'book') 195 choice = select_by(filterspec._choices(changelist), "display", self.django_book.title) 171 196 self.assertEqual(choice['selected'], True) 172 197 self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) 173 198 … … class FilterSpecsTests(TestCase): 184 209 185 210 # Make sure the last choice is None and is selected 186 211 filterspec = changelist.get_filters(request)[0][0] 187 self.assertEqual(force_unicode(filterspec. title()), u'completed')188 choices = list(filterspec. choices(changelist))212 self.assertEqual(force_unicode(filterspec.get_title()), u'completed') 213 choices = list(filterspec._choices(changelist)) 189 214 self.assertEqual(choices[-1]['selected'], False) 190 215 self.assertEqual(choices[-1]['query_string'], '?completed__exact=0') 191 216 … … class FilterSpecsTests(TestCase): 194 219 195 220 # Make sure the correct choice is selected 196 221 filterspec = changelist.get_filters(request)[0][0] 197 self.assertEqual(force_unicode(filterspec. title()), u'completed')222 self.assertEqual(force_unicode(filterspec.get_title()), u'completed') 198 223 # order of choices depends on User model, which has no order 199 choice = select_by(filterspec. choices(changelist), "display", "Yes")224 choice = select_by(filterspec._choices(changelist), "display", "Yes") 200 225 self.assertEqual(choice['selected'], True) 201 226 self.assertEqual(choice['query_string'], '?completed__exact=1') 202 227 228 def test_simple_filterspecs(self): 229 modeladmin = DecadeFilterBookAdmin(Book, admin.site) 230 231 # Look for books in the 1990s -------- 232 233 request = self.request_factory.get('/', {'publication-decade': '90s'}) 234 changelist = self.get_changelist(request, Book, modeladmin) 235 236 # Make sure the correct queryset is returned 237 queryset = changelist.get_query_set() 238 self.assertEqual(list(queryset), [self.bio_book]) 239 240 # Make sure the correct choice is selected 241 filterspec = changelist.get_filters(request)[0][1] 242 self.assertEqual(force_unicode(filterspec.get_title()), u'publication decade') 243 choices = list(filterspec._choices(changelist)) 244 self.assertEqual(choices[1]['selected'], True) 245 self.assertEqual(choices[1]['query_string'], '?publication-decade=90s') 246 247 # Look for books in the 2000s -------- 248 249 request = self.request_factory.get('/', {'publication-decade': '00s'}) 250 changelist = self.get_changelist(request, Book, modeladmin) 251 252 # Make sure the correct queryset is returned 253 queryset = changelist.get_query_set() 254 self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) 255 256 # Make sure the correct choice is selected 257 filterspec = changelist.get_filters(request)[0][1] 258 self.assertEqual(force_unicode(filterspec.get_title()), u'publication decade') 259 choices = list(filterspec._choices(changelist)) 260 self.assertEqual(choices[2]['selected'], True) 261 self.assertEqual(choices[2]['query_string'], '?publication-decade=00s') 262 263 # Combine multiple filters -------- 264 265 request = self.request_factory.get('/', {'publication-decade': '00s', 'author__id__exact': self.alfred.pk}) 266 changelist = self.get_changelist(request, Book, modeladmin) 267 268 # Make sure the correct queryset is returned 269 queryset = changelist.get_query_set() 270 self.assertEqual(list(queryset), [self.djangonaut_book]) 271 272 # Make sure the correct choices are selected 273 filterspec = changelist.get_filters(request)[0][1] 274 self.assertEqual(force_unicode(filterspec.get_title()), u'publication decade') 275 choices = list(filterspec._choices(changelist)) 276 self.assertEqual(choices[2]['selected'], True) 277 self.assertEqual(choices[2]['query_string'], '?publication-decade=00s&author__id__exact=%s' % self.alfred.pk) 278 279 filterspec = changelist.get_filters(request)[0][0] 280 self.assertEqual(force_unicode(filterspec.get_title()), u'author') 281 choice = select_by(filterspec._choices(changelist), "display", "alfred") 282 self.assertEqual(choice['selected'], True) 283 self.assertEqual(choice['query_string'], '?publication-decade=00s&author__id__exact=%s' % self.alfred.pk) 284 285 203 286 class CustomUserAdmin(UserAdmin): 204 287 list_filter = ('books_authored', 'books_contributed') 205 288 … … class BookAdmin(admin.ModelAdmin): 209 292 210 293 class BoolTestAdmin(admin.ModelAdmin): 211 294 list_filter = ('completed',) 295 296 class DecadeFilterBookAdmin(admin.ModelAdmin): 297 list_filter = ('author', DecadeFilterSpec) 298 order_by = '-id'