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