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