diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index cd356cc09..e82cf606b 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -21,9 +21,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant, TenantGroup from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, - BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField, ComponentForm, - ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField, - SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES + BulkEditNullBooleanSelect, ColorSelect, CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, + ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, JSONField, SelectWithPK, SmallTextarea, + SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import Cluster, ClusterGroup from .constants import * @@ -428,11 +428,8 @@ class RackRoleCSVForm(forms.ModelForm): # class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm): - group = ChainedModelChoiceField( + group = forms.ModelChoiceField( queryset=RackGroup.objects.all(), - chains=( - ('site', 'site'), - ), required=False, widget=APISelect( api_url='/api/dcim/rack-groups/', @@ -720,13 +717,9 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class RackElevationFilterForm(RackFilterForm): field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant'] - id = ChainedModelChoiceField( + id = forms.ModelChoiceField( queryset=Rack.objects.all(), label='Rack', - chains=( - ('site', 'site'), - ('group_id', 'group_id'), - ), required=False, widget=APISelectMultiple( api_url='/api/dcim/racks/', @@ -1376,11 +1369,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - rack = ChainedModelChoiceField( + rack = forms.ModelChoiceField( queryset=Rack.objects.all(), - chains=( - ('site', 'site'), - ), required=False, widget=APISelect( api_url='/api/dcim/racks/', @@ -1406,11 +1396,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - device_type = ChainedModelChoiceField( + device_type = forms.ModelChoiceField( queryset=DeviceType.objects.all(), - chains=( - ('manufacturer', 'manufacturer'), - ), label='Device type', widget=APISelect( api_url='/api/dcim/device-types/', @@ -1430,11 +1417,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - cluster = ChainedModelChoiceField( + cluster = forms.ModelChoiceField( queryset=Cluster.objects.all(), - chains=( - ('group', 'cluster_group'), - ), required=False, widget=APISelect( api_url='/api/virtualization/clusters/', @@ -2669,7 +2653,7 @@ class RearPortBulkDisconnectForm(ConfirmationForm): # Cables # -class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): +class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm): """ Base form for connecting a Cable to a Device component """ @@ -2685,11 +2669,8 @@ class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFo } ) ) - termination_b_rack = ChainedModelChoiceField( + termination_b_rack = forms.ModelChoiceField( queryset=Rack.objects.all(), - chains=( - ('site', 'termination_b_site'), - ), label='Rack', required=False, widget=APISelect( @@ -2702,12 +2683,8 @@ class ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFo } ) ) - termination_b_device = ChainedModelChoiceField( + termination_b_device = forms.ModelChoiceField( queryset=Device.objects.all(), - chains=( - ('site', 'termination_b_site'), - ('rack', 'termination_b_rack'), - ), label='Device', required=False, widget=APISelect( @@ -2800,7 +2777,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm): ) -class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): +class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): termination_b_provider = forms.ModelChoiceField( queryset=Provider.objects.all(), label='Provider', @@ -2823,11 +2800,8 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f } ) ) - termination_b_circuit = ChainedModelChoiceField( + termination_b_circuit = forms.ModelChoiceField( queryset=Circuit.objects.all(), - chains=( - ('provider', 'termination_b_provider'), - ), label='Circuit', widget=APISelect( api_url='/api/circuits/circuits/', @@ -2854,7 +2828,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f ] -class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): +class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm): termination_b_site = forms.ModelChoiceField( queryset=Site.objects.all(), label='Site', @@ -2868,12 +2842,9 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode } ) ) - termination_b_rackgroup = ChainedModelChoiceField( + termination_b_rackgroup = forms.ModelChoiceField( queryset=RackGroup.objects.all(), label='Rack Group', - chains=( - ('site', 'termination_b_site'), - ), required=False, widget=APISelect( api_url='/api/dcim/rack-groups/', @@ -2883,12 +2854,8 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode } ) ) - termination_b_powerpanel = ChainedModelChoiceField( + termination_b_powerpanel = forms.ModelChoiceField( queryset=PowerPanel.objects.all(), - chains=( - ('site', 'termination_b_site'), - ('rack_group', 'termination_b_rackgroup'), - ), label='Power Panel', required=False, widget=APISelect( @@ -3500,7 +3467,7 @@ class DeviceVCMembershipForm(forms.ModelForm): return vc_position -class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): +class VCMemberSelectForm(BootstrapMixin, forms.Form): site = forms.ModelChoiceField( queryset=Site.objects.all(), label='Site', @@ -3513,11 +3480,8 @@ class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): } ) ) - rack = ChainedModelChoiceField( + rack = forms.ModelChoiceField( queryset=Rack.objects.all(), - chains=( - ('site', 'site'), - ), label='Rack', required=False, widget=APISelect( @@ -3530,14 +3494,10 @@ class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): } ) ) - device = ChainedModelChoiceField( + device = forms.ModelChoiceField( queryset=Device.objects.filter( virtual_chassis__isnull=True ), - chains=( - ('site', 'site'), - ('rack', 'rack'), - ), label='Device', widget=APISelect( api_url='/api/dcim/devices/', @@ -3611,11 +3571,8 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm): # class PowerPanelForm(BootstrapMixin, forms.ModelForm): - rack_group = ChainedModelChoiceField( + rack_group = forms.ModelChoiceField( queryset=RackGroup.objects.all(), - chains=( - ('site', 'site'), - ), required=False, widget=APISelect( api_url='/api/dcim/rack-groups/', @@ -3717,7 +3674,7 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm): # class PowerFeedForm(BootstrapMixin, CustomFieldForm): - site = ChainedModelChoiceField( + site = forms.ModelChoiceField( queryset=Site.objects.all(), required=False, widget=APISelect( diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index c3387a5aa..5be821cba 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -8,9 +8,9 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( - add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, - CSVChoiceField, DatePicker, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, - SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES + add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, + DatePicker, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField, + StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import VirtualMachine from .constants import * @@ -278,11 +278,8 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - vlan_group = ChainedModelChoiceField( + vlan_group = forms.ModelChoiceField( queryset=VLANGroup.objects.all(), - chains=( - ('site', 'site'), - ), required=False, label='VLAN group', widget=APISelect( @@ -295,12 +292,8 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - vlan = ChainedModelChoiceField( + vlan = forms.ModelChoiceField( queryset=VLAN.objects.all(), - chains=( - ('site', 'site'), - ('group', 'vlan_group'), - ), required=False, label='VLAN', widget=APISelect( @@ -600,11 +593,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) } ) ) - nat_rack = ChainedModelChoiceField( + nat_rack = forms.ModelChoiceField( queryset=Rack.objects.all(), - chains=( - ('site', 'nat_site'), - ), required=False, label='Rack', widget=APISelect( @@ -618,12 +608,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) } ) ) - nat_device = ChainedModelChoiceField( + nat_device = forms.ModelChoiceField( queryset=Device.objects.all(), - chains=( - ('site', 'nat_site'), - ('rack', 'nat_rack'), - ), required=False, label='Device', widget=APISelect( @@ -634,11 +620,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) } ) ) - nat_inside = ChainedModelChoiceField( + nat_inside = forms.ModelChoiceField( queryset=IPAddress.objects.all(), - chains=( - ('interface__device', 'nat_device'), - ), required=False, label='IP Address', widget=APISelect( @@ -1089,11 +1072,8 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - group = ChainedModelChoiceField( + group = forms.ModelChoiceField( queryset=VLANGroup.objects.all(), - chains=( - ('site', 'site'), - ), required=False, label='Group', widget=APISelect( diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index f8aaa45e5..7378a9cbe 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -2,10 +2,7 @@ from django import forms from taggit.forms import TagField from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm -from utilities.forms import ( - APISelect, APISelectMultiple, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, - FilterChoiceField, SlugField, -) +from utilities.forms import APISelect, APISelectMultiple, BootstrapMixin, CommentField, FilterChoiceField, SlugField from .models import Tenant, TenantGroup @@ -119,7 +116,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): # Form extensions # -class TenancyForm(ChainedFieldsMixin, forms.Form): +class TenancyForm(forms.Form): tenant_group = forms.ModelChoiceField( queryset=TenantGroup.objects.all(), required=False, @@ -133,11 +130,8 @@ class TenancyForm(ChainedFieldsMixin, forms.Form): } ) ) - tenant = ChainedModelChoiceField( + tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), - chains=( - ('group', 'tenant_group'), - ), required=False, widget=APISelect( api_url='/api/tenancy/tenants/' diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 39422c265..721a58f95 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -557,34 +557,6 @@ class FlexibleModelChoiceField(forms.ModelChoiceField): return value -class ChainedModelChoiceField(forms.ModelChoiceField): - """ - A ModelChoiceField which is initialized based on the values of other fields within a form. `chains` is a dictionary - mapping of model fields to peer fields within the form. For example: - - country1 = forms.ModelChoiceField(queryset=Country.objects.all()) - city1 = ChainedModelChoiceField(queryset=City.objects.all(), chains={'country': 'country1'} - - The queryset of the `city1` field will be modified as - - .filter(country=) - - where is the value of the `country1` field. (Note: The form must inherit from ChainedFieldsMixin.) - """ - def __init__(self, chains=None, *args, **kwargs): - self.chains = chains - super().__init__(*args, **kwargs) - - -class ChainedModelMultipleChoiceField(forms.ModelMultipleChoiceField): - """ - See ChainedModelChoiceField - """ - def __init__(self, chains=None, *args, **kwargs): - self.chains = chains - super().__init__(*args, **kwargs) - - class SlugField(forms.SlugField): """ Extend the built-in SlugField to automatically populate from a field called `name` unless otherwise specified. @@ -690,46 +662,6 @@ class BootstrapMixin(forms.BaseForm): field.widget.attrs['placeholder'] = field.label -class ChainedFieldsMixin(forms.BaseForm): - """ - Iterate through all ChainedModelChoiceFields in the form and modify their querysets based on chained fields. - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - for field_name, field in self.fields.items(): - - if isinstance(field, ChainedModelChoiceField): - - filters_dict = {} - for (db_field, parent_field) in field.chains: - if self.is_bound and parent_field in self.data and self.data[parent_field]: - filters_dict[db_field] = self.data[parent_field] or None - elif self.initial.get(parent_field): - filters_dict[db_field] = self.initial[parent_field] - elif self.fields[parent_field].widget.attrs.get('nullable'): - filters_dict[db_field] = None - else: - break - - # Limit field queryset by chained field values - if filters_dict: - field.queryset = field.queryset.filter(**filters_dict) - # Editing an existing instance; limit field to its current value - elif not self.is_bound and getattr(self, 'instance', None) and hasattr(self.instance, field_name): - obj = getattr(self.instance, field_name) - if obj is not None: - field.queryset = field.queryset.filter(pk=obj.pk) - else: - field.queryset = field.queryset.none() - # Creating a new instance with no bound data; nullify queryset - elif not self.data.get(field_name): - field.queryset = field.queryset.none() - # Creating a new instance with bound data; limit queryset to the specified value - else: - field.queryset = field.queryset.filter(pk=self.data.get(field_name)) - - class ReturnURLForm(forms.Form): """ Provides a hidden return URL field to control where the user is directed after the form is submitted. diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 427e676f6..ebb99bf56 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -11,9 +11,8 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm, - ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, - SmallTextarea, StaticSelect2, StaticSelect2Multiple + CommentField, ComponentForm, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, + SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple ) from .constants import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -218,7 +217,7 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm): ) -class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): +class ClusterAddDevicesForm(BootstrapMixin, forms.Form): region = forms.ModelChoiceField( queryset=Region.objects.all(), required=False, @@ -232,11 +231,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): } ) ) - site = ChainedModelChoiceField( + site = forms.ModelChoiceField( queryset=Site.objects.all(), - chains=( - ('region', 'region'), - ), required=False, widget=APISelect( api_url='/api/dcim/sites/', @@ -246,11 +242,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): } ) ) - rack = ChainedModelChoiceField( + rack = forms.ModelChoiceField( queryset=Rack.objects.all(), - chains=( - ('site', 'site'), - ), required=False, widget=APISelect( api_url='/api/dcim/racks/', @@ -262,12 +255,8 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form): } ) ) - devices = ChainedModelMultipleChoiceField( + devices = forms.ModelMultipleChoiceField( queryset=Device.objects.filter(cluster__isnull=True), - chains=( - ('site', 'site'), - ('rack', 'rack'), - ), widget=APISelectMultiple( api_url='/api/dcim/devices/', display_field='display_name', @@ -327,11 +316,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): } ) ) - cluster = ChainedModelChoiceField( + cluster = forms.ModelChoiceField( queryset=Cluster.objects.all(), - chains=( - ('group', 'cluster_group'), - ), widget=APISelect( api_url='/api/virtualization/clusters/' )