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