From 94b3aae0a20a4e73529d98d54c9d9e4b1ac10658 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Mar 2025 12:25:34 -0400 Subject: [PATCH] Enable filtering by attribute values --- netbox/dcim/filtersets.py | 4 ++-- netbox/dcim/models/modules.py | 2 +- netbox/netbox/filtersets.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index fa6837418..fed660c1f 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -11,7 +11,7 @@ from ipam.filtersets import PrimaryIPFilterSet from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF from netbox.choices import ColorChoices from netbox.filtersets import ( - BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet, + AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet, ) from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet @@ -691,7 +691,7 @@ class ModuleTypeProfileFilterSet(NetBoxModelFilterSet): ) -class ModuleTypeFilterSet(NetBoxModelFilterSet): +class ModuleTypeFilterSet(AttributeFiltersMixin, NetBoxModelFilterSet): profile_id = django_filters.ModelMultipleChoiceFilter( queryset=ModuleTypeProfile.objects.all(), label=_('Profile (ID)'), diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py index edc4f7cce..f677c6ebc 100644 --- a/netbox/dcim/models/modules.py +++ b/netbox/dcim/models/modules.py @@ -128,7 +128,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): """ Returns a human-friendly representation of the attributes defined for a ModuleType according to its profile. """ - if self.profile is None or not self.profile.schema: + if not self.attribute_data or self.profile is None or not self.profile.schema: return {} attrs = {} for name, options in self.profile.schema.get('properties', {}).items(): diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index d80b07e90..1fc228abe 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -1,3 +1,5 @@ +import json + import django_filters from copy import deepcopy from django.contrib.contenttypes.models import ContentType @@ -20,6 +22,7 @@ from utilities.forms.fields import MACAddressField from utilities import filters __all__ = ( + 'AttributeFiltersMixin', 'BaseFilterSet', 'ChangeLoggedModelFilterSet', 'NetBoxModelFilterSet', @@ -345,3 +348,28 @@ class NestedGroupModelFilterSet(NetBoxModelFilterSet): ) return queryset + + +class AttributeFiltersMixin: + attributes_field_name = 'attribute_data' + attribute_filter_prefix = 'attr_' + + def __init__(self, data=None, queryset=None, *, request=None, prefix=None): + self.attr_filters = {} + + # Extract JSONField-based filters from the incoming data + if data is not None: + for key, value in data.items(): + if key.startswith(self.attribute_filter_prefix): + # Attempt to case the value to a native JSON type + try: + value = json.loads(value) + except (ValueError, json.JSONDecodeError): + pass + field = f'{self.attributes_field_name}__{key.split(self.attribute_filter_prefix, 1)[1]}' + self.attr_filters[field] = value + + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) + + def filter_queryset(self, queryset): + return super().filter_queryset(queryset).filter(**self.attr_filters)