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