From f190a23a3e551d7564373848d92fb001614a85fb Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 2 Jul 2024 09:20:08 -0400 Subject: [PATCH] Fixes #16779: Fix saved filter selection for child object lists --- netbox/dcim/views.py | 15 +++++++++++++++ netbox/ipam/views.py | 12 ++++++++++++ netbox/netbox/views/generic/bulk_views.py | 2 +- netbox/netbox/views/generic/object_views.py | 3 +++ netbox/templates/inc/table_controls_htmx.html | 14 ++++++++------ netbox/tenancy/views.py | 1 + netbox/virtualization/views.py | 5 +++++ 7 files changed, 45 insertions(+), 7 deletions(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 87f351e4d0..b3d8d298e7 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -31,6 +31,7 @@ GetRelatedModelsMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view ) from virtualization.filtersets import VirtualMachineFilterSet +from virtualization.forms import VirtualMachineFilterForm from virtualization.models import VirtualMachine from virtualization.tables import VirtualMachineTable from . import filtersets, forms, tables @@ -679,6 +680,7 @@ class RackRackReservationsView(generic.ObjectChildrenView): child_model = RackReservation table = tables.RackReservationTable filterset = filtersets.RackReservationFilterSet + filterset_form = forms.RackReservationFilterForm template_name = 'dcim/rack/reservations.html' tab = ViewTab( label=_('Reservations'), @@ -697,6 +699,7 @@ class RackNonRackedView(generic.ObjectChildrenView): child_model = Device table = tables.DeviceTable filterset = filtersets.DeviceFilterSet + filterset_form = forms.DeviceFilterForm template_name = 'dcim/rack/non_racked_devices.html' tab = ViewTab( label=_('Non-Racked Devices'), @@ -1835,6 +1838,7 @@ class DeviceConsolePortsView(DeviceComponentsView): child_model = ConsolePort table = tables.DeviceConsolePortTable filterset = filtersets.ConsolePortFilterSet + filterset_form = forms.ConsolePortFilterForm template_name = 'dcim/device/consoleports.html', tab = ViewTab( label=_('Console Ports'), @@ -1850,6 +1854,7 @@ class DeviceConsoleServerPortsView(DeviceComponentsView): child_model = ConsoleServerPort table = tables.DeviceConsoleServerPortTable filterset = filtersets.ConsoleServerPortFilterSet + filterset_form = forms.ConsoleServerPortFilterForm template_name = 'dcim/device/consoleserverports.html' tab = ViewTab( label=_('Console Server Ports'), @@ -1865,6 +1870,7 @@ class DevicePowerPortsView(DeviceComponentsView): child_model = PowerPort table = tables.DevicePowerPortTable filterset = filtersets.PowerPortFilterSet + filterset_form = forms.PowerPortFilterForm template_name = 'dcim/device/powerports.html' tab = ViewTab( label=_('Power Ports'), @@ -1880,6 +1886,7 @@ class DevicePowerOutletsView(DeviceComponentsView): child_model = PowerOutlet table = tables.DevicePowerOutletTable filterset = filtersets.PowerOutletFilterSet + filterset_form = forms.PowerOutletFilterForm template_name = 'dcim/device/poweroutlets.html' tab = ViewTab( label=_('Power Outlets'), @@ -1895,6 +1902,7 @@ class DeviceInterfacesView(DeviceComponentsView): child_model = Interface table = tables.DeviceInterfaceTable filterset = filtersets.InterfaceFilterSet + filterset_form = forms.InterfaceFilterForm template_name = 'dcim/device/interfaces.html' tab = ViewTab( label=_('Interfaces'), @@ -1916,6 +1924,7 @@ class DeviceFrontPortsView(DeviceComponentsView): child_model = FrontPort table = tables.DeviceFrontPortTable filterset = filtersets.FrontPortFilterSet + filterset_form = forms.FrontPortFilterForm template_name = 'dcim/device/frontports.html' tab = ViewTab( label=_('Front Ports'), @@ -1931,6 +1940,7 @@ class DeviceRearPortsView(DeviceComponentsView): child_model = RearPort table = tables.DeviceRearPortTable filterset = filtersets.RearPortFilterSet + filterset_form = forms.RearPortFilterForm template_name = 'dcim/device/rearports.html' tab = ViewTab( label=_('Rear Ports'), @@ -1946,6 +1956,7 @@ class DeviceModuleBaysView(DeviceComponentsView): child_model = ModuleBay table = tables.DeviceModuleBayTable filterset = filtersets.ModuleBayFilterSet + filterset_form = forms.ModuleBayFilterForm template_name = 'dcim/device/modulebays.html' actions = { **DEFAULT_ACTION_PERMISSIONS, @@ -1965,6 +1976,7 @@ class DeviceDeviceBaysView(DeviceComponentsView): child_model = DeviceBay table = tables.DeviceDeviceBayTable filterset = filtersets.DeviceBayFilterSet + filterset_form = forms.DeviceBayFilterForm template_name = 'dcim/device/devicebays.html' actions = { **DEFAULT_ACTION_PERMISSIONS, @@ -1984,6 +1996,7 @@ class DeviceInventoryView(DeviceComponentsView): child_model = InventoryItem table = tables.DeviceInventoryItemTable filterset = filtersets.InventoryItemFilterSet + filterset_form = forms.InventoryItemFilterForm template_name = 'dcim/device/inventory.html' actions = { **DEFAULT_ACTION_PERMISSIONS, @@ -2062,6 +2075,7 @@ class DeviceVirtualMachinesView(generic.ObjectChildrenView): child_model = VirtualMachine table = VirtualMachineTable filterset = VirtualMachineFilterSet + filterset_form = VirtualMachineFilterForm tab = ViewTab( label=_('Virtual Machines'), badge=lambda obj: VirtualMachine.objects.filter(cluster=obj.cluster, device=obj).count(), @@ -2944,6 +2958,7 @@ class InventoryItemChildrenView(generic.ObjectChildrenView): child_model = InventoryItem table = tables.InventoryItemTable filterset = filtersets.InventoryItemFilterSet + filterset_form = forms.InventoryItemFilterForm tab = ViewTab( label=_('Children'), badge=lambda obj: obj.child_items.count(), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 12c86c5331..e0087f5d10 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -7,6 +7,7 @@ from circuits.models import Provider from dcim.filtersets import InterfaceFilterSet +from dcim.forms import InterfaceFilterForm from dcim.models import Interface, Site from netbox.views import generic from tenancy.views import ObjectContactsView @@ -14,6 +15,7 @@ from utilities.tables import get_table_ordering from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view from virtualization.filtersets import VMInterfaceFilterSet +from virtualization.forms import VMInterfaceFilterForm from virtualization.models import VMInterface from . import filtersets, forms, tables from .choices import PrefixStatusChoices @@ -206,6 +208,7 @@ class ASNRangeASNsView(generic.ObjectChildrenView): child_model = ASN table = tables.ASNTable filterset = filtersets.ASNFilterSet + filterset_form = forms.ASNFilterForm tab = ViewTab( label=_('ASNs'), badge=lambda x: x.get_child_asns().count(), @@ -337,6 +340,7 @@ class AggregatePrefixesView(generic.ObjectChildrenView): child_model = Prefix table = tables.PrefixTable filterset = filtersets.PrefixFilterSet + filterset_form = forms.PrefixFilterForm template_name = 'ipam/aggregate/prefixes.html' tab = ViewTab( label=_('Prefixes'), @@ -523,6 +527,7 @@ class PrefixPrefixesView(generic.ObjectChildrenView): child_model = Prefix table = tables.PrefixTable filterset = filtersets.PrefixFilterSet + filterset_form = forms.PrefixFilterForm template_name = 'ipam/prefix/prefixes.html' tab = ViewTab( label=_('Child Prefixes'), @@ -558,6 +563,7 @@ class PrefixIPRangesView(generic.ObjectChildrenView): child_model = IPRange table = tables.IPRangeTable filterset = filtersets.IPRangeFilterSet + filterset_form = forms.IPRangeFilterForm template_name = 'ipam/prefix/ip_ranges.html' tab = ViewTab( label=_('Child Ranges'), @@ -584,6 +590,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): child_model = IPAddress table = tables.IPAddressTable filterset = filtersets.IPAddressFilterSet + filterset_form = forms.IPAddressFilterForm template_name = 'ipam/prefix/ip_addresses.html' tab = ViewTab( label=_('IP Addresses'), @@ -683,6 +690,7 @@ class IPRangeIPAddressesView(generic.ObjectChildrenView): child_model = IPAddress table = tables.IPAddressTable filterset = filtersets.IPAddressFilterSet + filterset_form = forms.IPRangeFilterForm template_name = 'ipam/iprange/ip_addresses.html' tab = ViewTab( label=_('IP Addresses'), @@ -885,6 +893,7 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView): child_model = IPAddress table = tables.IPAddressTable filterset = filtersets.IPAddressFilterSet + filterset_form = forms.IPAddressFilterForm tab = ViewTab( label=_('Related IPs'), badge=lambda x: x.get_related_ips().count(), @@ -957,6 +966,7 @@ class VLANGroupVLANsView(generic.ObjectChildrenView): child_model = VLAN table = tables.VLANTable filterset = filtersets.VLANFilterSet + filterset_form = forms.VLANFilterForm tab = ViewTab( label=_('VLANs'), badge=lambda x: x.get_child_vlans().count(), @@ -1112,6 +1122,7 @@ class VLANInterfacesView(generic.ObjectChildrenView): child_model = Interface table = tables.VLANDevicesTable filterset = InterfaceFilterSet + filterset_form = InterfaceFilterForm tab = ViewTab( label=_('Device Interfaces'), badge=lambda x: x.get_interfaces().count(), @@ -1129,6 +1140,7 @@ class VLANVMInterfacesView(generic.ObjectChildrenView): child_model = VMInterface table = tables.VLANVirtualMachinesTable filterset = VMInterfaceFilterSet + filterset_form = VMInterfaceFilterForm tab = ViewTab( label=_('VM Interfaces'), badge=lambda x: x.get_vminterfaces().count(), diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 87e352710c..71ce411ba7 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -176,7 +176,7 @@ def get(self, request): 'model': model, 'table': table, 'actions': actions, - 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, + 'filter_form': self.filterset_form(request.GET) if self.filterset_form else None, 'prerequisite_model': get_prerequisite_model(self.queryset), **self.get_extra_context(request), } diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 243ae2547d..462000c749 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -87,12 +87,14 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): child_model: The model class which represents the child objects table: The django-tables2 Table class used to render the child objects list filterset: A django-filter FilterSet that is applied to the queryset + filterset_form: The form class used to render filter options actions: A mapping of supported actions to their required permissions. When adding custom actions, bulk action names must be prefixed with `bulk_`. (See ActionsMixin.) """ child_model = None table = None filterset = None + filterset_form = None template_name = 'generic/object_children.html' def get_children(self, request, parent): @@ -152,6 +154,7 @@ def get(self, request, *args, **kwargs): 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', 'table': table, 'table_config': f'{table.name}_config', + 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, 'actions': actions, 'tab': self.tab, 'return_url': request.get_full_path(), diff --git a/netbox/templates/inc/table_controls_htmx.html b/netbox/templates/inc/table_controls_htmx.html index ec913d32af..a07b319efe 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -13,14 +13,16 @@ -
-
-
- + {% if filter_form %} +
+
+
+ +
+ {{ filter_form.filter_id }}
- {{ filter_form.filter_id }}
-
+ {% endif %}
{% if request.user.is_authenticated and table_modal %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 06fbcc5758..c1b3c1b267 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -13,6 +13,7 @@ class ObjectContactsView(generic.ObjectChildrenView): child_model = ContactAssignment table = tables.ContactAssignmentTable filterset = filtersets.ContactAssignmentFilterSet + filterset_form = forms.ContactAssignmentFilterForm template_name = 'tenancy/object_contacts.html' tab = ViewTab( label=_('Contacts'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index c143fff85b..1030fed04a 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -10,6 +10,7 @@ from jinja2.exceptions import TemplateError from dcim.filtersets import DeviceFilterSet +from dcim.forms import DeviceFilterForm from dcim.models import Device from dcim.tables import DeviceTable from extras.views import ObjectConfigContextView @@ -173,6 +174,7 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView): child_model = VirtualMachine table = tables.VirtualMachineTable filterset = filtersets.VirtualMachineFilterSet + filterset_form = forms.VirtualMachineFilterForm tab = ViewTab( label=_('Virtual Machines'), badge=lambda obj: obj.virtual_machines.count(), @@ -190,6 +192,7 @@ class ClusterDevicesView(generic.ObjectChildrenView): child_model = Device table = DeviceTable filterset = DeviceFilterSet + filterset_form = DeviceFilterForm template_name = 'virtualization/cluster/devices.html' actions = { 'add': {'add'}, @@ -350,6 +353,7 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView): child_model = VMInterface table = tables.VirtualMachineVMInterfaceTable filterset = filtersets.VMInterfaceFilterSet + filterset_form = forms.VMInterfaceFilterForm template_name = 'virtualization/virtualmachine/interfaces.html' actions = { **DEFAULT_ACTION_PERMISSIONS, @@ -375,6 +379,7 @@ class VirtualMachineVirtualDisksView(generic.ObjectChildrenView): child_model = VirtualDisk table = tables.VirtualMachineVirtualDiskTable filterset = filtersets.VirtualDiskFilterSet + filterset_form = forms.VirtualDiskFilterForm template_name = 'virtualization/virtualmachine/virtual_disks.html' tab = ViewTab( label=_('Virtual Disks'),