Skip to content

Commit c84f0de

Browse files
committed
#11625: Employ HTMX form rendering for device & VM interfaces
1 parent 368e774 commit c84f0de

File tree

13 files changed

+75
-274
lines changed

13 files changed

+75
-274
lines changed

netbox/core/forms/model_forms.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from core.models import *
66
from netbox.forms import NetBoxModelForm
77
from netbox.registry import registry
8-
from utilities.forms import CommentField
8+
from utilities.forms import CommentField, get_field_value
99

1010
__all__ = (
1111
'DataSourceForm',
@@ -44,24 +44,19 @@ def fieldsets(self):
4444
]
4545
if self.backend_fields:
4646
fieldsets.append(
47-
('Backend', self.backend_fields)
47+
('Backend Parameters', self.backend_fields)
4848
)
4949

5050
return fieldsets
5151

5252
def __init__(self, *args, **kwargs):
5353
super().__init__(*args, **kwargs)
5454

55-
backend_classes = registry['data_backends']
56-
57-
if self.is_bound and self.data.get('type') in backend_classes:
58-
type_ = self.data['type']
59-
elif self.initial and self.initial.get('type') in backend_classes:
60-
type_ = self.initial['type']
61-
else:
62-
type_ = self.fields['type'].initial
63-
backend = backend_classes.get(type_)
55+
# Determine the selected backend type
56+
backend_type = get_field_value(self, 'type')
57+
backend = registry['data_backends'].get(backend_type)
6458

59+
# Add backend-specific form fields
6560
self.backend_fields = []
6661
for name, form_field in backend.parameters.items():
6762
field_name = f'backend_{name}'

netbox/dcim/forms/common.py

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from dcim.choices import *
55
from dcim.constants import *
6+
from utilities.forms.utils import get_field_value
67

78
__all__ = (
89
'InterfaceCommonForm',
@@ -23,6 +24,20 @@ class InterfaceCommonForm(forms.Form):
2324
label=_('MTU')
2425
)
2526

27+
def __init__(self, *args, **kwargs):
28+
super().__init__(*args, **kwargs)
29+
30+
# Determine the selected 802.1Q mode
31+
interface_mode = get_field_value(self, 'mode')
32+
33+
# Delete VLAN tagging fields which are not relevant for the selected mode
34+
if interface_mode in (InterfaceModeChoices.MODE_ACCESS, InterfaceModeChoices.MODE_TAGGED_ALL):
35+
del self.fields['tagged_vlans']
36+
elif not interface_mode:
37+
del self.fields['vlan_group']
38+
del self.fields['untagged_vlan']
39+
del self.fields['tagged_vlans']
40+
2641
def clean(self):
2742
super().clean()
2843

netbox/dcim/forms/model_forms.py

+7
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,13 @@ class Meta:
13671367
]
13681368
widgets = {
13691369
'speed': SelectSpeedWidget(),
1370+
'mode': forms.Select(
1371+
attrs={
1372+
'hx-get': '.',
1373+
'hx-include': '#form_fields input',
1374+
'hx-target': '#form_fields',
1375+
}
1376+
),
13701377
}
13711378
labels = {
13721379
'mode': '802.1Q Mode',

netbox/netbox/views/generic/object_views.py

+6
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ def get(self, request):
431431
form = self.initialize_form(request)
432432
instance = self.alter_object(self.queryset.model(), request)
433433

434+
# If this is an HTMX request, return only the rendered form HTML
435+
if is_htmx(request):
436+
return render(request, 'htmx/form.html', {
437+
'form': form,
438+
})
439+
434440
return render(request, self.template_name, {
435441
'object': instance,
436442
'form': form,

netbox/project-static/dist/netbox.js

+7-7
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

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { initFormElements } from './elements';
22
import { initSpeedSelector } from './speedSelector';
33
import { initScopeSelector } from './scopeSelector';
4-
import { initVlanTags } from './vlanTags';
54

65
export function initForms(): void {
7-
for (const func of [initFormElements, initSpeedSelector, initScopeSelector, initVlanTags]) {
6+
for (const func of [initFormElements, initSpeedSelector, initScopeSelector]) {
87
func();
98
}
109
}

netbox/project-static/src/forms/vlanTags.ts

-148
This file was deleted.

netbox/templates/dcim/interface_edit.html

-101
This file was deleted.

netbox/templates/htmx/form.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ <h5 class="offset-sm-3">{{ group }}</h5>
1717
{% endif %}
1818
{% for name in fields %}
1919
{% with field=form|getfield:name %}
20-
{% if not field.field.widget.is_hidden %}
20+
{% if field and not field.field.widget.is_hidden %}
2121
{% render_field field %}
2222
{% endif %}
2323
{% endwith %}

netbox/utilities/forms/utils.py

+16
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'expand_alphanumeric_pattern',
1313
'expand_ipaddress_pattern',
1414
'form_from_model',
15+
'get_field_value',
1516
'get_selected_values',
1617
'parse_alphanumeric_range',
1718
'parse_numeric_range',
@@ -113,6 +114,21 @@ def expand_ipaddress_pattern(string, family):
113114
yield ''.join([lead, format(i, 'x' if family == 6 else 'd'), remnant])
114115

115116

117+
def get_field_value(form, field_name):
118+
"""
119+
Return the current bound or initial value associated with a form field, prior to calling
120+
clean() for the form.
121+
"""
122+
field = form.fields[field_name]
123+
124+
if form.is_bound:
125+
if data := form.data.get(field_name):
126+
if field.valid_value(data):
127+
return data
128+
129+
return form.get_initial_for_field(field, field_name)
130+
131+
116132
def get_selected_values(form, field_name):
117133
"""
118134
Return the list of selected human-friendly values for a form field

0 commit comments

Comments
 (0)