Procházet zdrojové kódy

18417 Add outer_height to racks (#18940)

* 18417 add rack outer height

* 18417 add rack outer height

* 18417 fix tests

* 18417 fix validation message

* Update netbox/dcim/filtersets.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/filtersets.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* 16224 review changes

* 16224 review changes

* 16224 update table display

* 18417 use TemplateColumn

* 18417 review changes

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson před 10 měsíci
rodič
revize
7a71c7b8f8

+ 1 - 1
docs/models/dcim/racktype.md

@@ -40,7 +40,7 @@ The number of the numerically lowest unit in the rack. This value defaults to on
 
 
 ### Outer Dimensions
 ### Outer Dimensions
 
 
-The external width and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
+The external width, height and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
 
 
 ### Mounting Depth
 ### Mounting Depth
 
 

+ 5 - 5
netbox/dcim/api/serializers_/racks.py

@@ -70,8 +70,8 @@ class RackTypeSerializer(RackBaseSerializer):
         model = RackType
         model = RackType
         fields = [
         fields = [
             'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
             'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
-            'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
-            'max_weight', 'weight_unit', 'mounting_depth', 'description', 'comments', 'tags',
+            'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
+            'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'comments', 'tags',
             'custom_fields', 'created', 'last_updated',
             'custom_fields', 'created', 'last_updated',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description')
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description')
@@ -129,9 +129,9 @@ class RackSerializer(RackBaseSerializer):
         fields = [
         fields = [
             'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status',
             'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status',
             'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'weight',
             'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'weight',
-            'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
-            'airflow', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
-            'powerfeed_count',
+            'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
+            'mounting_depth', 'airflow', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated', 'device_count', 'powerfeed_count',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
 
 

+ 4 - 4
netbox/dcim/filtersets.py

@@ -313,8 +313,8 @@ class RackTypeFilterSet(NetBoxModelFilterSet):
     class Meta:
     class Meta:
         model = RackType
         model = RackType
         fields = (
         fields = (
-            'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
+            'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
         )
         )
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
@@ -426,8 +426,8 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
         model = Rack
         model = Rack
         fields = (
         fields = (
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
-            'description',
+            'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
+            'weight_unit', 'description',
         )
         )
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):

+ 16 - 8
netbox/dcim/forms/bulk_edit.py

@@ -260,6 +260,11 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         required=False,
         min_value=1
         min_value=1
     )
     )
+    outer_height = forms.IntegerField(
+        label=_('Outer height'),
+        required=False,
+        min_value=1
+    )
     outer_depth = forms.IntegerField(
     outer_depth = forms.IntegerField(
         label=_('Outer depth'),
         label=_('Outer depth'),
         required=False,
         required=False,
@@ -302,7 +307,7 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
     fieldsets = (
     fieldsets = (
         FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
         FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
         FieldSet(
         FieldSet(
-            InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+            InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             'mounting_depth',
             'mounting_depth',
             name=_('Dimensions')
             name=_('Dimensions')
@@ -310,7 +315,7 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
     )
     )
     nullable_fields = (
     nullable_fields = (
-        'outer_width', 'outer_depth', 'outer_unit', 'weight',
+        'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
         'max_weight', 'weight_unit', 'description', 'comments',
         'max_weight', 'weight_unit', 'description', 'comments',
     )
     )
 
 
@@ -404,6 +409,11 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         required=False,
         min_value=1
         min_value=1
     )
     )
+    outer_height = forms.IntegerField(
+        label=_('Outer height'),
+        required=False,
+        min_value=1
+    )
     outer_depth = forms.IntegerField(
     outer_depth = forms.IntegerField(
         label=_('Outer depth'),
         label=_('Outer depth'),
         required=False,
         required=False,
@@ -451,15 +461,13 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
     fieldsets = (
     fieldsets = (
         FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')),
         FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')),
         FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
         FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
-        FieldSet(
-            'form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'outer_width', 'outer_depth', 'outer_unit',
-            'mounting_depth', name=_('Hardware')
-        ),
+        FieldSet('outer_width', 'outer_height', 'outer_depth', 'outer_unit', name=_('Outer Dimensions')),
+        FieldSet('form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'mounting_depth', name=_('Hardware')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
     )
     )
     nullable_fields = (
     nullable_fields = (
-        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
-        'max_weight', 'weight_unit', 'description', 'comments',
+        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_height', 'outer_depth',
+        'outer_unit', 'weight', 'max_weight', 'weight_unit', 'description', 'comments',
     )
     )
 
 
 
 

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

@@ -222,7 +222,7 @@ class RackTypeImportForm(NetBoxModelImportForm):
         model = RackType
         model = RackType
         fields = (
         fields = (
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
-            'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+            'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
             'weight_unit', 'description', 'comments', 'tags',
             'weight_unit', 'description', 'comments', 'tags',
         )
         )
 
 
@@ -307,7 +307,7 @@ class RackImportForm(NetBoxModelImportForm):
         model = Rack
         model = Rack
         fields = (
         fields = (
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'rack_type', 'form_factor', 'serial',
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'rack_type', 'form_factor', 'serial',
-            'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
+            'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
             'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
             'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
         )
         )
 
 

+ 7 - 6
netbox/dcim/forms/model_forms.py

@@ -226,7 +226,7 @@ class RackTypeForm(NetBoxModelForm):
         FieldSet('manufacturer', 'model', 'slug', 'description', 'form_factor', 'tags', name=_('Rack Type')),
         FieldSet('manufacturer', 'model', 'slug', 'description', 'form_factor', 'tags', name=_('Rack Type')),
         FieldSet(
         FieldSet(
             'width', 'u_height',
             'width', 'u_height',
-            InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+            InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             'mounting_depth', name=_('Dimensions')
             'mounting_depth', name=_('Dimensions')
         ),
         ),
@@ -237,8 +237,8 @@ class RackTypeForm(NetBoxModelForm):
         model = RackType
         model = RackType
         fields = [
         fields = [
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
-            'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
-            'description', 'comments', 'tags',
+            'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+            'weight_unit', 'description', 'comments', 'tags',
         ]
         ]
 
 
 
 
@@ -283,8 +283,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
         fields = [
         fields = [
             'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
             'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
             'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
             'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
-            'description', 'comments', 'tags',
+            'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
+            'weight_unit', 'description', 'comments', 'tags',
         ]
         ]
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
@@ -306,7 +306,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
                 *self.fieldsets,
                 *self.fieldsets,
                 FieldSet(
                 FieldSet(
                     'form_factor', 'width', 'starting_unit', 'u_height',
                     'form_factor', 'width', 'starting_unit', 'u_height',
-                    InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+                    InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit',
+                                 label=_('Outer Dimensions')),
                     InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
                     InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
                     'mounting_depth', 'desc_units', name=_('Dimensions')
                     'mounting_depth', 'desc_units', name=_('Dimensions')
                 ),
                 ),

+ 23 - 0
netbox/dcim/migrations/0203_add_rack_outer_height.py

@@ -0,0 +1,23 @@
+# Generated by Django 5.2b1 on 2025-03-18 15:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0202_location_comments_region_comments_sitegroup_comments'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='rack',
+            name='outer_height',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='racktype',
+            name='outer_height',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+    ]

+ 17 - 10
netbox/dcim/models/racks.py

@@ -73,6 +73,12 @@ class RackBase(WeightMixin, PrimaryModel):
         null=True,
         null=True,
         help_text=_('Outer dimension of rack (width)')
         help_text=_('Outer dimension of rack (width)')
     )
     )
+    outer_height = models.PositiveSmallIntegerField(
+        verbose_name=_('outer height'),
+        blank=True,
+        null=True,
+        help_text=_('Outer dimension of rack (height)')
+    )
     outer_depth = models.PositiveSmallIntegerField(
     outer_depth = models.PositiveSmallIntegerField(
         verbose_name=_('outer depth'),
         verbose_name=_('outer depth'),
         blank=True,
         blank=True,
@@ -140,7 +146,7 @@ class RackType(RackBase):
     )
     )
 
 
     clone_fields = (
     clone_fields = (
-        'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
+        'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
         'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
         'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
     )
     )
     prerequisite_models = (
     prerequisite_models = (
@@ -173,8 +179,8 @@ class RackType(RackBase):
         super().clean()
         super().clean()
 
 
         # Validate outer dimensions and unit
         # Validate outer dimensions and unit
-        if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
-            raise ValidationError(_("Must specify a unit when setting an outer width/depth"))
+        if any([self.outer_width, self.outer_depth, self.outer_height]) and not self.outer_unit:
+            raise ValidationError(_("Must specify a unit when setting an outer dimension"))
 
 
         # Validate max_weight and weight_unit
         # Validate max_weight and weight_unit
         if self.max_weight and not self.weight_unit:
         if self.max_weight and not self.weight_unit:
@@ -188,7 +194,7 @@ class RackType(RackBase):
             self._abs_max_weight = None
             self._abs_max_weight = None
 
 
         # Clear unit if outer width & depth are not set
         # Clear unit if outer width & depth are not set
-        if self.outer_width is None and self.outer_depth is None:
+        if not any([self.outer_width, self.outer_depth, self.outer_height]):
             self.outer_unit = None
             self.outer_unit = None
 
 
         super().save(*args, **kwargs)
         super().save(*args, **kwargs)
@@ -235,8 +241,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
     """
     """
     # Fields which cannot be set locally if a RackType is assigned
     # Fields which cannot be set locally if a RackType is assigned
     RACKTYPE_FIELDS = (
     RACKTYPE_FIELDS = (
-        'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
-        'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
+        'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
+        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
     )
     )
 
 
     form_factor = models.CharField(
     form_factor = models.CharField(
@@ -329,7 +335,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
 
 
     clone_fields = (
     clone_fields = (
         'site', 'location', 'tenant', 'status', 'role', 'form_factor', 'width', 'airflow', 'u_height', 'desc_units',
         'site', 'location', 'tenant', 'status', 'role', 'form_factor', 'width', 'airflow', 'u_height', 'desc_units',
-        'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
+        'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+        'weight_unit',
     )
     )
     prerequisite_models = (
     prerequisite_models = (
         'dcim.Site',
         'dcim.Site',
@@ -364,8 +371,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
             raise ValidationError(_("Assigned location must belong to parent site ({site}).").format(site=self.site))
             raise ValidationError(_("Assigned location must belong to parent site ({site}).").format(site=self.site))
 
 
         # Validate outer dimensions and unit
         # Validate outer dimensions and unit
-        if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
-            raise ValidationError(_("Must specify a unit when setting an outer width/depth"))
+        if any([self.outer_width, self.outer_depth, self.outer_height]) and not self.outer_unit:
+            raise ValidationError(_("Must specify a unit when setting an outer dimension"))
 
 
         # Validate max_weight and weight_unit
         # Validate max_weight and weight_unit
         if self.max_weight and not self.weight_unit:
         if self.max_weight and not self.weight_unit:
@@ -414,7 +421,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
             self._abs_max_weight = None
             self._abs_max_weight = None
 
 
         # Clear unit if outer width & depth are not set
         # Clear unit if outer width & depth are not set
-        if self.outer_width is None and self.outer_depth is None:
+        if not any([self.outer_width, self.outer_depth, self.outer_height]):
             self.outer_unit = None
             self.outer_unit = None
 
 
         super().save(*args, **kwargs)
         super().save(*args, **kwargs)

+ 22 - 13
netbox/dcim/tables/racks.py

@@ -5,7 +5,7 @@ from django_tables2.utils import Accessor
 from dcim.models import Rack, RackReservation, RackRole, RackType
 from dcim.models import Rack, RackReservation, RackRole, RackType
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
-from .template_code import WEIGHT
+from .template_code import OUTER_UNIT, WEIGHT
 
 
 __all__ = (
 __all__ = (
     'RackTable',
     'RackTable',
@@ -62,12 +62,16 @@ class RackTypeTable(NetBoxTable):
         template_code="{{ value }}U",
         template_code="{{ value }}U",
         verbose_name=_('Height')
         verbose_name=_('Height')
     )
     )
-    outer_width = tables.TemplateColumn(
-        template_code="{{ record.outer_width }} {{ record.outer_unit }}",
+    outer_width = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Width')
         verbose_name=_('Outer Width')
     )
     )
-    outer_depth = tables.TemplateColumn(
-        template_code="{{ record.outer_depth }} {{ record.outer_unit }}",
+    outer_height = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
+        verbose_name=_('Outer Height')
+    )
+    outer_depth = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Depth')
         verbose_name=_('Outer Depth')
     )
     )
     weight = columns.TemplateColumn(
     weight = columns.TemplateColumn(
@@ -96,8 +100,8 @@ class RackTypeTable(NetBoxTable):
         model = RackType
         model = RackType
         fields = (
         fields = (
             'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
             'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description', 'comments',
-            'instance_count', 'tags', 'created', 'last_updated',
+            'outer_height', 'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description',
+            'comments', 'instance_count', 'tags', 'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'instance_count',
             'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'instance_count',
@@ -159,12 +163,16 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
     tags = columns.TagColumn(
     tags = columns.TagColumn(
         url_name='dcim:rack_list'
         url_name='dcim:rack_list'
     )
     )
-    outer_width = tables.TemplateColumn(
-        template_code="{{ record.outer_width }} {{ record.outer_unit }}",
+    outer_width = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Width')
         verbose_name=_('Outer Width')
     )
     )
-    outer_depth = tables.TemplateColumn(
-        template_code="{{ record.outer_depth }} {{ record.outer_unit }}",
+    outer_height = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
+        verbose_name=_('Outer Height')
+    )
+    outer_depth = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Depth')
         verbose_name=_('Outer Depth')
     )
     )
     weight = columns.TemplateColumn(
     weight = columns.TemplateColumn(
@@ -183,8 +191,9 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
         fields = (
         fields = (
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role',
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role',
             'rack_type', 'serial', 'asset_tag', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
             'rack_type', 'serial', 'asset_tag', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'comments', 'device_count',
-            'get_utilization', 'get_power_utilization', 'description', 'contacts', 'tags', 'created', 'last_updated',
+            'outer_height', 'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'comments',
+            'device_count', 'get_utilization', 'get_power_utilization', 'description', 'contacts',
+            'tags', 'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'rack_type', 'u_height',
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'rack_type', 'u_height',

+ 5 - 0
netbox/dcim/tables/template_code.py

@@ -109,6 +109,11 @@ LOCATION_BUTTONS = """
 </a>
 </a>
 """
 """
 
 
+OUTER_UNIT = """
+{% load helpers %}
+{% if value %}{{ value }} {{ record.outer_unit }}{% endif %}
+"""
+
 #
 #
 # Device component templatebuttons
 # Device component templatebuttons
 #
 #

+ 16 - 0
netbox/dcim/tests/test_filtersets.py

@@ -585,6 +585,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=1,
                 starting_unit=1,
                 desc_units=False,
                 desc_units=False,
                 outer_width=100,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=100,
                 mounting_depth=100,
@@ -603,6 +604,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=2,
                 starting_unit=2,
                 desc_units=False,
                 desc_units=False,
                 outer_width=200,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=200,
                 mounting_depth=200,
@@ -621,6 +623,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=3,
                 starting_unit=3,
                 desc_units=True,
                 desc_units=True,
                 outer_width=300,
                 outer_width=300,
+                outer_height=300,
                 outer_depth=300,
                 outer_depth=300,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 mounting_depth=300,
                 mounting_depth=300,
@@ -681,6 +684,10 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'outer_width': [100, 200]}
         params = {'outer_width': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_outer_height(self):
+        params = {'outer_height': [100, 200]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_outer_depth(self):
     def test_outer_depth(self):
         params = {'outer_depth': [100, 200]}
         params = {'outer_depth': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -764,6 +771,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=1,
                 starting_unit=1,
                 desc_units=False,
                 desc_units=False,
                 outer_width=100,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=100,
                 mounting_depth=100,
@@ -782,6 +790,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=2,
                 starting_unit=2,
                 desc_units=False,
                 desc_units=False,
                 outer_width=200,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=200,
                 mounting_depth=200,
@@ -831,6 +840,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=42,
                 u_height=42,
                 desc_units=False,
                 desc_units=False,
                 outer_width=100,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 weight=10,
                 weight=10,
@@ -854,6 +864,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=43,
                 u_height=43,
                 desc_units=False,
                 desc_units=False,
                 outer_width=200,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 weight=20,
                 weight=20,
@@ -877,6 +888,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=44,
                 u_height=44,
                 desc_units=True,
                 desc_units=True,
                 outer_width=300,
                 outer_width=300,
+                outer_height=300,
                 outer_depth=300,
                 outer_depth=300,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 weight=30,
                 weight=30,
@@ -957,6 +969,10 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'outer_width': [100, 200]}
         params = {'outer_width': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_outer_height(self):
+        params = {'outer_height': [100, 200]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_outer_depth(self):
     def test_outer_depth(self):
         params = {'outer_depth': [100, 200]}
         params = {'outer_depth': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 10 - 0
netbox/templates/dcim/inc/panels/racktype_dimensions.html

@@ -24,6 +24,16 @@
         {% endif %}
         {% endif %}
       </td>
       </td>
     </tr>
     </tr>
+    <tr>
+      <th scope="row">{% trans "Outer Height" %}</th>
+      <td>
+        {% if object.outer_height %}
+          {{ object.outer_height }} {{ object.get_outer_unit_display }}
+        {% else %}
+          {{ ''|placeholder }}
+        {% endif %}
+      </td>
+    </tr>
     <tr>
     <tr>
       <th scope="row">{% trans "Outer Depth" %}</th>
       <th scope="row">{% trans "Outer Depth" %}</th>
       <td>
       <td>