1
0
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:
Jeremy Stretch
2017-03-21 12:54:08 -04:00
parent 122526a9d0
commit 22768ff6c6
18 changed files with 192 additions and 200 deletions

View File

@ -5,7 +5,7 @@ from mptt.admin import MPTTModelAdmin
from .models import (
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,
Site,
)
@ -183,8 +183,8 @@ class DeviceBayAdmin(admin.TabularInline):
readonly_fields = ['installed_device']
class ModuleAdmin(admin.TabularInline):
model = Module
class InventoryItemAdmin(admin.TabularInline):
model = InventoryItem
readonly_fields = ['parent', 'discovered']
@ -197,7 +197,7 @@ class DeviceAdmin(admin.ModelAdmin):
PowerOutletAdmin,
InterfaceAdmin,
DeviceBayAdmin,
ModuleAdmin,
InventoryItemAdmin,
]
list_display = ['display_name', 'device_type_full_name', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
'serial']

View File

@ -5,7 +5,7 @@ from ipam.models import IPAddress
from dcim.models import (
CONNECTION_STATUS_CHOICES, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
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,
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()
manufacturer = NestedManufacturerSerializer()
class Meta:
model = Module
model = InventoryItem
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']
class WritableModuleSerializer(serializers.ModelSerializer):
class WritableInventoryItemSerializer(serializers.ModelSerializer):
class Meta:
model = Module
model = InventoryItem
fields = ['id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'discovered']

View File

@ -48,7 +48,7 @@ router.register(r'power-ports', views.PowerPortViewSet)
router.register(r'power-outlets', views.PowerOutletViewSet)
router.register(r'interfaces', views.InterfaceViewSet)
router.register(r'device-bays', views.DeviceBayViewSet)
router.register(r'modules', views.ModuleViewSet)
router.register(r'inventory-items', views.InventoryItemViewSet)
# Interface connections
router.register(r'interface-connections', views.InterfaceConnectionViewSet)

View File

@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404
from dcim.models import (
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,
RackRole, Region, Site,
)
@ -294,11 +294,11 @@ class DeviceBayViewSet(WritableSerializerMixin, ModelViewSet):
filter_class = filters.DeviceBayFilter
class ModuleViewSet(WritableSerializerMixin, ModelViewSet):
queryset = Module.objects.select_related('device', 'manufacturer')
serializer_class = serializers.ModuleSerializer
write_serializer_class = serializers.WritableModuleSerializer
filter_class = filters.ModuleFilter
class InventoryItemViewSet(WritableSerializerMixin, ModelViewSet):
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
serializer_class = serializers.InventoryItemSerializer
write_serializer_class = serializers.WritableInventoryItemSerializer
filter_class = filters.InventoryItemFilter
#

View File

@ -9,7 +9,7 @@ from utilities.filters import NullableModelMultipleChoiceFilter
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
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,
)
@ -359,7 +359,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
return queryset.filter(
Q(name__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(modules__serial__icontains=value.strip()) |
Q(inventory_items__serial__icontains=value.strip()) |
Q(asset_tag=value.strip()) |
Q(comments__icontains=value)
).distinct()
@ -444,10 +444,10 @@ class DeviceBayFilter(DeviceComponentFilterSet):
fields = ['name']
class ModuleFilter(DeviceComponentFilterSet):
class InventoryItemFilter(DeviceComponentFilterSet):
class Meta:
model = Module
model = InventoryItem
fields = ['name']

View File

@ -21,7 +21,7 @@ from .models import (
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
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,
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:
model = Module
model = InventoryItem
fields = ['name', 'manufacturer', 'part_id', 'serial']

View File

@ -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'),
),
]

View File

@ -1397,19 +1397,19 @@ class DeviceBay(models.Model):
#
# Modules
# Inventory items
#
@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
for inventory purposes.
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.
"""
device = models.ForeignKey('Device', related_name='modules', on_delete=models.CASCADE)
parent = models.ForeignKey('self', related_name='submodules', blank=True, null=True, on_delete=models.CASCADE)
device = models.ForeignKey('Device', related_name='inventory_items', 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')
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)
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)

View File

@ -7,7 +7,7 @@ from django.urls import reverse
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
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,
)
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)
class ModuleTest(HttpStatusMixin, APITestCase):
class InventoryItemTest(HttpStatusMixin, APITestCase):
def setUp(self):
@ -1866,71 +1866,71 @@ class ModuleTest(HttpStatusMixin, APITestCase):
self.device = Device.objects.create(
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.module2 = Module.objects.create(device=self.device, name='Test Module 2')
self.module3 = Module.objects.create(device=self.device, name='Test Module 3')
self.inventoryitem1 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 1')
self.inventoryitem2 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 2')
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)
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)
self.assertEqual(response.data['count'], 3)
def test_create_module(self):
def test_create_inventoryitem(self):
data = {
'device': self.device.pk,
'parent': self.module1.pk,
'name': 'Test Module 4',
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item 4',
'manufacturer': self.manufacturer.pk,
}
url = reverse('dcim-api:module-list')
url = reverse('dcim-api:inventoryitem-list')
response = self.client.post(url, data, **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(Module.objects.count(), 4)
module4 = Module.objects.get(pk=response.data['id'])
self.assertEqual(module4.device_id, data['device'])
self.assertEqual(module4.parent_id, data['parent'])
self.assertEqual(module4.name, data['name'])
self.assertEqual(module4.manufacturer_id, data['manufacturer'])
self.assertEqual(InventoryItem.objects.count(), 4)
inventoryitem4 = InventoryItem.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitem4.device_id, data['device'])
self.assertEqual(inventoryitem4.parent_id, data['parent'])
self.assertEqual(inventoryitem4.name, data['name'])
self.assertEqual(inventoryitem4.manufacturer_id, data['manufacturer'])
def test_update_module(self):
def test_update_inventoryitem(self):
data = {
'device': self.device.pk,
'parent': self.module1.pk,
'name': 'Test Module X',
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item X',
'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)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(Module.objects.count(), 3)
module1 = Module.objects.get(pk=response.data['id'])
self.assertEqual(module1.device_id, data['device'])
self.assertEqual(module1.parent_id, data['parent'])
self.assertEqual(module1.name, data['name'])
self.assertEqual(module1.manufacturer_id, data['manufacturer'])
self.assertEqual(InventoryItem.objects.count(), 3)
inventoryitem1 = InventoryItem.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitem1.device_id, data['device'])
self.assertEqual(inventoryitem1.parent_id, data['parent'])
self.assertEqual(inventoryitem1.name, data['name'])
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)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(Module.objects.count(), 2)
self.assertEqual(InventoryItem.objects.count(), 2)
class InterfaceConnectionTest(HttpStatusMixin, APITestCase):

View File

@ -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+)/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
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'),
@ -181,9 +186,4 @@ urlpatterns = [
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'),
# 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'),
]

View File

@ -25,7 +25,7 @@ from . import filters, forms, tables
from .models import (
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
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,
)
@ -799,12 +799,12 @@ class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
def device_inventory(request, pk):
device = get_object_or_404(Device, pk=pk)
modules = Module.objects.filter(device=device, parent=None).select_related('manufacturer')\
.prefetch_related('submodules')
inventory_items = InventoryItem.objects.filter(device=device, parent=None).select_related('manufacturer')\
.prefetch_related('child_items')
return render(request, 'dcim/device_inventory.html', {
'device': device,
'modules': modules,
'inventory_items': inventory_items,
})
@ -1594,13 +1594,13 @@ def ipaddress_assign(request, pk):
#
# Modules
# Inventory items
#
class ModuleEditView(PermissionRequiredMixin, ComponentEditView):
permission_required = 'dcim.change_module'
model = Module
form_class = forms.ModuleForm
class InventoryItemEditView(PermissionRequiredMixin, ComponentEditView):
permission_required = 'dcim.change_inventoryitem'
model = InventoryItem
form_class = forms.InventoryItemForm
def alter_obj(self, obj, request, url_args, url_kwargs):
if 'device' in url_kwargs:
@ -1608,6 +1608,6 @@ class ModuleEditView(PermissionRequiredMixin, ComponentEditView):
return obj
class ModuleDeleteView(PermissionRequiredMixin, ComponentDeleteView):
permission_required = 'dcim.delete_module'
model = Module
class InventoryItemDeleteView(PermissionRequiredMixin, ComponentDeleteView):
permission_required = 'dcim.delete_inventoryitem'
model = InventoryItem

View File

@ -6,7 +6,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from dcim.models import Device, Module, Site
from dcim.models import Device, InventoryItem, Site
class Command(BaseCommand):
@ -25,12 +25,12 @@ class Command(BaseCommand):
def handle(self, *args, **options):
def create_modules(modules, parent=None):
for module in modules:
m = Module(device=device, parent=parent, name=module['name'], part_id=module['part_id'],
serial=module['serial'], discovered=True)
m.save()
create_modules(module.get('modules', []), parent=m)
def create_inventory_items(inventory_items, parent=None):
for item in inventory_items:
i = InventoryItem(device=device, parent=parent, name=item['name'], part_id=item['part_id'],
serial=item['serial'], discovered=True)
i.save()
create_inventory_items(item.get('items', []), parent=i)
# Credentials
if options['username']:
@ -107,9 +107,9 @@ class Command(BaseCommand):
self.stdout.write("")
self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial']))
self.stdout.write("\tDescription: {}".format(inventory['chassis']['description']))
for module in inventory['modules']:
self.stdout.write("\tModule: {} / {} ({})".format(module['name'], module['part_id'],
module['serial']))
for item in inventory['items']:
self.stdout.write("\tItem: {} / {} ({})".format(item['name'], item['part_id'],
item['serial']))
else:
self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial']))
@ -119,7 +119,7 @@ class Command(BaseCommand):
if device.serial != inventory['chassis']['serial']:
device.serial = inventory['chassis']['serial']
device.save()
Module.objects.filter(device=device, discovered=True).delete()
create_modules(inventory.get('modules', []))
InventoryItem.objects.filter(device=device, discovered=True).delete()
create_inventory_items(inventory.get('items', []))
self.stdout.write("Finished!")

View File

@ -33,14 +33,14 @@ class RPCClient(object):
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': {
'serial': <str>,
'description': <str>,
}
'modules': [
'items': [
{
'name': <str>,
'part_id': <str>,
@ -144,23 +144,23 @@ class JunosNC(RPCClient):
def get_inventory(self):
def glean_modules(node, depth=0):
modules = []
modules_list = node.get('chassis{}-module'.format('-sub' * depth), [])
def glean_items(node, depth=0):
items = []
items_list = node.get('chassis{}-module'.format('-sub' * depth), [])
# Junos like to return single children directly instead of as a single-item list
if hasattr(modules_list, 'items'):
modules_list = [modules_list]
for module in modules_list:
if hasattr(items_list, 'items'):
items_list = [items_list]
for item in items_list:
m = {
'name': module['name'],
'part_id': module.get('model-number') or module.get('part-number', ''),
'serial': module.get('serial-number', ''),
'name': item['name'],
'part_id': item.get('model-number') or item.get('part-number', ''),
'serial': item.get('serial-number', ''),
}
submodules = glean_modules(module, depth + 1)
if submodules:
m['modules'] = submodules
modules.append(m)
return modules
child_items = glean_items(item, depth + 1)
if child_items:
m['items'] = child_items
items.append(m)
return items
rpc_reply = self.manager.dispatch('get-chassis-inventory')
inventory_raw = xmltodict.parse(rpc_reply.xml)['rpc-reply']['chassis-inventory']['chassis']
@ -173,8 +173,8 @@ class JunosNC(RPCClient):
'description': inventory_raw['description'],
}
# Gather modules
result['modules'] = glean_modules(inventory_raw)
# Gather inventory items
result['items'] = glean_items(inventory_raw)
return result
@ -199,7 +199,7 @@ class IOSSSH(SSHClient):
'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')
for i in cmd:
i_fmt = i.replace('\r\n', ' ')
@ -207,7 +207,7 @@ class IOSSSH(SSHClient):
m_name = re.search('NAME: "([^"]+)"', i_fmt).group(1)
m_pid = re.search('PID: ([^\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':
yield {
'name': m_name,
@ -222,7 +222,7 @@ class IOSSSH(SSHClient):
return {
'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,
'description': description,
},
'modules': [],
'items': [],
}

View File

@ -46,7 +46,7 @@
<table class="table table-hover table-condensed panel-body" id="hardware">
<thead>
<tr>
<th>Module</th>
<th>Name</th>
<th></th>
<th>Manufacturer</th>
<th>Part Number</th>
@ -55,81 +55,18 @@
</tr>
</thead>
<tbody>
{% for m in modules %}
<tr>
<td>{{ m.name }}</td>
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
<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 %}
{% for item in inventory_items %}
{% with template_name='dcim/inc/inventoryitem.html' indent=0 %}
{% include template_name %}
{% endwith %}
{% endfor %}
</tbody>
</table>
</div>
{% if perms.dcim.add_module %}
<a href="{% url 'dcim:module_add' device=device.pk %}" class="btn btn-success">
{% if perms.dcim.add_inventoryitem %}
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-success">
<span class="fa fa-plus" aria-hidden="true"></span>
Add a Module
Add Inventory Item
</a>
{% endif %}
</div>

View 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 %}

View 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 %}

View File

@ -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 %}