diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9c9ba67..81f122587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ v2.5.5 (FUTURE) * [#2824](https://github.com/digitalocean/netbox/issues/2824) - Fix template exception when viewing rack elevations list * [#2833](https://github.com/digitalocean/netbox/issues/2833) - Fix form widget for front port template creation +* [#2835](https://github.com/digitalocean/netbox/issues/2835) - Fix certain model filters did not support the `q` query param --- diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 0982624d5..12955eeca 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -4,7 +4,7 @@ from django.db.models import Q from dcim.models import Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from .constants import CIRCUIT_STATUS_CHOICES from .models import Provider, Circuit, CircuitTermination, CircuitType @@ -47,7 +47,7 @@ class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet): ) -class CircuitTypeFilter(django_filters.FilterSet): +class CircuitTypeFilter(NameSlugSearchFilterSet): class Meta: model = CircuitType diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index e10cfe337..388426e06 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -8,7 +8,7 @@ from netaddr.core import AddrFormatError from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.constants import COLOR_CHOICES -from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter from virtualization.models import Cluster from .constants import * from .models import ( @@ -19,11 +19,7 @@ from .models import ( ) -class RegionFilter(django_filters.FilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) +class RegionFilter(NameSlugSearchFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), label='Parent region (ID)', @@ -39,15 +35,6 @@ class RegionFilter(django_filters.FilterSet): model = Region fields = ['name', 'slug'] - def search(self, queryset, name, value): - if not value.strip(): - return queryset - qs_filter = ( - Q(name__icontains=value) | - Q(slug__icontains=value) - ) - return queryset.filter(qs_filter) - class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): id__in = NumericInFilter( @@ -119,11 +106,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): ) -class RackGroupFilter(django_filters.FilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) +class RackGroupFilter(NameSlugSearchFilterSet): site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), label='Site (ID)', @@ -139,17 +122,8 @@ class RackGroupFilter(django_filters.FilterSet): model = RackGroup fields = ['site_id', 'name', 'slug'] - def search(self, queryset, name, value): - if not value.strip(): - return queryset - qs_filter = ( - Q(name__icontains=value) | - Q(slug__icontains=value) - ) - return queryset.filter(qs_filter) - -class RackRoleFilter(django_filters.FilterSet): +class RackRoleFilter(NameSlugSearchFilterSet): class Meta: model = RackRole @@ -303,7 +277,7 @@ class RackReservationFilter(django_filters.FilterSet): ) -class ManufacturerFilter(django_filters.FilterSet): +class ManufacturerFilter(NameSlugSearchFilterSet): class Meta: model = Manufacturer @@ -393,7 +367,7 @@ class DeviceTypeFilter(CustomFieldFilterSet): ) -class DeviceTypeComponentFilterSet(django_filters.FilterSet): +class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet): devicetype_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceType.objects.all(), field_name='device_type_id', @@ -457,14 +431,14 @@ class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet): fields = ['name'] -class DeviceRoleFilter(django_filters.FilterSet): +class DeviceRoleFilter(NameSlugSearchFilterSet): class Meta: model = DeviceRole fields = ['name', 'slug', 'color', 'vm_role'] -class PlatformFilter(django_filters.FilterSet): +class PlatformFilter(NameSlugSearchFilterSet): manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer', queryset=Manufacturer.objects.all(), @@ -696,6 +670,10 @@ class DeviceFilter(CustomFieldFilterSet): class DeviceComponentFilterSet(django_filters.FilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) device_id = django_filters.ModelChoiceFilter( queryset=Device.objects.all(), label='Device (ID)', @@ -707,6 +685,13 @@ class DeviceComponentFilterSet(django_filters.FilterSet): ) tag = TagFilter() + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) + ) + class ConsolePortFilter(DeviceComponentFilterSet): cabled = django_filters.BooleanFilter( diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 22df1b1c9..f7125ceb0 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -7,7 +7,7 @@ from netaddr.core import AddrFormatError from dcim.models import Site, Device, Interface from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from virtualization.models import VirtualMachine from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -48,7 +48,7 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet): fields = ['name', 'rd', 'enforce_unique'] -class RIRFilter(django_filters.FilterSet): +class RIRFilter(NameSlugSearchFilterSet): id__in = NumericInFilter( field_name='id', lookup_expr='in' @@ -96,7 +96,11 @@ class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet): return queryset.filter(qs_filter) -class RoleFilter(django_filters.FilterSet): +class RoleFilter(NameSlugSearchFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) class Meta: model = Role @@ -373,7 +377,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): return queryset.none() -class VLANGroupFilter(django_filters.FilterSet): +class VLANGroupFilter(NameSlugSearchFilterSet): site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), label='Site (ID)', diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index 5880fb9f9..6548708b5 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -3,11 +3,11 @@ from django.db.models import Q from dcim.models import Device from extras.filters import CustomFieldFilterSet -from utilities.filters import NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from .models import Secret, SecretRole -class SecretRoleFilter(django_filters.FilterSet): +class SecretRoleFilter(NameSlugSearchFilterSet): class Meta: model = SecretRole diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 5b3ec30d4..745391898 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -2,11 +2,11 @@ import django_filters from django.db.models import Q from extras.filters import CustomFieldFilterSet -from utilities.filters import NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from .models import Tenant, TenantGroup -class TenantGroupFilter(django_filters.FilterSet): +class TenantGroupFilter(NameSlugSearchFilterSet): class Meta: model = TenantGroup diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 4da3a9856..94edd3dd1 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -1,4 +1,5 @@ import django_filters +from django.db.models import Q from taggit.models import Tag @@ -35,3 +36,21 @@ class TagFilter(django_filters.ModelMultipleChoiceFilter): kwargs.setdefault('queryset', Tag.objects.all()) super().__init__(*args, **kwargs) + + +class NameSlugSearchFilterSet(django_filters.FilterSet): + """ + A base class for adding the search method to models which only expose the `name` and `slug` fields + """ + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(slug__icontains=value) + ) diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index a103e9b29..0b7e57ba7 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -7,19 +7,19 @@ from netaddr.core import AddrFormatError from dcim.models import DeviceRole, Interface, Platform, Region, Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NumericInFilter, TagFilter +from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from .constants import VM_STATUS_CHOICES from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine -class ClusterTypeFilter(django_filters.FilterSet): +class ClusterTypeFilter(NameSlugSearchFilterSet): class Meta: model = ClusterType fields = ['name', 'slug'] -class ClusterGroupFilter(django_filters.FilterSet): +class ClusterGroupFilter(NameSlugSearchFilterSet): class Meta: model = ClusterGroup @@ -196,6 +196,10 @@ class VirtualMachineFilter(CustomFieldFilterSet): class InterfaceFilter(django_filters.FilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) virtual_machine_id = django_filters.ModelMultipleChoiceFilter( field_name='virtual_machine', queryset=VirtualMachine.objects.all(), @@ -225,3 +229,10 @@ class InterfaceFilter(django_filters.FilterSet): return queryset.filter(mac_address=mac) except AddrFormatError: return queryset.none() + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) + )