From 6cb5173e273a2dfd52e00b51b89adc8985414f50 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 May 2019 12:25:33 -0400 Subject: [PATCH] Update query filters to OR multiple values --- netbox/circuits/filters.py | 4 +- netbox/ipam/filters.py | 10 ++-- netbox/secrets/filters.py | 2 +- netbox/tenancy/filters.py | 2 +- netbox/utilities/filters.py | 102 ++++++++++++++++++++++++++++++++++-- 5 files changed, 108 insertions(+), 12 deletions(-) diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 12955eeca..1ee0a9cf2 100644 --- a/netbox/circuits/filters.py +++ b/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' diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 2df680240..71597cfda 100644 --- a/netbox/ipam/filters.py +++ b/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' diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index 6548708b5..4be77cb15 100644 --- a/netbox/secrets/filters.py +++ b/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' diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 2610b3ec0..f3acb62a4 100644 --- a/netbox/tenancy/filters.py +++ b/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' diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 6e2116751..614c09902 100644 --- a/netbox/utilities/filters.py +++ b/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: [,] @@ -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 + }, +})