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 426302ea8..97c4ba79c 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 077c4bcc0..bbb2a3cc0 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ 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,