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
, request
, model
):
39 self
.params
= dict(request
.GET
.items())
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_inconnu(self
, prefix
):
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 return Q(date_debut_nulle
& date_fin_nulle
)
62 def get_q_range(self
, prefix
, borne_gauche
=None, borne_droite
=None):
64 date_debut_nulle
= Q(**{"%s%s__isnull" % (prefix
, KEY_DATE_DEBUT
) : True})
65 date_fin_nulle
= Q(**{"%s%s__isnull" % (prefix
, KEY_DATE_FIN
) : True})
66 date_debut_superieure_ou_egale_a_borne_gauche
= Q(**{"%s%s__gte" % (prefix
, KEY_DATE_DEBUT
) : borne_gauche
})
67 date_debut_strict_superieure_ou_egale_a_borne_gauche
= Q(**{"%s%s__gt" % (prefix
, KEY_DATE_DEBUT
) : borne_gauche
})
68 date_debut_inferieure_ou_egale_a_borne_gauche
= Q(**{"%s%s__lte" % (prefix
, KEY_DATE_DEBUT
) : borne_gauche
})
69 date_fin_superieure_ou_egale_a_borne_gauche
= Q(**{"%s%s__gte" % (prefix
, KEY_DATE_FIN
) : borne_gauche
})
70 date_fin_inferieure_ou_egale_a_borne_droite
= Q(**{"%s%s__lte" % (prefix
, KEY_DATE_FIN
) : borne_droite
})
71 date_fin_strict_inferieure_ou_egale_a_borne_droite
= Q(**{"%s%s__lt" % (prefix
, KEY_DATE_FIN
) : borne_droite
})
72 date_debut_inferieure_ou_egale_a_borne_droite
= Q(**{"%s%s__lte" % (prefix
, KEY_DATE_DEBUT
) : borne_droite
})
73 date_fin_superieure_ou_egale_a_borne_droite
= Q(**{"%s%s__gte" % (prefix
, KEY_DATE_FIN
) : borne_droite
})
75 if borne_droite
is None:
76 q_range
= date_debut_strict_superieure_ou_egale_a_borne_gauche
78 if borne_gauche
is None:
79 q_range
= date_fin_strict_inferieure_ou_egale_a_borne_droite
81 if borne_droite
is not None and borne_gauche
is not None:
82 q_range
= (date_debut_superieure_ou_egale_a_borne_gauche
& date_fin_inferieure_ou_egale_a_borne_droite
) | \
83 ((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
) | \
84 ((date_fin_superieure_ou_egale_a_borne_droite | date_fin_nulle
) & date_debut_inferieure_ou_egale_a_borne_droite
) | \
85 (date_debut_inferieure_ou_egale_a_borne_gauche
& date_fin_superieure_ou_egale_a_borne_droite
)
87 if borne_droite
is None and borne_gauche
is None:
92 def purge_params(self
, lookup_params
):
93 self
.lookup_recherche_temporelle
= {}
94 params
= lookup_params
.copy()
95 for key
, value
in lookup_params
.items():
96 # ignorer les param GET pour la recherche temporelle
97 if len([i
for i
in self
.internal_fields
if key
.endswith(i
)]) > 0:
99 self
.lookup_recherche_temporelle
[key
] = value
103 def filter_temporel(self
, qs
):
105 prefix
= self
.get_prefix()
108 for k
, v
in self
.lookup_recherche_temporelle
.items():
110 if k
.endswith(KEY_ANNEE
):
111 borne_gauche
= "%s-01-01" % v
112 borne_droite
= "%s-12-31" % v
114 if k
.endswith(KEY_DATE_DEBUT
):
115 date
= time
.strptime(v
, settings
.DATE_INPUT_FORMATS
[0])
116 borne_gauche
= time
.strftime("%Y-%m-%d", date
)
118 if k
.endswith(KEY_DATE_FIN
):
119 date
= time
.strptime(v
, settings
.DATE_INPUT_FORMATS
[0])
120 borne_droite
= time
.strftime("%Y-%m-%d", date
)
122 if k
.endswith(KEY_STATUT
):
123 aujourdhui
= datetime
.date
.today()
124 if v
== STATUT_ACTIF
:
125 borne_gauche
= aujourdhui
126 borne_droite
= aujourdhui
127 elif v
== STATUT_INACTIF
:
128 # dans le cas d'une FK, on retire des inactifs ceux qui ont une FK active
130 q_range
= self
.get_q_range(prefix
, aujourdhui
, aujourdhui
)
131 id_actifs
= [o
.id for o
in qs
.filter(q_range
).distinct()]
132 qs
= qs
.exclude(id__in
=id_actifs
)
133 borne_droite
= aujourdhui
134 elif v
== STATUT_FUTUR
:
135 borne_gauche
= aujourdhui
136 elif v
== STATUT_INCONNU
:
137 q
= q
& self
.get_q_inconnu(prefix
)
138 q_range
= self
.get_q_range(prefix
, borne_gauche
, borne_droite
)
139 qs
= qs
.filter(q
& q_range
).distinct()
142 RechercheTemporelle
.get_query_string
= DjangoChangeList
.get_query_string
.im_func
145 class ChangeList(DjangoChangeList
, RechercheTemporelle
):
147 def __init__(self
, *args
, **kwargs
):
148 super(ChangeList
, self
).__init__(*args
, **kwargs
)
150 def get_query_set(self
):
154 qs
= self
.root_query_set
155 lookup_params
= self
.params
.copy() # a dictionary of the query string
156 for i
in (ALL_VAR
, ORDER_VAR
, ORDER_TYPE_VAR
, SEARCH_VAR
, IS_POPUP_VAR
, TO_FIELD_VAR
):
157 if i
in lookup_params
:
160 lookup_params
= self
.purge_params(lookup_params
)
161 for key
, value
in lookup_params
.items():
162 if not isinstance(key
, str):
163 # 'key' will be used as a keyword argument later, so Python
164 # requires it to be a string.
165 del lookup_params
[key
]
166 lookup_params
[smart_str(key
)] = value
169 # Check if it's a relationship that might return more than one
171 field_name
= key
.split('__', 1)[0]
173 f
= self
.lookup_opts
.get_field_by_name(field_name
)[0]
174 except models
.FieldDoesNotExist
:
175 raise IncorrectLookupParameters
176 use_distinct
= field_needs_distinct(f
)
178 # if key ends with __in, split parameter into separate values
179 if key
.endswith('__in'):
180 value
= value
.split(',')
181 lookup_params
[key
] = value
183 # if key ends with __isnull, special case '' and false
184 if key
.endswith('__isnull'):
185 if value
.lower() in ('', 'false'):
189 lookup_params
[key
] = value
191 if not self
.model_admin
.lookup_allowed(key
, value
):
192 raise SuspiciousOperation(
193 "Filtering by %s not allowed" % key
196 # Apply lookup parameters from the query string.
198 qs
= qs
.filter(**lookup_params
)
199 # Naked except! Because we don't have any other way of validating "params".
200 # They might be invalid if the keyword arguments are incorrect, or if the
201 # values are not in the correct type, so we might get FieldError, ValueError,
202 # ValicationError, or ? from a custom field that raises yet something else
203 # when handed impossible data.
205 raise IncorrectLookupParameters
207 qs
= self
.filter_temporel(qs
)
209 # Use select_related() if one of the list_display options is a field
210 # with a relationship and the provided queryset doesn't already have
211 # select_related defined.
212 if not qs
.query
.select_related
:
213 if self
.list_select_related
:
214 qs
= qs
.select_related()
216 for field_name
in self
.list_display
:
218 f
= self
.lookup_opts
.get_field(field_name
)
219 except models
.FieldDoesNotExist
:
222 if isinstance(f
.rel
, models
.ManyToOneRel
):
223 qs
= qs
.select_related()
228 qs
= qs
.order_by('%s%s' % ((self
.order_type
== 'desc' and '-' or ''), self
.order_field
))
230 # Apply keyword searches.
231 def construct_search(field_name
):
232 if field_name
.startswith('^'):
233 return "%s__istartswith" % field_name
[1:]
234 elif field_name
.startswith('='):
235 return "%s__iexact" % field_name
[1:]
236 elif field_name
.startswith('@'):
237 return "%s__search" % field_name
[1:]
239 return "%s__icontains" % field_name
241 if self
.search_fields
and self
.query
:
242 orm_lookups
= [construct_search(str(search_field
))
243 for search_field
in self
.search_fields
]
244 for bit
in self
.query
.split():
245 or_queries
= [models
.Q(**{orm_lookup
: bit
})
246 for orm_lookup
in orm_lookups
]
247 qs
= qs
.filter(reduce(operator
.or_
, or_queries
))
249 for search_spec
in orm_lookups
:
250 field_name
= search_spec
.split('__', 1)[0]
251 f
= self
.lookup_opts
.get_field_by_name(field_name
)[0]
252 if field_needs_distinct(f
):