diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 96ecefafd..d06a65ad3 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -8,7 +8,9 @@ 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 NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter +from utilities.filters import ( + NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter +) from virtualization.models import Cluster from .constants import * from .models import ( @@ -49,14 +51,15 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): choices=SITE_STATUS_CHOICES, null_value=None ) - region_id = django_filters.NumberFilter( - method='filter_region', - field_name='pk', + region_id = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='region__in', label='Region (ID)', ) - region = django_filters.CharFilter( - method='filter_region', - field_name='slug', + region = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='region__in', + to_field_name='slug', label='Region (slug)', ) tenant_id = django_filters.ModelMultipleChoiceFilter( @@ -95,16 +98,6 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet): pass return queryset.filter(qs_filter) - def filter_region(self, queryset, name, value): - try: - region = Region.objects.get(**{name: value}) - except ObjectDoesNotExist: - return queryset.none() - return queryset.filter( - Q(region=region) | - Q(region__in=region.get_descendants()) - ) - class RackGroupFilter(NameSlugSearchFilterSet): site_id = django_filters.ModelMultipleChoiceFilter( @@ -513,14 +506,15 @@ class DeviceFilter(CustomFieldFilterSet): ) name = NullableCharFieldFilter() asset_tag = NullableCharFieldFilter() - region_id = django_filters.NumberFilter( - method='filter_region', - field_name='pk', + region_id = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='region__in', label='Region (ID)', ) - region = django_filters.CharFilter( - method='filter_region', - field_name='slug', + region = TreeNodeMultipleChoiceFilter( + queryset=Region.objects.all(), + field_name='region__in', + to_field_name='slug', label='Region (slug)', ) site_id = django_filters.ModelMultipleChoiceFilter( diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 40e687077..b0c2b3ec3 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -4,6 +4,15 @@ from django.db.models import Q from taggit.models import Tag +class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter): + """ + Filters for a set of Models, including all descendant models within a Tree. Example: [,] + """ + def filter(self, qs, value): + value = [node.get_descendants(include_self=True) for node in value] + return super().filter(qs, value) + + class NumericInFilter(django_filters.BaseInFilter, django_filters.NumberFilter): """ Filters for a set of numeric values. Example: id__in=100,200,300