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
This commit is contained in:
Jason Novinger 2025-03-13 15:36:55 -05:00
parent 2df68e29c9
commit 06a206ee33
4 changed files with 33 additions and 68 deletions

View File

@ -11,7 +11,8 @@ from ipam.filtersets import PrimaryIPFilterSet
from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF
from netbox.choices import ColorChoices from netbox.choices import ColorChoices
from netbox.filtersets import ( from netbox.filtersets import (
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
OrganizationalModelFilterSet,
) )
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from tenancy.models import * from tenancy.models import *
@ -81,7 +82,7 @@ __all__ = (
) )
class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet): class RegionFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label=_('Parent region (ID)'), label=_('Parent region (ID)'),
@ -110,18 +111,8 @@ class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
model = Region model = Region
fields = ('id', 'name', 'slug', 'description') 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(NestedGroupModelFilterSet, ContactModelFilterSet):
class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=SiteGroup.objects.all(), queryset=SiteGroup.objects.all(),
label=_('Parent site group (ID)'), label=_('Parent site group (ID)'),
@ -150,16 +141,6 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
model = SiteGroup model = SiteGroup
fields = ('id', 'name', 'slug', 'description') 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): class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
@ -225,7 +206,7 @@ class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
return queryset.filter(qs_filter).distinct() return queryset.filter(qs_filter).distinct()
class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalModelFilterSet): class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, NestedGroupModelFilterSet):
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
@ -295,15 +276,13 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM
fields = ('id', 'name', 'slug', 'facility', 'description') fields = ('id', 'name', 'slug', 'facility', 'description')
def search(self, queryset, name, value): def search(self, queryset, name, value):
if not value.strip(): # extended in order to include querying on Location.facility
return queryset queryset = super().search(queryset, name, value)
return queryset.filter(
Q(name__icontains=value) | if value.strip():
Q(slug__icontains=value) | queryset = queryset | queryset.model.objects.filter(facility__icontains=value)
Q(facility__icontains=value) |
Q(description__icontains=value) | return queryset
Q(comments__icontains=value)
)
class RackRoleFilterSet(OrganizationalModelFilterSet): class RackRoleFilterSet(OrganizationalModelFilterSet):

View File

@ -329,3 +329,19 @@ class OrganizationalModelFilterSet(NetBoxModelFilterSet):
models.Q(slug__icontains=value) | models.Q(slug__icontains=value) |
models.Q(description__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

View File

@ -2,7 +2,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _ 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 utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
from .models import * from .models import *
@ -22,7 +22,7 @@ __all__ = (
# Contacts # Contacts
# #
class ContactGroupFilterSet(OrganizationalModelFilterSet): class ContactGroupFilterSet(NestedGroupModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=ContactGroup.objects.all(), queryset=ContactGroup.objects.all(),
label=_('Parent contact group (ID)'), label=_('Parent contact group (ID)'),
@ -51,16 +51,6 @@ class ContactGroupFilterSet(OrganizationalModelFilterSet):
model = ContactGroup model = ContactGroup
fields = ('id', 'name', 'slug', 'description') 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): class ContactRoleFilterSet(OrganizationalModelFilterSet):
@ -173,7 +163,7 @@ class ContactModelFilterSet(django_filters.FilterSet):
# Tenancy # Tenancy
# #
class TenantGroupFilterSet(OrganizationalModelFilterSet): class TenantGroupFilterSet(NestedGroupModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label=_('Parent tenant group (ID)'), label=_('Parent tenant group (ID)'),
@ -202,16 +192,6 @@ class TenantGroupFilterSet(OrganizationalModelFilterSet):
model = TenantGroup model = TenantGroup
fields = ('id', 'name', 'slug', 'description') 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): class TenantFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
group_id = TreeNodeMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(

View File

@ -5,7 +5,7 @@ from dcim.choices import LinkStatusChoices
from dcim.base_filtersets import ScopedFilterSet from dcim.base_filtersets import ScopedFilterSet
from dcim.models import Interface from dcim.models import Interface
from ipam.models import VLAN from ipam.models import VLAN
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet from netbox.filtersets import NestedGroupModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import TenancyFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filters import TreeNodeMultipleChoiceFilter
from .choices import * from .choices import *
@ -18,7 +18,7 @@ __all__ = (
) )
class WirelessLANGroupFilterSet(OrganizationalModelFilterSet): class WirelessLANGroupFilterSet(NestedGroupModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=WirelessLANGroup.objects.all() queryset=WirelessLANGroup.objects.all()
) )
@ -43,16 +43,6 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
model = WirelessLANGroup model = WirelessLANGroup
fields = ('id', 'name', 'slug', 'description') 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): class WirelessLANFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet):
group_id = TreeNodeMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(