|
@@ -11,8 +11,9 @@ from ipam.models import IPAddress
|
|
|
from tenancy.models import Tenant
|
|
|
from utilities.forms import (
|
|
|
APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
|
|
- BulkImportForm, CommentField, CSVDataField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField,
|
|
|
- Livesearch, SelectWithDisabled, SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField,
|
|
|
+ BulkImportForm, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, CSVDataField, ExpandableNameField,
|
|
|
+ FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
|
|
+ FilterTreeNodeMultipleChoiceField,
|
|
|
)
|
|
|
|
|
|
from .formfields import MACAddressFormField
|
|
@@ -184,16 +185,23 @@ class RackRoleForm(BootstrapMixin, forms.ModelForm):
|
|
|
# Racks
|
|
|
#
|
|
|
|
|
|
-class RackForm(BootstrapMixin, CustomFieldForm):
|
|
|
- group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False, label='Group', widget=APISelect(
|
|
|
- api_url='/api/dcim/rack-groups/?site_id={{site}}',
|
|
|
- ))
|
|
|
+class RackForm(BootstrapMixin, ChainedFieldsMixin, CustomFieldForm):
|
|
|
+ group = ChainedModelChoiceField(
|
|
|
+ queryset=RackGroup.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/rack-groups/?site_id={{site}}',
|
|
|
+ )
|
|
|
+ )
|
|
|
comments = CommentField()
|
|
|
|
|
|
class Meta:
|
|
|
model = Rack
|
|
|
- fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
|
|
- 'comments']
|
|
|
+ fields = [
|
|
|
+ 'site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
|
|
+ 'comments',
|
|
|
+ ]
|
|
|
help_texts = {
|
|
|
'site': "The site at which the rack exists",
|
|
|
'name': "Organizational rack name",
|
|
@@ -204,18 +212,6 @@ class RackForm(BootstrapMixin, CustomFieldForm):
|
|
|
'site': forms.Select(attrs={'filter-for': 'group'}),
|
|
|
}
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
-
|
|
|
- super(RackForm, self).__init__(*args, **kwargs)
|
|
|
-
|
|
|
- # Limit rack group choices
|
|
|
- if self.is_bound and self.data.get('site'):
|
|
|
- self.fields['group'].queryset = RackGroup.objects.filter(site__pk=self.data['site'])
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['group'].queryset = RackGroup.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['group'].choices = []
|
|
|
-
|
|
|
|
|
|
class RackFromCSVForm(forms.ModelForm):
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
|
|
@@ -538,25 +534,46 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
|
|
|
# Devices
|
|
|
#
|
|
|
|
|
|
-class DeviceForm(BootstrapMixin, CustomFieldForm):
|
|
|
- site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
|
|
|
- rack = forms.ModelChoiceField(
|
|
|
- queryset=Rack.objects.all(), required=False, widget=APISelect(
|
|
|
+class DeviceForm(BootstrapMixin, ChainedFieldsMixin, CustomFieldForm):
|
|
|
+ site = forms.ModelChoiceField(
|
|
|
+ queryset=Site.objects.all(),
|
|
|
+ widget=forms.Select(
|
|
|
+ attrs={'filter-for': 'rack'}
|
|
|
+ )
|
|
|
+ )
|
|
|
+ rack = ChainedModelChoiceField(
|
|
|
+ queryset=Rack.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
+ required=False,
|
|
|
+ widget=APISelect(
|
|
|
api_url='/api/dcim/racks/?site_id={{site}}',
|
|
|
display_field='display_name',
|
|
|
attrs={'filter-for': 'position'}
|
|
|
)
|
|
|
)
|
|
|
position = forms.TypedChoiceField(
|
|
|
- required=False, empty_value=None, help_text="The lowest-numbered unit occupied by the device",
|
|
|
- widget=APISelect(api_url='/api/dcim/racks/{{rack}}/units/?face={{face}}', disabled_indicator='device')
|
|
|
+ required=False,
|
|
|
+ empty_value=None,
|
|
|
+ help_text="The lowest-numbered unit occupied by the device",
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/racks/{{rack}}/units/?face={{face}}',
|
|
|
+ disabled_indicator='device'
|
|
|
+ )
|
|
|
)
|
|
|
manufacturer = forms.ModelChoiceField(
|
|
|
- queryset=Manufacturer.objects.all(), widget=forms.Select(attrs={'filter-for': 'device_type'})
|
|
|
+ queryset=Manufacturer.objects.all(),
|
|
|
+ widget=forms.Select(
|
|
|
+ attrs={'filter-for': 'device_type'}
|
|
|
+ )
|
|
|
)
|
|
|
- device_type = forms.ModelChoiceField(
|
|
|
- queryset=DeviceType.objects.all(), label='Device type',
|
|
|
- widget=APISelect(api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}', display_field='model')
|
|
|
+ device_type = ChainedModelChoiceField(
|
|
|
+ queryset=DeviceType.objects.all(),
|
|
|
+ chains={'manufacturer': 'manufacturer'},
|
|
|
+ label='Device type',
|
|
|
+ widget=APISelect(
|
|
|
+ api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}',
|
|
|
+ display_field='model'
|
|
|
+ )
|
|
|
)
|
|
|
comments = CommentField()
|
|
|
|
|
@@ -572,18 +589,17 @@ class DeviceForm(BootstrapMixin, CustomFieldForm):
|
|
|
}
|
|
|
widgets = {
|
|
|
'face': forms.Select(attrs={'filter-for': 'position'}),
|
|
|
- 'manufacturer': forms.Select(attrs={'filter-for': 'device_type'}),
|
|
|
}
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
+ def __init__(self, instance=None, initial=None, *args, **kwargs):
|
|
|
|
|
|
- super(DeviceForm, self).__init__(*args, **kwargs)
|
|
|
+ # Initialize helper selections
|
|
|
+ if instance and instance.device_type is not None:
|
|
|
+ initial['manufacturer'] = instance.device_type.manufacturer
|
|
|
|
|
|
- if self.instance.pk:
|
|
|
+ super(DeviceForm, self).__init__(instance=instance, initial=initial, *args, **kwargs)
|
|
|
|
|
|
- # Initialize helper selections
|
|
|
- self.initial['site'] = self.instance.site
|
|
|
- self.initial['manufacturer'] = self.instance.device_type.manufacturer
|
|
|
+ if self.instance.pk:
|
|
|
|
|
|
# Compile list of choices for primary IPv4 and IPv6 addresses
|
|
|
for family in [4, 6]:
|
|
@@ -607,14 +623,6 @@ class DeviceForm(BootstrapMixin, CustomFieldForm):
|
|
|
self.fields['primary_ip6'].choices = []
|
|
|
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
|
|
|
|
|
- # Limit rack choices
|
|
|
- if self.is_bound and self.data.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site'])
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['rack'].choices = []
|
|
|
-
|
|
|
# Rack position
|
|
|
pk = self.instance.pk if self.instance.pk else None
|
|
|
try:
|
|
@@ -635,16 +643,6 @@ class DeviceForm(BootstrapMixin, CustomFieldForm):
|
|
|
}) for p in position_choices
|
|
|
]
|
|
|
|
|
|
- # Limit device_type choices
|
|
|
- if self.is_bound:
|
|
|
- self.fields['device_type'].queryset = DeviceType.objects.filter(manufacturer__pk=self.data['manufacturer'])\
|
|
|
- .select_related('manufacturer')
|
|
|
- elif self.initial.get('manufacturer'):
|
|
|
- self.fields['device_type'].queryset = DeviceType.objects.filter(manufacturer=self.initial['manufacturer'])\
|
|
|
- .select_related('manufacturer')
|
|
|
- else:
|
|
|
- self.fields['device_type'].choices = []
|
|
|
-
|
|
|
# Disable rack assignment if this is a child device installed in a parent device
|
|
|
if pk and self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
|
|
|
self.fields['site'].disabled = True
|
|
@@ -940,21 +938,23 @@ class ConsoleConnectionImportForm(BootstrapMixin, BulkImportForm):
|
|
|
self.cleaned_data['csv'] = connection_list
|
|
|
|
|
|
|
|
|
-class ConsolePortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
+class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
site = forms.ModelChoiceField(
|
|
|
queryset=Site.objects.all(),
|
|
|
widget=forms.HiddenInput(),
|
|
|
)
|
|
|
- rack = forms.ModelChoiceField(
|
|
|
+ rack = ChainedModelChoiceField(
|
|
|
queryset=Rack.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
label='Rack',
|
|
|
required=False,
|
|
|
widget=forms.Select(
|
|
|
attrs={'filter-for': 'console_server', 'nullable': 'true'}
|
|
|
)
|
|
|
)
|
|
|
- console_server = forms.ModelChoiceField(
|
|
|
- queryset=Device.objects.all(),
|
|
|
+ console_server = ChainedModelChoiceField(
|
|
|
+ queryset=Device.objects.filter(device_type__is_console_server=True),
|
|
|
+ chains={'site': 'site', 'rack': 'rack'},
|
|
|
label='Console Server',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -972,8 +972,9 @@ class ConsolePortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
field_to_update='console_server',
|
|
|
)
|
|
|
)
|
|
|
- cs_port = forms.ModelChoiceField(
|
|
|
+ cs_port = ChainedModelChoiceField(
|
|
|
queryset=ConsoleServerPort.objects.all(),
|
|
|
+ chains={'device': 'console_server'},
|
|
|
label='Port',
|
|
|
widget=APISelect(
|
|
|
api_url='/api/dcim/console-server-ports/?device_id={{console_server}}',
|
|
@@ -996,32 +997,6 @@ class ConsolePortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
if not self.instance.pk:
|
|
|
raise RuntimeError("ConsolePortConnectionForm must be initialized with an existing ConsolePort instance.")
|
|
|
|
|
|
- # Initialize rack choices if site is set
|
|
|
- if self.initial.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['rack'].choices = []
|
|
|
-
|
|
|
- # Initialize console_server choices if rack or site is set
|
|
|
- if self.initial.get('rack'):
|
|
|
- self.fields['console_server'].queryset = Device.objects.filter(
|
|
|
- rack=self.initial['rack'], device_type__is_console_server=True
|
|
|
- )
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['console_server'].queryset = Device.objects.filter(
|
|
|
- site=self.initial['site'], rack__isnull=True, device_type__is_console_server=True
|
|
|
- )
|
|
|
- else:
|
|
|
- self.fields['console_server'].choices = []
|
|
|
-
|
|
|
- # Initialize CS port choices if console_server is set
|
|
|
- if self.initial.get('console_server'):
|
|
|
- self.fields['cs_port'].queryset = ConsoleServerPort.objects.filter(
|
|
|
- device=self.initial['console_server']
|
|
|
- )
|
|
|
- else:
|
|
|
- self.fields['cs_port'].choices = []
|
|
|
-
|
|
|
|
|
|
#
|
|
|
# Console server ports
|
|
@@ -1041,21 +1016,23 @@ class ConsoleServerPortCreateForm(DeviceComponentForm):
|
|
|
name_pattern = ExpandableNameField(label='Name')
|
|
|
|
|
|
|
|
|
-class ConsoleServerPortConnectionForm(BootstrapMixin, forms.Form):
|
|
|
+class ConsoleServerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|
|
site = forms.ModelChoiceField(
|
|
|
queryset=Site.objects.all(),
|
|
|
widget=forms.HiddenInput(),
|
|
|
)
|
|
|
- rack = forms.ModelChoiceField(
|
|
|
+ rack = ChainedModelChoiceField(
|
|
|
queryset=Rack.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
label='Rack',
|
|
|
required=False,
|
|
|
widget=forms.Select(
|
|
|
attrs={'filter-for': 'device', 'nullable': 'true'}
|
|
|
)
|
|
|
)
|
|
|
- device = forms.ModelChoiceField(
|
|
|
+ device = ChainedModelChoiceField(
|
|
|
queryset=Device.objects.all(),
|
|
|
+ chains={'site': 'site', 'rack': 'rack'},
|
|
|
label='Device',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -1073,8 +1050,9 @@ class ConsoleServerPortConnectionForm(BootstrapMixin, forms.Form):
|
|
|
field_to_update='device'
|
|
|
)
|
|
|
)
|
|
|
- port = forms.ModelChoiceField(
|
|
|
+ port = ChainedModelChoiceField(
|
|
|
queryset=ConsolePort.objects.all(),
|
|
|
+ chains={'device': 'device'},
|
|
|
label='Port',
|
|
|
widget=APISelect(
|
|
|
api_url='/api/dcim/console-ports/?device_id={{device}}',
|
|
@@ -1096,30 +1074,6 @@ class ConsoleServerPortConnectionForm(BootstrapMixin, forms.Form):
|
|
|
'connection_status': 'Status',
|
|
|
}
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
-
|
|
|
- super(ConsoleServerPortConnectionForm, self).__init__(*args, **kwargs)
|
|
|
-
|
|
|
- # Initialize rack choices if site is set
|
|
|
- if self.initial.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['rack'].choices = []
|
|
|
-
|
|
|
- # Initialize device choices if rack or site is set
|
|
|
- if self.initial.get('rack'):
|
|
|
- self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['device'].queryset = Device.objects.filter(site=self.initial['site'], rack__isnull=True)
|
|
|
- else:
|
|
|
- self.fields['device'].choices = []
|
|
|
-
|
|
|
- # Initialize port choices if device is set
|
|
|
- if self.initial.get('device'):
|
|
|
- self.fields['port'].queryset = ConsolePort.objects.filter(device=self.initial['device'])
|
|
|
- else:
|
|
|
- self.fields['port'].choices = []
|
|
|
-
|
|
|
|
|
|
#
|
|
|
# Power ports
|
|
@@ -1211,18 +1165,20 @@ class PowerConnectionImportForm(BootstrapMixin, BulkImportForm):
|
|
|
self.cleaned_data['csv'] = connection_list
|
|
|
|
|
|
|
|
|
-class PowerPortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
+class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.HiddenInput())
|
|
|
- rack = forms.ModelChoiceField(
|
|
|
+ rack = ChainedModelChoiceField(
|
|
|
queryset=Rack.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
label='Rack',
|
|
|
required=False,
|
|
|
widget=forms.Select(
|
|
|
attrs={'filter-for': 'pdu', 'nullable': 'true'}
|
|
|
)
|
|
|
)
|
|
|
- pdu = forms.ModelChoiceField(
|
|
|
+ pdu = ChainedModelChoiceField(
|
|
|
queryset=Device.objects.all(),
|
|
|
+ chains={'site': 'site', 'rack': 'rack'},
|
|
|
label='PDU',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -1240,8 +1196,9 @@ class PowerPortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
field_to_update='pdu'
|
|
|
)
|
|
|
)
|
|
|
- power_outlet = forms.ModelChoiceField(
|
|
|
+ power_outlet = ChainedModelChoiceField(
|
|
|
queryset=PowerOutlet.objects.all(),
|
|
|
+ chains={'device': 'device'},
|
|
|
label='Outlet',
|
|
|
widget=APISelect(
|
|
|
api_url='/api/dcim/power-outlets/?device_id={{pdu}}',
|
|
@@ -1264,30 +1221,6 @@ class PowerPortConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
if not self.instance.pk:
|
|
|
raise RuntimeError("PowerPortConnectionForm must be initialized with an existing PowerPort instance.")
|
|
|
|
|
|
- # Initialize rack choices if site is set
|
|
|
- if self.initial.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['rack'].choices = []
|
|
|
-
|
|
|
- # Initialize pdu choices if rack or site is set
|
|
|
- if self.initial.get('rack'):
|
|
|
- self.fields['pdu'].queryset = Device.objects.filter(
|
|
|
- rack=self.initial['rack'], device_type__is_pdu=True
|
|
|
- )
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['pdu'].queryset = Device.objects.filter(
|
|
|
- site=self.initial['site'], rack__isnull=True, device_type__is_pdu=True
|
|
|
- )
|
|
|
- else:
|
|
|
- self.fields['pdu'].choices = []
|
|
|
-
|
|
|
- # Initialize power outlet choices if pdu is set
|
|
|
- if self.initial.get('pdu'):
|
|
|
- self.fields['power_outlet'].queryset = PowerOutlet.objects.filter(device=self.initial['pdu'])
|
|
|
- else:
|
|
|
- self.fields['power_outlet'].choices = []
|
|
|
-
|
|
|
|
|
|
#
|
|
|
# Power outlets
|
|
@@ -1307,21 +1240,23 @@ class PowerOutletCreateForm(DeviceComponentForm):
|
|
|
name_pattern = ExpandableNameField(label='Name')
|
|
|
|
|
|
|
|
|
-class PowerOutletConnectionForm(BootstrapMixin, forms.Form):
|
|
|
+class PowerOutletConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
|
|
site = forms.ModelChoiceField(
|
|
|
queryset=Site.objects.all(),
|
|
|
widget=forms.HiddenInput()
|
|
|
)
|
|
|
- rack = forms.ModelChoiceField(
|
|
|
+ rack = ChainedModelChoiceField(
|
|
|
queryset=Rack.objects.all(),
|
|
|
+ chains={'site': 'site'},
|
|
|
label='Rack',
|
|
|
required=False,
|
|
|
widget=forms.Select(
|
|
|
attrs={'filter-for': 'device', 'nullable': 'true'}
|
|
|
)
|
|
|
)
|
|
|
- device = forms.ModelChoiceField(
|
|
|
+ device = ChainedModelChoiceField(
|
|
|
queryset=Device.objects.all(),
|
|
|
+ chains={'site': 'site', 'rack': 'rack'},
|
|
|
label='Device',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -1339,8 +1274,9 @@ class PowerOutletConnectionForm(BootstrapMixin, forms.Form):
|
|
|
field_to_update='device'
|
|
|
)
|
|
|
)
|
|
|
- port = forms.ModelChoiceField(
|
|
|
+ port = ChainedModelChoiceField(
|
|
|
queryset=PowerPort.objects.all(),
|
|
|
+ chains={'device': 'device'},
|
|
|
label='Port',
|
|
|
widget=APISelect(
|
|
|
api_url='/api/dcim/power-ports/?device_id={{device}}',
|
|
@@ -1362,30 +1298,6 @@ class PowerOutletConnectionForm(BootstrapMixin, forms.Form):
|
|
|
'connection_status': 'Status',
|
|
|
}
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
-
|
|
|
- super(PowerOutletConnectionForm, self).__init__(*args, **kwargs)
|
|
|
-
|
|
|
- # Initialize rack choices if site is set
|
|
|
- if self.initial.get('site'):
|
|
|
- self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
|
|
- else:
|
|
|
- self.fields['rack'].choices = []
|
|
|
-
|
|
|
- # Initialize device choices if rack or site is set
|
|
|
- if self.initial.get('rack'):
|
|
|
- self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
|
|
|
- elif self.initial.get('site'):
|
|
|
- self.fields['device'].queryset = Device.objects.filter(site=self.initial['site'], rack__isnull=True)
|
|
|
- else:
|
|
|
- self.fields['device'].choices = []
|
|
|
-
|
|
|
- # Initialize port choices if device is set
|
|
|
- if self.initial.get('device'):
|
|
|
- self.fields['port'].queryset = PowerPort.objects.filter(device=self.initial['device'])
|
|
|
- else:
|
|
|
- self.fields['port'].choices = []
|
|
|
-
|
|
|
|
|
|
#
|
|
|
# Interfaces
|
|
@@ -1468,7 +1380,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
|
|
# Interface connections
|
|
|
#
|
|
|
|
|
|
-class InterfaceConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
+class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
|
|
interface_a = forms.ChoiceField(
|
|
|
choices=[],
|
|
|
widget=SelectWithDisabled,
|
|
@@ -1482,8 +1394,9 @@ class InterfaceConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
attrs={'filter-for': 'rack_b'}
|
|
|
)
|
|
|
)
|
|
|
- rack_b = forms.ModelChoiceField(
|
|
|
+ rack_b = ChainedModelChoiceField(
|
|
|
queryset=Rack.objects.all(),
|
|
|
+ chains = {'site': 'site_b'},
|
|
|
label='Rack',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -1491,8 +1404,9 @@ class InterfaceConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
attrs={'filter-for': 'device_b', 'nullable': 'true'}
|
|
|
)
|
|
|
)
|
|
|
- device_b = forms.ModelChoiceField(
|
|
|
+ device_b = ChainedModelChoiceField(
|
|
|
queryset=Device.objects.all(),
|
|
|
+ chains = {'site': 'site_b', 'rack': 'rack_b'},
|
|
|
label='Device',
|
|
|
required=False,
|
|
|
widget=APISelect(
|
|
@@ -1510,8 +1424,11 @@ class InterfaceConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
field_to_update='device_b'
|
|
|
)
|
|
|
)
|
|
|
- interface_b = forms.ModelChoiceField(
|
|
|
- queryset=Interface.objects.all(),
|
|
|
+ interface_b = ChainedModelChoiceField(
|
|
|
+ queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
|
|
|
+ 'circuit_termination', 'connected_as_a', 'connected_as_b'
|
|
|
+ ),
|
|
|
+ chains = {'device': 'device_b'},
|
|
|
label='Interface',
|
|
|
widget=APISelect(
|
|
|
api_url='/api/dcim/interfaces/?device_id={{device_b}}&type=physical',
|
|
@@ -1537,31 +1454,9 @@ class InterfaceConnectionForm(BootstrapMixin, forms.ModelForm):
|
|
|
(iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces
|
|
|
]
|
|
|
|
|
|
- # Initialize rack_b choices if site_b is set
|
|
|
- if self.initial.get('site_b'):
|
|
|
- self.fields['rack_b'].queryset = Rack.objects.filter(site=self.initial['site_b'])
|
|
|
- else:
|
|
|
- self.fields['rack_b'].choices = []
|
|
|
-
|
|
|
- # Initialize device_b choices if rack_b or site_b is set
|
|
|
- if self.initial.get('rack_b'):
|
|
|
- self.fields['device_b'].queryset = Device.objects.filter(rack=self.initial['rack_b'])
|
|
|
- elif self.initial.get('site_b'):
|
|
|
- self.fields['device_b'].queryset = Device.objects.filter(site=self.initial['site_b'], rack__isnull=True)
|
|
|
- else:
|
|
|
- self.fields['device_b'].choices = []
|
|
|
-
|
|
|
- # Initialize interface_b choices if device_b is set
|
|
|
- if self.initial.get('device_b'):
|
|
|
- device_b_interfaces = Interface.objects.filter(device=self.initial['device_b']).exclude(
|
|
|
- form_factor__in=VIRTUAL_IFACE_TYPES
|
|
|
- ).select_related(
|
|
|
- 'circuit_termination', 'connected_as_a', 'connected_as_b'
|
|
|
- )
|
|
|
- else:
|
|
|
- device_b_interfaces = []
|
|
|
+ # Mark connected interfaces as disabled
|
|
|
self.fields['interface_b'].choices = [
|
|
|
- (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_b_interfaces
|
|
|
+ (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in self.fields['interface_b'].queryset
|
|
|
]
|
|
|
|
|
|
|