From d599052a159454e33c44d9786f36189285d6c8ca Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Fri, 16 Jun 2006 19:42:06 +0000 Subject: [PATCH] Added AllValuesFilterSpec to admin changelist filters, which lets you put any arbitrary field in Admin.list_filter. To determine the list of all available choices, Django does a SELECT DISTINCT. Note this is backwards-incompatible for people who have defined and registered their own FilterSpecs, because each FilterSpec now takes a 'model' parameter. git-svn-id: http://code.djangoproject.com/svn/django/trunk@3136 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/filterspecs.py | 59 ++++++++++++++++++++--------- django/contrib/admin/views/main.py | 2 +- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 0284f13114..25376be12a 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -11,18 +11,18 @@ import datetime class FilterSpec(object): filter_specs = [] - def __init__(self, f, request, params): + def __init__(self, f, request, params, model): self.field = f self.params = params def register(cls, test, factory): - cls.filter_specs.append( (test, factory) ) + cls.filter_specs.append((test, factory)) register = classmethod(register) - def create(cls, f, request, params): + def create(cls, f, request, params, model): for test, factory in cls.filter_specs: if test(f): - return factory(f, request, params) + return factory(f, request, params, model) create = classmethod(create) def has_output(self): @@ -48,8 +48,8 @@ class FilterSpec(object): return "".join(t) class RelatedFilterSpec(FilterSpec): - def __init__(self, f, request, params): - super(RelatedFilterSpec, self).__init__(f, request, params) + def __init__(self, f, request, params, model): + super(RelatedFilterSpec, self).__init__(f, request, params, model) if isinstance(f, models.ManyToManyField): self.lookup_title = f.rel.to._meta.verbose_name else: @@ -71,31 +71,31 @@ class RelatedFilterSpec(FilterSpec): for val in self.lookup_choices: pk_val = getattr(val, self.field.rel.to._meta.pk.attname) yield {'selected': self.lookup_val == str(pk_val), - 'query_string': cl.get_query_string( {self.lookup_kwarg: pk_val}), + 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), 'display': val} FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec) class ChoicesFilterSpec(FilterSpec): - def __init__(self, f, request, params): - super(ChoicesFilterSpec, self).__init__(f, request, params) + def __init__(self, f, request, params, model): + super(ChoicesFilterSpec, self).__init__(f, request, params, model) self.lookup_kwarg = '%s__exact' % f.name self.lookup_val = request.GET.get(self.lookup_kwarg, None) def choices(self, cl): yield {'selected': self.lookup_val is None, - 'query_string': cl.get_query_string( {}, [self.lookup_kwarg]), + 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 'display': _('All')} for k, v in self.field.choices: yield {'selected': str(k) == self.lookup_val, - 'query_string': cl.get_query_string( {self.lookup_kwarg: k}), + 'query_string': cl.get_query_string({self.lookup_kwarg: k}), 'display': v} FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) class DateFieldFilterSpec(FilterSpec): - def __init__(self, f, request, params): - super(DateFieldFilterSpec, self).__init__(f, request, params) + def __init__(self, f, request, params, model): + super(DateFieldFilterSpec, self).__init__(f, request, params, model) self.field_generic = '%s__' % self.field.name @@ -123,14 +123,14 @@ class DateFieldFilterSpec(FilterSpec): def choices(self, cl): for title, param_dict in self.links: yield {'selected': self.date_params == param_dict, - 'query_string': cl.get_query_string( param_dict, self.field_generic), + 'query_string': cl.get_query_string(param_dict, self.field_generic), 'display': title} FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) class BooleanFieldFilterSpec(FilterSpec): - def __init__(self, f, request, params): - super(BooleanFieldFilterSpec, self).__init__(f, request, params) + def __init__(self, f, request, params, model): + super(BooleanFieldFilterSpec, self).__init__(f, request, params, model) self.lookup_kwarg = '%s__exact' % f.name self.lookup_kwarg2 = '%s__isnull' % f.name self.lookup_val = request.GET.get(self.lookup_kwarg, None) @@ -142,11 +142,34 @@ class BooleanFieldFilterSpec(FilterSpec): def choices(self, cl): for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): yield {'selected': self.lookup_val == v and not self.lookup_val2, - 'query_string': cl.get_query_string( {self.lookup_kwarg: v}, [self.lookup_kwarg2]), + 'query_string': cl.get_query_string({self.lookup_kwarg: v}, [self.lookup_kwarg2]), 'display': k} if isinstance(self.field, models.NullBooleanField): yield {'selected': self.lookup_val2 == 'True', - 'query_string': cl.get_query_string( {self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), + 'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), 'display': _('Unknown')} FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec) + +# This should be registered last, because it's a last resort. For example, +# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much +# more appropriate, and the AllValuesFilterSpec won't get used for it. +class AllValuesFilterSpec(FilterSpec): + def __init__(self, f, request, params, model): + super(AllValuesFilterSpec, self).__init__(f, request, params, model) + self.lookup_val = request.GET.get(f.name, None) + self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name) + + def title(self): + return self.field.verbose_name + + def choices(self, cl): + yield {'selected': self.lookup_val is None, + 'query_string': cl.get_query_string({}, [self.field.name]), + 'display': _('All')} + for val in self.lookup_choices: + val = str(val[self.field.name]) + yield {'selected': self.lookup_val == val, + 'query_string': cl.get_query_string({self.field.name: val}), + 'display': val} +FilterSpec.register(lambda f: True, AllValuesFilterSpec) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index af55587d23..ea7aeb490b 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -574,7 +574,7 @@ class ChangeList(object): filter_fields = [self.lookup_opts.get_field(field_name) \ for field_name in self.lookup_opts.admin.list_filter] for f in filter_fields: - spec = FilterSpec.create(f, request, self.params) + spec = FilterSpec.create(f, request, self.params, self.model) if spec and spec.has_output(): filter_specs.append(spec) return filter_specs, bool(filter_specs)