filters.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import django_filters
  2. from django import forms
  3. from django.conf import settings
  4. from django_filters.constants import EMPTY_VALUES
  5. from utilities.forms import MACAddressField
  6. def multivalue_field_factory(field_class):
  7. """
  8. Given a form field class, return a subclass capable of accepting multiple values. This allows us to OR on multiple
  9. filter values while maintaining the field's built-in validation. Example: GET /api/dcim/devices/?name=foo&name=bar
  10. """
  11. class NewField(field_class):
  12. widget = forms.SelectMultiple
  13. def to_python(self, value):
  14. if not value:
  15. return []
  16. field = field_class()
  17. return [
  18. # Only append non-empty values (this avoids e.g. trying to cast '' as an integer)
  19. field.to_python(v) for v in value if v
  20. ]
  21. return type('MultiValue{}'.format(field_class.__name__), (NewField,), dict())
  22. #
  23. # Filters
  24. #
  25. class MultiValueCharFilter(django_filters.MultipleChoiceFilter):
  26. field_class = multivalue_field_factory(forms.CharField)
  27. class MultiValueDateFilter(django_filters.MultipleChoiceFilter):
  28. field_class = multivalue_field_factory(forms.DateField)
  29. class MultiValueDateTimeFilter(django_filters.MultipleChoiceFilter):
  30. field_class = multivalue_field_factory(forms.DateTimeField)
  31. class MultiValueNumberFilter(django_filters.MultipleChoiceFilter):
  32. field_class = multivalue_field_factory(forms.IntegerField)
  33. class MultiValueTimeFilter(django_filters.MultipleChoiceFilter):
  34. field_class = multivalue_field_factory(forms.TimeField)
  35. class MACAddressFilter(django_filters.CharFilter):
  36. pass
  37. class MultiValueMACAddressFilter(django_filters.MultipleChoiceFilter):
  38. field_class = multivalue_field_factory(forms.CharField)
  39. class MultiValueWWNFilter(django_filters.MultipleChoiceFilter):
  40. field_class = multivalue_field_factory(forms.CharField)
  41. class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
  42. """
  43. Filters for a set of Models, including all descendant models within a Tree. Example: [<Region: R1>,<Region: R2>]
  44. """
  45. def get_filter_predicate(self, v):
  46. # Null value filtering
  47. if v is None:
  48. return {f"{self.field_name}__isnull": True}
  49. return super().get_filter_predicate(v)
  50. def filter(self, qs, value):
  51. value = [node.get_descendants(include_self=True) if not isinstance(node, str) else node for node in value]
  52. return super().filter(qs, value)
  53. class NullableCharFieldFilter(django_filters.CharFilter):
  54. """
  55. Allow matching on null field values by passing a special string used to signify NULL.
  56. """
  57. def filter(self, qs, value):
  58. if value != settings.FILTERS_NULL_CHOICE_VALUE:
  59. return super().filter(qs, value)
  60. qs = self.get_method(qs)(**{'{}__isnull'.format(self.field_name): True})
  61. return qs.distinct() if self.distinct else qs
  62. class NumericArrayFilter(django_filters.NumberFilter):
  63. """
  64. Filter based on the presence of an integer within an ArrayField.
  65. """
  66. def filter(self, qs, value):
  67. if value:
  68. value = [value]
  69. return super().filter(qs, value)
  70. class ContentTypeFilter(django_filters.CharFilter):
  71. """
  72. Allow specifying a ContentType by <app_label>.<model> (e.g. "dcim.site").
  73. """
  74. def filter(self, qs, value):
  75. if value in EMPTY_VALUES:
  76. return qs
  77. try:
  78. app_label, model = value.lower().split('.')
  79. except ValueError:
  80. return qs.none()
  81. return qs.filter(
  82. **{
  83. f'{self.field_name}__app_label': app_label,
  84. f'{self.field_name}__model': model
  85. }
  86. )