Răsfoiți Sursa

Introduce local ChoiceField and MultipleChoiceField classes

jeremystretch 3 ani în urmă
părinte
comite
3dc671395e

+ 10 - 0
docs/plugins/development/forms.md

@@ -35,6 +35,16 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
     selection:
       members: false
 
+## Choice Fields
+
+::: utilities.forms.ChoiceField
+    selection:
+      members: false
+
+::: utilities.forms.MultipleChoiceField
+    selection:
+      members: false
+
 ## Dynamic Object Fields
 
 ::: utilities.forms.DynamicModelChoiceField

+ 3 - 4
netbox/circuits/forms/filtersets.py

@@ -6,7 +6,7 @@ from circuits.models import *
 from dcim.models import Region, Site, SiteGroup
 from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
-from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
+from utilities.forms import DynamicModelMultipleChoiceField, MultipleChoiceField, TagFilterField
 
 __all__ = (
     'CircuitFilterForm',
@@ -101,10 +101,9 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
         },
         label=_('Provider network')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=CircuitStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),

+ 48 - 73
netbox/dcim/forms/filtersets.py

@@ -10,8 +10,8 @@ from ipam.models import ASN, VRF
 from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
 from utilities.forms import (
-    APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
-    StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, SelectSpeedWidget,
+    APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, MultipleChoiceField,
+    StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, SelectSpeedWidget,
 )
 from wireless.choices import *
 
@@ -140,10 +140,9 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
         ('Tenant', ('tenant_group_id', 'tenant_id')),
         ('Contacts', ('contact', 'contact_role')),
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=SiteStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple(),
+        required=False
     )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
@@ -239,20 +238,17 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
         },
         label=_('Location')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=RackStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=RackTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    width = forms.MultipleChoiceField(
+    width = MultipleChoiceField(
         choices=RackWidthChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     role_id = DynamicModelMultipleChoiceField(
         queryset=RackRole.objects.all(),
@@ -346,15 +342,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
     part_number = forms.CharField(
         required=False
     )
-    subdevice_role = forms.MultipleChoiceField(
+    subdevice_role = MultipleChoiceField(
         choices=add_blank_choice(SubdeviceRoleChoices),
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    airflow = forms.MultipleChoiceField(
+    airflow = MultipleChoiceField(
         choices=add_blank_choice(DeviceAirflowChoices),
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     console_ports = forms.NullBooleanField(
         required=False,
@@ -561,15 +555,13 @@ class DeviceFilterForm(
         null_option='None',
         label=_('Platform')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=DeviceStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    airflow = forms.MultipleChoiceField(
+    airflow = MultipleChoiceField(
         choices=add_blank_choice(DeviceAirflowChoices),
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     serial = forms.CharField(
         required=False
@@ -739,15 +731,13 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         },
         label=_('Device')
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=add_blank_choice(CableTypeChoices),
-        required=False,
-        widget=StaticSelect()
+        required=False
     )
-    status = forms.ChoiceField(
+    status = MultipleChoiceField(
         required=False,
-        choices=add_blank_choice(LinkStatusChoices),
-        widget=StaticSelect()
+        choices=add_blank_choice(LinkStatusChoices)
     )
     color = ColorField(
         required=False
@@ -843,10 +833,9 @@ class PowerFeedFilterForm(NetBoxModelFilterSetForm):
         },
         label=_('Rack')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=PowerFeedStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     type = forms.ChoiceField(
         choices=add_blank_choice(PowerFeedTypeChoices),
@@ -886,15 +875,13 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
         ('Attributes', ('name', 'label', 'type', 'speed')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=ConsolePortTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    speed = forms.MultipleChoiceField(
+    speed = MultipleChoiceField(
         choices=ConsolePortSpeedChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     tag = TagFilterField(model)
 
@@ -906,15 +893,13 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
         ('Attributes', ('name', 'label', 'type', 'speed')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=ConsolePortTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    speed = forms.MultipleChoiceField(
+    speed = MultipleChoiceField(
         choices=ConsolePortSpeedChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     tag = TagFilterField(model)
 
@@ -926,10 +911,9 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
         ('Attributes', ('name', 'label', 'type')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=PowerPortTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     tag = TagFilterField(model)
 
@@ -941,10 +925,9 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
         ('Attributes', ('name', 'label', 'type')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=PowerOutletTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     tag = TagFilterField(model)
 
@@ -958,26 +941,22 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         ('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(
+    kind = MultipleChoiceField(
         choices=InterfaceKindChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=InterfaceTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     speed = forms.IntegerField(
         required=False,
         label='Select Speed',
         widget=SelectSpeedWidget(attrs={'readonly': None})
     )
-    duplex = forms.MultipleChoiceField(
+    duplex = MultipleChoiceField(
         choices=InterfaceDuplexChoices,
-        required=False,
-        label='Select Duplex',
-        widget=StaticSelectMultiple()
+        required=False
     )
     enabled = forms.NullBooleanField(
         required=False,
@@ -999,16 +978,14 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         required=False,
         label='WWN'
     )
-    rf_role = forms.MultipleChoiceField(
+    rf_role = MultipleChoiceField(
         choices=WirelessRoleChoices,
         required=False,
-        widget=StaticSelectMultiple(),
         label='Wireless role'
     )
-    rf_channel = forms.MultipleChoiceField(
+    rf_channel = MultipleChoiceField(
         choices=WirelessChannelChoices,
         required=False,
-        widget=StaticSelectMultiple(),
         label='Wireless channel'
     )
     rf_channel_frequency = forms.IntegerField(
@@ -1040,10 +1017,9 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
     model = FrontPort
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=PortTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     color = ColorField(
         required=False
@@ -1058,10 +1034,9 @@ class RearPortFilterForm(DeviceComponentFilterForm):
         ('Attributes', ('name', 'label', 'type', 'color')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=PortTypeChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     color = ColorField(
         required=False

+ 3 - 5
netbox/extras/forms/filtersets.py

@@ -10,7 +10,7 @@ from extras.utils import FeatureQuery
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
     add_blank_choice, APISelectMultiple, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DateTimePicker,
-    DynamicModelMultipleChoiceField, FilterForm, StaticSelect, StaticSelectMultiple, BOOLEAN_WITH_BLANK_CHOICES,
+    DynamicModelMultipleChoiceField, FilterForm, MultipleChoiceField, StaticSelect, BOOLEAN_WITH_BLANK_CHOICES,
 )
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 
@@ -37,10 +37,9 @@ class CustomFieldFilterForm(FilterForm):
         limit_choices_to=FeatureQuery('custom_fields'),
         required=False
     )
-    type = forms.MultipleChoiceField(
+    type = MultipleChoiceField(
         choices=CustomFieldTypeChoices,
         required=False,
-        widget=StaticSelectMultiple(),
         label=_('Field type')
     )
     weight = forms.IntegerField(
@@ -117,10 +116,9 @@ class WebhookFilterForm(FilterForm):
         limit_choices_to=FeatureQuery('webhooks'),
         required=False
     )
-    http_method = forms.MultipleChoiceField(
+    http_method = MultipleChoiceField(
         choices=WebhookHttpMethodChoices,
         required=False,
-        widget=StaticSelectMultiple(),
         label=_('HTTP method')
     )
     enabled = forms.NullBooleanField(

+ 17 - 25
netbox/ipam/forms/filtersets.py

@@ -9,7 +9,7 @@ from ipam.models import ASN
 from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
-    add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple,
+    add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField, MultipleChoiceField, StaticSelect,
     TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
 )
 
@@ -164,11 +164,10 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Address family'),
         widget=StaticSelect()
     )
-    mask_length = forms.MultipleChoiceField(
+    mask_length = MultipleChoiceField(
         required=False,
         choices=PREFIX_MASK_LENGTH_CHOICES,
-        label=_('Mask length'),
-        widget=StaticSelectMultiple()
+        label=_('Mask length')
     )
     vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
@@ -181,10 +180,9 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         required=False,
         label=_('Present in VRF')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=PrefixStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
@@ -247,10 +245,9 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Assigned VRF'),
         null_option='Global'
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=PrefixStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     role_id = DynamicModelMultipleChoiceField(
         queryset=Role.objects.all(),
@@ -301,15 +298,13 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         required=False,
         label=_('Present in VRF')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=IPAddressStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
-    role = forms.MultipleChoiceField(
+    role = MultipleChoiceField(
         choices=IPAddressRoleChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     assigned_to_interface = forms.NullBooleanField(
         required=False,
@@ -328,20 +323,18 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
         ('Attributes', ('protocol', 'group_id')),
         ('Authentication', ('auth_type', 'auth_key')),
     )
-    protocol = forms.MultipleChoiceField(
+    protocol = MultipleChoiceField(
         choices=FHRPGroupProtocolChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     group_id = forms.IntegerField(
         min_value=0,
         required=False,
         label='Group ID'
     )
-    auth_type = forms.MultipleChoiceField(
+    auth_type = MultipleChoiceField(
         choices=FHRPGroupAuthTypeChoices,
         required=False,
-        widget=StaticSelectMultiple(),
         label='Authentication type'
     )
     auth_key = forms.CharField(
@@ -430,10 +423,9 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         },
         label=_('VLAN group')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=VLANStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     role_id = DynamicModelMultipleChoiceField(
         queryset=Role.objects.all(),
@@ -457,7 +449,7 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
     protocol = forms.ChoiceField(
         choices=add_blank_choice(ServiceProtocolChoices),
         required=False,
-        widget=StaticSelectMultiple()
+        widget=StaticSelect()
     )
     port = forms.IntegerField(
         required=False,

+ 20 - 0
netbox/utilities/forms/fields/fields.py

@@ -9,11 +9,13 @@ from utilities.forms import widgets
 from utilities.validators import EnhancedURLValidator
 
 __all__ = (
+    'ChoiceField',
     'ColorField',
     'CommentField',
     'JSONField',
     'LaxURLField',
     'MACAddressField',
+    'MultipleChoiceField',
     'SlugField',
     'TagFilterField',
 )
@@ -125,3 +127,21 @@ class MACAddressField(forms.Field):
             raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
 
         return value
+
+
+#
+# Choice fields
+#
+
+class ChoiceField(forms.ChoiceField):
+    """
+    Overrides Django's built-in `ChoiceField` to use NetBox's `StaticSelect` widget
+    """
+    widget = widgets.StaticSelect
+
+
+class MultipleChoiceField(forms.MultipleChoiceField):
+    """
+    Overrides Django's built-in `MultipleChoiceField` to use NetBox's `StaticSelectMultiple` widget
+    """
+    widget = widgets.StaticSelectMultiple

+ 4 - 3
netbox/utilities/forms/widgets.py

@@ -88,9 +88,10 @@ class StaticSelect(forms.Select):
 
 
 class StaticSelectMultiple(StaticSelect, forms.SelectMultiple):
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+    """
+    Extends `StaticSelect` to support multiple selections.
+    """
+    pass
 
 
 class SelectWithPK(StaticSelect):

+ 3 - 4
netbox/virtualization/forms/filtersets.py

@@ -7,7 +7,7 @@ from ipam.models import VRF
 from netbox.forms import NetBoxModelFilterSetForm
 from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
 from utilities.forms import (
-    DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
+    DynamicModelMultipleChoiceField, MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
 )
 from virtualization.choices import *
 from virtualization.models import *
@@ -135,10 +135,9 @@ class VirtualMachineFilterForm(
         },
         label=_('Role')
     )
-    status = forms.MultipleChoiceField(
+    status = MultipleChoiceField(
         choices=VirtualMachineStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
+        required=False
     )
     platform_id = DynamicModelMultipleChoiceField(
         queryset=Platform.objects.all(),