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