root / env / lib / python2.7 / site-packages / django / contrib / admin / filters.py @ 1a305335
History | View | Annotate | Download (15.9 KB)
1 | 1a305335 | officers | """
|
---|---|---|---|
2 | This 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 | import datetime |
||
9 | |||
10 | from django.db import models |
||
11 | from django.core.exceptions import ImproperlyConfigured, ValidationError |
||
12 | from django.utils.encoding import smart_unicode, force_unicode |
||
13 | from django.utils.translation import ugettext_lazy as _ |
||
14 | from django.utils import timezone |
||
15 | from django.contrib.admin.util import (get_model_from_relation, |
||
16 | reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) |
||
17 | from django.contrib.admin.options import IncorrectLookupParameters |
||
18 | |||
19 | class ListFilter(object): |
||
20 | title = None # Human-readable title to appear in the right sidebar. |
||
21 | template = 'admin/filter.html'
|
||
22 | |||
23 | def __init__(self, request, params, model, model_admin): |
||
24 | # This dictionary will eventually contain the request's query string
|
||
25 | # parameters actually used by this filter.
|
||
26 | self.used_parameters = {}
|
||
27 | if self.title is None: |
||
28 | raise ImproperlyConfigured(
|
||
29 | "The list filter '%s' does not specify "
|
||
30 | "a 'title'." % self.__class__.__name__) |
||
31 | |||
32 | def has_output(self): |
||
33 | """
|
||
34 | Returns True if some choices would be output for this filter.
|
||
35 | """
|
||
36 | raise NotImplementedError |
||
37 | |||
38 | def choices(self, cl): |
||
39 | """
|
||
40 | Returns choices ready to be output in the template.
|
||
41 | """
|
||
42 | raise NotImplementedError |
||
43 | |||
44 | def queryset(self, request, queryset): |
||
45 | """
|
||
46 | Returns the filtered queryset.
|
||
47 | """
|
||
48 | raise NotImplementedError |
||
49 | |||
50 | def expected_parameters(self): |
||
51 | """
|
||
52 | Returns the list of parameter names that are expected from the
|
||
53 | request's query string and that will be used by this filter.
|
||
54 | """
|
||
55 | raise NotImplementedError |
||
56 | |||
57 | |||
58 | class SimpleListFilter(ListFilter): |
||
59 | # The parameter that should be used in the query string for that filter.
|
||
60 | parameter_name = None
|
||
61 | |||
62 | def __init__(self, request, params, model, model_admin): |
||
63 | super(SimpleListFilter, self).__init__( |
||
64 | request, params, model, model_admin) |
||
65 | if self.parameter_name is None: |
||
66 | raise ImproperlyConfigured(
|
||
67 | "The list filter '%s' does not specify "
|
||
68 | "a 'parameter_name'." % self.__class__.__name__) |
||
69 | lookup_choices = self.lookups(request, model_admin)
|
||
70 | if lookup_choices is None: |
||
71 | lookup_choices = () |
||
72 | self.lookup_choices = list(lookup_choices) |
||
73 | if self.parameter_name in params: |
||
74 | value = params.pop(self.parameter_name)
|
||
75 | self.used_parameters[self.parameter_name] = value |
||
76 | |||
77 | def has_output(self): |
||
78 | return len(self.lookup_choices) > 0 |
||
79 | |||
80 | def value(self): |
||
81 | """
|
||
82 | Returns the value (in string format) provided in the request's
|
||
83 | query string for this filter, if any. If the value wasn't provided then
|
||
84 | returns None.
|
||
85 | """
|
||
86 | return self.used_parameters.get(self.parameter_name, None) |
||
87 | |||
88 | def lookups(self, request, model_admin): |
||
89 | """
|
||
90 | Must be overriden to return a list of tuples (value, verbose value)
|
||
91 | """
|
||
92 | raise NotImplementedError |
||
93 | |||
94 | def expected_parameters(self): |
||
95 | return [self.parameter_name] |
||
96 | |||
97 | def choices(self, cl): |
||
98 | yield {
|
||
99 | 'selected': self.value() is None, |
||
100 | 'query_string': cl.get_query_string({}, [self.parameter_name]), |
||
101 | 'display': _('All'), |
||
102 | } |
||
103 | for lookup, title in self.lookup_choices: |
||
104 | yield {
|
||
105 | 'selected': self.value() == force_unicode(lookup), |
||
106 | 'query_string': cl.get_query_string({
|
||
107 | self.parameter_name: lookup,
|
||
108 | }, []), |
||
109 | 'display': title,
|
||
110 | } |
||
111 | |||
112 | |||
113 | class FieldListFilter(ListFilter): |
||
114 | _field_list_filters = [] |
||
115 | _take_priority_index = 0
|
||
116 | |||
117 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
118 | self.field = field
|
||
119 | self.field_path = field_path
|
||
120 | self.title = getattr(field, 'verbose_name', field_path) |
||
121 | super(FieldListFilter, self).__init__( |
||
122 | request, params, model, model_admin) |
||
123 | for p in self.expected_parameters(): |
||
124 | if p in params: |
||
125 | value = params.pop(p) |
||
126 | self.used_parameters[p] = prepare_lookup_value(p, value)
|
||
127 | |||
128 | def has_output(self): |
||
129 | return True |
||
130 | |||
131 | def queryset(self, request, queryset): |
||
132 | try:
|
||
133 | return queryset.filter(**self.used_parameters) |
||
134 | except ValidationError, e:
|
||
135 | raise IncorrectLookupParameters(e)
|
||
136 | |||
137 | @classmethod
|
||
138 | def register(cls, test, list_filter_class, take_priority=False): |
||
139 | if take_priority:
|
||
140 | # This is to allow overriding the default filters for certain types
|
||
141 | # of fields with some custom filters. The first found in the list
|
||
142 | # is used in priority.
|
||
143 | cls._field_list_filters.insert( |
||
144 | cls._take_priority_index, (test, list_filter_class)) |
||
145 | cls._take_priority_index += 1
|
||
146 | else:
|
||
147 | cls._field_list_filters.append((test, list_filter_class)) |
||
148 | |||
149 | @classmethod
|
||
150 | def create(cls, field, request, params, model, model_admin, field_path): |
||
151 | for test, list_filter_class in cls._field_list_filters: |
||
152 | if not test(field): |
||
153 | continue
|
||
154 | return list_filter_class(field, request, params,
|
||
155 | model, model_admin, field_path=field_path) |
||
156 | |||
157 | |||
158 | class RelatedFieldListFilter(FieldListFilter): |
||
159 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
160 | other_model = get_model_from_relation(field) |
||
161 | if hasattr(field, 'rel'): |
||
162 | rel_name = field.rel.get_related_field().name |
||
163 | else:
|
||
164 | rel_name = other_model._meta.pk.name |
||
165 | self.lookup_kwarg = '%s__%s__exact' % (field_path, rel_name) |
||
166 | self.lookup_kwarg_isnull = '%s__isnull' % field_path |
||
167 | self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||
168 | self.lookup_val_isnull = request.GET.get(
|
||
169 | self.lookup_kwarg_isnull, None) |
||
170 | self.lookup_choices = field.get_choices(include_blank=False) |
||
171 | super(RelatedFieldListFilter, self).__init__( |
||
172 | field, request, params, model, model_admin, field_path) |
||
173 | if hasattr(field, 'verbose_name'): |
||
174 | self.lookup_title = field.verbose_name
|
||
175 | else:
|
||
176 | self.lookup_title = other_model._meta.verbose_name
|
||
177 | self.title = self.lookup_title |
||
178 | |||
179 | def has_output(self): |
||
180 | if (isinstance(self.field, models.related.RelatedObject) |
||
181 | and self.field.field.null or hasattr(self.field, 'rel') |
||
182 | and self.field.null): |
||
183 | extra = 1
|
||
184 | else:
|
||
185 | extra = 0
|
||
186 | return len(self.lookup_choices) + extra > 1 |
||
187 | |||
188 | def expected_parameters(self): |
||
189 | return [self.lookup_kwarg, self.lookup_kwarg_isnull] |
||
190 | |||
191 | def choices(self, cl): |
||
192 | from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE |
||
193 | yield {
|
||
194 | 'selected': self.lookup_val is None and not self.lookup_val_isnull, |
||
195 | 'query_string': cl.get_query_string({},
|
||
196 | [self.lookup_kwarg, self.lookup_kwarg_isnull]), |
||
197 | 'display': _('All'), |
||
198 | } |
||
199 | for pk_val, val in self.lookup_choices: |
||
200 | yield {
|
||
201 | 'selected': self.lookup_val == smart_unicode(pk_val), |
||
202 | 'query_string': cl.get_query_string({
|
||
203 | self.lookup_kwarg: pk_val,
|
||
204 | }, [self.lookup_kwarg_isnull]),
|
||
205 | 'display': val,
|
||
206 | } |
||
207 | if (isinstance(self.field, models.related.RelatedObject) |
||
208 | and self.field.field.null or hasattr(self.field, 'rel') |
||
209 | and self.field.null): |
||
210 | yield {
|
||
211 | 'selected': bool(self.lookup_val_isnull), |
||
212 | 'query_string': cl.get_query_string({
|
||
213 | self.lookup_kwarg_isnull: 'True', |
||
214 | }, [self.lookup_kwarg]),
|
||
215 | 'display': EMPTY_CHANGELIST_VALUE,
|
||
216 | } |
||
217 | |||
218 | FieldListFilter.register(lambda f: (
|
||
219 | hasattr(f, 'rel') and bool(f.rel) or |
||
220 | isinstance(f, models.related.RelatedObject)), RelatedFieldListFilter)
|
||
221 | |||
222 | |||
223 | class BooleanFieldListFilter(FieldListFilter): |
||
224 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
225 | self.lookup_kwarg = '%s__exact' % field_path |
||
226 | self.lookup_kwarg2 = '%s__isnull' % field_path |
||
227 | self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||
228 | self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) |
||
229 | super(BooleanFieldListFilter, self).__init__(field, |
||
230 | request, params, model, model_admin, field_path) |
||
231 | |||
232 | def expected_parameters(self): |
||
233 | return [self.lookup_kwarg, self.lookup_kwarg2] |
||
234 | |||
235 | def choices(self, cl): |
||
236 | for lookup, title in ( |
||
237 | (None, _('All')), |
||
238 | ('1', _('Yes')), |
||
239 | ('0', _('No'))): |
||
240 | yield {
|
||
241 | 'selected': self.lookup_val == lookup and not self.lookup_val2, |
||
242 | 'query_string': cl.get_query_string({
|
||
243 | self.lookup_kwarg: lookup,
|
||
244 | }, [self.lookup_kwarg2]),
|
||
245 | 'display': title,
|
||
246 | } |
||
247 | if isinstance(self.field, models.NullBooleanField): |
||
248 | yield {
|
||
249 | 'selected': self.lookup_val2 == 'True', |
||
250 | 'query_string': cl.get_query_string({
|
||
251 | self.lookup_kwarg2: 'True', |
||
252 | }, [self.lookup_kwarg]),
|
||
253 | 'display': _('Unknown'), |
||
254 | } |
||
255 | |||
256 | FieldListFilter.register(lambda f: isinstance(f, |
||
257 | (models.BooleanField, models.NullBooleanField)), BooleanFieldListFilter) |
||
258 | |||
259 | |||
260 | class ChoicesFieldListFilter(FieldListFilter): |
||
261 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
262 | self.lookup_kwarg = '%s__exact' % field_path |
||
263 | self.lookup_val = request.GET.get(self.lookup_kwarg) |
||
264 | super(ChoicesFieldListFilter, self).__init__( |
||
265 | field, request, params, model, model_admin, field_path) |
||
266 | |||
267 | def expected_parameters(self): |
||
268 | return [self.lookup_kwarg] |
||
269 | |||
270 | def choices(self, cl): |
||
271 | yield {
|
||
272 | 'selected': self.lookup_val is None, |
||
273 | 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), |
||
274 | 'display': _('All') |
||
275 | } |
||
276 | for lookup, title in self.field.flatchoices: |
||
277 | yield {
|
||
278 | 'selected': smart_unicode(lookup) == self.lookup_val, |
||
279 | 'query_string': cl.get_query_string({
|
||
280 | self.lookup_kwarg: lookup}),
|
||
281 | 'display': title,
|
||
282 | } |
||
283 | |||
284 | FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter) |
||
285 | |||
286 | |||
287 | class DateFieldListFilter(FieldListFilter): |
||
288 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
289 | self.field_generic = '%s__' % field_path |
||
290 | self.date_params = dict([(k, v) for k, v in params.items() |
||
291 | if k.startswith(self.field_generic)]) |
||
292 | |||
293 | now = timezone.now() |
||
294 | # When time zone support is enabled, convert "now" to the user's time
|
||
295 | # zone so Django's definition of "Today" matches what the user expects.
|
||
296 | if now.tzinfo is not None: |
||
297 | current_tz = timezone.get_current_timezone() |
||
298 | now = now.astimezone(current_tz) |
||
299 | if hasattr(current_tz, 'normalize'): |
||
300 | # available for pytz time zones
|
||
301 | now = current_tz.normalize(now) |
||
302 | |||
303 | if isinstance(field, models.DateTimeField): |
||
304 | today = now.replace(hour=0, minute=0, second=0, microsecond=0) |
||
305 | else: # field is a models.DateField |
||
306 | today = now.date() |
||
307 | tomorrow = today + datetime.timedelta(days=1)
|
||
308 | |||
309 | self.lookup_kwarg_since = '%s__gte' % field_path |
||
310 | self.lookup_kwarg_until = '%s__lt' % field_path |
||
311 | self.links = (
|
||
312 | (_('Any date'), {}),
|
||
313 | (_('Today'), {
|
||
314 | self.lookup_kwarg_since: str(today), |
||
315 | self.lookup_kwarg_until: str(tomorrow), |
||
316 | }), |
||
317 | (_('Past 7 days'), {
|
||
318 | self.lookup_kwarg_since: str(today - datetime.timedelta(days=7)), |
||
319 | self.lookup_kwarg_until: str(tomorrow), |
||
320 | }), |
||
321 | (_('This month'), {
|
||
322 | self.lookup_kwarg_since: str(today.replace(day=1)), |
||
323 | self.lookup_kwarg_until: str(tomorrow), |
||
324 | }), |
||
325 | (_('This year'), {
|
||
326 | self.lookup_kwarg_since: str(today.replace(month=1, day=1)), |
||
327 | self.lookup_kwarg_until: str(tomorrow), |
||
328 | }), |
||
329 | ) |
||
330 | super(DateFieldListFilter, self).__init__( |
||
331 | field, request, params, model, model_admin, field_path) |
||
332 | |||
333 | def expected_parameters(self): |
||
334 | return [self.lookup_kwarg_since, self.lookup_kwarg_until] |
||
335 | |||
336 | def choices(self, cl): |
||
337 | for title, param_dict in self.links: |
||
338 | yield {
|
||
339 | 'selected': self.date_params == param_dict, |
||
340 | 'query_string': cl.get_query_string(
|
||
341 | param_dict, [self.field_generic]),
|
||
342 | 'display': title,
|
||
343 | } |
||
344 | |||
345 | FieldListFilter.register( |
||
346 | lambda f: isinstance(f, models.DateField), DateFieldListFilter) |
||
347 | |||
348 | |||
349 | # This should be registered last, because it's a last resort. For example,
|
||
350 | # if a field is eligible to use the BooleanFieldListFilter, that'd be much
|
||
351 | # more appropriate, and the AllValuesFieldListFilter won't get used for it.
|
||
352 | class AllValuesFieldListFilter(FieldListFilter): |
||
353 | def __init__(self, field, request, params, model, model_admin, field_path): |
||
354 | self.lookup_kwarg = field_path
|
||
355 | self.lookup_kwarg_isnull = '%s__isnull' % field_path |
||
356 | self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||
357 | self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull, |
||
358 | None)
|
||
359 | parent_model, reverse_path = reverse_field_path(model, field_path) |
||
360 | queryset = parent_model._default_manager.all() |
||
361 | # optional feature: limit choices base on existing relationships
|
||
362 | # queryset = queryset.complex_filter(
|
||
363 | # {'%s__isnull' % reverse_path: False})
|
||
364 | limit_choices_to = get_limit_choices_to_from_path(model, field_path) |
||
365 | queryset = queryset.filter(limit_choices_to) |
||
366 | |||
367 | self.lookup_choices = (queryset
|
||
368 | .distinct() |
||
369 | .order_by(field.name) |
||
370 | .values_list(field.name, flat=True))
|
||
371 | super(AllValuesFieldListFilter, self).__init__( |
||
372 | field, request, params, model, model_admin, field_path) |
||
373 | |||
374 | def expected_parameters(self): |
||
375 | return [self.lookup_kwarg, self.lookup_kwarg_isnull] |
||
376 | |||
377 | def choices(self, cl): |
||
378 | from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE |
||
379 | yield {
|
||
380 | 'selected': (self.lookup_val is None |
||
381 | and self.lookup_val_isnull is None), |
||
382 | 'query_string': cl.get_query_string({},
|
||
383 | [self.lookup_kwarg, self.lookup_kwarg_isnull]), |
||
384 | 'display': _('All'), |
||
385 | } |
||
386 | include_none = False
|
||
387 | for val in self.lookup_choices: |
||
388 | if val is None: |
||
389 | include_none = True
|
||
390 | continue
|
||
391 | val = smart_unicode(val) |
||
392 | yield {
|
||
393 | 'selected': self.lookup_val == val, |
||
394 | 'query_string': cl.get_query_string({
|
||
395 | self.lookup_kwarg: val,
|
||
396 | }, [self.lookup_kwarg_isnull]),
|
||
397 | 'display': val,
|
||
398 | } |
||
399 | if include_none:
|
||
400 | yield {
|
||
401 | 'selected': bool(self.lookup_val_isnull), |
||
402 | 'query_string': cl.get_query_string({
|
||
403 | self.lookup_kwarg_isnull: 'True', |
||
404 | }, [self.lookup_kwarg]),
|
||
405 | 'display': EMPTY_CHANGELIST_VALUE,
|
||
406 | } |
||
407 | |||
408 | FieldListFilter.register(lambda f: True, AllValuesFieldListFilter) |