implemented a new device type for rack furniture with tests

This commit is contained in:
John Anderson 2018-07-02 01:16:21 -04:00
parent 3e9cec3e8e
commit 880d9f1713
14 changed files with 545 additions and 135 deletions

View File

@ -269,8 +269,8 @@ class DeviceTypeSerializer(CustomFieldModelSerializer):
model = DeviceType
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields',
'instance_count',
'is_console_server', 'is_pdu', 'is_network_device', 'is_rack_furniture', 'subdevice_role', 'comments',
'custom_fields', 'instance_count',
]
@ -289,7 +289,8 @@ class WritableDeviceTypeSerializer(CustomFieldModelSerializer):
model = DeviceType
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'custom_fields',
'is_console_server', 'is_pdu', 'is_network_device', 'is_rack_furniture', 'subdevice_role', 'comments',
'custom_fields',
]

View File

@ -512,8 +512,11 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldForm):
class Meta:
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']
fields = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
'is_pdu', 'is_network_device', 'is_rack_furniture', 'subdevice_role', 'interface_ordering',
'comments'
]
labels = {
'interface_ordering': 'Order interfaces by',
}
@ -562,6 +565,9 @@ class DeviceTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
is_network_device = forms.NullBooleanField(
required=False, widget=BulkEditNullBooleanSelect, label='Is a network device'
)
is_rack_furniture = forms.NullBooleanField(
required=False, widget=BulkEditNullBooleanSelect, label='Is rack furniture'
)
class Meta:
nullable_fields = []
@ -582,6 +588,9 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
is_network_device = forms.BooleanField(
required=False, label='Is a network device', widget=forms.CheckboxInput(attrs={'value': 'True'})
)
is_rack_furniture = forms.BooleanField(
required=False, label='Is rack furniture', widget=forms.CheckboxInput(attrs={'value': 'True'})
)
subdevice_role = forms.NullBooleanField(
required=False, label='Subdevice role', widget=forms.Select(choices=(
('', '---------'),

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-07-02 03:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0055_virtualchassis_ordering'),
]
operations = [
migrations.AddField(
model_name='devicetype',
name='is_rack_furniture',
field=models.BooleanField(default=False, help_text='This type of device is for rack furniture such as shelves or blanks', verbose_name='Is rack furniture'),
),
]

View File

@ -548,6 +548,11 @@ class DeviceType(models.Model, CustomFieldModel):
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")
is_rack_furniture = models.BooleanField(
default=False,
verbose_name="Is rack furniture",
help_text="This type of device is for rack furniture such as shelves or blanks",
)
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 "
@ -557,7 +562,7 @@ class DeviceType(models.Model, 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_rack_furniture', 'subdevice_role', 'interface_ordering', 'comments',
]
class Meta:
@ -590,6 +595,7 @@ class DeviceType(models.Model, CustomFieldModel):
self.is_console_server,
self.is_pdu,
self.is_network_device,
self.is_rack_furniture,
self.get_subdevice_role_display() if self.subdevice_role else None,
self.get_interface_ordering_display(),
self.comments,
@ -629,6 +635,37 @@ class DeviceType(models.Model, CustomFieldModel):
"device before declassifying it as a network device."
})
if self.is_rack_furniture and (self.is_console_server or self.is_pdu or self.is_network_device):
raise ValidationError({
'is_rack_furniture': "Rack furniture device types cannot simultaneously be any other device type."
})
if self.is_rack_furniture and self.subdevice_role:
raise ValidationError({
'is_rack_furniture': "Rack furniture device types cannot be a parent or child device type."
})
if self.is_rack_furniture and self.interface_templates.count():
# Rack furniture device types cannot have management interfaces
raise ValidationError({
'is_rack_furniture': "Must delete all interface templates associated with this "
"device before classifying it as rack furniture."
})
if self.is_rack_furniture and self.power_port_templates.count():
# Rack furniture device types cannot have power ports
raise ValidationError({
'is_rack_furniture': "Must delete all power ports associated with this "
"device before classifying it as rack furniture."
})
if self.is_rack_furniture and self.console_port_templates.count():
# Rack furniture device types cannot have power ports
raise ValidationError({
'is_rack_furniture': "Must delete all console ports associated with this "
"device before classifying it as rack furniture."
})
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 "
@ -668,6 +705,17 @@ class ConsolePortTemplate(models.Model):
def __str__(self):
return self.name
def clean(self):
"""
Validate model
"""
# Rack furniture device types cannot have console ports
if self.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have console ports."
)
@python_2_unicode_compatible
class ConsoleServerPortTemplate(models.Model):
@ -684,6 +732,14 @@ class ConsoleServerPortTemplate(models.Model):
def __str__(self):
return self.name
def clean(self):
# Rack furniture device types cannot have console ports
if self.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have console server ports."
)
@python_2_unicode_compatible
class PowerPortTemplate(models.Model):
@ -700,6 +756,17 @@ class PowerPortTemplate(models.Model):
def __str__(self):
return self.name
def clean(self):
"""
Validate model
"""
# Rack furniture device types cannot have power ports
if self.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have power ports."
)
@python_2_unicode_compatible
class PowerOutletTemplate(models.Model):
@ -716,6 +783,14 @@ class PowerOutletTemplate(models.Model):
def __str__(self):
return self.name
def clean(self):
# Rack furniture device types cannot have power outlets
if self.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have power outlets."
)
@python_2_unicode_compatible
class InterfaceTemplate(models.Model):
@ -736,6 +811,17 @@ class InterfaceTemplate(models.Model):
def __str__(self):
return self.name
def clean(self):
"""
Validate model
"""
# Rack furniture device types cannot have interfaces
if self.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have interfaces."
)
@python_2_unicode_compatible
class DeviceBayTemplate(models.Model):
@ -1003,6 +1089,11 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
pass
# Validate primary IP addresses
if self.device_type.is_rack_furniture and (self.primary_ip4 or self.primary_ip6):
# Rack furniture device types cannot have an assigned primary IP
raise ValidationError(
"Rack furniture device types cannot have an assigned primary IP address."
)
vc_interfaces = self.vc_interfaces.all()
if self.primary_ip4:
if self.primary_ip4.interface in vc_interfaces:
@ -1033,12 +1124,24 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
"to {}.".format(self.platform.manufacturer, self.device_type.manufacturer)
})
# Rack furniture device types cannot be assigned to a cluster
if self.cluster and self.device_type.is_rack_furniture:
raise ValidationError({
'cluster': "Rack furniture device types cannot be assigned to a cluster."
})
# A Device can only be assigned to a Cluster in the same Site (or no Site)
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
raise ValidationError({
'cluster': "The assigned cluster belongs to a different site ({})".format(self.cluster.site)
})
# Rack furniture device types cannot be assigned to a virtual chassis
if self.virtual_chassis and self.device_type.is_rack_furniture:
raise ValidationError({
'virtual_chassis': "Rack furniture device types cannot be assigned to a virtual chassis."
})
# Validate virtual chassis assignment
if self.virtual_chassis and self.vc_position is None:
raise ValidationError({
@ -1201,6 +1304,17 @@ class ConsolePort(models.Model):
self.get_connection_status_display(),
)
def clean(self):
"""
Validate model
"""
# Rack furniture device types cannot have console ports
if self.device.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have console ports."
)
#
# Console server ports
@ -1246,7 +1360,6 @@ class ConsoleServerPort(models.Model):
device_type.manufacturer, device_type
))
#
# Power ports
#
@ -1283,6 +1396,17 @@ class PowerPort(models.Model):
self.get_connection_status_display(),
)
def clean(self):
"""
Validate model
"""
# Rack furniture device types cannot have power ports
if self.device.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have power ports."
)
#
# Power outlets

View File

@ -326,6 +326,7 @@ class DeviceTypeTable(BaseTable):
is_console_server = tables.BooleanColumn(verbose_name='CS')
is_pdu = tables.BooleanColumn(verbose_name='PDU')
is_network_device = tables.BooleanColumn(verbose_name='Net')
is_rack_furniture = tables.BooleanColumn(verbose_name='RF')
subdevice_role = tables.TemplateColumn(
template_code=SUBDEVICE_ROLE_TEMPLATE,
verbose_name='Subdevice Role'
@ -339,7 +340,7 @@ class DeviceTypeTable(BaseTable):
model = DeviceType
fields = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
'is_network_device', 'subdevice_role', 'instance_count',
'is_network_device', 'is_rack_furniture', 'subdevice_role', 'instance_count',
)

View File

@ -260,3 +260,139 @@ class InterfaceTestCase(TestCase):
list(Interface.objects.all().order_naturally()),
[interface4, interface3, interface5, interface2, interface1, interface6]
)
class RackFurnitureDeviceTypeCase(TestCase):
def setUp(self):
self.manufacturer = Manufacturer.objects.create(
name='Acme',
slug='acme'
)
self.rack_furniture_type = DeviceType.objects.create(
manufacturer=self.manufacturer,
model='The Best Shelf 9000',
slug='rf9000',
is_network_device=False,
is_rack_furniture=True,
)
def test_rack_furniture_cs_port_template(self):
cs_port_template = ConsoleServerPortTemplate(
device_type=self.rack_furniture_type,
name="CS Port Template"
)
with self.assertRaises(ValidationError):
cs_port_template.clean()
def test_rack_furniture_console_port_template(self):
console_port_template = ConsolePortTemplate(
device_type=self.rack_furniture_type,
name="Console Port Template"
)
with self.assertRaises(ValidationError):
console_port_template.clean()
def test_rack_furniture_power_port_template(self):
power_port_template = PowerPortTemplate(
device_type=self.rack_furniture_type,
name="Power Port Template"
)
with self.assertRaises(ValidationError):
power_port_template.clean()
def test_rack_furniture_power_outlet_template(self):
power_outlet_template = PowerOutletTemplate(
device_type=self.rack_furniture_type,
name="Power Outlet Template"
)
with self.assertRaises(ValidationError):
power_outlet_template.clean()
def test_rack_furniture_interface_template(self):
interface_template = InterfaceTemplate(
device_type=self.rack_furniture_type,
name="Interface Template"
)
with self.assertRaises(ValidationError):
interface_template.clean()
class RackFurnitureDeviceCase(TestCase):
def setUp(self):
self.manufacturer = Manufacturer.objects.create(
name='Acme',
slug='acme'
)
self.rack_furniture_type = DeviceType.objects.create(
manufacturer=self.manufacturer,
model='The Best Shelf 9000',
slug='rf9000',
is_network_device=False,
is_rack_furniture=True,
)
self.site = Site.objects.create(
name="Site 1",
slug="site-1"
)
self.role = DeviceRole.objects.create(
name='RF',
slug='rf'
)
self.rack_furniture = Device.objects.create(
name="1U Blank",
device_type=self.rack_furniture_type,
site=self.site,
device_role=self.role,
)
def test_rack_furniture_cs_port(self):
cs_port = ConsoleServerPort(
device=self.rack_furniture,
name="CS Port"
)
with self.assertRaises(ValidationError):
cs_port.clean()
def test_rack_furniture_console_port(self):
console_port = ConsolePort(
device=self.rack_furniture,
name="Console Port"
)
with self.assertRaises(ValidationError):
console_port.clean()
def test_rack_furniture_power_port(self):
power_port = PowerPort(
device=self.rack_furniture,
name="Power Port"
)
with self.assertRaises(ValidationError):
power_port.clean()
def test_rack_furniture_power_outlet(self):
power_outlet = PowerOutlet(
device=self.rack_furniture,
name="Power Outlet"
)
with self.assertRaises(ValidationError):
power_outlet.clean()
def test_rack_furniture_interface(self):
interface = Interface(
device=self.rack_furniture,
name="Interface"
)
with self.assertRaises(ValidationError):
interface.clean()

View File

@ -684,3 +684,9 @@ class Service(CreatedUpdatedModel):
raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
if not self.device and not self.virtual_machine:
raise ValidationError("A service must be associated with either a device or a virtual machine.")
# rack furniture cannot have assgined services
if self.device and self.device.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have services."
)

View File

@ -4,7 +4,10 @@ import netaddr
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from ipam.models import IPAddress, Prefix, VRF
from ipam.models import IPAddress, Prefix, VRF, Service
from ipam.models import IP_PROTOCOL_TCP
from dcim.models import Manufacturer, DeviceType, Device, Site, DeviceRole
class TestPrefix(TestCase):
@ -59,3 +62,43 @@ class TestIPAddress(TestCase):
IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
self.assertRaises(ValidationError, duplicate_ip.clean)
class ServiceCase(TestCase):
def test_rack_funiture_no_assign(self):
manufacturer = Manufacturer.objects.create(
name='Acme',
slug='acme'
)
rack_furniture_type = DeviceType.objects.create(
manufacturer=manufacturer,
model='The Best Shelf 9000',
slug='rf9000',
is_network_device=False,
is_rack_furniture=True,
)
site = Site.objects.create(
name="Site 1",
slug="site-1"
)
role = DeviceRole.objects.create(
name='RF',
slug='rf'
)
rack_furniture = Device.objects.create(
name="1U Blank",
device_type=rack_furniture_type,
site=site,
device_role=role,
)
s = Service(
name="Service",
protocol=IP_PROTOCOL_TCP,
port=80,
device=rack_furniture
)
with self.assertRaises(ValidationError):
s.clean()

View File

@ -304,6 +304,17 @@ class Secret(CreatedUpdatedModel):
def get_absolute_url(self):
return reverse('secrets:secret', args=[self.pk])
def clean(self):
"""
Validate model
"""
# rack furniture cannot have assigned secrets
if self.device.device_type.is_rack_furniture:
raise ValidationError(
"Rack furniture device types cannot have secrets."
)
def _pad(self, s):
"""
Prepend the length of the plaintext (2B) and pad with garbage to a multiple of 16B (minimum of 64B).

View File

@ -8,6 +8,7 @@ from django.test import TestCase
from secrets.hashers import SecretValidationHasher
from secrets.models import UserKey, Secret, encrypt_master_key, decrypt_master_key, generate_random_key
from dcim.models import Manufacturer, DeviceType, Device, Site, DeviceRole
class UserKeyTestCase(TestCase):
@ -131,3 +132,39 @@ class SecretTestCase(TestCase):
self.assertEqual(duplicate_ivs, [], "One or more duplicate IVs found!")
duplicate_ciphertexts = [i for i, x in enumerate(ciphertexts) if ciphertexts.count(x) > 1]
self.assertEqual(duplicate_ciphertexts, [], "One or more duplicate ciphertexts (first blocks) found!")
def test_03_rack_funiture_no_assign(self):
manufacturer = Manufacturer.objects.create(
name='Acme',
slug='acme'
)
rack_furniture_type = DeviceType.objects.create(
manufacturer=manufacturer,
model='The Best Shelf 9000',
slug='rf9000',
is_network_device=False,
is_rack_furniture=True,
)
site = Site.objects.create(
name="Site 1",
slug="site-1"
)
role = DeviceRole.objects.create(
name='RF',
slug='rf'
)
rack_furniture = Device.objects.create(
name="1U Blank",
device_type=rack_furniture_type,
site=site,
device_role=role,
)
plaintext = "FooBar123"
secret_key = generate_random_key()
s = Secret(plaintext=plaintext)
s.encrypt(secret_key)
s.device = rack_furniture
with self.assertRaises(ValidationError):
s.clean()

View File

@ -165,36 +165,38 @@
<span class="label label-{{ device.get_status_class }}">{{ device.get_status_display }}</span>
</td>
</tr>
<tr>
<td>Primary IPv4</td>
<td>
{% if device.primary_ip4 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip4.pk %}">{{ device.primary_ip4.address.ip }}</a>
{% if device.primary_ip4.nat_inside %}
<span>(NAT for {{ device.primary_ip4.nat_inside.address.ip }})</span>
{% elif device.primary_ip4.nat_outside %}
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
{% if not device.device_type.is_rack_furniture %}
<tr>
<td>Primary IPv4</td>
<td>
{% if device.primary_ip4 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip4.pk %}">{{ device.primary_ip4.address.ip }}</a>
{% if device.primary_ip4.nat_inside %}
<span>(NAT for {{ device.primary_ip4.nat_inside.address.ip }})</span>
{% elif device.primary_ip4.nat_outside %}
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>Primary IPv6</td>
<td>
{% if device.primary_ip6 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip6.pk %}">{{ device.primary_ip6.address.ip }}</a>
{% if device.primary_ip6.nat_inside %}
<span>(NAT for {{ device.primary_ip6.nat_inside.address.ip }})</span>
{% elif device.primary_ip6.nat_outside %}
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
</td>
</tr>
<tr>
<td>Primary IPv6</td>
<td>
{% if device.primary_ip6 %}
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip6.pk %}">{{ device.primary_ip6.address.ip }}</a>
{% if device.primary_ip6.nat_inside %}
<span>(NAT for {{ device.primary_ip6.nat_inside.address.ip }})</span>
{% elif device.primary_ip6.nat_outside %}
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
</td>
</tr>
{% endif %}
{% if device.cluster %}
<tr>
<td>Cluster</td>
@ -226,107 +228,109 @@
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console / Power</strong>
{% if not device.device_type.is_rack_furniture %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console / Power</strong>
</div>
<table class="table table-hover panel-body component-list">
{% for cp in console_ports %}
{% include 'dcim/inc/consoleport.html' %}
{% empty %}
{% if device.device_type.console_port_templates.exists %}
<tr>
<td colspan="6" class="alert-warning">
<i class="fa fa-fw fa-warning"></i> No console ports defined
{% if perms.dcim.add_consoleport %}
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
{% for pp in power_ports %}
{% include 'dcim/inc/powerport.html' %}
{% empty %}
{% if device.device_type.power_port_templates.exists %}
<tr>
<td colspan="6" class="alert-warning">
<i class="fa fa-fw fa-warning"></i> No power ports defined
{% if perms.dcim.add_powerport %}
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
<div class="panel-footer text-right">
{% if perms.dcim.add_consoleport %}
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
{% endif %}
{% if perms.dcim.add_powerport %}
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
</a>
{% endif %}
</div>
{% endif %}
</div>
<table class="table table-hover panel-body component-list">
{% for cp in console_ports %}
{% include 'dcim/inc/consoleport.html' %}
{% empty %}
{% if device.device_type.console_port_templates.exists %}
<tr>
<td colspan="6" class="alert-warning">
<i class="fa fa-fw fa-warning"></i> No console ports defined
{% if perms.dcim.add_consoleport %}
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
{% endif %}
</td>
</tr>
{% if request.user.is_authenticated %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secrets</strong>
</div>
{% if secrets %}
<table class="table table-hover panel-body">
{% for secret in secrets %}
{% include 'secrets/inc/secret_tr.html' %}
{% endfor %}
</table>
{% else %}
<div class="panel-body text-muted">
None found
</div>
{% endif %}
{% endfor %}
{% for pp in power_ports %}
{% include 'dcim/inc/powerport.html' %}
{% empty %}
{% if device.device_type.power_port_templates.exists %}
<tr>
<td colspan="6" class="alert-warning">
<i class="fa fa-fw fa-warning"></i> No power ports defined
{% if perms.dcim.add_powerport %}
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
<div class="panel-footer text-right">
{% if perms.dcim.add_consoleport %}
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
{% endif %}
{% if perms.dcim.add_powerport %}
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
</a>
{% if perms.secrets.add_secret %}
<form id="secret_form">
{% csrf_token %}
</form>
<div class="panel-footer text-right">
<a href="{% url 'dcim:device_addsecret' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add secret
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% if request.user.is_authenticated %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secrets</strong>
<strong>Services</strong>
</div>
{% if secrets %}
{% if services %}
<table class="table table-hover panel-body">
{% for secret in secrets %}
{% include 'secrets/inc/secret_tr.html' %}
{% for service in services %}
{% include 'ipam/inc/service.html' %}
{% endfor %}
</table>
{% else %}
<div class="panel-body text-muted">
None found
None
</div>
{% endif %}
{% if perms.secrets.add_secret %}
<form id="secret_form">
{% csrf_token %}
</form>
{% if perms.ipam.add_service %}
<div class="panel-footer text-right">
<a href="{% url 'dcim:device_addsecret' pk=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add secret
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
</a>
</div>
{% endif %}
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Services</strong>
</div>
{% if services %}
<table class="table table-hover panel-body">
{% for service in services %}
{% include 'ipam/inc/service.html' %}
{% endfor %}
</table>
{% else %}
<div class="panel-body text-muted">
None
</div>
{% endif %}
{% if perms.ipam.add_service %}
<div class="panel-footer text-right">
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
</a>
</div>
{% endif %}
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Images</strong>

View File

@ -56,7 +56,7 @@
<div class="panel-body">
{% render_field form.status %}
{% render_field form.platform %}
{% if obj.pk %}
{% if obj.pk and not obj.device_type.is_rack_furniture %}
{% render_field form.primary_ip4 %}
{% render_field form.primary_ip6 %}
{% endif %}

View File

@ -69,10 +69,12 @@
{% endif %}
</td>
</tr>
<tr>
<td>Interface Ordering</td>
<td>{{ devicetype.get_interface_ordering_display }}</td>
</tr>
{% if not devicetype.is_rack_furniture %}
<tr>
<td>Interface Ordering</td>
<td>{{ devicetype.get_interface_ordering_display }}</td>
</tr>
{% endif %}
<tr>
<td>Instances</td>
<td><a href="{% url 'dcim:device_list' %}?device_type_id={{ devicetype.pk }}">{{ devicetype.instances.count }}</a></td>
@ -123,6 +125,19 @@
<small class="text-muted">This device {% if devicetype.is_network_device %}has{% else %}does not have{% endif %} network interfaces</small>
</td>
</tr>
<tr>
<td class="text-right">
{% if devicetype.is_rack_furniture %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
<td>
<strong>Rack Furniture</strong><br />
<small class="text-muted">This device {% if devicetype.is_rack_furniture %}is{% else %}is not{% endif %} rack furniture</small>
</td>
</tr>
<tr>
<td class="text-right">
{% if devicetype.subdevice_role == True %}
@ -163,19 +178,21 @@
</div>
</div>
<div class="col-md-7">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:devicetype_add_consoleport' delete_url='dcim:devicetype_delete_consoleport' %}
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
{% if devicetype.is_parent_device or devicebay_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicetype_add_devicebay' delete_url='dcim:devicetype_delete_devicebay' %}
{% endif %}
{% if devicetype.is_network_device or interface_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:devicetype_add_interface' edit_url='dcim:devicetype_bulkedit_interface' delete_url='dcim:devicetype_delete_interface' %}
{% endif %}
{% if devicetype.is_console_server or consoleserverport_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:devicetype_add_consoleserverport' delete_url='dcim:devicetype_delete_consoleserverport' %}
{% endif %}
{% if devicetype.is_pdu or poweroutlet_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:devicetype_add_poweroutlet' delete_url='dcim:devicetype_delete_poweroutlet' %}
{% if not devicetype.is_rack_furniture %}
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:devicetype_add_consoleport' delete_url='dcim:devicetype_delete_consoleport' %}
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
{% if devicetype.is_parent_device or devicebay_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicetype_add_devicebay' delete_url='dcim:devicetype_delete_devicebay' %}
{% endif %}
{% if devicetype.is_network_device or interface_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:devicetype_add_interface' edit_url='dcim:devicetype_bulkedit_interface' delete_url='dcim:devicetype_delete_interface' %}
{% endif %}
{% if devicetype.is_console_server or consoleserverport_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:devicetype_add_consoleserverport' delete_url='dcim:devicetype_delete_consoleserverport' %}
{% endif %}
{% if devicetype.is_pdu or poweroutlet_table.rows %}
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:devicetype_add_poweroutlet' delete_url='dcim:devicetype_delete_poweroutlet' %}
{% endif %}
{% endif %}
</div>
</div>

View File

@ -20,6 +20,7 @@
{% render_field form.is_console_server %}
{% render_field form.is_pdu %}
{% render_field form.is_network_device %}
{% render_field form.is_rack_furniture %}
{% render_field form.subdevice_role %}
</div>
</div>