1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Initial work on #91: Support for subdevices

This commit is contained in:
Jeremy Stretch
2016-07-01 17:12:43 -04:00
parent 35c5423127
commit 0123dbcf5f
17 changed files with 584 additions and 20 deletions

View File

@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Q, ObjectDoesNotExist
from django.db.models import Count, Q, ObjectDoesNotExist
from extras.rpc import RPC_CLIENTS
from utilities.fields import NullableCharField
@ -18,6 +18,14 @@ RACK_FACE_CHOICES = [
[RACK_FACE_REAR, 'Rear'],
]
SUBDEVICE_ROLE_PARENT = True
SUBDEVICE_ROLE_CHILD = False
SUBDEVICE_ROLE_CHOICES = (
(None, 'None'),
(SUBDEVICE_ROLE_PARENT, 'Parent'),
(SUBDEVICE_ROLE_CHILD, 'Child'),
)
COLOR_TEAL = 'teal'
COLOR_GREEN = 'green'
COLOR_BLUE = 'blue'
@ -274,6 +282,7 @@ class Rack(CreatedUpdatedModel):
# Add devices to rack units list
if self.pk:
for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\
.annotate(devicebay_count=Count('device_bays'))\
.exclude(pk=exclude)\
.filter(rack=self, position__gt=0)\
.filter(Q(face=face) | Q(device_type__is_full_depth=True)):
@ -380,6 +389,10 @@ class DeviceType(models.Model):
help_text="This type of device has power outlets")
is_network_device = models.BooleanField(default=True, verbose_name='Is a network device',
help_text="This type of device has network interfaces")
subdevice_role = models.NullBooleanField(default=None, verbose_name='Parent/child status',
choices=SUBDEVICE_ROLE_CHOICES,
help_text="Parent devices house child devices in device bays. Select "
"\"None\" if this device type is neither a parent nor a child.")
class Meta:
ordering = ['manufacturer', 'model']
@ -389,11 +402,24 @@ class DeviceType(models.Model):
]
def __unicode__(self):
return "{0} {1}".format(self.manufacturer, self.model)
return "{} {}".format(self.manufacturer, self.model)
def get_absolute_url(self):
return reverse('dcim:devicetype', args=[self.pk])
def clean(self):
if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD:
raise ValidationError("Child device types must be 0U.")
@property
def is_parent_device(self):
return bool(self.subdevice_role)
@property
def is_child_device(self):
return bool(self.subdevice_role is False)
class ConsolePortTemplate(models.Model):
"""
@ -481,6 +507,21 @@ class InterfaceTemplate(models.Model):
return self.name
class DeviceBayTemplate(models.Model):
"""
A template for a DeviceBay to be created for a new parent Device.
"""
device_type = models.ForeignKey('DeviceType', related_name='device_bay_templates', on_delete=models.CASCADE)
name = models.CharField(max_length=30)
class Meta:
ordering = ['device_type', 'name']
unique_together = ['device_type', 'name']
def __unicode__(self):
return self.name
#
# Devices
#
@ -563,6 +604,10 @@ class Device(CreatedUpdatedModel):
def clean(self):
# Child devices cannot be assigned to a rack face/unit
if self.device_type.is_child_device and (self.face is not None or self.position):
raise ValidationError("Child device types cannot be assigned a rack face or position.")
# Validate position/face combination
if self.position and self.face is None:
raise ValidationError("Must specify rack face with rack position.")
@ -610,6 +655,10 @@ class Device(CreatedUpdatedModel):
[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()]
)
DeviceBay.objects.bulk_create(
[DeviceBay(device=self, name=template.name) for template in
self.device_type.device_bay_templates.all()]
)
def to_csv(self):
return ','.join([
@ -643,6 +692,12 @@ class Device(CreatedUpdatedModel):
return self.name
return '{{{}}}'.format(self.pk)
def get_children(self):
"""
Return the set of child Devices installed in DeviceBays within this Device.
"""
return Device.objects.filter(parent_bay__device=self.pk)
def get_rpc_client(self):
"""
Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined.
@ -860,6 +915,33 @@ class InterfaceConnection(models.Model):
])
class DeviceBay(models.Model):
"""
An empty space within a Device which can house a child device
"""
device = models.ForeignKey('Device', related_name='device_bays', on_delete=models.CASCADE)
name = models.CharField(max_length=50, verbose_name='Name')
installed_device = models.OneToOneField('Device', related_name='parent_bay', blank=True, null=True)
class Meta:
ordering = ['device', 'name']
unique_together = ['device', 'name']
def __unicode__(self):
return '{} - {}'.format(self.device.name, self.name)
def clean(self):
# Validate that the parent Device can have DeviceBays
if not self.device.device_type.is_parent_device:
raise ValidationError("This type of device ({}) does not support device bays."
.format(self.device.device_type))
# Cannot install a device into itself, obviously
if self.device == self.installed_device:
raise ValidationError("Cannot install a device into itself.")
class Module(models.Model):
"""
A Module represents a piece of hardware within a Device, such as a line card or power supply. Modules are used only