1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

functional dynamic filter lookups

This commit is contained in:
John Anderson
2020-02-09 17:46:21 -05:00
parent a311002141
commit a6b43b30e9
9 changed files with 183 additions and 144 deletions

View File

@ -29,12 +29,14 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilte
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='circuits__terminations__site__region__in', field_name='circuits__terminations__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='circuits__terminations__site__region__in', field_name='circuits__terminations__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -120,12 +122,14 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='terminations__site__region__in', field_name='terminations__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='terminations__site__region__in', field_name='terminations__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )

View File

@ -7,7 +7,7 @@ from tenancy.models import Tenant
from utilities.constants import COLOR_CHOICES from utilities.constants import COLOR_CHOICES
from utilities.filters import ( from utilities.filters import (
BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
BaseFilterSet, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
) )
from virtualization.models import Cluster from virtualization.models import Cluster
from .choices import * from .choices import *
@ -92,12 +92,14 @@ class SiteFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='region__in', field_name='region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='region__in', field_name='region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -134,12 +136,14 @@ class SiteFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -177,12 +181,14 @@ class RackFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -402,7 +408,7 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFil
return queryset.exclude(device_bay_templates__isnull=value) return queryset.exclude(device_bay_templates__isnull=value)
class DeviceTypeComponentFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
devicetype_id = django_filters.ModelMultipleChoiceFilter( devicetype_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
field_name='device_type_id', field_name='device_type_id',
@ -410,56 +416,56 @@ class DeviceTypeComponentFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
) )
class ConsolePortTemplateFilterSet(DeviceTypeComponentFilterSet): class ConsolePortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = ConsolePortTemplate model = ConsolePortTemplate
fields = ['id', 'name', 'type'] fields = ['id', 'name', 'type']
class ConsoleServerPortTemplateFilterSet(DeviceTypeComponentFilterSet): class ConsoleServerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = ConsoleServerPortTemplate model = ConsoleServerPortTemplate
fields = ['id', 'name', 'type'] fields = ['id', 'name', 'type']
class PowerPortTemplateFilterSet(DeviceTypeComponentFilterSet): class PowerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = PowerPortTemplate model = PowerPortTemplate
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw'] fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
class PowerOutletTemplateFilterSet(DeviceTypeComponentFilterSet): class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = PowerOutletTemplate model = PowerOutletTemplate
fields = ['id', 'name', 'type', 'feed_leg'] fields = ['id', 'name', 'type', 'feed_leg']
class InterfaceTemplateFilterSet(DeviceTypeComponentFilterSet): class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = ['id', 'name', 'type', 'mgmt_only'] fields = ['id', 'name', 'type', 'mgmt_only']
class FrontPortTemplateFilterSet(DeviceTypeComponentFilterSet): class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = FrontPortTemplate model = FrontPortTemplate
fields = ['id', 'name', 'type'] fields = ['id', 'name', 'type']
class RearPortTemplateFilterSet(DeviceTypeComponentFilterSet): class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = RearPortTemplate model = RearPortTemplate
fields = ['id', 'name', 'type', 'positions'] fields = ['id', 'name', 'type', 'positions']
class DeviceBayTemplateFilterSet(DeviceTypeComponentFilterSet): class DeviceBayTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
class Meta: class Meta:
model = DeviceBayTemplate model = DeviceBayTemplate
@ -538,12 +544,14 @@ class DeviceFilterSet(BaseFilterSet, LocalConfigContextFilterSet, CustomFieldFil
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -690,19 +698,21 @@ class DeviceFilterSet(BaseFilterSet, LocalConfigContextFilterSet, CustomFieldFil
return queryset.exclude(device_bays__isnull=value) return queryset.exclude(device_bays__isnull=value)
class DeviceComponentFilterSet(BaseFilterSet): class DeviceComponentFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region__in', field_name='device__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region__in', field_name='device__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -738,7 +748,7 @@ class DeviceComponentFilterSet(BaseFilterSet):
) )
class ConsolePortFilterSet(DeviceComponentFilterSet): class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
null_value=None null_value=None
@ -754,7 +764,7 @@ class ConsolePortFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'description', 'connection_status'] fields = ['id', 'name', 'description', 'connection_status']
class ConsoleServerPortFilterSet(DeviceComponentFilterSet): class ConsoleServerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
null_value=None null_value=None
@ -770,7 +780,7 @@ class ConsoleServerPortFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'description', 'connection_status'] fields = ['id', 'name', 'description', 'connection_status']
class PowerPortFilterSet(DeviceComponentFilterSet): class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
null_value=None null_value=None
@ -786,7 +796,7 @@ class PowerPortFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'maximum_draw', 'allocated_draw', 'description', 'connection_status'] fields = ['id', 'name', 'maximum_draw', 'allocated_draw', 'description', 'connection_status']
class PowerOutletFilterSet(DeviceComponentFilterSet): class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
null_value=None null_value=None
@ -802,7 +812,7 @@ class PowerOutletFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'feed_leg', 'description', 'connection_status'] fields = ['id', 'name', 'feed_leg', 'description', 'connection_status']
class InterfaceFilterSet(DeviceComponentFilterSet): class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -900,7 +910,7 @@ class InterfaceFilterSet(DeviceComponentFilterSet):
}.get(value, queryset.none()) }.get(value, queryset.none())
class FrontPortFilterSet(DeviceComponentFilterSet): class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
field_name='cable', field_name='cable',
lookup_expr='isnull', lookup_expr='isnull',
@ -912,7 +922,7 @@ class FrontPortFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'type', 'description'] fields = ['id', 'name', 'type', 'description']
class RearPortFilterSet(DeviceComponentFilterSet): class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
field_name='cable', field_name='cable',
lookup_expr='isnull', lookup_expr='isnull',
@ -924,26 +934,28 @@ class RearPortFilterSet(DeviceComponentFilterSet):
fields = ['id', 'name', 'type', 'positions', 'description'] fields = ['id', 'name', 'type', 'positions', 'description']
class DeviceBayFilterSet(DeviceComponentFilterSet): class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet):
class Meta: class Meta:
model = DeviceBay model = DeviceBay
fields = ['id', 'name', 'description'] fields = ['id', 'name', 'description']
class InventoryItemFilterSet(DeviceComponentFilterSet): class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region__in', field_name='device__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region__in', field_name='device__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -1009,12 +1021,14 @@ class VirtualChassisFilterSet(BaseFilterSet):
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='master__site__region__in', field_name='master__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='master__site__region__in', field_name='master__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -1226,12 +1240,14 @@ class PowerPanelFilterSet(BaseFilterSet):
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -1275,12 +1291,14 @@ class PowerFeedFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='power_panel__site__region__in', field_name='power_panel__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='power_panel__site__region__in', field_name='power_panel__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )

View File

@ -235,7 +235,7 @@ class ConfigContextFilterSet(BaseFilterSet):
# Filter for Local Config Context Data # Filter for Local Config Context Data
# #
class LocalConfigContextFilterSet(BaseFilterSet): class LocalConfigContextFilterSet(django_filters.FilterSet):
local_context_data = django_filters.BooleanFilter( local_context_data = django_filters.BooleanFilter(
method='_local_context_data', method='_local_context_data',
label='Has local config context data', label='Has local config context data',

View File

@ -28,8 +28,8 @@ class GraphTestCase(TestCase):
Graph.objects.bulk_create(graphs) Graph.objects.bulk_create(graphs)
def test_name(self): def test_name(self):
params = {'name': 'Graph 1'} params = {'name': ['Graph 1', 'Graph 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_type(self): def test_type(self):
content_type = ContentType.objects.filter(GRAPH_MODELS).first() content_type = ContentType.objects.filter(GRAPH_MODELS).first()
@ -59,8 +59,8 @@ class ExportTemplateTestCase(TestCase):
ExportTemplate.objects.bulk_create(export_templates) ExportTemplate.objects.bulk_create(export_templates)
def test_name(self): def test_name(self):
params = {'name': 'Export Template 1'} params = {'name': ['Export Template 1', 'Export Template 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_content_type(self): def test_content_type(self):
params = {'content_type': ContentType.objects.get(model='site').pk} params = {'content_type': ContentType.objects.get(model='site').pk}
@ -154,8 +154,8 @@ class ConfigContextTestCase(TestCase):
c.tenants.set([tenants[i]]) c.tenants.set([tenants[i]])
def test_name(self): def test_name(self):
params = {'name': 'Config Context 1'} params = {'name': ['Config Context 1', 'Config Context 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_is_active(self): def test_is_active(self):
params = {'is_active': True} params = {'is_active': True}

View File

@ -8,7 +8,8 @@ from dcim.models import Device, Interface, Region, Site
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from utilities.filters import ( from utilities.filters import (
MultiValueCharFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, BaseFilterSet, MultiValueCharFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter,
TreeNodeMultipleChoiceFilter,
) )
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .choices import * from .choices import *
@ -28,7 +29,7 @@ __all__ = (
) )
class VRFFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -53,7 +54,7 @@ class VRFFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
fields = ['name', 'rd', 'enforce_unique'] fields = ['name', 'rd', 'enforce_unique']
class RIRFilterSet(NameSlugSearchFilterSet): class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -64,7 +65,7 @@ class RIRFilterSet(NameSlugSearchFilterSet):
fields = ['name', 'slug', 'is_private'] fields = ['name', 'slug', 'is_private']
class AggregateFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet): class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -114,7 +115,7 @@ class AggregateFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
return queryset.none() return queryset.none()
class RoleFilterSet(NameSlugSearchFilterSet): class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -125,7 +126,7 @@ class RoleFilterSet(NameSlugSearchFilterSet):
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class PrefixFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -166,12 +167,14 @@ class PrefixFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -273,7 +276,7 @@ class PrefixFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
return queryset.filter(prefix__net_mask_length=value) return queryset.filter(prefix__net_mask_length=value)
class IPAddressFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -395,15 +398,17 @@ class IPAddressFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedF
return queryset.exclude(interface__isnull=value) return queryset.exclude(interface__isnull=value)
class VLANGroupFilterSet(NameSlugSearchFilterSet): class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -423,7 +428,7 @@ class VLANGroupFilterSet(NameSlugSearchFilterSet):
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class VLANFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -434,12 +439,14 @@ class VLANFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilter
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -494,7 +501,7 @@ class VLANFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilter
return queryset.filter(qs_filter) return queryset.filter(qs_filter)
class ServiceFilterSet(CreatedUpdatedFilterSet): class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from dcim.models import Device from dcim.models import Device
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, NumericInFilter, TagFilter
from .models import Secret, SecretRole from .models import Secret, SecretRole
@ -13,14 +13,14 @@ __all__ = (
) )
class SecretRoleFilterSet(NameSlugSearchFilterSet): class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class Meta: class Meta:
model = SecretRole model = SecretRole
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class SecretFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet): class SecretFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'

View File

@ -13,14 +13,14 @@ __all__ = (
) )
class TenantGroupFilterSet(NameSlugSearchFilterSet): class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class Meta: class Meta:
model = TenantGroup model = TenantGroup
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class TenantFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet): class TenantFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'

View File

@ -1,9 +1,10 @@
import django_filters import django_filters
from copy import deepcopy
from dcim.forms import MACAddressField from dcim.forms import MACAddressField
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django_filters.utils import get_model_field from django_filters.utils import get_model_field, resolve_field
from extras.models import Tag from extras.models import Tag
from utilities.constants import ( from utilities.constants import (
@ -120,13 +121,62 @@ class BaseFilterSet(django_filters.FilterSet):
""" """
A base filterset which provides common functionaly to all NetBox filtersets A base filterset which provides common functionaly to all NetBox filtersets
""" """
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
FILTER_DEFAULTS.update({
models.AutoField: {
'filter_class': MultiValueNumberFilter
},
models.CharField: {
'filter_class': MultiValueCharFilter
},
models.DateField: {
'filter_class': MultiValueDateFilter
},
models.DateTimeField: {
'filter_class': MultiValueDateTimeFilter
},
models.DecimalField: {
'filter_class': MultiValueNumberFilter
},
models.EmailField: {
'filter_class': MultiValueCharFilter
},
models.FloatField: {
'filter_class': MultiValueNumberFilter
},
models.IntegerField: {
'filter_class': MultiValueNumberFilter
},
models.PositiveIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.PositiveSmallIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.SlugField: {
'filter_class': MultiValueCharFilter
},
models.SmallIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.TimeField: {
'filter_class': MultiValueTimeFilter
},
models.URLField: {
'filter_class': MultiValueCharFilter
},
MACAddressField: {
'filter_class': MultiValueMACAddressFilter
},
})
@classmethod @classmethod
def get_filters(cls): def get_filters(cls):
""" """
Override filter generation to support dynamic lookup expressions for certain filter types. Override filter generation to support dynamic lookup expressions for certain filter types.
For specific filter types, new filters are created based on defined lookup expressions in For specific filter types, new filters are created based on defined lookup expressions in
the form `<field_name>_<lookup_expr>` the form `<field_name>__<lookup_expr>`
""" """
filters = super().get_filters() filters = super().get_filters()
@ -136,7 +186,7 @@ class BaseFilterSet(django_filters.FilterSet):
# It the filter makes use of a custom filter method or lookup expression skip it # It the filter makes use of a custom filter method or lookup expression skip it
# as we cannot sanely handle these cases in a generic mannor # as we cannot sanely handle these cases in a generic mannor
if existing_filter.method is not None or existing_filter.lookup_expr != 'exact': if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
continue continue
# Choose the lookup expression map based on the filter type # Choose the lookup expression map based on the filter type
@ -169,7 +219,7 @@ class BaseFilterSet(django_filters.FilterSet):
) )
else: else:
# Do no augment any other filter types with more lookup expressions # Do not augment any other filter types with more lookup expressions
continue continue
# Get properties of the existing filter for later use # Get properties of the existing filter for later use
@ -178,24 +228,29 @@ class BaseFilterSet(django_filters.FilterSet):
# Create new filters for each lookup expression in the map # Create new filters for each lookup expression in the map
for lookup_name, lookup_expr in lookup_map.items(): for lookup_name, lookup_expr in lookup_map.items():
new_filter_name = '{}_{}'.format(existing_filter_name, lookup_name) new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
try: try:
print(existing_filter_name) if existing_filter_name in cls.declared_filters:
new_filter = cls.filter_for_field(field, field_name, lookup_expr) resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
new_filter = type(existing_filter)(
field_name=field_name,
lookup_expr=lookup_expr,
label=existing_filter.label,
exclude=existing_filter.exclude,
distinct=existing_filter.distinct,
**existing_filter.extra
)
else:
new_filter = cls.filter_for_field(field, field_name, lookup_expr)
except django_filters.exceptions.FieldLookupError: except django_filters.exceptions.FieldLookupError:
# The filter could not be created because the lookup expression is not supported # The filter could not be created because the lookup expression is not supported on the field
continue continue
help_text = FILTER_LOOKUP_HELP_TEXT_MAP[lookup_expr]
if lookup_name.startswith('n'): if lookup_name.startswith('n'):
# This is a negation filter which requires a queryselt.exclud() clause # This is a negation filter which requires a queryselt.exclud() clause
new_filter.exclude = True new_filter.exclude = True
help_text = 'negated {}'.format(help_text)
new_filter.extra = existing_filter.extra
new_filter.extra['help_text'] = '{} - {}'.format(field_name, help_text)
new_filters[new_filter_name] = new_filter new_filters[new_filter_name] = new_filter
filters.update(new_filters) filters.update(new_filters)
@ -218,54 +273,3 @@ class NameSlugSearchFilterSet(django_filters.FilterSet):
models.Q(name__icontains=value) | models.Q(name__icontains=value) |
models.Q(slug__icontains=value) models.Q(slug__icontains=value)
) )
#
# Update default filters
#
FILTER_DEFAULTS = django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS
FILTER_DEFAULTS.update({
models.AutoField: {
'filter_class': MultiValueNumberFilter
},
models.CharField: {
'filter_class': MultiValueCharFilter
},
models.DateField: {
'filter_class': MultiValueDateFilter
},
models.DateTimeField: {
'filter_class': MultiValueDateTimeFilter
},
models.DecimalField: {
'filter_class': MultiValueNumberFilter
},
models.EmailField: {
'filter_class': MultiValueCharFilter
},
models.FloatField: {
'filter_class': MultiValueNumberFilter
},
models.IntegerField: {
'filter_class': MultiValueNumberFilter
},
models.PositiveIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.PositiveSmallIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.SlugField: {
'filter_class': MultiValueCharFilter
},
models.SmallIntegerField: {
'filter_class': MultiValueNumberFilter
},
models.TimeField: {
'filter_class': MultiValueTimeFilter
},
models.URLField: {
'filter_class': MultiValueCharFilter
},
})

View File

@ -6,7 +6,8 @@ from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalC
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import ( from utilities.filters import (
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, BaseFilterSet, MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter,
TreeNodeMultipleChoiceFilter,
) )
from .choices import * from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -20,21 +21,21 @@ __all__ = (
) )
class ClusterTypeFilterSet(NameSlugSearchFilterSet): class ClusterTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class Meta: class Meta:
model = ClusterType model = ClusterType
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class ClusterGroupFilterSet(NameSlugSearchFilterSet): class ClusterGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class Meta: class Meta:
model = ClusterGroup model = ClusterGroup
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class ClusterFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet): class ClusterFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter( id__in = NumericInFilter(
field_name='id', field_name='id',
lookup_expr='in' lookup_expr='in'
@ -45,12 +46,14 @@ class ClusterFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region__in', field_name='site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -104,6 +107,7 @@ class ClusterFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
class VirtualMachineFilterSet( class VirtualMachineFilterSet(
BaseFilterSet,
LocalConfigContextFilterSet, LocalConfigContextFilterSet,
TenancyFilterSet, TenancyFilterSet,
CustomFieldFilterSet, CustomFieldFilterSet,
@ -149,12 +153,14 @@ class VirtualMachineFilterSet(
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='cluster__site__region__in', field_name='cluster__site__region',
lookup_expr='in',
label='Region (ID)', label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='cluster__site__region__in', field_name='cluster__site__region',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
@ -208,7 +214,7 @@ class VirtualMachineFilterSet(
) )
class InterfaceFilterSet(django_filters.FilterSet): class InterfaceFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',