1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00
netbox-community-netbox/netbox/dcim/models/device_component_templates.py
2021-07-06 12:10:29 -04:00

387 lines
11 KiB
Python

from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from dcim.choices import *
from dcim.constants import *
from extras.utils import extras_features
from netbox.models import ChangeLoggedModel
from utilities.fields import ColorField, NaturalOrderingField
from utilities.querysets import RestrictedQuerySet
from utilities.ordering import naturalize_interface
from .device_components import (
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, PowerOutlet, PowerPort, RearPort,
)
__all__ = (
'ConsolePortTemplate',
'ConsoleServerPortTemplate',
'DeviceBayTemplate',
'FrontPortTemplate',
'InterfaceTemplate',
'PowerOutletTemplate',
'PowerPortTemplate',
'RearPortTemplate',
)
class ComponentTemplateModel(ChangeLoggedModel):
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='%(class)ss'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
description = models.CharField(
max_length=200,
blank=True
)
objects = RestrictedQuerySet.as_manager()
class Meta:
abstract = True
def __str__(self):
if self.label:
return f"{self.name} ({self.label})"
return self.name
def instantiate(self, device):
"""
Instantiate a new component on the specified Device.
"""
raise NotImplementedError()
def to_objectchange(self, action):
# Annotate the parent DeviceType
try:
device_type = self.device_type
except ObjectDoesNotExist:
# The parent DeviceType has already been deleted
device_type = None
return super().to_objectchange(action, related_object=device_type)
@extras_features('webhooks')
class ConsolePortTemplate(ComponentTemplateModel):
"""
A template for a ConsolePort to be created for a new Device.
"""
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
blank=True
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return ConsolePort(
device=device,
name=self.name,
label=self.label,
type=self.type
)
@extras_features('webhooks')
class ConsoleServerPortTemplate(ComponentTemplateModel):
"""
A template for a ConsoleServerPort to be created for a new Device.
"""
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
blank=True
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return ConsoleServerPort(
device=device,
name=self.name,
label=self.label,
type=self.type
)
@extras_features('webhooks')
class PowerPortTemplate(ComponentTemplateModel):
"""
A template for a PowerPort to be created for a new Device.
"""
type = models.CharField(
max_length=50,
choices=PowerPortTypeChoices,
blank=True
)
maximum_draw = models.PositiveSmallIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(1)],
help_text="Maximum power draw (watts)"
)
allocated_draw = models.PositiveSmallIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(1)],
help_text="Allocated power draw (watts)"
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return PowerPort(
device=device,
name=self.name,
label=self.label,
type=self.type,
maximum_draw=self.maximum_draw,
allocated_draw=self.allocated_draw
)
def clean(self):
super().clean()
if self.maximum_draw is not None and self.allocated_draw is not None:
if self.allocated_draw > self.maximum_draw:
raise ValidationError({
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
})
@extras_features('webhooks')
class PowerOutletTemplate(ComponentTemplateModel):
"""
A template for a PowerOutlet to be created for a new Device.
"""
type = models.CharField(
max_length=50,
choices=PowerOutletTypeChoices,
blank=True
)
power_port = models.ForeignKey(
to='dcim.PowerPortTemplate',
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name='poweroutlet_templates'
)
feed_leg = models.CharField(
max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True,
help_text="Phase (for three-phase feeds)"
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def clean(self):
super().clean()
# Validate power port assignment
if self.power_port and self.power_port.device_type != self.device_type:
raise ValidationError(
"Parent power port ({}) must belong to the same device type".format(self.power_port)
)
def instantiate(self, device):
if self.power_port:
power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
else:
power_port = None
return PowerOutlet(
device=device,
name=self.name,
label=self.label,
type=self.type,
power_port=power_port,
feed_leg=self.feed_leg
)
@extras_features('webhooks')
class InterfaceTemplate(ComponentTemplateModel):
"""
A template for a physical data interface on a new Device.
"""
# Override ComponentTemplateModel._name to specify naturalize_interface function
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=InterfaceTypeChoices
)
mgmt_only = models.BooleanField(
default=False,
verbose_name='Management only'
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return Interface(
device=device,
name=self.name,
label=self.label,
type=self.type,
mgmt_only=self.mgmt_only
)
@extras_features('webhooks')
class FrontPortTemplate(ComponentTemplateModel):
"""
Template for a pass-through port on the front of a new Device.
"""
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
color = ColorField(
blank=True
)
rear_port = models.ForeignKey(
to='dcim.RearPortTemplate',
on_delete=models.CASCADE,
related_name='frontport_templates'
)
rear_port_position = models.PositiveSmallIntegerField(
default=1,
validators=[
MinValueValidator(REARPORT_POSITIONS_MIN),
MaxValueValidator(REARPORT_POSITIONS_MAX)
]
)
class Meta:
ordering = ('device_type', '_name')
unique_together = (
('device_type', 'name'),
('rear_port', 'rear_port_position'),
)
def clean(self):
super().clean()
try:
# 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
)
)
except RearPortTemplate.DoesNotExist:
pass
def instantiate(self, device):
if self.rear_port:
rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
else:
rear_port = None
return FrontPort(
device=device,
name=self.name,
label=self.label,
type=self.type,
color=self.color,
rear_port=rear_port,
rear_port_position=self.rear_port_position
)
@extras_features('webhooks')
class RearPortTemplate(ComponentTemplateModel):
"""
Template for a pass-through port on the rear of a new Device.
"""
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
color = ColorField(
blank=True
)
positions = models.PositiveSmallIntegerField(
default=1,
validators=[
MinValueValidator(REARPORT_POSITIONS_MIN),
MaxValueValidator(REARPORT_POSITIONS_MAX)
]
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return RearPort(
device=device,
name=self.name,
label=self.label,
type=self.type,
color=self.color,
positions=self.positions
)
@extras_features('webhooks')
class DeviceBayTemplate(ComponentTemplateModel):
"""
A template for a DeviceBay to be created for a new parent Device.
"""
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return DeviceBay(
device=device,
name=self.name,
label=self.label
)
def clean(self):
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT:
raise ValidationError(
f"Subdevice role of device type ({self.device_type}) must be set to \"parent\" to allow device bays."
)