From 8d391818429c4b2728de1bab42b52de31d2e8830 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Fri, 15 Dec 2023 22:07:15 +0100 Subject: [PATCH] Fixes #12751 - Usability improvements for object selector (#14387) * Usability improvements for object selector: * Adds preselected filters * Applies the filter on selection instead of requiring the search button to be pushed * Declare selector_fields on base form class --------- Co-authored-by: Jeremy Stretch --- netbox/circuits/forms/filtersets.py | 1 + netbox/dcim/forms/filtersets.py | 8 ++++++++ netbox/ipam/forms/filtersets.py | 2 ++ netbox/netbox/forms/base.py | 4 ++++ netbox/project-static/dist/netbox.js | Bin 529867 -> 529929 bytes netbox/project-static/dist/netbox.js.map | Bin 450255 -> 450302 bytes .../src/select/api/apiSelect.ts | 5 +++++ netbox/templates/htmx/object_selector.html | 6 +++--- netbox/virtualization/forms/filtersets.py | 2 ++ 9 files changed, 25 insertions(+), 3 deletions(-) diff --git a/netbox/circuits/forms/filtersets.py b/netbox/circuits/forms/filtersets.py index 1fb239023..643071be8 100644 --- a/netbox/circuits/forms/filtersets.py +++ b/netbox/circuits/forms/filtersets.py @@ -110,6 +110,7 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi (_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Contacts'), ('contact', 'contact_role', 'contact_group')), ) + selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'provider_id', 'provider_network_id') type_id = DynamicModelMultipleChoiceField( queryset=CircuitType.objects.all(), required=False, diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index d0d321187..41bb417aa 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -164,6 +164,7 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte (_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Contacts'), ('contact', 'contact_role', 'contact_group')), ) + selector_fields = ('filter_id', 'q', 'region_id', 'group_id') status = forms.MultipleChoiceField( label=_('Status'), choices=SiteStatusChoices, @@ -247,6 +248,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte (_('Contacts'), ('contact', 'contact_role', 'contact_group')), (_('Weight'), ('weight', 'max_weight', 'weight_unit')), ) + selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id') region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -419,6 +421,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): )), (_('Weight'), ('weight', 'weight_unit')), ) + selector_fields = ('filter_id', 'q', 'manufacturer_id') manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, @@ -543,6 +546,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm): )), (_('Weight'), ('weight', 'weight_unit')), ) + selector_fields = ('filter_id', 'q', 'manufacturer_id') manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, @@ -619,6 +623,7 @@ class DeviceRoleFilterForm(NetBoxModelFilterSetForm): class PlatformFilterForm(NetBoxModelFilterSetForm): model = Platform + selector_fields = ('filter_id', 'q', 'manufacturer_id') manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, @@ -653,6 +658,7 @@ class DeviceFilterForm( 'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data', )) ) + selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id') region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -996,6 +1002,7 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')), (_('Contacts'), ('contact', 'contact_role', 'contact_group')), ) + selector_fields = ('filter_id', 'q', 'site_id', 'location_id') region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -1227,6 +1234,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) + selector_fields = ('filter_id', 'q', 'device_id') vdc_id = DynamicModelMultipleChoiceField( queryset=VirtualDeviceContext.objects.all(), required=False, diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index a8ca91901..b72788387 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -300,6 +300,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): (_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Device/VM'), ('device_id', 'virtual_machine_id')), ) + selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'parent', 'status', 'role') parent = forms.CharField( required=False, widget=forms.TextInput( @@ -452,6 +453,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): (_('Attributes'), ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')), (_('Tenant'), ('tenant_group_id', 'tenant_id')), ) + selector_fields = ('filter_id', 'q', 'site_id') region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 43d0850f0..51e664a39 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -145,12 +145,16 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMi model: The model class associated with the form fieldsets: An iterable of two-tuples which define a heading and field set to display per section of the rendered form (optional). If not defined, the all fields will be rendered as a single section. + selector_fields: An iterable of names of fields to display by default when rendering the form as + a selector widget """ q = forms.CharField( required=False, label=_('Search') ) + selector_fields = ('filter_id', 'q') + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 426302ea83c97ad0ae968919b4ede00f323f214e..97c4ba79c3e5b51b62097e2d1b2dbf904da8a917 100644 GIT binary patch delta 56 zcmV-80LTB!t00N2Ab^AcgaU*Egam{Iv<4!|m-J!<6A2=7Xm58SDJCYD=gI~immlH< O7Kb#-2DdcI2bW7sdK85K delta 36 scmeBNqi}k)LPHB<3sVbo3rh>@7B;0*)0H2w3bhxVV%uJHiha5_00L7E4FCWD diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 077c4bcc0b0e1e6c7dbb1eb1722ea94132e260b2..bbb2a3cc01cdba0309a6ead9b117254ba9d182be 100644 GIT binary patch delta 115 zcmX@VSNh*x>4p}@7N!>F7M3ln>(3gyM(B7tItJ^6Iy#2x1cO+?j?R`&I)RRkfgl-A z9cM>JXB}@xM{k`(N8bV`9rx*iUzo+WpE}F>O@u3>(y7W>$KNrta{H-AtiJ3ldD>3K Q)6d(mifuQ1#X9c`0J+*HXaE2J delta 68 zcmV-K0K5PGy&KQH8-RoXgaU*Egaot&ugwHWW-LjU5%dEWxB1Nl_7n;;ctUzZEKfl+ ac(?iD1x*J8IXOZzm(M%}7PnyP1)lV-*Bbr+ diff --git a/netbox/project-static/src/select/api/apiSelect.ts b/netbox/project-static/src/select/api/apiSelect.ts index 53996910e..279340c12 100644 --- a/netbox/project-static/src/select/api/apiSelect.ts +++ b/netbox/project-static/src/select/api/apiSelect.ts @@ -264,6 +264,11 @@ export class APISelect { switch (this.trigger) { case 'collapse': if (collapse !== null) { + // If the element is collapsible but already shown, load the data immediately. + if (collapse.classList.contains('show')) { + Promise.all([this.loadData()]); + } + // If this element is part of a collapsible element, only load the data when the // collapsible element is shown. // See: https://getbootstrap.com/docs/5.0/components/collapse/#events diff --git a/netbox/templates/htmx/object_selector.html b/netbox/templates/htmx/object_selector.html index 0febb1069..280102ada 100644 --- a/netbox/templates/htmx/object_selector.html +++ b/netbox/templates/htmx/object_selector.html @@ -10,18 +10,18 @@
{% for field in form.visible_fields %} - + {{ field.label }} {% endfor %}
-
+
{% for field in form.visible_fields %} -
{% render_field field %}
+
{% render_field field %}
{% endfor %}
diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py index 99ac0cb77..4028bcc64 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -44,6 +44,7 @@ class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi (_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Contacts'), ('contact', 'contact_role', 'contact_group')), ) + selector_fields = ('filter_id', 'q', 'group_id') type_id = DynamicModelMultipleChoiceField( queryset=ClusterType.objects.all(), required=False, @@ -186,6 +187,7 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm): (_('Virtual Machine'), ('cluster_id', 'virtual_machine_id')), (_('Attributes'), ('enabled', 'mac_address', 'vrf_id', 'l2vpn_id')), ) + selector_fields = ('filter_id', 'q', 'virtual_machine_id') cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False,