diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 97ebe025f..315d9e46b 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -68,6 +68,24 @@ class SiteImportForm(BulkImportForm, BootstrapMixin): 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 # diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 624d1b753..dc55ed95c 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -142,6 +142,9 @@ class RackGroup(models.Model): def __unicode__(self): return self.name + def get_absolute_url(self): + return "{}?group={}".format(reverse('dcim:rack_list'), self.slug) + class Rack(models.Model): """ diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index be2d43631..1901089fc 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -1,27 +1,17 @@ import django_tables2 as tables from django_tables2.utils import Accessor -from .models import Site, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, PowerPortTemplate, \ - PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, PowerPort - - -PREFIXES_PER_VLAN = """ -{% for p in record.prefix_set.all %} - {{ p }} - {% if not forloop.last %}
{% endif %} -{% endfor %} -""" - -STATUS_LABEL = """ - - {{ record.status.name }} - -""" +from .models import Site, RackGroup, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, \ + PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, PowerPort DEVICE_LINK = """ {{ record.name|default:'Unnamed device' }} """ +RACKGROUP_EDIT_LINK = """ +Edit +""" + # # 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 # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c7b55ee80..5cb11d405 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -17,6 +17,12 @@ urlpatterns = [ url(r'^sites/(?P[\w-]+)/edit/$', views.site_edit, name='site_edit'), url(r'^sites/(?P[\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\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'), + # Racks url(r'^racks/$', views.RackListView.as_view(), name='rack_list'), url(r'^racks/add/$', views.rack_add, name='rack_add'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 3542d388c..4c1516ee8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -11,6 +11,7 @@ from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.utils.http import urlencode from django.views.generic import View +from django.views.generic.edit import CreateView, UpdateView from ipam.models import Prefix, IPAddress, VLAN from circuits.models import Circuit @@ -18,26 +19,28 @@ from utilities.error_handlers import handle_protectederror from utilities.forms import ConfirmationForm from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView -from .filters import RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, PowerConnectionFilter, \ - InterfaceConnectionFilter -from .forms import SiteForm, SiteImportForm, RackForm, RackImportForm, RackBulkEditForm, RackBulkDeleteForm, \ - 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, ConsolePortTemplateForm, \ - ConsoleServerPortTemplateForm, PowerPortTemplateForm, PowerOutletTemplateForm, InterfaceTemplateForm -from .models import Site, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, PowerPortTemplate, \ - PowerOutletTemplate, InterfaceTemplate, 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, \ - ConsolePortTemplateTable, ConsoleServerPortTemplateTable, PowerPortTemplateTable, PowerOutletTemplateTable, \ - InterfaceTemplateTable, ConsolePortTemplateBulkDeleteTable, ConsoleServerPortTemplateBulkDeleteTable, \ - PowerPortTemplateBulkDeleteTable, PowerOutletTemplateBulkDeleteTable, InterfaceTemplateBulkDeleteTable +from .filters import RackGroupFilter, RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, \ + PowerConnectionFilter, InterfaceConnectionFilter +from .forms import SiteForm, SiteImportForm, RackGroupFilterForm, RackGroupBulkDeleteForm, RackForm, RackImportForm, \ + RackBulkEditForm, RackBulkDeleteForm, 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, \ + ConsolePortTemplateForm, ConsoleServerPortTemplateForm, PowerPortTemplateForm, PowerOutletTemplateForm, \ + InterfaceTemplateForm +from .models import Site, RackGroup, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, \ + PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, ConsoleServerPort, PowerPort, \ + PowerOutlet, Interface, InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED +from .tables import SiteTable, RackGroupTable, RackGroupBulkEditTable, RackTable, RackBulkEditTable, DeviceTypeTable, \ + DeviceTypeBulkEditTable, DeviceTable, DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, \ + PowerConnectionTable, InterfaceConnectionTable, ConsolePortTemplateTable, ConsoleServerPortTemplateTable, \ + PowerPortTemplateTable, PowerOutletTemplateTable, InterfaceTemplateTable, ConsolePortTemplateBulkDeleteTable, \ + ConsoleServerPortTemplateBulkDeleteTable, PowerPortTemplateBulkDeleteTable, PowerOutletTemplateBulkDeleteTable, \ + InterfaceTemplateBulkDeleteTable EXPANSION_PATTERN = '\[(\d+-\d+)\]' @@ -171,6 +174,42 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView): 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 # diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 53aab7c1b..93f6e4fa7 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -27,53 +27,58 @@ Sites {% endif %} -