Procházet zdrojové kódy

add temperature to cooling feed

Arthur před 1 týdnem
rodič
revize
198579c854

+ 8 - 0
docs/models/dcim/coolingfeed.md

@@ -45,6 +45,14 @@ The coolant flow rate, expressed as a numeric value with a selectable unit (L/mi
 
 The operating pressure of the loop, expressed as a numeric value with a selectable unit (kPa, bar, or PSI).
 
+### Supply / Return Temperature
+
+The supply and return coolant temperatures, each expressed in the selected temperature unit.
+
+### Temperature Unit
+
+The unit (Celsius or Fahrenheit) in which the supply and return temperatures are expressed.
+
 ### Mark Connected
 
 If selected, the cooling feed will be treated as if a cable has been connected.

+ 8 - 1
netbox/dcim/api/serializers_/cooling.py

@@ -85,6 +85,12 @@ class CoolingFeedSerializer(PrimaryModelSerializer, CabledObjectSerializer, Conn
         required=False,
         allow_null=True
     )
+    temperature_unit = ChoiceField(
+        choices=TemperatureUnitChoices,
+        allow_blank=True,
+        required=False,
+        allow_null=True
+    )
     tenant = TenantSerializer(
         nested=True,
         required=False,
@@ -95,7 +101,8 @@ class CoolingFeedSerializer(PrimaryModelSerializer, CabledObjectSerializer, Conn
         model = CoolingFeed
         fields = [
             'id', 'url', 'display_url', 'display', 'cooling_source', 'rack', 'name', 'status', 'type', 'fluid_type',
-            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'mark_connected',
+            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature',
+            'return_temperature', 'temperature_unit', 'mark_connected',
             'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints', 'connected_endpoints_type',
             'connected_endpoints_reachable', 'description', 'tenant', 'owner', 'comments', 'tags', 'custom_fields',
             'created', 'last_updated', '_occupied',

+ 6 - 0
netbox/dcim/filtersets.py

@@ -3324,11 +3324,17 @@ class CoolingFeedFilterSet(PrimaryModelFilterSet, CabledObjectFilterSet, PathEnd
         distinct=False,
         null_value=None
     )
+    temperature_unit = django_filters.MultipleChoiceFilter(
+        choices=TemperatureUnitChoices,
+        distinct=False,
+        null_value=None
+    )
 
     class Meta:
         model = CoolingFeed
         fields = (
             'id', 'name', 'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit',
+            'supply_temperature', 'return_temperature', 'temperature_unit',
             'mark_connected', 'cable_end', 'cable_connector', 'description',
         )
 

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

@@ -1182,6 +1182,20 @@ class CoolingFeedBulkEditForm(PrimaryModelBulkEditForm):
         required=False,
         initial=''
     )
+    supply_temperature = forms.DecimalField(
+        label=_('Supply temperature'),
+        required=False
+    )
+    return_temperature = forms.DecimalField(
+        label=_('Return temperature'),
+        required=False
+    )
+    temperature_unit = forms.ChoiceField(
+        label=_('Temperature unit'),
+        choices=add_blank_choice(TemperatureUnitChoices),
+        required=False,
+        initial=''
+    )
     mark_connected = forms.NullBooleanField(
         label=_('Mark connected'),
         required=False,
@@ -1196,13 +1210,14 @@ class CoolingFeedBulkEditForm(PrimaryModelBulkEditForm):
     fieldsets = (
         FieldSet('cooling_source', 'rack', 'status', 'type', 'fluid_type', 'mark_connected', 'description', 'tenant'),
         FieldSet(
-            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit',
+            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature',
+            'return_temperature', 'temperature_unit',
             name=_('Characteristics')
         ),
     )
     nullable_fields = (
         'rack', 'fluid_type', 'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit',
-        'tenant', 'description', 'comments',
+        'supply_temperature', 'return_temperature', 'tenant', 'description', 'comments',
     )
 
 

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

@@ -2037,13 +2037,19 @@ class CoolingFeedImportForm(PrimaryModelImportForm):
         required=False,
         help_text=_('Unit for pressure')
     )
+    temperature_unit = CSVChoiceField(
+        label=_('Temperature unit'),
+        choices=TemperatureUnitChoices,
+        required=False,
+        help_text=_('Unit for supply/return temperatures')
+    )
 
     class Meta:
         model = CoolingFeed
         fields = (
             'site', 'cooling_source', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'fluid_type',
-            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'tenant',
-            'description', 'owner', 'comments', 'tags',
+            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature',
+            'return_temperature', 'temperature_unit', 'tenant', 'description', 'owner', 'comments', 'tags',
         )
 
     def __init__(self, data=None, *args, **kwargs):

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

@@ -1541,7 +1541,8 @@ class CoolingFeedFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
         FieldSet('region_id', 'site_group_id', 'site_id', 'cooling_source_id', 'rack_id', name=_('Location')),
         FieldSet('status', 'type', 'fluid_type', name=_('Attributes')),
         FieldSet(
-            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', name=_('Characteristics')
+            'cooling_capacity', 'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature',
+            'return_temperature', 'temperature_unit', name=_('Characteristics')
         ),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
         FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
@@ -1619,6 +1620,19 @@ class CoolingFeedFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
         choices=PressureUnitChoices,
         required=False
     )
+    supply_temperature = forms.DecimalField(
+        label=_('Supply temperature'),
+        required=False
+    )
+    return_temperature = forms.DecimalField(
+        label=_('Return temperature'),
+        required=False
+    )
+    temperature_unit = forms.MultipleChoiceField(
+        label=_('Temperature unit'),
+        choices=TemperatureUnitChoices,
+        required=False
+    )
     tag = TagFilterField(model)
 
 

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

@@ -1023,6 +1023,7 @@ class CoolingFeedForm(TenancyForm, PrimaryModelForm):
             'cooling_capacity',
             InlineFields('flow_rate', 'flow_rate_unit', label=_('Flow rate')),
             InlineFields('pressure', 'pressure_unit', label=_('Pressure')),
+            InlineFields('supply_temperature', 'return_temperature', 'temperature_unit', label=_('Temperatures')),
             name=_('Characteristics')
         ),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
@@ -1032,8 +1033,8 @@ class CoolingFeedForm(TenancyForm, PrimaryModelForm):
         model = CoolingFeed
         fields = [
             'cooling_source', 'rack', 'name', 'status', 'type', 'mark_connected', 'fluid_type', 'cooling_capacity',
-            'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'tenant_group', 'tenant', 'description',
-            'owner', 'comments', 'tags',
+            'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature', 'return_temperature',
+            'temperature_unit', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags',
         ]
 
 

+ 9 - 0
netbox/dcim/graphql/filters.py

@@ -970,6 +970,15 @@ class CoolingFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, Primar
     pressure_unit: (
         BaseFilterLookup[Annotated['PressureUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None
     ) = strawberry_django.filter_field()
+    supply_temperature: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+        strawberry_django.filter_field()
+    )
+    return_temperature: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+        strawberry_django.filter_field()
+    )
+    temperature_unit: (
+        BaseFilterLookup[Annotated['TemperatureUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+    ) = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.CoolingOutlet, lookups=True)

+ 42 - 14
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 21:29
+# Generated by Django 6.0.6 on 2026-06-24 21:45
 
 import django.contrib.postgres.fields
 import django.core.validators
@@ -675,19 +675,6 @@ class Migration(migrations.Migration):
                 ),
                 ("description", models.CharField(blank=True, max_length=200)),
                 ("comments", models.TextField(blank=True)),
-                ("name", models.CharField(db_collation="natural_sort", max_length=100)),
-                ("type", models.CharField(max_length=50)),
-                ("status", models.CharField(default="active", max_length=50)),
-                (
-                    "cooling_capacity",
-                    models.DecimalField(
-                        blank=True,
-                        decimal_places=2,
-                        max_digits=10,
-                        null=True,
-                        validators=[django.core.validators.MinValueValidator(0)],
-                    ),
-                ),
                 (
                     "supply_temperature",
                     models.DecimalField(
@@ -716,6 +703,19 @@ class Migration(migrations.Migration):
                         blank=True, decimal_places=4, max_digits=8, null=True
                     ),
                 ),
+                ("name", models.CharField(db_collation="natural_sort", max_length=100)),
+                ("type", models.CharField(max_length=50)),
+                ("status", models.CharField(default="active", max_length=50)),
+                (
+                    "cooling_capacity",
+                    models.DecimalField(
+                        blank=True,
+                        decimal_places=2,
+                        max_digits=10,
+                        null=True,
+                        validators=[django.core.validators.MinValueValidator(0)],
+                    ),
+                ),
                 (
                     "location",
                     models.ForeignKey(
@@ -842,6 +842,34 @@ class Migration(migrations.Migration):
                     ),
                 ),
                 ("mark_connected", models.BooleanField(default=False)),
+                (
+                    "supply_temperature",
+                    models.DecimalField(
+                        blank=True, decimal_places=2, max_digits=6, null=True
+                    ),
+                ),
+                (
+                    "return_temperature",
+                    models.DecimalField(
+                        blank=True, decimal_places=2, max_digits=6, null=True
+                    ),
+                ),
+                (
+                    "temperature_unit",
+                    models.CharField(blank=True, max_length=50, null=True),
+                ),
+                (
+                    "_abs_supply_temperature",
+                    models.DecimalField(
+                        blank=True, decimal_places=4, max_digits=8, null=True
+                    ),
+                ),
+                (
+                    "_abs_return_temperature",
+                    models.DecimalField(
+                        blank=True, decimal_places=4, max_digits=8, null=True
+                    ),
+                ),
                 ("name", models.CharField(db_collation="natural_sort", max_length=100)),
                 ("status", models.CharField(default="active", max_length=50)),
                 ("type", models.CharField(default="supply", max_length=50)),

+ 82 - 68
netbox/dcim/models/cooling.py

@@ -22,52 +22,17 @@ __all__ = (
 # Cooling
 #
 
-class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
+class CoolingTemperatureMixin(models.Model):
     """
-    A facility-level source of cooling; e.g. a chiller, cooling tower, or dry cooler.
+    Adds design supply/return temperatures sharing a single unit, with normalized (°C) fields for ordering.
     """
-    site = models.ForeignKey(
-        to='Site',
-        on_delete=models.PROTECT
-    )
-    location = models.ForeignKey(
-        to='dcim.Location',
-        on_delete=models.PROTECT,
-        blank=True,
-        null=True
-    )
-    name = models.CharField(
-        verbose_name=_('name'),
-        max_length=100,
-        db_collation="natural_sort"
-    )
-    type = models.CharField(
-        verbose_name=_('type'),
-        max_length=50,
-        choices=CoolingSourceTypeChoices
-    )
-    status = models.CharField(
-        verbose_name=_('status'),
-        max_length=50,
-        choices=CoolingSourceStatusChoices,
-        default=CoolingSourceStatusChoices.STATUS_ACTIVE
-    )
-    cooling_capacity = models.DecimalField(
-        verbose_name=_('cooling capacity'),
-        max_digits=10,
-        decimal_places=2,
-        blank=True,
-        null=True,
-        validators=[MinValueValidator(0)],
-        help_text=_('Total cooling capacity (kW)')
-    )
     supply_temperature = models.DecimalField(
         verbose_name=_('supply temperature'),
         max_digits=6,
         decimal_places=2,
         blank=True,
         null=True,
-        help_text=_('Design supply temperature')
+        help_text=_('Supply (cold) temperature')
     )
     return_temperature = models.DecimalField(
         verbose_name=_('return temperature'),
@@ -75,7 +40,7 @@ class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
         decimal_places=2,
         blank=True,
         null=True,
-        help_text=_('Design return temperature')
+        help_text=_('Return (warm) temperature')
     )
     temperature_unit = models.CharField(
         verbose_name=_('temperature unit'),
@@ -98,27 +63,8 @@ class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
         null=True
     )
 
-    clone_fields = (
-        'site', 'location', 'type', 'status', 'cooling_capacity', 'supply_temperature', 'return_temperature',
-        'temperature_unit',
-    )
-    prerequisite_models = (
-        'dcim.Site',
-    )
-
     class Meta:
-        ordering = ['site', 'name']
-        constraints = (
-            models.UniqueConstraint(
-                fields=('site', 'name'),
-                name='%(app_label)s_%(class)s_unique_site_name'
-            ),
-        )
-        verbose_name = _('cooling source')
-        verbose_name_plural = _('cooling sources')
-
-    def __str__(self):
-        return self.name
+        abstract = True
 
     @property
     def abs_supply_temperature(self):
@@ -130,9 +76,6 @@ class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
         # Public alias for _abs_return_temperature; Django templates cannot access underscore-prefixed attributes.
         return self._abs_return_temperature
 
-    def get_status_color(self):
-        return CoolingSourceStatusChoices.colors.get(self.status)
-
     def save(self, *args, **kwargs):
         # Store the given temperatures (if any) in degrees Celsius for use in database ordering
         if self.temperature_unit:
@@ -154,6 +97,80 @@ class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
 
         super().save(*args, **kwargs)
 
+    def clean(self):
+        super().clean()
+
+        # A temperature unit is required when a temperature is set
+        if (self.supply_temperature is not None or self.return_temperature is not None) and not self.temperature_unit:
+            raise ValidationError(_("Must specify a unit when setting a temperature"))
+
+
+class CoolingSource(CoolingTemperatureMixin, ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
+    """
+    A facility-level source of cooling; e.g. a chiller, cooling tower, or dry cooler.
+    """
+    site = models.ForeignKey(
+        to='Site',
+        on_delete=models.PROTECT
+    )
+    location = models.ForeignKey(
+        to='dcim.Location',
+        on_delete=models.PROTECT,
+        blank=True,
+        null=True
+    )
+    name = models.CharField(
+        verbose_name=_('name'),
+        max_length=100,
+        db_collation="natural_sort"
+    )
+    type = models.CharField(
+        verbose_name=_('type'),
+        max_length=50,
+        choices=CoolingSourceTypeChoices
+    )
+    status = models.CharField(
+        verbose_name=_('status'),
+        max_length=50,
+        choices=CoolingSourceStatusChoices,
+        default=CoolingSourceStatusChoices.STATUS_ACTIVE
+    )
+    cooling_capacity = models.DecimalField(
+        verbose_name=_('cooling capacity'),
+        max_digits=10,
+        decimal_places=2,
+        blank=True,
+        null=True,
+        validators=[MinValueValidator(0)],
+        help_text=_('Total cooling capacity (kW)')
+    )
+    # supply_temperature, return_temperature, temperature_unit, _abs_* provided by CoolingTemperatureMixin
+
+    clone_fields = (
+        'site', 'location', 'type', 'status', 'cooling_capacity', 'supply_temperature', 'return_temperature',
+        'temperature_unit',
+    )
+    prerequisite_models = (
+        'dcim.Site',
+    )
+
+    class Meta:
+        ordering = ['site', 'name']
+        constraints = (
+            models.UniqueConstraint(
+                fields=('site', 'name'),
+                name='%(app_label)s_%(class)s_unique_site_name'
+            ),
+        )
+        verbose_name = _('cooling source')
+        verbose_name_plural = _('cooling sources')
+
+    def __str__(self):
+        return self.name
+
+    def get_status_color(self):
+        return CoolingSourceStatusChoices.colors.get(self.status)
+
     def clean(self):
         super().clean()
 
@@ -164,12 +181,8 @@ class CoolingSource(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
                     location=self.location, location_site=self.location.site, site=self.site)
             )
 
-        # A temperature unit is required when a temperature is set
-        if (self.supply_temperature is not None or self.return_temperature is not None) and not self.temperature_unit:
-            raise ValidationError(_("Must specify a unit when setting a temperature"))
-
 
-class CoolingFeed(FlowRateMixin, PressureMixin, PrimaryModel, PathEndpoint, CabledObjectModel):
+class CoolingFeed(CoolingTemperatureMixin, FlowRateMixin, PressureMixin, PrimaryModel, PathEndpoint, CabledObjectModel):
     """
     A coolant loop delivered from a CoolingSource to a rack or CDU. Supply and return loops are
     represented as separate feeds so each can be traced independently.
@@ -231,7 +244,8 @@ class CoolingFeed(FlowRateMixin, PressureMixin, PrimaryModel, PathEndpoint, Cabl
 
     clone_fields = (
         'cooling_source', 'rack', 'status', 'type', 'mark_connected', 'fluid_type', 'cooling_capacity', 'flow_rate',
-        'flow_rate_unit', 'pressure', 'pressure_unit', 'tenant',
+        'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature', 'return_temperature', 'temperature_unit',
+        'tenant',
     )
     prerequisite_models = (
         'dcim.CoolingSource',

+ 14 - 2
netbox/dcim/tables/cooling.py

@@ -131,6 +131,17 @@ class CoolingFeedTable(TenancyColumnsMixin, CableTerminationTable, PrimaryModelT
     pressure_unit = columns.ChoiceFieldColumn(
         verbose_name=_('Pressure Unit'),
     )
+    supply_temperature = tables.Column(
+        verbose_name=_('Supply Temperature'),
+        order_by=('_abs_supply_temperature',)
+    )
+    return_temperature = tables.Column(
+        verbose_name=_('Return Temperature'),
+        order_by=('_abs_return_temperature',)
+    )
+    temperature_unit = columns.ChoiceFieldColumn(
+        verbose_name=_('Temperature Unit'),
+    )
     tenant = tables.Column(
         linkify=True,
         verbose_name=_('Tenant')
@@ -148,8 +159,9 @@ class CoolingFeedTable(TenancyColumnsMixin, CableTerminationTable, PrimaryModelT
         model = CoolingFeed
         fields = (
             'pk', 'id', 'name', 'cooling_source', 'site', 'rack', 'status', 'type', 'fluid_type', 'cooling_capacity',
-            'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'mark_connected', 'cable', 'cable_color',
-            'link_peer', 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated',
+            'flow_rate', 'flow_rate_unit', 'pressure', 'pressure_unit', 'supply_temperature', 'return_temperature',
+            'temperature_unit', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tenant', 'tenant_group',
+            'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'cooling_source', 'rack', 'status', 'type', 'fluid_type', 'cooling_capacity', 'flow_rate',

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

@@ -8418,6 +8418,9 @@ class CoolingFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
                 flow_rate_unit=FlowRateUnitChoices.UNIT_LITERS_PER_MINUTE,
                 pressure=100,
                 pressure_unit=PressureUnitChoices.UNIT_KILOPASCAL,
+                supply_temperature=18,
+                return_temperature=30,
+                temperature_unit=TemperatureUnitChoices.UNIT_CELSIUS,
                 description='foobar1'
             ),
             CoolingFeed(
@@ -8433,6 +8436,9 @@ class CoolingFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
                 flow_rate_unit=FlowRateUnitChoices.UNIT_LITERS_PER_MINUTE,
                 pressure=200,
                 pressure_unit=PressureUnitChoices.UNIT_KILOPASCAL,
+                supply_temperature=20,
+                return_temperature=32,
+                temperature_unit=TemperatureUnitChoices.UNIT_CELSIUS,
                 description='foobar2'
             ),
             CoolingFeed(
@@ -8448,6 +8454,9 @@ class CoolingFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
                 flow_rate_unit=FlowRateUnitChoices.UNIT_GALLONS_PER_MINUTE,
                 pressure=300,
                 pressure_unit=PressureUnitChoices.UNIT_PSI,
+                supply_temperature=22,
+                return_temperature=34,
+                temperature_unit=TemperatureUnitChoices.UNIT_FAHRENHEIT,
                 description='foobar3'
             ),
         )
@@ -8507,6 +8516,18 @@ class CoolingFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'pressure_unit': [PressureUnitChoices.UNIT_KILOPASCAL]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_supply_temperature(self):
+        params = {'supply_temperature': [18, 20]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+    def test_return_temperature(self):
+        params = {'return_temperature': [30, 32]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+    def test_temperature_unit(self):
+        params = {'temperature_unit': [TemperatureUnitChoices.UNIT_CELSIUS]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_description(self):
         params = {'description': ['foobar1', 'foobar2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 7 - 0
netbox/dcim/tests/test_views.py

@@ -24,6 +24,7 @@ from netbox.choices import (
     FlowRateUnitChoices,
     ImportFormatChoices,
     PressureUnitChoices,
+    TemperatureUnitChoices,
     WeightUnitChoices,
 )
 from tenancy.models import Tenant
@@ -4608,6 +4609,9 @@ class CoolingFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'flow_rate_unit': FlowRateUnitChoices.UNIT_LITERS_PER_MINUTE,
             'pressure': 200,
             'pressure_unit': PressureUnitChoices.UNIT_KILOPASCAL,
+            'supply_temperature': 18,
+            'return_temperature': 30,
+            'temperature_unit': TemperatureUnitChoices.UNIT_CELSIUS,
             'comments': 'New comments',
             'tags': [t.pk for t in tags],
         }
@@ -4637,6 +4641,9 @@ class CoolingFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'flow_rate_unit': FlowRateUnitChoices.UNIT_LITERS_PER_MINUTE,
             'pressure': 200,
             'pressure_unit': PressureUnitChoices.UNIT_KILOPASCAL,
+            'supply_temperature': 18,
+            'return_temperature': 30,
+            'temperature_unit': TemperatureUnitChoices.UNIT_CELSIUS,
             'comments': 'New comments',
         }
 

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

@@ -427,6 +427,8 @@ class CoolingFeedElectricalPanel(panels.ObjectAttributesPanel):
     cooling_capacity = attrs.TextAttr('cooling_capacity', format_string=_('{} kW'))
     flow_rate = attrs.NumericAttr('flow_rate', unit_accessor='get_flow_rate_unit_display')
     pressure = attrs.NumericAttr('pressure', unit_accessor='get_pressure_unit_display')
+    supply_temperature = attrs.NumericAttr('supply_temperature', unit_accessor='get_temperature_unit_display')
+    return_temperature = attrs.NumericAttr('return_temperature', unit_accessor='get_temperature_unit_display')
 
 
 class VirtualDeviceContextPanel(panels.ObjectAttributesPanel):