mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Initial work on #20 - patch panels
This commit is contained in:
@@ -5,6 +5,8 @@ from django.contrib.auth.models import User
|
||||
from django.contrib.postgres.forms.array import SimpleArrayField
|
||||
from django.db.models import Count, Q
|
||||
from mptt.forms import TreeNodeChoiceField
|
||||
from natsort import natsorted
|
||||
from operator import attrgetter
|
||||
from taggit.forms import TagField
|
||||
from timezone_field import TimeZoneFormField
|
||||
|
||||
@@ -19,17 +21,13 @@ from utilities.forms import (
|
||||
FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField,
|
||||
)
|
||||
from virtualization.models import Cluster
|
||||
from .constants import (
|
||||
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_FF_LAG,
|
||||
IFACE_MODE_ACCESS, IFACE_MODE_CHOICES, IFACE_MODE_TAGGED_ALL, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES,
|
||||
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHILD,
|
||||
SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES,
|
||||
)
|
||||
from .constants import *
|
||||
from .models import (
|
||||
DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||
Device, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem,
|
||||
Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation,
|
||||
RackRole, Region, Site, VirtualChassis
|
||||
Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
||||
VirtualChassis,
|
||||
)
|
||||
|
||||
DEVICE_BY_PK_RE = r'{\d+\}'
|
||||
@@ -532,7 +530,7 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldForm):
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
||||
'is_network_device', 'subdevice_role', 'interface_ordering', 'comments', 'tags',
|
||||
'is_network_device', 'is_patch_panel', 'subdevice_role', 'interface_ordering', 'comments', 'tags',
|
||||
]
|
||||
labels = {
|
||||
'interface_ordering': 'Order interfaces by',
|
||||
@@ -582,6 +580,9 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
|
||||
is_network_device = forms.NullBooleanField(
|
||||
required=False, widget=BulkEditNullBooleanSelect, label='Is a network device'
|
||||
)
|
||||
is_patch_panel = forms.NullBooleanField(
|
||||
required=False, widget=BulkEditNullBooleanSelect, label='Is a patch panel'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
@@ -602,6 +603,9 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
is_network_device = forms.BooleanField(
|
||||
required=False, label='Is a network device', widget=forms.CheckboxInput(attrs={'value': 'True'})
|
||||
)
|
||||
is_patch_panel = forms.BooleanField(
|
||||
required=False, label='Is a patch panel', widget=forms.CheckboxInput(attrs={'value': 'True'})
|
||||
)
|
||||
subdevice_role = forms.NullBooleanField(
|
||||
required=False, label='Subdevice role', widget=forms.Select(choices=(
|
||||
('', '---------'),
|
||||
@@ -696,6 +700,97 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
nullable_fields = []
|
||||
|
||||
|
||||
class FrontPanelPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = FrontPanelPortTemplate
|
||||
fields = ['device_type', 'name', 'type', 'rear_port', 'rear_port_position']
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class FrontPanelPortTemplateCreateForm(ComponentForm):
|
||||
name_pattern = ExpandableNameField(
|
||||
label='Name'
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
rear_port_set = forms.MultipleChoiceField(
|
||||
choices=[],
|
||||
label='Rear ports',
|
||||
help_text='Select one rear port assignment for each front port being created.'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(FrontPanelPortTemplateCreateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# 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 self.parent.front_panel_port_templates.all()
|
||||
]
|
||||
|
||||
# Populate rear port choices
|
||||
choices = []
|
||||
rear_ports = natsorted(RearPanelPortTemplate.objects.filter(device_type=self.parent), key=attrgetter('name'))
|
||||
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):
|
||||
|
||||
# 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 RearPanelPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = RearPanelPortTemplate
|
||||
fields = ['device_type', 'name', 'type', 'positions']
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class RearPanelPortTemplateCreateForm(ComponentForm):
|
||||
name_pattern = ExpandableNameField(
|
||||
label='Name'
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
positions = forms.IntegerField(
|
||||
min_value=1,
|
||||
max_value=64,
|
||||
initial=1,
|
||||
help_text='The number of front ports which may be mapped to each rear port'
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
@@ -2087,6 +2182,122 @@ class InterfaceConnectionCSVForm(forms.ModelForm):
|
||||
return interface
|
||||
|
||||
|
||||
#
|
||||
# Front panel ports
|
||||
#
|
||||
|
||||
class FrontPanelPortForm(BootstrapMixin, forms.ModelForm):
|
||||
tags = TagField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = FrontPanelPort
|
||||
fields = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'tags']
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
# TODO: Merge with FrontPanelPortTemplateCreateForm to remove duplicate logic
|
||||
class FrontPanelPortCreateForm(ComponentForm):
|
||||
name_pattern = ExpandableNameField(
|
||||
label='Name'
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
rear_port_set = forms.MultipleChoiceField(
|
||||
choices=[],
|
||||
label='Rear ports',
|
||||
help_text='Select one rear port assignment for each front port being created.'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(FrontPanelPortCreateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# 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 self.parent.front_panel_port_templates.all()
|
||||
]
|
||||
|
||||
# Populate rear port choices
|
||||
choices = []
|
||||
rear_ports = natsorted(RearPanelPort.objects.filter(device=self.parent), key=attrgetter('name'))
|
||||
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):
|
||||
|
||||
# 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 FrontPanelPortBulkRenameForm(BulkRenameForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=FrontPanelPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Rear panel ports
|
||||
#
|
||||
|
||||
class RearPanelPortForm(BootstrapMixin, forms.ModelForm):
|
||||
tags = TagField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = RearPanelPort
|
||||
fields = ['device', 'name', 'type', 'positions', 'tags']
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class RearPanelPortCreateForm(ComponentForm):
|
||||
name_pattern = ExpandableNameField(
|
||||
label='Name'
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
positions = forms.IntegerField(
|
||||
min_value=1,
|
||||
max_value=64,
|
||||
initial=1,
|
||||
help_text='The number of front ports which may be mapped to each rear port'
|
||||
)
|
||||
|
||||
|
||||
class RearPanelPortBulkRenameForm(BulkRenameForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RearPanelPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user