Просмотр исходного кода

Closes #18984: Add status field to Rack model (#20080)

Jeremy Stretch 6 месяцев назад
Родитель
Сommit
bb57021197

+ 7 - 0
docs/models/dcim/rackreservation.md

@@ -12,6 +12,13 @@ The [rack](./rack.md) being reserved.
 
 
 The rack unit or units being reserved. Multiple units can be expressed using commas and/or hyphens. For example, `1,3,5-7` specifies units 1, 3, 5, 6, and 7.
 The rack unit or units being reserved. Multiple units can be expressed using commas and/or hyphens. For example, `1,3,5-7` specifies units 1, 3, 5, 6, and 7.
 
 
+### Status
+
+The current status of the reservation. (This is for documentation only: The status of a reservation has no impact on the installation of devices within a reserved rack unit.)
+
+!!! tip
+    Additional statuses may be defined by setting `RackReservation.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
+
 ### User
 ### User
 
 
 The NetBox user account associated with the reservation. Note that users with sufficient permission can make rack reservations for other users.
 The NetBox user account associated with the reservation. Note that users with sufficient permission can make rack reservations for other users.

+ 18 - 6
netbox/dcim/api/serializers_/racks.py

@@ -137,17 +137,29 @@ class RackSerializer(RackBaseSerializer):
 
 
 
 
 class RackReservationSerializer(NetBoxModelSerializer):
 class RackReservationSerializer(NetBoxModelSerializer):
-    rack = RackSerializer(nested=True)
-    user = UserSerializer(nested=True)
-    tenant = TenantSerializer(nested=True, required=False, allow_null=True)
+    rack = RackSerializer(
+        nested=True,
+    )
+    status = ChoiceField(
+        choices=RackReservationStatusChoices,
+        required=False,
+    )
+    user = UserSerializer(
+        nested=True,
+    )
+    tenant = TenantSerializer(
+        nested=True,
+        required=False,
+        allow_null=True,
+    )
 
 
     class Meta:
     class Meta:
         model = RackReservation
         model = RackReservation
         fields = [
         fields = [
-            'id', 'url', 'display_url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant',
-            'description', 'comments', 'tags', 'custom_fields',
+            'id', 'url', 'display_url', 'display', 'rack', 'units', 'status', 'created', 'last_updated', 'user',
+            'tenant', 'description', 'comments', 'tags', 'custom_fields',
         ]
         ]
-        brief_fields = ('id', 'url', 'display', 'user', 'description', 'units')
+        brief_fields = ('id', 'url', 'display', 'status', 'user', 'description', 'units')
 
 
 
 
 class RackElevationDetailFilterSerializer(serializers.Serializer):
 class RackElevationDetailFilterSerializer(serializers.Serializer):

+ 18 - 0
netbox/dcim/choices.py

@@ -139,6 +139,24 @@ class RackAirflowChoices(ChoiceSet):
     ]
     ]
 
 
 
 
+#
+# Rack reservations
+#
+
+class RackReservationStatusChoices(ChoiceSet):
+    key = 'RackReservation.status'
+
+    STATUS_PENDING = 'pending'
+    STATUS_ACTIVE = 'active'
+    STATUS_STALE = 'stale'
+
+    CHOICES = [
+        (STATUS_PENDING, _('Pending'), 'cyan'),
+        (STATUS_ACTIVE, _('Active'), 'green'),
+        (STATUS_STALE, _('Stale'), 'orange'),
+    ]
+
+
 #
 #
 # DeviceTypes
 # DeviceTypes
 #
 #

+ 4 - 0
netbox/dcim/filtersets.py

@@ -499,6 +499,10 @@ class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label=_('Location (slug)'),
         label=_('Location (slug)'),
     )
     )
+    status = django_filters.MultipleChoiceFilter(
+        choices=RackReservationStatusChoices,
+        null_value=None
+    )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         label=_('User (ID)'),
         label=_('User (ID)'),

+ 7 - 1
netbox/dcim/forms/bulk_edit.py

@@ -476,6 +476,12 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
 
 
 
 
 class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
 class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
+    status = forms.ChoiceField(
+        label=_('Status'),
+        choices=add_blank_choice(RackReservationStatusChoices),
+        required=False,
+        initial=''
+    )
     user = forms.ModelChoiceField(
     user = forms.ModelChoiceField(
         label=_('User'),
         label=_('User'),
         queryset=User.objects.order_by('username'),
         queryset=User.objects.order_by('username'),
@@ -495,7 +501,7 @@ class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
 
 
     model = RackReservation
     model = RackReservation
     fieldsets = (
     fieldsets = (
-        FieldSet('user', 'tenant', 'description'),
+        FieldSet('status', 'user', 'tenant', 'description'),
     )
     )
     nullable_fields = ('comments',)
     nullable_fields = ('comments',)
 
 

+ 6 - 1
netbox/dcim/forms/bulk_import.py

@@ -358,6 +358,11 @@ class RackReservationImportForm(NetBoxModelImportForm):
         required=True,
         required=True,
         help_text=_('Comma-separated list of individual unit numbers')
         help_text=_('Comma-separated list of individual unit numbers')
     )
     )
+    status = CSVChoiceField(
+        label=_('Status'),
+        choices=RackReservationStatusChoices,
+        help_text=_('Operational status')
+    )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
         label=_('Tenant'),
         label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -368,7 +373,7 @@ class RackReservationImportForm(NetBoxModelImportForm):
 
 
     class Meta:
     class Meta:
         model = RackReservation
         model = RackReservation
-        fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments', 'tags')
+        fields = ('site', 'location', 'rack', 'units', 'status', 'tenant', 'description', 'comments', 'tags')
 
 
     def __init__(self, data=None, *args, **kwargs):
     def __init__(self, data=None, *args, **kwargs):
         super().__init__(data, *args, **kwargs)
         super().__init__(data, *args, **kwargs)

+ 6 - 1
netbox/dcim/forms/filtersets.py

@@ -417,7 +417,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RackReservation
     model = RackReservation
     fieldsets = (
     fieldsets = (
         FieldSet('q', 'filter_id', 'tag'),
         FieldSet('q', 'filter_id', 'tag'),
-        FieldSet('user_id', name=_('User')),
+        FieldSet('status', 'user_id', name=_('Reservation')),
         FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
         FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
     )
     )
@@ -458,6 +458,11 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         },
         },
         label=_('Rack')
         label=_('Rack')
     )
     )
+    status = forms.MultipleChoiceField(
+        label=_('Status'),
+        choices=RackReservationStatusChoices,
+        required=False
+    )
     user_id = DynamicModelMultipleChoiceField(
     user_id = DynamicModelMultipleChoiceField(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         required=False,
         required=False,

+ 2 - 2
netbox/dcim/forms/model_forms.py

@@ -336,14 +336,14 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        FieldSet('rack', 'units', 'user', 'description', 'tags', name=_('Reservation')),
+        FieldSet('rack', 'units', 'status', 'user', 'description', 'tags', name=_('Reservation')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
     )
     )
 
 
     class Meta:
     class Meta:
         model = RackReservation
         model = RackReservation
         fields = [
         fields = [
-            'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
+            'rack', 'units', 'status', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
         ]
         ]
 
 
 
 

+ 16 - 0
netbox/dcim/migrations/0213_rackreservation_status.py

@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0212_platform_rebuild'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='rackreservation',
+            name='status',
+            field=models.CharField(default='active', max_length=50),
+        ),
+    ]

+ 9 - 0
netbox/dcim/models/racks.py

@@ -673,6 +673,12 @@ class RackReservation(PrimaryModel):
         verbose_name=_('units'),
         verbose_name=_('units'),
         base_field=models.PositiveSmallIntegerField()
         base_field=models.PositiveSmallIntegerField()
     )
     )
+    status = models.CharField(
+        verbose_name=_('status'),
+        max_length=50,
+        choices=RackReservationStatusChoices,
+        default=RackReservationStatusChoices.STATUS_ACTIVE
+    )
     tenant = models.ForeignKey(
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         to='tenancy.Tenant',
         on_delete=models.PROTECT,
         on_delete=models.PROTECT,
@@ -733,6 +739,9 @@ class RackReservation(PrimaryModel):
     def unit_list(self):
     def unit_list(self):
         return array_to_string(self.units)
         return array_to_string(self.units)
 
 
+    def get_status_color(self):
+        return RackReservationStatusChoices.colors.get(self.status)
+
     def to_objectchange(self, action):
     def to_objectchange(self, action):
         objectchange = super().to_objectchange(action)
         objectchange = super().to_objectchange(action)
         objectchange.related_object = self.rack
         objectchange.related_object = self.rack

+ 5 - 2
netbox/dcim/tables/racks.py

@@ -229,6 +229,9 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
         orderable=False,
         orderable=False,
         verbose_name=_('Units')
         verbose_name=_('Units')
     )
     )
+    status = columns.ChoiceFieldColumn(
+        verbose_name=_('Status'),
+    )
     comments = columns.MarkdownColumn(
     comments = columns.MarkdownColumn(
         verbose_name=_('Comments'),
         verbose_name=_('Comments'),
     )
     )
@@ -239,7 +242,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = RackReservation
         model = RackReservation
         fields = (
         fields = (
-            'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant',
+            'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'status', 'user', 'created', 'tenant',
             'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
             'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
         )
         )
-        default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description')
+        default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'status', 'user', 'description')

+ 22 - 4
netbox/dcim/tests/test_api.py

@@ -465,7 +465,7 @@ class RackTest(APIViewTestCases.APIViewTestCase):
 
 
 class RackReservationTest(APIViewTestCases.APIViewTestCase):
 class RackReservationTest(APIViewTestCases.APIViewTestCase):
     model = RackReservation
     model = RackReservation
-    brief_fields = ['description', 'display', 'id', 'units', 'url', 'user']
+    brief_fields = ['description', 'display', 'id', 'status', 'units', 'url', 'user']
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
@@ -483,9 +483,24 @@ class RackReservationTest(APIViewTestCases.APIViewTestCase):
         Rack.objects.bulk_create(racks)
         Rack.objects.bulk_create(racks)
 
 
         rack_reservations = (
         rack_reservations = (
-            RackReservation(rack=racks[0], units=[1, 2, 3], user=user, description='Reservation #1'),
-            RackReservation(rack=racks[0], units=[4, 5, 6], user=user, description='Reservation #2'),
-            RackReservation(rack=racks[0], units=[7, 8, 9], user=user, description='Reservation #3'),
+            RackReservation(
+                rack=racks[0],
+                units=[1, 2, 3],
+                user=user,
+                description='Reservation #1',
+            ),
+            RackReservation(
+                rack=racks[0],
+                units=[4, 5, 6],
+                user=user,
+                description='Reservation #2'
+            ),
+            RackReservation(
+                rack=racks[0],
+                units=[7, 8, 9],
+                user=user,
+                description='Reservation #3',
+            ),
         )
         )
         RackReservation.objects.bulk_create(rack_reservations)
         RackReservation.objects.bulk_create(rack_reservations)
 
 
@@ -493,18 +508,21 @@ class RackReservationTest(APIViewTestCases.APIViewTestCase):
             {
             {
                 'rack': racks[1].pk,
                 'rack': racks[1].pk,
                 'units': [10, 11, 12],
                 'units': [10, 11, 12],
+                'status': RackReservationStatusChoices.STATUS_ACTIVE,
                 'user': user.pk,
                 'user': user.pk,
                 'description': 'Reservation #4',
                 'description': 'Reservation #4',
             },
             },
             {
             {
                 'rack': racks[1].pk,
                 'rack': racks[1].pk,
                 'units': [13, 14, 15],
                 'units': [13, 14, 15],
+                'status': RackReservationStatusChoices.STATUS_PENDING,
                 'user': user.pk,
                 'user': user.pk,
                 'description': 'Reservation #5',
                 'description': 'Reservation #5',
             },
             },
             {
             {
                 'rack': racks[1].pk,
                 'rack': racks[1].pk,
                 'units': [16, 17, 18],
                 'units': [16, 17, 18],
+                'status': RackReservationStatusChoices.STATUS_STALE,
                 'user': user.pk,
                 'user': user.pk,
                 'description': 'Reservation #6',
                 'description': 'Reservation #6',
             },
             },

+ 28 - 3
netbox/dcim/tests/test_filtersets.py

@@ -1141,9 +1141,30 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
         Tenant.objects.bulk_create(tenants)
         Tenant.objects.bulk_create(tenants)
 
 
         reservations = (
         reservations = (
-            RackReservation(rack=racks[0], units=[1, 2, 3], user=users[0], tenant=tenants[0], description='foobar1'),
-            RackReservation(rack=racks[1], units=[4, 5, 6], user=users[1], tenant=tenants[1], description='foobar2'),
-            RackReservation(rack=racks[2], units=[7, 8, 9], user=users[2], tenant=tenants[2], description='foobar3'),
+            RackReservation(
+                rack=racks[0],
+                units=[1, 2, 3],
+                status=RackReservationStatusChoices.STATUS_ACTIVE,
+                user=users[0],
+                tenant=tenants[0],
+                description='foobar1',
+            ),
+            RackReservation(
+                rack=racks[1],
+                units=[4, 5, 6],
+                status=RackReservationStatusChoices.STATUS_PENDING,
+                user=users[1],
+                tenant=tenants[1],
+                description='foobar2',
+            ),
+            RackReservation(
+                rack=racks[2],
+                units=[7, 8, 9],
+                status=RackReservationStatusChoices.STATUS_STALE,
+                user=users[2],
+                tenant=tenants[2],
+                description='foobar3',
+            ),
         )
         )
         RackReservation.objects.bulk_create(reservations)
         RackReservation.objects.bulk_create(reservations)
 
 
@@ -1179,6 +1200,10 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'location': [locations[0].slug, locations[1].slug]}
         params = {'location': [locations[0].slug, locations[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_status(self):
+        params = {'status': [RackReservationStatusChoices.STATUS_ACTIVE, RackReservationStatusChoices.STATUS_PENDING]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_user(self):
     def test_user(self):
         users = User.objects.all()[:2]
         users = User.objects.all()[:2]
         params = {'user_id': [users[0].pk, users[1].pk]}
         params = {'user_id': [users[0].pk, users[1].pk]}

+ 6 - 4
netbox/dcim/tests/test_views.py

@@ -337,6 +337,7 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         cls.form_data = {
         cls.form_data = {
             'rack': rack.pk,
             'rack': rack.pk,
             'units': "10,11,12",
             'units': "10,11,12",
+            'status': RackReservationStatusChoices.STATUS_PENDING,
             'user': user3.pk,
             'user': user3.pk,
             'tenant': None,
             'tenant': None,
             'description': 'Rack reservation',
             'description': 'Rack reservation',
@@ -344,10 +345,10 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         }
         }
 
 
         cls.csv_data = (
         cls.csv_data = (
-            'site,location,rack,units,description',
-            'Site 1,Location 1,Rack 1,"10,11,12",Reservation 1',
-            'Site 1,Location 1,Rack 1,"13,14,15",Reservation 2',
-            'Site 1,Location 1,Rack 1,"16,17,18",Reservation 3',
+            'site,location,rack,units,status,description',
+            'Site 1,Location 1,Rack 1,"10,11,12",active,Reservation 1',
+            'Site 1,Location 1,Rack 1,"13,14,15",pending,Reservation 2',
+            'Site 1,Location 1,Rack 1,"16,17,18",stale,Reservation 3',
         )
         )
 
 
         cls.csv_update_data = (
         cls.csv_update_data = (
@@ -358,6 +359,7 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         )
         )
 
 
         cls.bulk_edit_data = {
         cls.bulk_edit_data = {
+            'status': RackReservationStatusChoices.STATUS_STALE,
             'user': user3.pk,
             'user': user3.pk,
             'tenant': None,
             'tenant': None,
             'description': 'New description',
             'description': 'New description',

+ 76 - 72
netbox/templates/dcim/rackreservation.html

@@ -13,83 +13,87 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block content %}
 {% block content %}
-<div class="row mb-3">
-	<div class="col col-12 col-xl-5">
-        <div class="card">
-            <h2 class="card-header">{% trans "Rack" %}</h2>
-            <table class="table table-hover attr-table">
-                <tr>
-                    <th scope="row">{% trans "Region" %}</th>
-                    <td>
-                        {% nested_tree object.rack.site.region %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Site" %}</th>
-                    <td>{{ object.rack.site|linkify }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Location" %}</th>
-                    <td>{{ object.rack.location|linkify|placeholder }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Rack" %}</th>
-                    <td>{{ object.rack|linkify }}</td>
-                </tr>
-            </table>
-        </div>
-        <div class="card">
-            <h2 class="card-header">{% trans "Reservation Details" %}</h2>
-            <table class="table table-hover attr-table">
-                <tr>
-                    <th scope="row">{% trans "Units" %}</th>
-                    <td>{{ object.unit_list }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Tenant" %}</th>
-                    <td>
-                        {% if object.tenant.group %}
-                            {{ object.tenant.group|linkify }} /
-                        {% endif %}
-                        {{ object.tenant|linkify|placeholder }}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "User" %}</th>
-                    <td>{{ object.user }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">{% trans "Description" %}</th>
-                    <td>{{ object.description }}</td>
-                </tr>
-            </table>
-        </div>
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% include 'inc/panels/comments.html' %}
-        {% plugin_left_page object %}
-	</div>
-    <div class="col col-12 col-xl-7">
+  <div class="row mb-3">
+    <div class="col col-12 col-xl-5">
+      <div class="card">
+        <h2 class="card-header">{% trans "Rack" %}</h2>
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">{% trans "Region" %}</th>
+            <td>
+              {% nested_tree object.rack.site.region %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Site" %}</th>
+            <td>{{ object.rack.site|linkify }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Location" %}</th>
+            <td>{{ object.rack.location|linkify|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Rack" %}</th>
+            <td>{{ object.rack|linkify }}</td>
+          </tr>
+        </table>
+      </div>
+      <div class="card">
+        <h2 class="card-header">{% trans "Reservation Details" %}</h2>
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">{% trans "Units" %}</th>
+            <td>{{ object.unit_list }}</td>
+          </tr>
+           <tr>
+            <th scope="row">{% trans "Status" %}</th>
+            <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Tenant" %}</th>
+            <td>
+              {% if object.tenant.group %}
+                {{ object.tenant.group|linkify }} /
+              {% endif %}
+              {{ object.tenant|linkify|placeholder }}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "User" %}</th>
+            <td>{{ object.user }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Description" %}</th>
+            <td>{{ object.description }}</td>
+          </tr>
+        </table>
+      </div>
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
+      {% plugin_left_page object %}
+    </div>
+      <div class="col col-12 col-xl-7">
         <div class="row" style="margin-bottom: 20px">
         <div class="row" style="margin-bottom: 20px">
-            <div class="col col-md-6 col-sm-6 col-xs-12 text-center">
-                <div style="margin-left: 30px">
-                    <h2 class="h4">{% trans "Front" %}</h2>
-                    {% include 'dcim/inc/rack_elevation.html' with object=object.rack face='front' %}
-                </div>
+          <div class="col col-md-6 col-sm-6 col-xs-12 text-center">
+            <div style="margin-left: 30px">
+              <h2 class="h4">{% trans "Front" %}</h2>
+              {% include 'dcim/inc/rack_elevation.html' with object=object.rack face='front' %}
             </div>
             </div>
-            <div class="col col-md-6 col-sm-6 col-xs-12 text-center">
-                <div style="margin-left: -30px">
-                    <h2 class="h4">{% trans "Rear" %}</h2>
-                    {% include 'dcim/inc/rack_elevation.html' with object=object.rack face='rear' %}
-                </div>
+          </div>
+          <div class="col col-md-6 col-sm-6 col-xs-12 text-center">
+            <div style="margin-left: -30px">
+              <h2 class="h4">{% trans "Rear" %}</h2>
+              {% include 'dcim/inc/rack_elevation.html' with object=object.rack face='rear' %}
             </div>
             </div>
+          </div>
         </div>
         </div>
         {% plugin_right_page object %}
         {% plugin_right_page object %}
-    </div>
-</div>
-<div class="row">
+      </div>
+  </div>
+  <div class="row">
     <div class="col col-md-12">
     <div class="col col-md-12">
-        {% plugin_full_width_page object %}
+      {% plugin_full_width_page object %}
     </div>
     </div>
-</div>
+  </div>
 {% endblock %}
 {% endblock %}