Initial work on #20 - patch panels
This commit is contained in:
@@ -769,6 +769,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
||||
verbose_name='Is a network device',
|
||||
help_text='This type of device has network interfaces'
|
||||
)
|
||||
is_patch_panel = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Is a patch panel',
|
||||
help_text='This type of device has patch panel ports'
|
||||
)
|
||||
subdevice_role = models.NullBooleanField(
|
||||
default=None,
|
||||
verbose_name='Parent/child status',
|
||||
@@ -789,7 +794,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
||||
|
||||
csv_headers = [
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
|
||||
'is_pdu', 'is_network_device', 'subdevice_role', 'interface_ordering', 'comments',
|
||||
'is_pdu', 'is_network_device', 'is_patch_panel', 'subdevice_role', 'interface_ordering', 'comments',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
@@ -822,6 +827,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
||||
self.is_console_server,
|
||||
self.is_pdu,
|
||||
self.is_network_device,
|
||||
self.is_patch_panel,
|
||||
self.get_subdevice_role_display() if self.subdevice_role else None,
|
||||
self.get_interface_ordering_display(),
|
||||
self.comments,
|
||||
@@ -861,6 +867,14 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
||||
"device before declassifying it as a network device."
|
||||
})
|
||||
|
||||
if not self.is_patch_panel and (
|
||||
self.front_panel_port_templates.exists() or self.rear_panel_port_templates.exists()
|
||||
):
|
||||
raise ValidationError({
|
||||
'is_patch_panel': "Must delete all patch panel port templates associated with this device before "
|
||||
"declassifying it as a network device."
|
||||
})
|
||||
|
||||
if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count():
|
||||
raise ValidationError({
|
||||
'subdevice_role': "Must delete all device bay templates associated with this device before "
|
||||
@@ -1000,6 +1014,86 @@ class InterfaceTemplate(ComponentTemplateModel):
|
||||
return self.name
|
||||
|
||||
|
||||
class FrontPanelPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a front patch panel port on a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='front_panel_port_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
rear_port = models.ForeignKey(
|
||||
to='dcim.RearPanelPortTemplate',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='front_panel_port_templates'
|
||||
)
|
||||
rear_port_position = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = [
|
||||
['device_type', 'name'],
|
||||
['rear_port', 'rear_port_position'],
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Validate rear port assignment
|
||||
if self.rear_port.device_type != self.device_type:
|
||||
raise ValidationError(
|
||||
"Rear port ({}) must belong to the same device type".format(self.rear_port)
|
||||
)
|
||||
|
||||
# Validate rear port position assignment
|
||||
if self.rear_port_position > self.rear_port.positions:
|
||||
raise ValidationError(
|
||||
"Invalid rear port position ({}); rear port {} has only {} positions".format(
|
||||
self.rear_port_position, self.rear_port.name, self.rear_port.positions
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class RearPanelPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a rear patch panel port on a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='rear_panel_port_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
positions = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class DeviceBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a DeviceBay to be created for a new parent Device.
|
||||
@@ -1417,6 +1511,23 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||
[Interface(device=self, name=template.name, form_factor=template.form_factor,
|
||||
mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()]
|
||||
)
|
||||
RearPanelPort.objects.bulk_create([
|
||||
RearPanelPort(
|
||||
device=self,
|
||||
name=template.name,
|
||||
type=template.type,
|
||||
positions=template.positions
|
||||
) for template in self.device_type.rear_panel_port_templates.all()
|
||||
])
|
||||
FrontPanelPort.objects.bulk_create([
|
||||
FrontPanelPort(
|
||||
device=self,
|
||||
name=template.name,
|
||||
type=template.type,
|
||||
rear_port=RearPanelPort.objects.get(device=self, name=template.rear_port.name),
|
||||
rear_port_position=template.rear_port_position,
|
||||
) for template in self.device_type.front_panel_port_templates.all()
|
||||
])
|
||||
DeviceBay.objects.bulk_create(
|
||||
[DeviceBay(device=self, name=template.name) for template in
|
||||
self.device_type.device_bay_templates.all()]
|
||||
@@ -2040,6 +2151,94 @@ class InterfaceConnection(models.Model):
|
||||
).save()
|
||||
|
||||
|
||||
#
|
||||
# Patch panel ports
|
||||
#
|
||||
|
||||
class FrontPanelPort(ComponentModel):
|
||||
"""
|
||||
A port on the front of a patch panel.
|
||||
"""
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='front_panel_ports'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
rear_port = models.ForeignKey(
|
||||
to='dcim.RearPanelPort',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='front_panel_ports'
|
||||
)
|
||||
rear_port_position = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'name']
|
||||
unique_together = [
|
||||
['device', 'name'],
|
||||
['rear_port', 'rear_port_position'],
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Validate rear port assignment
|
||||
if self.rear_port.device != self.device:
|
||||
raise ValidationError(
|
||||
"Rear port ({}) must belong to the same device".format(self.rear_port)
|
||||
)
|
||||
|
||||
# Validate rear port position assignment
|
||||
if self.rear_port_position > self.rear_port.positions:
|
||||
raise ValidationError(
|
||||
"Invalid rear port position ({}); rear port {} has only {} positions".format(
|
||||
self.rear_port_position, self.rear_port.name, self.rear_port.positions
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class RearPanelPort(ComponentModel):
|
||||
"""
|
||||
A port on the rear of a patch panel.
|
||||
"""
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='rear_panel_ports'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=PANELPORT_TYPE_CHOICES
|
||||
)
|
||||
positions = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'name']
|
||||
unique_together = ['device', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
#
|
||||
|
Reference in New Issue
Block a user