diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index e27b669cd..f7460d92e 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -5,6 +5,7 @@ ### Enhancements * [#4309](https://github.com/netbox-community/netbox/issues/4309) - Add descriptive tooltip to custom fields on object views +* [#4369](https://github.com/netbox-community/netbox/issues/4369) - Add a dedicated view for rack reservations ### Bug Fixes diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index af785f0d2..94e8a2391 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -769,6 +769,9 @@ class RackReservation(ChangeLoggedModel): def __str__(self): return "Reservation for rack {}".format(self.rack) + def get_absolute_url(self): + return reverse('dcim:rackreservation', args=[self.pk]) + def clean(self): if self.units: diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index bc91dd70c..816318126 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -338,21 +338,38 @@ class RackDetailTable(RackTable): class RackReservationTable(BaseTable): pk = ToggleColumn() + reservation = tables.LinkColumn( + viewname='dcim:rackreservation', + args=[Accessor('pk')], + accessor='pk' + ) site = tables.LinkColumn( viewname='dcim:site', accessor=Accessor('rack.site'), args=[Accessor('rack.site.slug')], ) - tenant = tables.TemplateColumn(template_code=COL_TENANT) - rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')]) - unit_list = tables.Column(orderable=False, verbose_name='Units') + tenant = tables.TemplateColumn( + template_code=COL_TENANT + ) + rack = tables.LinkColumn( + viewname='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 noprint'}}, verbose_name='' + template_code=RACKRESERVATION_ACTIONS, + attrs={'td': {'class': 'text-right noprint'}}, + verbose_name='' ) class Meta(BaseTable.Meta): model = RackReservation - fields = ('pk', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'actions') + fields = ( + 'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'actions', + ) # diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index b5b156088..68ecda6e8 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -172,9 +172,6 @@ class RackRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = RackReservation - # Disable inapplicable tests - test_get_object = None - @classmethod def setUpTestData(cls): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c59d67917..05f0aa9c2 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -54,6 +54,7 @@ urlpatterns = [ path('rack-reservations/import/', views.RackReservationImportView.as_view(), name='rackreservation_import'), path('rack-reservations/edit/', views.RackReservationBulkEditView.as_view(), name='rackreservation_bulk_edit'), path('rack-reservations/delete/', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'), + path('rack-reservations//', views.RackReservationView.as_view(), name='rackreservation'), path('rack-reservations//edit/', views.RackReservationEditView.as_view(), name='rackreservation_edit'), path('rack-reservations//delete/', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'), path('rack-reservations//changelog/', ObjectChangeLogView.as_view(), name='rackreservation_changelog', kwargs={'model': RackReservation}), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index d1e5f019e..619e4b5b7 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -473,6 +473,18 @@ class RackReservationListView(PermissionRequiredMixin, ObjectListView): action_buttons = ('export',) +class RackReservationView(PermissionRequiredMixin, View): + permission_required = 'dcim.view_rackreservation' + + def get(self, request, pk): + + rackreservation = get_object_or_404(RackReservation.objects.prefetch_related('rack'), pk=pk) + + return render(request, 'dcim/rackreservation.html', { + 'rackreservation': rackreservation, + }) + + class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_rackreservation' model = RackReservation diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 3795b218b..ecd17172b 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -271,7 +271,9 @@ {% for resv in reservations %} - {{ resv.unit_list }} + + {{ resv.unit_list }} + {% if resv.tenant %} {{ resv.tenant }} diff --git a/netbox/templates/dcim/rackreservation.html b/netbox/templates/dcim/rackreservation.html new file mode 100644 index 000000000..ef9e49d23 --- /dev/null +++ b/netbox/templates/dcim/rackreservation.html @@ -0,0 +1,146 @@ +{% extends '_base.html' %} +{% load buttons %} +{% load custom_links %} +{% load helpers %} +{% load static %} + +{% block header %} +
+
+ +
+
+
+
+ + + + +
+
+
+
+
+ {% if perms.dcim.change_rackreservation %} + {% edit_button rackreservation %} + {% endif %} + {% if perms.dcim.delete_rackreservation %} + {% delete_button rackreservation %} + {% endif %} +
+

{% block title %}{{ rackreservation }}{% endblock %}

+ {% include 'inc/created_updated.html' with obj=rackreservation %} + +{% endblock %} + +{% block content %} +
+
+
+
+ Rack +
+ + {% with rack=rackreservation.rack %} + + + + + + + + + + + + + {% endwith %} +
Site + {% if rack.site.region %} + {{ rack.site.region }} + + {% endif %} + {{ rack.site }} +
Group + {% if rack.group %} + {{ rack.group }} + {% else %} + None + {% endif %} +
Rack + {{ rack }} +
+
+
+
+ Reservation Details +
+ + + + + + + + + + + + + + + + + +
Units{{ rackreservation.unit_list }}
Tenant + {% if rackreservation.tenant %} + {% if rackreservation.tenant.group %} + {{ rackreservation.tenant.group }} + + {% endif %} + {{ rackreservation.tenant }} + {% else %} + None + {% endif %} +
User{{ rackreservation.user }}
Description{{ rackreservation.description }}
+
+
+
+ {% with rack=rackreservation.rack %} +
+
+
+

Front

+
+ {% include 'dcim/inc/rack_elevation.html' with face='front' %} +
+
+
+

Rear

+
+ {% include 'dcim/inc/rack_elevation.html' with face='rear' %} +
+
+ {% endwith %} +
+
+{% endblock %} + +{% block javascript %} + +{% endblock %}