Просмотр исходного кода

Merge pull request #6315 from netbox-community/6314-filterset-cleanup

Closes #6314: FilterSet cleanup
Jeremy Stretch 4 лет назад
Родитель
Сommit
b93570eeb0
37 измененных файлов с 933 добавлено и 924 удалено
  1. 6 6
      netbox/circuits/api/views.py
  2. 10 11
      netbox/circuits/filtersets.py
  3. 1 1
      netbox/circuits/tests/test_filters.py
  4. 11 11
      netbox/circuits/views.py
  5. 37 37
      netbox/dcim/api/views.py
  6. 46 60
      netbox/dcim/filtersets.py
  7. 1 1
      netbox/dcim/tests/test_filters.py
  8. 80 80
      netbox/dcim/views.py
  9. 12 12
      netbox/extras/api/views.py
  10. 10 358
      netbox/extras/filters.py
  11. 340 0
      netbox/extras/filtersets.py
  12. 1 1
      netbox/extras/tests/test_customfields.py
  13. 1 1
      netbox/extras/tests/test_filters.py
  14. 8 8
      netbox/extras/views.py
  15. 11 11
      netbox/ipam/api/views.py
  16. 14 14
      netbox/ipam/filtersets.py
  17. 1 1
      netbox/ipam/tests/test_filters.py
  18. 30 30
      netbox/ipam/views.py
  19. 6 6
      netbox/netbox/constants.py
  20. 238 0
      netbox/netbox/filtersets.py
  21. 3 3
      netbox/secrets/api/views.py
  22. 4 4
      netbox/secrets/filtersets.py
  23. 1 1
      netbox/secrets/tests/test_filters.py
  24. 6 6
      netbox/secrets/views.py
  25. 3 3
      netbox/tenancy/api/views.py
  26. 5 4
      netbox/tenancy/filtersets.py
  27. 1 1
      netbox/tenancy/tests/test_filters.py
  28. 5 5
      netbox/tenancy/views.py
  29. 4 4
      netbox/users/api/views.py
  30. 1 1
      netbox/users/filtersets.py
  31. 1 1
      netbox/users/tests/test_filters.py
  32. 2 204
      netbox/utilities/filters.py
  33. 5 3
      netbox/utilities/tests/test_filters.py
  34. 6 6
      netbox/virtualization/api/views.py
  35. 10 17
      netbox/virtualization/filtersets.py
  36. 1 1
      netbox/virtualization/tests/test_filters.py
  37. 11 11
      netbox/virtualization/views.py

+ 6 - 6
netbox/circuits/api/views.py

@@ -1,6 +1,6 @@
 from rest_framework.routers import APIRootView
 from rest_framework.routers import APIRootView
 
 
-from circuits import filters
+from circuits import filtersets
 from circuits.models import *
 from circuits.models import *
 from dcim.api.views import PassThroughPortMixin
 from dcim.api.views import PassThroughPortMixin
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
@@ -26,7 +26,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
         circuit_count=count_related(Circuit, 'provider')
         circuit_count=count_related(Circuit, 'provider')
     )
     )
     serializer_class = serializers.ProviderSerializer
     serializer_class = serializers.ProviderSerializer
-    filterset_class = filters.ProviderFilterSet
+    filterset_class = filtersets.ProviderFilterSet
 
 
 
 
 #
 #
@@ -38,7 +38,7 @@ class CircuitTypeViewSet(CustomFieldModelViewSet):
         circuit_count=count_related(Circuit, 'type')
         circuit_count=count_related(Circuit, 'type')
     )
     )
     serializer_class = serializers.CircuitTypeSerializer
     serializer_class = serializers.CircuitTypeSerializer
-    filterset_class = filters.CircuitTypeFilterSet
+    filterset_class = filtersets.CircuitTypeFilterSet
 
 
 
 
 #
 #
@@ -50,7 +50,7 @@ class CircuitViewSet(CustomFieldModelViewSet):
         'type', 'tenant', 'provider', 'termination_a', 'termination_z'
         'type', 'tenant', 'provider', 'termination_a', 'termination_z'
     ).prefetch_related('tags')
     ).prefetch_related('tags')
     serializer_class = serializers.CircuitSerializer
     serializer_class = serializers.CircuitSerializer
-    filterset_class = filters.CircuitFilterSet
+    filterset_class = filtersets.CircuitFilterSet
 
 
 
 
 #
 #
@@ -62,7 +62,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet):
         'circuit', 'site', 'provider_network', 'cable'
         'circuit', 'site', 'provider_network', 'cable'
     )
     )
     serializer_class = serializers.CircuitTerminationSerializer
     serializer_class = serializers.CircuitTerminationSerializer
-    filterset_class = filters.CircuitTerminationFilterSet
+    filterset_class = filtersets.CircuitTerminationFilterSet
     brief_prefetch_fields = ['circuit']
     brief_prefetch_fields = ['circuit']
 
 
 
 
@@ -73,4 +73,4 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet):
 class ProviderNetworkViewSet(CustomFieldModelViewSet):
 class ProviderNetworkViewSet(CustomFieldModelViewSet):
     queryset = ProviderNetwork.objects.prefetch_related('tags')
     queryset = ProviderNetwork.objects.prefetch_related('tags')
     serializer_class = serializers.ProviderNetworkSerializer
     serializer_class = serializers.ProviderNetworkSerializer
-    filterset_class = filters.ProviderNetworkFilterSet
+    filterset_class = filtersets.ProviderNetworkFilterSet

+ 10 - 11
netbox/circuits/filters.py → netbox/circuits/filtersets.py

@@ -1,13 +1,12 @@
 import django_filters
 import django_filters
 from django.db.models import Q
 from django.db.models import Q
 
 
-from dcim.filters import CableTerminationFilterSet
+from dcim.filtersets import CableTerminationFilterSet
 from dcim.models import Region, Site, SiteGroup
 from dcim.models import Region, Site, SiteGroup
-from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
-from tenancy.filters import TenancyFilterSet
-from utilities.filters import (
-    BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
-)
+from extras.filters import TagFilter
+from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
+from tenancy.filtersets import TenancyFilterSet
+from utilities.filters import TreeNodeMultipleChoiceFilter
 from .choices import *
 from .choices import *
 from .models import *
 from .models import *
 
 
@@ -20,7 +19,7 @@ __all__ = (
 )
 )
 
 
 
 
-class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class ProviderFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -80,7 +79,7 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated
         )
         )
 
 
 
 
-class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class ProviderNetworkFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -110,14 +109,14 @@ class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, Created
         ).distinct()
         ).distinct()
 
 
 
 
-class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class CircuitTypeFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = CircuitType
         model = CircuitType
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
+class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -207,7 +206,7 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe
         ).distinct()
         ).distinct()
 
 
 
 
-class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableTerminationFilterSet):
+class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/circuits/tests/test_filters.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 from django.test import TestCase
 
 
 from circuits.choices import *
 from circuits.choices import *
-from circuits.filters import *
+from circuits.filtersets import *
 from circuits.models import *
 from circuits.models import *
 from dcim.models import Cable, Region, Site, SiteGroup
 from dcim.models import Cable, Region, Site, SiteGroup
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup

+ 11 - 11
netbox/circuits/views.py

@@ -7,7 +7,7 @@ from netbox.views import generic
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from utilities.utils import count_related
 from utilities.utils import count_related
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .choices import CircuitTerminationSideChoices
 from .choices import CircuitTerminationSideChoices
 from .models import *
 from .models import *
 
 
@@ -20,7 +20,7 @@ class ProviderListView(generic.ObjectListView):
     queryset = Provider.objects.annotate(
     queryset = Provider.objects.annotate(
         count_circuits=count_related(Circuit, 'provider')
         count_circuits=count_related(Circuit, 'provider')
     )
     )
-    filterset = filters.ProviderFilterSet
+    filterset = filtersets.ProviderFilterSet
     filterset_form = forms.ProviderFilterForm
     filterset_form = forms.ProviderFilterForm
     table = tables.ProviderTable
     table = tables.ProviderTable
 
 
@@ -63,7 +63,7 @@ class ProviderBulkEditView(generic.BulkEditView):
     queryset = Provider.objects.annotate(
     queryset = Provider.objects.annotate(
         count_circuits=count_related(Circuit, 'provider')
         count_circuits=count_related(Circuit, 'provider')
     )
     )
-    filterset = filters.ProviderFilterSet
+    filterset = filtersets.ProviderFilterSet
     table = tables.ProviderTable
     table = tables.ProviderTable
     form = forms.ProviderBulkEditForm
     form = forms.ProviderBulkEditForm
 
 
@@ -72,7 +72,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
     queryset = Provider.objects.annotate(
     queryset = Provider.objects.annotate(
         count_circuits=count_related(Circuit, 'provider')
         count_circuits=count_related(Circuit, 'provider')
     )
     )
-    filterset = filters.ProviderFilterSet
+    filterset = filtersets.ProviderFilterSet
     table = tables.ProviderTable
     table = tables.ProviderTable
 
 
 
 
@@ -82,7 +82,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
 
 
 class ProviderNetworkListView(generic.ObjectListView):
 class ProviderNetworkListView(generic.ObjectListView):
     queryset = ProviderNetwork.objects.all()
     queryset = ProviderNetwork.objects.all()
-    filterset = filters.ProviderNetworkFilterSet
+    filterset = filtersets.ProviderNetworkFilterSet
     filterset_form = forms.ProviderNetworkFilterForm
     filterset_form = forms.ProviderNetworkFilterForm
     table = tables.ProviderNetworkTable
     table = tables.ProviderNetworkTable
 
 
@@ -125,14 +125,14 @@ class ProviderNetworkBulkImportView(generic.BulkImportView):
 
 
 class ProviderNetworkBulkEditView(generic.BulkEditView):
 class ProviderNetworkBulkEditView(generic.BulkEditView):
     queryset = ProviderNetwork.objects.all()
     queryset = ProviderNetwork.objects.all()
-    filterset = filters.ProviderNetworkFilterSet
+    filterset = filtersets.ProviderNetworkFilterSet
     table = tables.ProviderNetworkTable
     table = tables.ProviderNetworkTable
     form = forms.ProviderNetworkBulkEditForm
     form = forms.ProviderNetworkBulkEditForm
 
 
 
 
 class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
 class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
     queryset = ProviderNetwork.objects.all()
     queryset = ProviderNetwork.objects.all()
-    filterset = filters.ProviderNetworkFilterSet
+    filterset = filtersets.ProviderNetworkFilterSet
     table = tables.ProviderNetworkTable
     table = tables.ProviderNetworkTable
 
 
 
 
@@ -183,7 +183,7 @@ class CircuitTypeBulkEditView(generic.BulkEditView):
     queryset = CircuitType.objects.annotate(
     queryset = CircuitType.objects.annotate(
         circuit_count=count_related(Circuit, 'type')
         circuit_count=count_related(Circuit, 'type')
     )
     )
-    filterset = filters.CircuitTypeFilterSet
+    filterset = filtersets.CircuitTypeFilterSet
     table = tables.CircuitTypeTable
     table = tables.CircuitTypeTable
     form = forms.CircuitTypeBulkEditForm
     form = forms.CircuitTypeBulkEditForm
 
 
@@ -203,7 +203,7 @@ class CircuitListView(generic.ObjectListView):
     queryset = Circuit.objects.prefetch_related(
     queryset = Circuit.objects.prefetch_related(
         'provider', 'type', 'tenant', 'termination_a', 'termination_z'
         'provider', 'type', 'tenant', 'termination_a', 'termination_z'
     )
     )
-    filterset = filters.CircuitFilterSet
+    filterset = filtersets.CircuitFilterSet
     filterset_form = forms.CircuitFilterForm
     filterset_form = forms.CircuitFilterForm
     table = tables.CircuitTable
     table = tables.CircuitTable
 
 
@@ -252,7 +252,7 @@ class CircuitBulkEditView(generic.BulkEditView):
     queryset = Circuit.objects.prefetch_related(
     queryset = Circuit.objects.prefetch_related(
         'provider', 'type', 'tenant', 'terminations'
         'provider', 'type', 'tenant', 'terminations'
     )
     )
-    filterset = filters.CircuitFilterSet
+    filterset = filtersets.CircuitFilterSet
     table = tables.CircuitTable
     table = tables.CircuitTable
     form = forms.CircuitBulkEditForm
     form = forms.CircuitBulkEditForm
 
 
@@ -261,7 +261,7 @@ class CircuitBulkDeleteView(generic.BulkDeleteView):
     queryset = Circuit.objects.prefetch_related(
     queryset = Circuit.objects.prefetch_related(
         'provider', 'type', 'tenant', 'terminations'
         'provider', 'type', 'tenant', 'terminations'
     )
     )
-    filterset = filters.CircuitFilterSet
+    filterset = filtersets.CircuitFilterSet
     table = tables.CircuitTable
     table = tables.CircuitTable
 
 
 
 

+ 37 - 37
netbox/dcim/api/views.py

@@ -16,7 +16,7 @@ from rest_framework.routers import APIRootView
 from rest_framework.viewsets import GenericViewSet, ViewSet
 from rest_framework.viewsets import GenericViewSet, ViewSet
 
 
 from circuits.models import Circuit
 from circuits.models import Circuit
-from dcim import filters
+from dcim import filtersets
 from dcim.models import *
 from dcim.models import *
 from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
 from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
 from ipam.models import Prefix, VLAN
 from ipam.models import Prefix, VLAN
@@ -103,7 +103,7 @@ class RegionViewSet(CustomFieldModelViewSet):
         cumulative=True
         cumulative=True
     )
     )
     serializer_class = serializers.RegionSerializer
     serializer_class = serializers.RegionSerializer
-    filterset_class = filters.RegionFilterSet
+    filterset_class = filtersets.RegionFilterSet
 
 
 
 
 #
 #
@@ -119,7 +119,7 @@ class SiteGroupViewSet(CustomFieldModelViewSet):
         cumulative=True
         cumulative=True
     )
     )
     serializer_class = serializers.SiteGroupSerializer
     serializer_class = serializers.SiteGroupSerializer
-    filterset_class = filters.SiteGroupFilterSet
+    filterset_class = filtersets.SiteGroupFilterSet
 
 
 
 
 #
 #
@@ -138,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet):
         virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
         virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
     )
     )
     serializer_class = serializers.SiteSerializer
     serializer_class = serializers.SiteSerializer
-    filterset_class = filters.SiteFilterSet
+    filterset_class = filtersets.SiteFilterSet
 
 
 
 
 #
 #
@@ -160,7 +160,7 @@ class LocationViewSet(CustomFieldModelViewSet):
         cumulative=True
         cumulative=True
     ).prefetch_related('site')
     ).prefetch_related('site')
     serializer_class = serializers.LocationSerializer
     serializer_class = serializers.LocationSerializer
-    filterset_class = filters.LocationFilterSet
+    filterset_class = filtersets.LocationFilterSet
 
 
 
 
 #
 #
@@ -172,7 +172,7 @@ class RackRoleViewSet(CustomFieldModelViewSet):
         rack_count=count_related(Rack, 'role')
         rack_count=count_related(Rack, 'role')
     )
     )
     serializer_class = serializers.RackRoleSerializer
     serializer_class = serializers.RackRoleSerializer
-    filterset_class = filters.RackRoleFilterSet
+    filterset_class = filtersets.RackRoleFilterSet
 
 
 
 
 #
 #
@@ -187,7 +187,7 @@ class RackViewSet(CustomFieldModelViewSet):
         powerfeed_count=count_related(PowerFeed, 'rack')
         powerfeed_count=count_related(PowerFeed, 'rack')
     )
     )
     serializer_class = serializers.RackSerializer
     serializer_class = serializers.RackSerializer
-    filterset_class = filters.RackFilterSet
+    filterset_class = filtersets.RackFilterSet
 
 
     @swagger_auto_schema(
     @swagger_auto_schema(
         responses={200: serializers.RackUnitSerializer(many=True)},
         responses={200: serializers.RackUnitSerializer(many=True)},
@@ -244,7 +244,7 @@ class RackViewSet(CustomFieldModelViewSet):
 class RackReservationViewSet(ModelViewSet):
 class RackReservationViewSet(ModelViewSet):
     queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
     queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
     serializer_class = serializers.RackReservationSerializer
     serializer_class = serializers.RackReservationSerializer
-    filterset_class = filters.RackReservationFilterSet
+    filterset_class = filtersets.RackReservationFilterSet
 
 
     # Assign user from request
     # Assign user from request
     def perform_create(self, serializer):
     def perform_create(self, serializer):
@@ -262,7 +262,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet):
         platform_count=count_related(Platform, 'manufacturer')
         platform_count=count_related(Platform, 'manufacturer')
     )
     )
     serializer_class = serializers.ManufacturerSerializer
     serializer_class = serializers.ManufacturerSerializer
-    filterset_class = filters.ManufacturerFilterSet
+    filterset_class = filtersets.ManufacturerFilterSet
 
 
 
 
 #
 #
@@ -274,7 +274,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
         device_count=count_related(Device, 'device_type')
         device_count=count_related(Device, 'device_type')
     )
     )
     serializer_class = serializers.DeviceTypeSerializer
     serializer_class = serializers.DeviceTypeSerializer
-    filterset_class = filters.DeviceTypeFilterSet
+    filterset_class = filtersets.DeviceTypeFilterSet
     brief_prefetch_fields = ['manufacturer']
     brief_prefetch_fields = ['manufacturer']
 
 
 
 
@@ -285,49 +285,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
 class ConsolePortTemplateViewSet(ModelViewSet):
 class ConsolePortTemplateViewSet(ModelViewSet):
     queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.ConsolePortTemplateSerializer
     serializer_class = serializers.ConsolePortTemplateSerializer
-    filterset_class = filters.ConsolePortTemplateFilterSet
+    filterset_class = filtersets.ConsolePortTemplateFilterSet
 
 
 
 
 class ConsoleServerPortTemplateViewSet(ModelViewSet):
 class ConsoleServerPortTemplateViewSet(ModelViewSet):
     queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.ConsoleServerPortTemplateSerializer
     serializer_class = serializers.ConsoleServerPortTemplateSerializer
-    filterset_class = filters.ConsoleServerPortTemplateFilterSet
+    filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
 
 
 
 
 class PowerPortTemplateViewSet(ModelViewSet):
 class PowerPortTemplateViewSet(ModelViewSet):
     queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.PowerPortTemplateSerializer
     serializer_class = serializers.PowerPortTemplateSerializer
-    filterset_class = filters.PowerPortTemplateFilterSet
+    filterset_class = filtersets.PowerPortTemplateFilterSet
 
 
 
 
 class PowerOutletTemplateViewSet(ModelViewSet):
 class PowerOutletTemplateViewSet(ModelViewSet):
     queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.PowerOutletTemplateSerializer
     serializer_class = serializers.PowerOutletTemplateSerializer
-    filterset_class = filters.PowerOutletTemplateFilterSet
+    filterset_class = filtersets.PowerOutletTemplateFilterSet
 
 
 
 
 class InterfaceTemplateViewSet(ModelViewSet):
 class InterfaceTemplateViewSet(ModelViewSet):
     queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.InterfaceTemplateSerializer
     serializer_class = serializers.InterfaceTemplateSerializer
-    filterset_class = filters.InterfaceTemplateFilterSet
+    filterset_class = filtersets.InterfaceTemplateFilterSet
 
 
 
 
 class FrontPortTemplateViewSet(ModelViewSet):
 class FrontPortTemplateViewSet(ModelViewSet):
     queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.FrontPortTemplateSerializer
     serializer_class = serializers.FrontPortTemplateSerializer
-    filterset_class = filters.FrontPortTemplateFilterSet
+    filterset_class = filtersets.FrontPortTemplateFilterSet
 
 
 
 
 class RearPortTemplateViewSet(ModelViewSet):
 class RearPortTemplateViewSet(ModelViewSet):
     queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.RearPortTemplateSerializer
     serializer_class = serializers.RearPortTemplateSerializer
-    filterset_class = filters.RearPortTemplateFilterSet
+    filterset_class = filtersets.RearPortTemplateFilterSet
 
 
 
 
 class DeviceBayTemplateViewSet(ModelViewSet):
 class DeviceBayTemplateViewSet(ModelViewSet):
     queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
     queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
     serializer_class = serializers.DeviceBayTemplateSerializer
     serializer_class = serializers.DeviceBayTemplateSerializer
-    filterset_class = filters.DeviceBayTemplateFilterSet
+    filterset_class = filtersets.DeviceBayTemplateFilterSet
 
 
 
 
 #
 #
@@ -340,7 +340,7 @@ class DeviceRoleViewSet(CustomFieldModelViewSet):
         virtualmachine_count=count_related(VirtualMachine, 'role')
         virtualmachine_count=count_related(VirtualMachine, 'role')
     )
     )
     serializer_class = serializers.DeviceRoleSerializer
     serializer_class = serializers.DeviceRoleSerializer
-    filterset_class = filters.DeviceRoleFilterSet
+    filterset_class = filtersets.DeviceRoleFilterSet
 
 
 
 
 #
 #
@@ -353,7 +353,7 @@ class PlatformViewSet(CustomFieldModelViewSet):
         virtualmachine_count=count_related(VirtualMachine, 'platform')
         virtualmachine_count=count_related(VirtualMachine, 'platform')
     )
     )
     serializer_class = serializers.PlatformSerializer
     serializer_class = serializers.PlatformSerializer
-    filterset_class = filters.PlatformFilterSet
+    filterset_class = filtersets.PlatformFilterSet
 
 
 
 
 #
 #
@@ -365,7 +365,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
         'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
         'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
         'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
         'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
     )
     )
-    filterset_class = filters.DeviceFilterSet
+    filterset_class = filtersets.DeviceFilterSet
 
 
     def get_serializer_class(self):
     def get_serializer_class(self):
         """
         """
@@ -510,7 +510,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
 class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
 class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
     queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
-    filterset_class = filters.ConsolePortFilterSet
+    filterset_class = filtersets.ConsolePortFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
@@ -519,21 +519,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
         'device', '_path__destination', 'cable', '_cable_peer', 'tags'
         'device', '_path__destination', 'cable', '_cable_peer', 'tags'
     )
     )
     serializer_class = serializers.ConsoleServerPortSerializer
     serializer_class = serializers.ConsoleServerPortSerializer
-    filterset_class = filters.ConsoleServerPortFilterSet
+    filterset_class = filtersets.ConsoleServerPortFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
 class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
     queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
-    filterset_class = filters.PowerPortFilterSet
+    filterset_class = filtersets.PowerPortFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
 class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
     queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
     serializer_class = serializers.PowerOutletSerializer
     serializer_class = serializers.PowerOutletSerializer
-    filterset_class = filters.PowerOutletFilterSet
+    filterset_class = filtersets.PowerOutletFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
@@ -542,35 +542,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
         'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
         'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
     )
     )
     serializer_class = serializers.InterfaceSerializer
     serializer_class = serializers.InterfaceSerializer
-    filterset_class = filters.InterfaceFilterSet
+    filterset_class = filtersets.InterfaceFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
 class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
     queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
     queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
     serializer_class = serializers.FrontPortSerializer
     serializer_class = serializers.FrontPortSerializer
-    filterset_class = filters.FrontPortFilterSet
+    filterset_class = filtersets.FrontPortFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
 class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
     queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
     queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
     serializer_class = serializers.RearPortSerializer
     serializer_class = serializers.RearPortSerializer
-    filterset_class = filters.RearPortFilterSet
+    filterset_class = filtersets.RearPortFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class DeviceBayViewSet(ModelViewSet):
 class DeviceBayViewSet(ModelViewSet):
     queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
     queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
     serializer_class = serializers.DeviceBaySerializer
     serializer_class = serializers.DeviceBaySerializer
-    filterset_class = filters.DeviceBayFilterSet
+    filterset_class = filtersets.DeviceBayFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
 class InventoryItemViewSet(ModelViewSet):
 class InventoryItemViewSet(ModelViewSet):
     queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
     queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
     serializer_class = serializers.InventoryItemSerializer
     serializer_class = serializers.InventoryItemSerializer
-    filterset_class = filters.InventoryItemFilterSet
+    filterset_class = filtersets.InventoryItemFilterSet
     brief_prefetch_fields = ['device']
     brief_prefetch_fields = ['device']
 
 
 
 
@@ -583,7 +583,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
         _path__destination_id__isnull=False
         _path__destination_id__isnull=False
     )
     )
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
-    filterset_class = filters.ConsoleConnectionFilterSet
+    filterset_class = filtersets.ConsoleConnectionFilterSet
 
 
 
 
 class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
 class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
@@ -591,7 +591,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
         _path__destination_id__isnull=False
         _path__destination_id__isnull=False
     )
     )
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
-    filterset_class = filters.PowerConnectionFilterSet
+    filterset_class = filtersets.PowerConnectionFilterSet
 
 
 
 
 class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
 class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
@@ -603,7 +603,7 @@ class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
         pk__lt=F('_path__destination_id')
         pk__lt=F('_path__destination_id')
     )
     )
     serializer_class = serializers.InterfaceConnectionSerializer
     serializer_class = serializers.InterfaceConnectionSerializer
-    filterset_class = filters.InterfaceConnectionFilterSet
+    filterset_class = filtersets.InterfaceConnectionFilterSet
 
 
 
 
 #
 #
@@ -616,7 +616,7 @@ class CableViewSet(ModelViewSet):
         'termination_a', 'termination_b'
         'termination_a', 'termination_b'
     )
     )
     serializer_class = serializers.CableSerializer
     serializer_class = serializers.CableSerializer
-    filterset_class = filters.CableFilterSet
+    filterset_class = filtersets.CableFilterSet
 
 
 
 
 #
 #
@@ -628,7 +628,7 @@ class VirtualChassisViewSet(ModelViewSet):
         member_count=count_related(Device, 'virtual_chassis')
         member_count=count_related(Device, 'virtual_chassis')
     )
     )
     serializer_class = serializers.VirtualChassisSerializer
     serializer_class = serializers.VirtualChassisSerializer
-    filterset_class = filters.VirtualChassisFilterSet
+    filterset_class = filtersets.VirtualChassisFilterSet
     brief_prefetch_fields = ['master']
     brief_prefetch_fields = ['master']
 
 
 
 
@@ -643,7 +643,7 @@ class PowerPanelViewSet(ModelViewSet):
         powerfeed_count=count_related(PowerFeed, 'power_panel')
         powerfeed_count=count_related(PowerFeed, 'power_panel')
     )
     )
     serializer_class = serializers.PowerPanelSerializer
     serializer_class = serializers.PowerPanelSerializer
-    filterset_class = filters.PowerPanelFilterSet
+    filterset_class = filtersets.PowerPanelFilterSet
 
 
 
 
 #
 #
@@ -655,7 +655,7 @@ class PowerFeedViewSet(PathEndpointMixin, CustomFieldModelViewSet):
         'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags'
         'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags'
     )
     )
     serializer_class = serializers.PowerFeedSerializer
     serializer_class = serializers.PowerFeedSerializer
-    filterset_class = filters.PowerFeedFilterSet
+    filterset_class = filtersets.PowerFeedFilterSet
 
 
 
 
 #
 #

+ 46 - 60
netbox/dcim/filters.py → netbox/dcim/filtersets.py

@@ -1,13 +1,16 @@
 import django_filters
 import django_filters
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 
 
-from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
-from tenancy.filters import TenancyFilterSet
+from extras.filters import TagFilter
+from extras.filtersets import LocalConfigContextFilterSet
+from netbox.filtersets import (
+    BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
+)
+from tenancy.filtersets import TenancyFilterSet
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.choices import ColorChoices
 from utilities.choices import ColorChoices
 from utilities.filters import (
 from utilities.filters import (
-    BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
-    NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter,
+    MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
 )
 )
 from virtualization.models import Cluster
 from virtualization.models import Cluster
 from .choices import *
 from .choices import *
@@ -57,7 +60,7 @@ __all__ = (
 )
 )
 
 
 
 
-class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class RegionFilterSet(OrganizationalModelFilterSet):
     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)',
@@ -74,7 +77,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilt
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class SiteGroupFilterSet(OrganizationalModelFilterSet):
     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)',
@@ -91,7 +94,7 @@ class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -154,7 +157,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class LocationFilterSet(OrganizationalModelFilterSet):
     region_id = TreeNodeMultipleChoiceFilter(
     region_id = TreeNodeMultipleChoiceFilter(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         field_name='site__region',
         field_name='site__region',
@@ -218,14 +221,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi
         )
         )
 
 
 
 
-class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class RackRoleFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = RackRole
         model = RackRole
         fields = ['id', 'name', 'slug', 'color']
         fields = ['id', 'name', 'slug', 'color']
 
 
 
 
-class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -323,7 +326,7 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
         )
         )
 
 
 
 
-class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -383,14 +386,14 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel
         )
         )
 
 
 
 
-class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class ManufacturerFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = Manufacturer
         model = Manufacturer
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class DeviceTypeFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -476,7 +479,7 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat
         return queryset.exclude(devicebaytemplates__isnull=value)
         return queryset.exclude(devicebaytemplates__isnull=value)
 
 
 
 
-class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class DeviceTypeComponentFilterSet(django_filters.FilterSet):
     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',
@@ -484,28 +487,28 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilter
     )
     )
 
 
 
 
-class ConsolePortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
 
 
     class Meta:
     class Meta:
         model = ConsolePortTemplate
         model = ConsolePortTemplate
         fields = ['id', 'name', 'type']
         fields = ['id', 'name', 'type']
 
 
 
 
-class ConsoleServerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
 
 
     class Meta:
     class Meta:
         model = ConsoleServerPortTemplate
         model = ConsoleServerPortTemplate
         fields = ['id', 'name', 'type']
         fields = ['id', 'name', 'type']
 
 
 
 
-class PowerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, 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(BaseFilterSet, DeviceTypeComponentFilterSet):
+class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
     feed_leg = django_filters.MultipleChoiceFilter(
     feed_leg = django_filters.MultipleChoiceFilter(
         choices=PowerOutletFeedLegChoices,
         choices=PowerOutletFeedLegChoices,
         null_value=None
         null_value=None
@@ -516,7 +519,7 @@ class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
         fields = ['id', 'name', 'type', 'feed_leg']
         fields = ['id', 'name', 'type', 'feed_leg']
 
 
 
 
-class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         null_value=None
         null_value=None
@@ -527,7 +530,7 @@ class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
         fields = ['id', 'name', 'type', 'mgmt_only']
         fields = ['id', 'name', 'type', 'mgmt_only']
 
 
 
 
-class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         null_value=None
         null_value=None
@@ -538,7 +541,7 @@ class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
         fields = ['id', 'name', 'type']
         fields = ['id', 'name', 'type']
 
 
 
 
-class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         null_value=None
         null_value=None
@@ -549,21 +552,21 @@ class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
         fields = ['id', 'name', 'type', 'positions']
         fields = ['id', 'name', 'type', 'positions']
 
 
 
 
-class DeviceBayTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
+class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
 
 
     class Meta:
     class Meta:
         model = DeviceBayTemplate
         model = DeviceBayTemplate
         fields = ['id', 'name']
         fields = ['id', 'name']
 
 
 
 
-class DeviceRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class DeviceRoleFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = DeviceRole
         model = DeviceRole
         fields = ['id', 'name', 'slug', 'color', 'vm_role']
         fields = ['id', 'name', 'slug', 'color', 'vm_role']
 
 
 
 
-class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class PlatformFilterSet(OrganizationalModelFilterSet):
     manufacturer_id = django_filters.ModelMultipleChoiceFilter(
     manufacturer_id = django_filters.ModelMultipleChoiceFilter(
         field_name='manufacturer',
         field_name='manufacturer',
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
@@ -581,13 +584,7 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi
         fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
         fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
 
 
 
 
-class DeviceFilterSet(
-    BaseFilterSet,
-    TenancyFilterSet,
-    LocalConfigContextFilterSet,
-    CustomFieldModelFilterSet,
-    CreatedUpdatedFilterSet
-):
+class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -792,7 +789,7 @@ class DeviceFilterSet(
         return queryset.exclude(devicebays__isnull=value)
         return queryset.exclude(devicebays__isnull=value)
 
 
 
 
-class DeviceComponentFilterSet(CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class DeviceComponentFilterSet(django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -876,7 +873,7 @@ class PathEndpointFilterSet(django_filters.FilterSet):
             return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
             return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
 
 
 
 
-class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
+class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         null_value=None
         null_value=None
@@ -887,12 +884,7 @@ class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina
         fields = ['id', 'name', 'label', 'description']
         fields = ['id', 'name', 'label', 'description']
 
 
 
 
-class ConsoleServerPortFilterSet(
-    BaseFilterSet,
-    DeviceComponentFilterSet,
-    CableTerminationFilterSet,
-    PathEndpointFilterSet
-):
+class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         null_value=None
         null_value=None
@@ -903,7 +895,7 @@ class ConsoleServerPortFilterSet(
         fields = ['id', 'name', 'label', 'description']
         fields = ['id', 'name', 'label', 'description']
 
 
 
 
-class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
+class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PowerPortTypeChoices,
         choices=PowerPortTypeChoices,
         null_value=None
         null_value=None
@@ -914,7 +906,7 @@ class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
         fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
         fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
 
 
 
 
-class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
+class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PowerOutletTypeChoices,
         choices=PowerOutletTypeChoices,
         null_value=None
         null_value=None
@@ -929,7 +921,7 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina
         fields = ['id', 'name', 'label', 'feed_leg', 'description']
         fields = ['id', 'name', 'label', 'feed_leg', 'description']
 
 
 
 
-class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
+class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1027,7 +1019,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
         }.get(value, queryset.none())
         }.get(value, queryset.none())
 
 
 
 
-class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
+class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         null_value=None
         null_value=None
@@ -1038,7 +1030,7 @@ class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
         fields = ['id', 'name', 'label', 'type', 'description']
         fields = ['id', 'name', 'label', 'type', 'description']
 
 
 
 
-class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
+class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         null_value=None
         null_value=None
@@ -1049,14 +1041,14 @@ class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminatio
         fields = ['id', 'name', 'label', 'type', 'positions', 'description']
         fields = ['id', 'name', 'label', 'type', 'positions', 'description']
 
 
 
 
-class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet):
+class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
 
 
     class Meta:
     class Meta:
         model = DeviceBay
         model = DeviceBay
         fields = ['id', 'name', 'label', 'description']
         fields = ['id', 'name', 'label', 'description']
 
 
 
 
-class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
+class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1129,7 +1121,7 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class VirtualChassisFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1209,7 +1201,7 @@ class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedU
         return queryset.filter(qs_filter).distinct()
         return queryset.filter(qs_filter).distinct()
 
 
 
 
-class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class CableFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1273,7 +1265,7 @@ class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFil
         return queryset
         return queryset
 
 
 
 
-class ConnectionFilterSet:
+class ConnectionFilterSet(BaseFilterSet):
 
 
     def filter_site(self, queryset, name, value):
     def filter_site(self, queryset, name, value):
         if not value.strip():
         if not value.strip():
@@ -1286,7 +1278,7 @@ class ConnectionFilterSet:
         return queryset.filter(**{f'{name}__in': value})
         return queryset.filter(**{f'{name}__in': value})
 
 
 
 
-class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
+class ConsoleConnectionFilterSet(ConnectionFilterSet):
     site = django_filters.CharFilter(
     site = django_filters.CharFilter(
         method='filter_site',
         method='filter_site',
         label='Site (slug)',
         label='Site (slug)',
@@ -1304,7 +1296,7 @@ class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
         fields = ['name']
         fields = ['name']
 
 
 
 
-class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
+class PowerConnectionFilterSet(ConnectionFilterSet):
     site = django_filters.CharFilter(
     site = django_filters.CharFilter(
         method='filter_site',
         method='filter_site',
         label='Site (slug)',
         label='Site (slug)',
@@ -1322,7 +1314,7 @@ class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
         fields = ['name']
         fields = ['name']
 
 
 
 
-class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
+class InterfaceConnectionFilterSet(ConnectionFilterSet):
     site = django_filters.CharFilter(
     site = django_filters.CharFilter(
         method='filter_site',
         method='filter_site',
         label='Site (slug)',
         label='Site (slug)',
@@ -1340,7 +1332,7 @@ class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
         fields = []
         fields = []
 
 
 
 
-class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class PowerPanelFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1402,13 +1394,7 @@ class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class PowerFeedFilterSet(
-    BaseFilterSet,
-    CableTerminationFilterSet,
-    PathEndpointFilterSet,
-    CustomFieldModelFilterSet,
-    CreatedUpdatedFilterSet
-):
+class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/dcim/tests/test_filters.py

@@ -2,7 +2,7 @@ from django.contrib.auth.models import User
 from django.test import TestCase
 from django.test import TestCase
 
 
 from dcim.choices import *
 from dcim.choices import *
-from dcim.filters import *
+from dcim.filtersets import *
 from dcim.models import *
 from dcim.models import *
 from ipam.models import IPAddress
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup

+ 80 - 80
netbox/dcim/views.py

@@ -24,7 +24,7 @@ from utilities.tables import paginate_table
 from utilities.utils import csv_format, count_related
 from utilities.utils import csv_format, count_related
 from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
 from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .choices import DeviceFaceChoices
 from .choices import DeviceFaceChoices
 from .constants import NONCONNECTABLE_IFACE_TYPES
 from .constants import NONCONNECTABLE_IFACE_TYPES
 from .models import (
 from .models import (
@@ -107,7 +107,7 @@ class RegionListView(generic.ObjectListView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.RegionFilterSet
+    filterset = filtersets.RegionFilterSet
     filterset_form = forms.RegionFilterForm
     filterset_form = forms.RegionFilterForm
     table = tables.RegionTable
     table = tables.RegionTable
 
 
@@ -163,7 +163,7 @@ class RegionBulkEditView(generic.BulkEditView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.RegionFilterSet
+    filterset = filtersets.RegionFilterSet
     table = tables.RegionTable
     table = tables.RegionTable
     form = forms.RegionBulkEditForm
     form = forms.RegionBulkEditForm
 
 
@@ -176,7 +176,7 @@ class RegionBulkDeleteView(generic.BulkDeleteView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.RegionFilterSet
+    filterset = filtersets.RegionFilterSet
     table = tables.RegionTable
     table = tables.RegionTable
 
 
 
 
@@ -192,7 +192,7 @@ class SiteGroupListView(generic.ObjectListView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.SiteGroupFilterSet
+    filterset = filtersets.SiteGroupFilterSet
     filterset_form = forms.SiteGroupFilterForm
     filterset_form = forms.SiteGroupFilterForm
     table = tables.SiteGroupTable
     table = tables.SiteGroupTable
 
 
@@ -248,7 +248,7 @@ class SiteGroupBulkEditView(generic.BulkEditView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.SiteGroupFilterSet
+    filterset = filtersets.SiteGroupFilterSet
     table = tables.SiteGroupTable
     table = tables.SiteGroupTable
     form = forms.SiteGroupBulkEditForm
     form = forms.SiteGroupBulkEditForm
 
 
@@ -261,7 +261,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
         'site_count',
         'site_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.SiteGroupFilterSet
+    filterset = filtersets.SiteGroupFilterSet
     table = tables.SiteGroupTable
     table = tables.SiteGroupTable
 
 
 
 
@@ -271,7 +271,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
 
 
 class SiteListView(generic.ObjectListView):
 class SiteListView(generic.ObjectListView):
     queryset = Site.objects.all()
     queryset = Site.objects.all()
-    filterset = filters.SiteFilterSet
+    filterset = filtersets.SiteFilterSet
     filterset_form = forms.SiteFilterForm
     filterset_form = forms.SiteFilterForm
     table = tables.SiteTable
     table = tables.SiteTable
 
 
@@ -326,14 +326,14 @@ class SiteBulkImportView(generic.BulkImportView):
 
 
 class SiteBulkEditView(generic.BulkEditView):
 class SiteBulkEditView(generic.BulkEditView):
     queryset = Site.objects.prefetch_related('region', 'tenant')
     queryset = Site.objects.prefetch_related('region', 'tenant')
-    filterset = filters.SiteFilterSet
+    filterset = filtersets.SiteFilterSet
     table = tables.SiteTable
     table = tables.SiteTable
     form = forms.SiteBulkEditForm
     form = forms.SiteBulkEditForm
 
 
 
 
 class SiteBulkDeleteView(generic.BulkDeleteView):
 class SiteBulkDeleteView(generic.BulkDeleteView):
     queryset = Site.objects.prefetch_related('region', 'tenant')
     queryset = Site.objects.prefetch_related('region', 'tenant')
-    filterset = filters.SiteFilterSet
+    filterset = filtersets.SiteFilterSet
     table = tables.SiteTable
     table = tables.SiteTable
 
 
 
 
@@ -355,7 +355,7 @@ class LocationListView(generic.ObjectListView):
         'rack_count',
         'rack_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.LocationFilterSet
+    filterset = filtersets.LocationFilterSet
     filterset_form = forms.LocationFilterForm
     filterset_form = forms.LocationFilterForm
     table = tables.LocationTable
     table = tables.LocationTable
 
 
@@ -414,7 +414,7 @@ class LocationBulkEditView(generic.BulkEditView):
         'rack_count',
         'rack_count',
         cumulative=True
         cumulative=True
     ).prefetch_related('site')
     ).prefetch_related('site')
-    filterset = filters.LocationFilterSet
+    filterset = filtersets.LocationFilterSet
     table = tables.LocationTable
     table = tables.LocationTable
     form = forms.LocationBulkEditForm
     form = forms.LocationBulkEditForm
 
 
@@ -427,7 +427,7 @@ class LocationBulkDeleteView(generic.BulkDeleteView):
         'rack_count',
         'rack_count',
         cumulative=True
         cumulative=True
     ).prefetch_related('site')
     ).prefetch_related('site')
-    filterset = filters.LocationFilterSet
+    filterset = filtersets.LocationFilterSet
     table = tables.LocationTable
     table = tables.LocationTable
 
 
 
 
@@ -478,7 +478,7 @@ class RackRoleBulkEditView(generic.BulkEditView):
     queryset = RackRole.objects.annotate(
     queryset = RackRole.objects.annotate(
         rack_count=count_related(Rack, 'role')
         rack_count=count_related(Rack, 'role')
     )
     )
-    filterset = filters.RackRoleFilterSet
+    filterset = filtersets.RackRoleFilterSet
     table = tables.RackRoleTable
     table = tables.RackRoleTable
     form = forms.RackRoleBulkEditForm
     form = forms.RackRoleBulkEditForm
 
 
@@ -500,7 +500,7 @@ class RackListView(generic.ObjectListView):
     ).annotate(
     ).annotate(
         device_count=count_related(Device, 'rack')
         device_count=count_related(Device, 'rack')
     )
     )
-    filterset = filters.RackFilterSet
+    filterset = filtersets.RackFilterSet
     filterset_form = forms.RackFilterForm
     filterset_form = forms.RackFilterForm
     table = tables.RackDetailTable
     table = tables.RackDetailTable
 
 
@@ -513,7 +513,7 @@ class RackElevationListView(generic.ObjectListView):
 
 
     def get(self, request):
     def get(self, request):
 
 
-        racks = filters.RackFilterSet(request.GET, self.queryset).qs
+        racks = filtersets.RackFilterSet(request.GET, self.queryset).qs
         total_count = racks.count()
         total_count = racks.count()
 
 
         # Determine ordering
         # Determine ordering
@@ -602,14 +602,14 @@ class RackBulkImportView(generic.BulkImportView):
 
 
 class RackBulkEditView(generic.BulkEditView):
 class RackBulkEditView(generic.BulkEditView):
     queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
     queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
-    filterset = filters.RackFilterSet
+    filterset = filtersets.RackFilterSet
     table = tables.RackTable
     table = tables.RackTable
     form = forms.RackBulkEditForm
     form = forms.RackBulkEditForm
 
 
 
 
 class RackBulkDeleteView(generic.BulkDeleteView):
 class RackBulkDeleteView(generic.BulkDeleteView):
     queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
     queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
-    filterset = filters.RackFilterSet
+    filterset = filtersets.RackFilterSet
     table = tables.RackTable
     table = tables.RackTable
 
 
 
 
@@ -619,7 +619,7 @@ class RackBulkDeleteView(generic.BulkDeleteView):
 
 
 class RackReservationListView(generic.ObjectListView):
 class RackReservationListView(generic.ObjectListView):
     queryset = RackReservation.objects.all()
     queryset = RackReservation.objects.all()
-    filterset = filters.RackReservationFilterSet
+    filterset = filtersets.RackReservationFilterSet
     filterset_form = forms.RackReservationFilterForm
     filterset_form = forms.RackReservationFilterForm
     table = tables.RackReservationTable
     table = tables.RackReservationTable
 
 
@@ -662,14 +662,14 @@ class RackReservationImportView(generic.BulkImportView):
 
 
 class RackReservationBulkEditView(generic.BulkEditView):
 class RackReservationBulkEditView(generic.BulkEditView):
     queryset = RackReservation.objects.prefetch_related('rack', 'user')
     queryset = RackReservation.objects.prefetch_related('rack', 'user')
-    filterset = filters.RackReservationFilterSet
+    filterset = filtersets.RackReservationFilterSet
     table = tables.RackReservationTable
     table = tables.RackReservationTable
     form = forms.RackReservationBulkEditForm
     form = forms.RackReservationBulkEditForm
 
 
 
 
 class RackReservationBulkDeleteView(generic.BulkDeleteView):
 class RackReservationBulkDeleteView(generic.BulkDeleteView):
     queryset = RackReservation.objects.prefetch_related('rack', 'user')
     queryset = RackReservation.objects.prefetch_related('rack', 'user')
-    filterset = filters.RackReservationFilterSet
+    filterset = filtersets.RackReservationFilterSet
     table = tables.RackReservationTable
     table = tables.RackReservationTable
 
 
 
 
@@ -722,7 +722,7 @@ class ManufacturerBulkEditView(generic.BulkEditView):
     queryset = Manufacturer.objects.annotate(
     queryset = Manufacturer.objects.annotate(
         devicetype_count=count_related(DeviceType, 'manufacturer')
         devicetype_count=count_related(DeviceType, 'manufacturer')
     )
     )
-    filterset = filters.ManufacturerFilterSet
+    filterset = filtersets.ManufacturerFilterSet
     table = tables.ManufacturerTable
     table = tables.ManufacturerTable
     form = forms.ManufacturerBulkEditForm
     form = forms.ManufacturerBulkEditForm
 
 
@@ -742,7 +742,7 @@ class DeviceTypeListView(generic.ObjectListView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
         instance_count=count_related(Device, 'device_type')
         instance_count=count_related(Device, 'device_type')
     )
     )
-    filterset = filters.DeviceTypeFilterSet
+    filterset = filtersets.DeviceTypeFilterSet
     filterset_form = forms.DeviceTypeFilterForm
     filterset_form = forms.DeviceTypeFilterForm
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
 
 
@@ -848,7 +848,7 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
         instance_count=count_related(Device, 'device_type')
         instance_count=count_related(Device, 'device_type')
     )
     )
-    filterset = filters.DeviceTypeFilterSet
+    filterset = filtersets.DeviceTypeFilterSet
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
     form = forms.DeviceTypeBulkEditForm
     form = forms.DeviceTypeBulkEditForm
 
 
@@ -857,7 +857,7 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
         instance_count=count_related(Device, 'device_type')
         instance_count=count_related(Device, 'device_type')
     )
     )
-    filterset = filters.DeviceTypeFilterSet
+    filterset = filtersets.DeviceTypeFilterSet
     table = tables.DeviceTypeTable
     table = tables.DeviceTypeTable
 
 
 
 
@@ -1190,7 +1190,7 @@ class DeviceRoleBulkEditView(generic.BulkEditView):
         device_count=count_related(Device, 'device_role'),
         device_count=count_related(Device, 'device_role'),
         vm_count=count_related(VirtualMachine, 'role')
         vm_count=count_related(VirtualMachine, 'role')
     )
     )
-    filterset = filters.DeviceRoleFilterSet
+    filterset = filtersets.DeviceRoleFilterSet
     table = tables.DeviceRoleTable
     table = tables.DeviceRoleTable
     form = forms.DeviceRoleBulkEditForm
     form = forms.DeviceRoleBulkEditForm
 
 
@@ -1249,7 +1249,7 @@ class PlatformBulkImportView(generic.BulkImportView):
 
 
 class PlatformBulkEditView(generic.BulkEditView):
 class PlatformBulkEditView(generic.BulkEditView):
     queryset = Platform.objects.all()
     queryset = Platform.objects.all()
-    filterset = filters.PlatformFilterSet
+    filterset = filtersets.PlatformFilterSet
     table = tables.PlatformTable
     table = tables.PlatformTable
     form = forms.PlatformBulkEditForm
     form = forms.PlatformBulkEditForm
 
 
@@ -1265,7 +1265,7 @@ class PlatformBulkDeleteView(generic.BulkDeleteView):
 
 
 class DeviceListView(generic.ObjectListView):
 class DeviceListView(generic.ObjectListView):
     queryset = Device.objects.all()
     queryset = Device.objects.all()
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     filterset_form = forms.DeviceFilterForm
     filterset_form = forms.DeviceFilterForm
     table = tables.DeviceTable
     table = tables.DeviceTable
     template_name = 'dcim/device_list.html'
     template_name = 'dcim/device_list.html'
@@ -1600,14 +1600,14 @@ class ChildDeviceBulkImportView(generic.BulkImportView):
 
 
 class DeviceBulkEditView(generic.BulkEditView):
 class DeviceBulkEditView(generic.BulkEditView):
     queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
     queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     form = forms.DeviceBulkEditForm
     form = forms.DeviceBulkEditForm
 
 
 
 
 class DeviceBulkDeleteView(generic.BulkDeleteView):
 class DeviceBulkDeleteView(generic.BulkDeleteView):
     queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
     queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
 
 
 
 
@@ -1617,7 +1617,7 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConsolePortListView(generic.ObjectListView):
 class ConsolePortListView(generic.ObjectListView):
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
-    filterset = filters.ConsolePortFilterSet
+    filterset = filtersets.ConsolePortFilterSet
     filterset_form = forms.ConsolePortFilterForm
     filterset_form = forms.ConsolePortFilterForm
     table = tables.ConsolePortTable
     table = tables.ConsolePortTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1652,7 +1652,7 @@ class ConsolePortBulkImportView(generic.BulkImportView):
 
 
 class ConsolePortBulkEditView(generic.BulkEditView):
 class ConsolePortBulkEditView(generic.BulkEditView):
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
-    filterset = filters.ConsolePortFilterSet
+    filterset = filtersets.ConsolePortFilterSet
     table = tables.ConsolePortTable
     table = tables.ConsolePortTable
     form = forms.ConsolePortBulkEditForm
     form = forms.ConsolePortBulkEditForm
 
 
@@ -1667,7 +1667,7 @@ class ConsolePortBulkDisconnectView(BulkDisconnectView):
 
 
 class ConsolePortBulkDeleteView(generic.BulkDeleteView):
 class ConsolePortBulkDeleteView(generic.BulkDeleteView):
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
-    filterset = filters.ConsolePortFilterSet
+    filterset = filtersets.ConsolePortFilterSet
     table = tables.ConsolePortTable
     table = tables.ConsolePortTable
 
 
 
 
@@ -1677,7 +1677,7 @@ class ConsolePortBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConsoleServerPortListView(generic.ObjectListView):
 class ConsoleServerPortListView(generic.ObjectListView):
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
-    filterset = filters.ConsoleServerPortFilterSet
+    filterset = filtersets.ConsoleServerPortFilterSet
     filterset_form = forms.ConsoleServerPortFilterForm
     filterset_form = forms.ConsoleServerPortFilterForm
     table = tables.ConsoleServerPortTable
     table = tables.ConsoleServerPortTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1712,7 +1712,7 @@ class ConsoleServerPortBulkImportView(generic.BulkImportView):
 
 
 class ConsoleServerPortBulkEditView(generic.BulkEditView):
 class ConsoleServerPortBulkEditView(generic.BulkEditView):
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
-    filterset = filters.ConsoleServerPortFilterSet
+    filterset = filtersets.ConsoleServerPortFilterSet
     table = tables.ConsoleServerPortTable
     table = tables.ConsoleServerPortTable
     form = forms.ConsoleServerPortBulkEditForm
     form = forms.ConsoleServerPortBulkEditForm
 
 
@@ -1727,7 +1727,7 @@ class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
 
 
 class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
 class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
-    filterset = filters.ConsoleServerPortFilterSet
+    filterset = filtersets.ConsoleServerPortFilterSet
     table = tables.ConsoleServerPortTable
     table = tables.ConsoleServerPortTable
 
 
 
 
@@ -1737,7 +1737,7 @@ class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
 
 
 class PowerPortListView(generic.ObjectListView):
 class PowerPortListView(generic.ObjectListView):
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
-    filterset = filters.PowerPortFilterSet
+    filterset = filtersets.PowerPortFilterSet
     filterset_form = forms.PowerPortFilterForm
     filterset_form = forms.PowerPortFilterForm
     table = tables.PowerPortTable
     table = tables.PowerPortTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1772,7 +1772,7 @@ class PowerPortBulkImportView(generic.BulkImportView):
 
 
 class PowerPortBulkEditView(generic.BulkEditView):
 class PowerPortBulkEditView(generic.BulkEditView):
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
-    filterset = filters.PowerPortFilterSet
+    filterset = filtersets.PowerPortFilterSet
     table = tables.PowerPortTable
     table = tables.PowerPortTable
     form = forms.PowerPortBulkEditForm
     form = forms.PowerPortBulkEditForm
 
 
@@ -1787,7 +1787,7 @@ class PowerPortBulkDisconnectView(BulkDisconnectView):
 
 
 class PowerPortBulkDeleteView(generic.BulkDeleteView):
 class PowerPortBulkDeleteView(generic.BulkDeleteView):
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
-    filterset = filters.PowerPortFilterSet
+    filterset = filtersets.PowerPortFilterSet
     table = tables.PowerPortTable
     table = tables.PowerPortTable
 
 
 
 
@@ -1797,7 +1797,7 @@ class PowerPortBulkDeleteView(generic.BulkDeleteView):
 
 
 class PowerOutletListView(generic.ObjectListView):
 class PowerOutletListView(generic.ObjectListView):
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
-    filterset = filters.PowerOutletFilterSet
+    filterset = filtersets.PowerOutletFilterSet
     filterset_form = forms.PowerOutletFilterForm
     filterset_form = forms.PowerOutletFilterForm
     table = tables.PowerOutletTable
     table = tables.PowerOutletTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1832,7 +1832,7 @@ class PowerOutletBulkImportView(generic.BulkImportView):
 
 
 class PowerOutletBulkEditView(generic.BulkEditView):
 class PowerOutletBulkEditView(generic.BulkEditView):
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
-    filterset = filters.PowerOutletFilterSet
+    filterset = filtersets.PowerOutletFilterSet
     table = tables.PowerOutletTable
     table = tables.PowerOutletTable
     form = forms.PowerOutletBulkEditForm
     form = forms.PowerOutletBulkEditForm
 
 
@@ -1847,7 +1847,7 @@ class PowerOutletBulkDisconnectView(BulkDisconnectView):
 
 
 class PowerOutletBulkDeleteView(generic.BulkDeleteView):
 class PowerOutletBulkDeleteView(generic.BulkDeleteView):
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
-    filterset = filters.PowerOutletFilterSet
+    filterset = filtersets.PowerOutletFilterSet
     table = tables.PowerOutletTable
     table = tables.PowerOutletTable
 
 
 
 
@@ -1857,7 +1857,7 @@ class PowerOutletBulkDeleteView(generic.BulkDeleteView):
 
 
 class InterfaceListView(generic.ObjectListView):
 class InterfaceListView(generic.ObjectListView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
-    filterset = filters.InterfaceFilterSet
+    filterset = filtersets.InterfaceFilterSet
     filterset_form = forms.InterfaceFilterForm
     filterset_form = forms.InterfaceFilterForm
     table = tables.InterfaceTable
     table = tables.InterfaceTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1927,7 +1927,7 @@ class InterfaceBulkImportView(generic.BulkImportView):
 
 
 class InterfaceBulkEditView(generic.BulkEditView):
 class InterfaceBulkEditView(generic.BulkEditView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
-    filterset = filters.InterfaceFilterSet
+    filterset = filtersets.InterfaceFilterSet
     table = tables.InterfaceTable
     table = tables.InterfaceTable
     form = forms.InterfaceBulkEditForm
     form = forms.InterfaceBulkEditForm
 
 
@@ -1942,7 +1942,7 @@ class InterfaceBulkDisconnectView(BulkDisconnectView):
 
 
 class InterfaceBulkDeleteView(generic.BulkDeleteView):
 class InterfaceBulkDeleteView(generic.BulkDeleteView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
-    filterset = filters.InterfaceFilterSet
+    filterset = filtersets.InterfaceFilterSet
     table = tables.InterfaceTable
     table = tables.InterfaceTable
 
 
 
 
@@ -1952,7 +1952,7 @@ class InterfaceBulkDeleteView(generic.BulkDeleteView):
 
 
 class FrontPortListView(generic.ObjectListView):
 class FrontPortListView(generic.ObjectListView):
     queryset = FrontPort.objects.all()
     queryset = FrontPort.objects.all()
-    filterset = filters.FrontPortFilterSet
+    filterset = filtersets.FrontPortFilterSet
     filterset_form = forms.FrontPortFilterForm
     filterset_form = forms.FrontPortFilterForm
     table = tables.FrontPortTable
     table = tables.FrontPortTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -1987,7 +1987,7 @@ class FrontPortBulkImportView(generic.BulkImportView):
 
 
 class FrontPortBulkEditView(generic.BulkEditView):
 class FrontPortBulkEditView(generic.BulkEditView):
     queryset = FrontPort.objects.all()
     queryset = FrontPort.objects.all()
-    filterset = filters.FrontPortFilterSet
+    filterset = filtersets.FrontPortFilterSet
     table = tables.FrontPortTable
     table = tables.FrontPortTable
     form = forms.FrontPortBulkEditForm
     form = forms.FrontPortBulkEditForm
 
 
@@ -2002,7 +2002,7 @@ class FrontPortBulkDisconnectView(BulkDisconnectView):
 
 
 class FrontPortBulkDeleteView(generic.BulkDeleteView):
 class FrontPortBulkDeleteView(generic.BulkDeleteView):
     queryset = FrontPort.objects.all()
     queryset = FrontPort.objects.all()
-    filterset = filters.FrontPortFilterSet
+    filterset = filtersets.FrontPortFilterSet
     table = tables.FrontPortTable
     table = tables.FrontPortTable
 
 
 
 
@@ -2012,7 +2012,7 @@ class FrontPortBulkDeleteView(generic.BulkDeleteView):
 
 
 class RearPortListView(generic.ObjectListView):
 class RearPortListView(generic.ObjectListView):
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
-    filterset = filters.RearPortFilterSet
+    filterset = filtersets.RearPortFilterSet
     filterset_form = forms.RearPortFilterForm
     filterset_form = forms.RearPortFilterForm
     table = tables.RearPortTable
     table = tables.RearPortTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -2047,7 +2047,7 @@ class RearPortBulkImportView(generic.BulkImportView):
 
 
 class RearPortBulkEditView(generic.BulkEditView):
 class RearPortBulkEditView(generic.BulkEditView):
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
-    filterset = filters.RearPortFilterSet
+    filterset = filtersets.RearPortFilterSet
     table = tables.RearPortTable
     table = tables.RearPortTable
     form = forms.RearPortBulkEditForm
     form = forms.RearPortBulkEditForm
 
 
@@ -2062,7 +2062,7 @@ class RearPortBulkDisconnectView(BulkDisconnectView):
 
 
 class RearPortBulkDeleteView(generic.BulkDeleteView):
 class RearPortBulkDeleteView(generic.BulkDeleteView):
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
-    filterset = filters.RearPortFilterSet
+    filterset = filtersets.RearPortFilterSet
     table = tables.RearPortTable
     table = tables.RearPortTable
 
 
 
 
@@ -2072,7 +2072,7 @@ class RearPortBulkDeleteView(generic.BulkDeleteView):
 
 
 class DeviceBayListView(generic.ObjectListView):
 class DeviceBayListView(generic.ObjectListView):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
-    filterset = filters.DeviceBayFilterSet
+    filterset = filtersets.DeviceBayFilterSet
     filterset_form = forms.DeviceBayFilterForm
     filterset_form = forms.DeviceBayFilterForm
     table = tables.DeviceBayTable
     table = tables.DeviceBayTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -2172,7 +2172,7 @@ class DeviceBayBulkImportView(generic.BulkImportView):
 
 
 class DeviceBayBulkEditView(generic.BulkEditView):
 class DeviceBayBulkEditView(generic.BulkEditView):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
-    filterset = filters.DeviceBayFilterSet
+    filterset = filtersets.DeviceBayFilterSet
     table = tables.DeviceBayTable
     table = tables.DeviceBayTable
     form = forms.DeviceBayBulkEditForm
     form = forms.DeviceBayBulkEditForm
 
 
@@ -2183,7 +2183,7 @@ class DeviceBayBulkRenameView(generic.BulkRenameView):
 
 
 class DeviceBayBulkDeleteView(generic.BulkDeleteView):
 class DeviceBayBulkDeleteView(generic.BulkDeleteView):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
-    filterset = filters.DeviceBayFilterSet
+    filterset = filtersets.DeviceBayFilterSet
     table = tables.DeviceBayTable
     table = tables.DeviceBayTable
 
 
 
 
@@ -2193,7 +2193,7 @@ class DeviceBayBulkDeleteView(generic.BulkDeleteView):
 
 
 class InventoryItemListView(generic.ObjectListView):
 class InventoryItemListView(generic.ObjectListView):
     queryset = InventoryItem.objects.all()
     queryset = InventoryItem.objects.all()
-    filterset = filters.InventoryItemFilterSet
+    filterset = filtersets.InventoryItemFilterSet
     filterset_form = forms.InventoryItemFilterForm
     filterset_form = forms.InventoryItemFilterForm
     table = tables.InventoryItemTable
     table = tables.InventoryItemTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -2227,7 +2227,7 @@ class InventoryItemBulkImportView(generic.BulkImportView):
 
 
 class InventoryItemBulkEditView(generic.BulkEditView):
 class InventoryItemBulkEditView(generic.BulkEditView):
     queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
     queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
-    filterset = filters.InventoryItemFilterSet
+    filterset = filtersets.InventoryItemFilterSet
     table = tables.InventoryItemTable
     table = tables.InventoryItemTable
     form = forms.InventoryItemBulkEditForm
     form = forms.InventoryItemBulkEditForm
 
 
@@ -2252,7 +2252,7 @@ class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView):
     form = forms.ConsolePortBulkCreateForm
     form = forms.ConsolePortBulkCreateForm
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
     model_form = forms.ConsolePortForm
     model_form = forms.ConsolePortForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2263,7 +2263,7 @@ class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView):
     form = forms.ConsoleServerPortBulkCreateForm
     form = forms.ConsoleServerPortBulkCreateForm
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
     model_form = forms.ConsoleServerPortForm
     model_form = forms.ConsoleServerPortForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2274,7 +2274,7 @@ class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView):
     form = forms.PowerPortBulkCreateForm
     form = forms.PowerPortBulkCreateForm
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
     model_form = forms.PowerPortForm
     model_form = forms.PowerPortForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2285,7 +2285,7 @@ class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView):
     form = forms.PowerOutletBulkCreateForm
     form = forms.PowerOutletBulkCreateForm
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
     model_form = forms.PowerOutletForm
     model_form = forms.PowerOutletForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2296,7 +2296,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
     form = forms.InterfaceBulkCreateForm
     form = forms.InterfaceBulkCreateForm
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
     model_form = forms.InterfaceForm
     model_form = forms.InterfaceForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2307,7 +2307,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
 #     form = forms.FrontPortBulkCreateForm
 #     form = forms.FrontPortBulkCreateForm
 #     queryset = FrontPort.objects.all()
 #     queryset = FrontPort.objects.all()
 #     model_form = forms.FrontPortForm
 #     model_form = forms.FrontPortForm
-#     filterset = filters.DeviceFilterSet
+#     filterset = filtersets.DeviceFilterSet
 #     table = tables.DeviceTable
 #     table = tables.DeviceTable
 #     default_return_url = 'dcim:device_list'
 #     default_return_url = 'dcim:device_list'
 
 
@@ -2318,7 +2318,7 @@ class DeviceBulkAddRearPortView(generic.BulkComponentCreateView):
     form = forms.RearPortBulkCreateForm
     form = forms.RearPortBulkCreateForm
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
     model_form = forms.RearPortForm
     model_form = forms.RearPortForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2329,7 +2329,7 @@ class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
     form = forms.DeviceBayBulkCreateForm
     form = forms.DeviceBayBulkCreateForm
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
     model_form = forms.DeviceBayForm
     model_form = forms.DeviceBayForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2340,7 +2340,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
     form = forms.InventoryItemBulkCreateForm
     form = forms.InventoryItemBulkCreateForm
     queryset = InventoryItem.objects.all()
     queryset = InventoryItem.objects.all()
     model_form = forms.InventoryItemForm
     model_form = forms.InventoryItemForm
-    filterset = filters.DeviceFilterSet
+    filterset = filtersets.DeviceFilterSet
     table = tables.DeviceTable
     table = tables.DeviceTable
     default_return_url = 'dcim:device_list'
     default_return_url = 'dcim:device_list'
 
 
@@ -2351,7 +2351,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
 
 
 class CableListView(generic.ObjectListView):
 class CableListView(generic.ObjectListView):
     queryset = Cable.objects.all()
     queryset = Cable.objects.all()
-    filterset = filters.CableFilterSet
+    filterset = filtersets.CableFilterSet
     filterset_form = forms.CableFilterForm
     filterset_form = forms.CableFilterForm
     table = tables.CableTable
     table = tables.CableTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -2484,14 +2484,14 @@ class CableBulkImportView(generic.BulkImportView):
 
 
 class CableBulkEditView(generic.BulkEditView):
 class CableBulkEditView(generic.BulkEditView):
     queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
     queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
-    filterset = filters.CableFilterSet
+    filterset = filtersets.CableFilterSet
     table = tables.CableTable
     table = tables.CableTable
     form = forms.CableBulkEditForm
     form = forms.CableBulkEditForm
 
 
 
 
 class CableBulkDeleteView(generic.BulkDeleteView):
 class CableBulkDeleteView(generic.BulkDeleteView):
     queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
     queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
-    filterset = filters.CableFilterSet
+    filterset = filtersets.CableFilterSet
     table = tables.CableTable
     table = tables.CableTable
 
 
 
 
@@ -2501,7 +2501,7 @@ class CableBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConsoleConnectionsListView(generic.ObjectListView):
 class ConsoleConnectionsListView(generic.ObjectListView):
     queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device')
     queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device')
-    filterset = filters.ConsoleConnectionFilterSet
+    filterset = filtersets.ConsoleConnectionFilterSet
     filterset_form = forms.ConsoleConnectionFilterForm
     filterset_form = forms.ConsoleConnectionFilterForm
     table = tables.ConsoleConnectionTable
     table = tables.ConsoleConnectionTable
     template_name = 'dcim/connections_list.html'
     template_name = 'dcim/connections_list.html'
@@ -2531,7 +2531,7 @@ class ConsoleConnectionsListView(generic.ObjectListView):
 
 
 class PowerConnectionsListView(generic.ObjectListView):
 class PowerConnectionsListView(generic.ObjectListView):
     queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device')
     queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device')
-    filterset = filters.PowerConnectionFilterSet
+    filterset = filtersets.PowerConnectionFilterSet
     filterset_form = forms.PowerConnectionFilterForm
     filterset_form = forms.PowerConnectionFilterForm
     table = tables.PowerConnectionTable
     table = tables.PowerConnectionTable
     template_name = 'dcim/connections_list.html'
     template_name = 'dcim/connections_list.html'
@@ -2565,7 +2565,7 @@ class InterfaceConnectionsListView(generic.ObjectListView):
         _path__isnull=False,
         _path__isnull=False,
         pk__lt=F('_path__destination_id')
         pk__lt=F('_path__destination_id')
     ).order_by('device')
     ).order_by('device')
-    filterset = filters.InterfaceConnectionFilterSet
+    filterset = filtersets.InterfaceConnectionFilterSet
     filterset_form = forms.InterfaceConnectionFilterForm
     filterset_form = forms.InterfaceConnectionFilterForm
     table = tables.InterfaceConnectionTable
     table = tables.InterfaceConnectionTable
     template_name = 'dcim/connections_list.html'
     template_name = 'dcim/connections_list.html'
@@ -2604,7 +2604,7 @@ class VirtualChassisListView(generic.ObjectListView):
         member_count=count_related(Device, 'virtual_chassis')
         member_count=count_related(Device, 'virtual_chassis')
     )
     )
     table = tables.VirtualChassisTable
     table = tables.VirtualChassisTable
-    filterset = filters.VirtualChassisFilterSet
+    filterset = filtersets.VirtualChassisFilterSet
     filterset_form = forms.VirtualChassisFilterForm
     filterset_form = forms.VirtualChassisFilterForm
 
 
 
 
@@ -2812,14 +2812,14 @@ class VirtualChassisBulkImportView(generic.BulkImportView):
 
 
 class VirtualChassisBulkEditView(generic.BulkEditView):
 class VirtualChassisBulkEditView(generic.BulkEditView):
     queryset = VirtualChassis.objects.all()
     queryset = VirtualChassis.objects.all()
-    filterset = filters.VirtualChassisFilterSet
+    filterset = filtersets.VirtualChassisFilterSet
     table = tables.VirtualChassisTable
     table = tables.VirtualChassisTable
     form = forms.VirtualChassisBulkEditForm
     form = forms.VirtualChassisBulkEditForm
 
 
 
 
 class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
 class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
     queryset = VirtualChassis.objects.all()
     queryset = VirtualChassis.objects.all()
-    filterset = filters.VirtualChassisFilterSet
+    filterset = filtersets.VirtualChassisFilterSet
     table = tables.VirtualChassisTable
     table = tables.VirtualChassisTable
 
 
 
 
@@ -2833,7 +2833,7 @@ class PowerPanelListView(generic.ObjectListView):
     ).annotate(
     ).annotate(
         powerfeed_count=count_related(PowerFeed, 'power_panel')
         powerfeed_count=count_related(PowerFeed, 'power_panel')
     )
     )
-    filterset = filters.PowerPanelFilterSet
+    filterset = filtersets.PowerPanelFilterSet
     filterset_form = forms.PowerPanelFilterForm
     filterset_form = forms.PowerPanelFilterForm
     table = tables.PowerPanelTable
     table = tables.PowerPanelTable
 
 
@@ -2873,7 +2873,7 @@ class PowerPanelBulkImportView(generic.BulkImportView):
 
 
 class PowerPanelBulkEditView(generic.BulkEditView):
 class PowerPanelBulkEditView(generic.BulkEditView):
     queryset = PowerPanel.objects.prefetch_related('site', 'location')
     queryset = PowerPanel.objects.prefetch_related('site', 'location')
-    filterset = filters.PowerPanelFilterSet
+    filterset = filtersets.PowerPanelFilterSet
     table = tables.PowerPanelTable
     table = tables.PowerPanelTable
     form = forms.PowerPanelBulkEditForm
     form = forms.PowerPanelBulkEditForm
 
 
@@ -2884,7 +2884,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
     ).annotate(
     ).annotate(
         powerfeed_count=count_related(PowerFeed, 'power_panel')
         powerfeed_count=count_related(PowerFeed, 'power_panel')
     )
     )
-    filterset = filters.PowerPanelFilterSet
+    filterset = filtersets.PowerPanelFilterSet
     table = tables.PowerPanelTable
     table = tables.PowerPanelTable
 
 
 
 
@@ -2894,7 +2894,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
 
 
 class PowerFeedListView(generic.ObjectListView):
 class PowerFeedListView(generic.ObjectListView):
     queryset = PowerFeed.objects.all()
     queryset = PowerFeed.objects.all()
-    filterset = filters.PowerFeedFilterSet
+    filterset = filtersets.PowerFeedFilterSet
     filterset_form = forms.PowerFeedFilterForm
     filterset_form = forms.PowerFeedFilterForm
     table = tables.PowerFeedTable
     table = tables.PowerFeedTable
 
 
@@ -2920,7 +2920,7 @@ class PowerFeedBulkImportView(generic.BulkImportView):
 
 
 class PowerFeedBulkEditView(generic.BulkEditView):
 class PowerFeedBulkEditView(generic.BulkEditView):
     queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
     queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
-    filterset = filters.PowerFeedFilterSet
+    filterset = filtersets.PowerFeedFilterSet
     table = tables.PowerFeedTable
     table = tables.PowerFeedTable
     form = forms.PowerFeedBulkEditForm
     form = forms.PowerFeedBulkEditForm
 
 
@@ -2931,5 +2931,5 @@ class PowerFeedBulkDisconnectView(BulkDisconnectView):
 
 
 class PowerFeedBulkDeleteView(generic.BulkDeleteView):
 class PowerFeedBulkDeleteView(generic.BulkDeleteView):
     queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
     queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
-    filterset = filters.PowerFeedFilterSet
+    filterset = filtersets.PowerFeedFilterSet
     table = tables.PowerFeedTable
     table = tables.PowerFeedTable

+ 12 - 12
netbox/extras/api/views.py

@@ -9,7 +9,7 @@ from rest_framework.routers import APIRootView
 from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
 from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
 from rq import Worker
 from rq import Worker
 
 
-from extras import filters
+from extras import filtersets
 from extras.choices import JobResultStatusChoices
 from extras.choices import JobResultStatusChoices
 from extras.models import *
 from extras.models import *
 from extras.models import CustomField
 from extras.models import CustomField
@@ -61,7 +61,7 @@ class WebhookViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = Webhook.objects.all()
     queryset = Webhook.objects.all()
     serializer_class = serializers.WebhookSerializer
     serializer_class = serializers.WebhookSerializer
-    filterset_class = filters.WebhookFilterSet
+    filterset_class = filtersets.WebhookFilterSet
 
 
 
 
 #
 #
@@ -72,7 +72,7 @@ class CustomFieldViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = CustomField.objects.all()
     queryset = CustomField.objects.all()
     serializer_class = serializers.CustomFieldSerializer
     serializer_class = serializers.CustomFieldSerializer
-    filterset_class = filters.CustomFieldFilterSet
+    filterset_class = filtersets.CustomFieldFilterSet
 
 
 
 
 class CustomFieldModelViewSet(ModelViewSet):
 class CustomFieldModelViewSet(ModelViewSet):
@@ -101,7 +101,7 @@ class CustomLinkViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = CustomLink.objects.all()
     queryset = CustomLink.objects.all()
     serializer_class = serializers.CustomLinkSerializer
     serializer_class = serializers.CustomLinkSerializer
-    filterset_class = filters.CustomLinkFilterSet
+    filterset_class = filtersets.CustomLinkFilterSet
 
 
 
 
 #
 #
@@ -112,7 +112,7 @@ class ExportTemplateViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = ExportTemplate.objects.all()
     queryset = ExportTemplate.objects.all()
     serializer_class = serializers.ExportTemplateSerializer
     serializer_class = serializers.ExportTemplateSerializer
-    filterset_class = filters.ExportTemplateFilterSet
+    filterset_class = filtersets.ExportTemplateFilterSet
 
 
 
 
 #
 #
@@ -124,7 +124,7 @@ class TagViewSet(ModelViewSet):
         tagged_items=count_related(TaggedItem, 'tag')
         tagged_items=count_related(TaggedItem, 'tag')
     )
     )
     serializer_class = serializers.TagSerializer
     serializer_class = serializers.TagSerializer
-    filterset_class = filters.TagFilterSet
+    filterset_class = filtersets.TagFilterSet
 
 
 
 
 #
 #
@@ -135,7 +135,7 @@ class ImageAttachmentViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = ImageAttachment.objects.all()
     queryset = ImageAttachment.objects.all()
     serializer_class = serializers.ImageAttachmentSerializer
     serializer_class = serializers.ImageAttachmentSerializer
-    filterset_class = filters.ImageAttachmentFilterSet
+    filterset_class = filtersets.ImageAttachmentFilterSet
 
 
 
 
 #
 #
@@ -146,7 +146,7 @@ class JournalEntryViewSet(ModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = JournalEntry.objects.all()
     queryset = JournalEntry.objects.all()
     serializer_class = serializers.JournalEntrySerializer
     serializer_class = serializers.JournalEntrySerializer
-    filterset_class = filters.JournalEntryFilterSet
+    filterset_class = filtersets.JournalEntryFilterSet
 
 
 
 
 #
 #
@@ -158,7 +158,7 @@ class ConfigContextViewSet(ModelViewSet):
         'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
         'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
     )
     )
     serializer_class = serializers.ConfigContextSerializer
     serializer_class = serializers.ConfigContextSerializer
-    filterset_class = filters.ConfigContextFilterSet
+    filterset_class = filtersets.ConfigContextFilterSet
 
 
 
 
 #
 #
@@ -358,7 +358,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet):
     metadata_class = ContentTypeMetadata
     metadata_class = ContentTypeMetadata
     queryset = ObjectChange.objects.prefetch_related('user')
     queryset = ObjectChange.objects.prefetch_related('user')
     serializer_class = serializers.ObjectChangeSerializer
     serializer_class = serializers.ObjectChangeSerializer
-    filterset_class = filters.ObjectChangeFilterSet
+    filterset_class = filtersets.ObjectChangeFilterSet
 
 
 
 
 #
 #
@@ -371,7 +371,7 @@ class JobResultViewSet(ReadOnlyModelViewSet):
     """
     """
     queryset = JobResult.objects.prefetch_related('user')
     queryset = JobResult.objects.prefetch_related('user')
     serializer_class = serializers.JobResultSerializer
     serializer_class = serializers.JobResultSerializer
-    filterset_class = filters.JobResultFilterSet
+    filterset_class = filtersets.JobResultFilterSet
 
 
 
 
 #
 #
@@ -384,4 +384,4 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
     """
     """
     queryset = ContentType.objects.order_by('app_label', 'model')
     queryset = ContentType.objects.order_by('app_label', 'model')
     serializer_class = serializers.ContentTypeSerializer
     serializer_class = serializers.ContentTypeSerializer
-    filterset_class = filters.ContentTypeFilterSet
+    filterset_class = filtersets.ContentTypeFilterSet

+ 10 - 358
netbox/extras/filters.py

@@ -1,31 +1,12 @@
 import django_filters
 import django_filters
-from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
-from django.db.models import Q
 from django.forms import DateField, IntegerField, NullBooleanField
 from django.forms import DateField, IntegerField, NullBooleanField
 
 
-from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
-from tenancy.models import Tenant, TenantGroup
-from utilities.filters import BaseFilterSet, ContentTypeFilter
-from virtualization.models import Cluster, ClusterGroup
+from .models import Tag
 from .choices import *
 from .choices import *
-from .models import *
-
 
 
 __all__ = (
 __all__ = (
-    'ConfigContextFilterSet',
-    'ContentTypeFilterSet',
-    'CreatedUpdatedFilterSet',
     'CustomFieldFilter',
     'CustomFieldFilter',
-    'CustomLinkFilterSet',
-    'CustomFieldModelFilterSet',
-    'ExportTemplateFilterSet',
-    'ImageAttachmentFilterSet',
-    'JournalEntryFilterSet',
-    'LocalConfigContextFilterSet',
-    'ObjectChangeFilterSet',
-    'TagFilterSet',
-    'WebhookFilterSet',
+    'TagFilter',
 )
 )
 
 
 EXACT_FILTER_TYPES = (
 EXACT_FILTER_TYPES = (
@@ -36,41 +17,6 @@ EXACT_FILTER_TYPES = (
 )
 )
 
 
 
 
-class CreatedUpdatedFilterSet(django_filters.FilterSet):
-    created = django_filters.DateFilter()
-    created__gte = django_filters.DateFilter(
-        field_name='created',
-        lookup_expr='gte'
-    )
-    created__lte = django_filters.DateFilter(
-        field_name='created',
-        lookup_expr='lte'
-    )
-    last_updated = django_filters.DateTimeFilter()
-    last_updated__gte = django_filters.DateTimeFilter(
-        field_name='last_updated',
-        lookup_expr='gte'
-    )
-    last_updated__lte = django_filters.DateTimeFilter(
-        field_name='last_updated',
-        lookup_expr='lte'
-    )
-
-
-class WebhookFilterSet(BaseFilterSet):
-    content_types = ContentTypeFilter()
-    http_method = django_filters.MultipleChoiceFilter(
-        choices=WebhookHttpMethodChoices
-    )
-
-    class Meta:
-        model = Webhook
-        fields = [
-            'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled',
-            'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
-        ]
-
-
 class CustomFieldFilter(django_filters.Filter):
 class CustomFieldFilter(django_filters.Filter):
     """
     """
     Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
     Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
@@ -94,310 +40,16 @@ class CustomFieldFilter(django_filters.Filter):
                 self.lookup_expr = 'icontains'
                 self.lookup_expr = 'icontains'
 
 
 
 
-class CustomFieldModelFilterSet(django_filters.FilterSet):
+class TagFilter(django_filters.ModelMultipleChoiceFilter):
     """
     """
-    Dynamically add a Filter for each CustomField applicable to the parent model.
+    Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered
+    to objects matching all tags.
     """
     """
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        custom_fields = CustomField.objects.filter(
-            content_types=ContentType.objects.get_for_model(self._meta.model)
-        ).exclude(
-            filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
-        )
-        for cf in custom_fields:
-            self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
-
-
-class CustomFieldFilterSet(django_filters.FilterSet):
-    content_types = ContentTypeFilter()
-
-    class Meta:
-        model = CustomField
-        fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
-
-
-class CustomLinkFilterSet(BaseFilterSet):
-
-    class Meta:
-        model = CustomLink
-        fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window']
-
-
-class ExportTemplateFilterSet(BaseFilterSet):
-
-    class Meta:
-        model = ExportTemplate
-        fields = ['id', 'content_type', 'name']
-
-
-class ImageAttachmentFilterSet(BaseFilterSet):
-    content_type = ContentTypeFilter()
-
-    class Meta:
-        model = ImageAttachment
-        fields = ['id', 'content_type_id', 'object_id', 'name']
-
-
-class JournalEntryFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-    created = django_filters.DateTimeFromToRangeFilter()
-    assigned_object_type = ContentTypeFilter()
-    created_by_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=User.objects.all(),
-        label='User (ID)',
-    )
-    created_by = django_filters.ModelMultipleChoiceFilter(
-        field_name='created_by__username',
-        queryset=User.objects.all(),
-        to_field_name='username',
-        label='User (name)',
-    )
-    kind = django_filters.MultipleChoiceFilter(
-        choices=JournalEntryKindChoices
-    )
-
-    class Meta:
-        model = JournalEntry
-        fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind']
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(comments__icontains=value)
 
 
+        kwargs.setdefault('field_name', 'tags__slug')
+        kwargs.setdefault('to_field_name', 'slug')
+        kwargs.setdefault('conjoined', True)
+        kwargs.setdefault('queryset', Tag.objects.all())
 
 
-class TagFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-
-    class Meta:
-        model = Tag
-        fields = ['id', 'name', 'slug', 'color']
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            Q(name__icontains=value) |
-            Q(slug__icontains=value)
-        )
-
-
-class ConfigContextFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-    region_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='regions',
-        queryset=Region.objects.all(),
-        label='Region',
-    )
-    region = django_filters.ModelMultipleChoiceFilter(
-        field_name='regions__slug',
-        queryset=Region.objects.all(),
-        to_field_name='slug',
-        label='Region (slug)',
-    )
-    site_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='site_groups__slug',
-        queryset=SiteGroup.objects.all(),
-        to_field_name='slug',
-        label='Site group (slug)',
-    )
-    site_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='site_groups',
-        queryset=SiteGroup.objects.all(),
-        label='Site group',
-    )
-    site_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='sites',
-        queryset=Site.objects.all(),
-        label='Site',
-    )
-    site = django_filters.ModelMultipleChoiceFilter(
-        field_name='sites__slug',
-        queryset=Site.objects.all(),
-        to_field_name='slug',
-        label='Site (slug)',
-    )
-    device_type_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='device_types',
-        queryset=DeviceType.objects.all(),
-        label='Device type',
-    )
-    role_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='roles',
-        queryset=DeviceRole.objects.all(),
-        label='Role',
-    )
-    role = django_filters.ModelMultipleChoiceFilter(
-        field_name='roles__slug',
-        queryset=DeviceRole.objects.all(),
-        to_field_name='slug',
-        label='Role (slug)',
-    )
-    platform_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='platforms',
-        queryset=Platform.objects.all(),
-        label='Platform',
-    )
-    platform = django_filters.ModelMultipleChoiceFilter(
-        field_name='platforms__slug',
-        queryset=Platform.objects.all(),
-        to_field_name='slug',
-        label='Platform (slug)',
-    )
-    cluster_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='cluster_groups',
-        queryset=ClusterGroup.objects.all(),
-        label='Cluster group',
-    )
-    cluster_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='cluster_groups__slug',
-        queryset=ClusterGroup.objects.all(),
-        to_field_name='slug',
-        label='Cluster group (slug)',
-    )
-    cluster_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='clusters',
-        queryset=Cluster.objects.all(),
-        label='Cluster',
-    )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant_groups',
-        queryset=TenantGroup.objects.all(),
-        label='Tenant group',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant_groups__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenants',
-        queryset=Tenant.objects.all(),
-        label='Tenant',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenants__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
-    tag = django_filters.ModelMultipleChoiceFilter(
-        field_name='tags__slug',
-        queryset=Tag.objects.all(),
-        to_field_name='slug',
-        label='Tag (slug)',
-    )
-
-    class Meta:
-        model = ConfigContext
-        fields = ['id', 'name', 'is_active']
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            Q(name__icontains=value) |
-            Q(description__icontains=value) |
-            Q(data__icontains=value)
-        )
-
-
-#
-# Filter for Local Config Context Data
-#
-
-class LocalConfigContextFilterSet(django_filters.FilterSet):
-    local_context_data = django_filters.BooleanFilter(
-        method='_local_context_data',
-        label='Has local config context data',
-    )
-
-    def _local_context_data(self, queryset, name, value):
-        return queryset.exclude(local_context_data__isnull=value)
-
-
-class ObjectChangeFilterSet(BaseFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-    time = django_filters.DateTimeFromToRangeFilter()
-    changed_object_type = ContentTypeFilter()
-    user_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=User.objects.all(),
-        label='User (ID)',
-    )
-    user = django_filters.ModelMultipleChoiceFilter(
-        field_name='user__username',
-        queryset=User.objects.all(),
-        to_field_name='username',
-        label='User name',
-    )
-
-    class Meta:
-        model = ObjectChange
-        fields = [
-            'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
-            'object_repr',
-        ]
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            Q(user_name__icontains=value) |
-            Q(object_repr__icontains=value)
-        )
-
-
-#
-# Job Results
-#
-
-class JobResultFilterSet(BaseFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-    created = django_filters.DateTimeFilter()
-    completed = django_filters.DateTimeFilter()
-    status = django_filters.MultipleChoiceFilter(
-        choices=JobResultStatusChoices,
-        null_value=None
-    )
-
-    class Meta:
-        model = JobResult
-        fields = [
-            'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name'
-        ]
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            Q(user__username__icontains=value)
-        )
-
-
-#
-# ContentTypes
-#
-
-class ContentTypeFilterSet(django_filters.FilterSet):
-
-    class Meta:
-        model = ContentType
-        fields = ['id', 'app_label', 'model']
+        super().__init__(*args, **kwargs)

+ 340 - 0
netbox/extras/filtersets.py

@@ -0,0 +1,340 @@
+import django_filters
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+
+from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
+from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet
+from tenancy.models import Tenant, TenantGroup
+from utilities.filters import ContentTypeFilter
+from virtualization.models import Cluster, ClusterGroup
+from .choices import *
+from .models import *
+
+
+__all__ = (
+    'ConfigContextFilterSet',
+    'ContentTypeFilterSet',
+    'CustomLinkFilterSet',
+    'ExportTemplateFilterSet',
+    'ImageAttachmentFilterSet',
+    'JournalEntryFilterSet',
+    'LocalConfigContextFilterSet',
+    'ObjectChangeFilterSet',
+    'TagFilterSet',
+    'WebhookFilterSet',
+)
+
+EXACT_FILTER_TYPES = (
+    CustomFieldTypeChoices.TYPE_BOOLEAN,
+    CustomFieldTypeChoices.TYPE_DATE,
+    CustomFieldTypeChoices.TYPE_INTEGER,
+    CustomFieldTypeChoices.TYPE_SELECT,
+)
+
+
+class WebhookFilterSet(BaseFilterSet):
+    content_types = ContentTypeFilter()
+    http_method = django_filters.MultipleChoiceFilter(
+        choices=WebhookHttpMethodChoices
+    )
+
+    class Meta:
+        model = Webhook
+        fields = [
+            'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled',
+            'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
+        ]
+
+
+class CustomFieldFilterSet(django_filters.FilterSet):
+    content_types = ContentTypeFilter()
+
+    class Meta:
+        model = CustomField
+        fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
+
+
+class CustomLinkFilterSet(BaseFilterSet):
+
+    class Meta:
+        model = CustomLink
+        fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window']
+
+
+class ExportTemplateFilterSet(BaseFilterSet):
+
+    class Meta:
+        model = ExportTemplate
+        fields = ['id', 'content_type', 'name']
+
+
+class ImageAttachmentFilterSet(BaseFilterSet):
+    content_type = ContentTypeFilter()
+
+    class Meta:
+        model = ImageAttachment
+        fields = ['id', 'content_type_id', 'object_id', 'name']
+
+
+class JournalEntryFilterSet(ChangeLoggedModelFilterSet):
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    created = django_filters.DateTimeFromToRangeFilter()
+    assigned_object_type = ContentTypeFilter()
+    created_by_id = django_filters.ModelMultipleChoiceFilter(
+        queryset=User.objects.all(),
+        label='User (ID)',
+    )
+    created_by = django_filters.ModelMultipleChoiceFilter(
+        field_name='created_by__username',
+        queryset=User.objects.all(),
+        to_field_name='username',
+        label='User (name)',
+    )
+    kind = django_filters.MultipleChoiceFilter(
+        choices=JournalEntryKindChoices
+    )
+
+    class Meta:
+        model = JournalEntry
+        fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind']
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(comments__icontains=value)
+
+
+class TagFilterSet(ChangeLoggedModelFilterSet):
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+
+    class Meta:
+        model = Tag
+        fields = ['id', 'name', 'slug', 'color']
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(name__icontains=value) |
+            Q(slug__icontains=value)
+        )
+
+
+class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    region_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='regions',
+        queryset=Region.objects.all(),
+        label='Region',
+    )
+    region = django_filters.ModelMultipleChoiceFilter(
+        field_name='regions__slug',
+        queryset=Region.objects.all(),
+        to_field_name='slug',
+        label='Region (slug)',
+    )
+    site_group = django_filters.ModelMultipleChoiceFilter(
+        field_name='site_groups__slug',
+        queryset=SiteGroup.objects.all(),
+        to_field_name='slug',
+        label='Site group (slug)',
+    )
+    site_group_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='site_groups',
+        queryset=SiteGroup.objects.all(),
+        label='Site group',
+    )
+    site_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='sites',
+        queryset=Site.objects.all(),
+        label='Site',
+    )
+    site = django_filters.ModelMultipleChoiceFilter(
+        field_name='sites__slug',
+        queryset=Site.objects.all(),
+        to_field_name='slug',
+        label='Site (slug)',
+    )
+    device_type_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='device_types',
+        queryset=DeviceType.objects.all(),
+        label='Device type',
+    )
+    role_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='roles',
+        queryset=DeviceRole.objects.all(),
+        label='Role',
+    )
+    role = django_filters.ModelMultipleChoiceFilter(
+        field_name='roles__slug',
+        queryset=DeviceRole.objects.all(),
+        to_field_name='slug',
+        label='Role (slug)',
+    )
+    platform_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='platforms',
+        queryset=Platform.objects.all(),
+        label='Platform',
+    )
+    platform = django_filters.ModelMultipleChoiceFilter(
+        field_name='platforms__slug',
+        queryset=Platform.objects.all(),
+        to_field_name='slug',
+        label='Platform (slug)',
+    )
+    cluster_group_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='cluster_groups',
+        queryset=ClusterGroup.objects.all(),
+        label='Cluster group',
+    )
+    cluster_group = django_filters.ModelMultipleChoiceFilter(
+        field_name='cluster_groups__slug',
+        queryset=ClusterGroup.objects.all(),
+        to_field_name='slug',
+        label='Cluster group (slug)',
+    )
+    cluster_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='clusters',
+        queryset=Cluster.objects.all(),
+        label='Cluster',
+    )
+    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant_groups',
+        queryset=TenantGroup.objects.all(),
+        label='Tenant group',
+    )
+    tenant_group = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant_groups__slug',
+        queryset=TenantGroup.objects.all(),
+        to_field_name='slug',
+        label='Tenant group (slug)',
+    )
+    tenant_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenants',
+        queryset=Tenant.objects.all(),
+        label='Tenant',
+    )
+    tenant = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenants__slug',
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        label='Tenant (slug)',
+    )
+    tag = django_filters.ModelMultipleChoiceFilter(
+        field_name='tags__slug',
+        queryset=Tag.objects.all(),
+        to_field_name='slug',
+        label='Tag (slug)',
+    )
+
+    class Meta:
+        model = ConfigContext
+        fields = ['id', 'name', 'is_active']
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(name__icontains=value) |
+            Q(description__icontains=value) |
+            Q(data__icontains=value)
+        )
+
+
+#
+# Filter for Local Config Context Data
+#
+
+class LocalConfigContextFilterSet(django_filters.FilterSet):
+    local_context_data = django_filters.BooleanFilter(
+        method='_local_context_data',
+        label='Has local config context data',
+    )
+
+    def _local_context_data(self, queryset, name, value):
+        return queryset.exclude(local_context_data__isnull=value)
+
+
+class ObjectChangeFilterSet(BaseFilterSet):
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    time = django_filters.DateTimeFromToRangeFilter()
+    changed_object_type = ContentTypeFilter()
+    user_id = django_filters.ModelMultipleChoiceFilter(
+        queryset=User.objects.all(),
+        label='User (ID)',
+    )
+    user = django_filters.ModelMultipleChoiceFilter(
+        field_name='user__username',
+        queryset=User.objects.all(),
+        to_field_name='username',
+        label='User name',
+    )
+
+    class Meta:
+        model = ObjectChange
+        fields = [
+            'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
+            'object_repr',
+        ]
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(user_name__icontains=value) |
+            Q(object_repr__icontains=value)
+        )
+
+
+#
+# Job Results
+#
+
+class JobResultFilterSet(BaseFilterSet):
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+    created = django_filters.DateTimeFilter()
+    completed = django_filters.DateTimeFilter()
+    status = django_filters.MultipleChoiceFilter(
+        choices=JobResultStatusChoices,
+        null_value=None
+    )
+
+    class Meta:
+        model = JobResult
+        fields = [
+            'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name'
+        ]
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            Q(user__username__icontains=value)
+        )
+
+
+#
+# ContentTypes
+#
+
+class ContentTypeFilterSet(django_filters.FilterSet):
+
+    class Meta:
+        model = ContentType
+        fields = ['id', 'app_label', 'model']

+ 1 - 1
netbox/extras/tests/test_customfields.py

@@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
 from django.urls import reverse
 from django.urls import reverse
 from rest_framework import status
 from rest_framework import status
 
 
-from dcim.filters import SiteFilterSet
+from dcim.filtersets import SiteFilterSet
 from dcim.forms import SiteCSVForm
 from dcim.forms import SiteCSVForm
 from dcim.models import Site, Rack
 from dcim.models import Site, Rack
 from extras.choices import *
 from extras.choices import *

+ 1 - 1
netbox/extras/tests/test_filters.py

@@ -6,7 +6,7 @@ from django.test import TestCase
 
 
 from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
 from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
-from extras.filters import *
+from extras.filtersets import *
 from extras.models import *
 from extras.models import *
 from ipam.models import IPAddress
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup

+ 8 - 8
netbox/extras/views.py

@@ -13,7 +13,7 @@ from utilities.forms import ConfirmationForm
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from utilities.utils import copy_safe_request, count_related, shallow_compare_dict
 from utilities.utils import copy_safe_request, count_related, shallow_compare_dict
 from utilities.views import ContentTypePermissionRequiredMixin
 from utilities.views import ContentTypePermissionRequiredMixin
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .choices import JobResultStatusChoices
 from .choices import JobResultStatusChoices
 from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem
 from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem
 from .reports import get_report, get_reports, run_report
 from .reports import get_report, get_reports, run_report
@@ -28,7 +28,7 @@ class TagListView(generic.ObjectListView):
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
         items=count_related(TaggedItem, 'tag')
         items=count_related(TaggedItem, 'tag')
     )
     )
-    filterset = filters.TagFilterSet
+    filterset = filtersets.TagFilterSet
     filterset_form = forms.TagFilterForm
     filterset_form = forms.TagFilterForm
     table = tables.TagTable
     table = tables.TagTable
 
 
@@ -94,7 +94,7 @@ class TagBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConfigContextListView(generic.ObjectListView):
 class ConfigContextListView(generic.ObjectListView):
     queryset = ConfigContext.objects.all()
     queryset = ConfigContext.objects.all()
-    filterset = filters.ConfigContextFilterSet
+    filterset = filtersets.ConfigContextFilterSet
     filterset_form = forms.ConfigContextFilterForm
     filterset_form = forms.ConfigContextFilterForm
     table = tables.ConfigContextTable
     table = tables.ConfigContextTable
     action_buttons = ('add',)
     action_buttons = ('add',)
@@ -127,7 +127,7 @@ class ConfigContextEditView(generic.ObjectEditView):
 
 
 class ConfigContextBulkEditView(generic.BulkEditView):
 class ConfigContextBulkEditView(generic.BulkEditView):
     queryset = ConfigContext.objects.all()
     queryset = ConfigContext.objects.all()
-    filterset = filters.ConfigContextFilterSet
+    filterset = filtersets.ConfigContextFilterSet
     table = tables.ConfigContextTable
     table = tables.ConfigContextTable
     form = forms.ConfigContextBulkEditForm
     form = forms.ConfigContextBulkEditForm
 
 
@@ -173,7 +173,7 @@ class ObjectConfigContextView(generic.ObjectView):
 
 
 class ObjectChangeListView(generic.ObjectListView):
 class ObjectChangeListView(generic.ObjectListView):
     queryset = ObjectChange.objects.all()
     queryset = ObjectChange.objects.all()
-    filterset = filters.ObjectChangeFilterSet
+    filterset = filtersets.ObjectChangeFilterSet
     filterset_form = forms.ObjectChangeFilterForm
     filterset_form = forms.ObjectChangeFilterForm
     table = tables.ObjectChangeTable
     table = tables.ObjectChangeTable
     template_name = 'extras/objectchange_list.html'
     template_name = 'extras/objectchange_list.html'
@@ -300,7 +300,7 @@ class ImageAttachmentDeleteView(generic.ObjectDeleteView):
 
 
 class JournalEntryListView(generic.ObjectListView):
 class JournalEntryListView(generic.ObjectListView):
     queryset = JournalEntry.objects.all()
     queryset = JournalEntry.objects.all()
-    filterset = filters.JournalEntryFilterSet
+    filterset = filtersets.JournalEntryFilterSet
     filterset_form = forms.JournalEntryFilterForm
     filterset_form = forms.JournalEntryFilterForm
     table = tables.JournalEntryTable
     table = tables.JournalEntryTable
     action_buttons = ('export',)
     action_buttons = ('export',)
@@ -338,14 +338,14 @@ class JournalEntryDeleteView(generic.ObjectDeleteView):
 
 
 class JournalEntryBulkEditView(generic.BulkEditView):
 class JournalEntryBulkEditView(generic.BulkEditView):
     queryset = JournalEntry.objects.prefetch_related('created_by')
     queryset = JournalEntry.objects.prefetch_related('created_by')
-    filterset = filters.JournalEntryFilterSet
+    filterset = filtersets.JournalEntryFilterSet
     table = tables.JournalEntryTable
     table = tables.JournalEntryTable
     form = forms.JournalEntryBulkEditForm
     form = forms.JournalEntryBulkEditForm
 
 
 
 
 class JournalEntryBulkDeleteView(generic.BulkDeleteView):
 class JournalEntryBulkDeleteView(generic.BulkDeleteView):
     queryset = JournalEntry.objects.prefetch_related('created_by')
     queryset = JournalEntry.objects.prefetch_related('created_by')
-    filterset = filters.JournalEntryFilterSet
+    filterset = filtersets.JournalEntryFilterSet
     table = tables.JournalEntryTable
     table = tables.JournalEntryTable
 
 
 
 

+ 11 - 11
netbox/ipam/api/views.py

@@ -10,7 +10,7 @@ from rest_framework.response import Response
 from rest_framework.routers import APIRootView
 from rest_framework.routers import APIRootView
 
 
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
-from ipam import filters
+from ipam import filtersets
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from netbox.api.views import ModelViewSet
 from netbox.api.views import ModelViewSet
 from utilities.constants import ADVISORY_LOCK_KEYS
 from utilities.constants import ADVISORY_LOCK_KEYS
@@ -38,7 +38,7 @@ class VRFViewSet(CustomFieldModelViewSet):
         prefix_count=count_related(Prefix, 'vrf')
         prefix_count=count_related(Prefix, 'vrf')
     )
     )
     serializer_class = serializers.VRFSerializer
     serializer_class = serializers.VRFSerializer
-    filterset_class = filters.VRFFilterSet
+    filterset_class = filtersets.VRFFilterSet
 
 
 
 
 #
 #
@@ -48,7 +48,7 @@ class VRFViewSet(CustomFieldModelViewSet):
 class RouteTargetViewSet(CustomFieldModelViewSet):
 class RouteTargetViewSet(CustomFieldModelViewSet):
     queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags')
     queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags')
     serializer_class = serializers.RouteTargetSerializer
     serializer_class = serializers.RouteTargetSerializer
-    filterset_class = filters.RouteTargetFilterSet
+    filterset_class = filtersets.RouteTargetFilterSet
 
 
 
 
 #
 #
@@ -60,7 +60,7 @@ class RIRViewSet(CustomFieldModelViewSet):
         aggregate_count=count_related(Aggregate, 'rir')
         aggregate_count=count_related(Aggregate, 'rir')
     )
     )
     serializer_class = serializers.RIRSerializer
     serializer_class = serializers.RIRSerializer
-    filterset_class = filters.RIRFilterSet
+    filterset_class = filtersets.RIRFilterSet
 
 
 
 
 #
 #
@@ -70,7 +70,7 @@ class RIRViewSet(CustomFieldModelViewSet):
 class AggregateViewSet(CustomFieldModelViewSet):
 class AggregateViewSet(CustomFieldModelViewSet):
     queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
     queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
     serializer_class = serializers.AggregateSerializer
     serializer_class = serializers.AggregateSerializer
-    filterset_class = filters.AggregateFilterSet
+    filterset_class = filtersets.AggregateFilterSet
 
 
 
 
 #
 #
@@ -83,7 +83,7 @@ class RoleViewSet(CustomFieldModelViewSet):
         vlan_count=count_related(VLAN, 'role')
         vlan_count=count_related(VLAN, 'role')
     )
     )
     serializer_class = serializers.RoleSerializer
     serializer_class = serializers.RoleSerializer
-    filterset_class = filters.RoleFilterSet
+    filterset_class = filtersets.RoleFilterSet
 
 
 
 
 #
 #
@@ -95,7 +95,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
         'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
         'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
     )
     )
     serializer_class = serializers.PrefixSerializer
     serializer_class = serializers.PrefixSerializer
-    filterset_class = filters.PrefixFilterSet
+    filterset_class = filtersets.PrefixFilterSet
 
 
     def get_serializer_class(self):
     def get_serializer_class(self):
         if self.action == "available_prefixes" and self.request.method == "POST":
         if self.action == "available_prefixes" and self.request.method == "POST":
@@ -275,7 +275,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
         'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
         'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
     )
     )
     serializer_class = serializers.IPAddressSerializer
     serializer_class = serializers.IPAddressSerializer
-    filterset_class = filters.IPAddressFilterSet
+    filterset_class = filtersets.IPAddressFilterSet
 
 
 
 
 #
 #
@@ -287,7 +287,7 @@ class VLANGroupViewSet(CustomFieldModelViewSet):
         vlan_count=count_related(VLAN, 'group')
         vlan_count=count_related(VLAN, 'group')
     )
     )
     serializer_class = serializers.VLANGroupSerializer
     serializer_class = serializers.VLANGroupSerializer
-    filterset_class = filters.VLANGroupFilterSet
+    filterset_class = filtersets.VLANGroupFilterSet
 
 
 
 
 #
 #
@@ -301,7 +301,7 @@ class VLANViewSet(CustomFieldModelViewSet):
         prefix_count=count_related(Prefix, 'vlan')
         prefix_count=count_related(Prefix, 'vlan')
     )
     )
     serializer_class = serializers.VLANSerializer
     serializer_class = serializers.VLANSerializer
-    filterset_class = filters.VLANFilterSet
+    filterset_class = filtersets.VLANFilterSet
 
 
 
 
 #
 #
@@ -313,4 +313,4 @@ class ServiceViewSet(ModelViewSet):
         'device', 'virtual_machine', 'tags', 'ipaddresses'
         'device', 'virtual_machine', 'tags', 'ipaddresses'
     )
     )
     serializer_class = serializers.ServiceSerializer
     serializer_class = serializers.ServiceSerializer
-    filterset_class = filters.ServiceFilterSet
+    filterset_class = filtersets.ServiceFilterSet

+ 14 - 14
netbox/ipam/filters.py → netbox/ipam/filtersets.py

@@ -6,11 +6,11 @@ from django.db.models import Q
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
 
 
 from dcim.models import Device, Interface, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Region, Site, SiteGroup
-from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
-from tenancy.filters import TenancyFilterSet
+from extras.filters import TagFilter
+from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
+from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import (
 from utilities.filters import (
-    BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet,
-    NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter,
+    ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
 )
 )
 from virtualization.models import VirtualMachine, VMInterface
 from virtualization.models import VirtualMachine, VMInterface
 from .choices import *
 from .choices import *
@@ -31,7 +31,7 @@ __all__ = (
 )
 )
 
 
 
 
-class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -74,7 +74,7 @@ class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, C
         fields = ['id', 'name', 'rd', 'enforce_unique']
         fields = ['id', 'name', 'rd', 'enforce_unique']
 
 
 
 
-class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -116,14 +116,14 @@ class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilt
         fields = ['id', 'name']
         fields = ['id', 'name']
 
 
 
 
-class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class RIRFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = RIR
         model = RIR
         fields = ['id', 'name', 'slug', 'is_private', 'description']
         fields = ['id', 'name', 'slug', 'is_private', 'description']
 
 
 
 
-class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -173,7 +173,7 @@ class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
             return queryset.none()
             return queryset.none()
 
 
 
 
-class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class RoleFilterSet(OrganizationalModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -184,7 +184,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilter
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -369,7 +369,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet
         )
         )
 
 
 
 
-class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -535,7 +535,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
         return queryset.exclude(assigned_object_id__isnull=value)
         return queryset.exclude(assigned_object_id__isnull=value)
 
 
 
 
-class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class VLANGroupFilterSet(OrganizationalModelFilterSet):
     scope_type = ContentTypeFilter()
     scope_type = ContentTypeFilter()
     region = django_filters.NumberFilter(
     region = django_filters.NumberFilter(
         method='filter_scope'
         method='filter_scope'
@@ -570,7 +570,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF
         )
         )
 
 
 
 
-class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -666,7 +666,7 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
         return queryset.get_for_virtualmachine(value)
         return queryset.get_for_virtualmachine(value)
 
 
 
 
-class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
+class ServiceFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/ipam/tests/test_filters.py

@@ -2,7 +2,7 @@ from django.test import TestCase
 
 
 from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
 from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
-from ipam.filters import *
+from ipam.filtersets import *
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup

+ 30 - 30
netbox/ipam/views.py

@@ -7,7 +7,7 @@ from netbox.views import generic
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from utilities.utils import count_related
 from utilities.utils import count_related
 from virtualization.models import VirtualMachine, VMInterface
 from virtualization.models import VirtualMachine, VMInterface
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .constants import *
 from .constants import *
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans
 from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans
@@ -19,7 +19,7 @@ from .utils import add_available_ipaddresses, add_available_prefixes, add_availa
 
 
 class VRFListView(generic.ObjectListView):
 class VRFListView(generic.ObjectListView):
     queryset = VRF.objects.all()
     queryset = VRF.objects.all()
-    filterset = filters.VRFFilterSet
+    filterset = filtersets.VRFFilterSet
     filterset_form = forms.VRFFilterForm
     filterset_form = forms.VRFFilterForm
     table = tables.VRFTable
     table = tables.VRFTable
 
 
@@ -65,14 +65,14 @@ class VRFBulkImportView(generic.BulkImportView):
 
 
 class VRFBulkEditView(generic.BulkEditView):
 class VRFBulkEditView(generic.BulkEditView):
     queryset = VRF.objects.prefetch_related('tenant')
     queryset = VRF.objects.prefetch_related('tenant')
-    filterset = filters.VRFFilterSet
+    filterset = filtersets.VRFFilterSet
     table = tables.VRFTable
     table = tables.VRFTable
     form = forms.VRFBulkEditForm
     form = forms.VRFBulkEditForm
 
 
 
 
 class VRFBulkDeleteView(generic.BulkDeleteView):
 class VRFBulkDeleteView(generic.BulkDeleteView):
     queryset = VRF.objects.prefetch_related('tenant')
     queryset = VRF.objects.prefetch_related('tenant')
-    filterset = filters.VRFFilterSet
+    filterset = filtersets.VRFFilterSet
     table = tables.VRFTable
     table = tables.VRFTable
 
 
 
 
@@ -82,7 +82,7 @@ class VRFBulkDeleteView(generic.BulkDeleteView):
 
 
 class RouteTargetListView(generic.ObjectListView):
 class RouteTargetListView(generic.ObjectListView):
     queryset = RouteTarget.objects.all()
     queryset = RouteTarget.objects.all()
-    filterset = filters.RouteTargetFilterSet
+    filterset = filtersets.RouteTargetFilterSet
     filterset_form = forms.RouteTargetFilterForm
     filterset_form = forms.RouteTargetFilterForm
     table = tables.RouteTargetTable
     table = tables.RouteTargetTable
 
 
@@ -123,14 +123,14 @@ class RouteTargetBulkImportView(generic.BulkImportView):
 
 
 class RouteTargetBulkEditView(generic.BulkEditView):
 class RouteTargetBulkEditView(generic.BulkEditView):
     queryset = RouteTarget.objects.prefetch_related('tenant')
     queryset = RouteTarget.objects.prefetch_related('tenant')
-    filterset = filters.RouteTargetFilterSet
+    filterset = filtersets.RouteTargetFilterSet
     table = tables.RouteTargetTable
     table = tables.RouteTargetTable
     form = forms.RouteTargetBulkEditForm
     form = forms.RouteTargetBulkEditForm
 
 
 
 
 class RouteTargetBulkDeleteView(generic.BulkDeleteView):
 class RouteTargetBulkDeleteView(generic.BulkDeleteView):
     queryset = RouteTarget.objects.prefetch_related('tenant')
     queryset = RouteTarget.objects.prefetch_related('tenant')
-    filterset = filters.RouteTargetFilterSet
+    filterset = filtersets.RouteTargetFilterSet
     table = tables.RouteTargetTable
     table = tables.RouteTargetTable
 
 
 
 
@@ -142,7 +142,7 @@ class RIRListView(generic.ObjectListView):
     queryset = RIR.objects.annotate(
     queryset = RIR.objects.annotate(
         aggregate_count=count_related(Aggregate, 'rir')
         aggregate_count=count_related(Aggregate, 'rir')
     )
     )
-    filterset = filters.RIRFilterSet
+    filterset = filtersets.RIRFilterSet
     filterset_form = forms.RIRFilterForm
     filterset_form = forms.RIRFilterForm
     table = tables.RIRTable
     table = tables.RIRTable
     template_name = 'ipam/rir_list.html'
     template_name = 'ipam/rir_list.html'
@@ -184,7 +184,7 @@ class RIRBulkEditView(generic.BulkEditView):
     queryset = RIR.objects.annotate(
     queryset = RIR.objects.annotate(
         aggregate_count=count_related(Aggregate, 'rir')
         aggregate_count=count_related(Aggregate, 'rir')
     )
     )
-    filterset = filters.RIRFilterSet
+    filterset = filtersets.RIRFilterSet
     table = tables.RIRTable
     table = tables.RIRTable
     form = forms.RIRBulkEditForm
     form = forms.RIRBulkEditForm
 
 
@@ -193,7 +193,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView):
     queryset = RIR.objects.annotate(
     queryset = RIR.objects.annotate(
         aggregate_count=count_related(Aggregate, 'rir')
         aggregate_count=count_related(Aggregate, 'rir')
     )
     )
-    filterset = filters.RIRFilterSet
+    filterset = filtersets.RIRFilterSet
     table = tables.RIRTable
     table = tables.RIRTable
 
 
 
 
@@ -205,7 +205,7 @@ class AggregateListView(generic.ObjectListView):
     queryset = Aggregate.objects.annotate(
     queryset = Aggregate.objects.annotate(
         child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
         child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
     )
     )
-    filterset = filters.AggregateFilterSet
+    filterset = filtersets.AggregateFilterSet
     filterset_form = forms.AggregateFilterForm
     filterset_form = forms.AggregateFilterForm
     table = tables.AggregateDetailTable
     table = tables.AggregateDetailTable
     template_name = 'ipam/aggregate_list.html'
     template_name = 'ipam/aggregate_list.html'
@@ -280,14 +280,14 @@ class AggregateBulkImportView(generic.BulkImportView):
 
 
 class AggregateBulkEditView(generic.BulkEditView):
 class AggregateBulkEditView(generic.BulkEditView):
     queryset = Aggregate.objects.prefetch_related('rir')
     queryset = Aggregate.objects.prefetch_related('rir')
-    filterset = filters.AggregateFilterSet
+    filterset = filtersets.AggregateFilterSet
     table = tables.AggregateTable
     table = tables.AggregateTable
     form = forms.AggregateBulkEditForm
     form = forms.AggregateBulkEditForm
 
 
 
 
 class AggregateBulkDeleteView(generic.BulkDeleteView):
 class AggregateBulkDeleteView(generic.BulkDeleteView):
     queryset = Aggregate.objects.prefetch_related('rir')
     queryset = Aggregate.objects.prefetch_related('rir')
-    filterset = filters.AggregateFilterSet
+    filterset = filtersets.AggregateFilterSet
     table = tables.AggregateTable
     table = tables.AggregateTable
 
 
 
 
@@ -337,7 +337,7 @@ class RoleBulkImportView(generic.BulkImportView):
 
 
 class RoleBulkEditView(generic.BulkEditView):
 class RoleBulkEditView(generic.BulkEditView):
     queryset = Role.objects.all()
     queryset = Role.objects.all()
-    filterset = filters.RoleFilterSet
+    filterset = filtersets.RoleFilterSet
     table = tables.RoleTable
     table = tables.RoleTable
     form = forms.RoleBulkEditForm
     form = forms.RoleBulkEditForm
 
 
@@ -353,7 +353,7 @@ class RoleBulkDeleteView(generic.BulkDeleteView):
 
 
 class PrefixListView(generic.ObjectListView):
 class PrefixListView(generic.ObjectListView):
     queryset = Prefix.objects.annotate_tree()
     queryset = Prefix.objects.annotate_tree()
-    filterset = filters.PrefixFilterSet
+    filterset = filtersets.PrefixFilterSet
     filterset_form = forms.PrefixFilterForm
     filterset_form = forms.PrefixFilterForm
     table = tables.PrefixDetailTable
     table = tables.PrefixDetailTable
     template_name = 'ipam/prefix_list.html'
     template_name = 'ipam/prefix_list.html'
@@ -493,14 +493,14 @@ class PrefixBulkImportView(generic.BulkImportView):
 
 
 class PrefixBulkEditView(generic.BulkEditView):
 class PrefixBulkEditView(generic.BulkEditView):
     queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
-    filterset = filters.PrefixFilterSet
+    filterset = filtersets.PrefixFilterSet
     table = tables.PrefixTable
     table = tables.PrefixTable
     form = forms.PrefixBulkEditForm
     form = forms.PrefixBulkEditForm
 
 
 
 
 class PrefixBulkDeleteView(generic.BulkDeleteView):
 class PrefixBulkDeleteView(generic.BulkDeleteView):
     queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
-    filterset = filters.PrefixFilterSet
+    filterset = filtersets.PrefixFilterSet
     table = tables.PrefixTable
     table = tables.PrefixTable
 
 
 
 
@@ -510,7 +510,7 @@ class PrefixBulkDeleteView(generic.BulkDeleteView):
 
 
 class IPAddressListView(generic.ObjectListView):
 class IPAddressListView(generic.ObjectListView):
     queryset = IPAddress.objects.all()
     queryset = IPAddress.objects.all()
-    filterset = filters.IPAddressFilterSet
+    filterset = filtersets.IPAddressFilterSet
     filterset_form = forms.IPAddressFilterForm
     filterset_form = forms.IPAddressFilterForm
     table = tables.IPAddressDetailTable
     table = tables.IPAddressDetailTable
 
 
@@ -613,7 +613,7 @@ class IPAddressAssignView(generic.ObjectView):
 
 
             addresses = self.queryset.prefetch_related('vrf', 'tenant')
             addresses = self.queryset.prefetch_related('vrf', 'tenant')
             # Limit to 100 results
             # Limit to 100 results
-            addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100]
+            addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
             table = tables.IPAddressAssignTable(addresses)
             table = tables.IPAddressAssignTable(addresses)
 
 
         return render(request, 'ipam/ipaddress_assign.html', {
         return render(request, 'ipam/ipaddress_assign.html', {
@@ -643,14 +643,14 @@ class IPAddressBulkImportView(generic.BulkImportView):
 
 
 class IPAddressBulkEditView(generic.BulkEditView):
 class IPAddressBulkEditView(generic.BulkEditView):
     queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
     queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
-    filterset = filters.IPAddressFilterSet
+    filterset = filtersets.IPAddressFilterSet
     table = tables.IPAddressTable
     table = tables.IPAddressTable
     form = forms.IPAddressBulkEditForm
     form = forms.IPAddressBulkEditForm
 
 
 
 
 class IPAddressBulkDeleteView(generic.BulkDeleteView):
 class IPAddressBulkDeleteView(generic.BulkDeleteView):
     queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
     queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
-    filterset = filters.IPAddressFilterSet
+    filterset = filtersets.IPAddressFilterSet
     table = tables.IPAddressTable
     table = tables.IPAddressTable
 
 
 
 
@@ -662,7 +662,7 @@ class VLANGroupListView(generic.ObjectListView):
     queryset = VLANGroup.objects.annotate(
     queryset = VLANGroup.objects.annotate(
         vlan_count=count_related(VLAN, 'group')
         vlan_count=count_related(VLAN, 'group')
     )
     )
-    filterset = filters.VLANGroupFilterSet
+    filterset = filtersets.VLANGroupFilterSet
     filterset_form = forms.VLANGroupFilterForm
     filterset_form = forms.VLANGroupFilterForm
     table = tables.VLANGroupTable
     table = tables.VLANGroupTable
 
 
@@ -718,7 +718,7 @@ class VLANGroupBulkEditView(generic.BulkEditView):
     queryset = VLANGroup.objects.annotate(
     queryset = VLANGroup.objects.annotate(
         vlan_count=count_related(VLAN, 'group')
         vlan_count=count_related(VLAN, 'group')
     )
     )
-    filterset = filters.VLANGroupFilterSet
+    filterset = filtersets.VLANGroupFilterSet
     table = tables.VLANGroupTable
     table = tables.VLANGroupTable
     form = forms.VLANGroupBulkEditForm
     form = forms.VLANGroupBulkEditForm
 
 
@@ -727,7 +727,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
     queryset = VLANGroup.objects.annotate(
     queryset = VLANGroup.objects.annotate(
         vlan_count=count_related(VLAN, 'group')
         vlan_count=count_related(VLAN, 'group')
     )
     )
-    filterset = filters.VLANGroupFilterSet
+    filterset = filtersets.VLANGroupFilterSet
     table = tables.VLANGroupTable
     table = tables.VLANGroupTable
 
 
 
 
@@ -737,7 +737,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
 
 
 class VLANListView(generic.ObjectListView):
 class VLANListView(generic.ObjectListView):
     queryset = VLAN.objects.all()
     queryset = VLAN.objects.all()
-    filterset = filters.VLANFilterSet
+    filterset = filtersets.VLANFilterSet
     filterset_form = forms.VLANFilterForm
     filterset_form = forms.VLANFilterForm
     table = tables.VLANDetailTable
     table = tables.VLANDetailTable
 
 
@@ -805,14 +805,14 @@ class VLANBulkImportView(generic.BulkImportView):
 
 
 class VLANBulkEditView(generic.BulkEditView):
 class VLANBulkEditView(generic.BulkEditView):
     queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
     queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
-    filterset = filters.VLANFilterSet
+    filterset = filtersets.VLANFilterSet
     table = tables.VLANTable
     table = tables.VLANTable
     form = forms.VLANBulkEditForm
     form = forms.VLANBulkEditForm
 
 
 
 
 class VLANBulkDeleteView(generic.BulkDeleteView):
 class VLANBulkDeleteView(generic.BulkDeleteView):
     queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
     queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
-    filterset = filters.VLANFilterSet
+    filterset = filtersets.VLANFilterSet
     table = tables.VLANTable
     table = tables.VLANTable
 
 
 
 
@@ -822,7 +822,7 @@ class VLANBulkDeleteView(generic.BulkDeleteView):
 
 
 class ServiceListView(generic.ObjectListView):
 class ServiceListView(generic.ObjectListView):
     queryset = Service.objects.all()
     queryset = Service.objects.all()
-    filterset = filters.ServiceFilterSet
+    filterset = filtersets.ServiceFilterSet
     filterset_form = forms.ServiceFilterForm
     filterset_form = forms.ServiceFilterForm
     table = tables.ServiceTable
     table = tables.ServiceTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -863,12 +863,12 @@ class ServiceDeleteView(generic.ObjectDeleteView):
 
 
 class ServiceBulkEditView(generic.BulkEditView):
 class ServiceBulkEditView(generic.BulkEditView):
     queryset = Service.objects.prefetch_related('device', 'virtual_machine')
     queryset = Service.objects.prefetch_related('device', 'virtual_machine')
-    filterset = filters.ServiceFilterSet
+    filterset = filtersets.ServiceFilterSet
     table = tables.ServiceTable
     table = tables.ServiceTable
     form = forms.ServiceBulkEditForm
     form = forms.ServiceBulkEditForm
 
 
 
 
 class ServiceBulkDeleteView(generic.BulkDeleteView):
 class ServiceBulkDeleteView(generic.BulkDeleteView):
     queryset = Service.objects.prefetch_related('device', 'virtual_machine')
     queryset = Service.objects.prefetch_related('device', 'virtual_machine')
-    filterset = filters.ServiceFilterSet
+    filterset = filtersets.ServiceFilterSet
     table = tables.ServiceTable
     table = tables.ServiceTable

+ 6 - 6
netbox/netbox/constants.py

@@ -1,9 +1,9 @@
 from collections import OrderedDict
 from collections import OrderedDict
 
 
-from circuits.filters import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
+from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
 from circuits.models import Circuit, ProviderNetwork, Provider
 from circuits.models import Circuit, ProviderNetwork, Provider
 from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable
 from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable
-from dcim.filters import (
+from dcim.filtersets import (
     CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
     CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
     SiteFilterSet, VirtualChassisFilterSet,
     SiteFilterSet, VirtualChassisFilterSet,
 )
 )
@@ -12,17 +12,17 @@ from dcim.tables import (
     CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
     CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
     VirtualChassisTable,
     VirtualChassisTable,
 )
 )
-from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
+from ipam.filtersets import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
 from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
 from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
 from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
-from secrets.filters import SecretFilterSet
+from secrets.filtersets import SecretFilterSet
 from secrets.models import Secret
 from secrets.models import Secret
 from secrets.tables import SecretTable
 from secrets.tables import SecretTable
-from tenancy.filters import TenantFilterSet
+from tenancy.filtersets import TenantFilterSet
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from tenancy.tables import TenantTable
 from tenancy.tables import TenantTable
 from utilities.utils import count_related
 from utilities.utils import count_related
-from virtualization.filters import ClusterFilterSet, VirtualMachineFilterSet
+from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet
 from virtualization.models import Cluster, VirtualMachine
 from virtualization.models import Cluster, VirtualMachine
 from virtualization.tables import ClusterTable, VirtualMachineDetailTable
 from virtualization.tables import ClusterTable, VirtualMachineDetailTable
 
 

+ 238 - 0
netbox/netbox/filtersets.py

@@ -0,0 +1,238 @@
+import django_filters
+from copy import deepcopy
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+from django_filters.utils import get_model_field, resolve_field
+
+from dcim.forms import MACAddressField
+from extras.choices import CustomFieldFilterLogicChoices
+from extras.filters import CustomFieldFilter, TagFilter
+from extras.models import CustomField
+from utilities.constants import (
+    FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
+    FILTER_NUMERIC_BASED_LOOKUP_MAP
+)
+from utilities import filters
+
+
+__all__ = (
+    'BaseFilterSet',
+    'ChangeLoggedModelFilterSet',
+    'OrganizationalModelFilterSet',
+    'PrimaryModelFilterSet',
+)
+
+
+#
+# FilterSets
+#
+
+class BaseFilterSet(django_filters.FilterSet):
+    """
+    A base FilterSet which provides common functionality to all NetBox FilterSets
+    """
+    FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
+    FILTER_DEFAULTS.update({
+        models.AutoField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.CharField: {
+            'filter_class': filters.MultiValueCharFilter
+        },
+        models.DateField: {
+            'filter_class': filters.MultiValueDateFilter
+        },
+        models.DateTimeField: {
+            'filter_class': filters.MultiValueDateTimeFilter
+        },
+        models.DecimalField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.EmailField: {
+            'filter_class': filters.MultiValueCharFilter
+        },
+        models.FloatField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.IntegerField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.PositiveIntegerField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.PositiveSmallIntegerField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.SlugField: {
+            'filter_class': filters.MultiValueCharFilter
+        },
+        models.SmallIntegerField: {
+            'filter_class': filters.MultiValueNumberFilter
+        },
+        models.TimeField: {
+            'filter_class': filters.MultiValueTimeFilter
+        },
+        models.URLField: {
+            'filter_class': filters.MultiValueCharFilter
+        },
+        MACAddressField: {
+            'filter_class': filters.MultiValueMACAddressFilter
+        },
+    })
+
+    @staticmethod
+    def _get_filter_lookup_dict(existing_filter):
+        # Choose the lookup expression map based on the filter type
+        if isinstance(existing_filter, (
+            filters.MultiValueDateFilter,
+            filters.MultiValueDateTimeFilter,
+            filters.MultiValueNumberFilter,
+            filters.MultiValueTimeFilter
+        )):
+            lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP
+
+        elif isinstance(existing_filter, (
+            filters.TreeNodeMultipleChoiceFilter,
+        )):
+            # TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
+            lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP
+
+        elif isinstance(existing_filter, (
+            django_filters.ModelChoiceFilter,
+            django_filters.ModelMultipleChoiceFilter,
+            TagFilter
+        )) or existing_filter.extra.get('choices'):
+            # These filter types support only negation
+            lookup_map = FILTER_NEGATION_LOOKUP_MAP
+
+        elif isinstance(existing_filter, (
+            django_filters.filters.CharFilter,
+            django_filters.MultipleChoiceFilter,
+            filters.MultiValueCharFilter,
+            filters.MultiValueMACAddressFilter
+        )):
+            lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP
+
+        else:
+            lookup_map = None
+
+        return lookup_map
+
+    @classmethod
+    def get_filters(cls):
+        """
+        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
+        the form `<field_name>__<lookup_expr>`
+        """
+        filters = super().get_filters()
+
+        new_filters = {}
+        for existing_filter_name, existing_filter in filters.items():
+            # Loop over existing filters to extract metadata by which to create new filters
+
+            # If 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
+            if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
+                continue
+
+            # Choose the lookup expression map based on the filter type
+            lookup_map = cls._get_filter_lookup_dict(existing_filter)
+            if lookup_map is None:
+                # Do not augment this filter type with more lookup expressions
+                continue
+
+            # Get properties of the existing filter for later use
+            field_name = existing_filter.field_name
+            field = get_model_field(cls._meta.model, field_name)
+
+            # Create new filters for each lookup expression in the map
+            for lookup_name, lookup_expr in lookup_map.items():
+                new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
+
+                try:
+                    if existing_filter_name in cls.declared_filters:
+                        # The filter field has been explicity defined on the filterset class so we must manually
+                        # create the new filter with the same type because there is no guarantee the defined type
+                        # is the same as the default type for the field
+                        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:
+                        # The filter field is listed in Meta.fields so we can safely rely on default behaviour
+                        # Will raise FieldLookupError if the lookup is invalid
+                        new_filter = cls.filter_for_field(field, field_name, lookup_expr)
+                except django_filters.exceptions.FieldLookupError:
+                    # The filter could not be created because the lookup expression is not supported on the field
+                    continue
+
+                if lookup_name.startswith('n'):
+                    # This is a negation filter which requires a queryset.exclude() clause
+                    # Of course setting the negation of the existing filter's exclude attribute handles both cases
+                    new_filter.exclude = not existing_filter.exclude
+
+                new_filters[new_filter_name] = new_filter
+
+        filters.update(new_filters)
+        return filters
+
+
+class ChangeLoggedModelFilterSet(BaseFilterSet):
+    created = django_filters.DateFilter()
+    created__gte = django_filters.DateFilter(
+        field_name='created',
+        lookup_expr='gte'
+    )
+    created__lte = django_filters.DateFilter(
+        field_name='created',
+        lookup_expr='lte'
+    )
+    last_updated = django_filters.DateTimeFilter()
+    last_updated__gte = django_filters.DateTimeFilter(
+        field_name='last_updated',
+        lookup_expr='gte'
+    )
+    last_updated__lte = django_filters.DateTimeFilter(
+        field_name='last_updated',
+        lookup_expr='lte'
+    )
+
+
+class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Dynamically add a Filter for each CustomField applicable to the parent model
+        custom_fields = CustomField.objects.filter(
+            content_types=ContentType.objects.get_for_model(self._meta.model)
+        ).exclude(
+            filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
+        )
+        for cf in custom_fields:
+            self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
+
+
+class OrganizationalModelFilterSet(PrimaryModelFilterSet):
+    """
+    A base class for adding the search method to models which only expose the `name` and `slug` fields
+    """
+    q = django_filters.CharFilter(
+        method='search',
+        label='Search',
+    )
+
+    def search(self, queryset, name, value):
+        if not value.strip():
+            return queryset
+        return queryset.filter(
+            models.Q(name__icontains=value) |
+            models.Q(slug__icontains=value)
+        )

+ 3 - 3
netbox/secrets/api/views.py

@@ -10,7 +10,7 @@ from rest_framework.viewsets import ViewSet
 
 
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from netbox.api.views import ModelViewSet
 from netbox.api.views import ModelViewSet
-from secrets import filters
+from secrets import filtersets
 from secrets.exceptions import InvalidKey
 from secrets.exceptions import InvalidKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from utilities.utils import count_related
 from utilities.utils import count_related
@@ -39,7 +39,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet):
         secret_count=count_related(Secret, 'role')
         secret_count=count_related(Secret, 'role')
     )
     )
     serializer_class = serializers.SecretRoleSerializer
     serializer_class = serializers.SecretRoleSerializer
-    filterset_class = filters.SecretRoleFilterSet
+    filterset_class = filtersets.SecretRoleFilterSet
 
 
 
 
 #
 #
@@ -49,7 +49,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet):
 class SecretViewSet(ModelViewSet):
 class SecretViewSet(ModelViewSet):
     queryset = Secret.objects.prefetch_related('role', 'tags')
     queryset = Secret.objects.prefetch_related('role', 'tags')
     serializer_class = serializers.SecretSerializer
     serializer_class = serializers.SecretSerializer
-    filterset_class = filters.SecretFilterSet
+    filterset_class = filtersets.SecretFilterSet
 
 
     master_key = None
     master_key = None
 
 

+ 4 - 4
netbox/secrets/filters.py → netbox/secrets/filtersets.py

@@ -2,8 +2,8 @@ import django_filters
 from django.db.models import Q
 from django.db.models import Q
 
 
 from dcim.models import Device
 from dcim.models import Device
-from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
-from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter
+from extras.filters import TagFilter
+from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
 from .models import Secret, SecretRole
 from .models import Secret, SecretRole
 
 
@@ -14,14 +14,14 @@ __all__ = (
 )
 )
 
 
 
 
-class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class SecretRoleFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = SecretRole
         model = SecretRole
         fields = ['id', 'name', 'slug']
         fields = ['id', 'name', 'slug']
 
 
 
 
-class SecretFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class SecretFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/secrets/tests/test_filters.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 from django.test import TestCase
 
 
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
-from secrets.filters import *
+from secrets.filtersets import *
 from secrets.models import Secret, SecretRole
 from secrets.models import Secret, SecretRole
 from virtualization.models import Cluster, ClusterType, VirtualMachine
 from virtualization.models import Cluster, ClusterType, VirtualMachine
 
 

+ 6 - 6
netbox/secrets/views.py

@@ -2,14 +2,14 @@ import base64
 import logging
 import logging
 
 
 from django.contrib import messages
 from django.contrib import messages
-from django.shortcuts import get_object_or_404, redirect, render
+from django.shortcuts import redirect, render
 from django.utils.html import escape
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
 from netbox.views import generic
 from netbox.views import generic
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from utilities.utils import count_related
 from utilities.utils import count_related
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .models import SecretRole, Secret, SessionKey, UserKey
 from .models import SecretRole, Secret, SessionKey, UserKey
 
 
 
 
@@ -70,7 +70,7 @@ class SecretRoleBulkEditView(generic.BulkEditView):
     queryset = SecretRole.objects.annotate(
     queryset = SecretRole.objects.annotate(
         secret_count=count_related(Secret, 'role')
         secret_count=count_related(Secret, 'role')
     )
     )
-    filterset = filters.SecretRoleFilterSet
+    filterset = filtersets.SecretRoleFilterSet
     table = tables.SecretRoleTable
     table = tables.SecretRoleTable
     form = forms.SecretRoleBulkEditForm
     form = forms.SecretRoleBulkEditForm
 
 
@@ -88,7 +88,7 @@ class SecretRoleBulkDeleteView(generic.BulkDeleteView):
 
 
 class SecretListView(generic.ObjectListView):
 class SecretListView(generic.ObjectListView):
     queryset = Secret.objects.all()
     queryset = Secret.objects.all()
-    filterset = filters.SecretFilterSet
+    filterset = filtersets.SecretFilterSet
     filterset_form = forms.SecretFilterForm
     filterset_form = forms.SecretFilterForm
     table = tables.SecretTable
     table = tables.SecretTable
     action_buttons = ('import', 'export')
     action_buttons = ('import', 'export')
@@ -220,12 +220,12 @@ class SecretBulkImportView(generic.BulkImportView):
 
 
 class SecretBulkEditView(generic.BulkEditView):
 class SecretBulkEditView(generic.BulkEditView):
     queryset = Secret.objects.prefetch_related('role')
     queryset = Secret.objects.prefetch_related('role')
-    filterset = filters.SecretFilterSet
+    filterset = filtersets.SecretFilterSet
     table = tables.SecretTable
     table = tables.SecretTable
     form = forms.SecretBulkEditForm
     form = forms.SecretBulkEditForm
 
 
 
 
 class SecretBulkDeleteView(generic.BulkDeleteView):
 class SecretBulkDeleteView(generic.BulkDeleteView):
     queryset = Secret.objects.prefetch_related('role')
     queryset = Secret.objects.prefetch_related('role')
-    filterset = filters.SecretFilterSet
+    filterset = filtersets.SecretFilterSet
     table = tables.SecretTable
     table = tables.SecretTable

+ 3 - 3
netbox/tenancy/api/views.py

@@ -4,7 +4,7 @@ from circuits.models import Circuit
 from dcim.models import Device, Rack, Site
 from dcim.models import Device, Rack, Site
 from extras.api.views import CustomFieldModelViewSet
 from extras.api.views import CustomFieldModelViewSet
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from ipam.models import IPAddress, Prefix, VLAN, VRF
-from tenancy import filters
+from tenancy import filtersets
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.utils import count_related
 from utilities.utils import count_related
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
@@ -32,7 +32,7 @@ class TenantGroupViewSet(CustomFieldModelViewSet):
         cumulative=True
         cumulative=True
     )
     )
     serializer_class = serializers.TenantGroupSerializer
     serializer_class = serializers.TenantGroupSerializer
-    filterset_class = filters.TenantGroupFilterSet
+    filterset_class = filtersets.TenantGroupFilterSet
 
 
 
 
 #
 #
@@ -54,4 +54,4 @@ class TenantViewSet(CustomFieldModelViewSet):
         vrf_count=count_related(VRF, 'tenant')
         vrf_count=count_related(VRF, 'tenant')
     )
     )
     serializer_class = serializers.TenantSerializer
     serializer_class = serializers.TenantSerializer
-    filterset_class = filters.TenantFilterSet
+    filterset_class = filtersets.TenantFilterSet

+ 5 - 4
netbox/tenancy/filters.py → netbox/tenancy/filtersets.py

@@ -1,8 +1,9 @@
 import django_filters
 import django_filters
 from django.db.models import Q
 from django.db.models import Q
 
 
-from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
-from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
+from extras.filters import TagFilter
+from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
+from utilities.filters import TreeNodeMultipleChoiceFilter
 from .models import Tenant, TenantGroup
 from .models import Tenant, TenantGroup
 
 
 
 
@@ -13,7 +14,7 @@ __all__ = (
 )
 )
 
 
 
 
-class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class TenantGroupFilterSet(OrganizationalModelFilterSet):
     parent_id = django_filters.ModelMultipleChoiceFilter(
     parent_id = django_filters.ModelMultipleChoiceFilter(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         label='Tenant group (ID)',
         label='Tenant group (ID)',
@@ -30,7 +31,7 @@ class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdate
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class TenantFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class TenantFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/tenancy/tests/test_filters.py

@@ -1,6 +1,6 @@
 from django.test import TestCase
 from django.test import TestCase
 
 
-from tenancy.filters import *
+from tenancy.filtersets import *
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 
 
 
 

+ 5 - 5
netbox/tenancy/views.py

@@ -4,7 +4,7 @@ from ipam.models import IPAddress, Prefix, VLAN, VRF
 from netbox.views import generic
 from netbox.views import generic
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from virtualization.models import VirtualMachine, Cluster
 from virtualization.models import VirtualMachine, Cluster
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .models import Tenant, TenantGroup
 from .models import Tenant, TenantGroup
 
 
 
 
@@ -63,7 +63,7 @@ class TenantGroupBulkEditView(generic.BulkEditView):
         'tenant_count',
         'tenant_count',
         cumulative=True
         cumulative=True
     )
     )
-    filterset = filters.TenantGroupFilterSet
+    filterset = filtersets.TenantGroupFilterSet
     table = tables.TenantGroupTable
     table = tables.TenantGroupTable
     form = forms.TenantGroupBulkEditForm
     form = forms.TenantGroupBulkEditForm
 
 
@@ -85,7 +85,7 @@ class TenantGroupBulkDeleteView(generic.BulkDeleteView):
 
 
 class TenantListView(generic.ObjectListView):
 class TenantListView(generic.ObjectListView):
     queryset = Tenant.objects.all()
     queryset = Tenant.objects.all()
-    filterset = filters.TenantFilterSet
+    filterset = filtersets.TenantFilterSet
     filterset_form = forms.TenantFilterForm
     filterset_form = forms.TenantFilterForm
     table = tables.TenantTable
     table = tables.TenantTable
 
 
@@ -130,12 +130,12 @@ class TenantBulkImportView(generic.BulkImportView):
 
 
 class TenantBulkEditView(generic.BulkEditView):
 class TenantBulkEditView(generic.BulkEditView):
     queryset = Tenant.objects.prefetch_related('group')
     queryset = Tenant.objects.prefetch_related('group')
-    filterset = filters.TenantFilterSet
+    filterset = filtersets.TenantFilterSet
     table = tables.TenantTable
     table = tables.TenantTable
     form = forms.TenantBulkEditForm
     form = forms.TenantBulkEditForm
 
 
 
 
 class TenantBulkDeleteView(generic.BulkDeleteView):
 class TenantBulkDeleteView(generic.BulkDeleteView):
     queryset = Tenant.objects.prefetch_related('group')
     queryset = Tenant.objects.prefetch_related('group')
-    filterset = filters.TenantFilterSet
+    filterset = filtersets.TenantFilterSet
     table = tables.TenantTable
     table = tables.TenantTable

+ 4 - 4
netbox/users/api/views.py

@@ -6,7 +6,7 @@ from rest_framework.routers import APIRootView
 from rest_framework.viewsets import ViewSet
 from rest_framework.viewsets import ViewSet
 
 
 from netbox.api.views import ModelViewSet
 from netbox.api.views import ModelViewSet
-from users import filters
+from users import filtersets
 from users.models import ObjectPermission, UserConfig
 from users.models import ObjectPermission, UserConfig
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import deepmerge
 from utilities.utils import deepmerge
@@ -28,13 +28,13 @@ class UsersRootView(APIRootView):
 class UserViewSet(ModelViewSet):
 class UserViewSet(ModelViewSet):
     queryset = RestrictedQuerySet(model=User).prefetch_related('groups').order_by('username')
     queryset = RestrictedQuerySet(model=User).prefetch_related('groups').order_by('username')
     serializer_class = serializers.UserSerializer
     serializer_class = serializers.UserSerializer
-    filterset_class = filters.UserFilterSet
+    filterset_class = filtersets.UserFilterSet
 
 
 
 
 class GroupViewSet(ModelViewSet):
 class GroupViewSet(ModelViewSet):
     queryset = RestrictedQuerySet(model=Group).annotate(user_count=Count('user')).order_by('name')
     queryset = RestrictedQuerySet(model=Group).annotate(user_count=Count('user')).order_by('name')
     serializer_class = serializers.GroupSerializer
     serializer_class = serializers.GroupSerializer
-    filterset_class = filters.GroupFilterSet
+    filterset_class = filtersets.GroupFilterSet
 
 
 
 
 #
 #
@@ -44,7 +44,7 @@ class GroupViewSet(ModelViewSet):
 class ObjectPermissionViewSet(ModelViewSet):
 class ObjectPermissionViewSet(ModelViewSet):
     queryset = ObjectPermission.objects.prefetch_related('object_types', 'groups', 'users')
     queryset = ObjectPermission.objects.prefetch_related('object_types', 'groups', 'users')
     serializer_class = serializers.ObjectPermissionSerializer
     serializer_class = serializers.ObjectPermissionSerializer
-    filterset_class = filters.ObjectPermissionFilterSet
+    filterset_class = filtersets.ObjectPermissionFilterSet
 
 
 
 
 #
 #

+ 1 - 1
netbox/users/filters.py → netbox/users/filtersets.py

@@ -2,8 +2,8 @@ import django_filters
 from django.contrib.auth.models import Group, User
 from django.contrib.auth.models import Group, User
 from django.db.models import Q
 from django.db.models import Q
 
 
+from netbox.filtersets import BaseFilterSet
 from users.models import ObjectPermission
 from users.models import ObjectPermission
-from utilities.filters import BaseFilterSet
 
 
 __all__ = (
 __all__ = (
     'GroupFilterSet',
     'GroupFilterSet',

+ 1 - 1
netbox/users/tests/test_filters.py

@@ -2,7 +2,7 @@ from django.contrib.auth.models import Group, User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 from django.test import TestCase
 
 
-from users.filters import GroupFilterSet, ObjectPermissionFilterSet, UserFilterSet
+from users.filtersets import GroupFilterSet, ObjectPermissionFilterSet, UserFilterSet
 from users.models import ObjectPermission
 from users.models import ObjectPermission
 
 
 
 

+ 2 - 204
netbox/utilities/filters.py

@@ -1,17 +1,9 @@
 import django_filters
 import django_filters
-from django_filters.constants import EMPTY_VALUES
-from copy import deepcopy
-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_filters.utils import get_model_field, resolve_field
+from django_filters.constants import EMPTY_VALUES
 
 
-from extras.models import Tag
-from utilities.constants import (
-    FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
-    FILTER_NUMERIC_BASED_LOOKUP_MAP
-)
+from dcim.forms import MACAddressField
 
 
 
 
 def multivalue_field_factory(field_class):
 def multivalue_field_factory(field_class):
@@ -91,21 +83,6 @@ class NullableCharFieldFilter(django_filters.CharFilter):
         return qs.distinct() if self.distinct else qs
         return qs.distinct() if self.distinct else qs
 
 
 
 
-class TagFilter(django_filters.ModelMultipleChoiceFilter):
-    """
-    Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered
-    to objects matching all tags.
-    """
-    def __init__(self, *args, **kwargs):
-
-        kwargs.setdefault('field_name', 'tags__slug')
-        kwargs.setdefault('to_field_name', 'slug')
-        kwargs.setdefault('conjoined', True)
-        kwargs.setdefault('queryset', Tag.objects.all())
-
-        super().__init__(*args, **kwargs)
-
-
 class NumericArrayFilter(django_filters.NumberFilter):
 class NumericArrayFilter(django_filters.NumberFilter):
     """
     """
     Filter based on the presence of an integer within an ArrayField.
     Filter based on the presence of an integer within an ArrayField.
@@ -134,182 +111,3 @@ class ContentTypeFilter(django_filters.CharFilter):
                 f'{self.field_name}__model': model
                 f'{self.field_name}__model': model
             }
             }
         )
         )
-
-
-#
-# FilterSets
-#
-
-class BaseFilterSet(django_filters.FilterSet):
-    """
-    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
-        },
-    })
-
-    @staticmethod
-    def _get_filter_lookup_dict(existing_filter):
-        # Choose the lookup expression map based on the filter type
-        if isinstance(existing_filter, (
-            MultiValueDateFilter,
-            MultiValueDateTimeFilter,
-            MultiValueNumberFilter,
-            MultiValueTimeFilter
-        )):
-            lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP
-
-        elif isinstance(existing_filter, (
-            TreeNodeMultipleChoiceFilter,
-        )):
-            # TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
-            lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP
-
-        elif isinstance(existing_filter, (
-            django_filters.ModelChoiceFilter,
-            django_filters.ModelMultipleChoiceFilter,
-            TagFilter
-        )) or existing_filter.extra.get('choices'):
-            # These filter types support only negation
-            lookup_map = FILTER_NEGATION_LOOKUP_MAP
-
-        elif isinstance(existing_filter, (
-            django_filters.filters.CharFilter,
-            django_filters.MultipleChoiceFilter,
-            MultiValueCharFilter,
-            MultiValueMACAddressFilter
-        )):
-            lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP
-
-        else:
-            lookup_map = None
-
-        return lookup_map
-
-    @classmethod
-    def get_filters(cls):
-        """
-        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
-        the form `<field_name>__<lookup_expr>`
-        """
-        filters = super().get_filters()
-
-        new_filters = {}
-        for existing_filter_name, existing_filter in filters.items():
-            # Loop over existing filters to extract metadata by which to create new filters
-
-            # If 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
-            if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
-                continue
-
-            # Choose the lookup expression map based on the filter type
-            lookup_map = cls._get_filter_lookup_dict(existing_filter)
-            if lookup_map is None:
-                # Do not augment this filter type with more lookup expressions
-                continue
-
-            # Get properties of the existing filter for later use
-            field_name = existing_filter.field_name
-            field = get_model_field(cls._meta.model, field_name)
-
-            # Create new filters for each lookup expression in the map
-            for lookup_name, lookup_expr in lookup_map.items():
-                new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
-
-                try:
-                    if existing_filter_name in cls.declared_filters:
-                        # The filter field has been explicity defined on the filterset class so we must manually
-                        # create the new filter with the same type because there is no guarantee the defined type
-                        # is the same as the default type for the field
-                        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:
-                        # The filter field is listed in Meta.fields so we can safely rely on default behaviour
-                        # Will raise FieldLookupError if the lookup is invalid
-                        new_filter = cls.filter_for_field(field, field_name, lookup_expr)
-                except django_filters.exceptions.FieldLookupError:
-                    # The filter could not be created because the lookup expression is not supported on the field
-                    continue
-
-                if lookup_name.startswith('n'):
-                    # This is a negation filter which requires a queryset.exclude() clause
-                    # Of course setting the negation of the existing filter's exclude attribute handles both cases
-                    new_filter.exclude = not existing_filter.exclude
-
-                new_filters[new_filter_name] = new_filter
-
-        filters.update(new_filters)
-        return filters
-
-
-class NameSlugSearchFilterSet(django_filters.FilterSet):
-    """
-    A base class for adding the search method to models which only expose the `name` and `slug` fields
-    """
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
-
-    def search(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            models.Q(name__icontains=value) |
-            models.Q(slug__icontains=value)
-        )

+ 5 - 3
netbox/utilities/tests/test_filters.py

@@ -7,14 +7,16 @@ from taggit.managers import TaggableManager
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.fields import MACAddressField
 from dcim.fields import MACAddressField
-from dcim.filters import DeviceFilterSet, SiteFilterSet
+from dcim.filtersets import DeviceFilterSet, SiteFilterSet
 from dcim.models import (
 from dcim.models import (
     Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, Rack, Region, Site
     Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, Rack, Region, Site
 )
 )
+from extras.filters import TagFilter
 from extras.models import TaggedItem
 from extras.models import TaggedItem
+from netbox.filtersets import BaseFilterSet
 from utilities.filters import (
 from utilities.filters import (
-    BaseFilterSet, MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter,
-    MultiValueNumberFilter, MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter,
+    MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, MultiValueNumberFilter,
+    MultiValueTimeFilter, TreeNodeMultipleChoiceFilter,
 )
 )
 
 
 
 

+ 6 - 6
netbox/virtualization/api/views.py

@@ -3,7 +3,7 @@ from rest_framework.routers import APIRootView
 from dcim.models import Device
 from dcim.models import Device
 from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet, ModelViewSet
 from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet, ModelViewSet
 from utilities.utils import count_related
 from utilities.utils import count_related
-from virtualization import filters
+from virtualization import filtersets
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from . import serializers
 from . import serializers
 
 
@@ -25,7 +25,7 @@ class ClusterTypeViewSet(CustomFieldModelViewSet):
         cluster_count=count_related(Cluster, 'type')
         cluster_count=count_related(Cluster, 'type')
     )
     )
     serializer_class = serializers.ClusterTypeSerializer
     serializer_class = serializers.ClusterTypeSerializer
-    filterset_class = filters.ClusterTypeFilterSet
+    filterset_class = filtersets.ClusterTypeFilterSet
 
 
 
 
 class ClusterGroupViewSet(CustomFieldModelViewSet):
 class ClusterGroupViewSet(CustomFieldModelViewSet):
@@ -33,7 +33,7 @@ class ClusterGroupViewSet(CustomFieldModelViewSet):
         cluster_count=count_related(Cluster, 'group')
         cluster_count=count_related(Cluster, 'group')
     )
     )
     serializer_class = serializers.ClusterGroupSerializer
     serializer_class = serializers.ClusterGroupSerializer
-    filterset_class = filters.ClusterGroupFilterSet
+    filterset_class = filtersets.ClusterGroupFilterSet
 
 
 
 
 class ClusterViewSet(CustomFieldModelViewSet):
 class ClusterViewSet(CustomFieldModelViewSet):
@@ -44,7 +44,7 @@ class ClusterViewSet(CustomFieldModelViewSet):
         virtualmachine_count=count_related(VirtualMachine, 'cluster')
         virtualmachine_count=count_related(VirtualMachine, 'cluster')
     )
     )
     serializer_class = serializers.ClusterSerializer
     serializer_class = serializers.ClusterSerializer
-    filterset_class = filters.ClusterFilterSet
+    filterset_class = filtersets.ClusterFilterSet
 
 
 
 
 #
 #
@@ -55,7 +55,7 @@ class VirtualMachineViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet)
     queryset = VirtualMachine.objects.prefetch_related(
     queryset = VirtualMachine.objects.prefetch_related(
         'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags'
         'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags'
     )
     )
-    filterset_class = filters.VirtualMachineFilterSet
+    filterset_class = filtersets.VirtualMachineFilterSet
 
 
     def get_serializer_class(self):
     def get_serializer_class(self):
         """
         """
@@ -83,5 +83,5 @@ class VMInterfaceViewSet(ModelViewSet):
         'virtual_machine', 'parent', 'tags', 'tagged_vlans', 'ip_addresses'
         'virtual_machine', 'parent', 'tags', 'tagged_vlans', 'ip_addresses'
     )
     )
     serializer_class = serializers.VMInterfaceSerializer
     serializer_class = serializers.VMInterfaceSerializer
-    filterset_class = filters.VMInterfaceFilterSet
+    filterset_class = filtersets.VMInterfaceFilterSet
     brief_prefetch_fields = ['virtual_machine']
     brief_prefetch_fields = ['virtual_machine']

+ 10 - 17
netbox/virtualization/filters.py → netbox/virtualization/filtersets.py

@@ -2,12 +2,11 @@ import django_filters
 from django.db.models import Q
 from django.db.models import Q
 
 
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
-from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet
-from tenancy.filters import TenancyFilterSet
-from utilities.filters import (
-    BaseFilterSet, MultiValueMACAddressFilter, NameSlugSearchFilterSet, TagFilter,
-    TreeNodeMultipleChoiceFilter,
-)
+from extras.filters import TagFilter
+from extras.filtersets import LocalConfigContextFilterSet
+from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
+from tenancy.filtersets import TenancyFilterSet
+from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
 from .choices import *
 from .choices import *
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
@@ -20,21 +19,21 @@ __all__ = (
 )
 )
 
 
 
 
-class ClusterTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class ClusterTypeFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = ClusterType
         model = ClusterType
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class ClusterGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
+class ClusterGroupFilterSet(OrganizationalModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = ClusterGroup
         model = ClusterGroup
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -110,13 +109,7 @@ class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSe
         )
         )
 
 
 
 
-class VirtualMachineFilterSet(
-    BaseFilterSet,
-    LocalConfigContextFilterSet,
-    TenancyFilterSet,
-    CustomFieldModelFilterSet,
-    CreatedUpdatedFilterSet
-):
+class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -237,7 +230,7 @@ class VirtualMachineFilterSet(
         return queryset.exclude(params)
         return queryset.exclude(params)
 
 
 
 
-class VMInterfaceFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
+class VMInterfaceFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',

+ 1 - 1
netbox/virtualization/tests/test_filters.py

@@ -4,7 +4,7 @@ from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
 from ipam.models import IPAddress
 from ipam.models import IPAddress
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from virtualization.choices import *
 from virtualization.choices import *
-from virtualization.filters import *
+from virtualization.filtersets import *
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
 
 

+ 11 - 11
netbox/virtualization/views.py

@@ -13,7 +13,7 @@ from netbox.views import generic
 from secrets.models import Secret
 from secrets.models import Secret
 from utilities.tables import paginate_table
 from utilities.tables import paginate_table
 from utilities.utils import count_related
 from utilities.utils import count_related
-from . import filters, forms, tables
+from . import filtersets, forms, tables
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
 
 
@@ -64,7 +64,7 @@ class ClusterTypeBulkEditView(generic.BulkEditView):
     queryset = ClusterType.objects.annotate(
     queryset = ClusterType.objects.annotate(
         cluster_count=count_related(Cluster, 'type')
         cluster_count=count_related(Cluster, 'type')
     )
     )
-    filterset = filters.ClusterTypeFilterSet
+    filterset = filtersets.ClusterTypeFilterSet
     table = tables.ClusterTypeTable
     table = tables.ClusterTypeTable
     form = forms.ClusterTypeBulkEditForm
     form = forms.ClusterTypeBulkEditForm
 
 
@@ -125,7 +125,7 @@ class ClusterGroupBulkEditView(generic.BulkEditView):
     queryset = ClusterGroup.objects.annotate(
     queryset = ClusterGroup.objects.annotate(
         cluster_count=count_related(Cluster, 'group')
         cluster_count=count_related(Cluster, 'group')
     )
     )
-    filterset = filters.ClusterGroupFilterSet
+    filterset = filtersets.ClusterGroupFilterSet
     table = tables.ClusterGroupTable
     table = tables.ClusterGroupTable
     form = forms.ClusterGroupBulkEditForm
     form = forms.ClusterGroupBulkEditForm
 
 
@@ -148,7 +148,7 @@ class ClusterListView(generic.ObjectListView):
         vm_count=count_related(VirtualMachine, 'cluster')
         vm_count=count_related(VirtualMachine, 'cluster')
     )
     )
     table = tables.ClusterTable
     table = tables.ClusterTable
-    filterset = filters.ClusterFilterSet
+    filterset = filtersets.ClusterFilterSet
     filterset_form = forms.ClusterFilterForm
     filterset_form = forms.ClusterFilterForm
 
 
 
 
@@ -205,14 +205,14 @@ class ClusterBulkImportView(generic.BulkImportView):
 
 
 class ClusterBulkEditView(generic.BulkEditView):
 class ClusterBulkEditView(generic.BulkEditView):
     queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
     queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
-    filterset = filters.ClusterFilterSet
+    filterset = filtersets.ClusterFilterSet
     table = tables.ClusterTable
     table = tables.ClusterTable
     form = forms.ClusterBulkEditForm
     form = forms.ClusterBulkEditForm
 
 
 
 
 class ClusterBulkDeleteView(generic.BulkDeleteView):
 class ClusterBulkDeleteView(generic.BulkDeleteView):
     queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
     queryset = Cluster.objects.prefetch_related('type', 'group', 'site')
-    filterset = filters.ClusterFilterSet
+    filterset = filtersets.ClusterFilterSet
     table = tables.ClusterTable
     table = tables.ClusterTable
 
 
 
 
@@ -304,7 +304,7 @@ class ClusterRemoveDevicesView(generic.ObjectEditView):
 
 
 class VirtualMachineListView(generic.ObjectListView):
 class VirtualMachineListView(generic.ObjectListView):
     queryset = VirtualMachine.objects.all()
     queryset = VirtualMachine.objects.all()
-    filterset = filters.VirtualMachineFilterSet
+    filterset = filtersets.VirtualMachineFilterSet
     filterset_form = forms.VirtualMachineFilterForm
     filterset_form = forms.VirtualMachineFilterForm
     table = tables.VirtualMachineDetailTable
     table = tables.VirtualMachineDetailTable
     template_name = 'virtualization/virtualmachine_list.html'
     template_name = 'virtualization/virtualmachine_list.html'
@@ -388,14 +388,14 @@ class VirtualMachineBulkImportView(generic.BulkImportView):
 
 
 class VirtualMachineBulkEditView(generic.BulkEditView):
 class VirtualMachineBulkEditView(generic.BulkEditView):
     queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
     queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
-    filterset = filters.VirtualMachineFilterSet
+    filterset = filtersets.VirtualMachineFilterSet
     table = tables.VirtualMachineTable
     table = tables.VirtualMachineTable
     form = forms.VirtualMachineBulkEditForm
     form = forms.VirtualMachineBulkEditForm
 
 
 
 
 class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
 class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
     queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
     queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role')
-    filterset = filters.VirtualMachineFilterSet
+    filterset = filtersets.VirtualMachineFilterSet
     table = tables.VirtualMachineTable
     table = tables.VirtualMachineTable
 
 
 
 
@@ -405,7 +405,7 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
 
 
 class VMInterfaceListView(generic.ObjectListView):
 class VMInterfaceListView(generic.ObjectListView):
     queryset = VMInterface.objects.all()
     queryset = VMInterface.objects.all()
-    filterset = filters.VMInterfaceFilterSet
+    filterset = filtersets.VMInterfaceFilterSet
     filterset_form = forms.VMInterfaceFilterForm
     filterset_form = forms.VMInterfaceFilterForm
     table = tables.VMInterfaceTable
     table = tables.VMInterfaceTable
     action_buttons = ('export',)
     action_buttons = ('export',)
@@ -500,7 +500,7 @@ class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView):
     form = forms.VMInterfaceBulkCreateForm
     form = forms.VMInterfaceBulkCreateForm
     queryset = VMInterface.objects.all()
     queryset = VMInterface.objects.all()
     model_form = forms.VMInterfaceForm
     model_form = forms.VMInterfaceForm
-    filterset = filters.VirtualMachineFilterSet
+    filterset = filtersets.VirtualMachineFilterSet
     table = tables.VirtualMachineTable
     table = tables.VirtualMachineTable
 
 
     def get_required_permission(self):
     def get_required_permission(self):