Ticket #5833: 5833.custom-filterspecs.7.diff
File 5833.custom-filterspecs.7.diff, 69.9 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..b22a5a2 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 (ListFilterBase, 8 SingleQueryParameterListFilter, FieldListFilter, BooleanFieldListFilter, 9 RelatedFieldListFilter, ChoicesFieldListFilter, DateFieldListFilter, 10 AllValuesFieldListFilter) 11 8 12 9 13 def autodiscover(): 10 14 """ -
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 965b32b..3d228b9 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 28 self.request = request 29 30 def has_output(self): 31 """ 32 Returns True if some choices would be output for the filter. 33 """ 34 raise NotImplementedError 35 36 def get_output_choices(self, cl): 37 """ 38 Returns choices ready to be output in the template. 39 """ 40 raise NotImplementedError 41 42 def get_query_set(self, queryset): 43 """ 44 Returns the filtered queryset. 45 """ 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 54 55 56 57 class SingleQueryParameterListFilter(ListFilterBase): 58 # The parameter that should be used in the query string for that filter. 59 # Defaults to the title, slugified. 60 query_parameter = None 61 62 def __init__(self, request, params, model, model_admin): 63 super(SingleQueryParameterListFilter, self).__init__(request, params, model, model_admin) 64 if self.query_parameter is None: 65 self.query_parameter = slugify(self.title) 66 self.lookup_choices = self.get_choices() 67 68 def has_output(self): 69 return len(self.lookup_choices) > 0 70 71 def get_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.query_parameter, None) 77 78 def get_choices(self): 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.query_parameter] 86 87 def get_output_choices(self, cl): 88 yield {'selected': self.get_value() is None, 89 'query_string': cl.get_query_string({}, [self.query_parameter]), 90 'display': _('All')} 91 for k, v in self.lookup_choices: 92 yield {'selected': self.get_value() == k, 93 'query_string': cl.get_query_string( 94 {self.query_parameter: k}, 95 []), 96 'display': v} 97 98 99 100 class FieldListFilter(ListFilterBase): 101 _field_list_filters = [] 102 _take_priority_index = 0 103 104 def __init__(self, field, request, params, model, model_admin, \ 105 field_path): 106 self.field = field 24 107 self.field_path = field_path 25 if field_path is None: 26 if isinstance(f, models.related.RelatedObject): 27 self.field_path = f.var_name 28 else: 29 self.field_path = f.name 30 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) 108 self.title = field_path 109 super(FieldListFilter, self).__init__(request, params, model, \ 110 model_admin) 41 111 42 112 def has_output(self): 43 113 return True 114 115 def get_query_set(self, queryset): 116 for p in self.used_params(): 117 if p in self.params: 118 return queryset.filter(**{p: self.params[p]}) 119 120 @classmethod 121 def register(cls, test, list_filter_class, take_priority=False): 122 if take_priority: 123 # This is to allow overriding the default filters for certain types 124 # of fields with some custom filters. The first found in the list 125 # is used in priority. 126 cls._field_list_filters.insert(cls._take_priority_index, (test, list_filter_class)) 127 _take_priority_index += 1 128 else: 129 cls._field_list_filters.append((test, list_filter_class)) 130 131 @classmethod 132 def create(cls, field, request, params, model, model_admin, field_path): 133 for test, list_filter_class in cls._field_list_filters: 134 if test(field): 135 return list_filter_class(field, request, params, model, model_admin, 136 field_path=field_path) 44 137 45 def choices(self, cl): 46 raise NotImplementedError() 47 48 def title(self): 49 return self.field.verbose_name 50 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)) 138 139 class RelatedFieldListFilter(FieldListFilter): 140 def __init__(self, field, request, params, model, model_admin, 141 field_path): 142 super(RelatedFieldListFilter, self).__init__( 143 field, request, params, model, model_admin, field_path) 63 144 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, 145 other_model = get_model_from_relation(field) 146 if isinstance(field, (models.ManyToManyField, 72 147 models.related.RelatedObject)): 73 148 # no direct field on this model, get name from other model 74 149 self.lookup_title = other_model._meta.verbose_name 75 150 else: 76 self.lookup_title = f .verbose_name # use field name151 self.lookup_title = field.verbose_name # use field name 77 152 rel_name = other_model._meta.pk.name 78 153 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 79 154 self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) 80 155 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 81 156 self.lookup_val_isnull = request.GET.get( 82 157 self.lookup_kwarg_isnull, None) 83 self.lookup_choices = f.get_choices(include_blank=False) 158 self.lookup_choices = field.get_choices(include_blank=False) 159 self.title = self.lookup_title 84 160 85 161 def has_output(self): 86 162 if isinstance(self.field, models.related.RelatedObject) \ … … class RelatedFilterSpec(FilterSpec): 91 167 extra = 0 92 168 return len(self.lookup_choices) + extra > 1 93 169 94 def title(self):95 return self.lookup_title96 97 def choices(self, cl):170 def used_params(self): 171 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 172 173 def get_output_choices(self, cl): 98 174 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 99 175 yield {'selected': self.lookup_val is None 100 176 and not self.lookup_val_isnull, … … class RelatedFilterSpec(FilterSpec): 117 193 [self.lookup_kwarg]), 118 194 'display': EMPTY_CHANGELIST_VALUE} 119 195 120 Fi lterSpec.register(lambda f: (196 FieldListFilter.register(lambda f: ( 121 197 hasattr(f, 'rel') and bool(f.rel) or 122 isinstance(f, models.related.RelatedObject)), RelatedFi lterSpec)198 isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter) 123 199 124 class BooleanField FilterSpec(FilterSpec):200 class BooleanFieldListFilter(FieldListFilter): 125 201 def __init__(self, f, request, params, model, model_admin, 126 field_path =None):127 super(BooleanField FilterSpec, self).__init__(f, request, params, model,202 field_path): 203 super(BooleanFieldListFilter, self).__init__(f, request, params, model, 128 204 model_admin, 129 field_path =field_path)205 field_path) 130 206 self.lookup_kwarg = '%s__exact' % self.field_path 131 207 self.lookup_kwarg2 = '%s__isnull' % self.field_path 132 208 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 133 209 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 134 210 135 def title(self):136 return self.field.verbose_name137 138 def choices(self, cl):211 def used_params(self): 212 return [self.lookup_kwarg, self.lookup_kwarg2] 213 214 def get_output_choices(self, cl): 139 215 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 140 216 yield {'selected': self.lookup_val == v and not self.lookup_val2, 141 217 'query_string': cl.get_query_string( … … class BooleanFieldFilterSpec(FilterSpec): 149 225 [self.lookup_kwarg]), 150 226 'display': _('Unknown')} 151 227 152 Fi lterSpec.register(lambda f: isinstance(f, models.BooleanField)228 FieldListFilter.register(lambda f: isinstance(f, models.BooleanField) 153 229 or isinstance(f, models.NullBooleanField), 154 BooleanField FilterSpec)230 BooleanFieldListFilter) 155 231 156 class ChoicesFi lterSpec(FilterSpec):232 class ChoicesFieldListFilter(FieldListFilter): 157 233 def __init__(self, f, request, params, model, model_admin, 158 field_path =None):159 super(ChoicesFi lterSpec, self).__init__(f, request, params, model,234 field_path): 235 super(ChoicesFieldListFilter, self).__init__(f, request, params, model, 160 236 model_admin, 161 field_path =field_path)237 field_path) 162 238 self.lookup_kwarg = '%s__exact' % self.field_path 163 self.lookup_val = request.GET.get(self.lookup_kwarg , None)239 self.lookup_val = request.GET.get(self.lookup_kwarg) 164 240 165 def choices(self, cl): 241 def used_params(self): 242 return [self.lookup_kwarg] 243 244 def get_output_choices(self, cl): 166 245 yield {'selected': self.lookup_val is None, 167 246 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 168 247 'display': _('All')} … … class ChoicesFilterSpec(FilterSpec): 172 251 {self.lookup_kwarg: k}), 173 252 'display': v} 174 253 175 Fi lterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)254 FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter) 176 255 177 class DateField FilterSpec(FilterSpec):256 class DateFieldListFilter(FieldListFilter): 178 257 def __init__(self, f, request, params, model, model_admin, 179 field_path =None):180 super(DateField FilterSpec, self).__init__(f, request, params, model,258 field_path): 259 super(DateFieldListFilter, self).__init__(f, request, params, model, 181 260 model_admin, 182 field_path =field_path)261 field_path) 183 262 184 263 self.field_generic = '%s__' % self.field_path 185 264 … … class DateFieldFilterSpec(FilterSpec): 192 271 and today.strftime('%Y-%m-%d 23:59:59') \ 193 272 or today.strftime('%Y-%m-%d') 194 273 274 self.lookup_kwarg_year = '%s__year' % self.field_path 275 self.lookup_kwarg_month = '%s__month' % self.field_path 276 self.lookup_kwarg_day = '%s__day' % self.field_path 277 self.lookup_kwarg_past_7_days_gte = '%s__gte' % self.field_path 278 self.lookup_kwarg_past_7_days_lte = '%s__lte' % self.field_path 279 195 280 self.links = ( 196 281 (_('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:282 (_('Today'), {self.lookup_kwarg_year: str(today.year), 283 self.lookup_kwarg_month: str(today.month), 284 self.lookup_kwarg_day: str(today.day)}), 285 (_('Past 7 days'), {self.lookup_kwarg_past_7_days_gte: 201 286 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)}) 287 self.lookup_kwarg_past_7_days_lte: 288 today_str}), 289 (_('This month'), {self.lookup_kwarg_year: str(today.year), 290 self.lookup_kwarg_month: str(today.month)}), 291 (_('This year'), {self.lookup_kwarg_year: str(today.year)}) 206 292 ) 207 293 208 def title(self): 209 return self.field.verbose_name 210 211 def choices(self, cl): 294 def used_params(self): 295 return [self.lookup_kwarg_year, self.lookup_kwarg_month, 296 self.lookup_kwarg_day, self.lookup_kwarg_past_7_days_gte, 297 self.lookup_kwarg_past_7_days_lte] 298 299 def get_query_set(self, queryset): 300 """ 301 Override the default behaviour since there can be multiple query 302 string parameters used for the same date filter (e.g. year + month). 303 """ 304 query_dict = {} 305 for p in self.used_params(): 306 if p in self.params: 307 query_dict[p] = self.params[p] 308 if len(query_dict): 309 return queryset.filter(**query_dict) 310 311 def get_output_choices(self, cl): 212 312 for title, param_dict in self.links: 213 313 yield {'selected': self.date_params == param_dict, 214 314 'query_string': cl.get_query_string( … … class DateFieldFilterSpec(FilterSpec): 216 316 [self.field_generic]), 217 317 'display': title} 218 318 219 Fi lterSpec.register(lambda f: isinstance(f, models.DateField),220 DateField FilterSpec)319 FieldListFilter.register(lambda f: isinstance(f, models.DateField), 320 DateFieldListFilter) 221 321 222 322 223 323 # 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):324 # if a field is eligible to use the BooleanFieldListFilter, that'd be much 325 # more appropriate, and the AllValuesFieldListFilter won't get used for it. 326 class AllValuesFieldListFilter(FieldListFilter): 227 327 def __init__(self, f, request, params, model, model_admin, 228 field_path =None):229 super(AllValuesFi lterSpec, self).__init__(f, request, params, model,328 field_path): 329 super(AllValuesFieldListFilter, self).__init__(f, request, params, model, 230 330 model_admin, 231 field_path =field_path)331 field_path) 232 332 self.lookup_kwarg = self.field_path 233 333 self.lookup_kwarg_isnull = '%s__isnull' % self.field_path 234 334 self.lookup_val = request.GET.get(self.lookup_kwarg, None) … … class AllValuesFilterSpec(FilterSpec): 245 345 self.lookup_choices = \ 246 346 queryset.distinct().order_by(f.name).values_list(f.name, flat=True) 247 347 248 def title(self):249 return self.field.verbose_name250 251 def choices(self, cl):348 def used_params(self): 349 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 350 351 def get_output_choices(self, cl): 252 352 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 253 353 yield {'selected': self.lookup_val is None 254 354 and self.lookup_val_isnull is None, … … class AllValuesFilterSpec(FilterSpec): 276 376 [self.lookup_kwarg]), 277 377 'display': EMPTY_CHANGELIST_VALUE} 278 378 279 Fi lterSpec.register(lambda f: True, AllValuesFilterSpec)379 FieldListFilter.register(lambda f: True, AllValuesFieldListFilter) -
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..3125116 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.get_output_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..b788a1a 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 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), e: 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..7e2165f 100644
a b 1 from django.contrib.admin.filterspecs import FilterSpec1 from django.contrib.admin.filterspecs import SingleQueryParameterListFilter, 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 … … def field_needs_distinct(field): 37 37 38 38 class ChangeList(object): 39 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): 40 self.request = request 40 41 self.model = model 41 42 self.opts = model._meta 42 43 self.lookup_opts = self.opts … … class ChangeList(object): 71 72 self.order_field, self.order_type = self.get_ordering() 72 73 self.query = request.GET.get(SEARCH_VAR, '') 73 74 self.query_set = self.get_query_set() 74 self.get_results( request)75 self.get_results() 75 76 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)77 77 self.pk_attname = self.lookup_opts.pk.attname 78 78 79 def get_filters(self , request):79 def get_filters(self): 80 80 filter_specs = [] 81 81 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) 82 for item in self.list_filter: 83 if callable(item): 84 # This is simply a custom list filter class. 85 spec = item(self.request, self.cleaned_params, self.model, self.model_admin) 86 else: 87 field_path = None 88 try: 89 # This is custom FieldListFilter class for a given field. 90 field, field_list_filter_class = item 91 except (TypeError, ValueError): 92 # This is simply a field name, so use the default 93 # FieldListFilter class that has been registered for 94 # the type of the given field. 95 field, field_list_filter_class = item, FieldListFilter.create 96 if not isinstance(field, models.Field): 97 field_path = field 98 field = get_fields_from_path(self.model, field_path)[-1] 99 spec = field_list_filter_class(field, self.request, self.cleaned_params, self.model, 100 self.model_admin, field_path=field_path) 87 101 if spec and spec.has_output(): 88 102 filter_specs.append(spec) 89 103 return filter_specs, bool(filter_specs) … … class ChangeList(object): 104 118 p[k] = v 105 119 return '?%s' % urlencode(p) 106 120 107 def get_results(self , request):108 paginator = self.model_admin.get_paginator( request, self.query_set, self.list_per_page)121 def get_results(self): 122 paginator = self.model_admin.get_paginator(self.request, self.query_set, self.list_per_page) 109 123 # Get the number of objects, with admin filters applied. 110 124 result_count = paginator.count 111 125 … … class ChangeList(object): 174 188 if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): 175 189 order_type = params[ORDER_TYPE_VAR] 176 190 return order_field, order_type 177 191 192 def apply_list_filters(self, qs, lookup_params): 193 for filter_spec in self.filter_specs: 194 new_qs = filter_spec.get_query_set(qs) 195 if new_qs is not None: 196 qs = new_qs 197 for param in filter_spec.used_params(): 198 try: 199 del lookup_params[param] 200 except KeyError: 201 pass 202 return qs 203 178 204 def get_query_set(self): 179 205 use_distinct = False 180 206 … … class ChangeList(object): 196 222 field_name = key.split('__', 1)[0] 197 223 try: 198 224 f = self.lookup_opts.get_field_by_name(field_name)[0] 225 use_distinct = field_needs_distinct(f) 199 226 except models.FieldDoesNotExist: 200 raise IncorrectLookupParameters201 use_distinct = field_needs_distinct(f)202 227 # It might be a custom NonFieldFilter 228 pass 229 203 230 # if key ends with __in, split parameter into separate values 204 231 if key.endswith('__in'): 205 232 value = value.split(',') … … class ChangeList(object): 217 244 raise SuspiciousOperation( 218 245 "Filtering by %s not allowed" % key 219 246 ) 220 221 # Apply lookup parameters from the query string. 247 248 # Keep a copy of cleaned querystring parameters so they can be passed 249 # to the list filters. 250 self.cleaned_params = lookup_params.copy() 251 252 self.filter_specs, self.has_filters = self.get_filters() 253 254 # Let every list filter modify the qs and params to its liking 255 qs = self.apply_list_filters(qs, lookup_params) 256 257 # Apply the remaining lookup parameters from the query string (i.e. 258 # those that haven't already been processed by the filters). 222 259 try: 223 260 qs = qs.filter(**lookup_params) 224 261 # 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 633c53f..b1cffe1 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:: 535 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: 528 .. versionchanged:: 1.4 541 529 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: 532 542 533 .. image:: _images/users_changelist.png 543 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') 534 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.SingleQueryParameterListFilter`, 554 where you need to provide a few attributes and override a few 555 methods:: 556 557 from django.contrib.admin import SingleQueryParameterListFilter 558 from django.db.models import Q 559 560 class DecadeBornListFilter(SingleQueryParameterListFilter): 561 # Human-readable title which will be displayed in the 562 # right sidebar just above the filter options. 563 title = u'decade born' 564 565 # Parameter for the filter that will be used in the url query. 566 # Providing this attribute is optional. If it is not provided then a 567 # slugified version of the title will automatically be used instead 568 # (that is, 'decade-born' in this example). 569 query_parameter = u'decade' 570 571 def get_choices(self): 572 """ 573 Returns a list of tuples. The first element in each tuple 574 is the coded value for the option that will appear in the 575 url query. The second element is the human-readable name 576 for the option that will appear in the right sidebar. 577 """ 578 return ( 579 (u'80s', u'in the eighties'), 580 (u'other', u'other'), 581 ) 582 583 def get_query_set(self, queryset): 584 """ 585 Returns the filtered queryset based on the value provided 586 in the query string and retrievable via `get_value()`. 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 .. note:: 602 603 As a convenience, the ``HttpRequest`` object is accessible using 604 ``self.request`` from any of the filter's methods, for example:: 605 606 class DecadeBornListFilter(SingleQueryParameterListFilter): 607 608 def get_choices(self): 609 if self.request.user.is_authenticated(): 610 return ( 611 (u'80s', u'in the eighties'), 612 (u'other', u'other'), 613 ) 614 else: 615 return ( 616 (u'90s', u'in the nineties'), 617 ) 618 619 * a tuple, where the first element is a field name and the second 620 element is a class inheriting from 621 :mod:`django.contrib.admin.FieldListFilter`, for example:: 622 623 from django.contrib.admin import BooleanFieldListFilter 624 625 class PersonAdmin(ModelAdmin): 626 list_filter = (('is_staff', BooleanFieldListFilter),) 627 628 .. note:: 629 630 The ``FieldListFilter`` API is currently considered internal and 631 prone to refactoring. 552 632 553 633 .. attribute:: ModelAdmin.list_per_page 554 634 -
tests/regressiontests/admin_changelist/tests.py
diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index 5186508..ae0db75 100644
a b class ChangeListTests(TransactionTestCase): 133 133 m.list_filter, m.date_hierarchy, m.search_fields, 134 134 m.list_select_related, m.list_per_page, m.list_editable, m) 135 135 136 cl.get_results( request)136 cl.get_results() 137 137 self.assertIsInstance(cl.paginator, CustomPaginator) 138 138 139 139 def test_distinct_for_m2m_in_list_filter(self): … … class ChangeListTests(TransactionTestCase): 155 155 m.search_fields, m.list_select_related, m.list_per_page, 156 156 m.list_editable, m) 157 157 158 cl.get_results( request)158 cl.get_results() 159 159 160 160 # There's only one Group instance 161 161 self.assertEqual(cl.result_count, 1) … … class ChangeListTests(TransactionTestCase): 178 178 m.search_fields, m.list_select_related, m.list_per_page, 179 179 m.list_editable, m) 180 180 181 cl.get_results( request)181 cl.get_results() 182 182 183 183 # There's only one Group instance 184 184 self.assertEqual(cl.result_count, 1) … … class ChangeListTests(TransactionTestCase): 202 202 m.search_fields, m.list_select_related, m.list_per_page, 203 203 m.list_editable, m) 204 204 205 cl.get_results( request)205 cl.get_results() 206 206 207 207 # There's only one Quartet instance 208 208 self.assertEqual(cl.result_count, 1) … … class ChangeListTests(TransactionTestCase): 226 226 m.search_fields, m.list_select_related, m.list_per_page, 227 227 m.list_editable, m) 228 228 229 cl.get_results( request)229 cl.get_results() 230 230 231 231 # There's only one ChordsBand instance 232 232 self.assertEqual(cl.result_count, 1) -
tests/regressiontests/admin_filterspecs/models.py
diff --git a/tests/regressiontests/admin_filterspecs/models.py b/tests/regressiontests/admin_filterspecs/models.py index 5b284c7..5e81d1c 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 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..cef5b98 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 (SingleQueryParameterListFilter, 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(SingleQueryParameterListFilter): 22 23 def get_choices(self): 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, 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 = 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() 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()[0][4] 87 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 88 choice = select_by(filterspec.get_output_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() 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()[0][4] 109 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 110 choice = select_by(filterspec.get_output_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() 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()[0][4] 129 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 130 choice = select_by(filterspec.get_output_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() 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()[0][4] 145 self.assertEqual(force_unicode(filterspec.title), u'date_registered') 146 choice = select_by(filterspec.get_output_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 IncorrectLookupParameters158 # Make sure the correct queryset is returned 48 159 queryset = changelist.get_query_set() 160 self.assertEqual(list(queryset), [self.django_book]) 49 161 50 162 # Make sure the last choice is None and is selected 51 filterspec = changelist.get_filters( request)[0][0]52 self.assertEqual(force_unicode(filterspec.title ()), u'year')53 choices = list(filterspec. choices(changelist))163 filterspec = changelist.get_filters()[0][0] 164 self.assertEqual(force_unicode(filterspec.title), u'year') 165 choices = list(filterspec.get_output_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): 58 170 changelist = self.get_changelist(request, Book, modeladmin) 59 171 60 172 # Make sure the correct choice is selected 61 filterspec = changelist.get_filters( request)[0][0]62 self.assertEqual(force_unicode(filterspec.title ()), u'year')63 choices = list(filterspec. choices(changelist))173 filterspec = changelist.get_filters()[0][0] 174 self.assertEqual(force_unicode(filterspec.title), u'year') 175 choices = list(filterspec.get_output_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 IncorrectLookupParameters185 # Make sure the correct queryset is returned 76 186 queryset = changelist.get_query_set() 187 self.assertEqual(list(queryset), [self.gipsy_book]) 77 188 78 189 # Make sure the last choice is None and is selected 79 filterspec = changelist.get_filters( request)[0][1]80 self.assertEqual(force_unicode(filterspec.title ()), u'author')81 choices = list(filterspec. choices(changelist))190 filterspec = changelist.get_filters()[0][1] 191 self.assertEqual(force_unicode(filterspec.title), u'author') 192 choices = list(filterspec.get_output_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): 86 197 changelist = self.get_changelist(request, Book, modeladmin) 87 198 88 199 # Make sure the correct choice is selected 89 filterspec = changelist.get_filters( request)[0][1]90 self.assertEqual(force_unicode(filterspec.title ()), u'author')200 filterspec = changelist.get_filters()[0][1] 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.get_output_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 IncorrectLookupParameters213 # Make sure the correct queryset is returned 103 214 queryset = changelist.get_query_set() 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 filterspec = changelist.get_filters( request)[0][2]107 self.assertEqual(force_unicode(filterspec.title ()), u'user')108 choices = list(filterspec. choices(changelist))218 filterspec = changelist.get_filters()[0][2] 219 self.assertEqual(force_unicode(filterspec.title), u'user') 220 choices = list(filterspec.get_output_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): 113 225 changelist = self.get_changelist(request, Book, modeladmin) 114 226 115 227 # Make sure the correct choice is selected 116 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")228 filterspec = changelist.get_filters()[0][2] 229 self.assertEqual(force_unicode(filterspec.title), u'user') 230 choice = select_by(filterspec.get_output_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 IncorrectLookupParameters241 # Make sure the correct queryset is returned 131 242 queryset = changelist.get_query_set() 243 self.assertEqual(list(queryset), [self.lisa]) 132 244 133 245 # Make sure the last choice is None and is selected 134 filterspec = changelist.get_filters( request)[0][0]135 self.assertEqual(force_unicode(filterspec.title ()), u'book')136 choices = list(filterspec. choices(changelist))246 filterspec = changelist.get_filters()[0][0] 247 self.assertEqual(force_unicode(filterspec.title), u'book') 248 choices = list(filterspec.get_output_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): 141 253 changelist = self.get_changelist(request, User, modeladmin) 142 254 143 255 # Make sure the correct choice is selected 144 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)256 filterspec = changelist.get_filters()[0][0] 257 self.assertEqual(force_unicode(filterspec.title), u'book') 258 choice = select_by(filterspec.get_output_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 IncorrectLookupParameters266 # Make sure the correct queryset is returned 155 267 queryset = changelist.get_query_set() 268 self.assertEqual(list(queryset), [self.alfred]) 156 269 157 270 # Make sure the last choice is None and is selected 158 filterspec = changelist.get_filters( request)[0][1]159 self.assertEqual(force_unicode(filterspec.title ()), u'book')160 choices = list(filterspec. choices(changelist))271 filterspec = changelist.get_filters()[0][1] 272 self.assertEqual(force_unicode(filterspec.title), u'book') 273 choices = list(filterspec.get_output_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): 165 278 changelist = self.get_changelist(request, User, modeladmin) 166 279 167 280 # Make sure the correct choice is selected 168 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)281 filterspec = changelist.get_filters()[0][1] 282 self.assertEqual(force_unicode(filterspec.title), u'book') 283 choice = select_by(filterspec.get_output_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 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 183 303 queryset = changelist.get_query_set() 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()[0][3] 308 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 309 choice = select_by(filterspec.get_output_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() 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()[0][3] 322 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 323 choice = select_by(filterspec.get_output_choices(changelist), "display", "Yes") 200 324 self.assertEqual(choice['selected'], True) 201 self.assertEqual(choice['query_string'], '?completed__exact=1') 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() 332 self.assertEqual(list(queryset), [self.django_book]) 333 334 # Make sure the correct choice is selected 335 filterspec = changelist.get_filters()[0][3] 336 self.assertEqual(force_unicode(filterspec.title), u'is_best_seller') 337 choice = select_by(filterspec.get_output_choices(changelist), "display", "Unknown") 338 self.assertEqual(choice['selected'], True) 339 self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') 340 341 def test_SingleQueryParameterListFilter(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() 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()[0][1] 355 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 356 choices = list(filterspec.get_output_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()[0][2] 368 self.assertEqual(force_unicode(filterspec.title), u'another publication decade') 369 choices = list(filterspec.get_output_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) 378 379 # Make sure the correct queryset is returned 380 queryset = changelist.get_query_set() 381 self.assertEqual(list(queryset), [self.bio_book]) 382 383 # Make sure the correct choice is selected 384 filterspec = changelist.get_filters()[0][1] 385 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 386 choices = list(filterspec.get_output_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) 202 395 396 # Make sure the correct queryset is returned 397 queryset = changelist.get_query_set() 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()[0][1] 402 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 403 choices = list(filterspec.get_output_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() 415 self.assertEqual(list(queryset), [self.djangonaut_book]) 416 417 # Make sure the correct choices are selected 418 filterspec = changelist.get_filters()[0][1] 419 self.assertEqual(force_unicode(filterspec.title), u'publication decade') 420 choices = list(filterspec.get_output_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()[0][0] 426 self.assertEqual(force_unicode(filterspec.title), u'author') 427 choice = select_by(filterspec.get_output_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/modeladmin/tests.py
diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py index a20e579..44bb957 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 (SingleQueryParameterListFilter, 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 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(SingleQueryParameterListFilter): 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,) 853 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