This commit is contained in:
parent
ac513345b5
commit
1f0d505b91
|
|
@ -194,7 +194,7 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||
)
|
||||
termination_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='circuit-termination'),
|
||||
label=_('Termination type')
|
||||
)
|
||||
termination = DynamicModelChoiceField(
|
||||
|
|
@ -208,7 +208,8 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||
FieldSet(
|
||||
'circuit', 'term_side', 'description', 'tags',
|
||||
'termination_type', 'termination',
|
||||
'mark_connected', name=_('Circuit Termination')
|
||||
'mark_connected', name=_('Circuit Termination'),
|
||||
html_id='circuit-termination',
|
||||
),
|
||||
FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')),
|
||||
)
|
||||
|
|
@ -291,7 +292,7 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
|||
)
|
||||
member_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(CIRCUIT_GROUP_ASSIGNMENT_MEMBER_MODELS),
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='circuit-group-assignment'),
|
||||
required=False,
|
||||
label=_('Circuit type')
|
||||
)
|
||||
|
|
@ -304,7 +305,10 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('group', 'member_type', 'member', 'priority', 'tags', name=_('Group Assignment')),
|
||||
FieldSet(
|
||||
'group', 'member_type', 'member', 'priority', 'tags',
|
||||
name=_('Group Assignment'), html_id='circuit-group-assignment',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -395,7 +399,6 @@ class VirtualCircuitTerminationForm(NetBoxModelForm):
|
|||
)
|
||||
role = forms.ChoiceField(
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
widget=HTMXSelect(),
|
||||
label=_('Role')
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
|
|
@ -412,7 +415,9 @@ class VirtualCircuitTerminationForm(NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('virtual_circuit', 'role', 'interface', 'description', 'tags'),
|
||||
FieldSet(
|
||||
'virtual_circuit', 'role', 'interface', 'description', 'tags',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ EMPTY_VALUES = ('', None, [], ())
|
|||
class DataSourceForm(PrimaryModelForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=get_data_backend_choices,
|
||||
# No hx_target_id: changing type adds/removes the Backend Parameters fieldset entirely.
|
||||
widget=HTMXSelect()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import warnings
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.db import connection
|
||||
|
|
@ -27,7 +30,8 @@ __all__ = (
|
|||
class ScopedForm(forms.Form):
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=LOCATION_SCOPE_TYPES),
|
||||
widget=HTMXSelect(),
|
||||
# hx_target_id='scope' — all ScopedForm consumers must declare a FieldSet with html_id='scope'
|
||||
widget=HTMXSelect(hx_target_id='scope'),
|
||||
required=False,
|
||||
label=_('Scope type')
|
||||
)
|
||||
|
|
@ -50,6 +54,18 @@ class ScopedForm(forms.Form):
|
|||
super().__init__(*args, **kwargs)
|
||||
self._set_scoped_values()
|
||||
|
||||
if settings.DEBUG:
|
||||
has_scope_fieldset = any(
|
||||
getattr(fs, 'html_id', None) == 'scope'
|
||||
for fs in getattr(self, 'fieldsets', [])
|
||||
)
|
||||
if not has_scope_fieldset:
|
||||
warnings.warn(
|
||||
f"{self.__class__.__name__} uses ScopedForm but declares no "
|
||||
"FieldSet with html_id='scope'; HTMX partial swap will fail silently.",
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ class ModuleTypeForm(PrimaryModelForm):
|
|||
queryset=ModuleTypeProfile.objects.all(),
|
||||
label=_('Profile'),
|
||||
required=False,
|
||||
widget=HTMXSelect()
|
||||
widget=HTMXSelect(hx_target_id='profile-attributes')
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
|
|
@ -487,7 +487,7 @@ class ModuleTypeForm(PrimaryModelForm):
|
|||
return [
|
||||
FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
||||
FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
|
||||
FieldSet('profile', *self.attr_fields, name=_('Profile & Attributes'))
|
||||
FieldSet('profile', *self.attr_fields, name=_('Profile & Attributes'), html_id='profile-attributes')
|
||||
]
|
||||
|
||||
class Meta:
|
||||
|
|
@ -873,13 +873,13 @@ class CableForm(TenancyForm, PrimaryModelForm):
|
|||
a_terminations_type = forms.ChoiceField(
|
||||
choices=get_termination_type_choices,
|
||||
required=False,
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='cable-side-a'),
|
||||
label=_('Type')
|
||||
)
|
||||
b_terminations_type = forms.ChoiceField(
|
||||
choices=get_termination_type_choices,
|
||||
required=False,
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='cable-side-b'),
|
||||
label=_('Type')
|
||||
)
|
||||
bundle = DynamicModelChoiceField(
|
||||
|
|
@ -1646,7 +1646,8 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
FieldSet(
|
||||
'mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy',
|
||||
name=_('802.1Q Switching')
|
||||
name=_('802.1Q Switching'),
|
||||
html_id='dot1q-switching',
|
||||
),
|
||||
FieldSet(
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||
|
|
@ -1667,7 +1668,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||
'speed': NumberWithOptions(
|
||||
options=InterfaceSpeedChoices
|
||||
),
|
||||
'mode': HTMXSelect(),
|
||||
'mode': HTMXSelect(hx_target_id='dot1q-switching'),
|
||||
}
|
||||
labels = {
|
||||
'mode': '802.1Q Mode',
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ class CustomFieldForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Mimic HTMXSelect()
|
||||
# Mimic HTMXSelect() — no hx_target_id because changing type adds/removes
|
||||
# Validation, Related Object, and Choices fieldsets dynamically.
|
||||
self.fields['type'].widget.attrs.update({
|
||||
'hx-get': '.',
|
||||
'hx-include': '#form_fields',
|
||||
|
|
@ -556,7 +557,7 @@ class EventRuleForm(OwnerMixin, NetBoxModelForm):
|
|||
fieldsets = (
|
||||
FieldSet('name', 'description', 'object_types', 'enabled', 'tags', name=_('Event Rule')),
|
||||
FieldSet('event_types', 'conditions', name=_('Triggers')),
|
||||
FieldSet('action_type', 'action_choice', 'action_data', name=_('Action')),
|
||||
FieldSet('action_type', 'action_choice', 'action_data', name=_('Action'), html_id='event-rule-action'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -567,7 +568,7 @@ class EventRuleForm(OwnerMixin, NetBoxModelForm):
|
|||
)
|
||||
widgets = {
|
||||
'conditions': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
'action_type': HTMXSelect(),
|
||||
'action_type': HTMXSelect(hx_target_id='event-rule-action'),
|
||||
'action_object_type': forms.HiddenInput,
|
||||
'action_object_id': forms.HiddenInput,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
|
|||
FieldSet(
|
||||
'prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
||||
),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
||||
FieldSet('vlan', name=_('VLAN Assignment')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
|
@ -261,7 +261,7 @@ class PrefixBulkAddForm(PrefixForm):
|
|||
FieldSet(
|
||||
'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
||||
),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
||||
FieldSet('vlan', name=_('VLAN Assignment')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
|
@ -625,7 +625,7 @@ class VLANGroupForm(TenancyForm, OrganizationalModelForm):
|
|||
)
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='scope'),
|
||||
required=False,
|
||||
label=_('Scope type')
|
||||
)
|
||||
|
|
@ -640,7 +640,7 @@ class VLANGroupForm(TenancyForm, OrganizationalModelForm):
|
|||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
||||
FieldSet('vid_ranges', name=_('Child VLANs')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
|
|
@ -780,7 +780,7 @@ class ServiceTemplateForm(PrimaryModelForm):
|
|||
class ServiceForm(PrimaryModelForm):
|
||||
parent_object_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS),
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='service'),
|
||||
required=True,
|
||||
label=_('Parent type')
|
||||
)
|
||||
|
|
@ -809,7 +809,8 @@ class ServiceForm(PrimaryModelForm):
|
|||
FieldSet(
|
||||
'parent_object_type', 'parent', 'name',
|
||||
InlineFields('protocol', 'ports', label=_('Port(s)')),
|
||||
'ipaddresses', 'description', 'tags', name=_('Application Service')
|
||||
'ipaddresses', 'description', 'tags', name=_('Application Service'),
|
||||
html_id='service',
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -875,7 +876,8 @@ class ServiceCreateForm(ServiceForm):
|
|||
FieldSet('service_template', name=_('From Template')),
|
||||
FieldSet('name', 'protocol', 'ports', name=_('Custom')),
|
||||
),
|
||||
'ipaddresses', 'description', 'tags', name=_('Application Service')
|
||||
'ipaddresses', 'description', 'tags', name=_('Application Service'),
|
||||
html_id='service',
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
{% endfor %}
|
||||
|
||||
{# A side termination #}
|
||||
<div class="field-group mb-5">
|
||||
<div class="field-group mb-5" id="cable-side-a">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "A Side" %}</h2>
|
||||
</div>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
|
||||
{# B side termination #}
|
||||
<div class="field-group mb-5">
|
||||
<div class="field-group mb-5" id="cable-side-b">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "B Side" %}</h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import random
|
||||
import re
|
||||
import string
|
||||
from functools import cached_property
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
__all__ = (
|
||||
'FieldSet',
|
||||
'InlineFields',
|
||||
|
|
@ -25,10 +28,16 @@ class FieldSet:
|
|||
Parameters:
|
||||
items: An iterable of items to be rendered (one per row)
|
||||
name: The fieldset's name, displayed as a heading (optional)
|
||||
html_id: An HTML id for the rendered fieldset div, enabling HTMX partial swaps (optional).
|
||||
Must be a valid CSS identifier: start with a letter, use only letters, digits, hyphens, underscores.
|
||||
"""
|
||||
def __init__(self, *items, name=None):
|
||||
def __init__(self, *items, name=None, html_id=None):
|
||||
if html_id is not None and settings.DEBUG:
|
||||
if not re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*$', html_id):
|
||||
raise ValueError(f"html_id {html_id!r} is not a valid CSS identifier")
|
||||
self.items = items
|
||||
self.name = name
|
||||
self.html_id = html_id
|
||||
|
||||
|
||||
class InlineFields:
|
||||
|
|
|
|||
|
|
@ -61,15 +61,18 @@ class HTMXSelect(forms.Select):
|
|||
"""
|
||||
Selection widget that will re-generate the HTML form upon the selection of a new option.
|
||||
"""
|
||||
def __init__(self, method='get', hx_url='.', hx_target_id='form_fields', attrs=None, **kwargs):
|
||||
def __init__(self, method='get', hx_url='.', hx_include_id='form_fields', hx_target_id=None, attrs=None, **kwargs):
|
||||
method = method.lower()
|
||||
if method not in ('delete', 'get', 'patch', 'post', 'put'):
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
_attrs = {
|
||||
f'hx-{method}': hx_url,
|
||||
'hx-include': f'#{hx_target_id}',
|
||||
'hx-target': f'#{hx_target_id}',
|
||||
'hx-include': f'#{hx_include_id}',
|
||||
'hx-target': f'#{hx_target_id}' if hx_target_id else f'#{hx_include_id}',
|
||||
}
|
||||
if hx_target_id:
|
||||
_attrs['hx-select'] = f'#{hx_target_id}'
|
||||
_attrs['hx-swap'] = 'outerHTML'
|
||||
if attrs:
|
||||
_attrs.update(attrs)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% load form_helpers %}
|
||||
<div class="field-group mb-5"{% if heading %} role="group" aria-label="{{ heading }}"{% endif %}>
|
||||
<div class="field-group mb-5"{% if html_id %} id="{{ html_id }}"{% endif %}{% if heading %} role="group" aria-label="{{ heading }}"{% endif %}>
|
||||
{% if heading %}
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{{ heading }}</h2>
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ def render_fieldset(form, fieldset):
|
|||
|
||||
return {
|
||||
'heading': fieldset.name,
|
||||
'html_id': fieldset.html_id,
|
||||
'rows': rows,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django import forms
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from dcim.models import Site
|
||||
from netbox.choices import ImportFormatChoices
|
||||
|
|
@ -7,13 +7,14 @@ from utilities.forms.bulk_import import BulkImportForm
|
|||
from utilities.forms.fields.csv import CSVSelectWidget
|
||||
from utilities.forms.fields.dynamic import DynamicChoiceField, DynamicMultipleChoiceField
|
||||
from utilities.forms.forms import BulkRenameForm
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.utils import (
|
||||
expand_alphanumeric_pattern,
|
||||
expand_ipnetwork_pattern,
|
||||
get_capacity_unit_label,
|
||||
get_field_value,
|
||||
)
|
||||
from utilities.forms.widgets.select import AvailableOptions, SelectedOptions
|
||||
from utilities.forms.widgets.select import AvailableOptions, HTMXSelect, SelectedOptions
|
||||
|
||||
|
||||
class ExpandIPNetworkTestCase(TestCase):
|
||||
|
|
@ -625,3 +626,44 @@ class GetCapacityUnitLabelTestCase(TestCase):
|
|||
|
||||
def test_iec_label(self):
|
||||
self.assertEqual(get_capacity_unit_label(1024), 'MiB')
|
||||
|
||||
|
||||
class FieldSetTestCase(TestCase):
|
||||
|
||||
def test_html_id_defaults_to_none(self):
|
||||
fs = FieldSet('field1', 'field2', name='Test')
|
||||
self.assertIsNone(fs.html_id)
|
||||
|
||||
def test_html_id_stored(self):
|
||||
fs = FieldSet('field1', 'field2', name='Test', html_id='my-fieldset')
|
||||
self.assertEqual(fs.html_id, 'my-fieldset')
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_html_id_invalid_css_identifier_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
FieldSet('field1', html_id='123-bad')
|
||||
|
||||
@override_settings(DEBUG=False)
|
||||
def test_html_id_invalid_css_identifier_ignored_outside_debug(self):
|
||||
fs = FieldSet('field1', html_id='123-bad')
|
||||
self.assertEqual(fs.html_id, '123-bad')
|
||||
|
||||
|
||||
class HTMXSelectTestCase(TestCase):
|
||||
|
||||
def test_default_targets_form_fields(self):
|
||||
widget = HTMXSelect()
|
||||
self.assertEqual(widget.attrs['hx-target'], '#form_fields')
|
||||
self.assertEqual(widget.attrs['hx-include'], '#form_fields')
|
||||
self.assertNotIn('hx-select', widget.attrs)
|
||||
self.assertNotIn('hx-swap', widget.attrs)
|
||||
|
||||
def test_hx_target_id_sets_target_select_and_swap(self):
|
||||
widget = HTMXSelect(hx_target_id='my-fieldset')
|
||||
self.assertEqual(widget.attrs['hx-target'], '#my-fieldset')
|
||||
self.assertEqual(widget.attrs['hx-select'], '#my-fieldset')
|
||||
self.assertEqual(widget.attrs['hx-swap'], 'outerHTML')
|
||||
|
||||
def test_hx_target_id_include_stays_on_form_fields(self):
|
||||
widget = HTMXSelect(hx_target_id='my-fieldset')
|
||||
self.assertEqual(widget.attrs['hx-include'], '#form_fields')
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class ClusterForm(TenancyForm, ScopedForm, PrimaryModelForm):
|
|||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'type', 'group', 'status', 'description', 'tags', name=_('Cluster')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
|
|
@ -198,6 +198,8 @@ class VirtualMachineForm(TenancyForm, PrimaryModelForm):
|
|||
label=_('Type'),
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
required=False,
|
||||
# No hx_target_id: type change populates defaults across both the Virtual Machine
|
||||
# and Resources fieldsets, so a single-fieldset partial swap would miss half the update.
|
||||
widget=HTMXSelect(),
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
|
|
@ -453,7 +455,8 @@ class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
|
|||
FieldSet('parent', 'bridge', name=_('Related Interfaces')),
|
||||
FieldSet(
|
||||
'mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy',
|
||||
name=_('802.1Q Switching')
|
||||
name=_('802.1Q Switching'),
|
||||
html_id='dot1q-switching',
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -468,7 +471,7 @@ class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
|
|||
'mode': _('802.1Q Mode'),
|
||||
}
|
||||
widgets = {
|
||||
'mode': HTMXSelect(),
|
||||
'mode': HTMXSelect(hx_target_id='dot1q-switching'),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class TunnelCreateForm(TunnelForm):
|
|||
termination1_type = forms.ChoiceField(
|
||||
choices=TunnelTerminationTypeChoices,
|
||||
required=False,
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='tunnel-termination1'),
|
||||
label=_('Type')
|
||||
)
|
||||
termination1_parent = DynamicModelChoiceField(
|
||||
|
|
@ -113,7 +113,7 @@ class TunnelCreateForm(TunnelForm):
|
|||
termination2_type = forms.ChoiceField(
|
||||
choices=TunnelTerminationTypeChoices,
|
||||
required=False,
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='tunnel-termination2'),
|
||||
label=_('Type')
|
||||
)
|
||||
termination2_parent = DynamicModelChoiceField(
|
||||
|
|
@ -145,10 +145,10 @@ class TunnelCreateForm(TunnelForm):
|
|||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
FieldSet(
|
||||
'termination1_role', 'termination1_type', 'termination1_parent', 'termination1_termination',
|
||||
'termination1_outside_ip', name=_('First Termination')),
|
||||
'termination1_outside_ip', name=_('First Termination'), html_id='tunnel-termination1'),
|
||||
FieldSet(
|
||||
'termination2_role', 'termination2_type', 'termination2_parent', 'termination2_termination',
|
||||
'termination2_outside_ip', name=_('Second Termination')),
|
||||
'termination2_outside_ip', name=_('Second Termination'), html_id='tunnel-termination2'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, initial=None, **kwargs):
|
||||
|
|
@ -225,7 +225,7 @@ class TunnelTerminationForm(NetBoxModelForm):
|
|||
)
|
||||
type = forms.ChoiceField(
|
||||
choices=TunnelTerminationTypeChoices,
|
||||
widget=HTMXSelect(),
|
||||
widget=HTMXSelect(hx_target_id='tunnel-termination'),
|
||||
label=_('Type')
|
||||
)
|
||||
parent = DynamicModelChoiceField(
|
||||
|
|
@ -250,7 +250,10 @@ class TunnelTerminationForm(NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('tunnel', 'role', 'type', 'parent', 'termination', 'outside_ip', 'tags'),
|
||||
FieldSet(
|
||||
'tunnel', 'role', 'type', 'parent', 'termination', 'outside_ip', 'tags',
|
||||
html_id='tunnel-termination',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class WirelessLANForm(ScopedForm, TenancyForm, PrimaryModelForm):
|
|||
|
||||
fieldsets = (
|
||||
FieldSet('ssid', 'group', 'vlan', 'status', 'description', 'tags', name=_('Wireless LAN')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||
FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue