Sfoglia il codice sorgente

add cooling to ModuleType

Arthur 1 settimana fa
parent
commit
fd77033e26

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

@@ -75,6 +75,10 @@ The numeric weight of the module, including a unit designation (e.g. 3 kilograms
 
 The direction in which air circulates through the device chassis for cooling.
 
+### Cooling Method
+
+The cooling method employed by modules of this type (air, liquid, hybrid, or immersion). This is useful for liquid-cooled modules such as direct-to-chip accelerator (OAM) modules or liquid-cooled line cards.
+
 ### Profile
 
 The assigned [profile](./moduletypeprofile.md) for the type of module. Profiles can be used to classify module types by function (e.g. power supply, hard disk, etc.), and they support the addition of user-configurable attributes on module types. The assignment of a module type to a profile is optional.

+ 3 - 1
netbox/dcim/api/serializers_/devicetypes.py

@@ -98,6 +98,7 @@ class ModuleTypeSerializer(PrimaryModelSerializer):
         required=False,
         allow_null=True
     )
+    cooling_method = ChoiceField(choices=CoolingMethodChoices, allow_blank=True, required=False, allow_null=True)
     attributes = AttributesField(
         source='attribute_data',
         required=False,
@@ -119,7 +120,8 @@ class ModuleTypeSerializer(PrimaryModelSerializer):
         model = ModuleType
         fields = [
             'id', 'url', 'display_url', 'display', 'profile', 'manufacturer', 'model', 'part_number', 'airflow',
-            'weight', 'weight_unit', 'description', 'attributes', 'owner', 'comments', 'tags', 'custom_fields',
+            'cooling_method', 'weight', 'weight_unit', 'description', 'attributes', 'owner', 'comments', 'tags',
+            'custom_fields',
             'created', 'last_updated', 'module_count', 'console_port_template_count',
             'console_server_port_template_count', 'power_port_template_count', 'power_outlet_template_count',
             'interface_template_count', 'front_port_template_count', 'rear_port_template_count',

+ 1 - 1
netbox/dcim/filtersets.py

@@ -889,7 +889,7 @@ class ModuleTypeFilterSet(AttributeFiltersMixin, PrimaryModelFilterSet):
     class Meta:
         model = ModuleType
         fields = (
-            'id', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description',
+            'id', 'model', 'part_number', 'airflow', 'cooling_method', 'weight', 'weight_unit', 'description',
 
             # Counters
             'console_port_template_count',

+ 9 - 2
netbox/dcim/forms/bulk_edit.py

@@ -633,6 +633,11 @@ class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
         choices=add_blank_choice(ModuleAirflowChoices),
         required=False
     )
+    cooling_method = forms.ChoiceField(
+        label=_('Cooling method'),
+        choices=add_blank_choice(CoolingMethodChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         min_value=0,
@@ -649,12 +654,14 @@ class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
     fieldsets = (
         FieldSet('profile', 'manufacturer', 'part_number', 'description', name=_('Module Type')),
         FieldSet(
-            'airflow',
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             name=_('Chassis')
         ),
+        FieldSet('cooling_method', 'airflow', name=_('Cooling')),
+    )
+    nullable_fields = (
+        'part_number', 'weight', 'weight_unit', 'profile', 'description', 'comments', 'cooling_method',
     )
-    nullable_fields = ('part_number', 'weight', 'weight_unit', 'profile', 'description', 'comments')
 
 
 class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):

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

@@ -505,6 +505,12 @@ class ModuleTypeImportForm(PrimaryModelImportForm):
         required=False,
         help_text=_('Airflow direction')
     )
+    cooling_method = CSVChoiceField(
+        label=_('Cooling method'),
+        choices=CoolingMethodChoices,
+        required=False,
+        help_text=_('Cooling method')
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         required=False,
@@ -525,8 +531,8 @@ class ModuleTypeImportForm(PrimaryModelImportForm):
     class Meta:
         model = ModuleType
         fields = [
-            'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'profile',
-            'attribute_data', 'owner', 'comments', 'tags',
+            'manufacturer', 'model', 'part_number', 'description', 'cooling_method', 'airflow', 'weight', 'weight_unit',
+            'profile', 'attribute_data', 'owner', 'comments', 'tags',
         ]
 
     def clean(self):

+ 7 - 2
netbox/dcim/forms/filtersets.py

@@ -731,9 +731,9 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
     fieldsets = (
         FieldSet('q', 'filter_id', 'tag'),
         FieldSet(
-            'profile_id', 'manufacturer_id', 'part_number', 'module_count',
-            'airflow', name=_('Hardware')
+            'profile_id', 'manufacturer_id', 'part_number', 'module_count', name=_('Hardware')
         ),
+        FieldSet('cooling_method', 'airflow', name=_('Cooling')),
         FieldSet(
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'pass_through_ports', 'module_bays', name=_('Components')
@@ -817,6 +817,11 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
         choices=add_blank_choice(ModuleAirflowChoices),
         required=False
     )
+    cooling_method = forms.MultipleChoiceField(
+        label=_('Cooling method'),
+        choices=add_blank_choice(CoolingMethodChoices),
+        required=False
+    )
     weight = forms.DecimalField(
         label=_('Weight'),
         required=False

+ 4 - 3
netbox/dcim/forms/model_forms.py

@@ -495,15 +495,16 @@ class ModuleTypeForm(PrimaryModelForm):
     def fieldsets(self):
         return [
             FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
-            FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
+            FieldSet('weight', 'weight_unit', name=_('Hardware')),
+            FieldSet('cooling_method', 'airflow', name=_('Cooling')),
             FieldSet('profile', *self.attr_fields, name=_('Profile & Attributes'), html_id='profile-attributes')
         ]
 
     class Meta:
         model = ModuleType
         fields = [
-            'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
-            'owner', 'comments', 'tags',
+            'profile', 'manufacturer', 'model', 'part_number', 'description', 'cooling_method', 'airflow', 'weight',
+            'weight_unit', 'owner', 'comments', 'tags',
         ]
 
     def __init__(self, *args, **kwargs):

+ 6 - 1
netbox/dcim/migrations/0241_device_cooling_method_device_cooling_outlet_count_and_more.py

@@ -1,4 +1,4 @@
-# Generated by Django 6.0.6 on 2026-06-24 18:32
+# Generated by Django 6.0.6 on 2026-06-24 20:03
 
 import django.contrib.postgres.fields
 import django.core.validators
@@ -72,6 +72,11 @@ class Migration(migrations.Migration):
                 to_model="dcim.CoolingPortTemplate",
             ),
         ),
+        migrations.AddField(
+            model_name="moduletype",
+            name="cooling_method",
+            field=models.CharField(blank=True, max_length=50, null=True),
+        ),
         migrations.AddField(
             model_name="moduletype",
             name="cooling_outlet_template_count",

+ 9 - 1
netbox/dcim/models/modules.py

@@ -89,6 +89,13 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
         blank=True,
         null=True
     )
+    cooling_method = models.CharField(
+        verbose_name=_('cooling method'),
+        max_length=50,
+        choices=CoolingMethodChoices,
+        blank=True,
+        null=True
+    )
     attribute_data = models.JSONField(
         blank=True,
         null=True,
@@ -141,7 +148,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
         to_field='module_type'
     )
 
-    clone_fields = ('profile', 'manufacturer', 'weight', 'weight_unit', 'airflow')
+    clone_fields = ('profile', 'manufacturer', 'weight', 'weight_unit', 'airflow', 'cooling_method')
     prerequisite_models = (
         'dcim.Manufacturer',
     )
@@ -202,6 +209,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
             'weight': float(self.weight) if self.weight is not None else None,
             'weight_unit': self.weight_unit,
             'airflow': self.airflow,
+            'cooling_method': self.cooling_method,
             'attribute_data': self.attribute_data,
             'comments': self.comments,
         }

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

@@ -51,6 +51,9 @@ class ModuleTypeTable(PrimaryModelTable):
         linkify=True,
         verbose_name=_('Module Type')
     )
+    cooling_method = columns.ChoiceFieldColumn(
+        verbose_name=_('Cooling Method'),
+    )
     weight = columns.TemplateColumn(
         verbose_name=_('Weight'),
         template_code=WEIGHT,
@@ -71,8 +74,8 @@ class ModuleTypeTable(PrimaryModelTable):
     class Meta(PrimaryModelTable.Meta):
         model = ModuleType
         fields = (
-            'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
-            'attributes', 'module_count', 'comments', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'cooling_method', 'weight',
+            'description', 'attributes', 'module_count', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'model', 'profile', 'manufacturer', 'part_number', 'module_count',

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

@@ -1632,6 +1632,7 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 weight_unit=WeightUnitChoices.UNIT_POUND,
                 description='foobar1',
                 airflow=ModuleAirflowChoices.FRONT_TO_REAR,
+                cooling_method=CoolingMethodChoices.METHOD_LIQUID,
                 profile=module_type_profiles[0],
                 attribute_data={
                     'string': 'string1',
@@ -1648,6 +1649,7 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 weight_unit=WeightUnitChoices.UNIT_POUND,
                 description='foobar2',
                 airflow=ModuleAirflowChoices.REAR_TO_FRONT,
+                cooling_method=CoolingMethodChoices.METHOD_HYBRID,
                 profile=module_type_profiles[1],
                 attribute_data={
                     'string': 'string2',
@@ -1791,6 +1793,10 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'airflow': RackAirflowChoices.FRONT_TO_REAR}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
+    def test_cooling_method(self):
+        params = {'cooling_method': CoolingMethodChoices.METHOD_LIQUID}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
     def test_profile(self):
         profiles = ModuleTypeProfile.objects.filter(name__startswith="Module Type Profile")[:2]
         params = {'profile_id': [profiles[0].pk, profiles[1].pk]}

+ 1 - 0
netbox/dcim/ui/panels.py

@@ -190,6 +190,7 @@ class ModuleTypePanel(panels.ObjectAttributesPanel):
     model = attrs.TextAttr('model', label=_('Model name'))
     part_number = attrs.TextAttr('part_number')
     description = attrs.TextAttr('description')
+    cooling_method = attrs.ChoiceAttr('cooling_method')
     airflow = attrs.ChoiceAttr('airflow')
     weight = attrs.WeightAttr('weight')