Просмотр исходного кода

Update query filters to OR multiple values

Jeremy Stretch 6 лет назад
Родитель
Сommit
6cb5173e27

+ 2 - 2
netbox/circuits/filters.py

@@ -9,7 +9,7 @@ from .constants import CIRCUIT_STATUS_CHOICES
 from .models import Provider, Circuit, CircuitTermination, CircuitType
 
 
-class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class ProviderFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'
@@ -54,7 +54,7 @@ class CircuitTypeFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
 
 
-class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class CircuitFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'

+ 5 - 5
netbox/ipam/filters.py

@@ -13,7 +13,7 @@ from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 
 
-class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class VRFFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'
@@ -59,7 +59,7 @@ class RIRFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug', 'is_private']
 
 
-class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class AggregateFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'
@@ -107,7 +107,7 @@ class RoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
 
 
-class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class PrefixFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'
@@ -254,7 +254,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
         return queryset.filter(prefix__net_mask_length=value)
 
 
-class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class IPAddressFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'
@@ -395,7 +395,7 @@ class VLANGroupFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
 
 
-class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class VLANFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'

+ 1 - 1
netbox/secrets/filters.py

@@ -14,7 +14,7 @@ class SecretRoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
 
 
-class SecretFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class SecretFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'

+ 1 - 1
netbox/tenancy/filters.py

@@ -13,7 +13,7 @@ class TenantGroupFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
 
 
-class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class TenantFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
         field_name='id',
         lookup_expr='in'

+ 99 - 3
netbox/utilities/filters.py

@@ -1,10 +1,51 @@
 import django_filters
+from django import forms
 from django.conf import settings
-from django.db.models import Q
+from django.db import models
 
 from extras.models import Tag
 
 
+def multivalue_field_factory(field_class):
+    """
+    Given a form field class, return a subclass capable of accepting multiple values. This allows us to OR on multiple
+    filter values while maintaining the field's built-in vlaidation. Example: GET /api/dcim/devices/?name=foo&name=bar
+    """
+    class NewField(field_class):
+        widget = forms.SelectMultiple
+
+        def to_python(self, value):
+            if not value:
+                return []
+            return [super(field_class, self).to_python(v) for v in value]
+
+    return type('MultiValue{}'.format(field_class.__name__), (NewField,), dict())
+
+
+#
+# Filters
+#
+
+class MultiValueCharFilter(django_filters.MultipleChoiceFilter):
+    field_class = multivalue_field_factory(forms.CharField)
+
+
+class MultiValueDateFilter(django_filters.MultipleChoiceFilter):
+    field_class = multivalue_field_factory(forms.DateField)
+
+
+class MultiValueDateTimeFilter(django_filters.MultipleChoiceFilter):
+    field_class = multivalue_field_factory(forms.DateTimeField)
+
+
+class MultiValueNumberFilter(django_filters.MultipleChoiceFilter):
+    field_class = multivalue_field_factory(forms.IntegerField)
+
+
+class MultiValueTimeFilter(django_filters.MultipleChoiceFilter):
+    field_class = multivalue_field_factory(forms.TimeField)
+
+
 class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
     """
     Filters for a set of Models, including all descendant models within a Tree.  Example: [<Region: R1>,<Region: R2>]
@@ -48,6 +89,10 @@ class TagFilter(django_filters.ModelMultipleChoiceFilter):
         super().__init__(*args, **kwargs)
 
 
+#
+# FilterSets
+#
+
 class NameSlugSearchFilterSet(django_filters.FilterSet):
     """
     A base class for adding the search method to models which only expose the `name` and `slug` fields
@@ -61,6 +106,57 @@ class NameSlugSearchFilterSet(django_filters.FilterSet):
         if not value.strip():
             return queryset
         return queryset.filter(
-            Q(name__icontains=value) |
-            Q(slug__icontains=value)
+            models.Q(name__icontains=value) |
+            models.Q(slug__icontains=value)
         )
+
+
+#
+# Update default filters
+#
+
+FILTER_DEFAULTS = django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS
+FILTER_DEFAULTS.update({
+    models.AutoField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.CharField: {
+        'filter_class': MultiValueCharFilter
+    },
+    models.DateField: {
+        'filter_class': MultiValueDateFilter
+    },
+    models.DateTimeField: {
+        'filter_class': MultiValueDateTimeFilter
+    },
+    models.DecimalField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.EmailField: {
+        'filter_class': MultiValueCharFilter
+    },
+    models.FloatField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.IntegerField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.PositiveIntegerField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.PositiveSmallIntegerField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.SlugField: {
+        'filter_class': MultiValueCharFilter
+    },
+    models.SmallIntegerField: {
+        'filter_class': MultiValueNumberFilter
+    },
+    models.TimeField: {
+        'filter_class': MultiValueTimeFilter
+    },
+    models.URLField: {
+        'filter_class': MultiValueCharFilter
+    },
+})