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

Move fieldsets out of Meta for model forms

jeremystretch 4 лет назад
Родитель
Сommit
ccb3a75281

+ 14 - 11
netbox/circuits/forms/models.py

@@ -27,15 +27,16 @@ class ProviderForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Provider', ('name', 'slug', 'asn', 'tags')),
+        ('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
+    )
+
     class Meta:
         model = Provider
         fields = [
             'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
         ]
-        fieldsets = (
-            ('Provider', ('name', 'slug', 'asn', 'tags')),
-            ('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
-        )
         widgets = {
             'noc_contact': SmallTextarea(
                 attrs={'rows': 5}
@@ -63,14 +64,15 @@ class ProviderNetworkForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
+    )
+
     class Meta:
         model = ProviderNetwork
         fields = [
             'provider', 'name', 'service_id', 'description', 'comments', 'tags',
         ]
-        fieldsets = (
-            ('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
-        )
 
 
 class CircuitTypeForm(NetBoxModelForm):
@@ -100,16 +102,17 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = Circuit
         fields = [
             'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
             'comments', 'tags',
         ]
-        fieldsets = (
-            ('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         help_texts = {
             'cid': "Unique circuit ID",
             'commit_rate': "Committed rate",

+ 61 - 53
netbox/dcim/forms/models.py

@@ -134,19 +134,20 @@ class SiteForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Site', (
+            'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
+        )),
+        ('Tenancy', ('tenant_group', 'tenant')),
+        ('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
+    )
+
     class Meta:
         model = Site
         fields = (
             'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
             'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
         )
-        fieldsets = (
-            ('Site', (
-                'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
-            )),
-            ('Tenancy', ('tenant_group', 'tenant')),
-            ('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
-        )
         widgets = {
             'physical_address': SmallTextarea(
                 attrs={
@@ -208,17 +209,18 @@ class LocationForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Location', (
+            'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tags',
+        )),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = Location
         fields = (
             'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tenant_group', 'tenant', 'tags',
         )
-        fieldsets = (
-            ('Location', (
-                'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tags',
-            )),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
 
 
 class RackRoleForm(NetBoxModelForm):
@@ -347,16 +349,17 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = RackReservation
         fields = [
             'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
             'description', 'tags',
         ]
-        fieldsets = (
-            ('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
 
 
 class ManufacturerForm(NetBoxModelForm):
@@ -386,21 +389,22 @@ class DeviceTypeForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Device Type', (
+            'manufacturer', 'model', 'slug', 'part_number', 'tags',
+        )),
+        ('Chassis', (
+            'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
+        )),
+        ('Images', ('front_image', 'rear_image')),
+    )
+
     class Meta:
         model = DeviceType
         fields = [
             'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
             'front_image', 'rear_image', 'comments', 'tags',
         ]
-        fieldsets = (
-            ('Device Type', (
-                'manufacturer', 'model', 'slug', 'part_number', 'tags',
-            )),
-            ('Chassis', (
-                'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
-            )),
-            ('Images', ('front_image', 'rear_image')),
-        )
         widgets = {
             'subdevice_role': StaticSelect(),
             'front_image': ClearableFileInput(attrs={
@@ -745,14 +749,15 @@ class PowerPanelForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
+    )
+
     class Meta:
         model = PowerPanel
         fields = [
             'region', 'site_group', 'site', 'location', 'name', 'tags',
         ]
-        fieldsets = (
-            ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
-        )
 
 
 class PowerFeedForm(NetBoxModelForm):
@@ -800,17 +805,18 @@ class PowerFeedForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Power Panel', ('region', 'site', 'power_panel')),
+        ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
+        ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
+    )
+
     class Meta:
         model = PowerFeed
         fields = [
             'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
             'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
         ]
-        fieldsets = (
-            ('Power Panel', ('region', 'site', 'power_panel')),
-            ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
-            ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
-        )
         widgets = {
             'status': StaticSelect(),
             'type': StaticSelect(),
@@ -1101,16 +1107,17 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
         widget=forms.HiddenInput
     )
 
+    fieldsets = (
+        ('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
+        ('Hardware', ('manufacturer', 'part_id')),
+    )
+
     class Meta:
         model = InventoryItemTemplate
         fields = [
             'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
             'component_type', 'component_id',
         ]
-        fieldsets = (
-            ('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
-            ('Hardware', ('manufacturer', 'part_id')),
-        )
         widgets = {
             'device_type': forms.HiddenInput(),
         }
@@ -1271,6 +1278,17 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
+        ('Addressing', ('vrf', 'mac_address', 'wwn')),
+        ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
+        ('Related Interfaces', ('parent', 'bridge', 'lag')),
+        ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
+        ('Wireless', (
+            'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
+        )),
+    )
+
     class Meta:
         model = Interface
         fields = [
@@ -1278,17 +1296,6 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
             'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
             'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
         ]
-        fieldsets = (
-            ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
-            ('Addressing', ('vrf', 'mac_address', 'wwn')),
-            ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
-            ('Related Interfaces', ('parent', 'bridge', 'lag')),
-            ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
-            ('Wireless', (
-                'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group',
-                'wireless_lans',
-            )),
-        )
         widgets = {
             'device': forms.HiddenInput(),
             'type': StaticSelect(),
@@ -1432,16 +1439,17 @@ class InventoryItemForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
+        ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
+    )
+
     class Meta:
         model = InventoryItem
         fields = [
             'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
             'description', 'component_type', 'component_id', 'tags',
         ]
-        fieldsets = (
-            ('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
-            ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
-        )
         widgets = {
             'device': forms.HiddenInput(),
         }

+ 33 - 28
netbox/extras/forms/models.py

@@ -30,16 +30,17 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
         limit_choices_to=FeatureQuery('custom_fields')
     )
 
+    fieldsets = (
+        ('Custom Field', ('name', 'label', 'type', 'object_type', 'weight', 'required', 'description')),
+        ('Assigned Models', ('content_types',)),
+        ('Behavior', ('filter_logic',)),
+        ('Values', ('default', 'choices')),
+        ('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
+    )
+
     class Meta:
         model = CustomField
         fields = '__all__'
-        fieldsets = (
-            ('Custom Field', ('name', 'label', 'type', 'object_type', 'weight', 'required', 'description')),
-            ('Assigned Models', ('content_types',)),
-            ('Behavior', ('filter_logic',)),
-            ('Values', ('default', 'choices')),
-            ('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
-        )
         widgets = {
             'type': StaticSelect(),
             'filter_logic': StaticSelect(),
@@ -52,13 +53,14 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
         limit_choices_to=FeatureQuery('custom_links')
     )
 
+    fieldsets = (
+        ('Custom Link', ('name', 'content_type', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
+        ('Templates', ('link_text', 'link_url')),
+    )
+
     class Meta:
         model = CustomLink
         fields = '__all__'
-        fieldsets = (
-            ('Custom Link', ('name', 'content_type', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
-            ('Templates', ('link_text', 'link_url')),
-        )
         widgets = {
             'button_class': StaticSelect(),
             'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
@@ -77,14 +79,15 @@ class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
         limit_choices_to=FeatureQuery('export_templates')
     )
 
+    fieldsets = (
+        ('Export Template', ('name', 'content_type', 'description')),
+        ('Template', ('template_code',)),
+        ('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
+    )
+
     class Meta:
         model = ExportTemplate
         fields = '__all__'
-        fieldsets = (
-            ('Export Template', ('name', 'content_type', 'description')),
-            ('Template', ('template_code',)),
-            ('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
-        )
         widgets = {
             'template_code': forms.Textarea(attrs={'class': 'font-monospace'}),
         }
@@ -96,18 +99,19 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
         limit_choices_to=FeatureQuery('webhooks')
     )
 
+    fieldsets = (
+        ('Webhook', ('name', 'content_types', 'enabled')),
+        ('Events', ('type_create', 'type_update', 'type_delete')),
+        ('HTTP Request', (
+            'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
+        )),
+        ('Conditions', ('conditions',)),
+        ('SSL', ('ssl_verification', 'ca_file_path')),
+    )
+
     class Meta:
         model = Webhook
         fields = '__all__'
-        fieldsets = (
-            ('Webhook', ('name', 'content_types', 'enabled')),
-            ('Events', ('type_create', 'type_update', 'type_delete')),
-            ('HTTP Request', (
-                'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
-            )),
-            ('Conditions', ('conditions',)),
-            ('SSL', ('ssl_verification', 'ca_file_path')),
-        )
         labels = {
             'type_create': 'Creations',
             'type_update': 'Updates',
@@ -123,14 +127,15 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
 class TagForm(BootstrapMixin, forms.ModelForm):
     slug = SlugField()
 
+    fieldsets = (
+        ('Tag', ('name', 'slug', 'color', 'description')),
+    )
+
     class Meta:
         model = Tag
         fields = [
             'name', 'slug', 'color', 'description'
         ]
-        fieldsets = (
-            ('Tag', ('name', 'slug', 'color', 'description')),
-        )
 
 
 class ConfigContextForm(BootstrapMixin, forms.ModelForm):

+ 44 - 36
netbox/ipam/forms/models.py

@@ -53,17 +53,18 @@ class VRFForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
+        ('Route Targets', ('import_targets', 'export_targets')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = VRF
         fields = [
             'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
             'tags',
         ]
-        fieldsets = (
-            ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
-            ('Route Targets', ('import_targets', 'export_targets')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         labels = {
             'rd': "RD",
         }
@@ -78,15 +79,16 @@ class RouteTargetForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Route Target', ('name', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = RouteTarget
         fields = [
             'name', 'description', 'tenant_group', 'tenant', 'tags',
         ]
-        fieldsets = (
-            ('Route Target', ('name', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
 
 
 class RIRForm(NetBoxModelForm):
@@ -113,15 +115,16 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = Aggregate
         fields = [
             'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
         ]
-        fieldsets = (
-            ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         help_texts = {
             'prefix': "IPv4 or IPv6 network",
             'rir': "Regional Internet Registry responsible for this prefix",
@@ -146,15 +149,16 @@ class ASNForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = ASN
         fields = [
             'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
         ]
-        fieldsets = (
-            ('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         help_texts = {
             'asn': "AS number",
             'rir': "Regional Internet Registry responsible for this prefix",
@@ -248,17 +252,18 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
+        ('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = Prefix
         fields = [
             'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
             'tenant_group', 'tenant', 'tags',
         ]
-        fieldsets = (
-            ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
-            ('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         widgets = {
             'status': StaticSelect(),
         }
@@ -279,15 +284,16 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = IPRange
         fields = [
             'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
         ]
-        fieldsets = (
-            ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
         widgets = {
             'status': StaticSelect(),
         }
@@ -562,16 +568,17 @@ class FHRPGroupForm(NetBoxModelForm):
         label='Status'
     )
 
+    fieldsets = (
+        ('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
+        ('Authentication', ('auth_type', 'auth_key')),
+        ('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
+    )
+
     class Meta:
         model = FHRPGroup
         fields = (
             'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
         )
-        fieldsets = (
-            ('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
-            ('Authentication', ('auth_type', 'auth_key')),
-            ('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
-        )
 
     def save(self, *args, **kwargs):
         instance = super().save(*args, **kwargs)
@@ -699,17 +706,18 @@ class VLANGroupForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('VLAN Group', ('name', 'slug', 'description', 'tags')),
+        ('Child VLANs', ('min_vid', 'max_vid')),
+        ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
+    )
+
     class Meta:
         model = VLANGroup
         fields = [
             'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
             'clustergroup', 'cluster', 'min_vid', 'max_vid', 'tags',
         ]
-        fieldsets = (
-            ('VLAN Group', ('name', 'slug', 'description', 'tags')),
-            ('Child VLANs', ('min_vid', 'max_vid')),
-            ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
-        )
         widgets = {
             'scope_type': StaticSelect,
         }

+ 6 - 0
netbox/netbox/forms/base.py

@@ -19,7 +19,13 @@ __all__ = (
 class NetBoxModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
     """
     Base form for creating & editing NetBox models. Adds support for custom fields.
+
+    Attributes:
+        fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
+            the rendered form (optional). If not defined, the all fields will be rendered as a single section.
     """
+    fieldsets = ()
+
     def _get_content_type(self):
         return ContentType.objects.get_for_model(self._meta.model)
 

+ 2 - 2
netbox/templates/generic/object_edit.html

@@ -33,7 +33,7 @@
         {% csrf_token %}
 
         {% block form %}
-          {% if form.Meta.fieldsets %}
+          {% if form.fieldsets %}
 
             {# Render hidden fields #}
             {% for field in form.hidden_fields %}
@@ -41,7 +41,7 @@
             {% endfor %}
 
             {# Render grouped fields according to Form #}
-            {% for group, fields in form.Meta.fieldsets %}
+            {% for group, fields in form.fieldsets %}
               <div class="field-group mb-5">
                 <div class="row mb-2">
                   <h5 class="offset-sm-3">{{ group }}</h5>

+ 1 - 1
netbox/templates/users/preferences.html

@@ -8,7 +8,7 @@
   <form method="post" action="" id="preferences-update">
     {% csrf_token %}
 
-    {% for group, fields in form.Meta.fieldsets %}
+    {% for group, fields in form.fieldsets %}
       <div class="field-group my-5">
         <div class="row mb-2">
           <h5 class="offset-sm-3">{{ group }}</h5>

+ 8 - 6
netbox/tenancy/forms/models.py

@@ -52,14 +52,15 @@ class TenantForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
+    )
+
     class Meta:
         model = Tenant
         fields = (
             'name', 'slug', 'group', 'description', 'comments', 'tags',
         )
-        fieldsets = (
-            ('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
-        )
 
 
 #
@@ -105,14 +106,15 @@ class ContactForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'tags')),
+    )
+
     class Meta:
         model = Contact
         fields = (
             'group', 'name', 'title', 'phone', 'email', 'address', 'comments', 'tags',
         )
-        fieldsets = (
-            ('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'tags')),
-        )
         widgets = {
             'address': SmallTextarea(attrs={'rows': 3}),
         }

+ 10 - 10
netbox/users/forms.py

@@ -40,20 +40,20 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
 
 
 class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMetaclass):
+    fieldsets = (
+        ('User Interface', (
+            'pagination.per_page',
+            'pagination.placement',
+            'ui.colormode',
+        )),
+        ('Miscellaneous', (
+            'data_format',
+        )),
+    )
 
     class Meta:
         model = UserConfig
         fields = ()
-        fieldsets = (
-            ('User Interface', (
-                'pagination.per_page',
-                'pagination.placement',
-                'ui.colormode',
-            )),
-            ('Miscellaneous', (
-                'data_format',
-            )),
-        )
 
     def __init__(self, *args, instance=None, **kwargs):
 

+ 14 - 12
netbox/virtualization/forms/models.py

@@ -90,15 +90,16 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+    )
+
     class Meta:
         model = Cluster
         fields = (
             'name', 'type', 'group', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
         )
-        fieldsets = (
-            ('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
 
 
 class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
@@ -206,20 +207,21 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Virtual Machine', ('name', 'role', 'status', 'tags')),
+        ('Cluster', ('cluster_group', 'cluster')),
+        ('Tenancy', ('tenant_group', 'tenant')),
+        ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
+        ('Resources', ('vcpus', 'memory', 'disk')),
+        ('Config Context', ('local_context_data',)),
+    )
+
     class Meta:
         model = VirtualMachine
         fields = [
             'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
             'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
         ]
-        fieldsets = (
-            ('Virtual Machine', ('name', 'role', 'status', 'tags')),
-            ('Cluster', ('cluster_group', 'cluster')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-            ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
-            ('Resources', ('vcpus', 'memory', 'disk')),
-            ('Config Context', ('local_context_data',)),
-        )
         help_texts = {
             'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
                                   "config context",

+ 13 - 11
netbox/wireless/forms/models.py

@@ -45,16 +45,17 @@ class WirelessLANForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
+        ('VLAN', ('vlan',)),
+        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+    )
+
     class Meta:
         model = WirelessLAN
         fields = [
             'ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
         ]
-        fieldsets = (
-            ('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
-            ('VLAN', ('vlan',)),
-            ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
-        )
         widgets = {
             'auth_type': StaticSelect,
             'auth_cipher': StaticSelect,
@@ -141,18 +142,19 @@ class WirelessLinkForm(NetBoxModelForm):
         required=False
     )
 
+    fieldsets = (
+        ('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
+        ('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
+        ('Link', ('status', 'ssid', 'description', 'tags')),
+        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+    )
+
     class Meta:
         model = WirelessLink
         fields = [
             'site_a', 'location_a', 'device_a', 'interface_a', 'site_b', 'location_b', 'device_b', 'interface_b',
             'status', 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
         ]
-        fieldsets = (
-            ('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
-            ('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
-            ('Link', ('status', 'ssid', 'description', 'tags')),
-            ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
-        )
         widgets = {
             'status': StaticSelect,
             'auth_type': StaticSelect,