Coverage for backend/django/flowsheetInternals/unitops/models/Port.py: 89%
69 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-12-18 04:00 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-12-18 04:00 +0000
1from django.db import models
2from core.auxiliary.enums import ConType
3from typing import TYPE_CHECKING
5from core.managers import AccessControlManager
6from core.auxiliary.models.PropertyValue import PropertyValue
7from core.auxiliary.models.IndexedItem import IndexedItem
8from flowsheetInternals.graphicData.models.graphicObjectModel import GraphicObject
9from flowsheetInternals.unitops.config import get_object_schema
11if TYPE_CHECKING:
12 from core.auxiliary.models.Flowsheet import Flowsheet
13 from flowsheetInternals.unitops.models.SimulationObject import SimulationObject
14 from flowsheetInternals.graphicData.models.graphicObjectModel import GraphicObject
17class Port(models.Model):
18 flowsheet = models.ForeignKey("core_auxiliary.Flowsheet", on_delete=models.CASCADE, related_name="Ports")
19 displayName = models.CharField(max_length=64)
20 direction = models.CharField(choices=ConType.choices)
21 key = models.CharField(max_length=64) # key to identify this port in the schema
22 index = models.IntegerField(default=0) # index of the port
23 unitOp = models.ForeignKey('SimulationObject', on_delete=models.CASCADE, related_name="ports", null=True)
24 stream = models.ForeignKey('SimulationObject', on_delete=models.SET_NULL, null=True, related_name="connectedPorts")
26 created_at = models.DateTimeField(auto_now_add=True)
27 objects = AccessControlManager()
29 # runtime-accessed relations
30 flowsheet: "Flowsheet"
31 unitOp: "SimulationObject | None"
32 stream: "SimulationObject | None"
34 class Meta:
35 ordering = ['index', 'created_at']
37 def default_stream_position(self, unitop: "flowsheetInternals.unitops.SimulationObject",
38 unitop_graphic_object: GraphicObject, port_index: int, num_ports: int) -> dict[
39 str, float]:
40 """Calculate the default position for a stream connected to this port"""
41 stream_offset = get_object_schema(unitop).ports[self.key].streamOffset
43 if self.direction == ConType.Inlet:
44 xOffset = -stream_offset
45 else:
46 xOffset = 1 + stream_offset
48 # Calculate y offset based on port index and total number of ports
49 yOffset = (port_index + 0.5) / num_ports
51 return {
52 "x": unitop_graphic_object.x + xOffset * unitop_graphic_object.width,
53 "y": unitop_graphic_object.y + yOffset * unitop_graphic_object.height
54 }
56 def default_stream_name(self, unit_op: "flowsheetInternals.unitops.SimulationObject") -> str:
57 return get_object_schema(unit_op).ports[self.key].streamName
59 def reindex_port_on_delete(self):
60 subsequent_ports = Port.objects.filter(
61 unitOp=self.unitOp,
62 key=self.key,
63 index__gt=self.index
64 ).order_by('index')
66 unit_op_schema = get_object_schema(self.unitOp)
67 display_name = unit_op_schema.ports[self.key].displayName
68 index_before_update = self.index
69 for p in subsequent_ports:
70 p.index -= 1
71 p.displayName = f"{display_name} {p.index + 1}"
72 p.save()
74 property_set = self.unitOp.properties
75 property_infos = property_set.containedProperties.filter(
76 index__gte=self.index
77 ).order_by('index')
79 unit = self.unitOp
81 value = False
82 if self.direction == ConType.Outlet:
83 # You can only delete outlets on something that has a split fraction.
84 # Therefore, delete the split fraction property associated with the outlet.
85 # Get the properties of the outlet to be deleted.
86 property_infos = unit.properties.ContainedProperties.all()
87 for property in property_infos:
88 # TODO: Surely we can use sumToOne or indexed by compounds instead of this?
89 if property.key == "split_fraction": 89 ↛ 91line 89 didn't jump to line 91 because the condition on line 89 was always true
90 property_info = property_infos.filter(key="split_fraction").first()
91 elif property.key == "priorities":
92 property_info = property_infos.filter(key="priorities").first()
93 elif property.key == "split_flow":
94 property_info = property_infos.filter(key="split_flow").first()
96 # Get the index of the outlet to be deleted.
97 indexed_item = IndexedItem.objects.filter(owner=unit, key="outlet_" + f"{index_before_update + 1}").first()
98 # Delete the outlet and its property values
99 PropertyValue.objects.filter(property=property_info, indexedItems=indexed_item).delete()
100 if indexed_item is not None: 100 ↛ 104line 100 didn't jump to line 104 because the condition on line 100 was always true
101 indexed_item.delete()
103 # Re-index the display names of the input fields for the remaining outlets.
104 indexed_items = IndexedItem.objects.filter(owner=unit, type="splitter_fraction")
105 # For the remaining outlets,
106 num_outlets = indexed_items.count()
107 for i in range(indexed_items.count()):
108 # Update the index
109 indexed_item = indexed_items[i]
110 indexed_item.key = "outlet_" + f"{i + 1}"
111 # Use the updated index to change the name displayed on the frontend
112 indexed_item.displayName = unit.schema.splitter_fraction_name + f" {i + 1}"
113 indexed_item.save()
115 self.delete()
116 unit.refresh_from_db()
117 unit.reevaluate_properties_enabled()
118 else:
119 self.delete()