Ticket #5833: 5833.custom-filterspecs.diff
File 5833.custom-filterspecs.diff, 15.5 KB (added by , 13 years ago) |
---|
-
django/contrib/admin/__init__.py
diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py index f8e634e..77c1143 100644
a b 1 1 # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here 2 2 # has been referenced in documentation. 3 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 3 4 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 4 5 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL 5 6 from django.contrib.admin.options import StackedInline, TabularInline -
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 965b32b..8580255 100644
a b import datetime 17 17 18 18 class FilterSpec(object): 19 19 filter_specs = [] 20 def __init__(self, f, request, params, model, model_admin, 21 field_path=None): 22 self.field = f 20 def __init__(self, request, params, model, model_admin): 23 21 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 22 42 23 def has_output(self): 43 24 return True … … class FilterSpec(object): 46 27 raise NotImplementedError() 47 28 48 29 def title(self): 49 return self.field.verbose_name 30 raise NotImplementedError() 31 32 def get_query_set(self, cl, qs): 33 return False 34 35 def consumed_params(self): 36 """ 37 Return a list of parameters to consume from the change list querystring. 38 39 Override this for non-field based FilterSpecs subclasses in order 40 to consume custom GET parameters, as any GET parameters that are not 41 consumed and are not a field name raises an exception. 42 """ 43 return [] 50 44 51 45 def output(self, cl): 52 46 t = [] … … class FilterSpec(object): 61 55 t.append('</ul>\n\n') 62 56 return mark_safe("".join(t)) 63 57 64 class RelatedFilterSpec(FilterSpec): 58 59 class FieldFilterSpec(FilterSpec): 60 field_filter_specs = [] 61 _high_priority_index = 0 62 63 def __init__(self, f, request, params, model, model_admin, field_path=None): 64 super(FieldFilterSpec, self).__init__(request, params, model, model_admin) 65 self.field = f 66 if field_path is None: 67 if isinstance(f, models.related.RelatedObject): 68 self.field_path = f.var_name 69 else: 70 self.field_path = f.name 71 else: 72 self.field_path = field_path 73 74 def title(self): 75 return self.field.verbose_name 76 77 @classmethod 78 def register(cls, test, factory, high_priority=True): 79 if high_priority: 80 cls.field_filter_specs.insert(cls._high_priority_index, (test, factory)) 81 cls._high_priority_index += 1 82 else: 83 cls.field_filter_specs.append((test, factory)) 84 85 @classmethod 86 def create(cls, f, request, params, model, model_admin, field_path=None): 87 for test, factory in cls.field_filter_specs: 88 if test(f): 89 return factory(f, request, params, model, model_admin, 90 field_path=field_path) 91 92 93 class RelatedFilterSpec(FieldFilterSpec): 65 94 def __init__(self, f, request, params, model, model_admin, 66 95 field_path=None): 67 96 super(RelatedFilterSpec, self).__init__( … … class RelatedFilterSpec(FilterSpec): 117 146 [self.lookup_kwarg]), 118 147 'display': EMPTY_CHANGELIST_VALUE} 119 148 120 Fi lterSpec.register(lambda f: (149 FieldFilterSpec.register(lambda f: ( 121 150 hasattr(f, 'rel') and bool(f.rel) or 122 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec )151 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec, False) 123 152 124 class BooleanFieldFilterSpec(Fi lterSpec):153 class BooleanFieldFilterSpec(FieldFilterSpec): 125 154 def __init__(self, f, request, params, model, model_admin, 126 155 field_path=None): 127 156 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, … … class BooleanFieldFilterSpec(FilterSpec): 132 161 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 133 162 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 134 163 135 def title(self):136 return self.field.verbose_name137 138 164 def choices(self, cl): 139 165 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 140 166 yield {'selected': self.lookup_val == v and not self.lookup_val2, … … class BooleanFieldFilterSpec(FilterSpec): 149 175 [self.lookup_kwarg]), 150 176 'display': _('Unknown')} 151 177 152 Fi lterSpec.register(lambda f: isinstance(f, models.BooleanField)178 FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField) 153 179 or isinstance(f, models.NullBooleanField), 154 BooleanFieldFilterSpec )180 BooleanFieldFilterSpec, False) 155 181 156 class ChoicesFilterSpec(Fi lterSpec):182 class ChoicesFilterSpec(FieldFilterSpec): 157 183 def __init__(self, f, request, params, model, model_admin, 158 184 field_path=None): 159 185 super(ChoicesFilterSpec, self).__init__(f, request, params, model, … … class ChoicesFilterSpec(FilterSpec): 172 198 {self.lookup_kwarg: k}), 173 199 'display': v} 174 200 175 Fi lterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)201 FieldFilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec, False) 176 202 177 class DateFieldFilterSpec(Fi lterSpec):203 class DateFieldFilterSpec(FieldFilterSpec): 178 204 def __init__(self, f, request, params, model, model_admin, 179 205 field_path=None): 180 206 super(DateFieldFilterSpec, self).__init__(f, request, params, model, … … class DateFieldFilterSpec(FilterSpec): 205 231 (_('This year'), {'%s__year' % self.field_path: str(today.year)}) 206 232 ) 207 233 208 def title(self):209 return self.field.verbose_name210 211 234 def choices(self, cl): 212 235 for title, param_dict in self.links: 213 236 yield {'selected': self.date_params == param_dict, … … class DateFieldFilterSpec(FilterSpec): 216 239 [self.field_generic]), 217 240 'display': title} 218 241 219 Fi lterSpec.register(lambda f: isinstance(f, models.DateField),220 DateFieldFilterSpec )242 FieldFilterSpec.register(lambda f: isinstance(f, models.DateField), 243 DateFieldFilterSpec, False) 221 244 222 245 223 246 # This should be registered last, because it's a last resort. For example, 224 247 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much 225 248 # more appropriate, and the AllValuesFilterSpec won't get used for it. 226 class AllValuesFilterSpec(Fi lterSpec):249 class AllValuesFilterSpec(FieldFilterSpec): 227 250 def __init__(self, f, request, params, model, model_admin, 228 251 field_path=None): 229 252 super(AllValuesFilterSpec, self).__init__(f, request, params, model, … … class AllValuesFilterSpec(FilterSpec): 245 268 self.lookup_choices = \ 246 269 queryset.distinct().order_by(f.name).values_list(f.name, flat=True) 247 270 248 def title(self):249 return self.field.verbose_name250 251 271 def choices(self, cl): 252 272 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 253 273 yield {'selected': self.lookup_val is None … … class AllValuesFilterSpec(FilterSpec): 276 296 [self.lookup_kwarg]), 277 297 'display': EMPTY_CHANGELIST_VALUE} 278 298 279 Fi lterSpec.register(lambda f: True, AllValuesFilterSpec)299 FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False) -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 159afa4..4fdb8a1 100644
a b from django.db import models 3 3 from django.db.models.fields import FieldDoesNotExist 4 4 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, 5 5 _get_foreign_key) 6 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 6 7 from django.contrib.admin.util import get_fields_from_path, NotRelationField 7 8 from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin, 8 9 HORIZONTAL, VERTICAL) … … def validate(cls, model): 54 55 # list_filter 55 56 if hasattr(cls, 'list_filter'): 56 57 check_isseq(cls, 'list_filter', cls.list_filter) 57 for idx, fpath in enumerate(cls.list_filter): 58 try: 59 get_fields_from_path(model, fpath) 60 except (NotRelationField, FieldDoesNotExist), e: 61 raise ImproperlyConfigured( 62 "'%s.list_filter[%d]' refers to '%s' which does not refer to a Field." % ( 63 cls.__name__, idx, fpath 64 ) 65 ) 58 for idx, item in enumerate(cls.list_filter): 59 # There are three methods of specifying a filter: 60 # 1: 'field' - a simple field filter, poss. w/ relationships (eg, 'field__rel') 61 # 2: ('field', SomeFieldFilterSpec) - a field-based filter spec 62 # 3: SomeFilterSpec - a non-field filter spec 63 if callable(item) and not isinstance(item, models.Field): 64 # If item is option 3, it should be a FilterSpec, but not a FieldFilterSpec 65 if not issubclass(item, FilterSpec) or issubclass(item, FieldFilterSpec): 66 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 67 " which is not of type FilterSpec." 68 % (cls.__name__, idx, item)) 69 else: 70 try: 71 # Check for option #2 (tuple) 72 field, factory = item 73 except (TypeError, ValueError): 74 # item is option #1 75 field = item 76 else: 77 # item is option #2 78 if not issubclass(factory, FieldFilterSpec): 79 raise ImproperlyConfigured("'%s.list_filter[%d][1]'" 80 " refers to '%s' which is not of type FieldFilterSpec." 81 % (cls.__name__, idx, factory.__name__)) 82 # Validate the field string 83 try: 84 get_fields_from_path(model, field) 85 except (NotRelationField, FieldDoesNotExist), e: 86 raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" 87 " which does not refer to a Field." 88 % (cls.__name__, idx, field)) 66 89 67 90 # list_per_page = 100 68 91 if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 170d168..5ce6fc3 100644
a b 1 from django.contrib.admin.filterspecs import FilterSpec 1 from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec 2 2 from django.contrib.admin.options import IncorrectLookupParameters 3 3 from django.contrib.admin.util import quote, get_fields_from_path 4 4 from django.core.exceptions import SuspiciousOperation … … class ChangeList(object): 59 59 self.list_editable = () 60 60 else: 61 61 self.list_editable = list_editable 62 self.filter_specs, self.has_filters = self.get_filters(request) 62 63 self.order_field, self.order_type = self.get_ordering() 63 64 self.query = request.GET.get(SEARCH_VAR, '') 64 65 self.query_set = self.get_query_set() 65 66 self.get_results(request) 66 67 self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name)) 67 self.filter_specs, self.has_filters = self.get_filters(request)68 68 self.pk_attname = self.lookup_opts.pk.attname 69 69 70 70 def get_filters(self, request): 71 71 filter_specs = [] 72 72 if self.list_filter: 73 for filter_name in self.list_filter: 74 field = get_fields_from_path(self.model, filter_name)[-1] 75 spec = FilterSpec.create(field, request, self.params, 76 self.model, self.model_admin, 77 field_path=filter_name) 73 for item in self.list_filter: 74 if callable(item): 75 spec = item(request, self.params, self.model, self.model_admin) 76 else: 77 field_path = None 78 try: 79 field, factory = item 80 except (TypeError, ValueError): 81 field, factory = item, FieldFilterSpec.create 82 if not isinstance(field, models.Field): 83 field_path = field 84 field = get_fields_from_path(self.model, field_path)[-1] 85 spec = factory(field, request, self.params, self.model, 86 self.model_admin, field_path=field_path) 78 87 if spec and spec.has_output(): 79 88 filter_specs.append(spec) 80 89 return filter_specs, bool(filter_specs) … … class ChangeList(object): 166 175 order_type = params[ORDER_TYPE_VAR] 167 176 return order_field, order_type 168 177 178 def apply_filter_specs(self, qs, lookup_params): 179 for filter_spec in self.filter_specs: 180 new_qs = filter_spec.get_query_set(self, qs) 181 if new_qs is not None and new_qs is not False: 182 qs = new_qs 183 # Only consume params if we got a new queryset 184 for param in filter_spec.consumed_params(): 185 try: 186 del lookup_params[param] 187 except KeyError: 188 pass 189 return qs 190 169 191 def get_query_set(self): 170 192 use_distinct = False 171 193 … … class ChangeList(object): 174 196 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR): 175 197 if i in lookup_params: 176 198 del lookup_params[i] 199 key = '' 177 200 for key, value in lookup_params.items(): 178 201 if not isinstance(key, str): 179 202 # 'key' will be used as a keyword argument later, so Python … … class ChangeList(object): 210 233 "Filtering by %s not allowed" % key 211 234 ) 212 235 236 # Let every filter spec modify the qs and params to its liking 237 qs = self.apply_filter_specs(qs, lookup_params) 238 213 239 # Apply lookup parameters from the query string. 214 240 try: 215 241 qs = qs.filter(**lookup_params)