Initial work on a front end for managing DeviceTypes

This commit is contained in:
Jeremy Stretch 2016-03-04 14:39:39 -05:00
parent 0b1df1e60d
commit 32f6b3694a
12 changed files with 474 additions and 9 deletions

View File

@ -65,12 +65,12 @@ class RackFilter(django_filters.FilterSet):
class DeviceTypeFilter(django_filters.FilterSet): class DeviceTypeFilter(django_filters.FilterSet):
manufacturer_id = django_filters.ModelChoiceFilter( manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='manufacturer', name='manufacturer',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)', label='Manufacturer (ID)',
) )
manufacturer = django_filters.ModelChoiceFilter( manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer', name='manufacturer',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -159,6 +159,38 @@ class RackFilterForm(forms.Form, BootstrapMixin):
widget=forms.SelectMultiple(attrs={'size': 8})) widget=forms.SelectMultiple(attrs={'size': 8}))
#
# Device types
#
class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
class Meta:
model = DeviceType
fields = ['manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
'is_network_device']
class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
u_height = forms.IntegerField(min_value=1, required=False)
class DeviceTypeBulkDeleteForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
def devicetype_manufacturer_choices():
manufacturer_choices = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
return [(m.slug, '{} ({})'.format(m.name, m.devicetype_count)) for m in manufacturer_choices]
class DeviceTypeFilterForm(forms.Form, BootstrapMixin):
manufacturer = forms.MultipleChoiceField(required=False, choices=devicetype_manufacturer_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
# #
# Devices # Devices
# #

View File

@ -1,7 +1,7 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from .models import Site, Rack, Device, ConsolePort, PowerPort from .models import Site, Rack, DeviceType, Device, ConsolePort, PowerPort
PREFIXES_PER_VLAN = """ PREFIXES_PER_VLAN = """
@ -74,6 +74,30 @@ class RackBulkEditTable(RackTable):
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height') fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height')
#
# Device types
#
class DeviceTypeTable(tables.Table):
model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
class Meta:
model = DeviceType
fields = ('model', 'manufacturer', 'u_height')
empty_text = "No device types were found."
attrs = {
'class': 'table table-hover',
}
class DeviceTypeBulkEditTable(DeviceTypeTable):
pk = tables.CheckBoxColumn()
class Meta(DeviceTypeTable.Meta):
model = None # django_tables2 bugfix
fields = ('pk', 'model', 'manufacturer', 'u_height')
# #
# Devices # Devices
# #

View File

@ -24,6 +24,15 @@ urlpatterns = [
url(r'^racks/(?P<pk>\d+)/edit/$', views.rack_edit, name='rack_edit'), url(r'^racks/(?P<pk>\d+)/edit/$', views.rack_edit, name='rack_edit'),
url(r'^racks/(?P<pk>\d+)/delete/$', views.rack_delete, name='rack_delete'), url(r'^racks/(?P<pk>\d+)/delete/$', views.rack_delete, name='rack_delete'),
# Device types
url(r'^device-types/$', views.DeviceTypeListView.as_view(), name='devicetype_list'),
url(r'^device-types/add/$', views.device_add, name='devicetype_add'),
url(r'^device-types/edit/$', views.DeviceTypeBulkEditView.as_view(), name='devicetype_bulk_edit'),
url(r'^device-types/delete/$', views.DeviceTypeBulkDeleteView.as_view(), name='devicetype_bulk_delete'),
url(r'^device-types/(?P<pk>\d+)/$', views.devicetype, name='devicetype'),
url(r'^device-types/(?P<pk>\d+)/edit/$', views.devicetype_edit, name='devicetype_edit'),
url(r'^device-types/(?P<pk>\d+)/delete/$', views.devicetype_delete, name='devicetype_delete'),
# Devices # Devices
url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'), url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'),
url(r'^devices/add/$', views.device_add, name='device_add'), url(r'^devices/add/$', views.device_add, name='device_add'),

View File

@ -15,19 +15,21 @@ from utilities.error_handlers import handle_protectederror
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView
from .filters import RackFilter, DeviceFilter, ConsoleConnectionFilter, PowerConnectionFilter, InterfaceConnectionFilter from .filters import RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, PowerConnectionFilter, \
InterfaceConnectionFilter
from .forms import SiteForm, SiteImportForm, RackForm, RackImportForm, RackBulkEditForm, RackBulkDeleteForm, \ from .forms import SiteForm, SiteImportForm, RackForm, RackImportForm, RackBulkEditForm, RackBulkDeleteForm, \
RackFilterForm, DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \ RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, \
DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \
ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, ConsoleConnectionImportForm, \ ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, ConsoleConnectionImportForm, \
ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, PowerPortForm, \ ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, PowerPortForm, \
PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, PowerOutletCreateForm, \ PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, PowerOutletCreateForm, \
PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, InterfaceConnectionForm, \ PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, InterfaceConnectionForm, \
InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, ConsoleConnectionFilterForm, \ InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, ConsoleConnectionFilterForm, \
PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm
from .models import Site, Rack, Device, ConsolePort, ConsoleServerPort, PowerPort, \ from .models import Site, Rack, DeviceType, Device, ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, \
PowerOutlet, Interface, InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED
from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTable, DeviceBulkEditTable, DeviceImportTable, \ from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTypeTable, DeviceTypeBulkEditTable, DeviceTable, \
ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable
EXPANSION_PATTERN = '\[(\d+-\d+)\]' EXPANSION_PATTERN = '\[(\d+-\d+)\]'
@ -307,6 +309,125 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
redirect_url = 'dcim:rack_list' redirect_url = 'dcim:rack_list'
#
# Device types
#
class DeviceTypeListView(ObjectListView):
queryset = DeviceType.objects.select_related('manufacturer')
filter = DeviceTypeFilter
filter_form = DeviceTypeFilterForm
table = DeviceTypeTable
edit_table = DeviceTypeBulkEditTable
edit_table_permissions = ['dcim.change_devicetype', 'dcim.delete_devicetype']
template_name = 'dcim/devicetype_list.html'
def devicetype(request, pk):
devicetype = get_object_or_404(DeviceType, pk=pk)
return render(request, 'dcim/devicetype.html', {
'devicetype': devicetype,
})
@permission_required('dcim.add_devicetype')
def devicetype_add(request):
if request.method == 'POST':
form = DeviceTypeForm(request.POST)
if form.is_valid():
devicetype = form.save()
messages.success(request, "Added new device type: {}".format(devicetype))
if '_addanother' in request.POST:
return redirect('dcim:devicetype_add')
else:
return redirect('dcim:devicetype', pk=devicetype.pk)
else:
form = DeviceTypeForm()
return render(request, 'dcim/devicetype_edit.html', {
'form': form,
'cancel_url': reverse('dcim:devicetype_list'),
})
@permission_required('dcim.change_devicetype')
def devicetype_edit(request, pk):
devicetype = get_object_or_404(DeviceType, pk=pk)
if request.method == 'POST':
form = DeviceTypeForm(request.POST, instance=devicetype)
if form.is_valid():
devicetype = form.save()
messages.success(request, "Modified device type {}".format(devicetype))
return redirect('dcim:devicetype', pk=devicetype.pk)
else:
form = DeviceTypeForm(instance=devicetype)
return render(request, 'dcim/devicetype_edit.html', {
'devicetype': devicetype,
'form': form,
'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
})
@permission_required('dcim.delete_devicetype')
def devicetype_delete(request, pk):
devicetype = get_object_or_404(DeviceType, pk=pk)
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
try:
devicetype.delete()
messages.success(request, "Device type {} has been deleted".format(devicetype))
return redirect('dcim:devicetype_list')
except ProtectedError, e:
handle_protectederror(devicetype, request, e)
return redirect('dcim:devicetype', pk=devicetype.pk)
else:
form = ConfirmationForm()
return render(request, 'dcim/devicetype_delete.html', {
'devicetype': device,
'form': form,
'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
})
class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_devicetype'
cls = DeviceType
form = DeviceTypeBulkEditForm
template_name = 'dcim/devicetype_bulk_edit.html'
redirect_url = 'dcim:devicetype_list'
def update_objects(self, pk_list, form):
fields_to_update = {}
for field in ['manufacturer', 'u_height']:
if form.cleaned_data[field]:
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
messages.success(self.request, "Updated {} device types".format(updated_count))
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_devicetype'
cls = DeviceType
form = DeviceTypeBulkDeleteForm
template_name = 'dcim/devicetype_bulk_delete.html'
redirect_url = 'dcim:devicetype_list'
# #
# Devices # Devices
# #

View File

@ -0,0 +1,147 @@
{% extends '_base.html' %}
{% load helpers %}
{% load render_table from django_tables2 %}
{% block title %}{{ devicetype }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
<li><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></li>
<li>{{ devicetype.model }}</li>
</ol>
</div>
</div>
{% if perms.dcim.change_devicetype %}
<div class="pull-right">
<a href="{% url 'dcim:devicetype_edit' pk=devicetype.pk %}" class="btn btn-warning">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit this device type
</a>
{% endif %}
{% if perms.dcim.delete_devicetype %}
<a href="{% url 'dcim:devicetype_delete' pk=devicetype.pk %}" class="btn btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete this device type
</a>
</div>
{% endif %}
<h1>{{ devicetype }}</h1>
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Chassis</strong>
</div>
<table class="table table-hover panel-body">
<tr>
<td>Manufacturer</td>
<td>{{ devicetype.manufacturer }}</td>
</tr>
<tr>
<td>Model Name</td>
<td>{{ devicetype.model }}</td>
</tr>
<tr>
<td>Height (U)</td>
<td>{{ devicetype.u_height }}</td>
</tr>
<tr>
<td>Full Depth</td>
<td>{{ devicetype.is_full_depth|yesno|capfirst }}</td>
</tr>
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Function</strong>
</div>
<table class="table table-hover panel-body">
<tr>
<td>Is a Console Server</td>
<td>{{ devicetype.is_console_server|yesno|capfirst }}</td>
</tr>
<tr>
<td>Is a PDU</td>
<td>{{ devicetype.is_pdu|yesno|capfirst }}</td>
</tr>
<tr>
<td>Is a Network Device</td>
<td>{{ devicetype.is_network_device|yesno|capfirst }}</td>
</tr>
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Ports</strong>
</div>
<table class="table table-hover panel-body">
{% for cp in devicetype.console_port_templates.all %}
<tr>
<td>{{ cp.name }}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Ports</strong>
</div>
<table class="table table-hover panel-body">
{% for pp in devicetype.power_port_templates.all %}
<tr>
<td>{{ pp.name }}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interfaces</strong>
</div>
<table class="table table-hover panel-body">
{% for iface in devicetype.interface_templates.all %}
<tr>
<td>{{ iface.name }}</td>
<td>{{ iface.get_form_factor_display }}</td>
<td>{{ iface.mgmt_only|yesno|capfirst }}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Server Ports</strong>
</div>
<table class="table table-hover panel-body">
{% for csp in devicetype.cs_port_templates.all %}
<tr>
<td>{{ csp.name }}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Outlets</strong>
</div>
<table class="table table-hover panel-body">
{% for po in devicetype.power_outlet_templates.all %}
<tr>
<td>{{ po.name }}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'utilities/confirmation_form.html' %}
{% load form_helpers %}
{% block title %}Delete Device Types?{% endblock %}
{% block message %}
<p>
Are you sure you want to delete these device types?
</p>
<ul>
{% for devicetype in selected_objects %}
<li><a href="{% url 'dcim:devicetype' pk=devicetype.pk %}">{{ devicetype }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'utilities/bulk_edit_form.html' %}
{% load form_helpers %}
{% block title %}Device Type Bulk Edit{% endblock %}
{% block select_objects_table %}
{% for devicetype in selected_objects %}
<tr>
<td><a href="{% url 'dcim:devicetype' pk=devicetype.pk %}">{{ devicetype }}</a></td>
<td>{{ devicetype.model }}</td>
<td>{{ devicetype.manufacturer }}</td>
<td>{{ device.u_height }}</td>
</tr>
{% endfor %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends 'utilities/confirmation_form.html' %}
{% load form_helpers %}
{% block title %}Delete device type {{ devicetype }}?{% endblock %}
{% block message %}
<p>Are you sure you want to delete <strong>{{ devicetype }}</strong>?</p>
{% endblock %}

View File

@ -0,0 +1,49 @@
{% extends '_base.html' %}
{% load form_helpers %}
{% block title %}{% if devicetype %}Editing device type {{ devicetype }}{% else %}Add a Device Type{% endif %}{% endblock %}
{% block content %}
{% if devicetype %}
<h1>{{ devicetype }}</h1>
{% else %}
<h1>Add a Device Type</h1>
{% endif %}
<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>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading"><strong>Device Type</strong></div>
<div class="panel-body">
{% render_form form %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center">
{% if devicetype %}
<button type="submit" name="_update" class="btn btn-primary">Update</button>
<a href="{% url 'dcim:devicetype' pk=devicetype.pk %}" class="btn btn-default">Cancel</a>
{% else %}
<button type="submit" name="_create" class="btn btn-primary">Create</button>
<button type="submit" name="_addanother" class="btn btn-primary">Create and Add Another</button>
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
{% endif %}
</div>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends '_base.html' %}
{% load helpers %}
{% block title %}Device Types{% endblock %}
{% block content %}
<div class="pull-right">
{% if perms.dcim.add_devicetype %}
<a href="{% url 'dcim:device_add' %}" class="btn btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add a device type
</a>
{% endif %}
</div>
<h1>Device Types</h1>
<div class="row">
<div class="col-md-9">
{% include 'dcim/inc/devicetype_table.html' %}
</div>
<div class="col-md-3">
{% include 'inc/filter_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% load render_table from django_tables2 %}
{% if perms.dcim.change_devicetype or perms.dcim.delete_devicetype %}
<form method="post" class="form form-horizontal">
{% csrf_token %}
{% render_table table table_template|default:'table.html' %}
{% if perms.dcim.change_devicetype %}
<button type="submit" name="_edit" formaction="{% url 'dcim:devicetype_bulk_edit' %}" class="btn btn-warning btn-sm">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit Selected
</button>
{% endif %}
{% if perms.dcim.delete_devicetype %}
<button type="submit" name="_delete" formaction="{% url 'dcim:devicetype_bulk_delete' %}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete Selected
</button>
{% endif %}
</form>
{% else %}
{% render_table table table_template|default:'table.html' %}
{% endif %}