Browse Source

Initial work on a front end for managing DeviceTypes

Jeremy Stretch 9 years ago
parent
commit
32f6b3694a

+ 2 - 2
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',

+ 32 - 0
netbox/dcim/forms.py

@@ -160,6 +160,38 @@ class RackFilterForm(forms.Form, BootstrapMixin):
 
 
 #
+# 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
 #
 

+ 25 - 1
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 = """
@@ -75,6 +75,30 @@ class RackBulkEditTable(RackTable):
 
 
 #
+# 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
 #
 

+ 9 - 0
netbox/dcim/urls.py

@@ -24,6 +24,15 @@ urlpatterns = [
     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'),
 
+    # 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
     url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'),
     url(r'^devices/add/$', views.device_add, name='device_add'),

+ 127 - 6
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+)\]'
@@ -308,6 +310,125 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 
 
 #
+# 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
 #
 

+ 147 - 0
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 %}
+<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 %}

+ 15 - 0
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 %}
+    <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 %}

+ 15 - 0
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 %}
+        <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 %}

+ 8 - 0
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 %}
+    <p>Are you sure you want to delete <strong>{{ devicetype }}</strong>?</p>
+{% endblock %}

+ 49 - 0
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 %}
+	<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 %}

+ 24 - 0
netbox/templates/dcim/devicetype_list.html

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

+ 21 - 0
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 %}
+    <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 %}