mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-26 18:38:38 -06:00
Enable dynamic form field rendering
This commit is contained in:
parent
a67ea1305e
commit
5f707320ea
@ -18,6 +18,7 @@ from utilities.forms.fields import (
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
|
||||
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
|
||||
from utilities.jsonschema import JSONSchemaProperty
|
||||
from virtualization.models import Cluster, VMInterface
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
from .common import InterfaceCommonForm, ModuleCommonForm
|
||||
@ -423,10 +424,11 @@ class ModuleTypeProfileForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class ModuleTypeForm(NetBoxModelForm):
|
||||
profile = DynamicModelChoiceField(
|
||||
label=_('Profile'),
|
||||
profile = forms.ModelChoiceField(
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
required=False
|
||||
label=_('Profile'),
|
||||
required=False,
|
||||
widget=HTMXSelect()
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
@ -434,19 +436,70 @@ class ModuleTypeForm(NetBoxModelForm):
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('profile', 'manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
||||
FieldSet('attribute_data', name=_('Profile Attributes')),
|
||||
FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
|
||||
)
|
||||
@property
|
||||
def fieldsets(self):
|
||||
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'))
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
|
||||
'attribute_data', 'comments', 'tags',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Track profile-specific attribute fields
|
||||
self.attr_fields = []
|
||||
|
||||
# Retrieve assigned ModuleTypeProfile, if any
|
||||
if not (profile_id := get_field_value(self, 'profile')):
|
||||
return
|
||||
if not (profile := ModuleTypeProfile.objects.filter(pk=profile_id).first()):
|
||||
return
|
||||
|
||||
# Extend form with fields for profile attributes
|
||||
for attr, form_field in self._get_attr_form_fields(profile).items():
|
||||
field_name = f'attr_{attr}'
|
||||
self.attr_fields.append(field_name)
|
||||
self.fields[field_name] = form_field
|
||||
if self.instance.attribute_data:
|
||||
self.fields[field_name].initial = self.instance.attribute_data.get(attr)
|
||||
|
||||
@staticmethod
|
||||
def _get_attr_form_fields(profile):
|
||||
"""
|
||||
Return a dictionary mapping of attribute names to form fields, suitable for extending
|
||||
the form per the selected ModuleTypeProfile.
|
||||
"""
|
||||
if not profile.schema:
|
||||
return {}
|
||||
|
||||
properties = profile.schema.get('properties', {})
|
||||
required_fields = profile.schema.get('required', [])
|
||||
|
||||
attr_fields = {}
|
||||
for name, options in properties.items():
|
||||
prop = JSONSchemaProperty(**options)
|
||||
attr_fields[name] = prop.to_form_field(name, required=name in required_fields)
|
||||
|
||||
return attr_fields
|
||||
|
||||
def _post_clean(self):
|
||||
|
||||
# Compile attribute data from the individual form fields
|
||||
self.instance.attribute_data = {
|
||||
name[5:]: self.cleaned_data[name] # Remove the attr_ prefix
|
||||
for name in self.attr_fields if self.cleaned_data[name] is not None
|
||||
}
|
||||
|
||||
return super()._post_clean()
|
||||
|
||||
|
||||
class DeviceRoleForm(NetBoxModelForm):
|
||||
config_template = DynamicModelChoiceField(
|
||||
|
@ -143,9 +143,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
||||
try:
|
||||
jsonschema.validate(self.attribute_data, schema=self.profile.schema)
|
||||
except JSONValidationError as e:
|
||||
raise ValidationError({
|
||||
'attributes': _("Invalid schema: {error}").format(error=e)
|
||||
})
|
||||
raise ValidationError(_("Invalid schema: {error}").format(error=e))
|
||||
else:
|
||||
self.attribute_data = None
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user