Skip to content

Commit

Permalink
Closes netbox-community#1052: Added rack reservation list and bulk de…
Browse files Browse the repository at this point in the history
…lete views
  • Loading branch information
jeremystretch committed Apr 6, 2017
1 parent b69edd7 commit 4bc4bf0
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 2 deletions.
37 changes: 37 additions & 0 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,33 @@ def search(self, queryset, name, value):


class RackReservationFilter(django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter(
method='search',
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='rack__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='rack__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
group_id = NullableModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = NullableModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
)
rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack',
queryset=Rack.objects.all(),
Expand All @@ -157,6 +184,16 @@ class Meta:
model = RackReservation
fields = ['rack', 'user']

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(rack__name__icontains=value) |
Q(rack__facility_id__icontains=value) |
Q(user__username__icontains=value) |
Q(description__icontains=value)
)


class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
Expand Down
13 changes: 13 additions & 0 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,19 @@ def _get_unit_choices(self):
return unit_choices


class RackReservationFilterForm(BootstrapMixin, forms.Form):
q = forms.CharField(required=False, label='Search')
site = FilterChoiceField(
queryset=Site.objects.annotate(filter_count=Count('racks__reservations')),
to_field_name='slug'
)
group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks__reservations')),
label='Rack group',
null_option=(0, 'None')
)


#
# Manufacturers
#
Expand Down
10 changes: 10 additions & 0 deletions netbox/dcim/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import OrderedDict
from itertools import count, groupby

from mptt.models import MPTTModel, TreeForeignKey

Expand Down Expand Up @@ -571,6 +572,15 @@ def clean(self):
)
})

@property
def unit_list(self):
"""
Express the assigned units as a string of summarized ranges. For example:
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
"""
group = (list(x) for _, x in groupby(sorted(self.units), lambda x, c=count(): next(c) - x))
return ', '.join('-'.join(map(str, (g[0], g[-1])[:len(g)])) for g in group)


#
# Device Types
Expand Down
25 changes: 24 additions & 1 deletion netbox/dcim/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
Interface, InterfaceTemplate, Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, Region, Site,
RackGroup, RackReservation, Region, Site,
)


Expand Down Expand Up @@ -64,6 +64,12 @@
{% endif %}
"""

RACKRESERVATION_ACTIONS = """
{% if perms.dcim.change_rackreservation %}
<a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
{% endif %}
"""

DEVICEROLE_ACTIONS = """
{% if perms.dcim.change_devicerole %}
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
Expand Down Expand Up @@ -226,6 +232,23 @@ class Meta(BaseTable.Meta):
fields = ('site', 'group', 'name', 'facility_id', 'tenant', 'u_height')


#
# Rack reservations
#

class RackReservationTable(BaseTable):
pk = ToggleColumn()
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
unit_list = tables.Column(orderable=False, verbose_name='Units')
actions = tables.TemplateColumn(
template_code=RACKRESERVATION_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
)

class Meta(BaseTable.Meta):
model = RackReservation
fields = ('pk', 'rack', 'unit_list', 'user', 'created', 'description', 'actions')


#
# Manufacturers
#
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
url(r'^rack-roles/(?P<pk>\d+)/edit/$', views.RackRoleEditView.as_view(), name='rackrole_edit'),

# Rack reservations
url(r'^rack-reservations/$', views.RackReservationListView.as_view(), name='rackreservation_list'),
url(r'^rack-reservations/delete/$', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'),
url(r'^rack-reservations/(?P<pk>\d+)/edit/$', views.RackReservationEditView.as_view(), name='rackreservation_edit'),
url(r'^rack-reservations/(?P<pk>\d+)/delete/$', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'),

Expand Down
14 changes: 14 additions & 0 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
# Rack reservations
#

class RackReservationListView(ObjectListView):
queryset = RackReservation.objects.all()
filter = filters.RackReservationFilter
filter_form = forms.RackReservationFilterForm
table = tables.RackReservationTable
template_name = 'dcim/rackreservation_list.html'


class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_rackreservation'
model = RackReservation
Expand All @@ -383,6 +391,12 @@ def get_return_url(self, obj):
return obj.rack.get_absolute_url()


class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_rackreservation'
cls = RackReservation
default_return_url = 'dcim:rackreservation_list'


#
# Manufacturers
#
Expand Down
2 changes: 2 additions & 0 deletions netbox/templates/_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
{% if perms.dcim.add_rackrole %}
<li><a href="{% url 'dcim:rackrole_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack Role</a></li>
{% endif %}
<li class="divider"></li>
<li><a href="{% url 'dcim:rackreservation_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Rack Reservations</a></li>
</ul>
</li>
<li class="dropdown{% if request.path|contains:'/dcim/device,/dcim/manufacturers/,/dcim/platforms/' %} active{% endif %}">
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/rack.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ <h1>Rack {{ rack.name }}</h1>
</tr>
{% for resv in reservations %}
<tr>
<td>{{ resv.units|join:', ' }}</td>
<td>{{ resv.unit_list }}</td>
<td>
{{ resv.description }}<br />
<small>{{ resv.user }} &middot; {{ resv.created }}</small>
Expand Down
14 changes: 14 additions & 0 deletions netbox/templates/dcim/rackreservation_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends '_base.html' %}
{% load helpers %}

{% block content %}
<h1>{% block title %}Rack Reservations{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackreservation_bulk_delete' %}
</div>
<div class="col-md-3">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

0 comments on commit 4bc4bf0

Please sign in to comment.