1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Added front-end UI for RackGroups

This commit is contained in:
Jeremy Stretch
2016-03-30 12:26:37 -04:00
parent 87fffce7ea
commit 2f86d5e43d
10 changed files with 265 additions and 76 deletions

View File

@ -68,6 +68,24 @@ class SiteImportForm(BulkImportForm, BootstrapMixin):
csv = CSVDataField(csv_form=SiteFromCSVForm) csv = CSVDataField(csv_form=SiteFromCSVForm)
#
# Rack groups
#
class RackGroupBulkDeleteForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(queryset=RackGroup.objects.all(), widget=forms.MultipleHiddenInput)
def rackgroup_site_choices():
site_choices = Site.objects.annotate(rack_count=Count('racks'))
return [(s.slug, '{} ({})'.format(s.name, s.rack_count)) for s in site_choices]
class RackGroupFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=rackgroup_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
# #
# Racks # Racks
# #

View File

@ -142,6 +142,9 @@ class RackGroup(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def get_absolute_url(self):
return "{}?group={}".format(reverse('dcim:rack_list'), self.slug)
class Rack(models.Model): class Rack(models.Model):
""" """

View File

@ -1,27 +1,17 @@
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, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, PowerPortTemplate, \ from .models import Site, RackGroup, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, \
PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, PowerPort PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, PowerPort
PREFIXES_PER_VLAN = """
{% for p in record.prefix_set.all %}
<a href="{% url 'ipam:prefix' pk=p.pk %}">{{ p }}</a>
{% if not forloop.last %}<br />{% endif %}
{% endfor %}
"""
STATUS_LABEL = """
<span class="label label-{{ record.status.get_bootstrap_class_display|lower }}">
{{ record.status.name }}
</span>
"""
DEVICE_LINK = """ DEVICE_LINK = """
<a href="{% url 'dcim:device' pk=record.pk %}">{{ record.name|default:'<span class="label label-info">Unnamed device</span>' }}</a> <a href="{% url 'dcim:device' pk=record.pk %}">{{ record.name|default:'<span class="label label-info">Unnamed device</span>' }}</a>
""" """
RACKGROUP_EDIT_LINK = """
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}">Edit</a>
"""
# #
# Sites # Sites
@ -46,6 +36,33 @@ class SiteTable(tables.Table):
} }
#
# Rack groups
#
class RackGroupTable(tables.Table):
name = tables.LinkColumn(verbose_name='Name')
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
slug = tables.Column(verbose_name='Slug')
class Meta:
model = RackGroup
fields = ('name', 'site', 'slug')
empty_text = "No rack groups were found."
attrs = {
'class': 'table table-hover',
}
class RackGroupBulkEditTable(RackGroupTable):
pk = tables.CheckBoxColumn()
edit = tables.TemplateColumn(template_code=RACKGROUP_EDIT_LINK, verbose_name='')
class Meta(RackGroupTable.Meta):
model = None # django_tables2 bugfix
fields = ('pk', 'name', 'site', 'slug', 'edit')
# #
# Racks # Racks
# #

View File

@ -17,6 +17,12 @@ urlpatterns = [
url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.site_edit, name='site_edit'), url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.site_edit, name='site_edit'),
url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.site_delete, name='site_delete'), url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.site_delete, name='site_delete'),
# Rack groups
url(r'^rack-groups/$', views.RackGroupListView.as_view(), name='rackgroup_list'),
url(r'^rack-groups/add/$', views.RackGroupAddView.as_view(), name='rackgroup_add'),
url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
# Racks # Racks
url(r'^racks/$', views.RackListView.as_view(), name='rack_list'), url(r'^racks/$', views.RackListView.as_view(), name='rack_list'),
url(r'^racks/add/$', views.rack_add, name='rack_add'), url(r'^racks/add/$', views.rack_add, name='rack_add'),

View File

@ -11,6 +11,7 @@ from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.utils.http import urlencode from django.utils.http import urlencode
from django.views.generic import View from django.views.generic import View
from django.views.generic.edit import CreateView, UpdateView
from ipam.models import Prefix, IPAddress, VLAN from ipam.models import Prefix, IPAddress, VLAN
from circuits.models import Circuit from circuits.models import Circuit
@ -18,26 +19,28 @@ 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, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, PowerConnectionFilter, \ from .filters import RackGroupFilter, RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, \
InterfaceConnectionFilter PowerConnectionFilter, InterfaceConnectionFilter
from .forms import SiteForm, SiteImportForm, RackForm, RackImportForm, RackBulkEditForm, RackBulkDeleteForm, \ from .forms import SiteForm, SiteImportForm, RackGroupFilterForm, RackGroupBulkDeleteForm, RackForm, RackImportForm, \
RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, \ RackBulkEditForm, RackBulkDeleteForm, RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, \
DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \ DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, DeviceForm, DeviceImportForm, DeviceBulkEditForm, \
ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, ConsoleConnectionImportForm, \ DeviceBulkDeleteForm, DeviceFilterForm, ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, \
ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, PowerPortForm, \ ConsoleConnectionImportForm, ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, \
PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, PowerOutletCreateForm, \ PowerPortForm, PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, \
PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, InterfaceConnectionForm, \ PowerOutletCreateForm, PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, \
InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, ConsoleConnectionFilterForm, \ InterfaceConnectionForm, InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, \
PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm, ConsolePortTemplateForm, \ ConsoleConnectionFilterForm, PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm, \
ConsoleServerPortTemplateForm, PowerPortTemplateForm, PowerOutletTemplateForm, InterfaceTemplateForm ConsolePortTemplateForm, ConsoleServerPortTemplateForm, PowerPortTemplateForm, PowerOutletTemplateForm, \
from .models import Site, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, PowerPortTemplate, \ InterfaceTemplateForm
PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, \ from .models import Site, RackGroup, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, \
InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, ConsoleServerPort, PowerPort, \
from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTypeTable, DeviceTypeBulkEditTable, DeviceTable, \ PowerOutlet, Interface, InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED
DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable, \ from .tables import SiteTable, RackGroupTable, RackGroupBulkEditTable, RackTable, RackBulkEditTable, DeviceTypeTable, \
ConsolePortTemplateTable, ConsoleServerPortTemplateTable, PowerPortTemplateTable, PowerOutletTemplateTable, \ DeviceTypeBulkEditTable, DeviceTable, DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, \
InterfaceTemplateTable, ConsolePortTemplateBulkDeleteTable, ConsoleServerPortTemplateBulkDeleteTable, \ PowerConnectionTable, InterfaceConnectionTable, ConsolePortTemplateTable, ConsoleServerPortTemplateTable, \
PowerPortTemplateBulkDeleteTable, PowerOutletTemplateBulkDeleteTable, InterfaceTemplateBulkDeleteTable PowerPortTemplateTable, PowerOutletTemplateTable, InterfaceTemplateTable, ConsolePortTemplateBulkDeleteTable, \
ConsoleServerPortTemplateBulkDeleteTable, PowerPortTemplateBulkDeleteTable, PowerOutletTemplateBulkDeleteTable, \
InterfaceTemplateBulkDeleteTable
EXPANSION_PATTERN = '\[(\d+-\d+)\]' EXPANSION_PATTERN = '\[(\d+-\d+)\]'
@ -171,6 +174,42 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
obj_list_url = 'dcim:site_list' obj_list_url = 'dcim:site_list'
#
# Rack groups
#
class RackGroupListView(ObjectListView):
queryset = RackGroup.objects.all()
filter = RackGroupFilter
filter_form = RackGroupFilterForm
table = RackGroupTable
edit_table = RackGroupBulkEditTable
edit_table_permissions = ['dcim.change_rackgroup', 'dcim.delete_rackgroup']
template_name = 'dcim/rackgroup_list.html'
class RackGroupAddView(PermissionRequiredMixin, CreateView):
permission_required = 'dcim.add_rackgroup'
model = RackGroup
fields = ['site', 'name', 'slug']
template_name = 'dcim/rackgroup_edit.html'
class RackGroupEditView(PermissionRequiredMixin, UpdateView):
permission_required = 'dcim.change_rackgroup'
model = RackGroup
fields = ['site', 'name', 'slug']
template_name = 'dcim/rackgroup_edit.html'
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_rackgroup'
cls = RackGroup
form = RackGroupBulkDeleteForm
template_name = 'dcim/rackgroup_bulk_delete.html'
redirect_url = 'dcim:rackgroup_list'
# #
# Racks # Racks
# #

View File

@ -27,53 +27,58 @@
<a href="{% url 'dcim:site_list' %}">Sites</a> <a href="{% url 'dcim:site_list' %}">Sites</a>
{% endif %} {% endif %}
</li> </li>
<li class="dropdown{% if '/racks/' in request.path %} active{% endif %}"> <li class="dropdown{% if '/racks/' in request.path or '/rack-groups/' in request.path %} active{% endif %}">
{% if perms.dcim.add_rack %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Racks <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Racks <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'dcim:rack_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Racks</a></li> <li><a href="{% url 'dcim:rack_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Racks</a></li>
<li><a href="{% url 'dcim:rack_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack</a></li> {% if perms.dcim.add_rack %}
<li><a href="{% url 'dcim:rack_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Racks</a></li> <li><a href="{% url 'dcim:rack_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack</a></li>
<li><a href="{% url 'dcim:rack_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Racks</a></li>
{% endif %}
{% if perms.dcim.add_rack or perms.dcim.add_rackgroup %}
<li class="divider"></li>
{% endif %}
<li><a href="{% url 'dcim:rackgroup_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Rack Groups</a></li>
{% if perms.dcim.add_rackgroup %}
<li><a href="{% url 'dcim:rackgroup_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
{% endif %}
</ul> </ul>
{% else %}
<a href="{% url 'dcim:rack_list' %}">Racks</a>
{% endif %}
</li> </li>
<li class="dropdown{% if '/devices/' in request.path or '/device-types/' in request.path or '-connections/' in request.path %} active{% endif %}"> <li class="dropdown{% if '/devices/' in request.path or '/device-types/' in request.path or '-connections/' in request.path %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Devices <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Devices <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="{% url 'dcim:device_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Devices</a></li> <li><a href="{% url 'dcim:device_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Devices</a></li>
{% if perms.dcim.add_device %} {% if perms.dcim.add_device %}
<li><a href="{% url 'dcim:device_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device</a></li> <li><a href="{% url 'dcim:device_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device</a></li>
<li><a href="{% url 'dcim:device_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Devices</a></li> <li><a href="{% url 'dcim:device_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Devices</a></li>
{% endif %} {% endif %}
{% if perms.ipam.add_device or perms.ipam.add_devicetype %} {% if perms.ipam.add_device or perms.ipam.add_devicetype %}
<li class="divider"></li>
{% endif %}
<li><a href="{% url 'dcim:devicetype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Types</a></li>
{% if perms.dcim.add_device %}
<li><a href="{% url 'dcim:devicetype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Type</a></li>
{% endif %}
<li class="divider"></li> <li class="divider"></li>
<li><a href="{% url 'dcim:console_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Console Connections</a></li> {% endif %}
{% if perms.dcim.change_consoleport %} <li><a href="{% url 'dcim:devicetype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Types</a></li>
<li><a href="{% url 'dcim:console_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Console Connections</a></li> {% if perms.dcim.add_device %}
{% endif %} <li><a href="{% url 'dcim:devicetype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Type</a></li>
{% if perms.ipam.change_consoleport or perms.ipam.change_powerport %} {% endif %}
<li class="divider"></li> <li class="divider"></li>
{% endif %} <li><a href="{% url 'dcim:console_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Console Connections</a></li>
<li><a href="{% url 'dcim:power_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Power Connections</a></li> {% if perms.dcim.change_consoleport %}
{% if perms.dcim.change_powerport %} <li><a href="{% url 'dcim:console_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Console Connections</a></li>
<li><a href="{% url 'dcim:power_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Power Connections</a></li> {% endif %}
{% endif %} {% if perms.ipam.change_consoleport or perms.ipam.change_powerport %}
{% if perms.ipam.change_powerport or perms.ipam.add_interfaceconnection %} <li class="divider"></li>
<li class="divider"></li> {% endif %}
{% endif %} <li><a href="{% url 'dcim:power_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Power Connections</a></li>
<li><a href="{% url 'dcim:interface_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Interface Connections</a></li> {% if perms.dcim.change_powerport %}
{% if perms.dcim.add_interfaceconnection %} <li><a href="{% url 'dcim:power_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Power Connections</a></li>
<li><a href="{% url 'dcim:interface_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Interface Connections</a></li> {% endif %}
{% endif %} {% if perms.ipam.change_powerport or perms.ipam.add_interfaceconnection %}
</ul> <li class="divider"></li>
{% endif %}
<li><a href="{% url 'dcim:interface_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Interface Connections</a></li>
{% if perms.dcim.add_interfaceconnection %}
<li><a href="{% url 'dcim:interface_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Interface Connections</a></li>
{% endif %}
</ul>
</li> </li>
<li class="dropdown{% if '/ip-addresses/' in request.path or '/prefixes/' in request.path or '/aggregates/' in request.path %} active{% endif %}"> <li class="dropdown{% if '/ip-addresses/' in request.path or '/prefixes/' in request.path or '/aggregates/' in request.path %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">IP Space <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">IP Space <span class="caret"></span></a>

View File

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

View File

@ -0,0 +1,15 @@
{% extends 'utilities/confirmation_form.html' %}
{% load form_helpers %}
{% block title %}Delete Rack Groups?{% endblock %}
{% block message %}
<p>
Are you sure you want to delete these rack groups?
</p>
<ul>
{% for rg in selected_objects %}
<li>{{ rg }} ({{ rg.site }})</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,49 @@
{% extends '_base.html' %}
{% load form_helpers %}
{% block title %}{% if rack %}Editing rack group {{ rackgroup }}{% else %}Add a rack group{% endif %}{% 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 rackgroup %}
<h1>Rack group {{ rackgroup }}</h1>
{% else %}
<h1>Add a Rack Group</h1>
{% endif %}
{% 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>Rack Group</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-right">
{% if rackgroup %}
<button type="submit" name="_update" class="btn btn-primary">Update</button>
<a href="{% url 'dcim:rackgroup_list' %}" 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="{% url 'dcim:rackgroup_list' %}" class="btn btn-default">Cancel</a>
{% endif %}
</div>
</div>
</form>
{% endblock %}

View File

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