2 from django
.db
.models
import Q
3 from django
.conf
import settings
4 from django
.contrib
.admin
.filterspecs
import FilterSpec
5 from django
.contrib
.admin
.options
import IncorrectLookupParameters
6 from django
.contrib
.admin
.util
import quote
, get_fields_from_path
7 from django
.contrib
.admin
.views
.main
import ChangeList
as DjangoChangeList
8 from 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
9 from django
.core
.exceptions
import SuspiciousOperation
10 from django
.core
.paginator
import InvalidPage
11 from django
.db
import models
12 from django
.utils
.encoding
import force_unicode
, smart_str
13 from django
.utils
.translation
import ugettext
, ugettext_lazy
14 from django
.utils
.http
import urlencode
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
):
40 #self.params = dict(request.GET.items())
43 klass
= getattr(self
, "model_admin", self
)
44 return getattr(klass
, 'prefixe_recherche_temporelle', "")
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
)
55 annees
.sort(reverse
=True)
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
)
63 def get_q_range(self
, prefix
, borne_gauche
=None, borne_droite
=None):
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
})
76 if borne_droite
is None:
77 q_range
= date_debut_strict_superieure_ou_egale_a_borne_gauche
79 if borne_gauche
is None:
80 q_range
= date_fin_strict_inferieure_ou_egale_a_borne_droite
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
)
88 if borne_droite
is None and borne_gauche
is None:
93 def purge_params(self
, lookup_params
):
94 self
.lookup_recherche_temporelle
= {}
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:
100 self
.lookup_recherche_temporelle
[key
] = value
104 def filter_temporel(self
, qs
):
106 prefix
= self
.get_prefix()
109 for k
, v
in self
.lookup_recherche_temporelle
.items():
111 if k
.endswith(KEY_ANNEE
):
112 borne_gauche
= "%s-01-01" % v
113 borne_droite
= "%s-12-31" % v
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
)
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
)
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
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
)
139 q_range
= self
.get_q_range(prefix
, borne_gauche
, borne_droite
)
140 qs
= qs
.filter(q
& q_range
).distinct()
143 RechercheTemporelle
.get_query_string
= DjangoChangeList
.get_query_string
.im_func
146 class ChangeList(DjangoChangeList
, RechercheTemporelle
):
148 def __init__(self
, *args
, **kwargs
):
149 super(ChangeList
, self
).__init__(*args
, **kwargs
)
151 def get_query_set(self
):
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
:
161 lookup_params
= self
.purge_params(lookup_params
)
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
170 # Check if it's a relationship that might return more than one
172 field_name
= key
.split('__', 1)[0]
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
)
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
184 # if key ends with __isnull, special case '' and false
185 if key
.endswith('__isnull'):
186 if value
.lower() in ('', 'false'):
190 lookup_params
[key
] = value
192 if not self
.model_admin
.lookup_allowed(key
, value
):
193 raise SuspiciousOperation(
194 "Filtering by %s not allowed" % key
197 # Apply lookup parameters from the query string.
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.
206 raise IncorrectLookupParameters
208 qs
= self
.filter_temporel(qs
)
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()
217 for field_name
in self
.list_display
:
219 f
= self
.lookup_opts
.get_field(field_name
)
220 except models
.FieldDoesNotExist
:
223 if isinstance(f
.rel
, models
.ManyToOneRel
):
224 qs
= qs
.select_related()
229 qs
= qs
.order_by('%s%s' % ((self
.order_type
== 'desc' and '-' or ''), self
.order_field
))
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:]
240 return "%s__icontains" % field_name
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
))
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
):