1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Refactor form rendering components & add docstrings

This commit is contained in:
Jeremy Stretch
2024-03-15 12:59:42 -04:00
parent 2aaa552067
commit 3b28e8e615
7 changed files with 54 additions and 36 deletions

View File

@ -7,7 +7,7 @@ from ipam.models import ASN
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
from utilities.forms.rendering import TabbedFieldGroups from utilities.forms.rendering import TabbedGroups
from utilities.forms.widgets import DatePicker, NumberWithOptions from utilities.forms.widgets import DatePicker, NumberWithOptions
__all__ = ( __all__ = (
@ -153,7 +153,7 @@ class CircuitTerminationForm(NetBoxModelForm):
'term_side', 'term_side',
'description', 'description',
'tags', 'tags',
TabbedFieldGroups( TabbedGroups(
(_('Site'), 'site'), (_('Site'), 'site'),
(_('Provider Network'), 'provider_network'), (_('Provider Network'), 'provider_network'),
), ),

View File

@ -16,7 +16,7 @@ from utilities.forms.fields import (
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
NumericArrayField, SlugField, NumericArrayField, SlugField,
) )
from utilities.forms.rendering import InlineFields, TabbedFieldGroups from utilities.forms.rendering import InlineFields, TabbedGroups
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
from virtualization.models import Cluster from virtualization.models import Cluster
from wireless.models import WirelessLAN, WirelessLANGroup from wireless.models import WirelessLAN, WirelessLANGroup
@ -237,8 +237,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
'width', 'width',
'starting_unit', 'starting_unit',
'u_height', 'u_height',
InlineFields(_('Outer Dimensions'), 'outer_width', 'outer_depth', 'outer_unit'), InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
InlineFields(_('Weight'), 'weight', 'max_weight', 'weight_unit'), InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
'mounting_depth', 'mounting_depth',
'desc_units', 'desc_units',
)), )),
@ -1415,7 +1415,7 @@ class InventoryItemForm(DeviceComponentForm):
(_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')), (_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
(_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')), (_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
(_('Component Assignment'), ( (_('Component Assignment'), (
TabbedFieldGroups( TabbedGroups(
(_('Interface'), 'interface'), (_('Interface'), 'interface'),
(_('Console Port'), 'consoleport'), (_('Console Port'), 'consoleport'),
(_('Console Server Port'), 'consoleserverport'), (_('Console Server Port'), 'consoleserverport'),

View File

@ -16,7 +16,7 @@ from utilities.forms.fields import (
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
SlugField, SlugField,
) )
from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedFieldGroups from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedGroups
from utilities.forms.widgets import DatePicker from utilities.forms.widgets import DatePicker
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
@ -312,7 +312,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
(_('IP Address'), ('address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags')), (_('IP Address'), ('address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags')),
(_('Tenancy'), ('tenant_group', 'tenant')), (_('Tenancy'), ('tenant_group', 'tenant')),
(_('Assignment'), ( (_('Assignment'), (
TabbedFieldGroups( TabbedGroups(
(_('Device'), 'interface'), (_('Device'), 'interface'),
(_('Virtual Machine'), 'vminterface'), (_('Virtual Machine'), 'vminterface'),
(_('FHRP Group'), 'fhrpgroup'), (_('FHRP Group'), 'fhrpgroup'),
@ -725,12 +725,12 @@ class ServiceForm(NetBoxModelForm):
fieldsets = ( fieldsets = (
(_('Service'), ( (_('Service'), (
TabbedFieldGroups( TabbedGroups(
(_('Device'), 'device'), (_('Device'), 'device'),
(_('Virtual Machine'), 'virtual_machine'), (_('Virtual Machine'), 'virtual_machine'),
), ),
'name', 'name',
InlineFields(_('Port(s)'), 'protocol', 'ports'), InlineFields('protocol', 'ports', label=_('Port(s)')),
'ipaddresses', 'ipaddresses',
'description', 'description',
'tags', 'tags',
@ -753,11 +753,11 @@ class ServiceCreateForm(ServiceForm):
fieldsets = ( fieldsets = (
(_('Service'), ( (_('Service'), (
TabbedFieldGroups( TabbedGroups(
(_('Device'), 'device'), (_('Device'), 'device'),
(_('Virtual Machine'), 'virtual_machine'), (_('Virtual Machine'), 'virtual_machine'),
), ),
TabbedFieldGroups( TabbedGroups(
(_('From Template'), 'service_template'), (_('From Template'), 'service_template'),
(_('Custom'), 'name', 'protocol', 'ports'), (_('Custom'), 'name', 'protocol', 'ports'),
), ),

View File

@ -9,8 +9,8 @@
{% endfor %} {% endfor %}
{# Render grouped fields according to Form #} {# Render grouped fields according to Form #}
{% for group, items in form.fieldsets %} {% for fieldset in form.fieldsets %}
{% render_fieldset form items heading=group %} {% render_fieldset form fieldset %}
{% endfor %} {% endfor %}
{% if form.custom_fields %} {% if form.custom_fields %}

View File

@ -3,28 +3,39 @@ import string
from functools import cached_property from functools import cached_property
__all__ = ( __all__ = (
'FieldSet',
'InlineFields', 'InlineFields',
'ObjectAttribute', 'ObjectAttribute',
'TabbedFieldGroups', 'TabbedGroups',
) )
class FieldGroup: class FieldSet:
"""
A generic grouping of fields, with an optional name. Each field will be rendered
on its own row under the heading (name).
"""
def __init__(self, *fields, name=None):
self.fields = fields
self.name = name
def __init__(self, label, *field_names):
self.field_names = field_names class InlineFields:
"""
A set of fields rendered inline (side-by-side) with a shared label; typically nested within a FieldSet.
"""
def __init__(self, *fields, label=None):
self.fields = fields
self.label = label self.label = label
class InlineFields(FieldGroup): class TabbedGroups:
pass """
Two or more groups of fields (FieldSets) arranged under tabs among which the user can navigate.
"""
class TabbedFieldGroups:
def __init__(self, *groups): def __init__(self, *groups):
self.groups = [ self.groups = [
FieldGroup(*group) for group in groups FieldSet(*group, name=name) for name, *group in groups
] ]
# Initialize a random ID for the group (for tab selection) # Initialize a random ID for the group (for tab selection)
@ -37,13 +48,15 @@ class TabbedFieldGroups:
return [ return [
{ {
'id': f'{self.id}_{i}', 'id': f'{self.id}_{i}',
'title': group.label, 'title': group.name,
'fields': group.field_names, 'fields': group.fields,
} for i, group in enumerate(self.groups, start=1) } for i, group in enumerate(self.groups, start=1)
] ]
class ObjectAttribute: class ObjectAttribute:
"""
Renders the value for a specific attribute on the form's instance.
"""
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name

View File

@ -1,6 +1,6 @@
from django import template from django import template
from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedFieldGroups from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
__all__ = ( __all__ = (
'getfield', 'getfield',
@ -48,24 +48,29 @@ def widget_type(field):
# #
@register.inclusion_tag('form_helpers/render_fieldset.html') @register.inclusion_tag('form_helpers/render_fieldset.html')
def render_fieldset(form, fieldset, heading=None): def render_fieldset(form, fieldset):
""" """
Render a group set of fields. Render a group set of fields.
""" """
# Handle legacy tuple-based fieldset definitions, e.g. (_('Label'), ('field1, 'field2', 'field3'))
if type(fieldset) is not FieldSet:
name, fields = fieldset
fieldset = FieldSet(*fields, name=name)
rows = [] rows = []
for item in fieldset: for item in fieldset.fields:
# Multiple fields side-by-side # Multiple fields side-by-side
if type(item) is InlineFields: if type(item) is InlineFields:
fields = [ fields = [
form[name] for name in item.field_names if name in form.fields form[name] for name in item.fields if name in form.fields
] ]
rows.append( rows.append(
('inline', item.label, fields) ('inline', item.label, fields)
) )
# Tabbed groups of fields # Tabbed groups of fields
elif type(item) is TabbedFieldGroups: elif type(item) is TabbedGroups:
tabs = [ tabs = [
{ {
'id': tab['id'], 'id': tab['id'],
@ -95,7 +100,7 @@ def render_fieldset(form, fieldset, heading=None):
) )
return { return {
'heading': heading, 'heading': fieldset.name,
'rows': rows, 'rows': rows,
} }

View File

@ -7,7 +7,7 @@ from ipam.models import IPAddress, RouteTarget, VLAN
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
from utilities.forms.rendering import TabbedFieldGroups from utilities.forms.rendering import TabbedGroups
from utilities.forms.utils import add_blank_choice, get_field_value from utilities.forms.utils import add_blank_choice, get_field_value
from utilities.forms.widgets import HTMXSelect from utilities.forms.widgets import HTMXSelect
from virtualization.models import VirtualMachine, VMInterface from virtualization.models import VirtualMachine, VMInterface
@ -448,7 +448,7 @@ class L2VPNTerminationForm(NetBoxModelForm):
fieldsets = ( fieldsets = (
(None, ( (None, (
'l2vpn', 'l2vpn',
TabbedFieldGroups( TabbedGroups(
(_('VLAN'), 'vlan'), (_('VLAN'), 'vlan'),
(_('Device'), 'interface'), (_('Device'), 'interface'),
(_('Virtual Machine'), 'vminterface'), (_('Virtual Machine'), 'vminterface'),