From 06a206ee33853ae07a6f29067f0cd74ce13efe62 Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Thu, 13 Mar 2025 15:36:55 -0500 Subject: [PATCH] Extract base NestedGroupModelFilterSet with base search behavior This can easily be extended (as in the case of LocationFilterSet) by calling super() and ORing a filter to the queryset that is returned. See: https://docs.djangoproject.com/en/5.1/ref/models/querysets/#or --- netbox/dcim/filtersets.py | 45 ++++++++++------------------------- netbox/netbox/filtersets.py | 16 +++++++++++++ netbox/tenancy/filtersets.py | 26 +++----------------- netbox/wireless/filtersets.py | 14 ++--------- 4 files changed, 33 insertions(+), 68 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 443a2dc36..6f9f481c3 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -11,7 +11,8 @@ from ipam.filtersets import PrimaryIPFilterSet from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF from netbox.choices import ColorChoices from netbox.filtersets import ( - BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet, + BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet, + OrganizationalModelFilterSet, ) from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet from tenancy.models import * @@ -81,7 +82,7 @@ __all__ = ( ) -class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet): +class RegionFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), label=_('Parent region (ID)'), @@ -110,18 +111,8 @@ class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet): model = Region fields = ('id', 'name', 'slug', 'description') - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ).distinct() - -class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet): +class SiteGroupFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=SiteGroup.objects.all(), label=_('Parent site group (ID)'), @@ -150,16 +141,6 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet): model = SiteGroup fields = ('id', 'name', 'slug', 'description') - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ).distinct() - class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet): status = django_filters.MultipleChoiceFilter( @@ -225,7 +206,7 @@ class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe return queryset.filter(qs_filter).distinct() -class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalModelFilterSet): +class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, NestedGroupModelFilterSet): region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', @@ -295,15 +276,13 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM fields = ('id', 'name', 'slug', 'facility', 'description') def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(facility__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ) + # extended in order to include querying on Location.facility + queryset = super().search(queryset, name, value) + + if value.strip(): + queryset = queryset | queryset.model.objects.filter(facility__icontains=value) + + return queryset class RackRoleFilterSet(OrganizationalModelFilterSet): diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index b8fbe7ad5..d80b07e90 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -329,3 +329,19 @@ class OrganizationalModelFilterSet(NetBoxModelFilterSet): models.Q(slug__icontains=value) | models.Q(description__icontains=value) ) + + +class NestedGroupModelFilterSet(NetBoxModelFilterSet): + """ + A base FilterSet for models that inherit from NestedGroupModel + """ + def search(self, queryset, name, value): + if value.strip(): + queryset = queryset.filter( + models.Q(name__icontains=value) | + models.Q(slug__icontains=value) | + models.Q(description__icontains=value) | + models.Q(comments__icontains=value) + ) + + return queryset diff --git a/netbox/tenancy/filtersets.py b/netbox/tenancy/filtersets.py index c70b381ee..db7236abe 100644 --- a/netbox/tenancy/filtersets.py +++ b/netbox/tenancy/filtersets.py @@ -2,7 +2,7 @@ import django_filters from django.db.models import Q from django.utils.translation import gettext as _ -from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet +from netbox.filtersets import NestedGroupModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter from .models import * @@ -22,7 +22,7 @@ __all__ = ( # Contacts # -class ContactGroupFilterSet(OrganizationalModelFilterSet): +class ContactGroupFilterSet(NestedGroupModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=ContactGroup.objects.all(), label=_('Parent contact group (ID)'), @@ -51,16 +51,6 @@ class ContactGroupFilterSet(OrganizationalModelFilterSet): model = ContactGroup fields = ('id', 'name', 'slug', 'description') - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ) - class ContactRoleFilterSet(OrganizationalModelFilterSet): @@ -173,7 +163,7 @@ class ContactModelFilterSet(django_filters.FilterSet): # Tenancy # -class TenantGroupFilterSet(OrganizationalModelFilterSet): +class TenantGroupFilterSet(NestedGroupModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=TenantGroup.objects.all(), label=_('Parent tenant group (ID)'), @@ -202,16 +192,6 @@ class TenantGroupFilterSet(OrganizationalModelFilterSet): model = TenantGroup fields = ('id', 'name', 'slug', 'description') - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ) - class TenantFilterSet(NetBoxModelFilterSet, ContactModelFilterSet): group_id = TreeNodeMultipleChoiceFilter( diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py index 17ef66c0a..bd96865ad 100644 --- a/netbox/wireless/filtersets.py +++ b/netbox/wireless/filtersets.py @@ -5,7 +5,7 @@ from dcim.choices import LinkStatusChoices from dcim.base_filtersets import ScopedFilterSet from dcim.models import Interface from ipam.models import VLAN -from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet +from netbox.filtersets import NestedGroupModelFilterSet, NetBoxModelFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import TreeNodeMultipleChoiceFilter from .choices import * @@ -18,7 +18,7 @@ __all__ = ( ) -class WirelessLANGroupFilterSet(OrganizationalModelFilterSet): +class WirelessLANGroupFilterSet(NestedGroupModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=WirelessLANGroup.objects.all() ) @@ -43,16 +43,6 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet): model = WirelessLANGroup fields = ('id', 'name', 'slug', 'description') - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) | - Q(description__icontains=value) | - Q(comments__icontains=value) - ) - class WirelessLANFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet): group_id = TreeNodeMultipleChoiceFilter(