From 6b5dc7ed2c1e7ea395eaa7f55dafdfb8d61b98d2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 31 Oct 2022 12:28:10 -0400 Subject: [PATCH] Return only enabled/shared filters --- netbox/circuits/forms/filtersets.py | 6 +-- netbox/dcim/forms/filtersets.py | 48 +++++++++---------- netbox/extras/api/views.py | 16 +++++++ netbox/extras/forms/__init__.py | 2 +- netbox/extras/forms/filtersets.py | 37 +++++++------- .../forms/{customfields.py => mixins.py} | 13 +++++ netbox/extras/views.py | 35 ++++++++++---- netbox/ipam/forms/filtersets.py | 33 ++++++------- netbox/netbox/filtersets.py | 2 +- netbox/netbox/forms/base.py | 10 ++-- netbox/tenancy/forms/filtersets.py | 2 +- netbox/utilities/templatetags/helpers.py | 2 +- netbox/virtualization/forms/filtersets.py | 8 ++-- netbox/wireless/forms/filtersets.py | 4 +- 14 files changed, 131 insertions(+), 87 deletions(-) rename netbox/extras/forms/{customfields.py => mixins.py} (85%) diff --git a/netbox/circuits/forms/filtersets.py b/netbox/circuits/forms/filtersets.py index 29410ffdf..9ad825299 100644 --- a/netbox/circuits/forms/filtersets.py +++ b/netbox/circuits/forms/filtersets.py @@ -20,7 +20,7 @@ __all__ = ( class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Provider fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('ASN', ('asn',)), ('Contacts', ('contact', 'contact_role', 'contact_group')), @@ -59,7 +59,7 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class ProviderNetworkFilterForm(NetBoxModelFilterSetForm): model = ProviderNetwork fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('provider_id', 'service_id')), ) provider_id = DynamicModelMultipleChoiceField( @@ -82,7 +82,7 @@ class CircuitTypeFilterForm(NetBoxModelFilterSetForm): class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Circuit fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Provider', ('provider_id', 'provider_network_id')), ('Attributes', ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')), ('Location', ('region_id', 'site_group_id', 'site_id')), diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 27efe97ff..905a898df 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -116,7 +116,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm): class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Region fieldsets = ( - (None, ('q', 'tag', 'parent_id')), + (None, ('q', 'filter', 'tag', 'parent_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) parent_id = DynamicModelMultipleChoiceField( @@ -130,7 +130,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = SiteGroup fieldsets = ( - (None, ('q', 'tag', 'parent_id')), + (None, ('q', 'filter', 'tag', 'parent_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) parent_id = DynamicModelMultipleChoiceField( @@ -174,7 +174,7 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Location fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), @@ -222,7 +222,7 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm): class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Rack fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Function', ('status', 'role_id')), ('Hardware', ('type', 'width', 'serial', 'asset_tag')), @@ -306,7 +306,7 @@ class RackElevationFilterForm(RackFilterForm): class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = RackReservation fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('User', ('user_id',)), ('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -362,7 +362,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Manufacturer fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) tag = TagFilterField(model) @@ -371,7 +371,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class DeviceTypeFilterForm(NetBoxModelFilterSetForm): model = DeviceType fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')), ('Images', ('has_front_image', 'has_rear_image')), ('Components', ( @@ -486,7 +486,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): class ModuleTypeFilterForm(NetBoxModelFilterSetForm): model = ModuleType fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Hardware', ('manufacturer_id', 'part_number')), ('Components', ( 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', @@ -578,7 +578,7 @@ class DeviceFilterForm( ): model = Device fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), ('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')), ('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')), @@ -731,7 +731,7 @@ class DeviceFilterForm( class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm): model = Module fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')), ) manufacturer_id = DynamicModelMultipleChoiceField( @@ -761,7 +761,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VirtualChassis fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -790,7 +790,7 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Cable fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('site_id', 'location_id', 'rack_id', 'device_id')), ('Attributes', ('type', 'status', 'color', 'length', 'length_unit')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -862,7 +862,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = PowerPanel fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), ) @@ -900,7 +900,7 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class PowerFeedFilterForm(NetBoxModelFilterSetForm): model = PowerFeed fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')), ('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')), ) @@ -1002,7 +1002,7 @@ class PathEndpointFilterForm(CabledFilterForm): class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = ConsolePort fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type', 'speed')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Connection', ('cabled', 'connected', 'occupied')), @@ -1021,7 +1021,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = ConsoleServerPort fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type', 'speed')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Connection', ('cabled', 'connected', 'occupied')), @@ -1040,7 +1040,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = PowerPort fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Connection', ('cabled', 'connected', 'occupied')), @@ -1055,7 +1055,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = PowerOutlet fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Connection', ('cabled', 'connected', 'occupied')), @@ -1070,7 +1070,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = Interface fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')), ('Addressing', ('vrf_id', 'mac_address', 'wwn')), ('PoE', ('poe_mode', 'poe_type')), @@ -1159,7 +1159,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type', 'color')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Cable', ('cabled', 'occupied')), @@ -1178,7 +1178,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): model = RearPort fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'type', 'color')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Cable', ('cabled', 'occupied')), @@ -1196,7 +1196,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): class ModuleBayFilterForm(DeviceComponentFilterForm): model = ModuleBay fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'position')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ) @@ -1209,7 +1209,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm): class DeviceBayFilterForm(DeviceComponentFilterForm): model = DeviceBay fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ) @@ -1219,7 +1219,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm): class InventoryItemFilterForm(DeviceComponentFilterForm): model = InventoryItem fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index d5463a65c..90f31ac97 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.db.models import Q from django.http import Http404 from django_rq.queues import get_connection from rest_framework import status @@ -108,6 +109,21 @@ class SavedFilterViewSet(NetBoxModelViewSet): serializer_class = serializers.SavedFilterSerializer filterset_class = filtersets.SavedFilterFilterSet + def get_queryset(self): + """ + Return only shared SavedFilters, or those owned by the current user, unless + this is a superuser. + """ + queryset = super().get_queryset() + user = self.request.user + if user.is_superuser: + return queryset + if user.is_anonymous: + return queryset.filter(shared=True) + return queryset.filter( + Q(shared=True) | Q(user=user) + ) + # # Tags diff --git a/netbox/extras/forms/__init__.py b/netbox/extras/forms/__init__.py index d2f2fb015..af0f7cf43 100644 --- a/netbox/extras/forms/__init__.py +++ b/netbox/extras/forms/__init__.py @@ -2,6 +2,6 @@ from .model_forms import * from .filtersets import * from .bulk_edit import * from .bulk_import import * -from .customfields import * +from .mixins import * from .config import * from .scripts import * diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index 2403bb174..479367ff0 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -15,6 +15,7 @@ from utilities.forms import ( StaticSelect, TagFilterField, ) from virtualization.models import Cluster, ClusterGroup, ClusterType +from .mixins import SavedFiltersMixin __all__ = ( 'ConfigContextFilterForm', @@ -31,9 +32,9 @@ __all__ = ( ) -class CustomFieldFilterForm(FilterForm): +class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('type', 'content_type_id', 'group_name', 'weight', 'required', 'ui_visibility')), ) content_type_id = ContentTypeMultipleChoiceField( @@ -67,9 +68,9 @@ class CustomFieldFilterForm(FilterForm): ) -class JobResultFilterForm(FilterForm): +class JobResultFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('obj_type', 'status')), ('Creation', ('created__before', 'created__after', 'completed__before', 'completed__after', 'scheduled_time__before', 'scheduled_time__after', 'user')), @@ -119,9 +120,9 @@ class JobResultFilterForm(FilterForm): ) -class CustomLinkFilterForm(FilterForm): +class CustomLinkFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('content_types', 'enabled', 'new_window', 'weight')), ) content_types = ContentTypeMultipleChoiceField( @@ -146,9 +147,9 @@ class CustomLinkFilterForm(FilterForm): ) -class ExportTemplateFilterForm(FilterForm): +class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('content_types', 'mime_type', 'file_extension', 'as_attachment')), ) content_types = ContentTypeMultipleChoiceField( @@ -171,9 +172,9 @@ class ExportTemplateFilterForm(FilterForm): ) -class SavedFilterFilterForm(FilterForm): +class SavedFilterFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('content_types', 'enabled', 'shared', 'weight')), ) content_types = ContentTypeMultipleChoiceField( @@ -198,9 +199,9 @@ class SavedFilterFilterForm(FilterForm): ) -class WebhookFilterForm(FilterForm): +class WebhookFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Attributes', ('content_type_id', 'http_method', 'enabled')), ('Events', ('type_create', 'type_update', 'type_delete')), ) @@ -241,7 +242,7 @@ class WebhookFilterForm(FilterForm): ) -class TagFilterForm(FilterForm): +class TagFilterForm(SavedFiltersMixin, FilterForm): model = Tag content_type_id = ContentTypeMultipleChoiceField( queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()), @@ -250,9 +251,9 @@ class TagFilterForm(FilterForm): ) -class ConfigContextFilterForm(FilterForm): +class ConfigContextFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'tag_id')), + (None, ('q', 'filter', 'tag_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Device', ('device_type_id', 'platform_id', 'role_id')), ('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')), @@ -339,7 +340,7 @@ class LocalConfigContextFilterForm(forms.Form): class JournalEntryFilterForm(NetBoxModelFilterSetForm): model = JournalEntry fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Creation', ('created_before', 'created_after', 'created_by_id')), ('Attributes', ('assigned_object_type_id', 'kind')) ) @@ -377,10 +378,10 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm): tag = TagFilterField(model) -class ObjectChangeFilterForm(FilterForm): +class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm): model = ObjectChange fieldsets = ( - (None, ('q',)), + (None, ('q', 'filter')), ('Time', ('time_before', 'time_after')), ('Attributes', ('action', 'user_id', 'changed_object_type_id')), ) diff --git a/netbox/extras/forms/customfields.py b/netbox/extras/forms/mixins.py similarity index 85% rename from netbox/extras/forms/customfields.py rename to netbox/extras/forms/mixins.py index 40d068450..ce91f2507 100644 --- a/netbox/extras/forms/customfields.py +++ b/netbox/extras/forms/mixins.py @@ -1,10 +1,13 @@ from django.contrib.contenttypes.models import ContentType +from django import forms from extras.models import * from extras.choices import CustomFieldVisibilityChoices +from utilities.forms.fields import DynamicModelMultipleChoiceField __all__ = ( 'CustomFieldsMixin', + 'SavedFiltersMixin', ) @@ -57,3 +60,13 @@ class CustomFieldsMixin: if customfield.group_name not in self.custom_field_groups: self.custom_field_groups[customfield.group_name] = [] self.custom_field_groups[customfield.group_name].append(field_name) + + +class SavedFiltersMixin(forms.Form): + filter = DynamicModelMultipleChoiceField( + queryset=SavedFilter.objects.all(), + required=False, + query_params={ + 'enabled': True, + } + ) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index c37a3fce2..4a1350bde 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,6 +1,6 @@ from django.contrib import messages from django.contrib.contenttypes.models import ContentType -from django.db.models import Count +from django.db.models import Count, Q from django.http import Http404, HttpResponseForbidden from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse @@ -162,20 +162,37 @@ class ExportTemplateBulkDeleteView(generic.BulkDeleteView): # Saved filters # -class SavedFilterListView(generic.ObjectListView): - queryset = SavedFilter.objects.all() +class SavedFilterMixin: + + def get_queryset(self, request): + """ + Return only shared SavedFilters, or those owned by the current user, unless + this is a superuser. + """ + queryset = SavedFilter.objects.all() + user = request.user + if user.is_superuser: + return queryset + if user.is_anonymous: + return queryset.filter(shared=True) + return queryset.filter( + Q(shared=True) | Q(user=user) + ) + + +class SavedFilterListView(SavedFilterMixin, generic.ObjectListView): filterset = filtersets.SavedFilterFilterSet filterset_form = forms.SavedFilterFilterForm table = tables.SavedFilterTable @register_model_view(SavedFilter) -class SavedFilterView(generic.ObjectView): +class SavedFilterView(SavedFilterMixin, generic.ObjectView): queryset = SavedFilter.objects.all() @register_model_view(SavedFilter, 'edit') -class SavedFilterEditView(generic.ObjectEditView): +class SavedFilterEditView(SavedFilterMixin, generic.ObjectEditView): queryset = SavedFilter.objects.all() form = forms.SavedFilterForm @@ -186,24 +203,24 @@ class SavedFilterEditView(generic.ObjectEditView): @register_model_view(SavedFilter, 'delete') -class SavedFilterDeleteView(generic.ObjectDeleteView): +class SavedFilterDeleteView(SavedFilterMixin, generic.ObjectDeleteView): queryset = SavedFilter.objects.all() -class SavedFilterBulkImportView(generic.BulkImportView): +class SavedFilterBulkImportView(SavedFilterMixin, generic.BulkImportView): queryset = SavedFilter.objects.all() model_form = forms.SavedFilterCSVForm table = tables.SavedFilterTable -class SavedFilterBulkEditView(generic.BulkEditView): +class SavedFilterBulkEditView(SavedFilterMixin, generic.BulkEditView): queryset = SavedFilter.objects.all() filterset = filtersets.SavedFilterFilterSet table = tables.SavedFilterTable form = forms.SavedFilterBulkEditForm -class SavedFilterBulkDeleteView(generic.BulkDeleteView): +class SavedFilterBulkDeleteView(SavedFilterMixin, generic.BulkDeleteView): queryset = SavedFilter.objects.all() filterset = filtersets.SavedFilterFilterSet table = tables.SavedFilterTable diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index a2ff7085b..7d277b33b 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -1,6 +1,5 @@ from django import forms from django.contrib.contenttypes.models import ContentType -from django.db.models import Q from django.utils.translation import gettext as _ from dcim.models import Location, Rack, Region, Site, SiteGroup, Device @@ -11,7 +10,7 @@ from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import TenancyFilterForm from utilities.forms import ( add_blank_choice, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, - MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, APISelectMultiple, + MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import VirtualMachine @@ -46,7 +45,7 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VRF fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Route Targets', ('import_target_id', 'export_target_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -66,7 +65,7 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = RouteTarget fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('VRF', ('importing_vrf_id', 'exporting_vrf_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -98,7 +97,7 @@ class RIRFilterForm(NetBoxModelFilterSetForm): class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Aggregate fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('family', 'rir_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -119,7 +118,7 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = ASN fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Assignment', ('rir_id', 'site_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -144,7 +143,7 @@ class RoleFilterForm(NetBoxModelFilterSetForm): class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Prefix fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Addressing', ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')), ('VRF', ('vrf_id', 'present_in_vrf_id')), ('Location', ('region_id', 'site_group_id', 'site_id')), @@ -233,7 +232,7 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPRange fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -265,7 +264,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPAddress fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')), ('VRF', ('vrf_id', 'present_in_vrf_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -334,7 +333,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class FHRPGroupFilterForm(NetBoxModelFilterSetForm): model = FHRPGroup fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('name', 'protocol', 'group_id')), ('Authentication', ('auth_type', 'auth_key')), ) @@ -364,7 +363,7 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm): class VLANGroupFilterForm(NetBoxModelFilterSetForm): fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region', 'sitegroup', 'site', 'location', 'rack')), ('VLAN ID', ('min_vid', 'max_vid')), ) @@ -412,7 +411,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm): class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VLAN fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Attributes', ('group_id', 'status', 'role_id', 'vid')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -465,7 +464,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ServiceTemplateFilterForm(NetBoxModelFilterSetForm): model = ServiceTemplate fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('protocol', 'port')), ) protocol = forms.ChoiceField( @@ -486,7 +485,7 @@ class ServiceFilterForm(ServiceTemplateFilterForm): class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = L2VPN fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('type', 'import_target_id', 'export_target_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -511,8 +510,10 @@ class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm): model = L2VPNTermination fieldsets = ( - (None, ('l2vpn_id', )), - ('Assigned Object', ('assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id')), + (None, ('filter', 'l2vpn_id',)), + ('Assigned Object', ( + 'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id', + )), ) l2vpn_id = DynamicModelChoiceField( queryset=L2VPN.objects.all(), diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index 2a9d3719e..02ccdca50 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -88,7 +88,7 @@ class BaseFilterSet(django_filters.FilterSet): self.base_filters = self.__class__.get_filters() # Apply any referenced SavedFilters - if 'filter' in data: + if data and 'filter' in data: data = data.copy() # Get a mutable copy saved_filters = SavedFilter.objects.filter(pk__in=data.pop('filter')) for sf in saved_filters: diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 70007e0d8..564e254a3 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -3,8 +3,8 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Q from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices -from extras.forms.customfields import CustomFieldsMixin -from extras.models import CustomField, SavedFilter, Tag +from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin +from extras.models import CustomField, Tag from utilities.forms import BootstrapMixin, CSVModelForm from utilities.forms.fields import DynamicModelMultipleChoiceField @@ -114,7 +114,7 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form): self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields) -class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form): +class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMixin, forms.Form): """ Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the corresponding FilterSet *must* provide a `q` filter. @@ -128,10 +128,6 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form): required=False, label='Search' ) - filter = DynamicModelMultipleChoiceField( - queryset=SavedFilter.objects.all(), - required=False - ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/netbox/tenancy/forms/filtersets.py b/netbox/tenancy/forms/filtersets.py index 02589d733..f840a2177 100644 --- a/netbox/tenancy/forms/filtersets.py +++ b/netbox/tenancy/forms/filtersets.py @@ -31,7 +31,7 @@ class TenantGroupFilterForm(NetBoxModelFilterSetForm): class TenantFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Tenant fieldsets = ( - (None, ('q', 'tag', 'group_id')), + (None, ('q', 'filter', 'tag', 'group_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) group_id = DynamicModelMultipleChoiceField( diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 231ac19f7..ed2e39041 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -309,7 +309,7 @@ def applied_filters(context, model, form, query_params): }) save_link = None - if len(applied_filters) > 1 and user.has_perm('extras.add_saved_filter') and 'filter' not in context['request'].GET: + if user.has_perm('extras.add_savedfilter') and 'filter' not in context['request'].GET: content_type = ContentType.objects.get_for_model(model).pk parameters = context['request'].GET.urlencode() url = reverse('extras:savedfilter_add') diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py index 4b8ff6d21..62fa4002e 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -30,7 +30,7 @@ class ClusterGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = ClusterGroup tag = TagFilterField(model) fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Contacts', ('contact', 'contact_role', 'contact_group')), ) @@ -38,7 +38,7 @@ class ClusterGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Cluster fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('group_id', 'type_id', 'status')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -90,7 +90,7 @@ class VirtualMachineFilterForm( ): model = VirtualMachine fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), @@ -175,7 +175,7 @@ class VirtualMachineFilterForm( class VMInterfaceFilterForm(NetBoxModelFilterSetForm): model = VMInterface fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Virtual Machine', ('cluster_id', 'virtual_machine_id')), ('Attributes', ('enabled', 'mac_address', 'vrf_id')), ) diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py index 9e8808e17..d7a6aac6e 100644 --- a/netbox/wireless/forms/filtersets.py +++ b/netbox/wireless/forms/filtersets.py @@ -28,7 +28,7 @@ class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm): class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = WirelessLAN fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('ssid', 'group_id',)), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), @@ -62,7 +62,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = WirelessLink fieldsets = ( - (None, ('q', 'tag')), + (None, ('q', 'filter', 'tag')), ('Attributes', ('ssid', 'status',)), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),