recherche temporelle
[auf_rh_dae.git] / project / rh / change_list.py
CommitLineData
a17e2236
OL
1import time, datetime
2from django.core.exceptions import SuspiciousOperation
3from django.conf import settings
4from django.contrib.admin.options import IncorrectLookupParameters
5from django.contrib.admin.views.main import ChangeList as DjangoChangeList
6from 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
7from django.utils.encoding import force_unicode, smart_str
8from django.utils.http import urlencode
9from django.db.models import Q
10
11
12KEY_ANNEE = 'annee'
13KEY_DATE_DEBUT = 'date_debut'
14KEY_DATE_FIN = 'date_fin'
15KEY_STATUT = 'statut'
16
17STATUT_ACTIF = 'Actif'
18STATUT_INACTIF = 'Inactif'
19STATUT_FUTUR = 'Futur'
20STATUT_INCONNU = 'Inconnu'
21
22class 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)