mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
276 lines
9.2 KiB
Python
276 lines
9.2 KiB
Python
from django import forms
|
|
|
|
from dcim.models import *
|
|
from netbox.forms import NetBoxModelForm
|
|
from utilities.forms import (
|
|
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
|
|
)
|
|
|
|
__all__ = (
|
|
'ComponentTemplateCreateForm',
|
|
'DeviceComponentCreateForm',
|
|
'FrontPortCreateForm',
|
|
'FrontPortTemplateCreateForm',
|
|
'InventoryItemCreateForm',
|
|
'ModularComponentTemplateCreateForm',
|
|
'ModuleBayCreateForm',
|
|
'ModuleBayTemplateCreateForm',
|
|
'VirtualChassisCreateForm',
|
|
)
|
|
|
|
|
|
class ComponentCreateForm(BootstrapMixin, forms.Form):
|
|
"""
|
|
Subclass this form when facilitating the creation of one or more device component or component templates based on
|
|
a name pattern.
|
|
"""
|
|
name_pattern = ExpandableNameField(
|
|
label='Name'
|
|
)
|
|
label_pattern = ExpandableNameField(
|
|
label='Label',
|
|
required=False,
|
|
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
|
)
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
|
|
# Validate that all patterned fields generate an equal number of values
|
|
patterned_fields = [
|
|
field_name for field_name in self.fields if field_name.endswith('_pattern')
|
|
]
|
|
pattern_count = len(self.cleaned_data['name_pattern'])
|
|
for field_name in patterned_fields:
|
|
value_count = len(self.cleaned_data[field_name])
|
|
if self.cleaned_data[field_name] and value_count != pattern_count:
|
|
raise forms.ValidationError({
|
|
field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
|
|
f'expected.'
|
|
}, code='label_pattern_mismatch')
|
|
|
|
|
|
class ComponentTemplateCreateForm(ComponentCreateForm):
|
|
"""
|
|
Creation form for component templates that can be assigned only to a DeviceType.
|
|
"""
|
|
device_type = DynamicModelChoiceField(
|
|
queryset=DeviceType.objects.all(),
|
|
)
|
|
field_order = ('device_type', 'name_pattern', 'label_pattern')
|
|
|
|
|
|
class ModularComponentTemplateCreateForm(ComponentCreateForm):
|
|
"""
|
|
Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
|
|
"""
|
|
device_type = DynamicModelChoiceField(
|
|
queryset=DeviceType.objects.all(),
|
|
required=False
|
|
)
|
|
module_type = DynamicModelChoiceField(
|
|
queryset=ModuleType.objects.all(),
|
|
required=False
|
|
)
|
|
field_order = ('device_type', 'module_type', 'name_pattern', 'label_pattern')
|
|
|
|
|
|
class DeviceComponentCreateForm(ComponentCreateForm):
|
|
device = DynamicModelChoiceField(
|
|
queryset=Device.objects.all()
|
|
)
|
|
field_order = ('device', 'name_pattern', 'label_pattern')
|
|
|
|
|
|
class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
|
rear_port_set = forms.MultipleChoiceField(
|
|
choices=[],
|
|
label='Rear ports',
|
|
help_text='Select one rear port assignment for each front port being created.',
|
|
)
|
|
field_order = (
|
|
'device_type', 'name_pattern', 'label_pattern', 'rear_port_set',
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# TODO: This needs better validation
|
|
if 'device_type' in self.initial or self.data.get('device_type'):
|
|
parent = DeviceType.objects.get(
|
|
pk=self.initial.get('device_type') or self.data.get('device_type')
|
|
)
|
|
elif 'module_type' in self.initial or self.data.get('module_type'):
|
|
parent = ModuleType.objects.get(
|
|
pk=self.initial.get('module_type') or self.data.get('module_type')
|
|
)
|
|
else:
|
|
return
|
|
|
|
# Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
|
|
occupied_port_positions = [
|
|
(front_port.rear_port_id, front_port.rear_port_position)
|
|
for front_port in parent.frontporttemplates.all()
|
|
]
|
|
|
|
# Populate rear port choices
|
|
choices = []
|
|
rear_ports = parent.rearporttemplates.all()
|
|
for rear_port in rear_ports:
|
|
for i in range(1, rear_port.positions + 1):
|
|
if (rear_port.pk, i) not in occupied_port_positions:
|
|
choices.append(
|
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
|
)
|
|
self.fields['rear_port_set'].choices = choices
|
|
|
|
def get_iterative_data(self, iteration):
|
|
|
|
# Assign rear port and position from selected set
|
|
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
|
|
|
return {
|
|
'rear_port': int(rear_port),
|
|
'rear_port_position': int(position),
|
|
}
|
|
|
|
|
|
class FrontPortCreateForm(DeviceComponentCreateForm):
|
|
rear_port_set = forms.MultipleChoiceField(
|
|
choices=[],
|
|
label='Rear ports',
|
|
help_text='Select one rear port assignment for each front port being created.',
|
|
)
|
|
field_order = (
|
|
'device', 'name_pattern', 'label_pattern', 'rear_port_set',
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
device = Device.objects.get(
|
|
pk=self.initial.get('device') or self.data.get('device')
|
|
)
|
|
|
|
# Determine which rear port positions are occupied. These will be excluded from the list of available
|
|
# mappings.
|
|
occupied_port_positions = [
|
|
(front_port.rear_port_id, front_port.rear_port_position)
|
|
for front_port in device.frontports.all()
|
|
]
|
|
|
|
# Populate rear port choices
|
|
choices = []
|
|
rear_ports = RearPort.objects.filter(device=device)
|
|
for rear_port in rear_ports:
|
|
for i in range(1, rear_port.positions + 1):
|
|
if (rear_port.pk, i) not in occupied_port_positions:
|
|
choices.append(
|
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
|
)
|
|
self.fields['rear_port_set'].choices = choices
|
|
|
|
def get_iterative_data(self, iteration):
|
|
|
|
# Assign rear port and position from selected set
|
|
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
|
|
|
return {
|
|
'rear_port': int(rear_port),
|
|
'rear_port_position': int(position),
|
|
}
|
|
|
|
|
|
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
|
|
position_pattern = ExpandableNameField(
|
|
label='Position',
|
|
required=False,
|
|
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
|
)
|
|
field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
|
|
|
|
|
|
class ModuleBayCreateForm(DeviceComponentCreateForm):
|
|
position_pattern = ExpandableNameField(
|
|
label='Position',
|
|
required=False,
|
|
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
|
)
|
|
field_order = ('device', 'name_pattern', 'label_pattern', 'position_pattern')
|
|
|
|
|
|
class InventoryItemCreateForm(ComponentCreateForm):
|
|
# Device is assigned by the model form
|
|
field_order = ('name_pattern', 'label_pattern')
|
|
|
|
|
|
class VirtualChassisCreateForm(NetBoxModelForm):
|
|
region = DynamicModelChoiceField(
|
|
queryset=Region.objects.all(),
|
|
required=False,
|
|
initial_params={
|
|
'sites': '$site'
|
|
}
|
|
)
|
|
site_group = DynamicModelChoiceField(
|
|
queryset=SiteGroup.objects.all(),
|
|
required=False,
|
|
initial_params={
|
|
'sites': '$site'
|
|
}
|
|
)
|
|
site = DynamicModelChoiceField(
|
|
queryset=Site.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'region_id': '$region',
|
|
'group_id': '$site_group',
|
|
}
|
|
)
|
|
rack = DynamicModelChoiceField(
|
|
queryset=Rack.objects.all(),
|
|
required=False,
|
|
null_option='None',
|
|
query_params={
|
|
'site_id': '$site'
|
|
}
|
|
)
|
|
members = DynamicModelMultipleChoiceField(
|
|
queryset=Device.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'site_id': '$site',
|
|
'rack_id': '$rack',
|
|
}
|
|
)
|
|
initial_position = forms.IntegerField(
|
|
initial=1,
|
|
required=False,
|
|
help_text='Position of the first member device. Increases by one for each additional member.'
|
|
)
|
|
|
|
class Meta:
|
|
model = VirtualChassis
|
|
fields = [
|
|
'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
|
|
]
|
|
|
|
def clean(self):
|
|
if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
|
|
raise forms.ValidationError({
|
|
'initial_position': "A position must be specified for the first VC member."
|
|
})
|
|
|
|
def save(self, *args, **kwargs):
|
|
instance = super().save(*args, **kwargs)
|
|
|
|
# Assign VC members
|
|
if instance.pk and self.cleaned_data['members']:
|
|
initial_position = self.cleaned_data.get('initial_position', 1)
|
|
for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
|
|
member.virtual_chassis = instance
|
|
member.vc_position = i
|
|
member.save()
|
|
|
|
return instance
|