Procházet zdrojové kódy

11969 airflow (#16967)

* 11960 Add airflow

* 11960 Add airflow

* 11960 fix tests

* 11960 fix racktype form

* 11969 different choices type

* 11969 update docs

* 11969 fix racktype copy

* 11969 fix

* Misc cleanup & reordering of form fields

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson před 1 rokem
rodič
revize
e62a42286a

+ 4 - 0
docs/models/dcim/moduletype.md

@@ -39,3 +39,7 @@ An alternative part number to uniquely identify the module type.
 ### Weight
 
 The numeric weight of the module, including a unit designation (e.g. 3 kilograms or 1 pound).
+
+### Airflow
+
+The direction in which air circulates through the device chassis for cooling.

+ 3 - 0
docs/models/dcim/racktype.md

@@ -54,4 +54,7 @@ The maximum total weight capacity for all installed devices, inclusive of the ra
 
 If selected, the rack's elevation will display unit 1 at the top of the rack. (Most racks use ascending numbering, with unit 1 assigned to the bottommost position.)
 
+### Airflow
+
+The direction in which air circulates through the rack for cooling.
 

+ 18 - 4
netbox/dcim/api/serializers_/devicetypes.py

@@ -62,13 +62,27 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
 
 
 class ModuleTypeSerializer(NetBoxModelSerializer):
-    manufacturer = ManufacturerSerializer(nested=True)
-    weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
+    manufacturer = ManufacturerSerializer(
+        nested=True
+    )
+    weight_unit = ChoiceField(
+        choices=WeightUnitChoices,
+        allow_blank=True,
+        required=False,
+        allow_null=True
+    )
+    airflow = ChoiceField(
+        choices=ModuleAirflowChoices,
+        allow_blank=True,
+        required=False,
+        allow_null=True
+    )
 
     class Meta:
         model = ModuleType
         fields = [
-            'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit',
-            'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'part_number', 'airflow',
+            'weight', 'weight_unit', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated',
         ]
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'description')

+ 13 - 3
netbox/dcim/api/serializers_/racks.py

@@ -64,14 +64,19 @@ class RackTypeSerializer(RackBaseSerializer):
     manufacturer = ManufacturerSerializer(
         nested=True
     )
+    airflow = ChoiceField(
+        choices=RackAirflowChoices,
+        allow_blank=True,
+        required=False
+    )
 
     class Meta:
         model = RackType
         fields = [
             'id', 'url', 'display_url', 'display', 'manufacturer', 'name', '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', 'custom_fields',
-            'created', 'last_updated',
+            'max_weight', 'weight_unit', 'mounting_depth', 'airflow', 'description', 'comments', 'tags',
+            'custom_fields', 'created', 'last_updated',
         ]
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'name', 'slug', 'description')
 
@@ -95,6 +100,11 @@ class RackSerializer(RackBaseSerializer):
         choices=RackStatusChoices,
         required=False
     )
+    airflow = ChoiceField(
+        choices=RackAirflowChoices,
+        allow_blank=True,
+        required=False
+    )
     role = RackRoleSerializer(
         nested=True,
         required=False,
@@ -124,7 +134,7 @@ class RackSerializer(RackBaseSerializer):
             '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',
             'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
-            'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
+            'airflow', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
             'powerfeed_count',
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')

+ 30 - 0
netbox/dcim/choices.py

@@ -127,6 +127,17 @@ class RackElevationDetailRenderChoices(ChoiceSet):
     )
 
 
+class RackAirflowChoices(ChoiceSet):
+
+    AIRFLOW_FRONT_TO_REAR = 'front-to-rear'
+    AIRFLOW_REAR_TO_FRONT = 'rear-to-front'
+
+    CHOICES = (
+        (AIRFLOW_FRONT_TO_REAR, _('Front to rear')),
+        (AIRFLOW_REAR_TO_FRONT, _('Rear to front')),
+    )
+
+
 #
 # DeviceTypes
 #
@@ -224,6 +235,25 @@ class ModuleStatusChoices(ChoiceSet):
     ]
 
 
+class ModuleAirflowChoices(ChoiceSet):
+
+    AIRFLOW_FRONT_TO_REAR = 'front-to-rear'
+    AIRFLOW_REAR_TO_FRONT = 'rear-to-front'
+    AIRFLOW_LEFT_TO_RIGHT = 'left-to-right'
+    AIRFLOW_RIGHT_TO_LEFT = 'right-to-left'
+    AIRFLOW_SIDE_TO_REAR = 'side-to-rear'
+    AIRFLOW_PASSIVE = 'passive'
+
+    CHOICES = (
+        (AIRFLOW_FRONT_TO_REAR, _('Front to rear')),
+        (AIRFLOW_REAR_TO_FRONT, _('Rear to front')),
+        (AIRFLOW_LEFT_TO_RIGHT, _('Left to right')),
+        (AIRFLOW_RIGHT_TO_LEFT, _('Right to left')),
+        (AIRFLOW_SIDE_TO_REAR, _('Side to rear')),
+        (AIRFLOW_PASSIVE, _('Passive')),
+    )
+
+
 #
 # ConsolePorts
 #

+ 4 - 3
netbox/dcim/filtersets.py

@@ -312,7 +312,7 @@ class RackTypeFilterSet(NetBoxModelFilterSet):
         model = RackType
         fields = (
             'id', 'name', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
-            'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
+            'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description',
         )
 
     def search(self, queryset, name, value):
@@ -413,7 +413,8 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
         model = Rack
         fields = (
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
+            'description',
         )
 
     def search(self, queryset, name, value):
@@ -698,7 +699,7 @@ class ModuleTypeFilterSet(NetBoxModelFilterSet):
 
     class Meta:
         model = ModuleType
-        fields = ('id', 'model', 'part_number', 'weight', 'weight_unit', 'description')
+        fields = ('id', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description')
 
     def search(self, queryset, name, value):
         if not value.strip():

+ 22 - 5
netbox/dcim/forms/bulk_edit.py

@@ -268,6 +268,11 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         min_value=1
     )
+    airflow = forms.ChoiceField(
+        label=_('Airflow'),
+        choices=add_blank_choice(RackAirflowChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         min_value=0,
@@ -293,10 +298,8 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
 
     model = RackType
     fieldsets = (
-        FieldSet('manufacturer', 'description', 'form_factor', name=_('Rack Type')),
+        FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', 'airflow', name=_('Rack Type')),
         FieldSet(
-            'width',
-            'u_height',
             InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             'mounting_depth',
@@ -409,6 +412,11 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         min_value=1
     )
+    airflow = forms.ChoiceField(
+        label=_('Airflow'),
+        choices=add_blank_choice(RackAirflowChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         min_value=0,
@@ -437,7 +445,7 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'description', name=_('Rack')),
         FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
         FieldSet(
-            'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
+            'form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'outer_width', 'outer_depth', 'outer_unit',
             'mounting_depth', name=_('Hardware')
         ),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
@@ -563,6 +571,11 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Part number'),
         required=False
     )
+    airflow = forms.ChoiceField(
+        label=_('Airflow'),
+        choices=add_blank_choice(ModuleAirflowChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         min_value=0,
@@ -584,7 +597,11 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
     model = ModuleType
     fieldsets = (
         FieldSet('manufacturer', 'part_number', 'description', name=_('Module Type')),
-        FieldSet('weight', 'weight_unit', name=_('Weight')),
+        FieldSet(
+            'airflow',
+            InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
+            name=_('Chassis')
+        ),
     )
     nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
 

+ 23 - 5
netbox/dcim/forms/bulk_import.py

@@ -206,6 +206,12 @@ class RackTypeImportForm(NetBoxModelImportForm):
         required=False,
         help_text=_('Unit for outer dimensions')
     )
+    airflow = CSVChoiceField(
+        label=_('Airflow'),
+        choices=RackAirflowChoices,
+        required=False,
+        help_text=_('Airflow direction')
+    )
     weight_unit = CSVChoiceField(
         label=_('Weight unit'),
         choices=WeightUnitChoices,
@@ -217,8 +223,8 @@ class RackTypeImportForm(NetBoxModelImportForm):
         model = RackType
         fields = (
             'manufacturer', 'name', '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_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
+            'weight_unit', 'description', 'comments', 'tags',
         )
 
     def __init__(self, data=None, *args, **kwargs):
@@ -273,6 +279,12 @@ class RackImportForm(NetBoxModelImportForm):
         required=False,
         help_text=_('Unit for outer dimensions')
     )
+    airflow = CSVChoiceField(
+        label=_('Airflow'),
+        choices=RackAirflowChoices,
+        required=False,
+        help_text=_('Airflow direction')
+    )
     weight_unit = CSVChoiceField(
         label=_('Weight unit'),
         choices=WeightUnitChoices,
@@ -284,8 +296,8 @@ class RackImportForm(NetBoxModelImportForm):
         model = Rack
         fields = (
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'form_factor', 'serial', 'asset_tag',
-            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight',
-            'max_weight', 'weight_unit', 'description', 'comments', 'tags',
+            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow',
+            'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
         )
 
     def __init__(self, data=None, *args, **kwargs):
@@ -400,6 +412,12 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
         queryset=Manufacturer.objects.all(),
         to_field_name='name'
     )
+    airflow = CSVChoiceField(
+        label=_('Airflow'),
+        choices=ModuleAirflowChoices,
+        required=False,
+        help_text=_('Airflow direction')
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         required=False,
@@ -414,7 +432,7 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
 
     class Meta:
         model = ModuleType
-        fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments', 'tags']
+        fields = ['manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'comments', 'tags']
 
 
 class DeviceRoleImportForm(NetBoxModelImportForm):

+ 13 - 3
netbox/dcim/forms/filtersets.py

@@ -267,6 +267,11 @@ class RackBaseFilterForm(NetBoxModelFilterSetForm):
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
     )
+    airflow = forms.MultipleChoiceField(
+        label=_('Airflow'),
+        choices=add_blank_choice(RackAirflowChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         required=False,
@@ -288,7 +293,7 @@ class RackTypeFilterForm(RackBaseFilterForm):
     model = RackType
     fieldsets = (
         FieldSet('q', 'filter_id', 'tag'),
-        FieldSet('form_factor', 'width', 'u_height', name=_('Rack Type')),
+        FieldSet('form_factor', 'width', 'u_height', 'airflow', name=_('Rack Type')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
     )
@@ -308,7 +313,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, RackBaseFilterFo
         FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
         FieldSet('status', 'role_id', 'serial', 'asset_tag', name=_('Rack')),
-        FieldSet('form_factor', 'width', 'u_height', name=_('Rack Type')),
+        FieldSet('form_factor', 'width', 'u_height', 'airflow', name=_('Rack Type')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
         FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
@@ -578,7 +583,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
     model = ModuleType
     fieldsets = (
         FieldSet('q', 'filter_id', 'tag'),
-        FieldSet('manufacturer_id', 'part_number', name=_('Hardware')),
+        FieldSet('manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
         FieldSet(
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'pass_through_ports', name=_('Components')
@@ -638,6 +643,11 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
         )
     )
     tag = TagFilterField(model)
+    airflow = forms.MultipleChoiceField(
+        label=_('Airflow'),
+        choices=add_blank_choice(ModuleAirflowChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         required=False

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

@@ -211,7 +211,7 @@ class RackTypeForm(NetBoxModelForm):
     slug = SlugField()
 
     fieldsets = (
-        FieldSet('manufacturer', 'name', 'slug', 'description', 'form_factor', 'tags', name=_('Rack Type')),
+        FieldSet('manufacturer', 'name', 'slug', 'description', 'form_factor', 'airflow', 'tags', name=_('Rack Type')),
         FieldSet(
             'width', 'u_height',
             InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
@@ -226,7 +226,7 @@ class RackTypeForm(NetBoxModelForm):
         fields = [
             'manufacturer', 'name', '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',
+            'airflow', 'description', 'comments', 'tags',
         ]
 
 
@@ -268,8 +268,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
         fields = [
             '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',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
-            'comments', 'tags',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
+            'description', 'comments', 'tags',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -290,7 +290,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
             self.fieldsets = (
                 *self.fieldsets,
                 FieldSet(
-                    'form_factor', 'width', 'starting_unit', 'u_height',
+                    'form_factor', 'width', 'starting_unit', 'u_height', 'airflow',
                     InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
                     InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
                     'mounting_depth', 'desc_units', name=_('Dimensions')
@@ -398,13 +398,14 @@ class ModuleTypeForm(NetBoxModelForm):
 
     fieldsets = (
         FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
-        FieldSet('weight', 'weight_unit', name=_('Weight'))
+        FieldSet('airflow', 'weight', 'weight_unit', name=_('Chassis'))
     )
 
     class Meta:
         model = ModuleType
         fields = [
-            'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description', 'comments', 'tags',
+            'manufacturer', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description',
+            'comments', 'tags',
         ]
 
 

+ 28 - 0
netbox/dcim/migrations/0189_moduletype_airflow_rack_airflow_racktype_airflow.py

@@ -0,0 +1,28 @@
+# Generated by Django 5.0.7 on 2024-07-25 07:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0188_racktype'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='moduletype',
+            name='airflow',
+            field=models.CharField(blank=True, max_length=50),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='airflow',
+            field=models.CharField(blank=True, max_length=50),
+        ),
+        migrations.AddField(
+            model_name='racktype',
+            name='airflow',
+            field=models.CharField(blank=True, max_length=50),
+        ),
+    ]

+ 7 - 1
netbox/dcim/models/devices.py

@@ -388,8 +388,14 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
         blank=True,
         help_text=_('Discrete part number (optional)')
     )
+    airflow = models.CharField(
+        verbose_name=_('airflow'),
+        max_length=50,
+        choices=ModuleAirflowChoices,
+        blank=True
+    )
 
-    clone_fields = ('manufacturer', 'weight', 'weight_unit',)
+    clone_fields = ('manufacturer', 'weight', 'weight_unit', 'airflow')
     prerequisite_models = (
         'dcim.Manufacturer',
     )

+ 12 - 6
netbox/dcim/models/racks.py

@@ -53,6 +53,12 @@ class RackBase(WeightMixin, PrimaryModel):
         verbose_name=_('width'),
         help_text=_('Rail-to-rail width')
     )
+    airflow = models.CharField(
+        verbose_name=_('airflow'),
+        max_length=50,
+        choices=RackAirflowChoices,
+        blank=True
+    )
 
     # Numbering
     u_height = models.PositiveSmallIntegerField(
@@ -232,10 +238,10 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
     Each Rack is assigned to a Site and (optionally) a Location.
     """
     # Fields which cannot be set locally if a RackType is assigned
-    RACKTYPE_FIELDS = [
-        'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
-        'mounting_depth', 'weight', 'weight_unit', 'max_weight'
-    ]
+    RACKTYPE_FIELDS = (
+        'form_factor', 'width', 'airflow', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
+        'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
+    )
 
     rack_type = models.ForeignKey(
         to='dcim.RackType',
@@ -316,8 +322,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
     )
 
     clone_fields = (
-        'site', 'location', 'tenant', 'status', 'role', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width',
-        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
+        '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',
     )
     prerequisite_models = (
         'dcim.Site',

+ 1 - 1
netbox/dcim/tables/modules.py

@@ -40,7 +40,7 @@ class ModuleTypeTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = ModuleType
         fields = (
-            'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'description', 'comments', 'tags',
+            'pk', 'id', 'model', 'manufacturer', 'part_number', 'airflow', 'weight', 'description', 'comments', 'tags',
         )
         default_columns = (
             'pk', 'model', 'manufacturer', 'part_number',

+ 3 - 3
netbox/dcim/tables/racks.py

@@ -92,8 +92,8 @@ class RackTypeTable(NetBoxTable):
         model = RackType
         fields = (
             'pk', 'id', 'name', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_depth', 'mounting_depth', 'weight', 'max_weight', 'description', 'comments', 'tags', 'created',
-            'last_updated',
+            'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description', 'comments', 'tags',
+            'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'manufacturer', 'type', 'u_height', 'description',
@@ -171,7 +171,7 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
         fields = (
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial',
             'asset_tag', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width', 'outer_depth',
-            'mounting_depth', 'weight', 'max_weight', 'comments', 'device_count', 'get_utilization',
+            'mounting_depth', 'airflow', 'weight', 'max_weight', 'comments', 'device_count', 'get_utilization',
             'get_power_utilization', 'description', 'contacts', 'tags', 'created', 'last_updated',
         )
         default_columns = (

+ 6 - 0
netbox/templates/dcim/moduletype.html

@@ -26,6 +26,12 @@
             <th scope="row">{% trans "Description" %}</th>
             <td>{{ object.description|placeholder }}</td>
           </tr>
+          <tr>
+              <th scope="row">{% trans "Airflow" %}</th>
+              <td>
+                  {{ object.get_airflow_display|placeholder }}
+              </td>
+          </tr>
           <tr>
             <th scope="row">{% trans "Weight" %}</th>
             <td>

+ 4 - 0
netbox/templates/dcim/rack.html

@@ -61,6 +61,10 @@
             <th scope="row">{% trans "Asset Tag" %}</th>
             <td class="font-monospace">{{ object.asset_tag|placeholder }}</td>
           </tr>
+          <tr>
+            <th scope="row">{% trans "Airflow" %}</th>
+            <td>{{ object.get_airflow_display|placeholder }}</td>
+          </tr>
           <tr>
             <th scope="row">{% trans "Space Utilization" %}</th>
             <td>{% utilization_graph object.get_utilization %}</td>

+ 4 - 0
netbox/templates/dcim/racktype.html

@@ -24,6 +24,10 @@
             <th scope="row">{% trans "Description" %}</th>
             <td>{{ object.description|placeholder }}</td>
           </tr>
+          <tr>
+            <th scope="row">{% trans "Airflow" %}</th>
+            <td>{{ object.get_airflow_display|placeholder }}</td>
+          </tr>
         </table>
       </div>
       {% include 'dcim/inc/panels/racktype_dimensions.html' %}