From f2c16fbf3c318f0aac8045b9fa5fa443e2d9f516 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 27 Jul 2017 09:53:41 -0400 Subject: [PATCH] Closes #893: Allow filtering by null values for NullCharacterFields (e.g. return only unnamed devices) --- netbox/dcim/filters.py | 12 ++++++++---- netbox/utilities/filters.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index e3579085a..ed6106c86 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -8,7 +8,7 @@ from django.db.models import Q from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant -from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter +from utilities.filters import NullableCharFieldFilter, NullableModelMultipleChoiceFilter, NumericInFilter from .models import ( ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection, @@ -113,6 +113,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet): method='search', label='Search', ) + facility_id = NullableCharFieldFilter() site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), label='Site (ID)', @@ -156,7 +157,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet): class Meta: model = Rack - fields = ['facility_id', 'type', 'width', 'u_height', 'desc_units'] + fields = ['type', 'width', 'u_height', 'desc_units'] def search(self, queryset, name, value): if not value.strip(): @@ -383,6 +384,8 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Platform (slug)', ) + name = NullableCharFieldFilter() + asset_tag = NullableCharFieldFilter() site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), label='Site (ID)', @@ -439,7 +442,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): class Meta: model = Device - fields = ['name', 'serial', 'asset_tag'] + fields = ['serial'] def search(self, queryset, name, value): if not value.strip(): @@ -596,10 +599,11 @@ class InventoryItemFilter(DeviceComponentFilterSet): to_field_name='slug', label='Manufacturer (slug)', ) + asset_tag = NullableCharFieldFilter() class Meta: model = InventoryItem - fields = ['name', 'part_id', 'serial', 'asset_tag', 'discovered'] + fields = ['name', 'part_id', 'serial', 'discovered'] class ConsoleConnectionFilter(django_filters.FilterSet): diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 5929c3ff1..5bd635a46 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -19,6 +19,16 @@ class NumericInFilter(django_filters.BaseInFilter, django_filters.NumberFilter): pass +class NullableCharFieldFilter(django_filters.CharFilter): + null_value = 'NULL' + + def filter(self, qs, value): + if value != self.null_value: + return super(NullableCharFieldFilter, self).filter(qs, value) + qs = self.get_method(qs)(**{'{}__isnull'.format(self.name): True}) + return qs.distinct() if self.distinct else qs + + class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField): """ This field operates like a normal ModelMultipleChoiceField except that it allows for one additional choice which is