diff --git a/netbox/circuits/forms/filtersets.py b/netbox/circuits/forms/filtersets.py index 9ad82529991..d7cfc494d9f 100644 --- a/netbox/circuits/forms/filtersets.py +++ b/netbox/circuits/forms/filtersets.py @@ -20,7 +20,7 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Provider fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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 cc4dd635c51..1b850b403a6 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -117,7 +117,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm): class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Region fieldsets = ( - (None, ('q', 'filter', 'tag', 'parent_id')), + (None, ('q', 'filter_id', 'tag', 'parent_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) parent_id = DynamicModelMultipleChoiceField( @@ -131,7 +131,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = SiteGroup fieldsets = ( - (None, ('q', 'filter', 'tag', 'parent_id')), + (None, ('q', 'filter_id', 'tag', 'parent_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) parent_id = DynamicModelMultipleChoiceField( @@ -145,7 +145,7 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Site fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('status', 'region_id', 'group_id', 'asn_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), @@ -175,7 +175,7 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Location fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), @@ -223,7 +223,7 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm): class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm): model = Rack fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Function', ('status', 'role_id')), ('Hardware', ('type', 'width', 'serial', 'asset_tag')), @@ -307,7 +307,7 @@ class RackElevationFilterForm(RackFilterForm): class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = RackReservation fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('User', ('user_id',)), ('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -363,7 +363,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = Manufacturer fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Contacts', ('contact', 'contact_role', 'contact_group')) ) tag = TagFilterField(model) @@ -372,7 +372,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class DeviceTypeFilterForm(NetBoxModelFilterSetForm): model = DeviceType fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')), ('Images', ('has_front_image', 'has_rear_image')), ('Components', ( @@ -487,7 +487,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): class ModuleTypeFilterForm(NetBoxModelFilterSetForm): model = ModuleType fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Hardware', ('manufacturer_id', 'part_number')), ('Components', ( 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', @@ -579,7 +579,7 @@ class DeviceFilterForm( ): model = Device fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -735,7 +735,7 @@ class VirtualDeviceContextFilterForm( ): model = VirtualDeviceContext fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Hardware', ('device', 'status', )), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Miscellaneous', ('has_primary_ip',)) @@ -763,7 +763,7 @@ class VirtualDeviceContextFilterForm( class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm): model = Module fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')), ) manufacturer_id = DynamicModelMultipleChoiceField( @@ -793,7 +793,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VirtualChassis fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -822,7 +822,7 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Cable fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('site_id', 'location_id', 'rack_id', 'device_id')), ('Attributes', ('type', 'status', 'color', 'length', 'length_unit')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -894,7 +894,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): model = PowerPanel fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), ) @@ -932,7 +932,7 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): class PowerFeedFilterForm(NetBoxModelFilterSetForm): model = PowerFeed fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')), ('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')), ) @@ -1034,7 +1034,7 @@ class PathEndpointFilterForm(CabledFilterForm): class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = ConsolePort fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1053,7 +1053,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = ConsoleServerPort fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1072,7 +1072,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = PowerPort fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1087,7 +1087,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = PowerOutlet fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1102,7 +1102,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): model = Interface fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')), ('Addressing', ('vrf_id', 'mac_address', 'wwn')), ('PoE', ('poe_mode', 'poe_type')), @@ -1200,7 +1200,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1219,7 +1219,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): model = RearPort fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -1237,7 +1237,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): class ModuleBayFilterForm(DeviceComponentFilterForm): model = ModuleBay fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'position')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ) @@ -1250,7 +1250,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm): class DeviceBayFilterForm(DeviceComponentFilterForm): model = DeviceBay fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ) @@ -1260,7 +1260,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm): class InventoryItemFilterForm(DeviceComponentFilterForm): model = InventoryItem fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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/nested_serializers.py b/netbox/extras/api/nested_serializers.py index dce062b84bf..5644b0b4eb9 100644 --- a/netbox/extras/api/nested_serializers.py +++ b/netbox/extras/api/nested_serializers.py @@ -64,7 +64,7 @@ class NestedSavedFilterSerializer(WritableNestedSerializer): class Meta: model = models.SavedFilter - fields = ['id', 'url', 'display', 'name'] + fields = ['id', 'url', 'display', 'name', 'slug'] class NestedImageAttachmentSerializer(WritableNestedSerializer): diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 1afb8fa8f2c..921720deaf1 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -164,8 +164,8 @@ class SavedFilterSerializer(ValidatedModelSerializer): class Meta: model = SavedFilter fields = [ - 'id', 'url', 'display', 'content_types', 'name', 'description', 'user', 'weight', - 'enabled', 'shared', 'parameters', 'created', 'last_updated', + 'id', 'url', 'display', 'content_types', 'name', 'slug', 'description', 'user', 'weight', 'enabled', + 'shared', 'parameters', 'created', 'last_updated', ] diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index 824ba90cbe8..8ed68e85039 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -165,7 +165,7 @@ class SavedFilterFilterSet(BaseFilterSet): class Meta: model = SavedFilter - fields = ['id', 'content_types', 'name', 'description', 'enabled', 'shared', 'weight'] + fields = ['id', 'content_types', 'name', 'slug', 'description', 'enabled', 'shared', 'weight'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 9def8fda6bb..7ef4739ad86 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -92,7 +92,7 @@ class SavedFilterCSVForm(CSVModelForm): class Meta: model = SavedFilter fields = ( - 'name', 'content_types', 'description', 'weight', 'enabled', 'shared', 'parameters', + 'name', 'slug', 'content_types', 'description', 'weight', 'enabled', 'shared', 'parameters', ) diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index 0421cab223e..d63378f6260 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -34,7 +34,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('type', 'content_type_id', 'group_name', 'weight', 'required', 'ui_visibility')), ) content_type_id = ContentTypeMultipleChoiceField( @@ -70,7 +70,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): class JobResultFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('obj_type', 'status')), ('Creation', ('created__before', 'created__after', 'completed__before', 'completed__after', 'scheduled_time__before', 'scheduled_time__after', 'user')), @@ -122,7 +122,7 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm): class CustomLinkFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('content_types', 'enabled', 'new_window', 'weight')), ) content_types = ContentTypeMultipleChoiceField( @@ -149,7 +149,7 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm): class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('content_types', 'mime_type', 'file_extension', 'as_attachment')), ) content_types = ContentTypeMultipleChoiceField( @@ -174,7 +174,7 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm): class SavedFilterFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('content_types', 'enabled', 'shared', 'weight')), ) content_types = ContentTypeMultipleChoiceField( @@ -201,7 +201,7 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm): class WebhookFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Attributes', ('content_type_id', 'http_method', 'enabled')), ('Events', ('type_create', 'type_update', 'type_delete')), ) @@ -253,7 +253,7 @@ class TagFilterForm(SavedFiltersMixin, FilterForm): class ConfigContextFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( - (None, ('q', 'filter', 'tag_id')), + (None, ('q', 'filter_id', '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')), @@ -340,7 +340,7 @@ class LocalConfigContextFilterForm(forms.Form): class JournalEntryFilterForm(NetBoxModelFilterSetForm): model = JournalEntry fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Creation', ('created_before', 'created_after', 'created_by_id')), ('Attributes', ('assigned_object_type_id', 'kind')) ) @@ -381,7 +381,7 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm): class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm): model = ObjectChange fieldsets = ( - (None, ('q', 'filter')), + (None, ('q', 'filter_id')), ('Time', ('time_before', 'time_after')), ('Attributes', ('action', 'user_id', 'changed_object_type_id')), ) diff --git a/netbox/extras/forms/mixins.py b/netbox/extras/forms/mixins.py index 0a7dbdbcfec..640bcc3dcbc 100644 --- a/netbox/extras/forms/mixins.py +++ b/netbox/extras/forms/mixins.py @@ -64,7 +64,7 @@ def _append_customfield_fields(self): class SavedFiltersMixin(forms.Form): - filter = DynamicModelMultipleChoiceField( + filter_id = DynamicModelMultipleChoiceField( queryset=SavedFilter.objects.all(), required=False, label=_('Saved Filter'), diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 192cdeeec7b..048d64d547c 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -112,12 +112,13 @@ class Meta: class SavedFilterForm(BootstrapMixin, forms.ModelForm): + slug = SlugField() content_types = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all() ) fieldsets = ( - ('Saved Filter', ('name', 'content_types', 'description', 'weight', 'enabled', 'shared')), + ('Saved Filter', ('name', 'slug', 'content_types', 'description', 'weight', 'enabled', 'shared')), ('Parameters', ('parameters',)), ) diff --git a/netbox/extras/migrations/0082_savedfilter.py b/netbox/extras/migrations/0082_savedfilter.py index 67ccc325f65..e2626ec6a7b 100644 --- a/netbox/extras/migrations/0082_savedfilter.py +++ b/netbox/extras/migrations/0082_savedfilter.py @@ -1,8 +1,6 @@ -# Generated by Django 4.1.1 on 2022-10-27 18:18 - +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -21,6 +19,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(auto_now_add=True, null=True)), ('last_updated', models.DateTimeField(auto_now=True, null=True)), ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), ('description', models.CharField(blank=True, max_length=200)), ('weight', models.PositiveSmallIntegerField(default=100)), ('enabled', models.BooleanField(default=True)), diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index c33245f99a7..b41e89d6c1f 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -365,6 +365,10 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge max_length=100, unique=True ) + slug = models.SlugField( + max_length=100, + unique=True + ) description = models.CharField( max_length=200, blank=True diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index da4241e690b..172fbfbf95d 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -100,7 +100,7 @@ class SavedFilterTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = SavedFilter fields = ( - 'pk', 'id', 'name', 'content_types', 'description', 'user', 'weight', 'enabled', 'shared', + 'pk', 'id', 'name', 'slug', 'content_types', 'description', 'user', 'weight', 'enabled', 'shared', 'created', 'last_updated', ) default_columns = ( diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 045391ea8b9..2b4a4aa5f11 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -192,11 +192,12 @@ def setUpTestData(cls): class SavedFilterTest(APIViewTestCases.APIViewTestCase): model = SavedFilter - brief_fields = ['display', 'id', 'name', 'url'] + brief_fields = ['display', 'id', 'name', 'slug', 'url'] create_data = [ { 'content_types': ['dcim.site'], 'name': 'Saved Filter 4', + 'slug': 'saved-filter-4', 'weight': 100, 'enabled': True, 'shared': True, @@ -205,6 +206,7 @@ class SavedFilterTest(APIViewTestCases.APIViewTestCase): { 'content_types': ['dcim.site'], 'name': 'Saved Filter 5', + 'slug': 'saved-filter-5', 'weight': 200, 'enabled': True, 'shared': True, @@ -213,6 +215,7 @@ class SavedFilterTest(APIViewTestCases.APIViewTestCase): { 'content_types': ['dcim.site'], 'name': 'Saved Filter 6', + 'slug': 'saved-filter-6', 'weight': 300, 'enabled': True, 'shared': True, @@ -232,6 +235,7 @@ def setUpTestData(cls): saved_filters = ( SavedFilter( name='Saved Filter 1', + slug='saved-filter-1', weight=100, enabled=True, shared=True, @@ -239,6 +243,7 @@ def setUpTestData(cls): ), SavedFilter( name='Saved Filter 2', + slug='saved-filter-2', weight=200, enabled=True, shared=True, @@ -246,6 +251,7 @@ def setUpTestData(cls): ), SavedFilter( name='Saved Filter 3', + slug='saved-filter-3', weight=300, enabled=True, shared=True, diff --git a/netbox/extras/tests/test_filtersets.py b/netbox/extras/tests/test_filtersets.py index 140f059069f..3c8899b5ebb 100644 --- a/netbox/extras/tests/test_filtersets.py +++ b/netbox/extras/tests/test_filtersets.py @@ -240,6 +240,7 @@ def setUpTestData(cls): saved_filters = ( SavedFilter( name='Saved Filter 1', + slug='saved-filter-1', user=users[0], weight=100, enabled=True, @@ -248,6 +249,7 @@ def setUpTestData(cls): ), SavedFilter( name='Saved Filter 2', + slug='saved-filter-2', user=users[1], weight=200, enabled=True, @@ -256,6 +258,7 @@ def setUpTestData(cls): ), SavedFilter( name='Saved Filter 3', + slug='saved-filter-3', user=users[2], weight=300, enabled=False, @@ -271,6 +274,10 @@ def test_name(self): params = {'name': ['Saved Filter 1', 'Saved Filter 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_slug(self): + params = {'slug': ['saved-filter-1', 'saved-filter-2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_content_types(self): params = {'content_types': 'dcim.site'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 175ffb9cab0..98de95e8f38 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -122,9 +122,27 @@ def setUpTestData(cls): User.objects.bulk_create(users) saved_filters = ( - SavedFilter(name='Saved Filter 1', user=users[0], weight=100, parameters={'status': ['active']}), - SavedFilter(name='Saved Filter 2', user=users[1], weight=200, parameters={'status': ['planned']}), - SavedFilter(name='Saved Filter 3', user=users[2], weight=300, parameters={'status': ['retired']}), + SavedFilter( + name='Saved Filter 1', + slug='saved-filter-1', + user=users[0], + weight=100, + parameters={'status': ['active']} + ), + SavedFilter( + name='Saved Filter 2', + slug='saved-filter-2', + user=users[1], + weight=200, + parameters={'status': ['planned']} + ), + SavedFilter( + name='Saved Filter 3', + slug='saved-filter-3', + user=users[2], + weight=300, + parameters={'status': ['retired']} + ), ) SavedFilter.objects.bulk_create(saved_filters) for i, savedfilter in enumerate(saved_filters): @@ -132,6 +150,7 @@ def setUpTestData(cls): cls.form_data = { 'name': 'Saved Filter X', + 'slug': 'saved-filter-x', 'content_types': [site_ct.pk], 'description': 'Foo', 'weight': 1000, @@ -141,10 +160,10 @@ def setUpTestData(cls): } cls.csv_data = ( - 'name,content_types,weight,enabled,shared,parameters', - 'Saved Filter 4,dcim.device,400,True,True,{"foo": "a"}', - 'Saved Filter 5,dcim.device,500,True,True,{"foo": "b"}', - 'Saved Filter 6,dcim.device,600,True,True,{"foo": "c"}', + 'name,slug,content_types,weight,enabled,shared,parameters', + 'Saved Filter 4,saved-filter-4,dcim.device,400,True,True,{"foo": "a"}', + 'Saved Filter 5,saved-filter-5,dcim.device,500,True,True,{"foo": "b"}', + 'Saved Filter 6,saved-filter-6,dcim.device,600,True,True,{"foo": "c"}', ) cls.csv_update_data = ( diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 1a1496d7b97..6f273b15864 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -45,7 +45,7 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VRF fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Route Targets', ('import_target_id', 'export_target_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -65,7 +65,7 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = RouteTarget fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('VRF', ('importing_vrf_id', 'exporting_vrf_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -97,7 +97,7 @@ class RIRFilterForm(NetBoxModelFilterSetForm): class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Aggregate fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('family', 'rir_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -118,7 +118,7 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = ASN fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Assignment', ('rir_id', 'site_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -143,7 +143,7 @@ class RoleFilterForm(NetBoxModelFilterSetForm): class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = Prefix fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', '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')), @@ -232,7 +232,7 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPRange fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -264,7 +264,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPAddress fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')), ('VRF', ('vrf_id', 'present_in_vrf_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -333,7 +333,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class FHRPGroupFilterForm(NetBoxModelFilterSetForm): model = FHRPGroup fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'protocol', 'group_id')), ('Authentication', ('auth_type', 'auth_key')), ) @@ -363,7 +363,7 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm): class VLANGroupFilterForm(NetBoxModelFilterSetForm): fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region', 'sitegroup', 'site', 'location', 'rack')), ('VLAN ID', ('min_vid', 'max_vid')), ) @@ -411,7 +411,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm): class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = VLAN fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Location', ('region_id', 'site_group_id', 'site_id')), ('Attributes', ('group_id', 'status', 'role_id', 'vid')), ('Tenant', ('tenant_group_id', 'tenant_id')), @@ -464,7 +464,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class ServiceTemplateFilterForm(NetBoxModelFilterSetForm): model = ServiceTemplate fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('protocol', 'port')), ) protocol = forms.ChoiceField( @@ -485,7 +485,7 @@ class ServiceFilterForm(ServiceTemplateFilterForm): class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = L2VPN fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('type', 'import_target_id', 'export_target_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) @@ -510,7 +510,7 @@ class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm): model = L2VPNTermination fieldsets = ( - (None, ('filter', 'l2vpn_id',)), + (None, ('filter_id', 'l2vpn_id',)), ('Assigned Object', ( 'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id', )), diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index 8c39db2879a..ee0ab330ca6 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -2,9 +2,9 @@ from copy import deepcopy from django.contrib.contenttypes.models import ContentType from django.db import models +from django.db.models import Q from django_filters.exceptions import FieldLookupError from django_filters.utils import get_model_field, resolve_field -from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as _ from extras.choices import CustomFieldFilterLogicChoices @@ -89,9 +89,12 @@ def __init__(self, data=None, *args, **kwargs): self.base_filters = self.__class__.get_filters() # Apply any referenced SavedFilters - if data and 'filter' in data: + if data and ('filter' in data or 'filter_id' in data): data = data.copy() # Get a mutable copy - saved_filters = SavedFilter.objects.filter(pk__in=data.pop('filter')) + saved_filters = SavedFilter.objects.filter( + Q(slug__in=data.pop('filter', [])) | + Q(pk__in=data.pop('filter_id', [])) + ) for sf in saved_filters: for key, value in sf.parameters.items(): # QueryDicts are... fun diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 2d785400c9c..b648712909f 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -141,7 +141,7 @@ def __init__(self, *args, **kwargs): # Limit saved filters to those applicable to the form's model content_type = ContentType.objects.get_for_model(self.model) - self.fields['filter'].widget.add_query_params({ + self.fields['filter_id'].widget.add_query_params({ 'content_type_id': content_type.pk, }) diff --git a/netbox/tenancy/forms/filtersets.py b/netbox/tenancy/forms/filtersets.py index f840a217722..c5d7fca0cd8 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', 'filter', 'tag', 'group_id')), + (None, ('q', 'filter_id', '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 ed2e39041fb..3b21a2c30b5 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 user.has_perm('extras.add_savedfilter') and 'filter' not in context['request'].GET: + if user.has_perm('extras.add_savedfilter') and 'filter_id' 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 c4fdf033a2e..2ead565953a 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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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', 'filter', 'tag')), + (None, ('q', 'filter_id', '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 287ef779c28..70aef96102c 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', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('ssid', 'group_id', 'status')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), @@ -67,7 +67,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = WirelessLink fieldsets = ( - (None, ('q', 'filter', 'tag')), + (None, ('q', 'filter_id', 'tag')), ('Attributes', ('ssid', 'status',)), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),