Ticket #5833: 5833-custom-filter-spec-1.3.diff
File 5833-custom-filter-spec-1.3.diff, 15.6 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/validation.py
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 from django.contrib.admin.options import HORIZONTAL, VERTICAL … … 49 50 fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field) 50 51 if field not in cls.list_display: 51 52 raise ImproperlyConfigured("'%s.list_display_links[%d]'" 52 " refers to '%s' which is not defined in 'list_display'."53 " refers to '%s' which is not defined in 'list_display'." 53 54 % (cls.__name__, idx, field)) 54 55 55 56 # list_filter 56 57 if hasattr(cls, 'list_filter'): 57 58 check_isseq(cls, 'list_filter', cls.list_filter) 58 for idx, fpath in enumerate(cls.list_filter): 59 try: 60 get_fields_from_path(model, fpath) 61 except (NotRelationField, FieldDoesNotExist), e: 62 raise ImproperlyConfigured( 63 "'%s.list_filter[%d]' refers to '%s' which does not refer to a Field." % ( 64 cls.__name__, idx, fpath 65 ) 66 ) 59 for idx, item in enumerate(cls.list_filter): 60 # There are three methods of specifying a filter: 61 # 1: 'field' - a simple field filter, poss. w/ relationships (eg, 'field__rel') 62 # 2: ('field', SomeFieldFilterSpec) - a field-based filter spec 63 # 3: SomeFilterSpec - a non-field filter spec 64 if callable(item) and not isinstance(item, models.Field): 65 # If item is option 3, it should be a FilterSpec, but not a FieldFilterSpec 66 if not issubclass(item, FilterSpec) or issubclass(item, FieldFilterSpec): 67 raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'" 68 " which is not of type FilterSpec." 69 % (cls.__name__, idx, item)) 70 else: 71 try: 72 # Check for option #2 (tuple) 73 field, factory = item 74 except (TypeError, ValueError): 75 # item is option #1 76 field = item 77 else: 78 # item is option #2 79 if not issubclass(factory, FieldFilterSpec): 80 raise ImproperlyConfigured("'%s.list_filter[%d][1]'" 81 " refers to '%s' which is not of type FieldFilterSpec." 82 % (cls.__name__, idx, factory.__name__)) 83 # Validate the field string 84 try: 85 get_fields_from_path(model, field) 86 except (NotRelationField, FieldDoesNotExist), e: 87 raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" 88 " which does not refer to a Field." 89 % (cls.__name__, idx, field)) 67 90 68 91 # list_per_page = 100 69 92 if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): -
django/contrib/admin/__init__.py
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/views/main.py
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.paginator import Paginator, InvalidPage … … 57 57 if ERROR_FLAG in self.params: 58 58 del self.params[ERROR_FLAG] 59 59 60 self.filter_specs, self.has_filters = self.get_filters(request) 60 61 self.order_field, self.order_type = self.get_ordering() 61 62 self.query = request.GET.get(SEARCH_VAR, '') 62 63 self.query_set = self.get_query_set() 63 64 self.get_results(request) 64 65 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)) 65 self.filter_specs, self.has_filters = self.get_filters(request)66 66 self.pk_attname = self.lookup_opts.pk.attname 67 67 68 68 def get_filters(self, request): 69 69 filter_specs = [] 70 70 if self.list_filter: 71 for filter_name in self.list_filter: 72 field = get_fields_from_path(self.model, filter_name)[-1] 73 spec = FilterSpec.create(field, request, self.params, 74 self.model, self.model_admin, 75 field_path=filter_name) 71 for item in self.list_filter: 72 if callable(item): 73 spec = item(request, self.params, self.model, self.model_admin) 74 else: 75 field_path = None 76 try: 77 field, factory = item 78 except (TypeError, ValueError): 79 field, factory = item, FieldFilterSpec.create 80 if not isinstance(field, models.Field): 81 field_path = field 82 field = get_fields_from_path(self.mode, field_path)[-1] 83 spec = factory(field, request, self.params, self.model, 84 self.model_admin, field_path=field_path) 85 86 76 87 if spec and spec.has_output(): 77 88 filter_specs.append(spec) 78 89 return filter_specs, bool(filter_specs) … … 164 175 order_type = params[ORDER_TYPE_VAR] 165 176 return order_field, order_type 166 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 167 191 def get_query_set(self): 168 192 qs = self.root_query_set 169 193 lookup_params = self.params.copy() # a dictionary of the query string 170 194 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): 171 195 if i in lookup_params: 172 196 del lookup_params[i] 197 key = '' 173 198 for key, value in lookup_params.items(): 174 199 if not isinstance(key, str): 175 200 # 'key' will be used as a keyword argument later, so Python … … 187 212 lookup_params[key] = False 188 213 else: 189 214 lookup_params[key] = True 215 216 # Let every filter spec modify the qs and params to its liking 217 qs = self.apply_filter_specs(self, qs, lookup_params) 190 218 191 219 # Apply lookup parameters from the query string. 192 220 try: -
django/contrib/admin/filterspecs.py
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 22 31 def register(cls, test, factory):32 cls.filter_specs.append((test, factory))33 register = classmethod(register)34 35 def create(cls, f, request, params, model, model_admin, field_path=None):36 for test, factory in cls.filter_specs:37 if test(f):38 return factory(f, request, params, model, model_admin,39 field_path=field_path)40 create = classmethod(create)41 42 23 def has_output(self): 43 24 return True 44 25 … … 46 27 raise NotImplementedError() 47 28 48 29 def title(self): 49 r eturn self.field.verbose_name30 raise NotImplementedError() 50 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 [] 44 51 45 def output(self, cl): 52 46 t = [] 53 47 if self.has_output(): … … 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__(f, request, params, model, model_admin, field_path=None) 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 72 def title(self): 73 return self.field.verbose_name 74 75 def register(cls, test, factory, high_priority=True): 76 if high_priority: 77 cls.field_filter_Specs.insert(cls._high_priority_index, (test, factory)) 78 cls._high_priority_index += 1 79 else: 80 cls.field_filter_specs.append((test, factory)) 81 register = classmethod(register) 82 83 def create(cls, f, request, params, model, model_admin, field_path=None): 84 for test, factory in cls.filter_specs: 85 if test(f): 86 return factory(f, request, params, model, model_admin, 87 field_path=field_path) 88 create = classmethod(create) 89 90 91 class RelatedFilterSpec(FieldFilterSpec): 65 92 def __init__(self, f, request, params, model, model_admin, 66 93 field_path=None): 67 94 super(RelatedFilterSpec, self).__init__( … … 94 121 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), 95 122 'display': val} 96 123 97 Fi lterSpec.register(lambda f: (124 FieldFilterSpec.register(lambda f: ( 98 125 hasattr(f, 'rel') and bool(f.rel) or 99 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec )126 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec, False) 100 127 101 class ChoicesFilterSpec(Fi lterSpec):128 class ChoicesFilterSpec(FieldFilterSpec): 102 129 def __init__(self, f, request, params, model, model_admin, 103 130 field_path=None): 104 131 super(ChoicesFilterSpec, self).__init__(f, request, params, model, … … 116 143 'query_string': cl.get_query_string({self.lookup_kwarg: k}), 117 144 'display': v} 118 145 119 Fi lterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)146 FieldFilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec, False) 120 147 121 class DateFieldFilterSpec(Fi lterSpec):148 class DateFieldFilterSpec(FieldFilterSpec): 122 149 def __init__(self, f, request, params, model, model_admin, 123 150 field_path=None): 124 151 super(DateFieldFilterSpec, self).__init__(f, request, params, model, … … 146 173 (_('This year'), {'%s__year' % self.field_path: str(today.year)}) 147 174 ) 148 175 149 def title(self):150 return self.field.verbose_name151 152 176 def choices(self, cl): 153 177 for title, param_dict in self.links: 154 178 yield {'selected': self.date_params == param_dict, 155 179 'query_string': cl.get_query_string(param_dict, [self.field_generic]), 156 180 'display': title} 157 181 158 Fi lterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)182 FieldFilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec, False) 159 183 160 class BooleanFieldFilterSpec(Fi lterSpec):184 class BooleanFieldFilterSpec(FieldFilterSpec): 161 185 def __init__(self, f, request, params, model, model_admin, 162 186 field_path=None): 163 187 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, … … 168 192 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 169 193 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 170 194 171 def title(self):172 return self.field.verbose_name173 174 195 def choices(self, cl): 175 196 for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): 176 197 yield {'selected': self.lookup_val == v and not self.lookup_val2, … … 181 202 'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), 182 203 'display': _('Unknown')} 183 204 184 Fi lterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)205 FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec, False) 185 206 186 207 # This should be registered last, because it's a last resort. For example, 187 208 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much 188 209 # more appropriate, and the AllValuesFilterSpec won't get used for it. 189 class AllValuesFilterSpec(Fi lterSpec):210 class AllValuesFilterSpec(FieldFilterSpec): 190 211 def __init__(self, f, request, params, model, model_admin, 191 212 field_path=None): 192 213 super(AllValuesFilterSpec, self).__init__(f, request, params, model, … … 204 225 self.lookup_choices = \ 205 226 queryset.distinct().order_by(f.name).values(f.name) 206 227 207 def title(self):208 return self.field.verbose_name209 210 228 def choices(self, cl): 211 229 yield {'selected': self.lookup_val is None, 212 230 'query_string': cl.get_query_string({}, [self.field_path]), … … 216 234 yield {'selected': self.lookup_val == val, 217 235 'query_string': cl.get_query_string({self.field_path: val}), 218 236 'display': val} 219 Fi lterSpec.register(lambda f: True, AllValuesFilterSpec)237 FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False)