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) |
---|