Skip to content

Commit 8d39181

Browse files
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 <[email protected]>
1 parent c81869c commit 8d39181

File tree

9 files changed

+27
-5
lines changed

9 files changed

+27
-5
lines changed

netbox/circuits/forms/filtersets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
110110
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
111111
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
112112
)
113+
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'provider_id', 'provider_network_id')
113114
type_id = DynamicModelMultipleChoiceField(
114115
queryset=CircuitType.objects.all(),
115116
required=False,

netbox/dcim/forms/filtersets.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
164164
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
165165
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
166166
)
167+
selector_fields = ('filter_id', 'q', 'region_id', 'group_id')
167168
status = forms.MultipleChoiceField(
168169
label=_('Status'),
169170
choices=SiteStatusChoices,
@@ -247,6 +248,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
247248
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
248249
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
249250
)
251+
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id')
250252
region_id = DynamicModelMultipleChoiceField(
251253
queryset=Region.objects.all(),
252254
required=False,
@@ -419,6 +421,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
419421
)),
420422
(_('Weight'), ('weight', 'weight_unit')),
421423
)
424+
selector_fields = ('filter_id', 'q', 'manufacturer_id')
422425
manufacturer_id = DynamicModelMultipleChoiceField(
423426
queryset=Manufacturer.objects.all(),
424427
required=False,
@@ -543,6 +546,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
543546
)),
544547
(_('Weight'), ('weight', 'weight_unit')),
545548
)
549+
selector_fields = ('filter_id', 'q', 'manufacturer_id')
546550
manufacturer_id = DynamicModelMultipleChoiceField(
547551
queryset=Manufacturer.objects.all(),
548552
required=False,
@@ -619,6 +623,7 @@ class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
619623

620624
class PlatformFilterForm(NetBoxModelFilterSetForm):
621625
model = Platform
626+
selector_fields = ('filter_id', 'q', 'manufacturer_id')
622627
manufacturer_id = DynamicModelMultipleChoiceField(
623628
queryset=Manufacturer.objects.all(),
624629
required=False,
@@ -653,6 +658,7 @@ class DeviceFilterForm(
653658
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
654659
))
655660
)
661+
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')
656662
region_id = DynamicModelMultipleChoiceField(
657663
queryset=Region.objects.all(),
658664
required=False,
@@ -996,6 +1002,7 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
9961002
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
9971003
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
9981004
)
1005+
selector_fields = ('filter_id', 'q', 'site_id', 'location_id')
9991006
region_id = DynamicModelMultipleChoiceField(
10001007
queryset=Region.objects.all(),
10011008
required=False,
@@ -1227,6 +1234,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
12271234
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
12281235
(_('Connection'), ('cabled', 'connected', 'occupied')),
12291236
)
1237+
selector_fields = ('filter_id', 'q', 'device_id')
12301238
vdc_id = DynamicModelMultipleChoiceField(
12311239
queryset=VirtualDeviceContext.objects.all(),
12321240
required=False,

netbox/ipam/forms/filtersets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
300300
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
301301
(_('Device/VM'), ('device_id', 'virtual_machine_id')),
302302
)
303+
selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'parent', 'status', 'role')
303304
parent = forms.CharField(
304305
required=False,
305306
widget=forms.TextInput(
@@ -452,6 +453,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
452453
(_('Attributes'), ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')),
453454
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
454455
)
456+
selector_fields = ('filter_id', 'q', 'site_id')
455457
region_id = DynamicModelMultipleChoiceField(
456458
queryset=Region.objects.all(),
457459
required=False,

netbox/netbox/forms/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,16 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMi
145145
model: The model class associated with the form
146146
fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
147147
the rendered form (optional). If not defined, the all fields will be rendered as a single section.
148+
selector_fields: An iterable of names of fields to display by default when rendering the form as
149+
a selector widget
148150
"""
149151
q = forms.CharField(
150152
required=False,
151153
label=_('Search')
152154
)
153155

156+
selector_fields = ('filter_id', 'q')
157+
154158
def __init__(self, *args, **kwargs):
155159
super().__init__(*args, **kwargs)
156160

netbox/project-static/dist/netbox.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/dist/netbox.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/src/select/api/apiSelect.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ export class APISelect {
264264
switch (this.trigger) {
265265
case 'collapse':
266266
if (collapse !== null) {
267+
// If the element is collapsible but already shown, load the data immediately.
268+
if (collapse.classList.contains('show')) {
269+
Promise.all([this.loadData()]);
270+
}
271+
267272
// If this element is part of a collapsible element, only load the data when the
268273
// collapsible element is shown.
269274
// See: https://getbootstrap.com/docs/5.0/components/collapse/#events

netbox/templates/htmx/object_selector.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ <h5 class="modal-title">{% trans "Select" %} {{ model|meta:"verbose_name"|better
1010
<div class="list-group list-group-flush">
1111
{% for field in form.visible_fields %}
1212
<a href="#" class="list-group-item list-group-item-action px-0 py-1" data-bs-toggle="collapse" data-bs-target="#checkmark{{ forloop.counter }}, #selector{{ forloop.counter }}">
13-
<span id="checkmark{{ forloop.counter }}" class="collapse{% if forloop.counter < 3 %} show{% endif %}"><i class="mdi mdi-check-bold"></i></span>
13+
<span id="checkmark{{ forloop.counter }}" class="collapse{% if forloop.counter < 3 or field.name in form.selector_fields %} show{% endif %}"><i class="mdi mdi-check-bold"></i></span>
1414
{{ field.label }}
1515
</a>
1616
{% endfor %}
1717
</div>
1818
</div>
1919
<div class="col-9">
20-
<form hx-get="{% url 'htmx_object_selector' %}?_model={{ model|meta:"label_lower" }}" hx-target="#selector_results" hx-trigger="load, submit, keyup from:#id_q delay:500ms">
20+
<form hx-get="{% url 'htmx_object_selector' %}?_model={{ model|meta:"label_lower" }}" hx-target="#selector_results" hx-trigger="load, submit, change, keyup from:#id_q delay:500ms">
2121
<input type="hidden" name="_search" value="true" />
2222
<div class="tab-content p-1">
2323
{% for field in form.visible_fields %}
24-
<div class="collapse{% if forloop.counter < 3 %} show{% endif %}" id="selector{{ forloop.counter }}">{% render_field field %}</div>
24+
<div class="collapse{% if field.name in form.selector_fields %} show{% endif %}" id="selector{{ forloop.counter }}">{% render_field field %}</div>
2525
{% endfor %}
2626
</div>
2727
<div class="text-end">

netbox/virtualization/forms/filtersets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
4444
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
4545
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
4646
)
47+
selector_fields = ('filter_id', 'q', 'group_id')
4748
type_id = DynamicModelMultipleChoiceField(
4849
queryset=ClusterType.objects.all(),
4950
required=False,
@@ -186,6 +187,7 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
186187
(_('Virtual Machine'), ('cluster_id', 'virtual_machine_id')),
187188
(_('Attributes'), ('enabled', 'mac_address', 'vrf_id', 'l2vpn_id')),
188189
)
190+
selector_fields = ('filter_id', 'q', 'virtual_machine_id')
189191
cluster_id = DynamicModelMultipleChoiceField(
190192
queryset=Cluster.objects.all(),
191193
required=False,

0 commit comments

Comments
 (0)