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