filters.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. from __future__ import unicode_literals
  2. import itertools
  3. import django_filters
  4. from django import forms
  5. from django.utils.encoding import force_text
  6. from taggit.models import Tag
  7. #
  8. # Filters
  9. #
  10. class NumericInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
  11. """
  12. Filters for a set of numeric values. Example: id__in=100,200,300
  13. """
  14. pass
  15. class NullableCharFieldFilter(django_filters.CharFilter):
  16. """
  17. Allow matching on null field values by passing a special string used to signify NULL.
  18. """
  19. null_value = 'NULL'
  20. def filter(self, qs, value):
  21. if value != self.null_value:
  22. return super(NullableCharFieldFilter, self).filter(qs, value)
  23. qs = self.get_method(qs)(**{'{}__isnull'.format(self.name): True})
  24. return qs.distinct() if self.distinct else qs
  25. class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField):
  26. """
  27. This field operates like a normal ModelMultipleChoiceField except that it allows for one additional choice which is
  28. used to represent a value of Null. This is accomplished by creating a new iterator which first yields the null
  29. choice before entering the queryset iterator, and by ignoring the null choice during cleaning. The effect is similar
  30. to defining a MultipleChoiceField with:
  31. choices = [(0, 'None')] + [(x.id, x) for x in Foo.objects.all()]
  32. However, the above approach forces immediate evaluation of the queryset, which can cause issues when calculating
  33. database migrations.
  34. """
  35. iterator = forms.models.ModelChoiceIterator
  36. def __init__(self, null_value=0, null_label='-- None --', *args, **kwargs):
  37. self.null_value = null_value
  38. self.null_label = null_label
  39. super(NullableModelMultipleChoiceField, self).__init__(*args, **kwargs)
  40. def _get_choices(self):
  41. if hasattr(self, '_choices'):
  42. return self._choices
  43. # Prepend the null choice to the queryset iterator
  44. return itertools.chain(
  45. [(self.null_value, self.null_label)],
  46. self.iterator(self),
  47. )
  48. choices = property(_get_choices, forms.ChoiceField._set_choices)
  49. def clean(self, value):
  50. # Strip all instances of the null value before cleaning
  51. if value is not None:
  52. stripped_value = [x for x in value if x != force_text(self.null_value)]
  53. else:
  54. stripped_value = value
  55. super(NullableModelMultipleChoiceField, self).clean(stripped_value)
  56. return value
  57. class TagFilter(django_filters.ModelMultipleChoiceFilter):
  58. """
  59. Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered
  60. to objects matching all tags.
  61. """
  62. def __init__(self, *args, **kwargs):
  63. kwargs.setdefault('name', 'tags__slug')
  64. kwargs.setdefault('to_field_name', 'slug')
  65. kwargs.setdefault('conjoined', True)
  66. kwargs.setdefault('queryset', Tag.objects.all())
  67. super(TagFilter, self).__init__(*args, **kwargs)