Browse Source

Refactor form rendering components & add docstrings

Jeremy Stretch 1 year ago
parent
commit
3b28e8e615

+ 2 - 2
netbox/circuits/forms/model_forms.py

@@ -7,7 +7,7 @@ from ipam.models import ASN
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 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
 
 __all__ = (
@@ -153,7 +153,7 @@ class CircuitTerminationForm(NetBoxModelForm):
             'term_side',
             'description',
             'tags',
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('Site'), 'site'),
                 (_('Provider Network'), 'provider_network'),
             ),

+ 4 - 4
netbox/dcim/forms/model_forms.py

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

+ 6 - 6
netbox/ipam/forms/model_forms.py

@@ -16,7 +16,7 @@ from utilities.forms.fields import (
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     SlugField,
 )
-from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedFieldGroups
+from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedGroups
 from utilities.forms.widgets import DatePicker
 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')),
         (_('Tenancy'), ('tenant_group', 'tenant')),
         (_('Assignment'), (
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('Device'), 'interface'),
                 (_('Virtual Machine'), 'vminterface'),
                 (_('FHRP Group'), 'fhrpgroup'),
@@ -725,12 +725,12 @@ class ServiceForm(NetBoxModelForm):
 
     fieldsets = (
         (_('Service'), (
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('Device'), 'device'),
                 (_('Virtual Machine'), 'virtual_machine'),
             ),
             'name',
-            InlineFields(_('Port(s)'), 'protocol', 'ports'),
+            InlineFields('protocol', 'ports', label=_('Port(s)')),
             'ipaddresses',
             'description',
             'tags',
@@ -753,11 +753,11 @@ class ServiceCreateForm(ServiceForm):
 
     fieldsets = (
         (_('Service'), (
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('Device'), 'device'),
                 (_('Virtual Machine'), 'virtual_machine'),
             ),
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('From Template'), 'service_template'),
                 (_('Custom'), 'name', 'protocol', 'ports'),
             ),

+ 2 - 2
netbox/templates/htmx/form.html

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

+ 27 - 14
netbox/utilities/forms/rendering.py

@@ -3,28 +3,39 @@ import string
 from functools import cached_property
 
 __all__ = (
+    'FieldSet',
     'InlineFields',
     'ObjectAttribute',
-    'TabbedFieldGroups',
+    'TabbedGroups',
 )
 
 
-class FieldGroup:
-
-    def __init__(self, label, *field_names):
-        self.field_names = field_names
-        self.label = label
-
+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
 
-class InlineFields(FieldGroup):
-    pass
 
+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
 
-class TabbedFieldGroups:
 
+class TabbedGroups:
+    """
+    Two or more groups of fields (FieldSets) arranged under tabs among which the user can navigate.
+    """
     def __init__(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)
@@ -37,13 +48,15 @@ class TabbedFieldGroups:
         return [
             {
                 'id': f'{self.id}_{i}',
-                'title': group.label,
-                'fields': group.field_names,
+                'title': group.name,
+                'fields': group.fields,
             } for i, group in enumerate(self.groups, start=1)
         ]
 
 
 class ObjectAttribute:
-
+    """
+    Renders the value for a specific attribute on the form's instance.
+    """
     def __init__(self, name):
         self.name = name

+ 11 - 6
netbox/utilities/templatetags/form_helpers.py

@@ -1,6 +1,6 @@
 from django import template
 
-from utilities.forms.rendering import InlineFields, ObjectAttribute, TabbedFieldGroups
+from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
 
 __all__ = (
     'getfield',
@@ -48,24 +48,29 @@ def widget_type(field):
 #
 
 @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.
     """
+    # 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 = []
-    for item in fieldset:
+    for item in fieldset.fields:
 
         # Multiple fields side-by-side
         if type(item) is InlineFields:
             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(
                 ('inline', item.label, fields)
             )
 
         # Tabbed groups of fields
-        elif type(item) is TabbedFieldGroups:
+        elif type(item) is TabbedGroups:
             tabs = [
                 {
                     'id': tab['id'],
@@ -95,7 +100,7 @@ def render_fieldset(form, fieldset, heading=None):
             )
 
     return {
-        'heading': heading,
+        'heading': fieldset.name,
         'rows': rows,
     }
 

+ 2 - 2
netbox/vpn/forms/model_forms.py

@@ -7,7 +7,7 @@ from ipam.models import IPAddress, RouteTarget, VLAN
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 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.widgets import HTMXSelect
 from virtualization.models import VirtualMachine, VMInterface
@@ -448,7 +448,7 @@ class L2VPNTerminationForm(NetBoxModelForm):
     fieldsets = (
         (None, (
             'l2vpn',
-            TabbedFieldGroups(
+            TabbedGroups(
                 (_('VLAN'), 'vlan'),
                 (_('Device'), 'interface'),
                 (_('Virtual Machine'), 'vminterface'),