mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Closes #1167: Introduced ChainedModelChoiceFields
This commit is contained in:
parent
0f97478b55
commit
58bb029666
@ -5,8 +5,8 @@ from dcim.models import Site, Device, Interface, Rack, VIRTUAL_IFACE_TYPES
|
||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
|
||||
SlugField,
|
||||
APISelect, BootstrapMixin, BulkImportForm, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, CSVDataField,
|
||||
FilterChoiceField, Livesearch, SmallTextarea, SlugField,
|
||||
)
|
||||
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
@ -152,15 +152,16 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
# Circuit terminations
|
||||
#
|
||||
|
||||
class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
||||
site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'rack'}
|
||||
)
|
||||
)
|
||||
rack = forms.ModelChoiceField(
|
||||
rack = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
chains={'site': 'site'},
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
@ -168,8 +169,9 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
attrs={'filter-for': 'device', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
device = forms.ModelChoiceField(
|
||||
device = ChainedModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
chains={'site': 'site', 'rack': 'rack'},
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
@ -187,8 +189,11 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
field_to_update='device'
|
||||
)
|
||||
)
|
||||
interface = forms.ModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
interface = ChainedModelChoiceField(
|
||||
queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
|
||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
||||
),
|
||||
chains={'device': 'device'},
|
||||
required=False,
|
||||
label='Interface',
|
||||
widget=APISelect(
|
||||
@ -210,51 +215,16 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
'term_side': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, instance=None, initial=None, *args, **kwargs):
|
||||
|
||||
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
|
||||
# Initialize helper selectors
|
||||
if instance and instance.interface is not None:
|
||||
initial['rack'] = instance.interface.device.rack
|
||||
initial['device'] = instance.interface.device
|
||||
|
||||
# If an interface has been assigned, initialize rack and device
|
||||
if self.instance.interface:
|
||||
self.initial['rack'] = self.instance.interface.device.rack
|
||||
self.initial['device'] = self.instance.interface.device
|
||||
super(CircuitTerminationForm, self).__init__(instance=instance, initial=initial, *args, **kwargs)
|
||||
|
||||
# Limit rack choices
|
||||
if self.is_bound:
|
||||
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 = []
|
||||
|
||||
# Limit device choices
|
||||
if self.is_bound and self.data.get('rack'):
|
||||
self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack'])
|
||||
elif self.initial.get('rack'):
|
||||
self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
|
||||
else:
|
||||
self.fields['device'].choices = []
|
||||
|
||||
# Limit interface choices
|
||||
if self.is_bound and self.data.get('device'):
|
||||
interfaces = Interface.objects.filter(device=self.data['device']).exclude(
|
||||
form_factor__in=VIRTUAL_IFACE_TYPES
|
||||
).select_related(
|
||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
||||
)
|
||||
self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
|
||||
elif self.initial.get('device'):
|
||||
interfaces = Interface.objects.filter(device=self.initial['device']).exclude(
|
||||
form_factor__in=VIRTUAL_IFACE_TYPES
|
||||
).select_related(
|
||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
||||
)
|
||||
self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
|
||||
else:
|
||||
interfaces = []
|
||||
# Mark connected interfaces as disabled
|
||||
self.fields['interface'].choices = [
|
||||
(iface.id, {
|
||||
'label': iface.name,
|
||||
'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
|
||||
}) for iface in interfaces
|
||||
(iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in self.fields['interface'].queryset
|
||||
]
|
||||
|
@ -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(
|
||||
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 = 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'
|
||||
)
|
||||
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')
|
||||
)
|
||||
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):
|
||||
|
||||
super(DeviceForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.instance.pk:
|
||||
def __init__(self, instance=None, initial=None, *args, **kwargs):
|
||||
|
||||
# Initialize helper selections
|
||||
self.initial['site'] = self.instance.site
|
||||
self.initial['manufacturer'] = self.instance.device_type.manufacturer
|
||||
if instance and instance.device_type is not None:
|
||||
initial['manufacturer'] = instance.device_type.manufacturer
|
||||
|
||||
super(DeviceForm, self).__init__(instance=instance, initial=initial, *args, **kwargs)
|
||||
|
||||
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
|
||||
]
|
||||
|
||||
|
||||
|
@ -5,8 +5,8 @@ from dcim.models import Site, Rack, Device, Interface
|
||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, BulkImportForm, CSVDataField, ExpandableIPAddressField,
|
||||
FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
||||
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, BulkImportForm, ChainedFieldsMixin, ChainedModelChoiceField,
|
||||
CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
||||
)
|
||||
|
||||
from .models import (
|
||||
@ -163,12 +163,17 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
|
||||
# Prefixes
|
||||
#
|
||||
|
||||
class PrefixForm(BootstrapMixin, CustomFieldForm):
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
||||
widget=forms.Select(attrs={'filter-for': 'vlan', 'nullable': 'true'}))
|
||||
vlan = forms.ModelChoiceField(queryset=VLAN.objects.all(), required=False, label='VLAN',
|
||||
widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}',
|
||||
display_field='display_name'))
|
||||
class PrefixForm(BootstrapMixin, ChainedFieldsMixin, CustomFieldForm):
|
||||
site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
|
||||
attrs={'filter-for': 'vlan', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
vlan = ChainedModelChoiceField(
|
||||
queryset=VLAN.objects.all(), chains={'site': 'site'}, required=False, label='VLAN', widget=APISelect(
|
||||
api_url='/api/ipam/vlans/?site_id={{site}}', display_field='display_name'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Prefix
|
||||
@ -179,14 +184,6 @@ class PrefixForm(BootstrapMixin, CustomFieldForm):
|
||||
|
||||
self.fields['vrf'].empty_label = 'Global'
|
||||
|
||||
# Initialize field without choices to avoid pulling all VLANs from the database
|
||||
if self.is_bound and self.data.get('site'):
|
||||
self.fields['vlan'].queryset = VLAN.objects.filter(site__pk=self.data['site'])
|
||||
elif self.initial.get('site'):
|
||||
self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
|
||||
else:
|
||||
self.fields['vlan'].queryset = VLAN.objects.filter(site=None)
|
||||
|
||||
|
||||
class PrefixFromCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
||||
@ -214,7 +211,6 @@ class PrefixFromCSVForm(forms.ModelForm):
|
||||
vlan_group_name = self.cleaned_data.get('vlan_group_name')
|
||||
vlan_vid = self.cleaned_data.get('vlan_vid')
|
||||
vlan_group = None
|
||||
vlan = None
|
||||
|
||||
# Validate VLAN group
|
||||
if vlan_group_name:
|
||||
@ -310,38 +306,93 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
# IP addresses
|
||||
#
|
||||
|
||||
class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
|
||||
class IPAddressForm(BootstrapMixin, ChainedFieldsMixin, ReturnURLForm, CustomFieldForm):
|
||||
interface_site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
label='Site',
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'interface_rack'}
|
||||
)
|
||||
)
|
||||
interface_rack = forms.ModelChoiceField(
|
||||
queryset=Rack.objects.all(), required=False, label='Rack', widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{interface_site}}', display_field='display_name',
|
||||
interface_rack = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
chains={'site': 'interface_site'},
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{interface_site}}',
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'interface_device', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
interface_device = forms.ModelChoiceField(
|
||||
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
|
||||
interface_device = ChainedModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
chains={'site': 'interface_site', 'rack': 'interface_rack'},
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/?site_id={{interface_site}}&rack_id={{interface_rack}}',
|
||||
display_field='display_name', attrs={'filter-for': 'interface'}
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'interface'}
|
||||
)
|
||||
)
|
||||
interface = ChainedModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
chains={'device': 'interface_device'},
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/interfaces/?device_id={{interface_device}}'
|
||||
)
|
||||
)
|
||||
nat_site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
label='Site',
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'nat_device'}
|
||||
)
|
||||
)
|
||||
nat_device = forms.ModelChoiceField(
|
||||
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
|
||||
api_url='/api/dcim/devices/?site_id={{nat_site}}', display_field='display_name',
|
||||
nat_rack = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
chains={'site': 'nat_site'},
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{interface_site}}',
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'nat_device', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
nat_device = ChainedModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
chains={'site': 'nat_site'},
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/?site_id={{nat_site}}',
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'nat_inside'}
|
||||
)
|
||||
)
|
||||
nat_inside = ChainedModelChoiceField(
|
||||
queryset=IPAddress.objects.all(),
|
||||
chains={'interface__device': 'nat_device'},
|
||||
required=False,
|
||||
label='IP Address',
|
||||
widget=APISelect(
|
||||
api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
|
||||
display_field='address'
|
||||
)
|
||||
)
|
||||
livesearch = forms.CharField(
|
||||
required=False, label='IP Address', widget=Livesearch(
|
||||
query_key='q', query_url='ipam-api:ipaddress-list', field_to_update='nat_inside', obj_label='address'
|
||||
required=False,
|
||||
label='IP Address',
|
||||
widget=Livesearch(
|
||||
query_key='q',
|
||||
query_url='ipam-api:ipaddress-list',
|
||||
field_to_update='nat_inside',
|
||||
obj_label='address'
|
||||
)
|
||||
)
|
||||
primary_for_device = forms.BooleanField(required=False, label='Make this the primary IP for the device')
|
||||
@ -349,46 +400,25 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['address', 'vrf', 'tenant', 'status', 'description', 'interface', 'primary_for_device', 'nat_inside']
|
||||
widgets = {
|
||||
'interface': APISelect(api_url='/api/dcim/interfaces/?device_id={{interface_device}}'),
|
||||
'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address')
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(IPAddressForm, self).__init__(*args, **kwargs)
|
||||
def __init__(self, instance=None, initial=None, *args, **kwargs):
|
||||
|
||||
# Initialize interface selectors
|
||||
if instance and instance.interface is not None:
|
||||
initial['interface_site'] = instance.interface.device.site
|
||||
initial['interface_rack'] = instance.interface.device.rack
|
||||
initial['interface_device'] = instance.interface.device
|
||||
|
||||
# Initialize NAT selectors
|
||||
if instance and instance.nat_inside is not None:
|
||||
initial['nat_site'] = instance.nat_inside.device.site
|
||||
initial['nat_rack'] = instance.nat_inside.device.rack
|
||||
initial['nat_device'] = instance.nat_inside.device
|
||||
|
||||
super(IPAddressForm, self).__init__(instance=instance, initial=initial, *args, **kwargs)
|
||||
|
||||
self.fields['vrf'].empty_label = 'Global'
|
||||
|
||||
# If an interface has been assigned, initialize site, rack, and device
|
||||
if self.instance.interface:
|
||||
self.initial['interface_site'] = self.instance.interface.device.site
|
||||
self.initial['interface_rack'] = self.instance.interface.device.rack
|
||||
self.initial['interface_device'] = self.instance.interface.device
|
||||
|
||||
# Limit rack choices
|
||||
if self.is_bound and self.data.get('interface_site'):
|
||||
self.fields['interface_rack'].queryset = Rack.objects.filter(site__pk=self.data['interface_site'])
|
||||
elif self.initial.get('interface_site'):
|
||||
self.fields['interface_rack'].queryset = Rack.objects.filter(site=self.initial['interface_site'])
|
||||
else:
|
||||
self.fields['interface_rack'].choices = []
|
||||
|
||||
# Limit device choices
|
||||
if self.is_bound and self.data.get('interface_rack'):
|
||||
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.data['interface_rack'])
|
||||
elif self.initial.get('interface_rack'):
|
||||
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.initial['interface_rack'])
|
||||
else:
|
||||
self.fields['interface_device'].choices = []
|
||||
|
||||
# Limit interface choices
|
||||
if self.is_bound and self.data.get('interface_device'):
|
||||
self.fields['interface'].queryset = Interface.objects.filter(device=self.data['interface_device'])
|
||||
elif self.initial.get('interface_device'):
|
||||
self.fields['interface'].queryset = Interface.objects.filter(device=self.initial['interface_device'])
|
||||
else:
|
||||
self.fields['interface'].choices = []
|
||||
|
||||
# Initialize primary_for_device if IP address is already assigned
|
||||
if self.instance.interface is not None:
|
||||
device = self.instance.interface.device
|
||||
@ -398,38 +428,6 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
|
||||
):
|
||||
self.initial['primary_for_device'] = True
|
||||
|
||||
if self.instance.nat_inside:
|
||||
nat_inside = self.instance.nat_inside
|
||||
# If the IP is assigned to an interface, populate site/device fields accordingly
|
||||
if self.instance.nat_inside.interface:
|
||||
self.initial['nat_site'] = self.instance.nat_inside.interface.device.site.pk
|
||||
self.initial['nat_device'] = self.instance.nat_inside.interface.device.pk
|
||||
self.fields['nat_device'].queryset = Device.objects.filter(
|
||||
site=nat_inside.interface.device.site
|
||||
)
|
||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(
|
||||
interface__device=nat_inside.interface.device
|
||||
)
|
||||
else:
|
||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
|
||||
else:
|
||||
# Initialize nat_device choices if nat_site is set
|
||||
if self.is_bound and self.data.get('nat_site'):
|
||||
self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site'])
|
||||
elif self.initial.get('nat_site'):
|
||||
self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site'])
|
||||
else:
|
||||
self.fields['nat_device'].choices = []
|
||||
# Initialize nat_inside choices if nat_device is set
|
||||
if self.is_bound and self.data.get('nat_device'):
|
||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(
|
||||
interface__device__pk=self.data['nat_device'])
|
||||
elif self.initial.get('nat_device'):
|
||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(
|
||||
interface__device__pk=self.initial['nat_device'])
|
||||
else:
|
||||
self.fields['nat_inside'].choices = []
|
||||
|
||||
def clean(self):
|
||||
super(IPAddressForm, self).clean()
|
||||
|
||||
@ -602,10 +600,22 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||
# VLANs
|
||||
#
|
||||
|
||||
class VLANForm(BootstrapMixin, CustomFieldForm):
|
||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, label='Group', widget=APISelect(
|
||||
class VLANForm(BootstrapMixin, ChainedFieldsMixin, CustomFieldForm):
|
||||
site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'group', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
group = ChainedModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
chains={'site': 'site'},
|
||||
required=False,
|
||||
label='Group',
|
||||
widget=APISelect(
|
||||
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
@ -618,21 +628,6 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
|
||||
'status': "Operational status of this VLAN",
|
||||
'role': "The primary function of this VLAN",
|
||||
}
|
||||
widgets = {
|
||||
'site': forms.Select(attrs={'filter-for': 'group', 'nullable': 'true'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(VLANForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Limit VLAN group choices
|
||||
if self.is_bound and self.data.get('site'):
|
||||
self.fields['group'].queryset = VLANGroup.objects.filter(site__pk=self.data['site'])
|
||||
elif self.initial.get('site'):
|
||||
self.fields['group'].queryset = VLANGroup.objects.filter(site=self.initial['site'])
|
||||
else:
|
||||
self.fields['group'].queryset = VLANGroup.objects.filter(site=None)
|
||||
|
||||
|
||||
class VLANFromCSVForm(forms.ModelForm):
|
||||
@ -663,7 +658,7 @@ class VLANFromCSVForm(forms.ModelForm):
|
||||
group_name = self.cleaned_data.get('group_name')
|
||||
if group_name:
|
||||
try:
|
||||
vlan_group = VLANGroup.objects.get(site=self.cleaned_data.get('site'), name=group_name)
|
||||
VLANGroup.objects.get(site=self.cleaned_data.get('site'), name=group_name)
|
||||
except VLANGroup.DoesNotExist:
|
||||
self.add_error('group_name', "Invalid VLAN group {}.".format(group_name))
|
||||
|
||||
|
@ -2,15 +2,12 @@ from django_tables2 import RequestConfig
|
||||
import netaddr
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.contrib import messages
|
||||
from django.db.models import Count, Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.models import Device
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
from utilities.views import (
|
||||
BulkAddView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
|
@ -331,6 +331,25 @@ 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(ChainedModelChoiceField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class SlugField(forms.SlugField):
|
||||
|
||||
def __init__(self, slug_source='name', *args, **kwargs):
|
||||
@ -411,6 +430,32 @@ 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(ChainedFieldsMixin, self).__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.items():
|
||||
if self.is_bound and self.data.get(parent_field):
|
||||
filters_dict[db_field] = self.data.get(parent_field)
|
||||
elif self.initial.get(parent_field):
|
||||
filters_dict[db_field] = self.initial[parent_field]
|
||||
else:
|
||||
filters_dict[db_field] = None
|
||||
|
||||
if filters_dict:
|
||||
field.queryset = field.queryset.filter(**filters_dict)
|
||||
else:
|
||||
field.queryset = field.queryset.none()
|
||||
|
||||
|
||||
class ReturnURLForm(forms.Form):
|
||||
"""
|
||||
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
||||
|
Loading…
Reference in New Issue
Block a user