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