Просмотр исходного кода

Refactor form rendering components & add docstrings

Jeremy Stretch 1 год назад
Родитель
Сommit
3b28e8e615

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

@@ -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'),
             ),
             ),

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

@@ -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(_('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',
             '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'),

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

@@ -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'),
             ),
             ),

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

@@ -9,8 +9,8 @@
   {% endfor %}
   {% endfor %}
 
 
   {# Render grouped fields according to Form #}
   {# 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 %}
   {% endfor %}
 
 
   {% if form.custom_fields %}
   {% if form.custom_fields %}

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

@@ -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:
-
-    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):
     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,
-                'fields': group.field_names,
+                'title': group.name,
+                '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

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

@@ -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,
     }
     }
 
 

+ 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 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'),