Commit | Line | Data |
---|---|---|
a17e2236 OL |
1 | import time, datetime |
2 | from django.core.exceptions import SuspiciousOperation | |
3 | from django.conf import settings | |
4 | from django.contrib.admin.options import IncorrectLookupParameters | |
5 | from django.contrib.admin.views.main import ChangeList as DjangoChangeList | |
6 | from django.contrib.admin.views.main import ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR, TO_FIELD_VAR, IS_POPUP_VAR, ERROR_FLAG, field_needs_distinct | |
7 | from django.utils.encoding import force_unicode, smart_str | |
8 | from django.utils.http import urlencode | |
9 | from django.db.models import Q | |
10 | ||
11 | ||
12 | KEY_ANNEE = 'annee' | |
13 | KEY_DATE_DEBUT = 'date_debut' | |
14 | KEY_DATE_FIN = 'date_fin' | |
15 | KEY_STATUT = 'statut' | |
16 | ||
17 | STATUT_ACTIF = 'Actif' | |
18 | STATUT_INACTIF = 'Inactif' | |
19 | STATUT_FUTUR = 'Futur' | |
20 | STATUT_INCONNU = 'Inconnu' | |
21 | ||
22 | class ChangeList(DjangoChangeList): | |
23 | ||
24 | internal_fields = (KEY_ANNEE, KEY_DATE_DEBUT, KEY_DATE_FIN, KEY_STATUT, ) | |
25 | STATUT_CHOICES = ('', STATUT_ACTIF, STATUT_INACTIF, STATUT_FUTUR, STATUT_INCONNU ) | |
26 | ||
27 | def __init__(self, *args, **kwargs): | |
28 | super(ChangeList, self).__init__(*args, **kwargs) | |
29 | ||
30 | def get_prefix(self): | |
31 | return getattr(self.model_admin, 'prefixe_recherche_temporelle', "") | |
32 | ||
33 | def get_annees(self): | |
34 | prefix = self.get_prefix() | |
35 | date_debut = "%s%s" % (prefix, KEY_DATE_DEBUT) | |
36 | date_fin = "%s%s" % (prefix, KEY_DATE_FIN) | |
37 | annees = self.model.objects.all().values(date_debut, date_fin) | |
38 | annees_debut = [d[date_debut].year for d in annees if d[date_debut] is not None] | |
39 | annees_fin = [d[date_fin].year for d in annees if d[date_fin] is not None] | |
40 | annees = set(annees_debut + annees_fin) | |
41 | annees = list(annees) | |
42 | annees.sort(reverse=True) | |
43 | return annees | |
44 | ||
45 | ||
46 | def get_q_inconnu(self, prefix): | |
47 | date_debut_nulle = Q(**{"%s%s__isnull" % (prefix, KEY_DATE_DEBUT) : True}) | |
48 | date_fin_nulle = Q(**{"%s%s__isnull" % (prefix, KEY_DATE_FIN) : True}) | |
49 | return Q(date_debut_nulle & date_fin_nulle) | |
50 | ||
51 | def get_q_range(self, prefix, borne_gauche=None, borne_droite=None): | |
52 | ||
53 | date_debut_nulle = Q(**{"%s%s__isnull" % (prefix, KEY_DATE_DEBUT) : True}) | |
54 | date_fin_nulle = Q(**{"%s%s__isnull" % (prefix, KEY_DATE_FIN) : True}) | |
55 | date_debut_superieure_ou_egale_a_borne_gauche = Q(**{"%s%s__gte" % (prefix, KEY_DATE_DEBUT) : borne_gauche}) | |
56 | date_debut_strict_superieure_ou_egale_a_borne_gauche = Q(**{"%s%s__gt" % (prefix, KEY_DATE_DEBUT) : borne_gauche}) | |
57 | date_debut_inferieure_ou_egale_a_borne_gauche = Q(**{"%s%s__lte" % (prefix, KEY_DATE_DEBUT) : borne_gauche}) | |
58 | date_fin_superieure_ou_egale_a_borne_gauche = Q(**{"%s%s__gte" % (prefix, KEY_DATE_FIN) : borne_gauche}) | |
59 | date_fin_inferieure_ou_egale_a_borne_droite = Q(**{"%s%s__lte" % (prefix, KEY_DATE_FIN) : borne_droite}) | |
60 | date_fin_strict_inferieure_ou_egale_a_borne_droite = Q(**{"%s%s__lt" % (prefix, KEY_DATE_FIN) : borne_droite}) | |
61 | date_debut_inferieure_ou_egale_a_borne_droite = Q(**{"%s%s__lte" % (prefix, KEY_DATE_DEBUT) : borne_droite}) | |
62 | date_fin_superieure_ou_egale_a_borne_droite = Q(**{"%s%s__gte" % (prefix, KEY_DATE_FIN) : borne_droite}) | |
63 | ||
64 | if borne_droite is None: | |
65 | q_range = date_debut_strict_superieure_ou_egale_a_borne_gauche | |
66 | ||
67 | if borne_gauche is None: | |
68 | q_range = date_fin_strict_inferieure_ou_egale_a_borne_droite | |
69 | ||
70 | if borne_droite is not None and borne_gauche is not None: | |
71 | q_range = (date_debut_superieure_ou_egale_a_borne_gauche & date_fin_inferieure_ou_egale_a_borne_droite) | \ | |
72 | ((date_debut_inferieure_ou_egale_a_borne_gauche | date_debut_nulle) & date_fin_superieure_ou_egale_a_borne_gauche & date_fin_inferieure_ou_egale_a_borne_droite) | \ | |
73 | ((date_fin_superieure_ou_egale_a_borne_droite | date_fin_nulle) & date_debut_inferieure_ou_egale_a_borne_droite) | \ | |
74 | (date_debut_inferieure_ou_egale_a_borne_gauche & date_fin_superieure_ou_egale_a_borne_droite) | |
75 | ||
76 | if borne_droite is None and borne_gauche is None: | |
77 | q_range = Q() | |
78 | ||
79 | return q_range | |
80 | ||
81 | def get_query_set(self): | |
82 | ||
83 | lookup_recherche_temporelle = {} | |
84 | use_distinct = False | |
85 | ||
86 | qs = self.root_query_set | |
87 | lookup_params = self.params.copy() # a dictionary of the query string | |
88 | for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR): | |
89 | if i in lookup_params: | |
90 | del lookup_params[i] | |
91 | for key, value in lookup_params.items(): | |
92 | if not isinstance(key, str): | |
93 | # 'key' will be used as a keyword argument later, so Python | |
94 | # requires it to be a string. | |
95 | del lookup_params[key] | |
96 | lookup_params[smart_str(key)] = value | |
97 | ||
98 | # ignorer les param GET pour la recherche temporelle | |
99 | if len([i for i in self.internal_fields if key.endswith(i)]) > 0: | |
100 | del lookup_params[key] | |
101 | lookup_recherche_temporelle[key] = value | |
102 | continue | |
103 | ||
104 | if not use_distinct: | |
105 | # Check if it's a relationship that might return more than one | |
106 | # instance | |
107 | field_name = key.split('__', 1)[0] | |
108 | try: | |
109 | f = self.lookup_opts.get_field_by_name(field_name)[0] | |
110 | except models.FieldDoesNotExist: | |
111 | raise IncorrectLookupParameters | |
112 | use_distinct = field_needs_distinct(f) | |
113 | ||
114 | # if key ends with __in, split parameter into separate values | |
115 | if key.endswith('__in'): | |
116 | value = value.split(',') | |
117 | lookup_params[key] = value | |
118 | ||
119 | # if key ends with __isnull, special case '' and false | |
120 | if key.endswith('__isnull'): | |
121 | if value.lower() in ('', 'false'): | |
122 | value = False | |
123 | else: | |
124 | value = True | |
125 | lookup_params[key] = value | |
126 | ||
127 | if not self.model_admin.lookup_allowed(key, value): | |
128 | raise SuspiciousOperation( | |
129 | "Filtering by %s not allowed" % key | |
130 | ) | |
131 | ||
132 | # Apply lookup parameters from the query string. | |
133 | try: | |
134 | qs = qs.filter(**lookup_params) | |
135 | # Naked except! Because we don't have any other way of validating "params". | |
136 | # They might be invalid if the keyword arguments are incorrect, or if the | |
137 | # values are not in the correct type, so we might get FieldError, ValueError, | |
138 | # ValicationError, or ? from a custom field that raises yet something else | |
139 | # when handed impossible data. | |
140 | except: | |
141 | raise IncorrectLookupParameters | |
142 | ||
143 | q = Q() | |
144 | prefix = self.get_prefix() | |
145 | borne_gauche = None | |
146 | borne_droite = None | |
147 | for k, v in lookup_recherche_temporelle.items(): | |
148 | ||
149 | if k.endswith(KEY_ANNEE): | |
150 | borne_gauche = "%s-01-01" % v | |
151 | borne_droite = "%s-12-31" % v | |
152 | ||
153 | if k.endswith(KEY_DATE_DEBUT): | |
154 | date = time.strptime(v, settings.DATE_INPUT_FORMATS[0]) | |
155 | borne_gauche = time.strftime("%Y-%m-%d", date) | |
156 | ||
157 | if k.endswith(KEY_DATE_FIN): | |
158 | date = time.strptime(v, settings.DATE_INPUT_FORMATS[0]) | |
159 | borne_droite = time.strftime("%Y-%m-%d", date) | |
160 | ||
161 | if k.endswith(KEY_STATUT): | |
162 | aujourdhui = datetime.date.today() | |
163 | if v == STATUT_ACTIF: | |
164 | borne_gauche = aujourdhui | |
165 | borne_droite = aujourdhui | |
166 | elif v == STATUT_INACTIF: | |
167 | borne_droite = aujourdhui | |
168 | elif v == STATUT_FUTUR: | |
169 | borne_gauche = aujourdhui | |
170 | elif v == STATUT_INCONNU: | |
171 | q = q & self.get_q_inconnu(prefix) | |
172 | ||
173 | q_range = self.get_q_range(prefix, borne_gauche, borne_droite) | |
174 | qs = qs.filter(q & q_range) | |
175 | ||
176 | ||
177 | # Use select_related() if one of the list_display options is a field | |
178 | # with a relationship and the provided queryset doesn't already have | |
179 | # select_related defined. | |
180 | if not qs.query.select_related: | |
181 | if self.list_select_related: | |
182 | qs = qs.select_related() | |
183 | else: | |
184 | for field_name in self.list_display: | |
185 | try: | |
186 | f = self.lookup_opts.get_field(field_name) | |
187 | except models.FieldDoesNotExist: | |
188 | pass | |
189 | else: | |
190 | if isinstance(f.rel, models.ManyToOneRel): | |
191 | qs = qs.select_related() | |
192 | break | |
193 | ||
194 | # Set ordering. | |
195 | if self.order_field: | |
196 | qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field)) | |
197 | ||
198 | # Apply keyword searches. | |
199 | def construct_search(field_name): | |
200 | if field_name.startswith('^'): | |
201 | return "%s__istartswith" % field_name[1:] | |
202 | elif field_name.startswith('='): | |
203 | return "%s__iexact" % field_name[1:] | |
204 | elif field_name.startswith('@'): | |
205 | return "%s__search" % field_name[1:] | |
206 | else: | |
207 | return "%s__icontains" % field_name | |
208 | ||
209 | if self.search_fields and self.query: | |
210 | orm_lookups = [construct_search(str(search_field)) | |
211 | for search_field in self.search_fields] | |
212 | for bit in self.query.split(): | |
213 | or_queries = [models.Q(**{orm_lookup: bit}) | |
214 | for orm_lookup in orm_lookups] | |
215 | qs = qs.filter(reduce(operator.or_, or_queries)) | |
216 | if not use_distinct: | |
217 | for search_spec in orm_lookups: | |
218 | field_name = search_spec.split('__', 1)[0] | |
219 | f = self.lookup_opts.get_field_by_name(field_name)[0] | |
220 | if field_needs_distinct(f): | |
221 | use_distinct = True | |
222 | break | |
223 | ||
224 | if use_distinct: | |
225 | return qs.distinct() | |
226 | else: | |
227 | return qs | |
228 | ||
229 | def get_query_string(self, new_params=None, remove=None): | |
230 | if new_params is None: new_params = {} | |
231 | if remove is None: remove = [] | |
232 | p = self.params.copy() | |
233 | for r in remove: | |
234 | for k in p.keys(): | |
235 | if k.startswith(r): | |
236 | del p[k] | |
237 | for k, v in new_params.items(): | |
238 | if v is None: | |
239 | if k in p: | |
240 | del p[k] | |
241 | else: | |
242 | p[k] = v | |
243 | return '?%s' % urlencode(p) |