Hello site
[auf_framonde.git] / eggs / Django-1.4.5-py2.7.egg / django / contrib / admin / filters.py
1 """
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)