Coverage for backend/django/flowsheetInternals/unitops/models/delete_factory.py: 91%
72 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 transaction
2from PinchAnalysis.models.InputModels import StreamDataEntry
3from flowsheetInternals.unitops.models.simulation_object_factory import SimulationObjectFactory
4from flowsheetInternals.unitops.models.Port import Port
5from flowsheetInternals.unitops.config.config_methods import get_object_schema
6from .SimulationObject import SimulationObject
9class DeleteFactory:
10 def __init__(self, objs: list[SimulationObject]) -> None:
11 self.simulation_objects = list(SimulationObject.objects.filter(
12 id__in=[obj.id for obj in objs]
13 ).prefetch_related(
14 "ports",
15 "ports__stream",
16 "properties",
17 "connectedPorts",
18 "connectedPorts__unitOp",
19 "grouping",
20 "recycleData",
21 "recycleConnection",
22 ))
24 @classmethod
25 def delete_object(cls, obj):
26 factory = DeleteFactory([obj])
27 factory.run_delete()
29 @classmethod
30 def delete_multiple_objects(cls, objs: list[SimulationObject]) -> None:
31 factory = DeleteFactory(objs)
32 factory.run_delete()
34 def run_delete(self) -> None:
36 list_of_streams: list[SimulationObject] = []
37 streams_to_update: list[SimulationObject] = []
39 for obj in self.simulation_objects:
40 # Handle streams connected to ports
41 for port in obj.ports.all():
42 stream = port.stream
43 if stream is not None and stream not in self.simulation_objects:
44 # this stream is not being deleted, so we need to update its enabled properties
45 list_of_streams.append(stream)
47 if obj.objectType == "group":
48 # Include everything inside the group in the deletion
49 group_sim_objects = obj.grouping.get_simulation_objects()
50 self.simulation_objects.extend(group_sim_objects)
52 if obj.objectType == "recycle":
53 # Handle recycle connections
54 if obj.recycleData.tearObject is not None:
55 streams_to_update.append(obj.recycleData.tearObject)
56 obj.recycleData.clear()
57 elif obj.is_stream():
58 # Decide if we should delete this stream:
59 if obj.connectedPorts.count() == 0:
60 # no problem, we can delete
61 pass
62 elif obj.connectedPorts.count() == 1:
63 # no problem, just delete
64 pass
65 else:
66 # Connected to two ports
67 connectedPorts = obj.connectedPorts.all()
68 if connectedPorts[0].unitOp in self.simulation_objects:
69 # connected to a unit op that is being deleted
70 if connectedPorts[1].unitOp in self.simulation_objects: 70 ↛ 76line 70 didn't jump to line 76 because the condition on line 70 was always true
71 # connected to two unit ops that are being deleted, just delete it
72 pass
73 else:
74 # Keep stream as it's connected to another unit op.
75 # Remove it from the list of streams to delete
76 self.simulation_objects.remove(obj)
77 elif connectedPorts[1].unitOp in self.simulation_objects:
78 self.simulation_objects.remove(obj)
79 else:
80 # neither unit op is being deleted, so we need to split the stream into two.
81 # Remove the current stream from deletion and create a new stream for the pump 1 outlet.
82 # keep the existing stream
83 self.simulation_objects.remove(obj)
84 connectedPorts[1].stream.split_stream()
86 # reindex ports on delete
87 for port in obj.connectedPorts.all():
88 unitop = port.unitOp
89 # If the unit operation itself is being deleted (or missing), skip reindexing
90 if unitop is None or unitop in self.simulation_objects:
91 continue
92 # This is for when a decision node has a port in a parent group and the other port in a child group
93 if getattr(unitop, "objectType", None) == "decisionNode": 93 ↛ 94line 93 didn't jump to line 94 because the condition on line 93 was never true
94 continue
95 unitop_config = get_object_schema(unitop)
96 ports_config = unitop_config.ports[port.key]
97 if ports_config and ports_config.many:
98 number_of_ports = sum(p.key == port.key for p in unitop.ports.all())
99 if number_of_ports > ports_config.minimum: 99 ↛ 100line 99 didn't jump to line 100 because the condition on line 99 was never true
100 port.reindex_port_on_delete()
102 with transaction.atomic():
103 # Update property access before deletion
104 for stream in streams_to_update:
105 if stream in self.simulation_objects: 105 ↛ 106line 105 didn't jump to line 106 because the condition on line 105 was never true
106 continue # skip, already being deleted
107 stream.reevaluate_properties_enabled()
109 # Disconnect ports before deletion
110 Port.objects.filter(
111 unitOp__id__in=[obj.id for obj in self.simulation_objects]
112 ).update(stream=None)
114 # Perform the deletion of Simulation objects, and related StreamDataEntry's
115 SimulationObject.objects.filter(id__in=[obj.id for obj in self.simulation_objects]).update(is_deleted=True)
116 StreamDataEntry.objects.filter(unitop_id__in=[obj.id for obj in self.simulation_objects]).delete()
118 # Update remaining streams' property access
119 for stream in list_of_streams:
120 stream.reevaluate_properties_enabled()
122 @classmethod
123 def _restore_object_ids(cls, object_ids: list[int]) -> None:
124 """
125 Restore simulation objects by setting is_deleted=False.
127 Note: This method only restores the objects themselves.
128 Port-stream connections must be restored separately via the
129 frontend connection restoration system which has the captured
130 connection state from before deletion.
131 """
132 with transaction.atomic():
133 # Restore the simulation objects
134 SimulationObject.objects.include_deleted().filter(
135 id__in=object_ids
136 ).update(is_deleted=False)
138 # Note: Ports are not soft-deleted, they remain in the database
139 # with stream=None. The frontend is responsible for restoring
140 # the port-stream connections using the captured connection data.
142 # Note: StreamDataEntry's are not soft deleted.
143 # The user is expected to re-extract their flowsheet in the pinch workflow
144 # after any and all changes made on the flowsheet