mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
161 lines
6.8 KiB
Python
161 lines
6.8 KiB
Python
from django import forms
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from dcim.choices import *
|
|
from dcim.constants import *
|
|
from utilities.forms import get_field_value
|
|
|
|
__all__ = (
|
|
'InterfaceCommonForm',
|
|
'ModuleCommonForm'
|
|
)
|
|
|
|
|
|
class InterfaceCommonForm(forms.Form):
|
|
mtu = forms.IntegerField(
|
|
required=False,
|
|
min_value=INTERFACE_MTU_MIN,
|
|
max_value=INTERFACE_MTU_MAX,
|
|
label=_('MTU')
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Determine the selected 802.1Q mode
|
|
interface_mode = get_field_value(self, 'mode')
|
|
|
|
# Delete VLAN tagging fields which are not relevant for the selected mode
|
|
if interface_mode in (InterfaceModeChoices.MODE_ACCESS, InterfaceModeChoices.MODE_TAGGED_ALL):
|
|
del self.fields['tagged_vlans']
|
|
elif not interface_mode:
|
|
del self.fields['vlan_group']
|
|
del self.fields['untagged_vlan']
|
|
del self.fields['tagged_vlans']
|
|
if interface_mode != InterfaceModeChoices.MODE_Q_IN_Q:
|
|
del self.fields['qinq_svlan']
|
|
|
|
if self.instance and self.instance.pk:
|
|
filter_name = f'{self._meta.model._meta.model_name}_id'
|
|
self.fields['primary_mac_address'].widget.add_query_param(filter_name, self.instance.pk)
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
|
if 'tagged_vlans' in self.fields.keys():
|
|
tagged_vlans = self.cleaned_data.get('tagged_vlans') if self.is_bound else \
|
|
self.get_initial_for_field(self.fields['tagged_vlans'], 'tagged_vlans')
|
|
else:
|
|
tagged_vlans = []
|
|
|
|
# Validate tagged VLANs; must be a global VLAN or in the same site
|
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED and tagged_vlans:
|
|
valid_sites = [None, self.cleaned_data[parent_field].site]
|
|
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
|
|
|
if invalid_vlans:
|
|
raise forms.ValidationError({
|
|
'tagged_vlans': _(
|
|
"The tagged VLANs ({vlans}) must belong to the same site as the interface's parent device/VM, "
|
|
"or they must be global"
|
|
).format(vlans=', '.join(invalid_vlans))
|
|
})
|
|
# Validate mode change
|
|
if self.instance.pk and (self.instance.mode != self.cleaned_data['mode']):
|
|
if 'untagged_vlan' not in self.cleaned_data and self.instance.untagged_vlan is not None:
|
|
self.instance.untagged_vlan = None
|
|
if 'tagged_vlans' not in self.cleaned_data and self.instance.tagged_vlans is not None:
|
|
self.instance.tagged_vlans.clear()
|
|
|
|
|
|
class ModuleCommonForm(forms.Form):
|
|
|
|
def _get_module_bay_tree(self, module_bay):
|
|
module_bays = []
|
|
while module_bay:
|
|
module_bays.append(module_bay)
|
|
if module_bay.module:
|
|
module_bay = module_bay.module.module_bay
|
|
else:
|
|
module_bay = None
|
|
|
|
module_bays.reverse()
|
|
return module_bays
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
|
|
replicate_components = self.cleaned_data.get('replicate_components')
|
|
adopt_components = self.cleaned_data.get('adopt_components')
|
|
device = self.cleaned_data.get('device')
|
|
module_type = self.cleaned_data.get('module_type')
|
|
module_bay = self.cleaned_data.get('module_bay')
|
|
|
|
if adopt_components:
|
|
self.instance._adopt_components = True
|
|
|
|
# Bail out if we are not installing a new module or if we are not replicating components (or if
|
|
# validation has already failed)
|
|
if self.errors or self.instance.pk or not replicate_components:
|
|
self.instance._disable_replication = True
|
|
return
|
|
|
|
module_bays = self._get_module_bay_tree(module_bay)
|
|
|
|
for templates, component_attribute in [
|
|
("consoleporttemplates", "consoleports"),
|
|
("consoleserverporttemplates", "consoleserverports"),
|
|
("interfacetemplates", "interfaces"),
|
|
("powerporttemplates", "powerports"),
|
|
("poweroutlettemplates", "poweroutlets"),
|
|
("rearporttemplates", "rearports"),
|
|
("frontporttemplates", "frontports")
|
|
]:
|
|
# Prefetch installed components
|
|
installed_components = {
|
|
component.name: component for component in getattr(device, component_attribute).all()
|
|
}
|
|
|
|
# Get the templates for the module type.
|
|
for template in getattr(module_type, templates).all():
|
|
resolved_name = template.name
|
|
# Installing modules with placeholders require that the bay has a position value
|
|
if MODULE_TOKEN in template.name:
|
|
if not module_bay.position:
|
|
raise forms.ValidationError(
|
|
_("Cannot install module with placeholder values in a module bay with no position defined.")
|
|
)
|
|
|
|
if len(module_bays) != template.name.count(MODULE_TOKEN):
|
|
raise forms.ValidationError(
|
|
_(
|
|
"Cannot install module with placeholder values in a module bay tree {level} in tree "
|
|
"but {tokens} placeholders given."
|
|
).format(
|
|
level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
|
|
)
|
|
)
|
|
|
|
for module_bay in module_bays:
|
|
resolved_name = resolved_name.replace(MODULE_TOKEN, module_bay.position, 1)
|
|
|
|
existing_item = installed_components.get(resolved_name)
|
|
|
|
# It is not possible to adopt components already belonging to a module
|
|
if adopt_components and existing_item and existing_item.module:
|
|
raise forms.ValidationError(
|
|
_("Cannot adopt {model} {name} as it already belongs to a module").format(
|
|
model=template.component_model.__name__,
|
|
name=resolved_name
|
|
)
|
|
)
|
|
|
|
# If we are not adopting components we error if the component exists
|
|
if not adopt_components and resolved_name in installed_components:
|
|
raise forms.ValidationError(
|
|
_("A {model} named {name} already exists").format(
|
|
model=template.component_model.__name__,
|
|
name=resolved_name
|
|
)
|
|
)
|