mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
Fixes #3912: Remove chaining (now handled via APISelect)
This commit is contained in:
parent
5d7af0fae9
commit
a5294036e8
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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/'
|
||||
|
@ -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=<value>)
|
||||
|
||||
where <value> 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.
|
||||
|
@ -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/'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user