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

+ 26 - 14
netbox/circuits/forms.py

@@ -107,9 +107,9 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBu
 class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = Provider
     field_groups = [
-        ['q'],
-        ['region_id', 'site_id'],
-        ['asn', 'tag'],
+        ['q', 'tag'],
+        ['region_id', 'site_group_id', 'site_id'],
+        ['asn'],
     ]
     q = forms.CharField(
         required=False,
@@ -122,11 +122,18 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
         label=_('Region'),
         fetch_trigger='open'
     )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'site_group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -202,7 +209,10 @@ class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField
 
 class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = ProviderNetwork
-    field_order = ['q', 'provider_id', 'tag']
+    field_groups = (
+        ('q', 'tag'),
+        ('provider_id',),
+    )
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
@@ -368,17 +378,12 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul
 
 class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = Circuit
-    field_order = [
-        'q', 'type_id', 'provider_id', 'provider_network_id', 'status', 'region_id', 'site_id', 'tenant_group_id',
-        'tenant_id', 'commit_rate',
-    ]
     field_groups = [
-        ['q'],
-        ['type_id', 'status', 'commit_rate'],
+        ['q', 'tag'],
         ['provider_id', 'provider_network_id'],
-        ['region_id', 'site_id'],
+        ['type_id', 'status', 'commit_rate'],
+        ['region_id', 'site_group_id', 'site_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['tag']
     ]
     q = forms.CharField(
         required=False,
@@ -417,11 +422,18 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
         label=_('Region'),
         fetch_trigger='open'
     )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'site_group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'

+ 82 - 89
netbox/dcim/forms.py

@@ -58,11 +58,6 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     field_order = [
         'q', 'name', 'label', 'region_id', 'site_group_id', 'site_id',
     ]
-    field_groups = [
-        ['q'],
-        ['name', 'label'],
-        ['region_id', 'site_group_id', 'site_id'],
-    ]
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
@@ -90,7 +85,8 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -250,12 +246,19 @@ class RegionFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = Site
     field_groups = [
         ['q'],
+        ['parent_id'],
     ]
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
         label=_('Search')
     )
+    parent_id = DynamicModelMultipleChoiceField(
+        queryset=Region.objects.all(),
+        required=False,
+        label=_('Parent region'),
+        fetch_trigger='open'
+    )
 
 
 #
@@ -311,12 +314,19 @@ class SiteGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = SiteGroup
     field_groups = [
         ['q'],
+        ['parent_id'],
     ]
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
         label=_('Search')
     )
+    parent_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Parent group'),
+        fetch_trigger='open'
+    )
 
 
 #
@@ -476,10 +486,9 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
     model = Site
     field_order = ['q', 'status', 'region_id', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
-        ['status', 'region_id'],
+        ['q', 'tag'],
+        ['status', 'region_id', 'group_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['tag']
     ]
     q = forms.CharField(
         required=False,
@@ -500,7 +509,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
     group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         required=False,
-        label=_('Group'),
+        label=_('Site group'),
         fetch_trigger='open'
     )
     tag = TagFilterField(model)
@@ -607,11 +616,18 @@ class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
         label=_('Region'),
         fetch_trigger='open'
     )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -897,9 +913,10 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
     model = Rack
     field_order = ['q', 'region_id', 'site_id', 'location_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
-        ['status', 'role_id'],
+        ['q', 'tag'],
         ['region_id', 'site_id', 'location_id'],
+        ['status', 'role_id'],
+        ['type', 'width', 'asset_tag'],
         ['tenant_group_id', 'tenant_id'],
     ]
     q = forms.CharField(
@@ -1134,9 +1151,10 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMo
     model = RackReservation
     field_order = ['q', 'region_id', 'site_id', 'location_id', 'user_id', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
+        ['user_id'],
         ['region_id', 'site_id', 'location_id'],
-        ['user_id', 'tenant_group_id', 'tenant_id'],
+        ['tenant_group_id', 'tenant_id'],
     ]
     q = forms.CharField(
         required=False,
@@ -1155,7 +1173,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMo
         query_params={
             'region_id': '$region_id'
         },
-        label=_('Region'),
+        label=_('Site'),
         fetch_trigger='open'
     )
     location_id = DynamicModelMultipleChoiceField(
@@ -1292,12 +1310,9 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModel
 class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = DeviceType
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['manufacturer_id', 'subdevice_role'],
-        ['console_ports', 'console_server_ports'],
-        ['power_ports', 'power_outlets'],
-        ['interfaces', 'pass_through_ports'],
-        ['tag']
+        ['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
     ]
     q = forms.CharField(
         required=False,
@@ -2526,12 +2541,15 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
         'tenant_id', 'manufacturer_id', 'device_type_id', 'asset_tag', 'mac_address', 'has_primary_ip',
     ]
     field_groups = [
-        ['q'],
-        ['region_id', 'site_id', 'location_id', 'rack_id'],
-        ['status', 'role_id', 'asset_tag'],
+        ['q', 'tag'],
+        ['region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id'],
+        ['status', 'role_id', 'asset_tag', 'mac_address'],
+        ['manufacturer_id', 'device_type_id', 'platform_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['manufacturer_id', 'device_type_id'],
-        ['mac_address', 'has_primary_ip'],
+        [
+            'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports',
+            'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data',
+        ],
     ]
     q = forms.CharField(
         required=False,
@@ -2547,7 +2565,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         required=False,
-        label=_('Site group')
+        label=_('Site group'),
+        fetch_trigger='open'
     )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
@@ -2723,11 +2742,9 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentFor
 class ConsolePortFilterForm(DeviceComponentFilterForm):
     model = ConsolePort
     field_groups = [
-        ['q'],
-        ['name', 'label'],
-        ['type', 'speed'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['q', 'tag'],
+        ['name', 'label', 'type', 'speed'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=ConsolePortTypeChoices,
@@ -2831,11 +2848,9 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm):
 class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
     model = ConsoleServerPort
     field_groups = [
-        ['q'],
-        ['name', 'label'],
-        ['type', 'speed'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['q', 'tag'],
+        ['name', 'label', 'type', 'speed'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=ConsolePortTypeChoices,
@@ -2939,10 +2954,9 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
 class PowerPortFilterForm(DeviceComponentFilterForm):
     model = PowerPort
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['name', 'label', 'type'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=PowerPortTypeChoices,
@@ -3045,10 +3059,9 @@ class PowerPortCSVForm(CustomFieldModelCSVForm):
 class PowerOutletFilterForm(DeviceComponentFilterForm):
     model = PowerOutlet
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['name', 'label', 'type'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=PowerOutletTypeChoices,
@@ -3218,11 +3231,9 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm):
 class InterfaceFilterForm(DeviceComponentFilterForm):
     model = Interface
     field_groups = [
-        ['q'],
-        ['name', 'label', 'type', 'enabled'],
-        ['mgmt_only', 'mac_address'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag'],
+        ['q', 'tag'],
+        ['name', 'label', 'type', 'enabled', 'mgmt_only', 'mac_address'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=InterfaceTypeChoices,
@@ -3578,10 +3589,9 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
 
 class FrontPortFilterForm(DeviceComponentFilterForm):
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['name', 'label', 'type', 'color'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     model = FrontPort
     type = forms.MultipleChoiceField(
@@ -3768,10 +3778,9 @@ class FrontPortCSVForm(CustomFieldModelCSVForm):
 class RearPortFilterForm(DeviceComponentFilterForm):
     model = RearPort
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['name', 'label', 'type', 'color'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     type = forms.MultipleChoiceField(
         choices=PortTypeChoices,
@@ -3870,10 +3879,9 @@ class RearPortCSVForm(CustomFieldModelCSVForm):
 class DeviceBayFilterForm(DeviceComponentFilterForm):
     model = DeviceBay
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['name', 'label'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     tag = TagFilterField(model)
 
@@ -4122,11 +4130,9 @@ class InventoryItemBulkEditForm(
 class InventoryItemFilterForm(DeviceComponentFilterForm):
     model = InventoryItem
     field_groups = [
-        ['q'],
-        ['name', 'label', 'manufacturer_id'],
-        ['serial', 'asset_tag', 'discovered'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tag']
+        ['q', 'tag'],
+        ['name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered'],
+        ['region_id', 'site_group_id', 'site_id', 'device_id'],
     ]
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
@@ -4598,11 +4604,10 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkE
 class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = Cable
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
+        ['site_id', 'rack_id', 'device_id'],
         ['type', 'status', 'color'],
-        ['device_id', 'rack_id'],
-        ['region_id', 'site_id', 'tenant_id'],
-        ['tag']
+        ['tenant_id'],
     ]
     q = forms.CharField(
         required=False,
@@ -4672,11 +4677,6 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
 #
 
 class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -4704,11 +4704,6 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
 
 
 class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -4736,11 +4731,6 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
 
 
 class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -5010,10 +5000,9 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
     model = VirtualChassis
     field_order = ['q', 'region_id', 'site_group_id', 'site_id', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['region_id', 'site_group_id', 'site_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['tag']
     ]
     q = forms.CharField(
         required=False,
@@ -5036,7 +5025,8 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -5159,6 +5149,10 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModel
 
 class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = PowerPanel
+    field_groups = (
+        ('q', 'tag'),
+        ('region_id', 'site_group_id', 'site_id', 'location_id')
+    )
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
@@ -5180,7 +5174,8 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
         queryset=Site.objects.all(),
         required=False,
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -5402,12 +5397,10 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelB
 class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = PowerFeed
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['region_id', 'site_group_id', 'site_id'],
         ['power_panel_id', 'rack_id'],
-        ['type', 'supply', 'max_utilization'],
-        ['phase', 'voltage', 'amperage'],
-        ['status', 'tag']
+        ['status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization'],
     ]
     q = forms.CharField(
         required=False,

+ 21 - 20
netbox/extras/forms.py

@@ -88,12 +88,14 @@ class CustomFieldFilterForm(BootstrapMixin, forms.Form):
     )
     content_types = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
-        limit_choices_to=FeatureQuery('custom_fields')
+        limit_choices_to=FeatureQuery('custom_fields'),
+        required=False
     )
     type = forms.MultipleChoiceField(
         choices=CustomFieldTypeChoices,
         required=False,
-        widget=StaticSelectMultiple()
+        widget=StaticSelectMultiple(),
+        label=_('Field type')
     )
     weight = forms.IntegerField(
         required=False
@@ -174,8 +176,7 @@ class CustomLinkBulkEditForm(BootstrapMixin, BulkEditForm):
 class CustomLinkFilterForm(BootstrapMixin, forms.Form):
     field_groups = [
         ['q'],
-        ['content_type'],
-        ['weight', 'new_window'],
+        ['content_type', 'weight', 'new_window'],
     ]
     q = forms.CharField(
         required=False,
@@ -184,7 +185,8 @@ class CustomLinkFilterForm(BootstrapMixin, forms.Form):
     )
     content_type = ContentTypeChoiceField(
         queryset=ContentType.objects.all(),
-        limit_choices_to=FeatureQuery('custom_fields')
+        limit_choices_to=FeatureQuery('custom_fields'),
+        required=False
     )
     weight = forms.IntegerField(
         required=False
@@ -265,8 +267,7 @@ class ExportTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class ExportTemplateFilterForm(BootstrapMixin, forms.Form):
     field_groups = [
         ['q'],
-        ['content_type', 'mime_type'],
-        ['file_extension', 'as_attachment'],
+        ['content_type', 'mime_type', 'file_extension', 'as_attachment'],
     ]
     q = forms.CharField(
         required=False,
@@ -275,10 +276,12 @@ class ExportTemplateFilterForm(BootstrapMixin, forms.Form):
     )
     content_type = ContentTypeChoiceField(
         queryset=ContentType.objects.all(),
-        limit_choices_to=FeatureQuery('custom_fields')
+        limit_choices_to=FeatureQuery('custom_fields'),
+        required=False
     )
     mime_type = forms.CharField(
-        required=False
+        required=False,
+        label=_('MIME type')
     )
     file_extension = forms.CharField(
         required=False
@@ -377,8 +380,8 @@ class WebhookBulkEditForm(BootstrapMixin, BulkEditForm):
 class WebhookFilterForm(BootstrapMixin, forms.Form):
     field_groups = [
         ['q'],
-        ['content_types', 'http_method'],
-        ['enabled', 'type_create', 'type_update', 'type_delete'],
+        ['content_types', 'http_method', 'enabled'],
+        ['type_create', 'type_update', 'type_delete'],
     ]
     q = forms.CharField(
         required=False,
@@ -387,12 +390,14 @@ class WebhookFilterForm(BootstrapMixin, forms.Form):
     )
     content_types = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
-        limit_choices_to=FeatureQuery('custom_fields')
+        limit_choices_to=FeatureQuery('custom_fields'),
+        required=False
     )
     http_method = forms.MultipleChoiceField(
         choices=WebhookHttpMethodChoices,
         required=False,
-        widget=StaticSelectMultiple()
+        widget=StaticSelectMultiple(),
+        label=_('HTTP method')
     )
     enabled = forms.NullBooleanField(
         required=False,
@@ -693,16 +698,12 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
 
 
 class ConfigContextFilterForm(BootstrapMixin, forms.Form):
-    field_order = [
-        'q', 'region_id', 'site_group_id', 'site_id', 'role_id', 'platform_id', 'cluster_group_id',
-        'cluster_id', 'tenant_group_id', 'tenant_id',
-    ]
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['region_id', 'site_group_id', 'site_id'],
-        ['device_type_id', 'role_id', 'platform_id'],
+        ['device_type_id', 'platform_id', 'role_id'],
         ['cluster_group_id', 'cluster_id'],
-        ['tenant_group_id', 'tenant_id', 'tag']
+        ['tenant_group_id', 'tenant_id']
     ]
     q = forms.CharField(
         required=False,

+ 21 - 33
netbox/ipam/forms.py

@@ -107,12 +107,10 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEdi
 
 class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = VRF
-    field_order = ['q', 'import_target_id', 'export_target_id', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['import_target_id', 'export_target_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['tag']
     ]
     q = forms.CharField(
         required=False,
@@ -186,9 +184,8 @@ class RouteTargetBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
 
 class RouteTargetFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = RouteTarget
-    field_order = ['q', 'name', 'tenant_group_id', 'tenant_id', 'importing_vrfs', 'exporting_vrfs']
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['importing_vrf_id', 'exporting_vrf_id'],
         ['tenant_group_id', 'tenant_id'],
     ]
@@ -348,9 +345,8 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelB
 
 class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = Aggregate
-    field_order = ['q', 'family', 'rir', 'tenant_group_id', 'tenant_id']
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['family', 'rir_id'],
         ['tenant_group_id', 'tenant_id']
     ]
@@ -628,17 +624,13 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulk
 
 class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = Prefix
-    field_order = [
-        'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status',
-        'region_id', 'site_group_id', 'site_id', 'role_id', 'tenant_group_id', 'tenant_id',
-        'is_pool', 'mark_utilized',
-    ]
     field_groups = [
-        ['q'],
-        ['role_id', 'within_include', 'family', 'mask_length'],
-        ['vrf_id', 'present_in_vrf_id', 'is_pool', 'mark_utilized'],
+        ['q', 'tag'],
+        ['within_include', 'family', 'status', 'role_id'],
+        ['vrf_id', 'present_in_vrf_id'],
+        ['mask_length', 'is_pool', 'mark_utilized'],
         ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id', 'status', 'tag']
+        ['tenant_group_id', 'tenant_id']
     ]
     q = forms.CharField(
         required=False,
@@ -838,13 +830,10 @@ class IPRangeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul
 
 class IPRangeFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = IPRange
-    field_order = [
-        'q', 'family', 'vrf_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
-    ]
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['family', 'vrf_id', 'status', 'role_id'],
-        ['tenant_group_id', 'tenant_id', 'tag'],
+        ['tenant_group_id', 'tenant_id'],
     ]
     q = forms.CharField(
         required=False,
@@ -1280,10 +1269,10 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFil
         'assigned_to_interface', 'tenant_group_id', 'tenant_id',
     ]
     field_groups = [
-        ['q'],
-        ['parent', 'family', 'mask_length'],
-        ['status', 'vrf_id', 'present_in_vrf_id'],
-        ['role', 'assigned_to_interface'],
+        ['q', 'tag'],
+        ['parent', 'family', 'status', 'role'],
+        ['vrf_id', 'present_in_vrf_id'],
+        ['mask_length', 'assigned_to_interface'],
         ['tenant_group_id', 'tenant_id'],
     ]
     q = forms.CharField(
@@ -1489,8 +1478,7 @@ class VLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
 class VLANGroupFilterForm(BootstrapMixin, forms.Form):
     field_groups = [
         ['q'],
-        ['region', 'sitegroup', 'site'],
-        ['location', 'rack']
+        ['region', 'sitegroup', 'site', 'location', 'rack']
     ]
     q = forms.CharField(
         required=False,
@@ -1707,14 +1695,10 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEd
 
 class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = VLAN
-    field_order = [
-        'q', 'region_id', 'site_group_id', 'site_id', 'group_id', 'status', 'role_id',
-        'tenant_group_id', 'tenant_id',
-    ]
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['region_id', 'site_group_id', 'site_id'],
-        ['group_id', 'role_id', 'status'],
+        ['group_id', 'status', 'role_id'],
         ['tenant_group_id', 'tenant_id'],
     ]
     q = forms.CharField(
@@ -1818,6 +1802,10 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm):
 
 class ServiceFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = Service
+    field_groups = (
+        ('q', 'tag'),
+        ('protocol', 'port'),
+    )
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),

+ 43 - 33
netbox/templates/inc/filter_list.html

@@ -4,42 +4,52 @@
 <form action="." method="get">
   <div class="card">
     <div class="card-body overflow-visible d-flex flex-wrap justify-content-between py-3">
-          {% for field in filter_form.hidden_fields %}
-              {{ field }}
-          {% endfor %}
-          {% if filter_form.field_groups %}
-              {% 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>
-                  <hr class="card-divider mt-0" />
-              {% endfor %}
-              {% for name in filter_form.custom_field_filters %}
-                <div class="col col-12">
-                  {% with field=filter_form|get_item:name %}
-                    {% render_field field %}
-                  {% endwith %}
-                </div>
-              {% endfor %}
-          {% else %}
-              {% for field in filter_form.visible_fields %}
-                  <div class="col col-12">
-                      {% render_field field %}
-                  </div>
-              {% endfor %}
+      {% 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" />
           {% endif %}
+        {% endfor %}
+      {% else %}
+        {# List all non-customfield filters as declared in the form class #}
+        {% for field in filter_form.visible_fields %}
+          {% if not filter_form.custom_field_filters or field.name not in filter_form.custom_field_filters %}
+            <div class="col col-12">
+              {% render_field field %}
+            </div>
+          {% endif %}
+        {% endfor %}
+      {% endif %}
+      {% if filter_form.custom_field_filters %}
+        {# List all custom field filters #}
+        <hr class="card-divider mt-0" />
+        {% for name in filter_form.custom_field_filters %}
+          <div class="col col-12">
+            {% with field=filter_form|get_item:name %}
+              {% render_field field %}
+            {% endwith %}
+          </div>
+        {% endfor %}
+      {% endif %}
     </div>
     <div class="card-footer text-end noprint border-0">
-        <button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
-            <i class="mdi mdi-backspace"></i> Reset
-        </button>
-        <button type="submit" class="btn btn-sm btn-primary m-1">
-            <i class="mdi mdi-filter-variant"></i> Filter
-        </button>
+      <button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
+        <i class="mdi mdi-backspace"></i> Reset
+      </button>
+      <button type="submit" class="btn btn-sm btn-primary m-1">
+        <i class="mdi mdi-filter-variant"></i> Filter
+      </button>
     </div>
   </div>
 </form>

+ 4 - 0
netbox/tenancy/forms.py

@@ -135,6 +135,10 @@ class TenantBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulk
 
 class TenantFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
     model = Tenant
+    field_groups = (
+        ('q', 'tag'),
+        ('group_id',),
+    )
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),

+ 15 - 16
netbox/virtualization/forms.py

@@ -228,11 +228,10 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
         'q', 'type_id', 'region_id', 'site_id', 'group_id', 'tenant_group_id', 'tenant_id',
     ]
     field_groups = [
-        ['q'],
-        ['type_id'],
-        ['region_id', 'site_id'],
+        ['q', 'tag'],
+        ['group_id', 'type_id'],
+        ['region_id', 'site_group_id', 'site_id'],
         ['tenant_group_id', 'tenant_id'],
-        ['tag'],
     ]
     q = forms.CharField(
         required=False,
@@ -251,12 +250,19 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilte
         label=_('Region'),
         fetch_trigger='open'
     )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         required=False,
         null_option='None',
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'site_group_id': '$site_group_id',
         },
         label=_('Site'),
         fetch_trigger='open'
@@ -541,18 +547,12 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldM
 
 class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = VirtualMachine
-    field_order = [
-        'q', 'cluster_group_id', 'cluster_type_id', 'cluster_id', 'status', 'role_id', 'region_id', 'site_group_id',
-        'site_id', 'tenant_group_id', 'tenant_id', 'platform_id', 'mac_address',
-    ]
     field_groups = [
-        ['q'],
-        ['status', 'role_id'],
-        ['platform_id', 'mac_address'],
+        ['q', 'tag'],
         ['cluster_group_id', 'cluster_type_id', 'cluster_id'],
-        ['region_id', 'site_id'],
+        ['region_id', 'site_group_id', 'site_id'],
+        ['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip'],
         ['tenant_group_id', 'tenant_id'],
-
     ]
     q = forms.CharField(
         required=False,
@@ -878,10 +878,9 @@ class VMInterfaceBulkRenameForm(BulkRenameForm):
 class VMInterfaceFilterForm(BootstrapMixin, forms.Form):
     model = VMInterface
     field_groups = [
-        ['q'],
+        ['q', 'tag'],
         ['cluster_id', 'virtual_machine_id'],
         ['enabled', 'mac_address'],
-        ['tag']
     ]
     q = forms.CharField(
         required=False,