Skip to content

Instantly share code, notes, and snippets.

@nkryptic
Last active December 15, 2015 14:39
Show Gist options
  • Save nkryptic/5275724 to your computer and use it in GitHub Desktop.
Save nkryptic/5275724 to your computer and use it in GitHub Desktop.
different variations for django-filter's qs method. all require the new 'strict' attribute for the FilterSet class
all implementations ensure that:
non-empty querysets are always ordered. either by submitted value when bound,
the order field's initial value or the value of the first ordering choice
when filterset is strict and bound:
* the form is validated, so errors are available
* returns empty queryset when form is invalid
* returns fully filtered queryset otherwise
when filterset is strict and unbound:
* the form is not validated
* returns empty queryset when form *would* be invalid, based on initial values
* returns fully filtered queryset otherwise
when filterset is forgiving and bound:
* the form is validated, so errors are available
* returns queryset filtered from valid fields
when filterset is forgiving and unbound:
* the form is not validated
* returns queryset filtered from fields that *would* be valid, based on initial values
"""
This version is the simplest, at the expense of calling is_valid on the form
when 'bound' and also cleaning each field a second time.
"""
@property
def qs(self):
if not hasattr(self, '_qs'):
if self.is_bound:
if not self.form.is_valid() and self.strict:
self._qs = self.queryset.none()
return self._qs
qs = self.queryset.all()
for name, filter_ in six.iteritems(self.filters):
data = self.form[name].value()
try:
value = self.form.fields[name].clean(data)
qs = filter_.filter(qs, value)
except forms.ValidationError:
if self.strict:
self._qs = self.queryset.none()
return self._qs
if self._meta.order_by:
value = None
order_field = self.form.fields[self.order_by_field]
data = self.form[self.order_by_field].value()
try:
value = order_field.clean(data)
except forms.ValidationError:
if self.strict:
self._qs = self.queryset.none()
return self._qs
if not value:
value = order_field.choices[0][0]
qs = qs.order_by(value)
self._qs = qs
return self._qs
"""
This version is the same as the first, but uses the form's cleaned_data when available.
"""
@property
def qs(self):
if not hasattr(self, '_qs'):
valid = self.is_bound and self.form.is_valid()
if self.strict and self.is_bound and not valid:
self._qs = self.queryset.none()
return self._qs
sentinel = ()
def get_value(name):
value = sentinel
if valid:
value = self.form.cleaned_data[name]
else:
data = self.form[name].value()
try:
value = self.form.fields[name].clean(data)
except forms.ValidationError:
pass
return value
qs = self.queryset.all()
for name, filter_ in six.iteritems(self.filters):
value = get_value(name)
if value is sentinel:
if self.strict:
self._qs = self.queryset.none()
return self._qs
else:
qs = filter_.filter(qs, value)
if self._meta.order_by:
value = get_value(self.order_by_field)
if value is sentinel:
if self.strict:
self._qs = self.queryset.none()
return self._qs
if not value:
value = self.form.fields[self.order_by_field].choices[0][0]
qs = qs.order_by(value)
self._qs = qs
return self._qs
"""
This version uses a 'validation-only' bound form, when the filterset is unbound.
But, it relies on Django 1.5's cleaned_data support when the form is invalid, so
it can't be used unless we drop 1.4 compatibility.
"""
def get_form_class(self):
fields = SortedDict([
(name, filter_.field)
for name, filter_ in six.iteritems(self.filters)])
fields[self.order_by_field] = self.ordering_field
Form = type(str('%sForm' % self.__class__.__name__),
(self._meta.form,), fields)
return Form
@property
def form(self):
if not hasattr(self, '_form'):
Form = self.get_form_class()
if self.is_bound:
self._form = Form(self.data, prefix=self.form_prefix)
else:
self._form = Form(prefix=self.form_prefix)
return self._form
@property
def qs(self):
if not hasattr(self, '_qs'):
if self.is_bound:
form = self.form
else:
Form = self.get_form_class()
data = dict([(x, y.initial) for x, y in Form.base_fields.items()])
form = Form(data, prefix=self.form_prefix)
if form.is_valid() or not self.strict:
qs = self.queryset.all()
data = form.cleaned_data
for name, filter_ in six.iteritems(self.filters):
if name in data:
qs = filter_.filter(qs, data[name])
if self._meta.order_by:
value = None
if self.order_by_field in data:
value = data[self.order_by_field]
if not value:
value = form.fields[self.order_by_field].choices[0][0]
qs = qs.order_by(value)
else:
qs = self.queryset.none()
self._qs = qs
return self._qs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment