From 32f6b3694a9f1d2c85036066f41c028b2b975dee Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Mar 2016 14:39:39 -0500 Subject: [PATCH] Initial work on a front end for managing DeviceTypes --- netbox/dcim/filters.py | 4 +- netbox/dcim/forms.py | 32 ++++ netbox/dcim/tables.py | 26 +++- netbox/dcim/urls.py | 9 ++ netbox/dcim/views.py | 133 +++++++++++++++- netbox/templates/dcim/devicetype.html | 147 ++++++++++++++++++ .../dcim/devicetype_bulk_delete.html | 15 ++ .../templates/dcim/devicetype_bulk_edit.html | 15 ++ netbox/templates/dcim/devicetype_delete.html | 8 + netbox/templates/dcim/devicetype_edit.html | 49 ++++++ netbox/templates/dcim/devicetype_list.html | 24 +++ .../templates/dcim/inc/devicetype_table.html | 21 +++ 12 files changed, 474 insertions(+), 9 deletions(-) create mode 100644 netbox/templates/dcim/devicetype.html create mode 100644 netbox/templates/dcim/devicetype_bulk_delete.html create mode 100644 netbox/templates/dcim/devicetype_bulk_edit.html create mode 100644 netbox/templates/dcim/devicetype_delete.html create mode 100644 netbox/templates/dcim/devicetype_edit.html create mode 100644 netbox/templates/dcim/devicetype_list.html create mode 100644 netbox/templates/dcim/inc/devicetype_table.html diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index c4827e526..0ac50bf14 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -65,12 +65,12 @@ class RackFilter(django_filters.FilterSet): class DeviceTypeFilter(django_filters.FilterSet): - manufacturer_id = django_filters.ModelChoiceFilter( + manufacturer_id = django_filters.ModelMultipleChoiceFilter( name='manufacturer', queryset=Manufacturer.objects.all(), label='Manufacturer (ID)', ) - manufacturer = django_filters.ModelChoiceFilter( + manufacturer = django_filters.ModelMultipleChoiceFilter( name='manufacturer', queryset=Manufacturer.objects.all(), to_field_name='slug', diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4bd088d04..2acdeb4f6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -159,6 +159,38 @@ class RackFilterForm(forms.Form, BootstrapMixin): 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 # diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 1a49a8b0a..4502c0aec 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -1,7 +1,7 @@ import django_tables2 as tables 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 = """ @@ -74,6 +74,30 @@ class RackBulkEditTable(RackTable): 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 # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index d0f1bfec0..7a319dd81 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -24,6 +24,15 @@ urlpatterns = [ url(r'^racks/(?P\d+)/edit/$', views.rack_edit, name='rack_edit'), url(r'^racks/(?P\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\d+)/$', views.devicetype, name='devicetype'), + url(r'^device-types/(?P\d+)/edit/$', views.devicetype_edit, name='devicetype_edit'), + url(r'^device-types/(?P\d+)/delete/$', views.devicetype_delete, name='devicetype_delete'), + # Devices url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'), url(r'^devices/add/$', views.device_add, name='device_add'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 61cc37049..095b5d06a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -15,19 +15,21 @@ from utilities.error_handlers import handle_protectederror from utilities.forms import ConfirmationForm 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, \ - RackFilterForm, DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \ + RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, \ + DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \ ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, ConsoleConnectionImportForm, \ ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, PowerPortForm, \ PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, PowerOutletCreateForm, \ PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, InterfaceConnectionForm, \ InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, ConsoleConnectionFilterForm, \ PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm -from .models import Site, Rack, Device, ConsolePort, ConsoleServerPort, PowerPort, \ - PowerOutlet, Interface, InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED -from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTable, DeviceBulkEditTable, DeviceImportTable, \ - ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable +from .models import Site, Rack, DeviceType, Device, ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, \ + InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED +from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTypeTable, DeviceTypeBulkEditTable, DeviceTable, \ + DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable EXPANSION_PATTERN = '\[(\d+-\d+)\]' @@ -307,6 +309,125 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): 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 # diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html new file mode 100644 index 000000000..c3538fd82 --- /dev/null +++ b/netbox/templates/dcim/devicetype.html @@ -0,0 +1,147 @@ +{% extends '_base.html' %} +{% load helpers %} +{% load render_table from django_tables2 %} + +{% block title %}{{ devicetype }}{% endblock %} + +{% block content %} +
+
+ +
+
+{% if perms.dcim.change_devicetype %} +
+ + + Edit this device type + + {% endif %} + {% if perms.dcim.delete_devicetype %} + + + Delete this device type + +
+{% endif %} +

{{ devicetype }}

+
+
+
+
+ Chassis +
+ + + + + + + + + + + + + + + + + +
Manufacturer{{ devicetype.manufacturer }}
Model Name{{ devicetype.model }}
Height (U){{ devicetype.u_height }}
Full Depth{{ devicetype.is_full_depth|yesno|capfirst }}
+
+
+
+ Function +
+ + + + + + + + + + + + + +
Is a Console Server{{ devicetype.is_console_server|yesno|capfirst }}
Is a PDU{{ devicetype.is_pdu|yesno|capfirst }}
Is a Network Device{{ devicetype.is_network_device|yesno|capfirst }}
+
+
+
+ Console Ports +
+ + {% for cp in devicetype.console_port_templates.all %} + + + + + {% endfor %} +
{{ cp.name }}
+
+
+
+ Power Ports +
+ + {% for pp in devicetype.power_port_templates.all %} + + + + + {% endfor %} +
{{ pp.name }}
+
+
+
+
+
+ Interfaces +
+ + {% for iface in devicetype.interface_templates.all %} + + + + + + + {% endfor %} +
{{ iface.name }}{{ iface.get_form_factor_display }}{{ iface.mgmt_only|yesno|capfirst }}
+
+
+
+ Console Server Ports +
+ + {% for csp in devicetype.cs_port_templates.all %} + + + + + {% endfor %} +
{{ csp.name }}
+
+
+
+ Power Outlets +
+ + {% for po in devicetype.power_outlet_templates.all %} + + + + + {% endfor %} +
{{ po.name }}
+
+
+
+{% endblock %} diff --git a/netbox/templates/dcim/devicetype_bulk_delete.html b/netbox/templates/dcim/devicetype_bulk_delete.html new file mode 100644 index 000000000..d779bbbc2 --- /dev/null +++ b/netbox/templates/dcim/devicetype_bulk_delete.html @@ -0,0 +1,15 @@ +{% extends 'utilities/confirmation_form.html' %} +{% load form_helpers %} + +{% block title %}Delete Device Types?{% endblock %} + +{% block message %} +

+ Are you sure you want to delete these device types? +

+
    + {% for devicetype in selected_objects %} +
  • {{ devicetype }}
  • + {% endfor %} +
+{% endblock %} diff --git a/netbox/templates/dcim/devicetype_bulk_edit.html b/netbox/templates/dcim/devicetype_bulk_edit.html new file mode 100644 index 000000000..e2b6c6ef5 --- /dev/null +++ b/netbox/templates/dcim/devicetype_bulk_edit.html @@ -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 %} + + {{ devicetype }} + {{ devicetype.model }} + {{ devicetype.manufacturer }} + {{ device.u_height }} + + {% endfor %} +{% endblock %} diff --git a/netbox/templates/dcim/devicetype_delete.html b/netbox/templates/dcim/devicetype_delete.html new file mode 100644 index 000000000..4965ff5a1 --- /dev/null +++ b/netbox/templates/dcim/devicetype_delete.html @@ -0,0 +1,8 @@ +{% extends 'utilities/confirmation_form.html' %} +{% load form_helpers %} + +{% block title %}Delete device type {{ devicetype }}?{% endblock %} + +{% block message %} +

Are you sure you want to delete {{ devicetype }}?

+{% endblock %} diff --git a/netbox/templates/dcim/devicetype_edit.html b/netbox/templates/dcim/devicetype_edit.html new file mode 100644 index 000000000..0ef6481cf --- /dev/null +++ b/netbox/templates/dcim/devicetype_edit.html @@ -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 %} +

{{ devicetype }}

+{% else %} +

Add a Device Type

+{% endif %} +
+ {% csrf_token %} +
+
+ {% if form.non_field_errors %} +
+
Errors
+
+ {{ form.non_field_errors }} +
+
+ {% endif %} +
+
+
+
+
+
Device Type
+
+ {% render_form form %} +
+
+
+
+
+
+ {% if devicetype %} + + Cancel + {% else %} + + + Cancel + {% endif %} +
+
+
+{% endblock %} diff --git a/netbox/templates/dcim/devicetype_list.html b/netbox/templates/dcim/devicetype_list.html new file mode 100644 index 000000000..e14720e6d --- /dev/null +++ b/netbox/templates/dcim/devicetype_list.html @@ -0,0 +1,24 @@ +{% extends '_base.html' %} +{% load helpers %} + +{% block title %}Device Types{% endblock %} + +{% block content %} +
+ {% if perms.dcim.add_devicetype %} + + + Add a device type + + {% endif %} +
+

Device Types

+
+
+ {% include 'dcim/inc/devicetype_table.html' %} +
+
+ {% include 'inc/filter_panel.html' %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/inc/devicetype_table.html b/netbox/templates/dcim/inc/devicetype_table.html new file mode 100644 index 000000000..f77a040ea --- /dev/null +++ b/netbox/templates/dcim/inc/devicetype_table.html @@ -0,0 +1,21 @@ +{% load render_table from django_tables2 %} +{% if perms.dcim.change_devicetype or perms.dcim.delete_devicetype %} +
+ {% csrf_token %} + {% render_table table table_template|default:'table.html' %} + {% if perms.dcim.change_devicetype %} + + {% endif %} + {% if perms.dcim.delete_devicetype %} + + {% endif %} +
+{% else %} + {% render_table table table_template|default:'table.html' %} +{% endif %}