Ticket #5833: 5833.custom-filterspecs.9.diff
File 5833.custom-filterspecs.9.diff, 81.4 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..6a0d6b1 100644
a b from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 4 4 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 5 5 from django.contrib.admin.options import StackedInline, TabularInline 6 6 from django.contrib.admin.sites import AdminSite, site 7 from django.contrib.admin.filters import (ListFilterBase, 8 SimpleListFilter, FieldListFilter, BooleanFieldListFilter, 9 RelatedFieldListFilter, ChoicesFieldListFilter, DateFieldListFilter, 10 AllValuesFieldListFilter) 7 11 8 12 9 13 def autodiscover(): -
new file django/contrib/admin/filters.py
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py new file mode 100644 index 0000000..bb3bef8
- + 1 """ 2 FilterSpec encapsulates the logic for displaying filters in the Django admin. 3 Filters are specified in models with the "list_filter" option. 4 5 Each filter subclass knows how to display a filter for a field that passes a 6 certain test -- e.g. being a DateField or ForeignKey. 7 """ 8 import datetime 9 10 from django.db import models 11 from django.core.exceptions import ImproperlyConfigured 12 from django.utils.encoding import smart_unicode 13 from django.utils.translation import ugettext_lazy as _ 14 15 from django.contrib.admin.util import (get_model_from_relation, 16 reverse_field_path, get_limit_choices_to_from_path) 17 18 class ListFilterBase(object): 19 title = None # Human-readable title to appear in the right sidebar. 20 21 def __init__(self, request, params, model, model_admin): 22 self.params = params 23 if self.title is None: 24 raise ImproperlyConfigured( 25 "The list filter '%s' does not specify " 26 "a 'title'." % self.__class__.__name__) 27 28 def has_output(self): 29 """ 30 Returns True if some choices would be output for the filter. 31 """ 32 raise NotImplementedError 33 34 def choices(self, cl): 35 """ 36 Returns choices ready to be output in the template. 37 """ 38 raise NotImplementedError 39 40 def queryset(self, request, queryset): 41 """ 42 Returns the filtered queryset. 43 """ 44 raise NotImplementedError 45 46 def used_params(self): 47 """ 48 Return a list of parameters to consume from the change list 49 querystring. 50 """ 51 raise NotImplementedError 52 53 54 55 class SimpleListFilter(ListFilterBase): 56 # The parameter that should be used in the query string for that filter. 57 parameter_name = None 58 59 def __init__(self, request, params, model, model_admin): 60 super(SimpleListFilter, self).__init__( 61 request, params, model, model_admin) 62 if self.parameter_name is None: 63 raise ImproperlyConfigured( 64 "The list filter '%s' does not specify " 65 "a 'parameter_name'." % self.__class__.__name__) 66 self.lookup_choices = self.lookups(request) 67 68 def has_output(self): 69 return len(self.lookup_choices) > 0 70 71 def value(self): 72 """ 73 Returns the value given in the query string for this filter, 74 if any. Returns None otherwise. 75 """ 76 return self.params.get(self.parameter_name, None) 77 78 def lookups(self, request): 79 """ 80 Must be overriden to return a list of tuples (value, verbose value) 81 """ 82 raise NotImplementedError 83 84 def used_params(self): 85 return [self.parameter_name] 86 87 def choices(self, cl): 88 yield { 89 'selected': self.value() is None, 90 'query_string': cl.get_query_string({}, [self.parameter_name]), 91 'display': _('All'), 92 } 93 for lookup, title in self.lookup_choices: 94 yield { 95 'selected': self.value() == lookup, 96 'query_string': cl.get_query_string({ 97 self.parameter_name: lookup, 98 }, []), 99 'display': title, 100 } 101 102 103 class FieldListFilter(ListFilterBase): 104 _field_list_filters = [] 105 _take_priority_index = 0 106 107 def __init__(self, field, request, params, model, model_admin, field_path): 108 self.field = field 109 self.field_path = field_path 110 self.title = field_path 111 super(FieldListFilter, self).__init__(request, params, model, model_admin) 112 113 def has_output(self): 114 return True 115 116 def queryset(self, request, queryset): 117 for p in self.used_params(): 118 if p in self.params: 119 return queryset.filter(**{p: self.params[p]}) 120 121 @classmethod 122 def register(cls, test, list_filter_class, take_priority=False): 123 if take_priority: 124 # This is to allow overriding the default filters for certain types 125 # of fields with some custom filters. The first found in the list 126 # is used in priority. 127 cls._field_list_filters.insert( 128 cls._take_priority_index, (test, list_filter_class)) 129 cls._take_priority_index += 1 130 else: 131 cls._field_list_filters.append((test, list_filter_class)) 132 133 @classmethod 134 def create(cls, field, request, params, model, model_admin, field_path): 135 for test, list_filter_class in cls._field_list_filters: 136 if not test(field): 137 continue 138 return list_filter_class(field, request, params, 139 model, model_admin, field_path=field_path) 140 141 142 class RelatedFieldListFilter(FieldListFilter): 143 def __init__(self, field, request, params, model, model_admin, field_path): 144 super(RelatedFieldListFilter, self).__init__( 145 field, request, params, model, model_admin, field_path) 146 147 other_model = get_model_from_relation(field) 148 if isinstance(field, (models.ManyToManyField, 149 models.related.RelatedObject)): 150 # no direct field on this model, get name from other model 151 self.lookup_title = other_model._meta.verbose_name 152 else: 153 self.lookup_title = field.verbose_name # use field name 154 rel_name = other_model._meta.pk.name 155 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 156 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) 157 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 158 self.lookup_val_isnull = request.GET.get( 159 self.lookup_kwarg_isnull, None) 160 self.lookup_choices = field.get_choices(include_blank=False) 161 self.title = self.lookup_title 162 163 def has_output(self): 164 if (isinstance(self.field, models.related.RelatedObject) 165 and self.field.field.null or hasattr(self.field, 'rel') 166 and self.field.null): 167 extra = 1 168 else: 169 extra = 0 170 return len(self.lookup_choices) + extra > 1 171 172 def used_params(self): 173 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 174 175 def choices(self, cl): 176 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 177 yield { 178 'selected': self.lookup_val is None and not self.lookup_val_isnull, 179 'query_string': cl.get_query_string({}, 180 [self.lookup_kwarg, self.lookup_kwarg_isnull]), 181 'display': _('All'), 182 } 183 for pk_val, val in self.lookup_choices: 184 yield { 185 'selected': self.lookup_val == smart_unicode(pk_val), 186 'query_string': cl.get_query_string({ 187 self.lookup_kwarg: pk_val, 188 }, [self.lookup_kwarg_isnull]), 189 'display': val, 190 } 191 if (isinstance(self.field, models.related.RelatedObject) 192 and self.field.field.null or hasattr(self.field, 'rel') 193 and self.field.null): 194 yield { 195 'selected': bool(self.lookup_val_isnull), 196 'query_string': cl.get_query_string({ 197 self.lookup_kwarg_isnull: 'True', 198 }, [self.lookup_kwarg]), 199 'display': EMPTY_CHANGELIST_VALUE, 200 } 201 202 FieldListFilter.register(lambda f: ( 203 hasattr(f, 'rel') and bool(f.rel) or 204 isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter) 205 206 class BooleanFieldListFilter(FieldListFilter): 207 def __init__(self, field, request, params, model, model_admin, field_path): 208 super(BooleanFieldListFilter, self).__init__(field, 209 request, params, model, model_admin, field_path) 210 self.lookup_kwarg = '%s__exact' % self.field_path 211 self.lookup_kwarg2 = '%s__isnull' % self.field_path 212 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 213 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 214 215 def used_params(self): 216 return [self.lookup_kwarg, self.lookup_kwarg2] 217 218 def choices(self, cl): 219 for lookup, title in ( 220 (None, _('All')), 221 ('1', _('Yes')), 222 ('0', _('No'))): 223 yield { 224 'selected': self.lookup_val == lookup and not self.lookup_val2, 225 'query_string': cl.get_query_string({ 226 self.lookup_kwarg: lookup, 227 }, [self.lookup_kwarg2]), 228 'display': title, 229 } 230 if isinstance(self.field, models.NullBooleanField): 231 yield { 232 'selected': self.lookup_val2 == 'True', 233 'query_string': cl.get_query_string({ 234 self.lookup_kwarg2: 'True', 235 }, [self.lookup_kwarg]), 236 'display': _('Unknown'), 237 } 238 239 FieldListFilter.register(lambda f: isinstance(f, 240 (models.BooleanField, models.NullBooleanField)), BooleanFieldListFilter) 241 242 243 class ChoicesFieldListFilter(FieldListFilter): 244 def __init__(self, field, request, params, model, model_admin, field_path): 245 super(ChoicesFieldListFilter, self).__init__( 246 field, request, params, model, model_admin, field_path) 247 self.lookup_kwarg = '%s__exact' % self.field_path 248 self.lookup_val = request.GET.get(self.lookup_kwarg) 249 250 def used_params(self): 251 return [self.lookup_kwarg] 252 253 def choices(self, cl): 254 yield { 255 'selected': self.lookup_val is None, 256 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 257 'display': _('All') 258 } 259 for lookup, title in self.field.flatchoices: 260 yield { 261 'selected': smart_unicode(lookup) == self.lookup_val, 262 'query_string': cl.get_query_string({self.lookup_kwarg: lookup}), 263 'display': title, 264 } 265 266 FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter) 267 268 269 class DateFieldListFilter(FieldListFilter): 270 def __init__(self, field, request, params, model, model_admin, field_path): 271 super(DateFieldListFilter, self).__init__( 272 field, request, params, model, model_admin, field_path) 273 274 self.field_generic = '%s__' % self.field_path 275 self.date_params = dict([(k, v) for k, v in params.items() 276 if k.startswith(self.field_generic)]) 277 278 today = datetime.date.today() 279 one_week_ago = today - datetime.timedelta(days=7) 280 today_str = (isinstance(self.field, models.DateTimeField) 281 and today.strftime('%Y-%m-%d 23:59:59') 282 or today.strftime('%Y-%m-%d')) 283 284 self.lookup_kwarg_year = '%s__year' % self.field_path 285 self.lookup_kwarg_month = '%s__month' % self.field_path 286 self.lookup_kwarg_day = '%s__day' % self.field_path 287 self.lookup_kwarg_past_7_days_gte = '%s__gte' % self.field_path 288 self.lookup_kwarg_past_7_days_lte = '%s__lte' % self.field_path 289 290 self.links = ( 291 (_('Any date'), {}), 292 (_('Today'), { 293 self.lookup_kwarg_year: str(today.year), 294 self.lookup_kwarg_month: str(today.month), 295 self.lookup_kwarg_day: str(today.day), 296 }), 297 (_('Past 7 days'), { 298 self.lookup_kwarg_past_7_days_gte: one_week_ago.strftime('%Y-%m-%d'), 299 self.lookup_kwarg_past_7_days_lte: today_str, 300 }), 301 (_('This month'), { 302 self.lookup_kwarg_year: str(today.year), 303 self.lookup_kwarg_month: str(today.month), 304 }), 305 (_('This year'), { 306 self.lookup_kwarg_year: str(today.year), 307 }), 308 ) 309 310 def used_params(self): 311 return [ 312 self.lookup_kwarg_year, self.lookup_kwarg_month, self.lookup_kwarg_day, 313 self.lookup_kwarg_past_7_days_gte, self.lookup_kwarg_past_7_days_lte 314 ] 315 316 def queryset(self, request, queryset): 317 """ 318 Override the default behaviour since there can be multiple query 319 string parameters used for the same date filter (e.g. year + month). 320 """ 321 query_dict = {} 322 for p in self.used_params(): 323 if p in self.params: 324 query_dict[p] = self.params[p] 325 if len(query_dict): 326 return queryset.filter(**query_dict) 327 328 def choices(self, cl): 329 for title, param_dict in self.links: 330 yield { 331 'selected': self.date_params == param_dict, 332 'query_string': cl.get_query_string( 333 param_dict, [self.field_generic]), 334 'display': title, 335 } 336 337 FieldListFilter.register( 338 lambda f: isinstance(f, models.DateField), DateFieldListFilter) 339 340 341 # This should be registered last, because it's a last resort. For example, 342 # if a field is eligible to use the BooleanFieldListFilter, that'd be much 343 # more appropriate, and the AllValuesFieldListFilter won't get used for it. 344 class AllValuesFieldListFilter(FieldListFilter): 345 def __init__(self, field, request, params, model, model_admin, field_path): 346 super(AllValuesFieldListFilter, self).__init__( 347 field, request, params, model, model_admin, field_path) 348 self.lookup_kwarg = self.field_path 349 self.lookup_kwarg_isnull = '%s__isnull' % self.field_path 350 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 351 self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull, None) 352 parent_model, reverse_path = reverse_field_path(model, self.field_path) 353 queryset = parent_model._default_manager.all() 354 # optional feature: limit choices base on existing relationships 355 # queryset = queryset.complex_filter( 356 # {'%s__isnull' % reverse_path: False}) 357 limit_choices_to = get_limit_choices_to_from_path(model, field_path) 358 queryset = queryset.filter(limit_choices_to) 359 360 self.lookup_choices = queryset.distinct( 361 ).order_by(field.name).values_list(field.name, flat=True) 362 363 def used_params(self): 364 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 365 366 def choices(self, cl): 367 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 368 yield { 369 'selected': (self.lookup_val is None 370 and self.lookup_val_isnull is None), 371 'query_string': cl.get_query_string({}, 372 [self.lookup_kwarg, self.lookup_kwarg_isnull]), 373 'display': _('All'), 374 } 375 include_none = False 376 for val in self.lookup_choices: 377 if val is None: 378 include_none = True 379 continue 380 val = smart_unicode(val) 381 yield { 382 'selected': self.lookup_val == val, 383 'query_string': cl.get_query_string({ 384 self.lookup_kwarg: val, 385 }, [self.lookup_kwarg_isnull]), 386 'display': val, 387 } 388 if include_none: 389 yield { 390 'selected': bool(self.lookup_val_isnull), 391 'query_string': cl.get_query_string({ 392 self.lookup_kwarg_isnull: 'True', 393 }, [self.lookup_kwarg]), 394 'display': EMPTY_CHANGELIST_VALUE, 395 } 396 397 FieldListFilter.register(lambda f: True, AllValuesFieldListFilter) -
deleted file django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py deleted file mode 100644 index 965b32b..0000000
+ - 1 """2 FilterSpec encapsulates the logic for displaying filters in the Django admin.3 Filters are specified in models with the "list_filter" option.4 5 Each filter subclass knows how to display a filter for a field that passes a6 certain test -- e.g. being a DateField or ForeignKey.7 """8 9 from django.db import models10 from django.utils.encoding import smart_unicode, iri_to_uri11 from django.utils.translation import ugettext as _12 from django.utils.html import escape13 from django.utils.safestring import mark_safe14 from django.contrib.admin.util import get_model_from_relation, \15 reverse_field_path, get_limit_choices_to_from_path16 import datetime17 18 class FilterSpec(object):19 filter_specs = []20 def __init__(self, f, request, params, model, model_admin,21 field_path=None):22 self.field = f23 self.params = params24 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 42 def has_output(self):43 return True44 45 def choices(self, cl):46 raise NotImplementedError()47 48 def title(self):49 return self.field.verbose_name50 51 def output(self, cl):52 t = []53 if self.has_output():54 t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title()))55 56 for choice in self.choices(cl):57 t.append(u'<li%s><a href="%s">%s</a></li>\n' % \58 ((choice['selected'] and ' class="selected"' or ''),59 iri_to_uri(choice['query_string']),60 choice['display']))61 t.append('</ul>\n\n')62 return mark_safe("".join(t))63 64 class RelatedFilterSpec(FilterSpec):65 def __init__(self, f, request, params, model, model_admin,66 field_path=None):67 super(RelatedFilterSpec, self).__init__(68 f, request, params, model, model_admin, field_path=field_path)69 70 other_model = get_model_from_relation(f)71 if isinstance(f, (models.ManyToManyField,72 models.related.RelatedObject)):73 # no direct field on this model, get name from other model74 self.lookup_title = other_model._meta.verbose_name75 else:76 self.lookup_title = f.verbose_name # use field name77 rel_name = other_model._meta.pk.name78 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name)79 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)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)83 self.lookup_choices = f.get_choices(include_blank=False)84 85 def has_output(self):86 if isinstance(self.field, models.related.RelatedObject) \87 and self.field.field.null or hasattr(self.field, 'rel') \88 and self.field.null:89 extra = 190 else:91 extra = 092 return len(self.lookup_choices) + extra > 193 94 def title(self):95 return self.lookup_title96 97 def choices(self, cl):98 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE99 yield {'selected': self.lookup_val is None100 and not self.lookup_val_isnull,101 'query_string': cl.get_query_string(102 {},103 [self.lookup_kwarg, self.lookup_kwarg_isnull]),104 'display': _('All')}105 for pk_val, val in self.lookup_choices:106 yield {'selected': self.lookup_val == smart_unicode(pk_val),107 'query_string': cl.get_query_string(108 {self.lookup_kwarg: pk_val},109 [self.lookup_kwarg_isnull]),110 'display': val}111 if isinstance(self.field, models.related.RelatedObject) \112 and self.field.field.null or hasattr(self.field, 'rel') \113 and self.field.null:114 yield {'selected': bool(self.lookup_val_isnull),115 'query_string': cl.get_query_string(116 {self.lookup_kwarg_isnull: 'True'},117 [self.lookup_kwarg]),118 'display': EMPTY_CHANGELIST_VALUE}119 120 FilterSpec.register(lambda f: (121 hasattr(f, 'rel') and bool(f.rel) or122 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec)123 124 class BooleanFieldFilterSpec(FilterSpec):125 def __init__(self, f, request, params, model, model_admin,126 field_path=None):127 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model,128 model_admin,129 field_path=field_path)130 self.lookup_kwarg = '%s__exact' % self.field_path131 self.lookup_kwarg2 = '%s__isnull' % self.field_path132 self.lookup_val = request.GET.get(self.lookup_kwarg, None)133 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)134 135 def title(self):136 return self.field.verbose_name137 138 def choices(self, cl):139 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):140 yield {'selected': self.lookup_val == v and not self.lookup_val2,141 'query_string': cl.get_query_string(142 {self.lookup_kwarg: v},143 [self.lookup_kwarg2]),144 'display': k}145 if isinstance(self.field, models.NullBooleanField):146 yield {'selected': self.lookup_val2 == 'True',147 'query_string': cl.get_query_string(148 {self.lookup_kwarg2: 'True'},149 [self.lookup_kwarg]),150 'display': _('Unknown')}151 152 FilterSpec.register(lambda f: isinstance(f, models.BooleanField)153 or isinstance(f, models.NullBooleanField),154 BooleanFieldFilterSpec)155 156 class ChoicesFilterSpec(FilterSpec):157 def __init__(self, f, request, params, model, model_admin,158 field_path=None):159 super(ChoicesFilterSpec, self).__init__(f, request, params, model,160 model_admin,161 field_path=field_path)162 self.lookup_kwarg = '%s__exact' % self.field_path163 self.lookup_val = request.GET.get(self.lookup_kwarg, None)164 165 def choices(self, cl):166 yield {'selected': self.lookup_val is None,167 'query_string': cl.get_query_string({}, [self.lookup_kwarg]),168 'display': _('All')}169 for k, v in self.field.flatchoices:170 yield {'selected': smart_unicode(k) == self.lookup_val,171 'query_string': cl.get_query_string(172 {self.lookup_kwarg: k}),173 'display': v}174 175 FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)176 177 class DateFieldFilterSpec(FilterSpec):178 def __init__(self, f, request, params, model, model_admin,179 field_path=None):180 super(DateFieldFilterSpec, self).__init__(f, request, params, model,181 model_admin,182 field_path=field_path)183 184 self.field_generic = '%s__' % self.field_path185 186 self.date_params = dict([(k, v) for k, v in params.items()187 if k.startswith(self.field_generic)])188 189 today = datetime.date.today()190 one_week_ago = today - datetime.timedelta(days=7)191 today_str = isinstance(self.field, models.DateTimeField) \192 and today.strftime('%Y-%m-%d 23:59:59') \193 or today.strftime('%Y-%m-%d')194 195 self.links = (196 (_('Any date'), {}),197 (_('Today'), {'%s__year' % self.field_path: str(today.year),198 '%s__month' % self.field_path: str(today.month),199 '%s__day' % self.field_path: str(today.day)}),200 (_('Past 7 days'), {'%s__gte' % self.field_path:201 one_week_ago.strftime('%Y-%m-%d'),202 '%s__lte' % self.field_path: today_str}),203 (_('This month'), {'%s__year' % self.field_path: str(today.year),204 '%s__month' % self.field_path: str(today.month)}),205 (_('This year'), {'%s__year' % self.field_path: str(today.year)})206 )207 208 def title(self):209 return self.field.verbose_name210 211 def choices(self, cl):212 for title, param_dict in self.links:213 yield {'selected': self.date_params == param_dict,214 'query_string': cl.get_query_string(215 param_dict,216 [self.field_generic]),217 'display': title}218 219 FilterSpec.register(lambda f: isinstance(f, models.DateField),220 DateFieldFilterSpec)221 222 223 # This should be registered last, because it's a last resort. For example,224 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much225 # more appropriate, and the AllValuesFilterSpec won't get used for it.226 class AllValuesFilterSpec(FilterSpec):227 def __init__(self, f, request, params, model, model_admin,228 field_path=None):229 super(AllValuesFilterSpec, self).__init__(f, request, params, model,230 model_admin,231 field_path=field_path)232 self.lookup_kwarg = self.field_path233 self.lookup_kwarg_isnull = '%s__isnull' % self.field_path234 self.lookup_val = request.GET.get(self.lookup_kwarg, None)235 self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull,236 None)237 parent_model, reverse_path = reverse_field_path(model, self.field_path)238 queryset = parent_model._default_manager.all()239 # optional feature: limit choices base on existing relationships240 # queryset = queryset.complex_filter(241 # {'%s__isnull' % reverse_path: False})242 limit_choices_to = get_limit_choices_to_from_path(model, field_path)243 queryset = queryset.filter(limit_choices_to)244 245 self.lookup_choices = \246 queryset.distinct().order_by(f.name).values_list(f.name, flat=True)247 248 def title(self):249 return self.field.verbose_name250 251 def choices(self, cl):252 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE253 yield {'selected': self.lookup_val is None254 and self.lookup_val_isnull is None,255 'query_string': cl.get_query_string(256 {},257 [self.lookup_kwarg, self.lookup_kwarg_isnull]),258 'display': _('All')}259 include_none = False260 261 for val in self.lookup_choices:262 if val is None:263 include_none = True264 continue265 val = smart_unicode(val)266 267 yield {'selected': self.lookup_val == val,268 'query_string': cl.get_query_string(269 {self.lookup_kwarg: val},270 [self.lookup_kwarg_isnull]),271 'display': val}272 if include_none:273 yield {'selected': bool(self.lookup_val_isnull),274 'query_string': cl.get_query_string(275 {self.lookup_kwarg_isnull: 'True'},276 [self.lookup_kwarg]),277 'display': EMPTY_CHANGELIST_VALUE}278 279 FilterSpec.register(lambda f: True, AllValuesFilterSpec) -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 098eda5..787f856 100644
a b class ModelAdmin(BaseModelAdmin): 1091 1091 if (actions and request.method == 'POST' and 1092 1092 'index' in request.POST and '_save' not in request.POST): 1093 1093 if selected: 1094 response = self.response_action(request, queryset=cl.get_query_set( ))1094 response = self.response_action(request, queryset=cl.get_query_set(request)) 1095 1095 if response: 1096 1096 return response 1097 1097 else: … … class ModelAdmin(BaseModelAdmin): 1107 1107 helpers.ACTION_CHECKBOX_NAME in request.POST and 1108 1108 'index' not in request.POST and '_save' not in request.POST): 1109 1109 if selected: 1110 response = self.response_action(request, queryset=cl.get_query_set( ))1110 response = self.response_action(request, queryset=cl.get_query_set(request)) 1111 1111 if response: 1112 1112 return response 1113 1113 else: -
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 5e199ce..b72c0be 100644
a b def search_form(cl): 319 319 320 320 @register.inclusion_tag('admin/filter.html') 321 321 def admin_list_filter(cl, spec): 322 return {'title': spec.title (), 'choices' : list(spec.choices(cl))}322 return {'title': spec.title, 'choices' : list(spec.choices(cl))} 323 323 324 324 @register.inclusion_tag('admin/actions.html', takes_context=True) 325 325 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..73b400c 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 import ListFilterBase, FieldListFilter 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 options for specifying a filter: 60 # 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel') 61 # 2: ('field', SomeFieldListFilter) - a field-based list filter class 62 # 3: SomeListFilter - a non-field list filter class 63 if callable(item) and not isinstance(item, models.Field): 64 # If item is option 3, it should be a ListFilterBase... 65 if not issubclass(item, ListFilterBase): 66 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 67 " which is not a descendant of ListFilterBase." 68 % (cls.__name__, idx, item.__name__)) 69 # ... but not a FieldListFilter. 70 if issubclass(item, FieldListFilter): 71 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 72 " which is of type FieldListFilter but is not" 73 " associated with a field name." 74 % (cls.__name__, idx, item.__name__)) 75 else: 76 try: 77 # Check for option #2 (tuple) 78 field, list_filter_class = item 79 except (TypeError, ValueError): 80 # item is option #1 81 field = item 82 else: 83 # item is option #2 84 if not issubclass(list_filter_class, FieldListFilter): 85 raise ImproperlyConfigured("'%s.list_filter[%d][1]'" 86 " is '%s' which is not of type FieldListFilter." 87 % (cls.__name__, idx, list_filter_class.__name__)) 88 # Validate the field string 89 try: 90 get_fields_from_path(model, field) 91 except (NotRelationField, FieldDoesNotExist): 92 raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" 93 " which does not refer to a Field." 94 % (cls.__name__, idx, field)) 66 95 67 96 # list_per_page = 100 68 97 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 0cfc43d..d5f401c 100644
a b 1 from django.contrib.admin.filterspecs import FilterSpec 2 from django.contrib.admin.options import IncorrectLookupParameters 3 from django.contrib.admin.util import quote, get_fields_from_path 1 import operator 2 4 3 from django.core.exceptions import SuspiciousOperation 5 4 from django.core.paginator import InvalidPage 6 5 from django.db import models 7 6 from django.utils.encoding import force_unicode, smart_str 8 7 from django.utils.translation import ugettext, ugettext_lazy 9 8 from django.utils.http import urlencode 10 import operator 9 10 from django.contrib.admin import FieldListFilter 11 from django.contrib.admin.options import IncorrectLookupParameters 12 from django.contrib.admin.util import quote, get_fields_from_path 11 13 12 14 # The system will display a "Show all" link on the change list only if the 13 15 # total result count is less than or equal to this setting. … … TO_FIELD_VAR = 't' 23 25 IS_POPUP_VAR = 'pop' 24 26 ERROR_FLAG = 'e' 25 27 28 IGNORED_PARAMS = ( 29 ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR) 30 26 31 # Text to display within change-list table cells if the value is blank. 27 32 EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)') 28 33 … … def field_needs_distinct(field): 36 41 37 42 38 43 class ChangeList(object): 39 def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin): 44 def __init__(self, request, model, list_display, list_display_links, 45 list_filter, date_hierarchy, search_fields, list_select_related, 46 list_per_page, list_editable, model_admin): 40 47 self.model = model 41 48 self.opts = model._meta 42 49 self.lookup_opts = self.opts … … class ChangeList(object): 70 77 self.list_editable = list_editable 71 78 self.order_field, self.order_type = self.get_ordering() 72 79 self.query = request.GET.get(SEARCH_VAR, '') 73 self.query_set = self.get_query_set( )80 self.query_set = self.get_query_set(request) 74 81 self.get_results(request) 75 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)) 76 self.filter_specs, self.has_filters = self.get_filters(request) 82 if self.is_popup: 83 title = ugettext('Select %s') 84 else: 85 title = ugettext('Select %s to change') 86 self.title = title % force_unicode(self.opts.verbose_name) 77 87 self.pk_attname = self.lookup_opts.pk.attname 78 88 79 def get_filters(self, request ):89 def get_filters(self, request, use_distinct=False): 80 90 filter_specs = [] 91 cleaned_params, use_distinct = self.get_lookup_params(use_distinct) 81 92 if self.list_filter: 82 for filter_name in self.list_filter: 83 field = get_fields_from_path(self.model, filter_name)[-1] 84 spec = FilterSpec.create(field, request, self.params, 85 self.model, self.model_admin, 86 field_path=filter_name) 93 for list_filer in self.list_filter: 94 if callable(list_filer): 95 # This is simply a custom list filter class. 96 spec = list_filer(request, cleaned_params, 97 self.model, self.model_admin) 98 else: 99 field_path = None 100 try: 101 # This is custom FieldListFilter class for a given field. 102 field, field_list_filter_class = list_filer 103 except (TypeError, ValueError): 104 # This is simply a field name, so use the default 105 # FieldListFilter class that has been registered for 106 # the type of the given field. 107 field, field_list_filter_class = list_filer, FieldListFilter.create 108 if not isinstance(field, models.Field): 109 field_path = field 110 field = get_fields_from_path(self.model, field_path)[-1] 111 spec = field_list_filter_class(field, request, cleaned_params, 112 self.model, self.model_admin, field_path=field_path) 87 113 if spec and spec.has_output(): 88 114 filter_specs.append(spec) 89 115 return filter_specs, bool(filter_specs) … … class ChangeList(object): 175 201 order_type = params[ORDER_TYPE_VAR] 176 202 return order_field, order_type 177 203 178 def get_query_set(self): 179 use_distinct = False 180 181 qs = self.root_query_set 204 def get_lookup_params(self, use_distinct=False): 182 205 lookup_params = self.params.copy() # a dictionary of the query string 183 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR): 184 if i in lookup_params: 185 del lookup_params[i] 206 207 for ignored in IGNORED_PARAMS: 208 if ignored in lookup_params: 209 del lookup_params[ignored] 210 186 211 for key, value in lookup_params.items(): 187 212 if not isinstance(key, str): 188 213 # 'key' will be used as a keyword argument later, so Python … … class ChangeList(object): 195 220 # instance 196 221 field_name = key.split('__', 1)[0] 197 222 try: 198 f = self.lookup_opts.get_field_by_name(field_name)[0] 223 field = self.lookup_opts.get_field_by_name(field_name)[0] 224 use_distinct = field_needs_distinct(field) 199 225 except models.FieldDoesNotExist: 200 raise IncorrectLookupParameters201 use_distinct = field_needs_distinct(f)226 # It might be a custom NonFieldFilter 227 pass 202 228 203 229 # if key ends with __in, split parameter into separate values 204 230 if key.endswith('__in'): … … class ChangeList(object): 214 240 lookup_params[key] = value 215 241 216 242 if not self.model_admin.lookup_allowed(key, value): 217 raise SuspiciousOperation( 218 "Filtering by %s not allowed" % key 219 ) 243 raise SuspiciousOperation("Filtering by %s not allowed" % key) 244 245 return lookup_params, use_distinct 246 247 def get_query_set(self, request): 248 lookup_params, use_distinct = self.get_lookup_params(use_distinct=False) 249 self.filter_specs, self.has_filters = self.get_filters(request, use_distinct) 250 251 # Let every list filter modify the qs and params to its liking 252 qs = self.root_query_set 253 for filter_spec in self.filter_specs: 254 new_qs = filter_spec.queryset(request, qs) 255 if new_qs is not None: 256 qs = new_qs 257 for param in filter_spec.used_params(): 258 try: 259 del lookup_params[param] 260 except KeyError: 261 pass 220 262 221 # Apply lookup parameters from the query string. 263 # Apply the remaining lookup parameters from the query string (i.e. 264 # those that haven't already been processed by the filters). 222 265 try: 223 266 qs = qs.filter(**lookup_params) 224 267 # Naked except! Because we don't have any other way of validating "params". … … class ChangeList(object): 226 269 # values are not in the correct type, so we might get FieldError, ValueError, 227 270 # ValicationError, or ? from a custom field that raises yet something else 228 271 # when handed impossible data. 229 except :230 raise IncorrectLookupParameters 272 except Exception, e: 273 raise IncorrectLookupParameters(e) 231 274 232 275 # Use select_related() if one of the list_display options is a field 233 276 # with a relationship and the provided queryset doesn't already have … … class ChangeList(object): 238 281 else: 239 282 for field_name in self.list_display: 240 283 try: 241 f = self.lookup_opts.get_field(field_name)284 field = self.lookup_opts.get_field(field_name) 242 285 except models.FieldDoesNotExist: 243 286 pass 244 287 else: 245 if isinstance(f .rel, models.ManyToOneRel):288 if isinstance(field.rel, models.ManyToOneRel): 246 289 qs = qs.select_related() 247 290 break 248 291 -
django/db/models/related.py
diff --git a/django/db/models/related.py b/django/db/models/related.py index 7734230..90995d7 100644
a b class RelatedObject(object): 27 27 as SelectField choices for this field. 28 28 29 29 Analogue of django.db.models.fields.Field.get_choices, provided 30 initially for utilisation by RelatedFi lterSpec.30 initially for utilisation by RelatedFieldListFilter. 31 31 """ 32 32 first_choice = include_blank and blank_choice or [] 33 33 queryset = self.model._default_manager.all() -
docs/ref/contrib/admin/index.txt
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 633c53f..7178037 100644
a b subclass:: 525 525 526 526 .. attribute:: ModelAdmin.list_filter 527 527 528 Set ``list_filter`` to activate filters in the right sidebar of the change 529 list page of the admin. This should be a list of field names, and each 530 specified field should be either a ``BooleanField``, ``CharField``, 531 ``DateField``, ``DateTimeField``, ``IntegerField`` or ``ForeignKey``. 532 533 This example, taken from the ``django.contrib.auth.models.User`` model, 534 shows how both ``list_display`` and ``list_filter`` work:: 528 .. versionchanged:: 1.4 535 529 536 class UserAdmin(admin.ModelAdmin): 537 list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') 538 list_filter = ('is_staff', 'is_superuser') 539 540 The above code results in an admin change list page that looks like this: 530 Set ``list_filter`` to activate filters in the right sidebar of the change 531 list page of the admin, as illustrated in the following screenshot: 541 532 542 533 .. image:: _images/users_changelist.png 543 534 544 (This example also has ``search_fields`` defined. See below.) 545 546 .. versionadded:: 1.3 547 548 Fields in ``list_filter`` can also span relations using the ``__`` lookup:: 549 550 class UserAdminWithLookup(UserAdmin): 551 list_filter = ('groups__name') 535 ``list_filter`` should be a list of elements, where each element should be 536 of one of the following types: 537 538 * a field name, where the specified field should be either a 539 ``BooleanField``, ``CharField``, ``DateField``, ``DateTimeField``, 540 ``IntegerField``, ``ForeignKey`` or ``ManyToManyField``, for example:: 541 542 class PersonAdmin(ModelAdmin): 543 list_filter = ('is_staff', 'company') 544 545 .. versionadded:: 1.3 546 547 Field names in ``list_filter`` can also span relations 548 using the ``__`` lookup, for example:: 549 550 class PersonAdmin(UserAdmin): 551 list_filter = ('company__name',) 552 553 * a class inheriting from :mod:`django.contrib.admin.SimpleListFilter`, 554 where you need to provide a few attributes and override a few 555 methods:: 556 557 from django.db.models import Q 558 from django.utils.translation import ugettext_lazy as _ 559 560 from django.contrib.admin import SimpleListFilter 561 562 class DecadeBornListFilter(SimpleListFilter): 563 # Human-readable title which will be displayed in the 564 # right sidebar just above the filter options. 565 verbose_name = _('decade born') 566 567 # Parameter for the filter that will be used in the url query. 568 parameter_name = 'decade' 569 570 def lookups(self, request): 571 """ 572 Returns a list of tuples. The first element in each tuple 573 is the coded value for the option that will appear in the 574 url query. The second element is the human-readable name 575 for the option that will appear in the right sidebar. 576 """ 577 return ( 578 ('80s', 'in the eighties'), 579 ('other', 'other'), 580 ) 581 582 def queryset(self, request, queryset): 583 """ 584 Returns the filtered queryset based on the value provided 585 in the query string and retrievable via `value()`. 586 """ 587 # Compare the requested value (either '80s' or 'other') 588 # to decide how to filter the queryset. 589 if self.value() == '80s': 590 return queryset.filter(birthday__year__gte=1980, 591 birthday__year__lte=1989) 592 if self.value() == 'other': 593 return queryset.filter(Q(year__lte=1979) | 594 Q(year__gte=1990)) 595 596 class PersonAdmin(ModelAdmin): 597 list_filter = (DecadeBornListFilter,) 598 599 .. note:: 600 601 As a convenience, the ``HttpRequest`` object is passed to the 602 filter's methods, for example:: 603 604 class AuthDecadeBornListFilter(DecadeBornListFilter): 605 606 def lookups(self, request): 607 if request.user.is_authenticated(): 608 return ( 609 ('80s', 'in the eighties'), 610 ('other', 'other'), 611 ) 612 else: 613 return ( 614 ('90s', 'in the nineties'), 615 ) 616 617 * a tuple, where the first element is a field name and the second 618 element is a class inheriting from 619 :mod:`django.contrib.admin.FieldListFilter`, for example:: 620 621 from django.contrib.admin import BooleanFieldListFilter 622 623 class PersonAdmin(ModelAdmin): 624 list_filter = ( 625 ('is_staff', BooleanFieldListFilter), 626 ) 627 628 .. note:: 629 630 The ``FieldListFilter`` API is currently considered internal 631 and prone to refactoring. 552 632 553 633 .. attribute:: ModelAdmin.list_per_page 554 634 -
tests/regressiontests/admin_filterspecs/models.py
diff --git a/tests/regressiontests/admin_filterspecs/models.py b/tests/regressiontests/admin_filterspecs/models.py index 5b284c7..80d54c7 100644
a b from django.db import models 2 2 from django.contrib.auth.models import User 3 3 4 4 class Book(models.Model): 5 title = models.CharField(max_length= 25)5 title = models.CharField(max_length=50) 6 6 year = models.PositiveIntegerField(null=True, blank=True) 7 7 author = models.ForeignKey(User, related_name='books_authored', blank=True, null=True) 8 8 contributors = models.ManyToManyField(User, related_name='books_contributed', blank=True, null=True) 9 is_best_seller = models.NullBooleanField(default=0) 10 date_registered = models.DateField(null=True) 9 11 10 12 def __unicode__(self): 11 13 return self.title 12 13 class BoolTest(models.Model):14 NO = False15 YES = True16 YES_NO_CHOICES = (17 (NO, 'no'),18 (YES, 'yes')19 )20 completed = models.BooleanField(21 default=NO,22 choices=YES_NO_CHOICES23 ) -
tests/regressiontests/admin_filterspecs/tests.py
diff --git a/tests/regressiontests/admin_filterspecs/tests.py b/tests/regressiontests/admin_filterspecs/tests.py index 8b9e734..7cec6db 100644
a b 1 from __future__ import with_statement 2 3 import datetime 4 import calendar 5 1 6 from django.contrib.auth.admin import UserAdmin 2 7 from django.test import TestCase 3 8 from django.test.client import RequestFactory … … from django.contrib.auth.models import User 5 10 from django.contrib import admin 6 11 from django.contrib.admin.views.main import ChangeList 7 12 from django.utils.encoding import force_unicode 13 from django.core.exceptions import ImproperlyConfigured 14 from django.contrib.admin import (SimpleListFilter, 15 BooleanFieldListFilter, FieldListFilter) 8 16 9 from models import Book , BoolTest17 from models import Book 10 18 11 19 def select_by(dictlist, key, value): 12 20 return [x for x in dictlist if x[key] == value][0] 13 21 14 class FilterSpecsTests(TestCase):15 22 16 def setUp(self): 17 # Users 18 self.alfred = User.objects.create_user('alfred', 'alfred@example.com') 19 self.bob = User.objects.create_user('bob', 'bob@example.com') 20 lisa = User.objects.create_user('lisa', 'lisa@example.com') 23 class DecadeListFilterBase(SimpleListFilter): 24 25 def lookups(self, request): 26 return ( 27 (u'the 90s', u'the 1990\'s'), 28 (u'the 00s', u'the 2000\'s'), 29 (u'other', u'other decades'), 30 ) 31 32 def queryset(self, request, queryset): 33 decade = self.value() 34 if decade == u'the 90s': 35 return queryset.filter(year__gte=1990, year__lte=1999) 36 if decade == u'the 00s': 37 return queryset.filter(year__gte=2000, year__lte=2009) 38 return queryset 39 40 class DecadeListFilterWithTitleAndParameter(DecadeListFilterBase): 41 title = 'publication decade' 42 parameter_name = 'publication-decade' 43 44 class DecadeListFilterWithoutTitle(DecadeListFilterBase): 45 parameter_name = 'publication-decade' 46 47 class DecadeListFilterWithoutParameter(DecadeListFilterBase): 48 title = 'publication decade' 49 50 class CustomUserAdmin(UserAdmin): 51 list_filter = ('books_authored', 'books_contributed') 52 53 class BookAdmin(admin.ModelAdmin): 54 list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered') 55 order_by = '-id' 21 56 22 #Books 23 self.bio_book = Book.objects.create(title='Django: a biography', year=1999, author=self.alfred) 24 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() 57 class DecadeFilterBookAdmin(admin.ModelAdmin): 58 list_filter = ('author', DecadeListFilterWithTitleAndParameter) 59 order_by = '-id' 60 61 class DecadeFilterBookAdminWithoutTitle(admin.ModelAdmin): 62 list_filter = (DecadeListFilterWithoutTitle,) 63 64 class DecadeFilterBookAdminWithoutParameter(admin.ModelAdmin): 65 list_filter = (DecadeListFilterWithoutParameter,) 66 67 class ListFiltersTests(TestCase): 28 68 29 # BoolTests30 self.t rueTest = BoolTest.objects.create(completed=True)31 self. falseTest = BoolTest.objects.create(completed=False)69 def setUp(self): 70 self.today = datetime.date.today() 71 self.one_week_ago = self.today - datetime.timedelta(days=7) 32 72 33 73 self.request_factory = RequestFactory() 34 74 75 # Users 76 self.alfred = User.objects.create_user('alfred', 'alfred@example.com') 77 self.bob = User.objects.create_user('bob', 'bob@example.com') 78 self.lisa = User.objects.create_user('lisa', 'lisa@example.com') 79 80 # Books 81 self.djangonaut_book = Book.objects.create(title='Djangonaut: an art of living', year=2009, author=self.alfred, is_best_seller=True, date_registered=self.today) 82 self.bio_book = Book.objects.create(title='Django: a biography', year=1999, author=self.alfred, is_best_seller=False) 83 self.django_book = Book.objects.create(title='The Django Book', year=None, author=self.bob, is_best_seller=None, date_registered=self.today) 84 self.gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002, is_best_seller=True, date_registered=self.one_week_ago) 85 self.gipsy_book.contributors = [self.bob, self.lisa] 86 self.gipsy_book.save() 35 87 36 88 def get_changelist(self, request, model, modeladmin): 37 89 return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links, 38 90 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 39 91 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 40 92 41 def test_AllValuesFilterSpec(self): 93 def test_DateFieldListFilter(self): 94 modeladmin = BookAdmin(Book, admin.site) 95 96 request = self.request_factory.get('/') 97 changelist = self.get_changelist(request, Book, modeladmin) 98 99 request = self.request_factory.get('/', {'date_registered__year': self.today.year, 100 'date_registered__month': self.today.month, 101 'date_registered__day': self.today.day}) 102 changelist = self.get_changelist(request, Book, modeladmin) 103 104 # Make sure the correct queryset is returned 105 queryset = changelist.get_query_set(request) 106 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 107 108 # Make sure the correct choice is selected 109 filterspec = changelist.get_filters(request)[0][4] 110 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 111 choice = select_by(filterspec.choices(changelist), "display", "Today") 112 self.assertEqual(choice['selected'], True) 113 self.assertEqual(choice['query_string'], '?date_registered__day=%s' 114 '&date_registered__month=%s' 115 '&date_registered__year=%s' 116 % (self.today.day, self.today.month, self.today.year)) 117 118 request = self.request_factory.get('/', {'date_registered__year': self.today.year, 119 'date_registered__month': self.today.month}) 120 changelist = self.get_changelist(request, Book, modeladmin) 121 122 # Make sure the correct queryset is returned 123 queryset = changelist.get_query_set(request) 124 if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month): 125 # In case one week ago is in the same month. 126 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 127 else: 128 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 129 130 # Make sure the correct choice is selected 131 filterspec = changelist.get_filters(request)[0][4] 132 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 133 choice = select_by(filterspec.choices(changelist), "display", "This month") 134 self.assertEqual(choice['selected'], True) 135 self.assertEqual(choice['query_string'], '?date_registered__month=%s' 136 '&date_registered__year=%s' 137 % (self.today.month, self.today.year)) 138 139 request = self.request_factory.get('/', {'date_registered__year': self.today.year}) 140 changelist = self.get_changelist(request, Book, modeladmin) 141 142 # Make sure the correct queryset is returned 143 queryset = changelist.get_query_set(request) 144 if self.today.year == self.one_week_ago.year: 145 # In case one week ago is in the same year. 146 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 147 else: 148 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 149 150 # Make sure the correct choice is selected 151 filterspec = changelist.get_filters(request)[0][4] 152 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 153 choice = select_by(filterspec.choices(changelist), "display", "This year") 154 self.assertEqual(choice['selected'], True) 155 self.assertEqual(choice['query_string'], '?date_registered__year=%s' 156 % (self.today.year)) 157 158 request = self.request_factory.get('/', {'date_registered__gte': self.one_week_ago.strftime('%Y-%m-%d'), 159 'date_registered__lte': self.today.strftime('%Y-%m-%d')}) 160 changelist = self.get_changelist(request, Book, modeladmin) 161 162 # Make sure the correct queryset is returned 163 queryset = changelist.get_query_set(request) 164 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 165 166 # Make sure the correct choice is selected 167 filterspec = changelist.get_filters(request)[0][4] 168 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 169 choice = select_by(filterspec.choices(changelist), "display", "Past 7 days") 170 self.assertEqual(choice['selected'], True) 171 self.assertEqual(choice['query_string'], '?date_registered__gte=%s' 172 '&date_registered__lte=%s' 173 % (self.one_week_ago.strftime('%Y-%m-%d'), self.today.strftime('%Y-%m-%d'))) 174 175 def test_AllValuesFieldListFilter(self): 42 176 modeladmin = BookAdmin(Book, admin.site) 43 177 44 178 request = self.request_factory.get('/', {'year__isnull': 'True'}) 45 179 changelist = self.get_changelist(request, Book, modeladmin) 46 180 47 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 48 queryset = changelist.get_query_set() 181 # Make sure the correct queryset is returned 182 queryset = changelist.get_query_set(request) 183 self.assertEqual(list(queryset), [self.django_book]) 49 184 50 185 # Make sure the last choice is None and is selected 51 186 filterspec = changelist.get_filters(request)[0][0] 52 self.assertEqual(force_unicode(filterspec.title ()), u'year')187 self.assertEqual(force_unicode(filterspec.title), u'year') 53 188 choices = list(filterspec.choices(changelist)) 54 189 self.assertEqual(choices[-1]['selected'], True) 55 190 self.assertEqual(choices[-1]['query_string'], '?year__isnull=True') … … class FilterSpecsTests(TestCase): 59 194 60 195 # Make sure the correct choice is selected 61 196 filterspec = changelist.get_filters(request)[0][0] 62 self.assertEqual(force_unicode(filterspec.title ()), u'year')197 self.assertEqual(force_unicode(filterspec.title), u'year') 63 198 choices = list(filterspec.choices(changelist)) 64 199 self.assertEqual(choices[2]['selected'], True) 65 200 self.assertEqual(choices[2]['query_string'], '?year=2002') 66 201 67 def test_RelatedFi lterSpec_ForeignKey(self):202 def test_RelatedFieldListFilter_ForeignKey(self): 68 203 modeladmin = BookAdmin(Book, admin.site) 69 204 70 205 request = self.request_factory.get('/', {'author__isnull': 'True'}) 71 changelist = ChangeList(request, Book, modeladmin.list_display, modeladmin.list_display_links, 72 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 73 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 206 changelist = self.get_changelist(request, Book, modeladmin) 74 207 75 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 76 queryset = changelist.get_query_set() 208 # Make sure the correct queryset is returned 209 queryset = changelist.get_query_set(request) 210 self.assertEqual(list(queryset), [self.gipsy_book]) 77 211 78 212 # Make sure the last choice is None and is selected 79 213 filterspec = changelist.get_filters(request)[0][1] 80 self.assertEqual(force_unicode(filterspec.title ()), u'author')214 self.assertEqual(force_unicode(filterspec.title), u'author') 81 215 choices = list(filterspec.choices(changelist)) 82 216 self.assertEqual(choices[-1]['selected'], True) 83 217 self.assertEqual(choices[-1]['query_string'], '?author__isnull=True') … … class FilterSpecsTests(TestCase): 87 221 88 222 # Make sure the correct choice is selected 89 223 filterspec = changelist.get_filters(request)[0][1] 90 self.assertEqual(force_unicode(filterspec.title ()), u'author')224 self.assertEqual(force_unicode(filterspec.title), u'author') 91 225 # order of choices depends on User model, which has no order 92 226 choice = select_by(filterspec.choices(changelist), "display", "alfred") 93 227 self.assertEqual(choice['selected'], True) 94 228 self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk) 95 229 96 def test_RelatedFi lterSpec_ManyToMany(self):230 def test_RelatedFieldListFilter_ManyToMany(self): 97 231 modeladmin = BookAdmin(Book, admin.site) 98 232 99 233 request = self.request_factory.get('/', {'contributors__isnull': 'True'}) 100 234 changelist = self.get_changelist(request, Book, modeladmin) 101 235 102 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 103 queryset = changelist.get_query_set() 236 # Make sure the correct queryset is returned 237 queryset = changelist.get_query_set(request) 238 self.assertEqual(list(queryset), [self.django_book, self.bio_book, self.djangonaut_book]) 104 239 105 240 # Make sure the last choice is None and is selected 106 241 filterspec = changelist.get_filters(request)[0][2] 107 self.assertEqual(force_unicode(filterspec.title ()), u'user')242 self.assertEqual(force_unicode(filterspec.title), u'user') 108 243 choices = list(filterspec.choices(changelist)) 109 244 self.assertEqual(choices[-1]['selected'], True) 110 245 self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True') … … class FilterSpecsTests(TestCase): 114 249 115 250 # Make sure the correct choice is selected 116 251 filterspec = changelist.get_filters(request)[0][2] 117 self.assertEqual(force_unicode(filterspec.title ()), u'user')252 self.assertEqual(force_unicode(filterspec.title), u'user') 118 253 choice = select_by(filterspec.choices(changelist), "display", "bob") 119 254 self.assertEqual(choice['selected'], True) 120 255 self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk) 121 256 122 123 def test_RelatedFilterSpec_reverse_relationships(self): 257 def test_RelatedFieldListFilter_reverse_relationships(self): 124 258 modeladmin = CustomUserAdmin(User, admin.site) 125 259 126 260 # FK relationship ----- 127 261 request = self.request_factory.get('/', {'books_authored__isnull': 'True'}) 128 262 changelist = self.get_changelist(request, User, modeladmin) 129 263 130 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 131 queryset = changelist.get_query_set() 264 # Make sure the correct queryset is returned 265 queryset = changelist.get_query_set(request) 266 self.assertEqual(list(queryset), [self.lisa]) 132 267 133 268 # Make sure the last choice is None and is selected 134 269 filterspec = changelist.get_filters(request)[0][0] 135 self.assertEqual(force_unicode(filterspec.title ()), u'book')270 self.assertEqual(force_unicode(filterspec.title), u'book') 136 271 choices = list(filterspec.choices(changelist)) 137 272 self.assertEqual(choices[-1]['selected'], True) 138 273 self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True') … … class FilterSpecsTests(TestCase): 142 277 143 278 # Make sure the correct choice is selected 144 279 filterspec = changelist.get_filters(request)[0][0] 145 self.assertEqual(force_unicode(filterspec.title ()), u'book')280 self.assertEqual(force_unicode(filterspec.title), u'book') 146 281 choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title) 147 282 self.assertEqual(choice['selected'], True) 148 283 self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk) … … class FilterSpecsTests(TestCase): 151 286 request = self.request_factory.get('/', {'books_contributed__isnull': 'True'}) 152 287 changelist = self.get_changelist(request, User, modeladmin) 153 288 154 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 155 queryset = changelist.get_query_set() 289 # Make sure the correct queryset is returned 290 queryset = changelist.get_query_set(request) 291 self.assertEqual(list(queryset), [self.alfred]) 156 292 157 293 # Make sure the last choice is None and is selected 158 294 filterspec = changelist.get_filters(request)[0][1] 159 self.assertEqual(force_unicode(filterspec.title ()), u'book')295 self.assertEqual(force_unicode(filterspec.title), u'book') 160 296 choices = list(filterspec.choices(changelist)) 161 297 self.assertEqual(choices[-1]['selected'], True) 162 298 self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True') … … class FilterSpecsTests(TestCase): 166 302 167 303 # Make sure the correct choice is selected 168 304 filterspec = changelist.get_filters(request)[0][1] 169 self.assertEqual(force_unicode(filterspec.title ()), u'book')305 self.assertEqual(force_unicode(filterspec.title), u'book') 170 306 choice = select_by(filterspec.choices(changelist), "display", self.django_book.title) 171 307 self.assertEqual(choice['selected'], True) 172 308 self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) 173 309 174 def test_BooleanFilterSpec(self): 175 modeladmin = BoolTestAdmin(BoolTest, admin.site) 310 def test_BooleanFieldListFilter(self): 311 modeladmin = BookAdmin(Book, admin.site) 312 self.verify_BooleanFieldListFilter(modeladmin) 176 313 314 def test_BooleanFieldListFilter_Tuple(self): 315 modeladmin = BookAdmin(Book, admin.site) 316 self.verify_BooleanFieldListFilter(modeladmin) 317 318 def verify_BooleanFieldListFilter(self, modeladmin): 177 319 request = self.request_factory.get('/') 178 changelist = ChangeList(request, BoolTest, modeladmin.list_display, modeladmin.list_display_links, 179 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 180 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 320 changelist = self.get_changelist(request, Book, modeladmin) 181 321 182 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters183 queryset = changelist.get_query_set()322 request = self.request_factory.get('/', {'is_best_seller__exact': 0}) 323 changelist = self.get_changelist(request, Book, modeladmin) 184 324 185 # Make sure the last choice is None and is selected 186 filterspec = changelist.get_filters(request)[0][0] 187 self.assertEqual(force_unicode(filterspec.title()), u'completed') 188 choices = list(filterspec.choices(changelist)) 189 self.assertEqual(choices[-1]['selected'], False) 190 self.assertEqual(choices[-1]['query_string'], '?completed__exact=0') 325 # Make sure the correct queryset is returned 326 queryset = changelist.get_query_set(request) 327 self.assertEqual(list(queryset), [self.bio_book]) 191 328 192 request = self.request_factory.get('/', {'completed__exact': 1}) 193 changelist = self.get_changelist(request, BoolTest, modeladmin) 329 # Make sure the correct choice is selected 330 filterspec = changelist.get_filters(request)[0][3] 331 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 332 choice = select_by(filterspec.choices(changelist), "display", "No") 333 self.assertEqual(choice['selected'], True) 334 self.assertEqual(choice['query_string'], '?is_best_seller__exact=0') 335 336 request = self.request_factory.get('/', {'is_best_seller__exact': 1}) 337 changelist = self.get_changelist(request, Book, modeladmin) 338 339 # Make sure the correct queryset is returned 340 queryset = changelist.get_query_set(request) 341 self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) 194 342 195 343 # Make sure the correct choice is selected 196 filterspec = changelist.get_filters(request)[0][0] 197 self.assertEqual(force_unicode(filterspec.title()), u'completed') 198 # order of choices depends on User model, which has no order 344 filterspec = changelist.get_filters(request)[0][3] 345 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 199 346 choice = select_by(filterspec.choices(changelist), "display", "Yes") 200 347 self.assertEqual(choice['selected'], True) 201 self.assertEqual(choice['query_string'], '? completed__exact=1')348 self.assertEqual(choice['query_string'], '?is_best_seller__exact=1') 202 349 203 class CustomUserAdmin(UserAdmin): 204 list_filter = ('books_authored', 'books_contributed')350 request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'}) 351 changelist = self.get_changelist(request, Book, modeladmin) 205 352 206 class BookAdmin(admin.ModelAdmin): 207 list_filter = ('year', 'author', 'contributors') 208 order_by = '-id' 353 # Make sure the correct queryset is returned 354 queryset = changelist.get_query_set(request) 355 self.assertEqual(list(queryset), [self.django_book]) 356 357 # Make sure the correct choice is selected 358 filterspec = changelist.get_filters(request)[0][3] 359 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 360 choice = select_by(filterspec.choices(changelist), "display", "Unknown") 361 self.assertEqual(choice['selected'], True) 362 self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') 363 364 def test_SimpleListFilter(self): 365 modeladmin = DecadeFilterBookAdmin(Book, admin.site) 366 367 # Make sure that the first option is 'All' --------------------------- 368 369 request = self.request_factory.get('/', {}) 370 changelist = self.get_changelist(request, Book, modeladmin) 371 372 # Make sure the correct queryset is returned 373 queryset = changelist.get_query_set(request) 374 self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id'))) 375 376 # Make sure the correct choice is selected 377 filterspec = changelist.get_filters(request)[0][1] 378 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 379 choices = list(filterspec.choices(changelist)) 380 self.assertEqual(choices[0]['display'], u'All') 381 self.assertEqual(choices[0]['selected'], True) 382 self.assertEqual(choices[0]['query_string'], '?') 383 384 # Look for books in the 1990s ---------------------------------------- 385 386 request = self.request_factory.get('/', {'publication-decade': 'the 90s'}) 387 changelist = self.get_changelist(request, Book, modeladmin) 209 388 210 class BoolTestAdmin(admin.ModelAdmin): 211 list_filter = ('completed',) 389 # Make sure the correct queryset is returned 390 queryset = changelist.get_query_set(request) 391 self.assertEqual(list(queryset), [self.bio_book]) 392 393 # Make sure the correct choice is selected 394 filterspec = changelist.get_filters(request)[0][1] 395 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 396 choices = list(filterspec.choices(changelist)) 397 self.assertEqual(choices[1]['display'], u'the 1990\'s') 398 self.assertEqual(choices[1]['selected'], True) 399 self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s') 400 401 # Look for books in the 2000s ---------------------------------------- 402 403 request = self.request_factory.get('/', {'publication-decade': 'the 00s'}) 404 changelist = self.get_changelist(request, Book, modeladmin) 405 406 # Make sure the correct queryset is returned 407 queryset = changelist.get_query_set(request) 408 self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) 409 410 # Make sure the correct choice is selected 411 filterspec = changelist.get_filters(request)[0][1] 412 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 413 choices = list(filterspec.choices(changelist)) 414 self.assertEqual(choices[2]['display'], u'the 2000\'s') 415 self.assertEqual(choices[2]['selected'], True) 416 self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s') 417 418 # Combine multiple filters ------------------------------------------- 419 420 request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk}) 421 changelist = self.get_changelist(request, Book, modeladmin) 422 423 # Make sure the correct queryset is returned 424 queryset = changelist.get_query_set(request) 425 self.assertEqual(list(queryset), [self.djangonaut_book]) 426 427 # Make sure the correct choices are selected 428 filterspec = changelist.get_filters(request)[0][1] 429 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 430 choices = list(filterspec.choices(changelist)) 431 self.assertEqual(choices[2]['display'], u'the 2000\'s') 432 self.assertEqual(choices[2]['selected'], True) 433 self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) 434 435 filterspec = changelist.get_filters(request)[0][0] 436 self.assertEqual(force_unicode(filterspec.title), u'author') 437 choice = select_by(filterspec.choices(changelist), "display", "alfred") 438 self.assertEqual(choice['selected'], True) 439 self.assertEqual(choice['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) 440 441 def test_ListFilter_Without_Title(self): 442 """ 443 Any filter must define a title. 444 """ 445 modeladmin = DecadeFilterBookAdminWithoutTitle(Book, admin.site) 446 447 request = self.request_factory.get('/', {}) 448 449 with self.assertRaises(ImproperlyConfigured) as context_manager: 450 changelist = self.get_changelist(request, Book, modeladmin) 451 self.assertEqual(context_manager.exception.message, "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.") 452 453 def test_SimpleListFilter_Without_Parameter(self): 454 """ 455 Any SimpleListFilter must define a parameter_name. 456 """ 457 modeladmin = DecadeFilterBookAdminWithoutParameter(Book, admin.site) 458 459 request = self.request_factory.get('/', {}) 460 461 with self.assertRaises(ImproperlyConfigured) as context_manager: 462 changelist = self.get_changelist(request, Book, modeladmin) 463 self.assertEqual(context_manager.exception.message, "The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.") -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 97f6708..6e34ee1 100644
a b class Gadget(models.Model): 611 611 return self.name 612 612 613 613 class CustomChangeList(ChangeList): 614 def get_query_set(self ):614 def get_query_set(self, request): 615 615 return self.root_query_set.filter(pk=9999) # Does not exist 616 616 617 617 class GadgetAdmin(admin.ModelAdmin): -
tests/regressiontests/modeladmin/tests.py
diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py index a20e579..a1c99b9 100644
a b from datetime import date 2 2 3 3 from django import forms 4 4 from django.conf import settings 5 from django.contrib.admin.options import ModelAdmin, TabularInline, \6 HORIZONTAL, VERTICAL 5 from django.contrib.admin.options import (ModelAdmin, TabularInline, 6 HORIZONTAL, VERTICAL) 7 7 from django.contrib.admin.sites import AdminSite 8 8 from django.contrib.admin.validation import validate 9 9 from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect 10 from django.contrib.admin import (SimpleListFilter, 11 BooleanFieldListFilter) 10 12 from django.core.exceptions import ImproperlyConfigured 11 13 from django.forms.models import BaseModelFormSet 12 14 from django.forms.widgets import Select 13 15 from django.test import TestCase 14 16 from django.utils import unittest 15 17 16 from models import Band, Concert, ValidationTestModel, \17 ValidationTestInlineModel 18 from models import (Band, Concert, ValidationTestModel, 19 ValidationTestInlineModel) 18 20 19 21 20 22 # None of the following tests really depend on the content of the request, … … class ValidationTests(unittest.TestCase): 851 853 ValidationTestModel, 852 854 ) 853 855 856 class RandomClass(object): 857 pass 858 859 class ValidationTestModelAdmin(ModelAdmin): 860 list_filter = (RandomClass,) 861 862 self.assertRaisesRegexp( 863 ImproperlyConfigured, 864 "'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilterBase.", 865 validate, 866 ValidationTestModelAdmin, 867 ValidationTestModel, 868 ) 869 870 class ValidationTestModelAdmin(ModelAdmin): 871 list_filter = (('is_active', RandomClass),) 872 873 self.assertRaisesRegexp( 874 ImproperlyConfigured, 875 "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.", 876 validate, 877 ValidationTestModelAdmin, 878 ValidationTestModel, 879 ) 880 881 class AwesomeFilter(SimpleListFilter): 882 def get_title(self): 883 return 'awesomeness' 884 def get_choices(self, request): 885 return (('bit', 'A bit awesome'), ('very', 'Very awesome'), ) 886 def get_query_set(self, cl, qs): 887 return qs 888 889 class ValidationTestModelAdmin(ModelAdmin): 890 list_filter = (('is_active', AwesomeFilter),) 891 892 self.assertRaisesRegexp( 893 ImproperlyConfigured, 894 "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.", 895 validate, 896 ValidationTestModelAdmin, 897 ValidationTestModel, 898 ) 899 900 class ValidationTestModelAdmin(ModelAdmin): 901 list_filter = (BooleanFieldListFilter,) 902 903 self.assertRaisesRegexp( 904 ImproperlyConfigured, 905 "'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.", 906 validate, 907 ValidationTestModelAdmin, 908 ValidationTestModel, 909 ) 910 911 # Valid declarations below ----------- 912 854 913 class ValidationTestModelAdmin(ModelAdmin): 855 list_filter = ('is_active', )914 list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter)) 856 915 857 916 validate(ValidationTestModelAdmin, ValidationTestModel) 858 917