mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Renamed Module to InventoryItem (prep for #824)
This commit is contained in:
@ -93,9 +93,12 @@ A device's platform is used to denote the type of software running on it. This c
|
|||||||
|
|
||||||
The assignment of platforms to devices is an optional feature, and may be disregarded if not desired.
|
The assignment of platforms to devices is an optional feature, and may be disregarded if not desired.
|
||||||
|
|
||||||
### Modules
|
### Inventory Items
|
||||||
|
|
||||||
A device can be assigned modules which represent internal components. Currently, these are used merely for inventory tracking, although future development might see their functionality expand. Each module can optionally be assigned to a manufacturer.
|
Inventory items represent hardware components installed within a device, such as a power supply or CPU. Currently, these are used merely for inventory tracking, although future development might see their functionality expand. Each item can optionally be assigned a manufacturer.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Prior to version 2.0, inventory items were called modules.
|
||||||
|
|
||||||
### Components
|
### Components
|
||||||
|
|
||||||
@ -113,6 +116,3 @@ Console ports connect only to console server ports, and power ports connect only
|
|||||||
Each interface is a assigned a form factor denoting its physical properties. Two special form factors exist: the "virtual" form factor can be used to designate logical interfaces (such as SVIs), and the "LAG" form factor can be used to desinate link aggregation groups to which physical interfaces can be assigned. Each interface can also be designated as management-only (for out-of-band management) and assigned a short description.
|
Each interface is a assigned a form factor denoting its physical properties. Two special form factors exist: the "virtual" form factor can be used to designate logical interfaces (such as SVIs), and the "LAG" form factor can be used to desinate link aggregation groups to which physical interfaces can be assigned. Each interface can also be designated as management-only (for out-of-band management) and assigned a short description.
|
||||||
|
|
||||||
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear on rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
|
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear on rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
|
||||||
|
|
||||||
!!! note
|
|
||||||
Child devices differ from modules in that they are still treated as independent devices, with their own console/power/data components, modules, and IP addresses. Modules, on the other hand, are parts within a device, such as a hard disk or power supply, which do not provide their own management plane.
|
|
||||||
|
@ -5,7 +5,7 @@ from mptt.admin import MPTTModelAdmin
|
|||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Module, Platform,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, InventoryItem, Platform,
|
||||||
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region,
|
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region,
|
||||||
Site,
|
Site,
|
||||||
)
|
)
|
||||||
@ -183,8 +183,8 @@ class DeviceBayAdmin(admin.TabularInline):
|
|||||||
readonly_fields = ['installed_device']
|
readonly_fields = ['installed_device']
|
||||||
|
|
||||||
|
|
||||||
class ModuleAdmin(admin.TabularInline):
|
class InventoryItemAdmin(admin.TabularInline):
|
||||||
model = Module
|
model = InventoryItem
|
||||||
readonly_fields = ['parent', 'discovered']
|
readonly_fields = ['parent', 'discovered']
|
||||||
|
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ class DeviceAdmin(admin.ModelAdmin):
|
|||||||
PowerOutletAdmin,
|
PowerOutletAdmin,
|
||||||
InterfaceAdmin,
|
InterfaceAdmin,
|
||||||
DeviceBayAdmin,
|
DeviceBayAdmin,
|
||||||
ModuleAdmin,
|
InventoryItemAdmin,
|
||||||
]
|
]
|
||||||
list_display = ['display_name', 'device_type_full_name', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
|
list_display = ['display_name', 'device_type_full_name', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
|
||||||
'serial']
|
'serial']
|
||||||
|
@ -5,7 +5,7 @@ from ipam.models import IPAddress
|
|||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
CONNECTION_STATUS_CHOICES, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
CONNECTION_STATUS_CHOICES, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||||
DeviceBay, DeviceBayTemplate, DeviceType, DeviceRole, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, Interface,
|
DeviceBay, DeviceBayTemplate, DeviceType, DeviceRole, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, Interface,
|
||||||
InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RACK_FACE_CHOICES, RACK_TYPE_CHOICES,
|
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RACK_FACE_CHOICES, RACK_TYPE_CHOICES,
|
||||||
RACK_WIDTH_CHOICES, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
RACK_WIDTH_CHOICES, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
||||||
)
|
)
|
||||||
@ -642,22 +642,22 @@ class WritableDeviceBaySerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Modules
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
|
||||||
class ModuleSerializer(serializers.ModelSerializer):
|
class InventoryItemSerializer(serializers.ModelSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
manufacturer = NestedManufacturerSerializer()
|
manufacturer = NestedManufacturerSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = InventoryItem
|
||||||
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
|
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
|
||||||
|
|
||||||
|
|
||||||
class WritableModuleSerializer(serializers.ModelSerializer):
|
class WritableInventoryItemSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = InventoryItem
|
||||||
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
|
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ router.register(r'power-ports', views.PowerPortViewSet)
|
|||||||
router.register(r'power-outlets', views.PowerOutletViewSet)
|
router.register(r'power-outlets', views.PowerOutletViewSet)
|
||||||
router.register(r'interfaces', views.InterfaceViewSet)
|
router.register(r'interfaces', views.InterfaceViewSet)
|
||||||
router.register(r'device-bays', views.DeviceBayViewSet)
|
router.register(r'device-bays', views.DeviceBayViewSet)
|
||||||
router.register(r'modules', views.ModuleViewSet)
|
router.register(r'inventory-items', views.InventoryItemViewSet)
|
||||||
|
|
||||||
# Interface connections
|
# Interface connections
|
||||||
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
|
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
|
||||||
|
@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem,
|
||||||
Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation,
|
Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation,
|
||||||
RackRole, Region, Site,
|
RackRole, Region, Site,
|
||||||
)
|
)
|
||||||
@ -294,11 +294,11 @@ class DeviceBayViewSet(WritableSerializerMixin, ModelViewSet):
|
|||||||
filter_class = filters.DeviceBayFilter
|
filter_class = filters.DeviceBayFilter
|
||||||
|
|
||||||
|
|
||||||
class ModuleViewSet(WritableSerializerMixin, ModelViewSet):
|
class InventoryItemViewSet(WritableSerializerMixin, ModelViewSet):
|
||||||
queryset = Module.objects.select_related('device', 'manufacturer')
|
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||||
serializer_class = serializers.ModuleSerializer
|
serializer_class = serializers.InventoryItemSerializer
|
||||||
write_serializer_class = serializers.WritableModuleSerializer
|
write_serializer_class = serializers.WritableInventoryItemSerializer
|
||||||
filter_class = filters.ModuleFilter
|
filter_class = filters.InventoryItemFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -9,7 +9,7 @@ from utilities.filters import NullableModelMultipleChoiceFilter
|
|||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site, VIRTUAL_IFACE_TYPES,
|
RackReservation, RackRole, Region, Site, VIRTUAL_IFACE_TYPES,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
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(modules__serial__icontains=value.strip()) |
|
Q(inventory_items__serial__icontains=value.strip()) |
|
||||||
Q(asset_tag=value.strip()) |
|
Q(asset_tag=value.strip()) |
|
||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
@ -444,10 +444,10 @@ class DeviceBayFilter(DeviceComponentFilterSet):
|
|||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class ModuleFilter(DeviceComponentFilterSet):
|
class InventoryItemFilter(DeviceComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = InventoryItem
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from .models import (
|
|||||||
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||||
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES,
|
||||||
RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD,
|
RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD,
|
||||||
VIRTUAL_IFACE_TYPES
|
VIRTUAL_IFACE_TYPES
|
||||||
)
|
)
|
||||||
@ -1684,11 +1684,11 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Modules
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
|
||||||
class ModuleForm(BootstrapMixin, forms.ModelForm):
|
class InventoryItemForm(BootstrapMixin, forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = InventoryItem
|
||||||
fields = ['name', 'manufacturer', 'part_id', 'serial']
|
fields = ['name', 'manufacturer', 'part_id', 'serial']
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.6 on 2017-03-21 14:55
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0033_rackreservation_rack_editable'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Module',
|
||||||
|
new_name='InventoryItem',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='device',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='dcim.Device'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='parent',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.InventoryItem'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventoryitem',
|
||||||
|
name='manufacturer',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.Manufacturer'),
|
||||||
|
),
|
||||||
|
]
|
@ -1397,19 +1397,19 @@ class DeviceBay(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Modules
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Module(models.Model):
|
class InventoryItem(models.Model):
|
||||||
"""
|
"""
|
||||||
A Module represents a piece of hardware within a Device, such as a line card or power supply. Modules are used only
|
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
|
||||||
for inventory purposes.
|
InventoryItems are used only for inventory purposes.
|
||||||
"""
|
"""
|
||||||
device = models.ForeignKey('Device', related_name='modules', on_delete=models.CASCADE)
|
device = models.ForeignKey('Device', related_name='inventory_items', on_delete=models.CASCADE)
|
||||||
parent = models.ForeignKey('self', related_name='submodules', blank=True, null=True, on_delete=models.CASCADE)
|
parent = models.ForeignKey('self', related_name='child_items', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=50, verbose_name='Name')
|
name = models.CharField(max_length=50, verbose_name='Name')
|
||||||
manufacturer = models.ForeignKey('Manufacturer', related_name='modules', blank=True, null=True,
|
manufacturer = models.ForeignKey('Manufacturer', related_name='inventory_items', blank=True, null=True,
|
||||||
on_delete=models.PROTECT)
|
on_delete=models.PROTECT)
|
||||||
part_id = models.CharField(max_length=50, verbose_name='Part ID', blank=True)
|
part_id = models.CharField(max_length=50, verbose_name='Part ID', blank=True)
|
||||||
serial = models.CharField(max_length=50, verbose_name='Serial number', blank=True)
|
serial = models.CharField(max_length=50, verbose_name='Serial number', blank=True)
|
||||||
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
|||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, Module, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
Manufacturer, InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
||||||
)
|
)
|
||||||
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||||
@ -1847,7 +1847,7 @@ class DeviceBayTest(HttpStatusMixin, APITestCase):
|
|||||||
self.assertEqual(DeviceBay.objects.count(), 2)
|
self.assertEqual(DeviceBay.objects.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTest(HttpStatusMixin, APITestCase):
|
class InventoryItemTest(HttpStatusMixin, APITestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
@ -1866,71 +1866,71 @@ class ModuleTest(HttpStatusMixin, APITestCase):
|
|||||||
self.device = Device.objects.create(
|
self.device = Device.objects.create(
|
||||||
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
|
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
|
||||||
)
|
)
|
||||||
self.module1 = Module.objects.create(device=self.device, name='Test Module 1')
|
self.inventoryitem1 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 1')
|
||||||
self.module2 = Module.objects.create(device=self.device, name='Test Module 2')
|
self.inventoryitem2 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 2')
|
||||||
self.module3 = Module.objects.create(device=self.device, name='Test Module 3')
|
self.inventoryitem3 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 3')
|
||||||
|
|
||||||
def test_get_module(self):
|
def test_get_inventoryitem(self):
|
||||||
|
|
||||||
url = reverse('dcim-api:module-detail', kwargs={'pk': self.module1.pk})
|
url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
|
||||||
response = self.client.get(url, **self.header)
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
self.assertEqual(response.data['name'], self.module1.name)
|
self.assertEqual(response.data['name'], self.inventoryitem1.name)
|
||||||
|
|
||||||
def test_list_modules(self):
|
def test_list_inventoryitems(self):
|
||||||
|
|
||||||
url = reverse('dcim-api:module-list')
|
url = reverse('dcim-api:inventoryitem-list')
|
||||||
response = self.client.get(url, **self.header)
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
def test_create_module(self):
|
def test_create_inventoryitem(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'parent': self.module1.pk,
|
'parent': self.inventoryitem1.pk,
|
||||||
'name': 'Test Module 4',
|
'name': 'Test Inventory Item 4',
|
||||||
'manufacturer': self.manufacturer.pk,
|
'manufacturer': self.manufacturer.pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('dcim-api:module-list')
|
url = reverse('dcim-api:inventoryitem-list')
|
||||||
response = self.client.post(url, data, **self.header)
|
response = self.client.post(url, data, **self.header)
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(Module.objects.count(), 4)
|
self.assertEqual(InventoryItem.objects.count(), 4)
|
||||||
module4 = Module.objects.get(pk=response.data['id'])
|
inventoryitem4 = InventoryItem.objects.get(pk=response.data['id'])
|
||||||
self.assertEqual(module4.device_id, data['device'])
|
self.assertEqual(inventoryitem4.device_id, data['device'])
|
||||||
self.assertEqual(module4.parent_id, data['parent'])
|
self.assertEqual(inventoryitem4.parent_id, data['parent'])
|
||||||
self.assertEqual(module4.name, data['name'])
|
self.assertEqual(inventoryitem4.name, data['name'])
|
||||||
self.assertEqual(module4.manufacturer_id, data['manufacturer'])
|
self.assertEqual(inventoryitem4.manufacturer_id, data['manufacturer'])
|
||||||
|
|
||||||
def test_update_module(self):
|
def test_update_inventoryitem(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'parent': self.module1.pk,
|
'parent': self.inventoryitem1.pk,
|
||||||
'name': 'Test Module X',
|
'name': 'Test Inventory Item X',
|
||||||
'manufacturer': self.manufacturer.pk,
|
'manufacturer': self.manufacturer.pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('dcim-api:module-detail', kwargs={'pk': self.module1.pk})
|
url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
|
||||||
response = self.client.put(url, data, **self.header)
|
response = self.client.put(url, data, **self.header)
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
self.assertEqual(Module.objects.count(), 3)
|
self.assertEqual(InventoryItem.objects.count(), 3)
|
||||||
module1 = Module.objects.get(pk=response.data['id'])
|
inventoryitem1 = InventoryItem.objects.get(pk=response.data['id'])
|
||||||
self.assertEqual(module1.device_id, data['device'])
|
self.assertEqual(inventoryitem1.device_id, data['device'])
|
||||||
self.assertEqual(module1.parent_id, data['parent'])
|
self.assertEqual(inventoryitem1.parent_id, data['parent'])
|
||||||
self.assertEqual(module1.name, data['name'])
|
self.assertEqual(inventoryitem1.name, data['name'])
|
||||||
self.assertEqual(module1.manufacturer_id, data['manufacturer'])
|
self.assertEqual(inventoryitem1.manufacturer_id, data['manufacturer'])
|
||||||
|
|
||||||
def test_delete_module(self):
|
def test_delete_inventoryitem(self):
|
||||||
|
|
||||||
url = reverse('dcim-api:module-detail', kwargs={'pk': self.module1.pk})
|
url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
|
||||||
response = self.client.delete(url, **self.header)
|
response = self.client.delete(url, **self.header)
|
||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
||||||
self.assertEqual(Module.objects.count(), 2)
|
self.assertEqual(InventoryItem.objects.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionTest(HttpStatusMixin, APITestCase):
|
class InterfaceConnectionTest(HttpStatusMixin, APITestCase):
|
||||||
|
@ -173,6 +173,11 @@ urlpatterns = [
|
|||||||
url(r'^device-bays/(?P<pk>\d+)/populate/$', views.devicebay_populate, name='devicebay_populate'),
|
url(r'^device-bays/(?P<pk>\d+)/populate/$', views.devicebay_populate, name='devicebay_populate'),
|
||||||
url(r'^device-bays/(?P<pk>\d+)/depopulate/$', views.devicebay_depopulate, name='devicebay_depopulate'),
|
url(r'^device-bays/(?P<pk>\d+)/depopulate/$', views.devicebay_depopulate, name='devicebay_depopulate'),
|
||||||
|
|
||||||
|
# Inventory items
|
||||||
|
url(r'^devices/(?P<device>\d+)/inventory-items/add/$', views.InventoryItemEditView.as_view(), name='inventoryitem_add'),
|
||||||
|
url(r'^inventory-items/(?P<pk>\d+)/edit/$', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'),
|
||||||
|
url(r'^inventory-items/(?P<pk>\d+)/delete/$', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'),
|
||||||
|
|
||||||
# Console/power/interface connections
|
# Console/power/interface connections
|
||||||
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
url(r'^console-connections/$', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||||
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
url(r'^console-connections/import/$', views.ConsoleConnectionsBulkImportView.as_view(), name='console_connections_import'),
|
||||||
@ -181,9 +186,4 @@ urlpatterns = [
|
|||||||
url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
|
url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
|
||||||
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
||||||
|
|
||||||
# Modules
|
|
||||||
url(r'^devices/(?P<device>\d+)/modules/add/$', views.ModuleEditView.as_view(), name='module_add'),
|
|
||||||
url(r'^modules/(?P<pk>\d+)/edit/$', views.ModuleEditView.as_view(), name='module_edit'),
|
|
||||||
url(r'^modules/(?P<pk>\d+)/delete/$', views.ModuleDeleteView.as_view(), name='module_delete'),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -25,7 +25,7 @@ from . import filters, forms, tables
|
|||||||
from .models import (
|
from .models import (
|
||||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||||
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site,
|
RackReservation, RackRole, Region, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -799,12 +799,12 @@ class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
def device_inventory(request, pk):
|
def device_inventory(request, pk):
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
modules = Module.objects.filter(device=device, parent=None).select_related('manufacturer')\
|
inventory_items = InventoryItem.objects.filter(device=device, parent=None).select_related('manufacturer')\
|
||||||
.prefetch_related('submodules')
|
.prefetch_related('child_items')
|
||||||
|
|
||||||
return render(request, 'dcim/device_inventory.html', {
|
return render(request, 'dcim/device_inventory.html', {
|
||||||
'device': device,
|
'device': device,
|
||||||
'modules': modules,
|
'inventory_items': inventory_items,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -1594,13 +1594,13 @@ def ipaddress_assign(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Modules
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
|
||||||
class ModuleEditView(PermissionRequiredMixin, ComponentEditView):
|
class InventoryItemEditView(PermissionRequiredMixin, ComponentEditView):
|
||||||
permission_required = 'dcim.change_module'
|
permission_required = 'dcim.change_inventoryitem'
|
||||||
model = Module
|
model = InventoryItem
|
||||||
form_class = forms.ModuleForm
|
form_class = forms.InventoryItemForm
|
||||||
|
|
||||||
def alter_obj(self, obj, request, url_args, url_kwargs):
|
def alter_obj(self, obj, request, url_args, url_kwargs):
|
||||||
if 'device' in url_kwargs:
|
if 'device' in url_kwargs:
|
||||||
@ -1608,6 +1608,6 @@ class ModuleEditView(PermissionRequiredMixin, ComponentEditView):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class ModuleDeleteView(PermissionRequiredMixin, ComponentDeleteView):
|
class InventoryItemDeleteView(PermissionRequiredMixin, ComponentDeleteView):
|
||||||
permission_required = 'dcim.delete_module'
|
permission_required = 'dcim.delete_inventoryitem'
|
||||||
model = Module
|
model = InventoryItem
|
||||||
|
@ -6,7 +6,7 @@ from django.conf import settings
|
|||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from dcim.models import Device, Module, Site
|
from dcim.models import Device, InventoryItem, Site
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -25,12 +25,12 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
def create_modules(modules, parent=None):
|
def create_inventory_items(inventory_items, parent=None):
|
||||||
for module in modules:
|
for item in inventory_items:
|
||||||
m = Module(device=device, parent=parent, name=module['name'], part_id=module['part_id'],
|
i = InventoryItem(device=device, parent=parent, name=item['name'], part_id=item['part_id'],
|
||||||
serial=module['serial'], discovered=True)
|
serial=item['serial'], discovered=True)
|
||||||
m.save()
|
i.save()
|
||||||
create_modules(module.get('modules', []), parent=m)
|
create_inventory_items(item.get('items', []), parent=i)
|
||||||
|
|
||||||
# Credentials
|
# Credentials
|
||||||
if options['username']:
|
if options['username']:
|
||||||
@ -107,9 +107,9 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write("")
|
self.stdout.write("")
|
||||||
self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial']))
|
self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial']))
|
||||||
self.stdout.write("\tDescription: {}".format(inventory['chassis']['description']))
|
self.stdout.write("\tDescription: {}".format(inventory['chassis']['description']))
|
||||||
for module in inventory['modules']:
|
for item in inventory['items']:
|
||||||
self.stdout.write("\tModule: {} / {} ({})".format(module['name'], module['part_id'],
|
self.stdout.write("\tItem: {} / {} ({})".format(item['name'], item['part_id'],
|
||||||
module['serial']))
|
item['serial']))
|
||||||
else:
|
else:
|
||||||
self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial']))
|
self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial']))
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ class Command(BaseCommand):
|
|||||||
if device.serial != inventory['chassis']['serial']:
|
if device.serial != inventory['chassis']['serial']:
|
||||||
device.serial = inventory['chassis']['serial']
|
device.serial = inventory['chassis']['serial']
|
||||||
device.save()
|
device.save()
|
||||||
Module.objects.filter(device=device, discovered=True).delete()
|
InventoryItem.objects.filter(device=device, discovered=True).delete()
|
||||||
create_modules(inventory.get('modules', []))
|
create_inventory_items(inventory.get('items', []))
|
||||||
|
|
||||||
self.stdout.write("Finished!")
|
self.stdout.write("Finished!")
|
||||||
|
@ -33,14 +33,14 @@ class RPCClient(object):
|
|||||||
|
|
||||||
def get_inventory(self):
|
def get_inventory(self):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary representing the device chassis and installed modules.
|
Returns a dictionary representing the device chassis and installed inventory items.
|
||||||
|
|
||||||
{
|
{
|
||||||
'chassis': {
|
'chassis': {
|
||||||
'serial': <str>,
|
'serial': <str>,
|
||||||
'description': <str>,
|
'description': <str>,
|
||||||
}
|
}
|
||||||
'modules': [
|
'items': [
|
||||||
{
|
{
|
||||||
'name': <str>,
|
'name': <str>,
|
||||||
'part_id': <str>,
|
'part_id': <str>,
|
||||||
@ -144,23 +144,23 @@ class JunosNC(RPCClient):
|
|||||||
|
|
||||||
def get_inventory(self):
|
def get_inventory(self):
|
||||||
|
|
||||||
def glean_modules(node, depth=0):
|
def glean_items(node, depth=0):
|
||||||
modules = []
|
items = []
|
||||||
modules_list = node.get('chassis{}-module'.format('-sub' * depth), [])
|
items_list = node.get('chassis{}-module'.format('-sub' * depth), [])
|
||||||
# Junos like to return single children directly instead of as a single-item list
|
# Junos like to return single children directly instead of as a single-item list
|
||||||
if hasattr(modules_list, 'items'):
|
if hasattr(items_list, 'items'):
|
||||||
modules_list = [modules_list]
|
items_list = [items_list]
|
||||||
for module in modules_list:
|
for item in items_list:
|
||||||
m = {
|
m = {
|
||||||
'name': module['name'],
|
'name': item['name'],
|
||||||
'part_id': module.get('model-number') or module.get('part-number', ''),
|
'part_id': item.get('model-number') or item.get('part-number', ''),
|
||||||
'serial': module.get('serial-number', ''),
|
'serial': item.get('serial-number', ''),
|
||||||
}
|
}
|
||||||
submodules = glean_modules(module, depth + 1)
|
child_items = glean_items(item, depth + 1)
|
||||||
if submodules:
|
if child_items:
|
||||||
m['modules'] = submodules
|
m['items'] = child_items
|
||||||
modules.append(m)
|
items.append(m)
|
||||||
return modules
|
return items
|
||||||
|
|
||||||
rpc_reply = self.manager.dispatch('get-chassis-inventory')
|
rpc_reply = self.manager.dispatch('get-chassis-inventory')
|
||||||
inventory_raw = xmltodict.parse(rpc_reply.xml)['rpc-reply']['chassis-inventory']['chassis']
|
inventory_raw = xmltodict.parse(rpc_reply.xml)['rpc-reply']['chassis-inventory']['chassis']
|
||||||
@ -173,8 +173,8 @@ class JunosNC(RPCClient):
|
|||||||
'description': inventory_raw['description'],
|
'description': inventory_raw['description'],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Gather modules
|
# Gather inventory items
|
||||||
result['modules'] = glean_modules(inventory_raw)
|
result['items'] = glean_items(inventory_raw)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class IOSSSH(SSHClient):
|
|||||||
'description': parse(sh_ver, 'cisco ([^\s]+)')
|
'description': parse(sh_ver, 'cisco ([^\s]+)')
|
||||||
}
|
}
|
||||||
|
|
||||||
def modules(chassis_serial=None):
|
def items(chassis_serial=None):
|
||||||
cmd = self._send('show inventory').split('\r\n\r\n')
|
cmd = self._send('show inventory').split('\r\n\r\n')
|
||||||
for i in cmd:
|
for i in cmd:
|
||||||
i_fmt = i.replace('\r\n', ' ')
|
i_fmt = i.replace('\r\n', ' ')
|
||||||
@ -207,7 +207,7 @@ class IOSSSH(SSHClient):
|
|||||||
m_name = re.search('NAME: "([^"]+)"', i_fmt).group(1)
|
m_name = re.search('NAME: "([^"]+)"', i_fmt).group(1)
|
||||||
m_pid = re.search('PID: ([^\s]+)', i_fmt).group(1)
|
m_pid = re.search('PID: ([^\s]+)', i_fmt).group(1)
|
||||||
m_serial = re.search('SN: ([^\s]+)', i_fmt).group(1)
|
m_serial = re.search('SN: ([^\s]+)', i_fmt).group(1)
|
||||||
# Omit built-in modules and those with no PID
|
# Omit built-in items and those with no PID
|
||||||
if m_serial != chassis_serial and m_pid.lower() != 'unspecified':
|
if m_serial != chassis_serial and m_pid.lower() != 'unspecified':
|
||||||
yield {
|
yield {
|
||||||
'name': m_name,
|
'name': m_name,
|
||||||
@ -222,7 +222,7 @@ class IOSSSH(SSHClient):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'chassis': sh_version,
|
'chassis': sh_version,
|
||||||
'modules': list(modules(chassis_serial=sh_version.get('serial')))
|
'items': list(items(chassis_serial=sh_version.get('serial')))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ class OpengearSSH(SSHClient):
|
|||||||
'serial': serial,
|
'serial': serial,
|
||||||
'description': description,
|
'description': description,
|
||||||
},
|
},
|
||||||
'modules': [],
|
'items': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
<table class="table table-hover table-condensed panel-body" id="hardware">
|
<table class="table table-hover table-condensed panel-body" id="hardware">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Module</th>
|
<th>Name</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Manufacturer</th>
|
<th>Manufacturer</th>
|
||||||
<th>Part Number</th>
|
<th>Part Number</th>
|
||||||
@ -55,81 +55,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in modules %}
|
{% for item in inventory_items %}
|
||||||
<tr>
|
{% with template_name='dcim/inc/inventoryitem.html' indent=0 %}
|
||||||
<td>{{ m.name }}</td>
|
{% include template_name %}
|
||||||
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
{% endwith %}
|
||||||
<td>{{ m.manufacturer|default:'' }}</td>
|
|
||||||
<td>{{ m.part_id }}</td>
|
|
||||||
<td>{{ m.serial }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.dcim.change_module %}
|
|
||||||
<a href="{% url 'dcim:module_edit' pk=m.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_module %}
|
|
||||||
<a href="{% url 'dcim:module_delete' pk=m.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% for m2 in m.submodules.all %}
|
|
||||||
<tr>
|
|
||||||
<td style="padding-left: 20px">{{ m2.name }}</td>
|
|
||||||
<td>{% if not m2.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
|
||||||
<td>{{ m2.manufacturer|default:'' }}</td>
|
|
||||||
<td>{{ m2.part_id }}</td>
|
|
||||||
<td>{{ m2.serial }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.dcim.change_module %}
|
|
||||||
<a href="{% url 'dcim:module_edit' pk=m2.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_module %}
|
|
||||||
<a href="{% url 'dcim:module_delete' pk=m2.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% for m3 in m2.submodules.all %}
|
|
||||||
<tr>
|
|
||||||
<td style="padding-left: 40px">{{ m3.name }}</td>
|
|
||||||
<td>{% if not m3.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
|
||||||
<td>{{ m3.manufacturer|default:'' }}</td>
|
|
||||||
<td>{{ m3.part_id }}</td>
|
|
||||||
<td>{{ m3.serial }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.dcim.change_module %}
|
|
||||||
<a href="{% url 'dcim:module_edit' pk=m3.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_module %}
|
|
||||||
<a href="{% url 'dcim:module_delete' pk=m3.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% for m4 in m3.submodules.all %}
|
|
||||||
<tr>
|
|
||||||
<td style="padding-left: 60px">{{ m4.name }}</td>
|
|
||||||
<td>{% if not m4.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
|
||||||
<td>{{ m4.manufacturer|default:'' }}</td>
|
|
||||||
<td>{{ m4.part_id }}</td>
|
|
||||||
<td>{{ m4.serial }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.dcim.change_module %}
|
|
||||||
<a href="{% url 'dcim:module_edit' pk=m4.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_module %}
|
|
||||||
<a href="{% url 'dcim:module_delete' pk=m4.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.dcim.add_module %}
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
<a href="{% url 'dcim:module_add' device=device.pk %}" class="btn btn-success">
|
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-success">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
Add a Module
|
Add Inventory Item
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
20
netbox/templates/dcim/inc/inventoryitem.html
Normal file
20
netbox/templates/dcim/inc/inventoryitem.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<tr>
|
||||||
|
<td style="padding-left: {{ indent|add:5 }}px">{{ item.name }}</td>
|
||||||
|
<td>{% if not item.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
|
<td>{{ item.manufacturer|default:'' }}</td>
|
||||||
|
<td>{{ item.part_id }}</td>
|
||||||
|
<td>{{ item.serial }}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if perms.dcim.change_inventory_item %}
|
||||||
|
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.delete_inventory_item %}
|
||||||
|
<a href="{% url 'dcim:inventoryitem_delete' pk=item.pk %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% for item in item.child_items.all %}
|
||||||
|
{% with template_name='dcim/inc/inventoryitem.html' indent=indent|add:20 %}
|
||||||
|
{% include template_name %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
8
netbox/templates/dcim/inventoryitem_delete.html
Normal file
8
netbox/templates/dcim/inventoryitem_delete.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'utilities/confirmation_form.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Delete inventory item {{ inventoryitem }}?{% endblock %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>Are you sure you want to delete this inventory item from <strong>{{ inventoryitem.device }}</strong>?</p>
|
||||||
|
{% endblock %}
|
@ -1,8 +0,0 @@
|
|||||||
{% extends 'utilities/confirmation_form.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block title %}Delete module {{ module }}?{% endblock %}
|
|
||||||
|
|
||||||
{% block message %}
|
|
||||||
<p>Are you sure you want to delete this module from <strong>{{ module.device }}</strong>?</p>
|
|
||||||
{% endblock %}
|
|
Reference in New Issue
Block a user