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

Replace filter_groups with fieldsets on filter forms

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

+ 15 - 15
netbox/circuits/forms/filtersets.py

@@ -18,11 +18,11 @@ __all__ = (
 
 class ProviderFilterForm(NetBoxModelFilterSetForm):
     model = Provider
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['asn'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('ASN', ('asn',)),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -51,9 +51,9 @@ class ProviderFilterForm(NetBoxModelFilterSetForm):
 
 class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
     model = ProviderNetwork
-    field_groups = (
-        ('q', 'tag'),
-        ('provider_id',),
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('provider_id', 'service_id')),
     )
     provider_id = DynamicModelMultipleChoiceField(
         queryset=Provider.objects.all(),
@@ -74,13 +74,13 @@ class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
 
 class CircuitFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Circuit
-    field_groups = [
-        ['q', 'tag'],
-        ['provider_id', 'provider_network_id'],
-        ['type_id', 'status', 'commit_rate'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Provider', ('provider_id', 'provider_network_id')),
+        ('Attributes', ('type_id', 'status', 'commit_rate')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     type_id = DynamicModelMultipleChoiceField(
         queryset=CircuitType.objects.all(),
         required=False,

+ 125 - 122
netbox/dcim/forms/filtersets.py

@@ -126,12 +126,11 @@ class SiteGroupFilterForm(NetBoxModelFilterSetForm):
 
 class SiteFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Site
-    field_groups = [
-        ['q', 'tag'],
-        ['status', 'region_id', 'group_id'],
-        ['tenant_group_id', 'tenant_id'],
-        ['asn_id']
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     status = forms.MultipleChoiceField(
         choices=SiteStatusChoices,
         required=False,
@@ -157,11 +156,11 @@ class SiteFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class LocationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Location
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id', 'parent_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Parent', ('region_id', 'site_group_id', 'site_id', 'parent_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -200,13 +199,13 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm):
 
 class RackFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Rack
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_id', 'location_id'],
-        ['status', 'role_id'],
-        ['type', 'width', 'serial', 'asset_tag'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_id', 'location_id')),
+        ('Function', ('status', 'role_id')),
+        ('Hardware', ('type', 'width', 'serial', 'asset_tag')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -273,12 +272,12 @@ class RackElevationFilterForm(RackFilterForm):
 
 class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RackReservation
-    field_groups = [
-        ['q', 'tag'],
-        ['user_id'],
-        ['region_id', 'site_id', 'location_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('User', ('user_id',)),
+        ('Rack', ('region_id', 'site_id', 'location_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -316,11 +315,14 @@ class ManufacturerFilterForm(NetBoxModelFilterSetForm):
 
 class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
     model = DeviceType
-    field_groups = [
-        ['q', 'tag'],
-        ['manufacturer_id', 'part_number', 'subdevice_role', 'airflow'],
-        ['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
+        ('Components', (
+            'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
+            'pass_through_ports',
+        )),
+    )
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         required=False,
@@ -386,11 +388,14 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
 
 class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
     model = ModuleType
-    field_groups = [
-        ['q', 'tag'],
-        ['manufacturer_id', 'part_number'],
-        ['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Hardware', ('manufacturer_id', 'part_number')),
+        ('Components', (
+            'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
+            'pass_through_ports',
+        )),
+    )
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         required=False,
@@ -462,17 +467,17 @@ class PlatformFilterForm(NetBoxModelFilterSetForm):
 
 class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Device
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id'],
-        ['status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address'],
-        ['manufacturer_id', 'device_type_id', 'platform_id'],
-        ['tenant_group_id', 'tenant_id'],
-        [
-            'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports',
-            'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data',
-        ],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        ('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
+        ('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        ('Components', (
+            'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
+        )),
+        ('Miscellaneous', ('has_primary_ip', 'virtual_chassis_member', 'local_context_data'))
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -616,11 +621,10 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
 
 class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Module
-    field_groups = [
-        ['q', 'tag'],
-        ['manufacturer_id', 'module_type_id'],
-        ['serial', 'asset_tag'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')),
+    )
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         required=False,
@@ -647,11 +651,11 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
 
 class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VirtualChassis
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -676,12 +680,12 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Cable
-    field_groups = [
-        ['q', 'tag'],
-        ['site_id', 'rack_id', 'device_id'],
-        ['type', 'status', 'color', 'length', 'length_unit'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('site_id', 'rack_id', 'device_id')),
+        ('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -739,9 +743,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class PowerPanelFilterForm(NetBoxModelFilterSetForm):
     model = PowerPanel
-    field_groups = (
-        ('q', 'tag'),
-        ('region_id', 'site_group_id', 'site_id', 'location_id')
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id'))
     )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
@@ -776,12 +780,11 @@ class PowerPanelFilterForm(NetBoxModelFilterSetForm):
 
 class PowerFeedFilterForm(NetBoxModelFilterSetForm):
     model = PowerFeed
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['power_panel_id', 'rack_id'],
-        ['status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
+        ('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -856,11 +859,11 @@ class PowerFeedFilterForm(NetBoxModelFilterSetForm):
 
 class ConsolePortFilterForm(DeviceComponentFilterForm):
     model = ConsolePort
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type', 'speed'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type', 'speed')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     type = forms.MultipleChoiceField(
         choices=ConsolePortTypeChoices,
         required=False,
@@ -876,11 +879,11 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
 
 class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
     model = ConsoleServerPort
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type', 'speed'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type', 'speed')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     type = forms.MultipleChoiceField(
         choices=ConsolePortTypeChoices,
         required=False,
@@ -896,11 +899,11 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
 
 class PowerPortFilterForm(DeviceComponentFilterForm):
     model = PowerPort
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     type = forms.MultipleChoiceField(
         choices=PowerPortTypeChoices,
         required=False,
@@ -911,11 +914,11 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
 
 class PowerOutletFilterForm(DeviceComponentFilterForm):
     model = PowerOutlet
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     type = forms.MultipleChoiceField(
         choices=PowerOutletTypeChoices,
         required=False,
@@ -926,13 +929,13 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
 
 class InterfaceFilterForm(DeviceComponentFilterForm):
     model = Interface
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only'],
-        ['vrf_id', 'mac_address', 'wwn'],
-        ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
+        ('Addressing', ('vrf_id', 'mac_address', 'wwn')),
+        ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     kind = forms.MultipleChoiceField(
         choices=InterfaceKindChoices,
         required=False,
@@ -1009,11 +1012,11 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
 
 
 class FrontPortFilterForm(DeviceComponentFilterForm):
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type', 'color'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type', 'color')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     model = FrontPort
     type = forms.MultipleChoiceField(
         choices=PortTypeChoices,
@@ -1028,11 +1031,11 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
 
 class RearPortFilterForm(DeviceComponentFilterForm):
     model = RearPort
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'type', 'color'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'type', 'color')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     type = forms.MultipleChoiceField(
         choices=PortTypeChoices,
         required=False,
@@ -1046,11 +1049,11 @@ class RearPortFilterForm(DeviceComponentFilterForm):
 
 class ModuleBayFilterForm(DeviceComponentFilterForm):
     model = ModuleBay
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'position'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'position')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     tag = TagFilterField(model)
     position = forms.CharField(
         required=False
@@ -1059,21 +1062,21 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
 
 class DeviceBayFilterForm(DeviceComponentFilterForm):
     model = DeviceBay
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     tag = TagFilterField(model)
 
 
 class InventoryItemFilterForm(DeviceComponentFilterForm):
     model = InventoryItem
-    field_groups = [
-        ['q', 'tag'],
-        ['name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered'],
-        ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
+        ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
+    )
     role_id = DynamicModelMultipleChoiceField(
         queryset=InventoryItemRole.objects.all(),
         required=False,

+ 34 - 35
netbox/extras/forms/filtersets.py

@@ -28,11 +28,10 @@ __all__ = (
 
 
 class CustomFieldFilterForm(FilterForm):
-    field_groups = [
-        ['q'],
-        ['type', 'content_types'],
-        ['weight', 'required'],
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Attributes', ('type', 'content_types', 'weight', 'required')),
+    )
     content_types = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
@@ -56,10 +55,10 @@ class CustomFieldFilterForm(FilterForm):
 
 
 class CustomLinkFilterForm(FilterForm):
-    field_groups = [
-        ['q'],
-        ['content_type', 'enabled', 'new_window', 'weight'],
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Attributes', ('content_type', 'enabled', 'new_window', 'weight')),
+    )
     content_type = ContentTypeChoiceField(
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
@@ -83,10 +82,10 @@ class CustomLinkFilterForm(FilterForm):
 
 
 class ExportTemplateFilterForm(FilterForm):
-    field_groups = [
-        ['q'],
-        ['content_type', 'mime_type', 'file_extension', 'as_attachment'],
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Attributes', ('content_type', 'mime_type', 'file_extension', 'as_attachment')),
+    )
     content_type = ContentTypeChoiceField(
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
@@ -108,11 +107,11 @@ class ExportTemplateFilterForm(FilterForm):
 
 
 class WebhookFilterForm(FilterForm):
-    field_groups = [
-        ['q'],
-        ['content_types', 'http_method', 'enabled'],
-        ['type_create', 'type_update', 'type_delete'],
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Attributes', ('content_types', 'http_method', 'enabled')),
+        ('Events', ('type_create', 'type_update', 'type_delete')),
+    )
     content_types = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
@@ -160,13 +159,13 @@ class TagFilterForm(FilterForm):
 
 
 class ConfigContextFilterForm(FilterForm):
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['device_type_id', 'platform_id', 'role_id'],
-        ['cluster_type_id', 'cluster_group_id', 'cluster_id'],
-        ['tenant_group_id', 'tenant_id']
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Device', ('device_type_id', 'platform_id', 'role_id')),
+        ('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id'))
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -243,11 +242,11 @@ class LocalConfigContextFilterForm(forms.Form):
 
 class JournalEntryFilterForm(FilterForm):
     model = JournalEntry
-    field_groups = [
-        ['q'],
-        ['created_before', 'created_after', 'created_by_id'],
-        ['assigned_object_type_id', 'kind']
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Creation', ('created_before', 'created_after', 'created_by_id')),
+        ('Attributes', ('assigned_object_type_id', 'kind'))
+    )
     created_after = forms.DateTimeField(
         required=False,
         label=_('After'),
@@ -283,11 +282,11 @@ class JournalEntryFilterForm(FilterForm):
 
 class ObjectChangeFilterForm(FilterForm):
     model = ObjectChange
-    field_groups = [
-        ['q'],
-        ['time_before', 'time_after', 'action'],
-        ['user_id', 'changed_object_type_id'],
-    ]
+    fieldsets = (
+        (None, ('q',)),
+        ('Time', ('time_before', 'time_after')),
+        ('Attributes', ('action', 'user_id', 'changed_object_type_id')),
+    )
     time_after = forms.DateTimeField(
         required=False,
         label=_('After'),

+ 57 - 57
netbox/ipam/forms/filtersets.py

@@ -41,11 +41,11 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
 
 class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VRF
-    field_groups = [
-        ['q', 'tag'],
-        ['import_target_id', 'export_target_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Route Targets', ('import_target_id', 'export_target_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     import_target_id = DynamicModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         required=False,
@@ -61,11 +61,11 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RouteTarget
-    field_groups = [
-        ['q', 'tag'],
-        ['importing_vrf_id', 'exporting_vrf_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('VRF', ('importing_vrf_id', 'exporting_vrf_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     importing_vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         required=False,
@@ -93,11 +93,11 @@ class RIRFilterForm(NetBoxModelFilterSetForm):
 
 class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Aggregate
-    field_groups = [
-        ['q', 'tag'],
-        ['family', 'rir_id'],
-        ['tenant_group_id', 'tenant_id']
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('family', 'rir_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     family = forms.ChoiceField(
         required=False,
         choices=add_blank_choice(IPAddressFamilyChoices),
@@ -114,12 +114,11 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = ASN
-    field_groups = [
-        ['q'],
-        ['rir_id'],
-        ['tenant_group_id', 'tenant_id'],
-        ['site_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Assignment', ('rir_id', 'site_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     rir_id = DynamicModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         required=False,
@@ -130,6 +129,7 @@ class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         required=False,
         label=_('Site')
     )
+    tag = TagFilterField(model)
 
 
 class RoleFilterForm(NetBoxModelFilterSetForm):
@@ -139,13 +139,13 @@ class RoleFilterForm(NetBoxModelFilterSetForm):
 
 class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Prefix
-    field_groups = [
-        ['q', 'tag'],
-        ['within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized'],
-        ['vrf_id', 'present_in_vrf_id'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id']
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Addressing', ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
+        ('VRF', ('vrf_id', 'present_in_vrf_id')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     mask_length__lte = forms.IntegerField(
         widget=forms.HiddenInput()
     )
@@ -230,11 +230,11 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPRange
-    field_groups = [
-        ['q', 'tag'],
-        ['family', 'vrf_id', 'status', 'role_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     family = forms.ChoiceField(
         required=False,
         choices=add_blank_choice(IPAddressFamilyChoices),
@@ -263,12 +263,12 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPAddress
-    field_groups = [
-        ['q', 'tag'],
-        ['parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface'],
-        ['vrf_id', 'present_in_vrf_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')),
+        ('VRF', ('vrf_id', 'present_in_vrf_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     parent = forms.CharField(
         required=False,
         widget=forms.TextInput(
@@ -323,10 +323,10 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
     model = FHRPGroup
-    field_groups = (
-        ('q', 'tag'),
-        ('protocol', 'group_id'),
-        ('auth_type', 'auth_key'),
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('protocol', 'group_id')),
+        ('Authentication', ('auth_type', 'auth_key')),
     )
     protocol = forms.MultipleChoiceField(
         choices=FHRPGroupProtocolChoices,
@@ -352,11 +352,11 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
 
 
 class VLANGroupFilterForm(NetBoxModelFilterSetForm):
-    field_groups = [
-        ['q', 'tag'],
-        ['region', 'sitegroup', 'site', 'location', 'rack'],
-        ['min_vid', 'max_vid'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region', 'sitegroup', 'site', 'location', 'rack')),
+        ('VLAN ID', ('min_vid', 'max_vid')),
+    )
     model = VLANGroup
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
@@ -396,12 +396,12 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
 
 class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VLAN
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['group_id', 'status', 'role_id', 'vid'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Attributes', ('group_id', 'status', 'role_id', 'vid')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -450,9 +450,9 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
     model = ServiceTemplate
-    field_groups = (
-        ('q', 'tag'),
-        ('protocol', 'port'),
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('protocol', 'port')),
     )
     protocol = forms.ChoiceField(
         choices=add_blank_choice(ServiceProtocolChoices),

+ 16 - 15
netbox/templates/inc/filter_list.html

@@ -7,21 +7,22 @@
       {% for field in filter_form.hidden_fields %}
         {{ field }}
       {% endfor %}
-      {% if filter_form.field_groups %}
-        {# List filters by group #}
-        {% for group in filter_form.field_groups %}
-          <div class="col col-12">
-            {% for name in group %}
-              {% with field=filter_form|get_item:name %}
-                {% render_field field %}
-              {% endwith %}
-            {% endfor %}
-          </div>
-          {% if not forloop.last %}
-            <hr class="card-divider mt-0" />
+      {# List filters by group #}
+      {% for heading, fields in filter_form.fieldsets %}
+        <div class="col col-12">
+          {% if heading %}
+            <h6>{{ heading }}</h6>
           {% endif %}
-        {% endfor %}
-      {% else %}
+          {% for name in fields %}
+            {% with field=filter_form|get_item:name %}
+              {% render_field field %}
+            {% endwith %}
+          {% endfor %}
+        </div>
+        {% if not forloop.last %}
+          <hr class="card-divider mt-0" />
+        {% endif %}
+      {% empty %}
         {# List all non-customfield filters as declared in the form class #}
         {% for field in filter_form.visible_fields %}
           {% if not filter_form.custom_fields or field.name not in filter_form.custom_fields %}
@@ -30,7 +31,7 @@
             </div>
           {% endif %}
         {% endfor %}
-      {% endif %}
+      {% endfor %}
       {% if filter_form.custom_fields %}
         {# List all custom field filters #}
         <hr class="card-divider mt-0" />

+ 0 - 8
netbox/tenancy/forms/filtersets.py

@@ -29,10 +29,6 @@ class TenantGroupFilterForm(NetBoxModelFilterSetForm):
 
 class TenantFilterForm(NetBoxModelFilterSetForm):
     model = Tenant
-    field_groups = (
-        ('q', 'tag'),
-        ('group_id',),
-    )
     group_id = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         required=False,
@@ -63,10 +59,6 @@ class ContactRoleFilterForm(NetBoxModelFilterSetForm):
 
 class ContactFilterForm(NetBoxModelFilterSetForm):
     model = Contact
-    field_groups = (
-        ('q', 'tag'),
-        ('group_id',),
-    )
     group_id = DynamicModelMultipleChoiceField(
         queryset=ContactGroup.objects.all(),
         required=False,

+ 18 - 18
netbox/virtualization/forms/filtersets.py

@@ -32,12 +32,12 @@ class ClusterGroupFilterForm(NetBoxModelFilterSetForm):
 
 class ClusterFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Cluster
-    field_groups = [
-        ['q', 'tag'],
-        ['group_id', 'type_id'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('group_id', 'type_id')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     type_id = DynamicModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         required=False,
@@ -74,13 +74,13 @@ class ClusterFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VirtualMachine
-    field_groups = [
-        ['q', 'tag'],
-        ['cluster_group_id', 'cluster_type_id', 'cluster_id'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')),
+        ('Location', ('region_id', 'site_group_id', 'site_id')),
+        ('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
+    )
     cluster_group_id = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,
@@ -154,11 +154,11 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
 
 class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
     model = VMInterface
-    field_groups = [
-        ['q', 'tag'],
-        ['cluster_id', 'virtual_machine_id'],
-        ['enabled', 'mac_address'],
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Virtual Machine', ('cluster_id', 'virtual_machine_id')),
+        ('Attributes', ('enabled', 'mac_address')),
+    )
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         required=False,

+ 5 - 4
netbox/wireless/forms/filtersets.py

@@ -26,10 +26,11 @@ class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm):
 
 class WirelessLANFilterForm(NetBoxModelFilterSetForm):
     model = WirelessLAN
-    field_groups = [
-        ('q', 'tag'),
-        ('group_id',),
-    ]
+    fieldsets = (
+        (None, ('q', 'tag')),
+        ('Attributes', ('ssid', 'group_id',)),
+        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+    )
     ssid = forms.CharField(
         required=False,
         label='SSID'