From 4b34af3e1d54a3af1fc71156b01d635c54e8285f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jun 2016 13:27:12 -0400 Subject: [PATCH] Introduced ability to edit/delete modules --- netbox/dcim/admin.py | 2 +- netbox/dcim/forms.py | 13 ++++- .../dcim/migrations/0007_module_discovered.py | 20 ++++++++ netbox/dcim/models.py | 2 +- netbox/dcim/urls.py | 4 ++ netbox/dcim/views.py | 48 +++++++++++++++++++ .../management/commands/run_inventory.py | 4 +- netbox/templates/dcim/device_inventory.html | 40 +++++++++++++++- netbox/templates/dcim/module_delete.html | 8 ++++ netbox/templates/dcim/module_edit.html | 47 ++++++++++++++++++ 10 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 netbox/dcim/migrations/0007_module_discovered.py create mode 100644 netbox/templates/dcim/module_delete.html create mode 100644 netbox/templates/dcim/module_edit.html diff --git a/netbox/dcim/admin.py b/netbox/dcim/admin.py index 4f9f8319d..9975a3ba0 100644 --- a/netbox/dcim/admin.py +++ b/netbox/dcim/admin.py @@ -146,7 +146,7 @@ class InterfaceAdmin(admin.TabularInline): class ModuleAdmin(admin.TabularInline): model = Module - readonly_fields = ['parent'] + readonly_fields = ['parent', 'discovered'] @admin.register(Device) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index d97b52ce6..c4a0a6ab0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -12,7 +12,7 @@ from utilities.forms import ( from .models import ( CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, Interface, IFACE_FF_VIRTUAL, - InterfaceConnection, InterfaceTemplate, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, + InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site, STATUS_CHOICES ) @@ -1107,3 +1107,14 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin): # If this device does not have any IP addresses assigned, default to setting the first IP as its primary if not IPAddress.objects.filter(interface__device=device).count(): self.fields['set_as_primary'].initial = True + + +# +# Interfaces +# + +class ModuleForm(forms.ModelForm, BootstrapMixin): + + class Meta: + model = Module + fields = ['name', 'part_id', 'serial'] diff --git a/netbox/dcim/migrations/0007_module_discovered.py b/netbox/dcim/migrations/0007_module_discovered.py new file mode 100644 index 000000000..df3417fa3 --- /dev/null +++ b/netbox/dcim/migrations/0007_module_discovered.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-06-15 16:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0006_remove_device_ro_snmp'), + ] + + operations = [ + migrations.AddField( + model_name='module', + name='discovered', + field=models.BooleanField(default=False, verbose_name=b'Discovered'), + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index fc8a1014b..4e77f59e9 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -692,7 +692,6 @@ class InterfaceConnection(models.Model): verbose_name='Status') def clean(self): - if self.interface_a == self.interface_b: raise ValidationError("Cannot connect an interface to itself") @@ -706,6 +705,7 @@ class Module(models.Model): name = models.CharField(max_length=50, verbose_name='Name') 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) + discovered = models.BooleanField(default=False, verbose_name='Discovered') class Meta: ordering = ['device__id', 'parent__id', 'name'] diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 699405f5e..91ab46b21 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -141,4 +141,8 @@ urlpatterns = [ url(r'^interfaces/(?P\d+)/edit/$', views.interface_edit, name='interface_edit'), url(r'^interfaces/(?P\d+)/delete/$', views.interface_delete, name='interface_delete'), + # Modules + url(r'^modules/(?P\d+)/edit/$', views.module_edit, name='module_edit'), + url(r'^modules/(?P\d+)/delete/$', views.module_delete, name='module_delete'), + ] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 69d237dde..05e915240 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1497,3 +1497,51 @@ def ipaddress_assign(request, pk): 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) + + +# +# Modules +# + +@permission_required('dcim.change_module') +def module_edit(request, pk): + + module = get_object_or_404(Module, pk=pk) + + if request.method == 'POST': + form = forms.ModuleForm(request.POST, instance=module) + if form.is_valid(): + module = form.save() + messages.success(request, "Modified {} module {}".format(module.device.name, module.name)) + return redirect('dcim:device_inventory', pk=module.device.pk) + + else: + form = forms.ModuleForm(instance=module) + + return render(request, 'dcim/module_edit.html', { + 'module': module, + 'form': form, + 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}), + }) + + +@permission_required('dcim.delete_module') +def module_delete(request, pk): + + module = get_object_or_404(Module, pk=pk) + + if request.method == 'POST': + form = ConfirmationForm(request.POST) + if form.is_valid(): + module.delete() + messages.success(request, "Module {} has been deleted from {}".format(module, module.device)) + return redirect('dcim:device_inventory', pk=module.device.pk) + + else: + form = ConfirmationForm() + + return render(request, 'dcim/module_delete.html', { + 'module': module, + 'form': form, + 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}), + }) diff --git a/netbox/extras/management/commands/run_inventory.py b/netbox/extras/management/commands/run_inventory.py index 143d99b9b..11495ce44 100644 --- a/netbox/extras/management/commands/run_inventory.py +++ b/netbox/extras/management/commands/run_inventory.py @@ -28,7 +28,7 @@ class Command(BaseCommand): 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']) + serial=module['serial'], discovered=True) m.save() create_modules(module.get('modules', []), parent=m) @@ -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).delete() + Module.objects.filter(device=device, discovered=True).delete() create_modules(inventory.get('modules', [])) self.stdout.write("Finished!") diff --git a/netbox/templates/dcim/device_inventory.html b/netbox/templates/dcim/device_inventory.html index 814f2a105..e4aa899f8 100644 --- a/netbox/templates/dcim/device_inventory.html +++ b/netbox/templates/dcim/device_inventory.html @@ -42,38 +42,76 @@
Hardware
- +
+ + {% for m in modules %} + + {% for m2 in m.submodules.all %} + + {% for m3 in m2.submodules.all %} + + {% for m4 in m3.submodules.all %} + + {% endfor %} {% endfor %} diff --git a/netbox/templates/dcim/module_delete.html b/netbox/templates/dcim/module_delete.html new file mode 100644 index 000000000..017464293 --- /dev/null +++ b/netbox/templates/dcim/module_delete.html @@ -0,0 +1,8 @@ +{% extends 'utilities/confirmation_form.html' %} +{% load form_helpers %} + +{% block title %}Delete module {{ module }}?{% endblock %} + +{% block message %} +

Are you sure you want to delete this module from {{ module.device }}?

+{% endblock %} diff --git a/netbox/templates/dcim/module_edit.html b/netbox/templates/dcim/module_edit.html new file mode 100644 index 000000000..409c8ffc5 --- /dev/null +++ b/netbox/templates/dcim/module_edit.html @@ -0,0 +1,47 @@ +{% extends '_base.html' %} +{% load form_helpers %} + +{% block title %}Editing {{ module.device }} {{ module }}{% endblock %} + +{% block content %} + + {% csrf_token %} +
+
+ {% if form.non_field_errors %} +
+
Errors
+
+ {{ form.non_field_errors }} +
+
+ {% endif %} +
+
+ Editing {{ module.device }} {{ module }} +
+
+
+ +
+

{{ module.device }}

+
+
+ {% render_form form %} +
+
+
+
+ {% if module.pk %} + + {% else %} + + + {% endif %} + Cancel +
+
+
+
+ +{% endblock %}
Module Part Number Serial Number
{% if not m.discovered %}{% endif %} {{ m.name }} {{ m.part_id }} {{ m.serial }} + {% if perms.dcim.change_module %} + + {% endif %} + {% if perms.dcim.delete_module %} + + {% endif %} +
{% if not m.discovered %}{% endif %} {{ m2.name }} {{ m2.part_id }} {{ m2.serial }} + {% if perms.dcim.change_module %} + + {% endif %} + {% if perms.dcim.delete_module %} + + {% endif %} +
{% if not m.discovered %}{% endif %} {{ m3.name }} {{ m3.part_id }} {{ m3.serial }} + {% if perms.dcim.change_module %} + + {% endif %} + {% if perms.dcim.delete_module %} + + {% endif %} +
{% if not m.discovered %}{% endif %} {{ m4.name }} {{ m4.part_id }} {{ m4.serial }} + {% if perms.dcim.change_module %} + + {% endif %} + {% if perms.dcim.delete_module %} + + {% endif %} +