Move device and device_type ForeignKeys to abstract component models

This commit is contained in:
Jeremy Stretch 2020-07-02 13:07:32 -04:00
parent d03d302eef
commit 1f9cdc71d4
10 changed files with 186 additions and 207 deletions

View File

@ -384,28 +384,28 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFil
) )
def _console_ports(self, queryset, name, value): def _console_ports(self, queryset, name, value):
return queryset.exclude(consoleport_templates__isnull=value) return queryset.exclude(consoleporttemplates__isnull=value)
def _console_server_ports(self, queryset, name, value): def _console_server_ports(self, queryset, name, value):
return queryset.exclude(consoleserverport_templates__isnull=value) return queryset.exclude(consoleserverporttemplates__isnull=value)
def _power_ports(self, queryset, name, value): def _power_ports(self, queryset, name, value):
return queryset.exclude(powerport_templates__isnull=value) return queryset.exclude(powerporttemplates__isnull=value)
def _power_outlets(self, queryset, name, value): def _power_outlets(self, queryset, name, value):
return queryset.exclude(poweroutlet_templates__isnull=value) return queryset.exclude(poweroutlettemplates__isnull=value)
def _interfaces(self, queryset, name, value): def _interfaces(self, queryset, name, value):
return queryset.exclude(interface_templates__isnull=value) return queryset.exclude(interfacetemplates__isnull=value)
def _pass_through_ports(self, queryset, name, value): def _pass_through_ports(self, queryset, name, value):
return queryset.exclude( return queryset.exclude(
frontport_templates__isnull=value, frontporttemplates__isnull=value,
rearport_templates__isnull=value rearporttemplates__isnull=value
) )
def _device_bays(self, queryset, name, value): def _device_bays(self, queryset, name, value):
return queryset.exclude(device_bay_templates__isnull=value) return queryset.exclude(devicebaytemplates__isnull=value)
class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet): class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
@ -656,7 +656,7 @@ class DeviceFilterSet(
return queryset.filter( return queryset.filter(
Q(name__icontains=value) | Q(name__icontains=value) |
Q(serial__icontains=value.strip()) | Q(serial__icontains=value.strip()) |
Q(inventory_items__serial__icontains=value.strip()) | Q(inventoryitems__serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) |
Q(comments__icontains=value) Q(comments__icontains=value)
).distinct() ).distinct()
@ -698,7 +698,7 @@ class DeviceFilterSet(
) )
def _device_bays(self, queryset, name, value): def _device_bays(self, queryset, name, value):
return queryset.exclude(device_bays__isnull=value) return queryset.exclude(devicebays__isnull=value)
class DeviceComponentFilterSet(django_filters.FilterSet): class DeviceComponentFilterSet(django_filters.FilterSet):

View File

@ -1392,7 +1392,7 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
# Determine which rear port positions are occupied. These will be excluded from the list of available mappings. # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
occupied_port_positions = [ occupied_port_positions = [
(front_port.rear_port_id, front_port.rear_port_position) (front_port.rear_port_id, front_port.rear_port_position)
for front_port in device_type.frontport_templates.all() for front_port in device_type.frontporttemplates.all()
] ]
# Populate rear port choices # Populate rear port choices

View File

@ -1,68 +0,0 @@
# Generated by Django 3.0.6 on 2020-07-02 16:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0111_component_template_description'),
]
operations = [
migrations.AlterField(
model_name='consoleport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebay',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='inventoryitem',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlet',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerporttemplate',
name='name',
field=models.CharField(max_length=64),
),
]

View File

@ -0,0 +1,120 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0111_component_template_description'),
]
operations = [
# Set max_length=64 for all name fields
migrations.AlterField(
model_name='consoleport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebay',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='inventoryitem',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlet',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerporttemplate',
name='name',
field=models.CharField(max_length=64),
),
# Update related_name for necessary component and component template models
migrations.AlterField(
model_name='consoleporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='devicebay',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebays', to='dcim.Device'),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebaytemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='frontporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='interfacetemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='inventoryitem',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventoryitems', to='dcim.Device'),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='powerporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='rearporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.DeviceType'),
),
]

View File

@ -678,7 +678,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
'device_type__manufacturer', 'device_type__manufacturer',
'device_role' 'device_role'
).annotate( ).annotate(
devicebay_count=Count('device_bays') devicebay_count=Count('devicebays')
).exclude( ).exclude(
pk=exclude pk=exclude
).filter( ).filter(
@ -1049,23 +1049,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
)) ))
# Component templates # Component templates
if self.consoleport_templates.exists(): if self.consoleporttemplates.exists():
data['console-ports'] = [ data['console-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
} }
for c in self.consoleport_templates.all() for c in self.consoleporttemplates.all()
] ]
if self.consoleserverport_templates.exists(): if self.consoleserverporttemplates.exists():
data['console-server-ports'] = [ data['console-server-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
} }
for c in self.consoleserverport_templates.all() for c in self.consoleserverporttemplates.all()
] ]
if self.powerport_templates.exists(): if self.powerporttemplates.exists():
data['power-ports'] = [ data['power-ports'] = [
{ {
'name': c.name, 'name': c.name,
@ -1073,9 +1073,9 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'maximum_draw': c.maximum_draw, 'maximum_draw': c.maximum_draw,
'allocated_draw': c.allocated_draw, 'allocated_draw': c.allocated_draw,
} }
for c in self.powerport_templates.all() for c in self.powerporttemplates.all()
] ]
if self.poweroutlet_templates.exists(): if self.poweroutlettemplates.exists():
data['power-outlets'] = [ data['power-outlets'] = [
{ {
'name': c.name, 'name': c.name,
@ -1083,18 +1083,18 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'power_port': c.power_port.name if c.power_port else None, 'power_port': c.power_port.name if c.power_port else None,
'feed_leg': c.feed_leg, 'feed_leg': c.feed_leg,
} }
for c in self.poweroutlet_templates.all() for c in self.poweroutlettemplates.all()
] ]
if self.interface_templates.exists(): if self.interfacetemplates.exists():
data['interfaces'] = [ data['interfaces'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
'mgmt_only': c.mgmt_only, 'mgmt_only': c.mgmt_only,
} }
for c in self.interface_templates.all() for c in self.interfacetemplates.all()
] ]
if self.frontport_templates.exists(): if self.frontporttemplates.exists():
data['front-ports'] = [ data['front-ports'] = [
{ {
'name': c.name, 'name': c.name,
@ -1102,23 +1102,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'rear_port': c.rear_port.name, 'rear_port': c.rear_port.name,
'rear_port_position': c.rear_port_position, 'rear_port_position': c.rear_port_position,
} }
for c in self.frontport_templates.all() for c in self.frontporttemplates.all()
] ]
if self.rearport_templates.exists(): if self.rearporttemplates.exists():
data['rear-ports'] = [ data['rear-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
'positions': c.positions, 'positions': c.positions,
} }
for c in self.rearport_templates.all() for c in self.rearporttemplates.all()
] ]
if self.device_bay_templates.exists(): if self.devicebaytemplates.exists():
data['device-bays'] = [ data['device-bays'] = [
{ {
'name': c.name, 'name': c.name,
} }
for c in self.device_bay_templates.all() for c in self.devicebaytemplates.all()
] ]
return yaml.dump(dict(data), sort_keys=False) return yaml.dump(dict(data), sort_keys=False)
@ -1159,7 +1159,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
if ( if (
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
) and self.device_bay_templates.count(): ) and self.devicebaytemplates.count():
raise ValidationError({ raise ValidationError({
'subdevice_role': "Must delete all device bay templates associated with this device before " 'subdevice_role': "Must delete all device bay templates associated with this device before "
"declassifying it as a parent device." "declassifying it as a parent device."
@ -1634,28 +1634,28 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
# If this is a new Device, instantiate all of the related components per the DeviceType definition # If this is a new Device, instantiate all of the related components per the DeviceType definition
if is_new: if is_new:
ConsolePort.objects.bulk_create( ConsolePort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.consoleport_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.consoleporttemplates.unrestricted()]
) )
ConsoleServerPort.objects.bulk_create( ConsoleServerPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.consoleserverport_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.consoleserverporttemplates.unrestricted()]
) )
PowerPort.objects.bulk_create( PowerPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.powerport_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.powerporttemplates.unrestricted()]
) )
PowerOutlet.objects.bulk_create( PowerOutlet.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.poweroutlet_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.poweroutlettemplates.unrestricted()]
) )
Interface.objects.bulk_create( Interface.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.interface_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.interfacetemplates.unrestricted()]
) )
RearPort.objects.bulk_create( RearPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.rearport_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.rearporttemplates.unrestricted()]
) )
FrontPort.objects.bulk_create( FrontPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.frontport_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.frontporttemplates.unrestricted()]
) )
DeviceBay.objects.bulk_create( DeviceBay.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.device_bay_templates.unrestricted()] [x.instantiate(self) for x in self.device_type.devicebaytemplates.unrestricted()]
) )
# Update Site and Rack assignment for any child Devices # Update Site and Rack assignment for any child Devices

View File

@ -27,6 +27,11 @@ __all__ = (
class ComponentTemplateModel(models.Model): class ComponentTemplateModel(models.Model):
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='%(class)ss'
)
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
@ -81,11 +86,6 @@ class ConsolePortTemplate(ComponentTemplateModel):
""" """
A template for a ConsolePort to be created for a new Device. A template for a ConsolePort to be created for a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='consoleport_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -108,11 +108,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
""" """
A template for a ConsoleServerPort to be created for a new Device. A template for a ConsoleServerPort to be created for a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='consoleserverport_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -135,11 +130,6 @@ class PowerPortTemplate(ComponentTemplateModel):
""" """
A template for a PowerPort to be created for a new Device. A template for a PowerPort to be created for a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='powerport_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
@ -176,11 +166,6 @@ class PowerOutletTemplate(ComponentTemplateModel):
""" """
A template for a PowerOutlet to be created for a new Device. A template for a PowerOutlet to be created for a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='poweroutlet_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
@ -230,11 +215,7 @@ class InterfaceTemplate(ComponentTemplateModel):
""" """
A template for a physical data interface on a new Device. A template for a physical data interface on a new Device.
""" """
device_type = models.ForeignKey( # Override ComponentTemplateModel._name to specify naturalize_interface function
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='interface_templates'
)
_name = NaturalOrderingField( _name = NaturalOrderingField(
target_field='name', target_field='name',
naturalize_function=naturalize_interface, naturalize_function=naturalize_interface,
@ -267,11 +248,6 @@ class FrontPortTemplate(ComponentTemplateModel):
""" """
Template for a pass-through port on the front of a new Device. Template for a pass-through port on the front of a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='frontport_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -327,11 +303,6 @@ class RearPortTemplate(ComponentTemplateModel):
""" """
Template for a pass-through port on the rear of a new Device. Template for a pass-through port on the rear of a new Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='rearport_templates'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -358,12 +329,6 @@ class DeviceBayTemplate(ComponentTemplateModel):
""" """
A template for a DeviceBay to be created for a new parent Device. A template for a DeviceBay to be created for a new parent Device.
""" """
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='device_bay_templates'
)
class Meta: class Meta:
ordering = ('device_type', '_name') ordering = ('device_type', '_name')
unique_together = ('device_type', 'name') unique_together = ('device_type', 'name')

View File

@ -36,6 +36,11 @@ __all__ = (
class ComponentModel(models.Model): class ComponentModel(models.Model):
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='%(class)ss'
)
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
@ -246,11 +251,6 @@ class ConsolePort(CableTermination, ComponentModel):
""" """
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='consoleports'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -298,11 +298,6 @@ class ConsoleServerPort(CableTermination, ComponentModel):
""" """
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='consoleserverports'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -343,11 +338,6 @@ class PowerPort(CableTermination, ComponentModel):
""" """
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='powerports'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
@ -496,11 +486,6 @@ class PowerOutlet(CableTermination, ComponentModel):
""" """
A physical power outlet (output) within a Device which provides power to a PowerPort. A physical power outlet (output) within a Device which provides power to a PowerPort.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='poweroutlets'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
@ -560,6 +545,9 @@ class PowerOutlet(CableTermination, ComponentModel):
# #
class BaseInterface(models.Model): class BaseInterface(models.Model):
"""
Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
"""
enabled = models.BooleanField( enabled = models.BooleanField(
default=True default=True
) )
@ -589,13 +577,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
""" """
A network interface within a Device. A physical Interface can connect to exactly one other Interface. A network interface within a Device. A physical Interface can connect to exactly one other Interface.
""" """
device = models.ForeignKey( # Override ComponentModel._name to specify naturalize_interface function
to='Device',
on_delete=models.CASCADE,
related_name='interfaces',
null=True,
blank=True
)
_name = NaturalOrderingField( _name = NaturalOrderingField(
target_field='name', target_field='name',
naturalize_function=naturalize_interface, naturalize_function=naturalize_interface,
@ -807,11 +789,6 @@ class FrontPort(CableTermination, ComponentModel):
""" """
A pass-through port on the front of a Device. A pass-through port on the front of a Device.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='frontports'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -872,11 +849,6 @@ class RearPort(CableTermination, ComponentModel):
""" """
A pass-through port on the rear of a Device. A pass-through port on the rear of a Device.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='rearports'
)
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -916,11 +888,6 @@ class DeviceBay(ComponentModel):
""" """
An empty space within a Device which can house a child device An empty space within a Device which can house a child device
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='device_bays'
)
installed_device = models.OneToOneField( installed_device = models.OneToOneField(
to='dcim.Device', to='dcim.Device',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -981,11 +948,6 @@ class InventoryItem(ComponentModel):
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
InventoryItems are used only for inventory purposes. InventoryItems are used only for inventory purposes.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='inventory_items'
)
parent = models.ForeignKey( parent = models.ForeignKey(
to='self', to='self',
on_delete=models.CASCADE, on_delete=models.CASCADE,

View File

@ -481,45 +481,45 @@ device-bays:
self.assertEqual(dt.comments, 'test comment') self.assertEqual(dt.comments, 'test comment')
# Verify all of the components were created # Verify all of the components were created
self.assertEqual(dt.consoleport_templates.count(), 3) self.assertEqual(dt.consoleporttemplates.count(), 3)
cp1 = ConsolePortTemplate.objects.first() cp1 = ConsolePortTemplate.objects.first()
self.assertEqual(cp1.name, 'Console Port 1') self.assertEqual(cp1.name, 'Console Port 1')
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9) self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
self.assertEqual(dt.consoleserverport_templates.count(), 3) self.assertEqual(dt.consoleserverporttemplates.count(), 3)
csp1 = ConsoleServerPortTemplate.objects.first() csp1 = ConsoleServerPortTemplate.objects.first()
self.assertEqual(csp1.name, 'Console Server Port 1') self.assertEqual(csp1.name, 'Console Server Port 1')
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45) self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
self.assertEqual(dt.powerport_templates.count(), 3) self.assertEqual(dt.powerporttemplates.count(), 3)
pp1 = PowerPortTemplate.objects.first() pp1 = PowerPortTemplate.objects.first()
self.assertEqual(pp1.name, 'Power Port 1') self.assertEqual(pp1.name, 'Power Port 1')
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14) self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
self.assertEqual(dt.poweroutlet_templates.count(), 3) self.assertEqual(dt.poweroutlettemplates.count(), 3)
po1 = PowerOutletTemplate.objects.first() po1 = PowerOutletTemplate.objects.first()
self.assertEqual(po1.name, 'Power Outlet 1') self.assertEqual(po1.name, 'Power Outlet 1')
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13) self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
self.assertEqual(po1.power_port, pp1) self.assertEqual(po1.power_port, pp1)
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A) self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
self.assertEqual(dt.interface_templates.count(), 3) self.assertEqual(dt.interfacetemplates.count(), 3)
iface1 = InterfaceTemplate.objects.first() iface1 = InterfaceTemplate.objects.first()
self.assertEqual(iface1.name, 'Interface 1') self.assertEqual(iface1.name, 'Interface 1')
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED) self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
self.assertTrue(iface1.mgmt_only) self.assertTrue(iface1.mgmt_only)
self.assertEqual(dt.rearport_templates.count(), 3) self.assertEqual(dt.rearporttemplates.count(), 3)
rp1 = RearPortTemplate.objects.first() rp1 = RearPortTemplate.objects.first()
self.assertEqual(rp1.name, 'Rear Port 1') self.assertEqual(rp1.name, 'Rear Port 1')
self.assertEqual(dt.frontport_templates.count(), 3) self.assertEqual(dt.frontporttemplates.count(), 3)
fp1 = FrontPortTemplate.objects.first() fp1 = FrontPortTemplate.objects.first()
self.assertEqual(fp1.name, 'Front Port 1') self.assertEqual(fp1.name, 'Front Port 1')
self.assertEqual(fp1.rear_port, rp1) self.assertEqual(fp1.rear_port, rp1)
self.assertEqual(fp1.rear_port_position, 1) self.assertEqual(fp1.rear_port_position, 1)
self.assertEqual(dt.device_bay_templates.count(), 3) self.assertEqual(dt.devicebaytemplates.count(), 3)
db1 = DeviceBayTemplate.objects.first() db1 = DeviceBayTemplate.objects.first()
self.assertEqual(db1.name, 'Device Bay 1') self.assertEqual(db1.name, 'Device Bay 1')

View File

@ -101,7 +101,7 @@
</li> </li>
<li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}> <li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}>
<a href="{% url 'dcim:device_inventory' pk=device.pk %}"> <a href="{% url 'dcim:device_inventory' pk=device.pk %}">
Inventory <span class="badge">{{ device.inventory_items.count }}</span> Inventory <span class="badge">{{ device.inventoryitems.count }}</span>
</a> </a>
</li> </li>
{% if perms.dcim.napalm_read_device %} {% if perms.dcim.napalm_read_device %}

View File

@ -155,7 +155,7 @@
{% plugin_right_page devicetype %} {% plugin_right_page devicetype %}
</div> </div>
</div> </div>
{% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %} {% if devicetype.consoleporttemplates.exists or devicetype.powerporttemplates.exists %}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:consoleporttemplate_add' edit_url='dcim:consoleporttemplate_bulk_edit' delete_url='dcim:consoleporttemplate_bulk_delete' %} {% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:consoleporttemplate_add' edit_url='dcim:consoleporttemplate_bulk_edit' delete_url='dcim:consoleporttemplate_bulk_delete' %}
@ -177,28 +177,28 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if devicetype.consoleserverport_templates.exists %} {% if devicetype.consoleserverporttemplates.exists %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:consoleserverporttemplate_add' edit_url='dcim:consoleserverporttemplate_bulk_edit' delete_url='dcim:consoleserverporttemplate_bulk_delete' %} {% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:consoleserverporttemplate_add' edit_url='dcim:consoleserverporttemplate_bulk_edit' delete_url='dcim:consoleserverporttemplate_bulk_delete' %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if devicetype.poweroutlet_templates.exists %} {% if devicetype.poweroutlettemplates.exists %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:poweroutlettemplate_add' edit_url='dcim:poweroutlettemplate_bulk_edit' delete_url='dcim:poweroutlettemplate_bulk_delete' %} {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:poweroutlettemplate_add' edit_url='dcim:poweroutlettemplate_bulk_edit' delete_url='dcim:poweroutlettemplate_bulk_delete' %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if devicetype.interface_templates.exists %} {% if devicetype.interfacetemplates.exists %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:interfacetemplate_add' edit_url='dcim:interfacetemplate_bulk_edit' delete_url='dcim:interfacetemplate_bulk_delete' %} {% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:interfacetemplate_add' edit_url='dcim:interfacetemplate_bulk_edit' delete_url='dcim:interfacetemplate_bulk_delete' %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if devicetype.frontport_templates.exists or devicetype.rearport_templates.exists %} {% if devicetype.frontporttemplates.exists or devicetype.rearporttemplates.exists %}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' add_url='dcim:frontporttemplate_add' edit_url='dcim:frontporttemplate_bulk_edit' delete_url='dcim:frontporttemplate_bulk_delete' %} {% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' add_url='dcim:frontporttemplate_add' edit_url='dcim:frontporttemplate_bulk_edit' delete_url='dcim:frontporttemplate_bulk_delete' %}