#22447: Pre-release QA (#22908)

* Fixes #22447: Pre-release QA

Add the missing `cooling_outflow` GraphQL filter on CoolingIntake, so an
intake can be filtered by its upstream outflow. CoolingOutflow already
exposes the reverse `cooling_intake` filter and the REST filterset already
carries `cooling_outflow_id`; the GraphQL intake filter was the only side
missing it.

Correct the CoolingIntake docstring, which referenced a direct CoolingFeed
relationship that does not exist. The serving feed is derived from the
device's rack, not stored on the intake.

* Fixes #22447: Pre-release QA (filter form + table parity)

Address the same-class gaps surfaced in review, all mirror images of the
intake/outflow filter parity already fixed:

Add the `cooling_intake_id` filter to CoolingOutflowFilterForm and
CoolingOutflowTemplateFilterForm. The underlying filtersets already carried
`cooling_intake_id` and GraphQL supported it, but the list-view filter panel
did not expose it, so an outflow could not be filtered by its downstream
intake from the UI.

Add `cooling_outflow` to the default columns on CoolingIntakeTable and
DeviceCoolingIntakeTable. The outflow tables already default-show
`cooling_intake`; the intake tables hid the reverse, so the same relationship
displayed inconsistently between the two sides.

Note in the CoolingIntake docstring why CoolingIntakeTemplate has no
upstream-outflow field: an intake's outflow normally lives on a different
device (a CDU), which a device-type template cannot express.
This commit is contained in:
Jason Novinger 2026-08-12 11:57:00 -05:00 committed by GitHub
parent bfb665ccb9
commit feffda99d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 5 deletions

View File

@ -1940,7 +1940,7 @@ class CoolingOutflowFilterForm(DeviceComponentFilterForm):
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet(
'name', 'label', 'type', 'diameter', 'diameter_unit', name=_('Attributes')
'name', 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake_id', name=_('Attributes')
),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
@ -1963,6 +1963,11 @@ class CoolingOutflowFilterForm(DeviceComponentFilterForm):
choices=DiameterUnitChoices,
required=False
)
cooling_intake_id = DynamicModelMultipleChoiceField(
label=_('Cooling intake'),
queryset=CoolingIntake.objects.all(),
required=False
)
tag = TagFilterField(model)
@ -1970,7 +1975,7 @@ class CoolingOutflowTemplateFilterForm(ModularDeviceComponentTemplateFilterForm)
model = CoolingOutflowTemplate
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('name', 'label', 'type', 'diameter', 'diameter_unit', name=_('Attributes')),
FieldSet('name', 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake_id', name=_('Attributes')),
FieldSet('device_type_id', 'module_type_id', name=_('Device')),
)
type = forms.MultipleChoiceField(
@ -1987,6 +1992,11 @@ class CoolingOutflowTemplateFilterForm(ModularDeviceComponentTemplateFilterForm)
choices=DiameterUnitChoices,
required=False
)
cooling_intake_id = DynamicModelMultipleChoiceField(
label=_('Cooling intake'),
queryset=CoolingIntakeTemplate.objects.all(),
required=False
)
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):

View File

@ -1068,6 +1068,10 @@ class CoolingIntakeFilter(ModularComponentFilterMixin, NetBoxModelFilter):
max_flow_unit: BaseFilterLookup[
Annotated['FlowRateUnitEnum', strawberry.lazy('dcim.graphql.enums')]
] | None = strawberry_django.filter_field()
cooling_outflow: Annotated['CoolingOutflowFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
cooling_outflow_id: ID | None = strawberry_django.filter_field()
@register_filter(models.CoolingIntakeTemplate, lookups=True)

View File

@ -700,7 +700,12 @@ class CoolingIntake(
):
"""
A coolant intake port within a Device (e.g. a server cold-plate inlet or CDU intake). A
CoolingIntake is supplied by an upstream CoolingOutflow or CoolingFeed.
CoolingIntake is supplied by an upstream CoolingOutflow. The serving CoolingFeed is
derived from the Device's Rack rather than referenced directly.
Unlike CoolingOutflow (whose parent intake is on the same Device and can therefore be
templated), an intake's upstream outflow typically lives on a different Device (e.g. a
CDU), so there is deliberately no upstream-outflow field on CoolingIntakeTemplate.
"""
type = models.CharField(
verbose_name=_('type'),

View File

@ -166,7 +166,7 @@ class CoolingIntakeTable(ModularDeviceComponentTable):
)
default_columns = (
'pk', 'name', 'device', 'label', 'type', 'diameter', 'max_flow',
'description',
'cooling_outflow', 'description',
)
@ -281,7 +281,7 @@ class DeviceCoolingIntakeTable(CoolingIntakeTable):
)
default_columns = (
'pk', 'name', 'label', 'type', 'diameter', 'max_flow',
'description',
'cooling_outflow', 'description',
)