1 # -*- coding: utf-8 -*-
6 from django
.db
.models
import Q
7 from django
.conf
import settings
8 from django
.contrib
.admin
.options
import IncorrectLookupParameters
9 from django
.contrib
.admin
.views
.main
import ChangeList
as DjangoChangeList
10 from django
.contrib
.admin
.views
.main
import ALL_VAR
, ORDER_VAR
, \
11 ORDER_TYPE_VAR
, SEARCH_VAR
, TO_FIELD_VAR
, IS_POPUP_VAR
, field_needs_distinct
12 from django
.core
.exceptions
import SuspiciousOperation
13 from django
.db
import models
14 from django
.utils
.encoding
import smart_str
18 KEY_DATE_DEBUT
= 'date_debut'
19 KEY_DATE_FIN
= 'date_fin'
22 STATUT_ACTIF
= 'Actif'
23 STATUT_INACTIF
= 'Inactif'
24 STATUT_FUTUR
= 'Futur'
25 STATUT_INCONNU
= 'Inconnu'
28 class RechercheTemporelle(object):
31 prefixe_recherche_temporelle
= ""
32 lookup_recherche_temporelle
= {}
34 internal_fields
= (KEY_ANNEE
, KEY_DATE_DEBUT
, KEY_DATE_FIN
, KEY_STATUT
, )
35 STATUT_CHOICES
= (STATUT_ACTIF
, STATUT_INACTIF
, STATUT_FUTUR
, STATUT_INCONNU
)
37 def __init__(self
, params
=params
, model
=model
):
42 klass
= getattr(self
, "model_admin", self
)
43 return getattr(klass
, 'prefixe_recherche_temporelle', "")
46 prefix
= self
.get_prefix()
47 date_debut
= "%s%s" % (prefix
, KEY_DATE_DEBUT
)
48 date_fin
= "%s%s" % (prefix
, KEY_DATE_FIN
)
49 annees
= self
.model
.objects
.all().values(date_debut
, date_fin
)
50 annees_debut
= [d
[date_debut
].year
for d
in annees
if d
[date_debut
] is not None]
51 annees_fin
= [d
[date_fin
].year
for d
in annees
if d
[date_fin
] is not None]
52 annees
= set(annees_debut
+ annees_fin
)
54 annees
.sort(reverse
=True)
57 def get_q_actifs(self
):
58 qs
= self
.model
.objects
.get_query_set()
62 def get_q_inactifs(self
):
63 qs
= self
.model
.objects
.get_query_set()
64 q
= qs
.get_q_inactifs()
67 def get_q_futurs(self
):
68 qs
= self
.model
.objects
.get_query_set()
72 def get_q_inconnus(self
):
73 qs
= self
.model
.objects
.get_query_set()
74 q
= qs
.get_q_inconnus()
77 def get_q_range(self
, prefix
, borne_gauche
=None, borne_droite
=None):
78 # on filtre si on a au moins une borne, car par défaut q_actifs est
79 # un filtre sur actif à l'instant présent
80 if borne_gauche
is not None and borne_droite
is not None:
81 qs
= self
.model
.objects
.get_query_set()
82 q
= qs
.get_q_actifs(borne_gauche
, borne_droite
)
87 def purge_params(self
, lookup_params
):
88 self
.lookup_recherche_temporelle
= {}
89 params
= lookup_params
.copy()
90 for key
, value
in lookup_params
.items():
91 # ignorer les param GET pour la recherche temporelle
92 if len([i
for i
in self
.internal_fields
if key
.endswith(i
)]) > 0:
94 self
.lookup_recherche_temporelle
[key
] = value
98 def get_q_temporel(self
, qs
):
100 prefix
= self
.get_prefix()
104 for k
, v
in self
.lookup_recherche_temporelle
.items():
106 if k
.endswith(KEY_ANNEE
):
107 borne_gauche
= "%s-01-01" % v
108 borne_droite
= "%s-12-31" % v
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
)
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
)
118 if k
.endswith(KEY_STATUT
):
119 aujourdhui
= datetime
.date
.today()
120 if v
== STATUT_ACTIF
:
121 q
= q
& self
.get_q_actifs()
122 elif v
== STATUT_INACTIF
:
123 q
= q
& self
.get_q_inactifs()
124 borne_droite
= aujourdhui
125 elif v
== STATUT_FUTUR
:
126 q
= q
& self
.get_q_futurs()
127 elif v
== STATUT_INCONNU
:
128 q
= q
& self
.get_q_inconnus()
129 q_range
= self
.get_q_range(prefix
, borne_gauche
, borne_droite
)
132 RechercheTemporelle
.get_query_string
= DjangoChangeList
.get_query_string
.im_func
135 class ChangeList(DjangoChangeList
, RechercheTemporelle
):
137 def __init__(self
, *args
, **kwargs
):
138 super(ChangeList
, self
).__init__(*args
, **kwargs
)
140 def get_query_set(self
):
144 qs
= self
.root_query_set
146 lookup_params
= self
.params
.copy() # a dictionary of the query string
147 for i
in (ALL_VAR
, ORDER_VAR
, ORDER_TYPE_VAR
, SEARCH_VAR
, IS_POPUP_VAR
, TO_FIELD_VAR
):
148 if i
in lookup_params
:
151 lookup_params
= self
.purge_params(lookup_params
)
152 for key
, value
in lookup_params
.items():
153 if not isinstance(key
, str):
154 # 'key' will be used as a keyword argument later, so Python
155 # requires it to be a string.
156 del lookup_params
[key
]
157 lookup_params
[smart_str(key
)] = value
160 # Check if it's a relationship that might return more than one
162 field_name
= key
.split('__', 1)[0]
164 f
= self
.lookup_opts
.get_field_by_name(field_name
)[0]
165 except models
.FieldDoesNotExist
:
166 raise IncorrectLookupParameters
167 use_distinct
= field_needs_distinct(f
)
169 # Commenting out, not sure why we try to .split() a
170 # iterable / queryset, but it doesn't seem to work. Hope
171 # i'm not breaking something.
172 # if key ends with __in, split parameter into separate
174 # if key.endswith('__in'):
175 # lookup_params[key] = value
177 # if key ends with __isnull, special case '' and false
178 if key
.endswith('__isnull'):
179 if value
.lower() in ('', 'false'):
183 lookup_params
[key
] = value
185 if not self
.model_admin
.lookup_allowed(key
, value
):
186 raise SuspiciousOperation(
187 "Filtering by %s not allowed" % key
190 q_temporel
= self
.get_q_temporel(qs
)
191 q
= Q(**lookup_params
) & q_temporel
193 # Apply lookup parameters from the query string.
196 # Naked except! Because we don't have any other way of validating "params".
197 # They might be invalid if the keyword arguments are incorrect, or if the
198 # values are not in the correct type, so we might get FieldError, ValueError,
199 # ValicationError, or ? from a custom field that raises yet something else
200 # when handed impossible data.
202 raise IncorrectLookupParameters
205 # Use select_related() if one of the list_display options is a field
206 # with a relationship and the provided queryset doesn't already have
207 # select_related defined.
208 if not qs
.query
.select_related
:
209 if self
.list_select_related
:
210 qs
= qs
.select_related()
212 for field_name
in self
.list_display
:
214 f
= self
.lookup_opts
.get_field(field_name
)
215 except models
.FieldDoesNotExist
:
218 if isinstance(f
.rel
, models
.ManyToOneRel
):
219 qs
= qs
.select_related()
224 qs
= qs
.order_by('%s%s' % ((self
.order_type
== 'desc' and '-' or ''), self
.order_field
))
226 # Apply keyword searches.
227 def construct_search(field_name
):
228 if field_name
.startswith('^'):
229 return "%s__istartswith" % field_name
[1:]
230 elif field_name
.startswith('='):
231 return "%s__iexact" % field_name
[1:]
232 elif field_name
.startswith('@'):
233 return "%s__search" % field_name
[1:]
235 return "%s__icontains" % field_name
237 if self
.search_fields
and self
.query
:
238 orm_lookups
= [construct_search(str(search_field
))
239 for search_field
in self
.search_fields
]
240 for bit
in self
.query
.split():
241 or_queries
= [models
.Q(**{orm_lookup
: bit
})
242 for orm_lookup
in orm_lookups
]
243 qs
= qs
.filter(reduce(operator
.or_
, or_queries
))
245 for search_spec
in orm_lookups
:
246 field_name
= search_spec
.split('__', 1)[0]
247 f
= self
.lookup_opts
.get_field_by_name(field_name
)[0]
248 if field_needs_distinct(f
):