Ticket #5833: 5833.custom-filterspecs.6.diff
File 5833.custom-filterspecs.6.diff, 67.1 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..d725568 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 7 from django.contrib.admin.filterspecs import ListFilter, FieldListFilter 8 8 9 9 def autodiscover(): 10 10 """ -
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 965b32b..39288f0 100644
a b certain test -- e.g. being a DateField or ForeignKey. 7 7 """ 8 8 9 9 from django.db import models 10 from django.core.exceptions import ImproperlyConfigured 10 11 from django.utils.encoding import smart_unicode, iri_to_uri 11 12 from django.utils.translation import ugettext as _ 12 13 from django.utils.html import escape 13 14 from django.utils.safestring import mark_safe 14 from django.contrib.admin.util import get_model_from_relation, \ 15 reverse_field_path, get_limit_choices_to_from_path 15 from django.contrib.admin.util import (get_model_from_relation, 16 reverse_field_path, get_limit_choices_to_from_path) 17 from django.template.defaultfilters import slugify 16 18 import datetime 17 19 18 class FilterSpec(object): 19 filter_specs = [] 20 def __init__(self, f, request, params, model, model_admin, 21 field_path=None): 22 self.field = f 20 class ListFilterBase(object): 21 title = None # Human-readable title to appear in the right sidebar. 22 23 def __init__(self, request, params, model, model_admin): 24 if self.title is None: 25 raise ImproperlyConfigured("The list filter '%s' does not specify " 26 "a 'title'." % self.__class__.__name__) 23 27 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 28 29 def should_be_used(self): 30 """ 31 Returns True if the filter should be used, based on the parameters 32 given in the query string. 33 """ 34 for p in self.used_params(): 35 if p in self.params: 36 return True 37 return False 38 42 39 def has_output(self): 43 r eturn True40 raise NotImplementedError 44 41 45 def choices(self, cl):46 raise NotImplementedError ()42 def _choices(self, cl): 43 raise NotImplementedError 47 44 48 def title(self): 49 return self.field.verbose_name 45 def get_query_set(self, request, queryset): 46 raise NotImplementedError 47 48 def used_params(self): 49 """ 50 Return a list of parameters to consume from the change list 51 querystring. 52 """ 53 raise NotImplementedError 50 54 51 55 def output(self, cl): 52 56 t = [] 53 57 if self.has_output(): 54 t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title ()))58 t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title)) 55 59 56 for choice in self. choices(cl):60 for choice in self._choices(cl): 57 61 t.append(u'<li%s><a href="%s">%s</a></li>\n' % \ 58 62 ((choice['selected'] and ' class="selected"' or ''), 59 63 iri_to_uri(choice['query_string']), 60 64 choice['display'])) 61 65 t.append('</ul>\n\n') 62 66 return mark_safe("".join(t)) 67 68 69 70 71 72 class ListFilter(ListFilterBase): 73 """ 74 API to make the creation of a custom non-field list filter as simple 75 and easy as possible. 76 """ 77 78 # Parameter that should be used in the query string for that filter. 79 # Defaults to the title, slugified. 80 query_parameter_name = None 81 82 def __init__(self, request, params, model, model_admin): 83 super(ListFilter, self).__init__(request, params, model, model_admin) 84 if self.query_parameter_name is None: 85 self.query_parameter_name = slugify(self.title) 86 self.lookup_choices = self.get_choices(request) 87 88 def has_output(self): 89 return len(self.lookup_choices) > 0 90 91 def get_value(self): 92 """ 93 Returns the value given in the query string for this filter, 94 if any. Returns None otherwise. 95 """ 96 return self.params.get(self.query_parameter_name, None) 97 98 def get_choices(self, request): 99 """ 100 Must be overriden to return a list of tuples (value, verbose value) 101 """ 102 raise NotImplementedError 103 104 def used_params(self): 105 return [self.query_parameter_name] 106 107 def _choices(self, cl): 108 yield {'selected': self.get_value() is None, 109 'query_string': cl.get_query_string({}, [self.query_parameter_name]), 110 'display': _('All')} 111 for k, v in self.lookup_choices: 112 yield {'selected': self.get_value() == k, 113 'query_string': cl.get_query_string( 114 {self.query_parameter_name: k}, 115 []), 116 'display': v} 117 118 119 120 class FieldListFilter(ListFilterBase): 121 _field_list_filters = [] 122 _take_priority_index = 0 123 124 def __init__(self, field, request, params, model, model_admin, \ 125 field_path): 126 self.field = field 127 self.field_path = field_path 128 self.title = field_path 129 super(FieldListFilter, self).__init__(request, params, model, \ 130 model_admin) 63 131 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) 132 def has_output(self): 133 return True 134 135 def get_query_set(self, request, queryset): 136 for p in self.used_params(): 137 if p in self.params: 138 return queryset.filter(**{p: self.params[p]}) 139 140 @classmethod 141 def register(cls, test, list_filter_class, take_priority=False): 142 if take_priority: 143 # This is to allow overriding the default filters for certain types 144 # of fields with some custom filters. The first found in the list 145 # is used in priority. 146 cls._field_list_filters.insert(cls._take_priority_index, (test, list_filter_class)) 147 _take_priority_index += 1 148 else: 149 cls._field_list_filters.append((test, list_filter_class)) 150 151 @classmethod 152 def create(cls, field, request, params, model, model_admin, field_path): 153 for test, list_filter_class in cls._field_list_filters: 154 if test(field): 155 return list_filter_class(field, request, params, model, model_admin, 156 field_path=field_path) 69 157 70 other_model = get_model_from_relation(f) 71 if isinstance(f, (models.ManyToManyField, 158 159 class RelatedFieldListFilter(FieldListFilter): 160 def __init__(self, field, request, params, model, model_admin, 161 field_path): 162 super(RelatedFieldListFilter, self).__init__( 163 field, request, params, model, model_admin, field_path) 164 165 other_model = get_model_from_relation(field) 166 if isinstance(field, (models.ManyToManyField, 72 167 models.related.RelatedObject)): 73 168 # no direct field on this model, get name from other model 74 169 self.lookup_title = other_model._meta.verbose_name 75 170 else: 76 self.lookup_title = f .verbose_name # use field name171 self.lookup_title = field.verbose_name # use field name 77 172 rel_name = other_model._meta.pk.name 78 173 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 79 174 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) 80 175 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 81 176 self.lookup_val_isnull = request.GET.get( 82 177 self.lookup_kwarg_isnull, None) 83 self.lookup_choices = f.get_choices(include_blank=False) 178 self.lookup_choices = field.get_choices(include_blank=False) 179 self.title = self.lookup_title 84 180 85 181 def has_output(self): 86 182 if isinstance(self.field, models.related.RelatedObject) \ … … class RelatedFilterSpec(FilterSpec): 91 187 extra = 0 92 188 return len(self.lookup_choices) + extra > 1 93 189 94 def title(self):95 return self.lookup_title96 97 def choices(self, cl):190 def used_params(self): 191 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 192 193 def _choices(self, cl): 98 194 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 99 195 yield {'selected': self.lookup_val is None 100 196 and not self.lookup_val_isnull, … … class RelatedFilterSpec(FilterSpec): 117 213 [self.lookup_kwarg]), 118 214 'display': EMPTY_CHANGELIST_VALUE} 119 215 120 Fi lterSpec.register(lambda f: (216 FieldListFilter.register(lambda f: ( 121 217 hasattr(f, 'rel') and bool(f.rel) or 122 isinstance(f, models.related.RelatedObject)), RelatedFi lterSpec)218 isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter) 123 219 124 class BooleanField FilterSpec(FilterSpec):220 class BooleanFieldListFilter(FieldListFilter): 125 221 def __init__(self, f, request, params, model, model_admin, 126 field_path =None):127 super(BooleanField FilterSpec, self).__init__(f, request, params, model,222 field_path): 223 super(BooleanFieldListFilter, self).__init__(f, request, params, model, 128 224 model_admin, 129 field_path =field_path)225 field_path) 130 226 self.lookup_kwarg = '%s__exact' % self.field_path 131 227 self.lookup_kwarg2 = '%s__isnull' % self.field_path 132 228 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 133 229 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 134 230 135 def title(self):136 return self.field.verbose_name137 138 def choices(self, cl):231 def used_params(self): 232 return [self.lookup_kwarg, self.lookup_kwarg2] 233 234 def _choices(self, cl): 139 235 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 140 236 yield {'selected': self.lookup_val == v and not self.lookup_val2, 141 237 'query_string': cl.get_query_string( … … class BooleanFieldFilterSpec(FilterSpec): 149 245 [self.lookup_kwarg]), 150 246 'display': _('Unknown')} 151 247 152 Fi lterSpec.register(lambda f: isinstance(f, models.BooleanField)248 FieldListFilter.register(lambda f: isinstance(f, models.BooleanField) 153 249 or isinstance(f, models.NullBooleanField), 154 BooleanField FilterSpec)250 BooleanFieldListFilter) 155 251 156 class ChoicesFi lterSpec(FilterSpec):252 class ChoicesFieldListFilter(FieldListFilter): 157 253 def __init__(self, f, request, params, model, model_admin, 158 field_path =None):159 super(ChoicesFi lterSpec, self).__init__(f, request, params, model,254 field_path): 255 super(ChoicesFieldListFilter, self).__init__(f, request, params, model, 160 256 model_admin, 161 field_path =field_path)257 field_path) 162 258 self.lookup_kwarg = '%s__exact' % self.field_path 163 self.lookup_val = request.GET.get(self.lookup_kwarg , None)259 self.lookup_val = request.GET.get(self.lookup_kwarg) 164 260 165 def choices(self, cl): 261 def used_params(self): 262 return [self.lookup_kwarg] 263 264 def _choices(self, cl): 166 265 yield {'selected': self.lookup_val is None, 167 266 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 168 267 'display': _('All')} … … class ChoicesFilterSpec(FilterSpec): 172 271 {self.lookup_kwarg: k}), 173 272 'display': v} 174 273 175 Fi lterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)274 FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter) 176 275 177 class DateField FilterSpec(FilterSpec):276 class DateFieldListFilter(FieldListFilter): 178 277 def __init__(self, f, request, params, model, model_admin, 179 field_path =None):180 super(DateField FilterSpec, self).__init__(f, request, params, model,278 field_path): 279 super(DateFieldListFilter, self).__init__(f, request, params, model, 181 280 model_admin, 182 field_path =field_path)281 field_path) 183 282 184 283 self.field_generic = '%s__' % self.field_path 185 284 … … class DateFieldFilterSpec(FilterSpec): 192 291 and today.strftime('%Y-%m-%d 23:59:59') \ 193 292 or today.strftime('%Y-%m-%d') 194 293 294 self.lookup_kwarg_year = '%s__year' % self.field_path 295 self.lookup_kwarg_month = '%s__month' % self.field_path 296 self.lookup_kwarg_day = '%s__day' % self.field_path 297 self.lookup_kwarg_past_7_days_gte = '%s__gte' % self.field_path 298 self.lookup_kwarg_past_7_days_lte = '%s__lte' % self.field_path 299 195 300 self.links = ( 196 301 (_('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:302 (_('Today'), {self.lookup_kwarg_year: str(today.year), 303 self.lookup_kwarg_month: str(today.month), 304 self.lookup_kwarg_day: str(today.day)}), 305 (_('Past 7 days'), {self.lookup_kwarg_past_7_days_gte: 201 306 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)}) 307 self.lookup_kwarg_past_7_days_lte: 308 today_str}), 309 (_('This month'), {self.lookup_kwarg_year: str(today.year), 310 self.lookup_kwarg_month: str(today.month)}), 311 (_('This year'), {self.lookup_kwarg_year: str(today.year)}) 206 312 ) 207 313 208 def title(self): 209 return self.field.verbose_name 210 211 def choices(self, cl): 314 def used_params(self): 315 return [self.lookup_kwarg_year, self.lookup_kwarg_month, 316 self.lookup_kwarg_day, self.lookup_kwarg_past_7_days_gte, 317 self.lookup_kwarg_past_7_days_lte] 318 319 def get_query_set(self, request, queryset): 320 """ 321 Override the default behaviour since there can be multiple query 322 string parameters used for the same date filter (e.g. year + month). 323 """ 324 query_dict = {} 325 for p in self.used_params(): 326 if p in self.params: 327 query_dict[p] = self.params[p] 328 if len(query_dict): 329 return queryset.filter(**query_dict) 330 331 def _choices(self, cl): 212 332 for title, param_dict in self.links: 213 333 yield {'selected': self.date_params == param_dict, 214 334 'query_string': cl.get_query_string( … … class DateFieldFilterSpec(FilterSpec): 216 336 [self.field_generic]), 217 337 'display': title} 218 338 219 Fi lterSpec.register(lambda f: isinstance(f, models.DateField),220 DateField FilterSpec)339 FieldListFilter.register(lambda f: isinstance(f, models.DateField), 340 DateFieldListFilter) 221 341 222 342 223 343 # This should be registered last, because it's a last resort. For example, 224 # if a field is eligible to use the BooleanField FilterSpec, that'd be much225 # more appropriate, and the AllValuesFi lterSpecwon't get used for it.226 class AllValuesFi lterSpec(FilterSpec):344 # if a field is eligible to use the BooleanFieldListFilter, that'd be much 345 # more appropriate, and the AllValuesFieldListFilter won't get used for it. 346 class AllValuesFieldListFilter(FieldListFilter): 227 347 def __init__(self, f, request, params, model, model_admin, 228 field_path =None):229 super(AllValuesFi lterSpec, self).__init__(f, request, params, model,348 field_path): 349 super(AllValuesFieldListFilter, self).__init__(f, request, params, model, 230 350 model_admin, 231 field_path =field_path)351 field_path) 232 352 self.lookup_kwarg = self.field_path 233 353 self.lookup_kwarg_isnull = '%s__isnull' % self.field_path 234 354 self.lookup_val = request.GET.get(self.lookup_kwarg, None) … … class AllValuesFilterSpec(FilterSpec): 245 365 self.lookup_choices = \ 246 366 queryset.distinct().order_by(f.name).values_list(f.name, flat=True) 247 367 248 def title(self):249 return self.field.verbose_name250 251 def choices(self, cl):368 def used_params(self): 369 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 370 371 def _choices(self, cl): 252 372 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 253 373 yield {'selected': self.lookup_val is None 254 374 and self.lookup_val_isnull is None, … … class AllValuesFilterSpec(FilterSpec): 276 396 [self.lookup_kwarg]), 277 397 'display': EMPTY_CHANGELIST_VALUE} 278 398 279 Fi lterSpec.register(lambda f: True, AllValuesFilterSpec)399 FieldListFilter.register(lambda f: True, AllValuesFieldListFilter) -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index fbda8b7..cd91273 100644
a b class ModelAdmin(BaseModelAdmin): 1075 1075 if (actions and request.method == 'POST' and 1076 1076 'index' in request.POST and '_save' not in request.POST): 1077 1077 if selected: 1078 response = self.response_action(request, queryset=cl.get_query_set( ))1078 response = self.response_action(request, queryset=cl.get_query_set(request)) 1079 1079 if response: 1080 1080 return response 1081 1081 else: … … class ModelAdmin(BaseModelAdmin): 1091 1091 helpers.ACTION_CHECKBOX_NAME in request.POST and 1092 1092 'index' not 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: -
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..4919eda 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.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..40d14f6 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 ListFilter, 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 methods 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 ListFilter. 65 if not issubclass(item, ListFilter): 66 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 67 " which is not of type ListFilter." 68 % (cls.__name__, idx, item.__name__)) 69 else: 70 try: 71 # Check for option #2 (tuple) 72 field, list_filter_class = item 73 except (TypeError, ValueError): 74 # item is option #1 75 field = item 76 else: 77 # item is option #2 78 if not issubclass(list_filter_class, FieldListFilter): 79 raise ImproperlyConfigured("'%s.list_filter[%d][1]'" 80 " is '%s' which is not of type FieldListFilter." 81 % (cls.__name__, idx, list_filter_class.__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..d854703 100644
a b 1 from django.contrib.admin.filterspecs import FilterSpec1 from django.contrib.admin.filterspecs import ListFilter, FieldListFilter 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): 61 61 self.list_editable = list_editable 62 62 self.order_field, self.order_type = self.get_ordering() 63 63 self.query = request.GET.get(SEARCH_VAR, '') 64 self.query_set = self.get_query_set( )64 self.query_set = self.get_query_set(request) 65 65 self.get_results(request) 66 66 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 67 self.pk_attname = self.lookup_opts.pk.attname 69 68 70 69 def get_filters(self, request): 71 70 filter_specs = [] 72 71 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) 72 for item in self.list_filter: 73 if callable(item): 74 # This is simply a custom ListFilter class. 75 spec = item(request, self.cleaned_params, self.model, self.model_admin) 76 else: 77 field_path = None 78 try: 79 # This is custom FieldListFilter class for a given field. 80 field, field_list_filter_class = item 81 except (TypeError, ValueError): 82 # This is simply a field name, so use the default 83 # FieldListFilter class that has been registered for 84 # the type of the given field. 85 field, field_list_filter_class = item, FieldListFilter.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 = field_list_filter_class(field, request, self.cleaned_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): 165 178 if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): 166 179 order_type = params[ORDER_TYPE_VAR] 167 180 return order_field, order_type 168 169 def get_query_set(self): 181 182 def apply_list_filters(self, request, qs, lookup_params): 183 self.filter_specs, self.has_filters = self.get_filters(request) 184 for filter_spec in self.filter_specs: 185 if filter_spec.should_be_used(): 186 qs = filter_spec.get_query_set(request, qs) 187 for param in filter_spec.used_params(): 188 try: 189 del lookup_params[param] 190 except KeyError: 191 pass 192 return qs 193 194 def get_query_set(self, request): 170 195 use_distinct = False 171 196 172 197 qs = self.root_query_set … … class ChangeList(object): 187 212 field_name = key.split('__', 1)[0] 188 213 try: 189 214 f = self.lookup_opts.get_field_by_name(field_name)[0] 215 if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): 216 use_distinct = True 190 217 except models.FieldDoesNotExist: 191 raise IncorrectLookupParameters 192 if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): 193 use_distinct = True 218 # It might be for a non-field custom filter specs. 219 pass 194 220 195 221 # if key ends with __in, split parameter into separate values 196 222 if key.endswith('__in'): … … class ChangeList(object): 209 235 raise SuspiciousOperation( 210 236 "Filtering by %s not allowed" % key 211 237 ) 212 213 # Apply lookup parameters from the query string. 238 239 # Keep a copy of cleaned querystring values so it can be passed to 240 # the list filters. 241 self.cleaned_params = lookup_params.copy() 242 243 # Let every list filter modify the qs and params to its liking 244 qs = self.apply_list_filters(request, qs, lookup_params) 245 246 # Apply the remaining lookup parameters from the query string (i.e. 247 # those that haven't already been processed by the filters). 214 248 try: 215 249 qs = qs.filter(**lookup_params) 216 250 # Naked except! Because we don't have any other way of validating "params". -
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 415e1fe..47b987a 100644
a b subclass:: 512 512 .. note:: 513 513 514 514 ``list_editable`` interacts with a couple of other options in 515 particular ways; you should note the following rules: 515 particular ways; you should note the following rules:: 516 516 517 517 * Any field in ``list_editable`` must also be in ``list_display``. 518 518 You can't edit a field that's not displayed! … … 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``. 528 .. versionchanged:: Development version 532 529 533 This example, taken from the ``django.contrib.auth.models.User`` model, 534 shows how both ``list_display`` and ``list_filter`` work:: 530 Set ``list_filter`` to activate filters in the right sidebar of the change 531 list page of the admin. This should be a list of elements, where each 532 element should be of one of the following types:: 533 534 * a field name, where the specified field should be either a 535 ``BooleanField``, ``CharField``, ``DateField``, ``DateTimeField``, 536 ``IntegerField``, ``ForeignKey`` or ``ManyToManyField``. 537 538 .. versionadded:: 1.3 539 540 Field names in ``list_filter`` can also span relations 541 using the ``__`` lookup, for example:: 542 543 class UserAdminWithLookup(UserAdmin): 544 list_filter = ('groups__name') 545 546 * a class inheriting from :mod:`django.contrib.admin.ListFilter`, 547 where you need to provide a few attributes and override a few 548 methods:: 549 550 from django.contrib.admin import ListFilter 551 from django.db.models import Q 552 553 class DecadeBornListFilter(ListFilter): 554 # Human-readable title which will be displayed in the 555 # right sidebar just above the filter options. 556 title = u'decade born' 557 558 # This is the code name for the filter that will be used in 559 # the url query. Providing this attribute is optional. If it is 560 # not provided then a slugified version of the title will 561 # automatically be used instead (that is, 'decade-born' in this example). 562 query_parameter_name = u'decade' 563 564 def get_choices(self, request): 565 """ 566 Returns a list of tuples. The first element in each tuple 567 is the coded value for the option that will appear in the 568 url query. The second element is the human-readable name 569 for the option that will appear in the right sidebar. You 570 may specify as many choices as you like, and you may even 571 vary the list of choices depending on the HttpRequest 572 object provided as argument to this method. 573 """ 574 return ( 575 (u'80s', u'in the eighties'), 576 (u'other', u'other'), 577 ) 578 579 def get_query_set(self, request, queryset): 580 """ 581 Returns the filtered queryset based on the value provided 582 in the query string and retrievable via `get_value()`. 583 This method is only called when necessary, that is, if 584 the corresponding parameter is present in the query string. 585 The HttpRequest object is also provided for your convenience 586 in case you need to modify the list of results for each request. 587 """ 588 # First, retrieve the requested value (either '80s' or 'other'). 589 decade = self.get_value() 590 # Then decide how to filter the queryset based on that value. 591 if decade == u'80s': 592 return queryset.filter(birthday__year__gte=1980, 593 birthday__year__lte=1989) 594 if decade == u'other': 595 return queryset.filter(Q(year__lte=1979) | 596 Q(year__gte=1990) 597 598 class PersonAdmin(ModelAdmin): 599 list_filter = (DecadeBornListFilter,) 600 601 * a tuple, where the first element is a field name and the second 602 element is a class inheriting from 603 :mod:`django.contrib.admin.FieldListFilter`. Note that the 604 `FieldListFilter` API is currently considered internal and prone to 605 refactoring. 606 607 Finally, the following example, taken from the ``django.contrib.auth.models.User`` 608 model, shows how both ``list_display`` and ``list_filter`` work:: 535 609 536 610 class UserAdmin(admin.ModelAdmin): 537 611 list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') … … subclass:: 543 617 544 618 (This example also has ``search_fields`` defined. See below.) 545 619 546 .. versionadded:: 1.3547 548 Fields in ``list_filter`` can also span relations using the ``__`` lookup::549 550 class UserAdminWithLookup(UserAdmin):551 list_filter = ('groups__name')552 553 620 .. attribute:: ModelAdmin.list_per_page 554 621 555 622 Set ``list_per_page`` to control how many items appear on each paginated -
tests/regressiontests/admin_filterspecs/models.py
diff --git a/tests/regressiontests/admin_filterspecs/models.py b/tests/regressiontests/admin_filterspecs/models.py index 5b284c7..db9912f 100644
a b class Book(models.Model): 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 9 is_best_seller = models.NullBooleanField(default=0) 10 date_registered = models.DateField(null=True) 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..e9c68dd 100644
a b 1 import datetime 2 import calendar 3 1 4 from django.contrib.auth.admin import UserAdmin 2 5 from django.test import TestCase 3 6 from django.test.client import RequestFactory … … from django.contrib.auth.models import User 5 8 from django.contrib import admin 6 9 from django.contrib.admin.views.main import ChangeList 7 10 from django.utils.encoding import force_unicode 11 from django.contrib.admin.filterspecs import (ListFilter, 12 BooleanFieldListFilter, FieldListFilter) 8 13 9 from models import Book , BoolTest14 from models import Book 10 15 11 16 def select_by(dictlist, key, value): 12 17 return [x for x in dictlist if x[key] == value][0] 13 18 14 class FilterSpecsTests(TestCase): 19 20 21 class DecadeListFilterBase(ListFilter): 22 23 def get_choices(self, request): 24 return ( 25 (u'the 90s', u'the 1990\'s'), 26 (u'the 00s', u'the 2000\'s'), 27 (u'other', u'other decades'), 28 ) 29 30 def get_query_set(self, request, queryset): 31 decade = self.get_value() 32 if decade == u'the 90s': 33 return queryset.filter(year__gte=1990, year__lte=1999) 34 if decade == u'the 00s': 35 return queryset.filter(year__gte=2000, year__lte=2009) 36 37 class DecadeListFilterWithTitle(DecadeListFilterBase): 38 title = u'publication decade' 39 40 class DecadeListFilterWithParamName(DecadeListFilterBase): 41 title = u'another publication decade' 42 query_parameter_name = u'blah' 43 44 class ListFiltersTests(TestCase): 15 45 16 46 def setUp(self): 47 self.today = datetime.date.today() 48 self.one_week_ago = self.today - datetime.timedelta(days=7) 49 50 self.request_factory = RequestFactory() 51 17 52 # Users 18 53 self.alfred = User.objects.create_user('alfred', 'alfred@example.com') 19 54 self.bob = User.objects.create_user('bob', 'bob@example.com') 20 lisa = User.objects.create_user('lisa', 'lisa@example.com') 21 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() 28 29 # BoolTests 30 self.trueTest = BoolTest.objects.create(completed=True) 31 self.falseTest = BoolTest.objects.create(completed=False) 32 33 self.request_factory = RequestFactory() 55 self.lisa = User.objects.create_user('lisa', 'lisa@example.com') 34 56 57 # Books 58 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) 59 self.bio_book = Book.objects.create(title='Django: a biography', year=1999, author=self.alfred, is_best_seller=False) 60 self.django_book = Book.objects.create(title='The Django Book', year=None, author=self.bob, is_best_seller=None, date_registered=self.today) 61 self.gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002, is_best_seller=True, date_registered=self.one_week_ago) 62 self.gipsy_book.contributors = [self.bob, self.lisa] 63 self.gipsy_book.save() 35 64 36 65 def get_changelist(self, request, model, modeladmin): 37 66 return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links, 38 67 modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, 39 68 modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_editable, modeladmin) 40 69 41 def test_AllValuesFilterSpec(self): 70 def test_DateFieldListFilter(self): 71 modeladmin = BookAdmin(Book, admin.site) 72 73 request = self.request_factory.get('/') 74 changelist = self.get_changelist(request, Book, modeladmin) 75 76 request = self.request_factory.get('/', {'date_registered__year': self.today.year, 77 'date_registered__month': self.today.month, 78 'date_registered__day': self.today.day}) 79 changelist = self.get_changelist(request, Book, modeladmin) 80 81 # Make sure the correct queryset is returned 82 queryset = changelist.get_query_set(request) 83 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 84 85 # Make sure the correct choice is selected 86 filterspec = changelist.get_filters(request)[0][4] 87 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 88 choice = select_by(filterspec._choices(changelist), "display", "Today") 89 self.assertEqual(choice['selected'], True) 90 self.assertEqual(choice['query_string'], '?date_registered__day=%s' 91 '&date_registered__month=%s' 92 '&date_registered__year=%s' 93 % (self.today.day, self.today.month, self.today.year)) 94 95 request = self.request_factory.get('/', {'date_registered__year': self.today.year, 96 'date_registered__month': self.today.month}) 97 changelist = self.get_changelist(request, Book, modeladmin) 98 99 # Make sure the correct queryset is returned 100 queryset = changelist.get_query_set(request) 101 if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month): 102 # In case one week ago is in the same month. 103 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 104 else: 105 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 106 107 # Make sure the correct choice is selected 108 filterspec = changelist.get_filters(request)[0][4] 109 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 110 choice = select_by(filterspec._choices(changelist), "display", "This month") 111 self.assertEqual(choice['selected'], True) 112 self.assertEqual(choice['query_string'], '?date_registered__month=%s' 113 '&date_registered__year=%s' 114 % (self.today.month, self.today.year)) 115 116 request = self.request_factory.get('/', {'date_registered__year': self.today.year}) 117 changelist = self.get_changelist(request, Book, modeladmin) 118 119 # Make sure the correct queryset is returned 120 queryset = changelist.get_query_set(request) 121 if self.today.year == self.one_week_ago.year: 122 # In case one week ago is in the same year. 123 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 124 else: 125 self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) 126 127 # Make sure the correct choice is selected 128 filterspec = changelist.get_filters(request)[0][4] 129 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 130 choice = select_by(filterspec._choices(changelist), "display", "This year") 131 self.assertEqual(choice['selected'], True) 132 self.assertEqual(choice['query_string'], '?date_registered__year=%s' 133 % (self.today.year)) 134 135 request = self.request_factory.get('/', {'date_registered__gte': self.one_week_ago.strftime('%Y-%m-%d'), 136 'date_registered__lte': self.today.strftime('%Y-%m-%d')}) 137 changelist = self.get_changelist(request, Book, modeladmin) 138 139 # Make sure the correct queryset is returned 140 queryset = changelist.get_query_set(request) 141 self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) 142 143 # Make sure the correct choice is selected 144 filterspec = changelist.get_filters(request)[0][4] 145 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 146 choice = select_by(filterspec._choices(changelist), "display", "Past 7 days") 147 self.assertEqual(choice['selected'], True) 148 self.assertEqual(choice['query_string'], '?date_registered__gte=%s' 149 '&date_registered__lte=%s' 150 % (self.one_week_ago.strftime('%Y-%m-%d'), self.today.strftime('%Y-%m-%d'))) 151 152 def test_AllValuesFieldListFilter(self): 42 153 modeladmin = BookAdmin(Book, admin.site) 43 154 44 155 request = self.request_factory.get('/', {'year__isnull': 'True'}) 45 156 changelist = self.get_changelist(request, Book, modeladmin) 46 157 47 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 48 queryset = changelist.get_query_set() 158 # Make sure the correct queryset is returned 159 queryset = changelist.get_query_set(request) 160 self.assertEqual(list(queryset), [self.django_book]) 49 161 50 162 # Make sure the last choice is None and is selected 51 163 filterspec = changelist.get_filters(request)[0][0] 52 self.assertEqual(force_unicode(filterspec.title ()), u'year')53 choices = list(filterspec. choices(changelist))164 self.assertEqual(force_unicode(filterspec.title), u'year') 165 choices = list(filterspec._choices(changelist)) 54 166 self.assertEqual(choices[-1]['selected'], True) 55 167 self.assertEqual(choices[-1]['query_string'], '?year__isnull=True') 56 168 … … class FilterSpecsTests(TestCase): 59 171 60 172 # Make sure the correct choice is selected 61 173 filterspec = changelist.get_filters(request)[0][0] 62 self.assertEqual(force_unicode(filterspec.title ()), u'year')63 choices = list(filterspec. choices(changelist))174 self.assertEqual(force_unicode(filterspec.title), u'year') 175 choices = list(filterspec._choices(changelist)) 64 176 self.assertEqual(choices[2]['selected'], True) 65 177 self.assertEqual(choices[2]['query_string'], '?year=2002') 66 178 67 def test_RelatedFi lterSpec_ForeignKey(self):179 def test_RelatedFieldListFilter_ForeignKey(self): 68 180 modeladmin = BookAdmin(Book, admin.site) 69 181 70 182 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) 183 changelist = self.get_changelist(request, Book, modeladmin) 74 184 75 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 76 queryset = changelist.get_query_set() 185 # Make sure the correct queryset is returned 186 queryset = changelist.get_query_set(request) 187 self.assertEqual(list(queryset), [self.gipsy_book]) 77 188 78 189 # Make sure the last choice is None and is selected 79 190 filterspec = changelist.get_filters(request)[0][1] 80 self.assertEqual(force_unicode(filterspec.title ()), u'author')81 choices = list(filterspec. choices(changelist))191 self.assertEqual(force_unicode(filterspec.title), u'author') 192 choices = list(filterspec._choices(changelist)) 82 193 self.assertEqual(choices[-1]['selected'], True) 83 194 self.assertEqual(choices[-1]['query_string'], '?author__isnull=True') 84 195 … … class FilterSpecsTests(TestCase): 87 198 88 199 # Make sure the correct choice is selected 89 200 filterspec = changelist.get_filters(request)[0][1] 90 self.assertEqual(force_unicode(filterspec.title ()), u'author')201 self.assertEqual(force_unicode(filterspec.title), u'author') 91 202 # order of choices depends on User model, which has no order 92 choice = select_by(filterspec. choices(changelist), "display", "alfred")203 choice = select_by(filterspec._choices(changelist), "display", "alfred") 93 204 self.assertEqual(choice['selected'], True) 94 205 self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk) 95 206 96 def test_RelatedFi lterSpec_ManyToMany(self):207 def test_RelatedFieldListFilter_ManyToMany(self): 97 208 modeladmin = BookAdmin(Book, admin.site) 98 209 99 210 request = self.request_factory.get('/', {'contributors__isnull': 'True'}) 100 211 changelist = self.get_changelist(request, Book, modeladmin) 101 212 102 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 103 queryset = changelist.get_query_set() 213 # Make sure the correct queryset is returned 214 queryset = changelist.get_query_set(request) 215 self.assertEqual(list(queryset), [self.django_book, self.bio_book, self.djangonaut_book]) 104 216 105 217 # Make sure the last choice is None and is selected 106 218 filterspec = changelist.get_filters(request)[0][2] 107 self.assertEqual(force_unicode(filterspec.title ()), u'user')108 choices = list(filterspec. choices(changelist))219 self.assertEqual(force_unicode(filterspec.title), u'user') 220 choices = list(filterspec._choices(changelist)) 109 221 self.assertEqual(choices[-1]['selected'], True) 110 222 self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True') 111 223 … … class FilterSpecsTests(TestCase): 114 226 115 227 # Make sure the correct choice is selected 116 228 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")229 self.assertEqual(force_unicode(filterspec.title), u'user') 230 choice = select_by(filterspec._choices(changelist), "display", "bob") 119 231 self.assertEqual(choice['selected'], True) 120 232 self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk) 121 233 122 123 def test_RelatedFilterSpec_reverse_relationships(self): 234 def test_RelatedFieldListFilter_reverse_relationships(self): 124 235 modeladmin = CustomUserAdmin(User, admin.site) 125 236 126 237 # FK relationship ----- 127 238 request = self.request_factory.get('/', {'books_authored__isnull': 'True'}) 128 239 changelist = self.get_changelist(request, User, modeladmin) 129 240 130 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 131 queryset = changelist.get_query_set() 241 # Make sure the correct queryset is returned 242 queryset = changelist.get_query_set(request) 243 self.assertEqual(list(queryset), [self.lisa]) 132 244 133 245 # Make sure the last choice is None and is selected 134 246 filterspec = changelist.get_filters(request)[0][0] 135 self.assertEqual(force_unicode(filterspec.title ()), u'book')136 choices = list(filterspec. choices(changelist))247 self.assertEqual(force_unicode(filterspec.title), u'book') 248 choices = list(filterspec._choices(changelist)) 137 249 self.assertEqual(choices[-1]['selected'], True) 138 250 self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True') 139 251 … … class FilterSpecsTests(TestCase): 142 254 143 255 # Make sure the correct choice is selected 144 256 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)257 self.assertEqual(force_unicode(filterspec.title), u'book') 258 choice = select_by(filterspec._choices(changelist), "display", self.bio_book.title) 147 259 self.assertEqual(choice['selected'], True) 148 260 self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk) 149 261 … … class FilterSpecsTests(TestCase): 151 263 request = self.request_factory.get('/', {'books_contributed__isnull': 'True'}) 152 264 changelist = self.get_changelist(request, User, modeladmin) 153 265 154 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 155 queryset = changelist.get_query_set() 266 # Make sure the correct queryset is returned 267 queryset = changelist.get_query_set(request) 268 self.assertEqual(list(queryset), [self.alfred]) 156 269 157 270 # Make sure the last choice is None and is selected 158 271 filterspec = changelist.get_filters(request)[0][1] 159 self.assertEqual(force_unicode(filterspec.title ()), u'book')160 choices = list(filterspec. choices(changelist))272 self.assertEqual(force_unicode(filterspec.title), u'book') 273 choices = list(filterspec._choices(changelist)) 161 274 self.assertEqual(choices[-1]['selected'], True) 162 275 self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True') 163 276 … … class FilterSpecsTests(TestCase): 166 279 167 280 # Make sure the correct choice is selected 168 281 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)282 self.assertEqual(force_unicode(filterspec.title), u'book') 283 choice = select_by(filterspec._choices(changelist), "display", self.django_book.title) 171 284 self.assertEqual(choice['selected'], True) 172 285 self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) 173 286 174 def test_BooleanFilterSpec(self): 175 modeladmin = BoolTestAdmin(BoolTest, admin.site) 176 287 def test_BooleanFieldListFilter(self): 288 modeladmin = BookAdmin(Book, admin.site) 289 self.verify_BooleanFieldListFilter(modeladmin) 290 291 def test_BooleanFieldListFilter_Tuple(self): 292 modeladmin = BookAdmin(Book, admin.site) 293 self.verify_BooleanFieldListFilter(modeladmin) 294 295 def verify_BooleanFieldListFilter(self, modeladmin): 177 296 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) 297 changelist = self.get_changelist(request, Book, modeladmin) 181 298 182 # Make sure changelist.get_query_set() does not raise IncorrectLookupParameters 183 queryset = changelist.get_query_set() 299 request = self.request_factory.get('/', {'is_best_seller__exact': 0}) 300 changelist = self.get_changelist(request, Book, modeladmin) 301 302 # Make sure the correct queryset is returned 303 queryset = changelist.get_query_set(request) 304 self.assertEqual(list(queryset), [self.bio_book]) 184 305 185 # Make sure the last choice is None andis selected186 filterspec = changelist.get_filters(request)[0][ 0]187 self.assertEqual(force_unicode(filterspec.title ()), u'completed')188 choice s = list(filterspec.choices(changelist))189 self.assertEqual(choice s[-1]['selected'], False)190 self.assertEqual(choice s[-1]['query_string'], '?completed__exact=0')306 # Make sure the correct choice is selected 307 filterspec = changelist.get_filters(request)[0][3] 308 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 309 choice = select_by(filterspec._choices(changelist), "display", "No") 310 self.assertEqual(choice['selected'], True) 311 self.assertEqual(choice['query_string'], '?is_best_seller__exact=0') 191 312 192 request = self.request_factory.get('/', {' completed__exact': 1})193 changelist = self.get_changelist(request, Boo lTest, modeladmin)313 request = self.request_factory.get('/', {'is_best_seller__exact': 1}) 314 changelist = self.get_changelist(request, Book, modeladmin) 194 315 316 # Make sure the correct queryset is returned 317 queryset = changelist.get_query_set(request) 318 self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) 319 195 320 # 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 199 choice = select_by(filterspec.choices(changelist), "display", "Yes") 321 filterspec = changelist.get_filters(request)[0][3] 322 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 323 choice = select_by(filterspec._choices(changelist), "display", "Yes") 324 self.assertEqual(choice['selected'], True) 325 self.assertEqual(choice['query_string'], '?is_best_seller__exact=1') 326 327 request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'}) 328 changelist = self.get_changelist(request, Book, modeladmin) 329 330 # Make sure the correct queryset is returned 331 queryset = changelist.get_query_set(request) 332 self.assertEqual(list(queryset), [self.django_book]) 333 334 # Make sure the correct choice is selected 335 filterspec = changelist.get_filters(request)[0][3] 336 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 337 choice = select_by(filterspec._choices(changelist), "display", "Unknown") 200 338 self.assertEqual(choice['selected'], True) 201 self.assertEqual(choice['query_string'], '?completed__exact=1') 339 self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') 340 341 def test_ListFilter(self): 342 modeladmin = DecadeFilterBookAdmin(Book, admin.site) 343 344 # Make sure that the first option is 'All' --------------------------- 345 346 request = self.request_factory.get('/', {}) 347 changelist = self.get_changelist(request, Book, modeladmin) 348 349 # Make sure the correct queryset is returned 350 queryset = changelist.get_query_set(request) 351 self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id'))) 352 353 # Make sure the correct choice is selected 354 filterspec = changelist.get_filters(request)[0][1] 355 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 356 choices = list(filterspec._choices(changelist)) 357 self.assertEqual(choices[0]['display'], u'All') 358 self.assertEqual(choices[0]['selected'], True) 359 self.assertEqual(choices[0]['query_string'], '?') 360 361 # Make sure that one can override the query parameter name ----------- 362 363 request = self.request_factory.get('/', {'blah': 'the 90s'}) 364 changelist = self.get_changelist(request, Book, modeladmin) 365 366 # Make sure the correct choice is selected 367 filterspec = changelist.get_filters(request)[0][2] 368 self.assertEqual(force_unicode(filterspec.title), u'another publication decade') 369 choices = list(filterspec._choices(changelist)) 370 self.assertEqual(choices[1]['display'], u'the 1990\'s') 371 self.assertEqual(choices[1]['selected'], True) 372 self.assertEqual(choices[1]['query_string'], '?blah=the+90s') 373 374 # Look for books in the 1990s ---------------------------------------- 375 376 request = self.request_factory.get('/', {'publication-decade': 'the 90s'}) 377 changelist = self.get_changelist(request, Book, modeladmin) 202 378 379 # Make sure the correct queryset is returned 380 queryset = changelist.get_query_set(request) 381 self.assertEqual(list(queryset), [self.bio_book]) 382 383 # Make sure the correct choice is selected 384 filterspec = changelist.get_filters(request)[0][1] 385 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 386 choices = list(filterspec._choices(changelist)) 387 self.assertEqual(choices[1]['display'], u'the 1990\'s') 388 self.assertEqual(choices[1]['selected'], True) 389 self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s') 390 391 # Look for books in the 2000s ---------------------------------------- 392 393 request = self.request_factory.get('/', {'publication-decade': 'the 00s'}) 394 changelist = self.get_changelist(request, Book, modeladmin) 395 396 # Make sure the correct queryset is returned 397 queryset = changelist.get_query_set(request) 398 self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) 399 400 # Make sure the correct choice is selected 401 filterspec = changelist.get_filters(request)[0][1] 402 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 403 choices = list(filterspec._choices(changelist)) 404 self.assertEqual(choices[2]['display'], u'the 2000\'s') 405 self.assertEqual(choices[2]['selected'], True) 406 self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s') 407 408 # Combine multiple filters ------------------------------------------- 409 410 request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk}) 411 changelist = self.get_changelist(request, Book, modeladmin) 412 413 # Make sure the correct queryset is returned 414 queryset = changelist.get_query_set(request) 415 self.assertEqual(list(queryset), [self.djangonaut_book]) 416 417 # Make sure the correct choices are selected 418 filterspec = changelist.get_filters(request)[0][1] 419 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 420 choices = list(filterspec._choices(changelist)) 421 self.assertEqual(choices[2]['display'], u'the 2000\'s') 422 self.assertEqual(choices[2]['selected'], True) 423 self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) 424 425 filterspec = changelist.get_filters(request)[0][0] 426 self.assertEqual(force_unicode(filterspec.title), u'author') 427 choice = select_by(filterspec._choices(changelist), "display", "alfred") 428 self.assertEqual(choice['selected'], True) 429 self.assertEqual(choice['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) 430 203 431 class CustomUserAdmin(UserAdmin): 204 432 list_filter = ('books_authored', 'books_contributed') 205 433 206 434 class BookAdmin(admin.ModelAdmin): 207 list_filter = ('year', 'author', 'contributors') 435 list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered') 436 order_by = '-id' 437 438 class DecadeFilterBookAdmin(admin.ModelAdmin): 439 list_filter = ('author', DecadeListFilterWithTitle, DecadeListFilterWithParamName) 208 440 order_by = '-id' 209 210 class BoolTestAdmin(admin.ModelAdmin):211 list_filter = ('completed',) -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index b65f8a4..cfa4123 100644
a b class Gadget(models.Model): 566 566 return self.name 567 567 568 568 class CustomChangeList(ChangeList): 569 def get_query_set(self ):569 def get_query_set(self, request): 570 570 return self.root_query_set.filter(pk=9999) # Does not exist 571 571 572 572 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..c14f743 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.filterspecs import (ListFilter, 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): 850 852 ValidationTestModelAdmin, 851 853 ValidationTestModel, 852 854 ) 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 of type ListFilter.", 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(ListFilter): 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),) 853 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 # Valid declarations below ----------- 901 854 902 class ValidationTestModelAdmin(ModelAdmin): 855 list_filter = ('is_active', )903 list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter)) 856 904 857 905 validate(ValidationTestModelAdmin, ValidationTestModel) 858 906