mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 19:19:22 -06:00
Introduced ability to edit/delete modules
This commit is contained in:
parent
4258e40ec1
commit
4b34af3e1d
@ -146,7 +146,7 @@ class InterfaceAdmin(admin.TabularInline):
|
|||||||
|
|
||||||
class ModuleAdmin(admin.TabularInline):
|
class ModuleAdmin(admin.TabularInline):
|
||||||
model = Module
|
model = Module
|
||||||
readonly_fields = ['parent']
|
readonly_fields = ['parent', 'discovered']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Device)
|
@admin.register(Device)
|
||||||
|
@ -12,7 +12,7 @@ from utilities.forms import (
|
|||||||
from .models import (
|
from .models import (
|
||||||
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate,
|
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate,
|
||||||
ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, Interface, IFACE_FF_VIRTUAL,
|
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
|
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 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():
|
if not IPAddress.objects.filter(interface__device=device).count():
|
||||||
self.fields['set_as_primary'].initial = True
|
self.fields['set_as_primary'].initial = True
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Interfaces
|
||||||
|
#
|
||||||
|
|
||||||
|
class ModuleForm(forms.ModelForm, BootstrapMixin):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Module
|
||||||
|
fields = ['name', 'part_id', 'serial']
|
||||||
|
20
netbox/dcim/migrations/0007_module_discovered.py
Normal file
20
netbox/dcim/migrations/0007_module_discovered.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -692,7 +692,6 @@ class InterfaceConnection(models.Model):
|
|||||||
verbose_name='Status')
|
verbose_name='Status')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
if self.interface_a == self.interface_b:
|
if self.interface_a == self.interface_b:
|
||||||
raise ValidationError("Cannot connect an interface to itself")
|
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')
|
name = models.CharField(max_length=50, verbose_name='Name')
|
||||||
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)
|
||||||
|
discovered = models.BooleanField(default=False, verbose_name='Discovered')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device__id', 'parent__id', 'name']
|
ordering = ['device__id', 'parent__id', 'name']
|
||||||
|
@ -141,4 +141,8 @@ urlpatterns = [
|
|||||||
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.interface_edit, name='interface_edit'),
|
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.interface_edit, name='interface_edit'),
|
||||||
url(r'^interfaces/(?P<pk>\d+)/delete/$', views.interface_delete, name='interface_delete'),
|
url(r'^interfaces/(?P<pk>\d+)/delete/$', views.interface_delete, name='interface_delete'),
|
||||||
|
|
||||||
|
# Modules
|
||||||
|
url(r'^modules/(?P<pk>\d+)/edit/$', views.module_edit, name='module_edit'),
|
||||||
|
url(r'^modules/(?P<pk>\d+)/delete/$', views.module_delete, name='module_delete'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -1497,3 +1497,51 @@ def ipaddress_assign(request, pk):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
|
'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}),
|
||||||
|
})
|
||||||
|
@ -28,7 +28,7 @@ class Command(BaseCommand):
|
|||||||
def create_modules(modules, parent=None):
|
def create_modules(modules, parent=None):
|
||||||
for module in modules:
|
for module in modules:
|
||||||
m = Module(device=device, parent=parent, name=module['name'], part_id=module['part_id'],
|
m = Module(device=device, parent=parent, name=module['name'], part_id=module['part_id'],
|
||||||
serial=module['serial'])
|
serial=module['serial'], discovered=True)
|
||||||
m.save()
|
m.save()
|
||||||
create_modules(module.get('modules', []), parent=m)
|
create_modules(module.get('modules', []), parent=m)
|
||||||
|
|
||||||
@ -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).delete()
|
Module.objects.filter(device=device, discovered=True).delete()
|
||||||
create_modules(inventory.get('modules', []))
|
create_modules(inventory.get('modules', []))
|
||||||
|
|
||||||
self.stdout.write("Finished!")
|
self.stdout.write("Finished!")
|
||||||
|
@ -42,38 +42,76 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Hardware</strong>
|
<strong>Hardware</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body" id="hardware">
|
<table class="table table-hover table-condensed panel-body" id="hardware">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Module</th>
|
<th>Module</th>
|
||||||
<th>Part Number</th>
|
<th>Part Number</th>
|
||||||
<th>Serial Number</th>
|
<th>Serial Number</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in modules %}
|
{% for m in modules %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
<td>{{ m.name }}</td>
|
<td>{{ m.name }}</td>
|
||||||
<td>{{ m.part_id }}</td>
|
<td>{{ m.part_id }}</td>
|
||||||
<td>{{ m.serial }}</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 %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for m2 in m.submodules.all %}
|
{% for m2 in m.submodules.all %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
<td style="padding-left: 20px">{{ m2.name }}</td>
|
<td style="padding-left: 20px">{{ m2.name }}</td>
|
||||||
<td>{{ m2.part_id }}</td>
|
<td>{{ m2.part_id }}</td>
|
||||||
<td>{{ m2.serial }}</td>
|
<td>{{ m2.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 %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for m3 in m2.submodules.all %}
|
{% for m3 in m2.submodules.all %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
<td style="padding-left: 40px">{{ m3.name }}</td>
|
<td style="padding-left: 40px">{{ m3.name }}</td>
|
||||||
<td>{{ m3.part_id }}</td>
|
<td>{{ m3.part_id }}</td>
|
||||||
<td>{{ m3.serial }}</td>
|
<td>{{ m3.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 %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for m4 in m3.submodules.all %}
|
{% for m4 in m3.submodules.all %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% if not m.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
<td style="padding-left: 60px">{{ m4.name }}</td>
|
<td style="padding-left: 60px">{{ m4.name }}</td>
|
||||||
<td>{{ m4.part_id }}</td>
|
<td>{{ m4.part_id }}</td>
|
||||||
<td>{{ m4.serial }}</td>
|
<td>{{ m4.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 %}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
8
netbox/templates/dcim/module_delete.html
Normal file
8
netbox/templates/dcim/module_delete.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% 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 %}
|
47
netbox/templates/dcim/module_edit.html
Normal file
47
netbox/templates/dcim/module_edit.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Editing {{ module.device }} {{ module }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="." method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Editing {{ module.device }} {{ module }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Device</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ module.device }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% render_form form %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-9 col-md-offset-3">
|
||||||
|
{% if module.pk %}
|
||||||
|
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
||||||
|
{% else %}
|
||||||
|
<button type="submit" name="_create" class="btn btn-primary">Create</button>
|
||||||
|
<button type="submit" name="_addanother" class="btn btn-primary">Create and Add More</button>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user