app/django/contrib/admin/filterspecs.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 FilterSpec encapsulates the logic for displaying filters in the Django admin.
       
     3 Filters are specified in models with the "list_filter" option.
       
     4 
       
     5 Each filter subclass knows how to display a filter for a field that passes a
       
     6 certain test -- e.g. being a DateField or ForeignKey.
       
     7 """
       
     8 
       
     9 from django.db import models
       
    10 from django.utils.encoding import smart_unicode, iri_to_uri
       
    11 from django.utils.translation import ugettext as _
       
    12 from django.utils.html import escape
       
    13 from django.utils.safestring import mark_safe
       
    14 import datetime
       
    15 
       
    16 class FilterSpec(object):
       
    17     filter_specs = []
       
    18     def __init__(self, f, request, params, model):
       
    19         self.field = f
       
    20         self.params = params
       
    21 
       
    22     def register(cls, test, factory):
       
    23         cls.filter_specs.append((test, factory))
       
    24     register = classmethod(register)
       
    25 
       
    26     def create(cls, f, request, params, model):
       
    27         for test, factory in cls.filter_specs:
       
    28             if test(f):
       
    29                 return factory(f, request, params, model)
       
    30     create = classmethod(create)
       
    31 
       
    32     def has_output(self):
       
    33         return True
       
    34 
       
    35     def choices(self, cl):
       
    36         raise NotImplementedError()
       
    37 
       
    38     def title(self):
       
    39         return self.field.verbose_name
       
    40 
       
    41     def output(self, cl):
       
    42         t = []
       
    43         if self.has_output():
       
    44             t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title()))
       
    45 
       
    46             for choice in self.choices(cl):
       
    47                 t.append(u'<li%s><a href="%s">%s</a></li>\n' % \
       
    48                     ((choice['selected'] and ' class="selected"' or ''),
       
    49                      iri_to_uri(choice['query_string']),
       
    50                      choice['display']))
       
    51             t.append('</ul>\n\n')
       
    52         return mark_safe("".join(t))
       
    53 
       
    54 class RelatedFilterSpec(FilterSpec):
       
    55     def __init__(self, f, request, params, model):
       
    56         super(RelatedFilterSpec, self).__init__(f, request, params, model)
       
    57         if isinstance(f, models.ManyToManyField):
       
    58             self.lookup_title = f.rel.to._meta.verbose_name
       
    59         else:
       
    60             self.lookup_title = f.verbose_name
       
    61         self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name)
       
    62         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
       
    63         self.lookup_choices = f.rel.to._default_manager.all()
       
    64 
       
    65     def has_output(self):
       
    66         return len(self.lookup_choices) > 1
       
    67 
       
    68     def title(self):
       
    69         return self.lookup_title
       
    70 
       
    71     def choices(self, cl):
       
    72         yield {'selected': self.lookup_val is None,
       
    73                'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
       
    74                'display': _('All')}
       
    75         for val in self.lookup_choices:
       
    76             pk_val = getattr(val, self.field.rel.to._meta.pk.attname)
       
    77             yield {'selected': self.lookup_val == smart_unicode(pk_val),
       
    78                    'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
       
    79                    'display': val}
       
    80 
       
    81 FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
       
    82 
       
    83 class ChoicesFilterSpec(FilterSpec):
       
    84     def __init__(self, f, request, params, model):
       
    85         super(ChoicesFilterSpec, self).__init__(f, request, params, model)
       
    86         self.lookup_kwarg = '%s__exact' % f.name
       
    87         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
       
    88 
       
    89     def choices(self, cl):
       
    90         yield {'selected': self.lookup_val is None,
       
    91                'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
       
    92                'display': _('All')}
       
    93         for k, v in self.field.choices:
       
    94             yield {'selected': smart_unicode(k) == self.lookup_val,
       
    95                     'query_string': cl.get_query_string({self.lookup_kwarg: k}),
       
    96                     'display': v}
       
    97 
       
    98 FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
       
    99 
       
   100 class DateFieldFilterSpec(FilterSpec):
       
   101     def __init__(self, f, request, params, model):
       
   102         super(DateFieldFilterSpec, self).__init__(f, request, params, model)
       
   103 
       
   104         self.field_generic = '%s__' % self.field.name
       
   105 
       
   106         self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)])
       
   107 
       
   108         today = datetime.date.today()
       
   109         one_week_ago = today - datetime.timedelta(days=7)
       
   110         today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
       
   111 
       
   112         self.links = (
       
   113             (_('Any date'), {}),
       
   114             (_('Today'), {'%s__year' % self.field.name: str(today.year),
       
   115                        '%s__month' % self.field.name: str(today.month),
       
   116                        '%s__day' % self.field.name: str(today.day)}),
       
   117             (_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'),
       
   118                              '%s__lte' % f.name: today_str}),
       
   119             (_('This month'), {'%s__year' % self.field.name: str(today.year),
       
   120                              '%s__month' % f.name: str(today.month)}),
       
   121             (_('This year'), {'%s__year' % self.field.name: str(today.year)})
       
   122         )
       
   123 
       
   124     def title(self):
       
   125         return self.field.verbose_name
       
   126 
       
   127     def choices(self, cl):
       
   128         for title, param_dict in self.links:
       
   129             yield {'selected': self.date_params == param_dict,
       
   130                    'query_string': cl.get_query_string(param_dict, [self.field_generic]),
       
   131                    'display': title}
       
   132 
       
   133 FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
       
   134 
       
   135 class BooleanFieldFilterSpec(FilterSpec):
       
   136     def __init__(self, f, request, params, model):
       
   137         super(BooleanFieldFilterSpec, self).__init__(f, request, params, model)
       
   138         self.lookup_kwarg = '%s__exact' % f.name
       
   139         self.lookup_kwarg2 = '%s__isnull' % f.name
       
   140         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
       
   141         self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
       
   142 
       
   143     def title(self):
       
   144         return self.field.verbose_name
       
   145 
       
   146     def choices(self, cl):
       
   147         for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):
       
   148             yield {'selected': self.lookup_val == v and not self.lookup_val2,
       
   149                    'query_string': cl.get_query_string({self.lookup_kwarg: v}, [self.lookup_kwarg2]),
       
   150                    'display': k}
       
   151         if isinstance(self.field, models.NullBooleanField):
       
   152             yield {'selected': self.lookup_val2 == 'True',
       
   153                    'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
       
   154                    'display': _('Unknown')}
       
   155 
       
   156 FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)
       
   157 
       
   158 # This should be registered last, because it's a last resort. For example,
       
   159 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
       
   160 # more appropriate, and the AllValuesFilterSpec won't get used for it.
       
   161 class AllValuesFilterSpec(FilterSpec):
       
   162     def __init__(self, f, request, params, model):
       
   163         super(AllValuesFilterSpec, self).__init__(f, request, params, model)
       
   164         self.lookup_val = request.GET.get(f.name, None)
       
   165         self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name)
       
   166 
       
   167     def title(self):
       
   168         return self.field.verbose_name
       
   169 
       
   170     def choices(self, cl):
       
   171         yield {'selected': self.lookup_val is None,
       
   172                'query_string': cl.get_query_string({}, [self.field.name]),
       
   173                'display': _('All')}
       
   174         for val in self.lookup_choices:
       
   175             val = smart_unicode(val[self.field.name])
       
   176             yield {'selected': self.lookup_val == val,
       
   177                    'query_string': cl.get_query_string({self.field.name: val}),
       
   178                    'display': val}
       
   179 FilterSpec.register(lambda f: True, AllValuesFilterSpec)