1
0
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:
Jeremy Stretch
2018-10-03 14:04:16 -04:00
parent 364bbdeab8
commit 3eddeeadc5
18 changed files with 1101 additions and 34 deletions

View File

@@ -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
#