mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
644 lines
20 KiB
Python
644 lines
20 KiB
Python
from django import forms
|
|
|
|
from dcim.choices import *
|
|
from dcim.constants import *
|
|
from dcim.models import *
|
|
from extras.forms import CustomFieldModelForm, CustomFieldsMixin
|
|
from extras.models import Tag
|
|
from ipam.models import VLAN
|
|
from utilities.forms import (
|
|
add_blank_choice, BootstrapMixin, ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
|
ExpandableNameField, StaticSelect,
|
|
)
|
|
from wireless.choices import *
|
|
from .common import InterfaceCommonForm
|
|
|
|
__all__ = (
|
|
'ConsolePortCreateForm',
|
|
'ConsolePortTemplateCreateForm',
|
|
'ConsoleServerPortCreateForm',
|
|
'ConsoleServerPortTemplateCreateForm',
|
|
'DeviceBayCreateForm',
|
|
'DeviceBayTemplateCreateForm',
|
|
'FrontPortCreateForm',
|
|
'FrontPortTemplateCreateForm',
|
|
'InterfaceCreateForm',
|
|
'InterfaceTemplateCreateForm',
|
|
'InventoryItemCreateForm',
|
|
'PowerOutletCreateForm',
|
|
'PowerOutletTemplateCreateForm',
|
|
'PowerPortCreateForm',
|
|
'PowerPortTemplateCreateForm',
|
|
'RearPortCreateForm',
|
|
'RearPortTemplateCreateForm',
|
|
'VirtualChassisCreateForm',
|
|
)
|
|
|
|
|
|
class ComponentForm(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 the number of components being created from both the name_pattern and label_pattern are equal
|
|
if self.cleaned_data['label_pattern']:
|
|
name_pattern_count = len(self.cleaned_data['name_pattern'])
|
|
label_pattern_count = len(self.cleaned_data['label_pattern'])
|
|
if name_pattern_count != label_pattern_count:
|
|
raise forms.ValidationError({
|
|
'label_pattern': f'The provided name pattern will create {name_pattern_count} components, however '
|
|
f'{label_pattern_count} labels will be generated. These counts must match.'
|
|
}, code='label_pattern_mismatch')
|
|
|
|
|
|
class VirtualChassisCreateForm(BootstrapMixin, CustomFieldModelForm):
|
|
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.'
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
queryset=Tag.objects.all(),
|
|
required=False
|
|
)
|
|
|
|
class Meta:
|
|
model = VirtualChassis
|
|
fields = [
|
|
'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
|
|
]
|
|
|
|
def save(self, *args, **kwargs):
|
|
instance = super().save(*args, **kwargs)
|
|
|
|
# Assign VC members
|
|
if instance.pk:
|
|
initial_position = self.cleaned_data.get('initial_position') or 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
|
|
|
|
|
|
#
|
|
# Component templates
|
|
#
|
|
|
|
class ComponentTemplateCreateForm(BootstrapMixin, ComponentForm):
|
|
"""
|
|
Base form for the creation of device component templates (subclassed from ComponentTemplateModel).
|
|
"""
|
|
manufacturer = DynamicModelChoiceField(
|
|
queryset=Manufacturer.objects.all(),
|
|
required=False,
|
|
initial_params={
|
|
'device_types': 'device_type'
|
|
}
|
|
)
|
|
device_type = DynamicModelChoiceField(
|
|
queryset=DeviceType.objects.all(),
|
|
query_params={
|
|
'manufacturer_id': '$manufacturer'
|
|
}
|
|
)
|
|
description = forms.CharField(
|
|
required=False
|
|
)
|
|
|
|
|
|
class ConsolePortTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
|
widget=StaticSelect()
|
|
)
|
|
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description')
|
|
|
|
|
|
class ConsoleServerPortTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
|
widget=StaticSelect()
|
|
)
|
|
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description')
|
|
|
|
|
|
class PowerPortTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerPortTypeChoices),
|
|
required=False
|
|
)
|
|
maximum_draw = forms.IntegerField(
|
|
min_value=1,
|
|
required=False,
|
|
help_text="Maximum power draw (watts)"
|
|
)
|
|
allocated_draw = forms.IntegerField(
|
|
min_value=1,
|
|
required=False,
|
|
help_text="Allocated power draw (watts)"
|
|
)
|
|
field_order = (
|
|
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw',
|
|
'description',
|
|
)
|
|
|
|
|
|
class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
|
required=False
|
|
)
|
|
power_port = forms.ModelChoiceField(
|
|
queryset=PowerPortTemplate.objects.all(),
|
|
required=False
|
|
)
|
|
feed_leg = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
field_order = (
|
|
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg',
|
|
'description',
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Limit power_port choices to current DeviceType
|
|
device_type = DeviceType.objects.get(
|
|
pk=self.initial.get('device_type') or self.data.get('device_type')
|
|
)
|
|
self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(
|
|
device_type=device_type
|
|
)
|
|
|
|
|
|
class InterfaceTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=InterfaceTypeChoices,
|
|
widget=StaticSelect()
|
|
)
|
|
mgmt_only = forms.BooleanField(
|
|
required=False,
|
|
label='Management only'
|
|
)
|
|
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'mgmt_only', 'description')
|
|
|
|
|
|
class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=PortTypeChoices,
|
|
widget=StaticSelect()
|
|
)
|
|
color = ColorField(
|
|
required=False
|
|
)
|
|
rear_port_set = forms.MultipleChoiceField(
|
|
choices=[],
|
|
label='Rear ports',
|
|
help_text='Select one rear port assignment for each front port being created.',
|
|
)
|
|
field_order = (
|
|
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', 'description',
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
device_type = DeviceType.objects.get(
|
|
pk=self.initial.get('device_type') or self.data.get('device_type')
|
|
)
|
|
|
|
# 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_type.frontporttemplates.all()
|
|
]
|
|
|
|
# Populate rear port choices
|
|
choices = []
|
|
rear_ports = RearPortTemplate.objects.filter(device_type=device_type)
|
|
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 clean(self):
|
|
super().clean()
|
|
|
|
# Validate that the number of ports being created equals the number of selected (rear port, position) tuples
|
|
front_port_count = len(self.cleaned_data['name_pattern'])
|
|
rear_port_count = len(self.cleaned_data['rear_port_set'])
|
|
if front_port_count != rear_port_count:
|
|
raise forms.ValidationError({
|
|
'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments '
|
|
'were selected. These counts must match.'.format(front_port_count, rear_port_count)
|
|
})
|
|
|
|
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 RearPortTemplateCreateForm(ComponentTemplateCreateForm):
|
|
type = forms.ChoiceField(
|
|
choices=PortTypeChoices,
|
|
widget=StaticSelect(),
|
|
)
|
|
color = ColorField(
|
|
required=False
|
|
)
|
|
positions = forms.IntegerField(
|
|
min_value=REARPORT_POSITIONS_MIN,
|
|
max_value=REARPORT_POSITIONS_MAX,
|
|
initial=1,
|
|
help_text='The number of front ports which may be mapped to each rear port'
|
|
)
|
|
field_order = (
|
|
'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'description',
|
|
)
|
|
|
|
|
|
class DeviceBayTemplateCreateForm(ComponentTemplateCreateForm):
|
|
field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'description')
|
|
|
|
|
|
#
|
|
# Device components
|
|
#
|
|
|
|
class ComponentCreateForm(BootstrapMixin, CustomFieldsMixin, ComponentForm):
|
|
"""
|
|
Base form for the creation of device components (models subclassed from ComponentModel).
|
|
"""
|
|
device = DynamicModelChoiceField(
|
|
queryset=Device.objects.all()
|
|
)
|
|
description = forms.CharField(
|
|
max_length=200,
|
|
required=False
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
queryset=Tag.objects.all(),
|
|
required=False
|
|
)
|
|
|
|
|
|
class ConsolePortCreateForm(ComponentCreateForm):
|
|
model = ConsolePort
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
speed = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortSpeedChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
|
|
|
|
|
class ConsoleServerPortCreateForm(ComponentCreateForm):
|
|
model = ConsoleServerPort
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
speed = forms.ChoiceField(
|
|
choices=add_blank_choice(ConsolePortSpeedChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
|
|
|
|
|
class PowerPortCreateForm(ComponentCreateForm):
|
|
model = PowerPort
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerPortTypeChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
maximum_draw = forms.IntegerField(
|
|
min_value=1,
|
|
required=False,
|
|
help_text="Maximum draw in watts"
|
|
)
|
|
allocated_draw = forms.IntegerField(
|
|
min_value=1,
|
|
required=False,
|
|
help_text="Allocated draw in watts"
|
|
)
|
|
field_order = (
|
|
'device', 'name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
|
'description', 'tags',
|
|
)
|
|
|
|
|
|
class PowerOutletCreateForm(ComponentCreateForm):
|
|
model = PowerOutlet
|
|
type = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
power_port = forms.ModelChoiceField(
|
|
queryset=PowerPort.objects.all(),
|
|
required=False
|
|
)
|
|
feed_leg = forms.ChoiceField(
|
|
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
|
required=False
|
|
)
|
|
field_order = (
|
|
'device', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
|
'tags',
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Limit power_port queryset to PowerPorts which belong to the parent Device
|
|
device = Device.objects.get(
|
|
pk=self.initial.get('device') or self.data.get('device')
|
|
)
|
|
self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
|
|
|
|
|
|
class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
|
|
model = Interface
|
|
type = forms.ChoiceField(
|
|
choices=InterfaceTypeChoices,
|
|
widget=StaticSelect(),
|
|
)
|
|
enabled = forms.BooleanField(
|
|
required=False,
|
|
initial=True
|
|
)
|
|
parent = DynamicModelChoiceField(
|
|
queryset=Interface.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'device_id': '$device',
|
|
}
|
|
)
|
|
bridge = DynamicModelChoiceField(
|
|
queryset=Interface.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'device_id': '$device',
|
|
}
|
|
)
|
|
lag = DynamicModelChoiceField(
|
|
queryset=Interface.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'device_id': '$device',
|
|
'type': 'lag',
|
|
}
|
|
)
|
|
mac_address = forms.CharField(
|
|
required=False,
|
|
label='MAC Address'
|
|
)
|
|
mgmt_only = forms.BooleanField(
|
|
required=False,
|
|
label='Management only',
|
|
help_text='This interface is used only for out-of-band management'
|
|
)
|
|
mode = forms.ChoiceField(
|
|
choices=add_blank_choice(InterfaceModeChoices),
|
|
required=False,
|
|
widget=StaticSelect()
|
|
)
|
|
rf_role = forms.ChoiceField(
|
|
choices=add_blank_choice(WirelessRoleChoices),
|
|
required=False,
|
|
widget=StaticSelect(),
|
|
label='Wireless role'
|
|
)
|
|
rf_channel = forms.ChoiceField(
|
|
choices=add_blank_choice(WirelessChannelChoices),
|
|
required=False,
|
|
widget=StaticSelect(),
|
|
label='Wireless channel'
|
|
)
|
|
rf_channel_frequency = forms.DecimalField(
|
|
required=False,
|
|
label='Channel frequency (MHz)'
|
|
)
|
|
rf_channel_width = forms.DecimalField(
|
|
required=False,
|
|
label='Channel width (MHz)'
|
|
)
|
|
untagged_vlan = DynamicModelChoiceField(
|
|
queryset=VLAN.objects.all(),
|
|
required=False
|
|
)
|
|
tagged_vlans = DynamicModelMultipleChoiceField(
|
|
queryset=VLAN.objects.all(),
|
|
required=False
|
|
)
|
|
field_order = (
|
|
'device', 'name_pattern', 'label_pattern', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu', 'mac_address',
|
|
'description', 'mgmt_only', 'mark_connected', 'rf_role', 'rf_channel', 'rf_channel_frequency',
|
|
'rf_channel_width', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags'
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Limit VLAN choices by device
|
|
device_id = self.initial.get('device') or self.data.get('device')
|
|
self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device_id)
|
|
self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device_id)
|
|
|
|
|
|
class FrontPortCreateForm(ComponentCreateForm):
|
|
model = FrontPort
|
|
type = forms.ChoiceField(
|
|
choices=PortTypeChoices,
|
|
widget=StaticSelect(),
|
|
)
|
|
color = ColorField(
|
|
required=False
|
|
)
|
|
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', 'type', 'color', 'rear_port_set', 'mark_connected', 'description',
|
|
'tags',
|
|
)
|
|
|
|
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 clean(self):
|
|
super().clean()
|
|
|
|
# Validate that the number of ports being created equals the number of selected (rear port, position) tuples
|
|
front_port_count = len(self.cleaned_data['name_pattern'])
|
|
rear_port_count = len(self.cleaned_data['rear_port_set'])
|
|
if front_port_count != rear_port_count:
|
|
raise forms.ValidationError({
|
|
'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments '
|
|
'were selected. These counts must match.'.format(front_port_count, rear_port_count)
|
|
})
|
|
|
|
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 RearPortCreateForm(ComponentCreateForm):
|
|
model = RearPort
|
|
type = forms.ChoiceField(
|
|
choices=PortTypeChoices,
|
|
widget=StaticSelect(),
|
|
)
|
|
color = ColorField(
|
|
required=False
|
|
)
|
|
positions = forms.IntegerField(
|
|
min_value=REARPORT_POSITIONS_MIN,
|
|
max_value=REARPORT_POSITIONS_MAX,
|
|
initial=1,
|
|
help_text='The number of front ports which may be mapped to each rear port'
|
|
)
|
|
field_order = (
|
|
'device', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'mark_connected', 'description',
|
|
'tags',
|
|
)
|
|
|
|
|
|
class DeviceBayCreateForm(ComponentCreateForm):
|
|
model = DeviceBay
|
|
field_order = ('device', 'name_pattern', 'label_pattern', 'description', 'tags')
|
|
|
|
|
|
class InventoryItemCreateForm(ComponentCreateForm):
|
|
model = InventoryItem
|
|
manufacturer = DynamicModelChoiceField(
|
|
queryset=Manufacturer.objects.all(),
|
|
required=False
|
|
)
|
|
parent = DynamicModelChoiceField(
|
|
queryset=InventoryItem.objects.all(),
|
|
required=False,
|
|
query_params={
|
|
'device_id': '$device'
|
|
}
|
|
)
|
|
part_id = forms.CharField(
|
|
max_length=50,
|
|
required=False,
|
|
label='Part ID'
|
|
)
|
|
serial = forms.CharField(
|
|
max_length=50,
|
|
required=False,
|
|
)
|
|
asset_tag = forms.CharField(
|
|
max_length=50,
|
|
required=False,
|
|
)
|
|
field_order = (
|
|
'device', 'parent', 'name_pattern', 'label_pattern', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
|
'description', 'tags',
|
|
)
|