Coverage for backend/django/flowsheetInternals/unitops/models/delete_factory.py: 93%
76 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1from django.db import transaction
2from PinchAnalysis.models.InputModels import StreamDataEntry
3from flowsheetInternals.unitops.models.Port import Port
4from flowsheetInternals.unitops.config.config_methods import get_object_schema
5from .SimulationObject import SimulationObject
6from Economics.costing.costable_items.deleted_objects import (
7 delete_economics_lines_for_simulation_objects,
8)
9from flowsheetInternals.unitops.services.edit_operations.recorder import (
10 tracked_queryset_update,
11)
14class DeleteFactory:
15 def __init__(self, objs: list[SimulationObject]) -> None:
16 self.simulation_objects = list(
17 SimulationObject.objects.filter(
18 id__in=[obj.id for obj in objs]
19 ).prefetch_related(
20 "ports",
21 "ports__stream",
22 "properties",
23 "connectedPorts",
24 "connectedPorts__unitOp",
25 "grouping",
26 "recycleData",
27 "recycleConnection",
28 )
29 )
31 @classmethod
32 def delete_object(cls, obj):
33 factory = DeleteFactory([obj])
34 return factory.run_delete()
36 @classmethod
37 def delete_multiple_objects(cls, objs: list[SimulationObject]) -> list[int]:
38 factory = DeleteFactory(objs)
39 return factory.run_delete()
41 @classmethod
42 def _restore_object_ids(cls, object_ids: list[int]) -> None:
43 """Reactivate object tombstones for the legacy restore endpoint.
45 Connections are restored separately by that endpoint's client-side
46 workflow; database-backed undo replays the complete recorded change set.
47 """
49 with transaction.atomic():
50 tracked_queryset_update(
51 SimulationObject.objects.include_deleted().filter(
52 id__in=object_ids,
53 ),
54 is_deleted=False,
55 )
57 def run_delete(self) -> list[int]:
58 """Delete the resolved object batch and return every affected object ID."""
60 list_of_streams: list[SimulationObject] = []
61 streams_to_update: list[SimulationObject] = []
63 for obj in self.simulation_objects:
64 # Handle streams connected to ports
65 for port in obj.ports.all():
66 stream = port.stream
67 if stream is not None and stream not in self.simulation_objects:
68 # this stream is not being deleted, so we need to update its enabled properties
69 list_of_streams.append(stream)
71 if obj.objectType == "group":
72 # Include everything inside the group in the deletion
73 group_sim_objects = obj.grouping.get_simulation_objects()
74 self.simulation_objects.extend(group_sim_objects)
76 if obj.objectType == "recycle":
77 # Handle recycle connections
78 if obj.recycleData.tearObject is not None:
79 streams_to_update.append(obj.recycleData.tearObject)
80 obj.recycleData.clear()
81 elif obj.is_stream():
82 # Decide if we should delete this stream:
83 if obj.connectedPorts.count() == 0:
84 # no problem, we can delete
85 pass
86 elif obj.connectedPorts.count() == 1:
87 # no problem, just delete
88 pass
89 else:
90 # Connected to two ports
91 connectedPorts = obj.connectedPorts.all()
92 if connectedPorts[0].unitOp in self.simulation_objects:
93 # connected to a unit op that is being deleted
94 if connectedPorts[1].unitOp in self.simulation_objects: 94 ↛ 100line 94 didn't jump to line 100 because the condition on line 94 was always true
95 # connected to two unit ops that are being deleted, just delete it
96 pass
97 else:
98 # Keep stream as it's connected to another unit op.
99 # Remove it from the list of streams to delete
100 self.simulation_objects.remove(obj)
101 elif connectedPorts[1].unitOp in self.simulation_objects:
102 self.simulation_objects.remove(obj)
103 else:
104 # neither unit op is being deleted, so we need to split the stream into two.
105 # Remove the current stream from deletion and create a new stream for the pump 1 outlet.
106 # keep the existing stream
107 self.simulation_objects.remove(obj)
108 connectedPorts[1].stream.split_stream()
110 # reindex ports on delete
111 for port in obj.connectedPorts.all():
112 unitop = port.unitOp
113 # If the unit operation itself is being deleted (or missing), skip reindexing
114 if unitop is None or unitop in self.simulation_objects:
115 continue
116 # This is for when a decision node has a port in a parent group and the other port in a child group
117 if getattr(unitop, "objectType", None) == "decisionNode": 117 ↛ 118line 117 didn't jump to line 118 because the condition on line 117 was never true
118 continue
119 unitop_config = get_object_schema(unitop)
120 ports_config = unitop_config.ports[port.key]
121 if ports_config and ports_config.many:
122 number_of_ports = sum(
123 p.key == port.key for p in unitop.ports.all()
124 )
125 if number_of_ports > ports_config.minimum: 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 port.reindex_port_on_delete()
128 with transaction.atomic():
129 deleted_object_ids = [obj.id for obj in self.simulation_objects]
130 delete_economics_lines_for_simulation_objects(deleted_object_ids)
132 # Update property access before deletion
133 for stream in streams_to_update:
134 if stream in self.simulation_objects: 134 ↛ 135line 134 didn't jump to line 135 because the condition on line 134 was never true
135 continue # skip, already being deleted
136 stream.reevaluate_properties_enabled()
138 # Disconnect ports before deletion
139 tracked_queryset_update(
140 Port.objects.filter(unitOp__id__in=deleted_object_ids),
141 stream=None,
142 )
144 # Perform the deletion of Simulation objects, and related StreamDataEntry's
145 tracked_queryset_update(
146 SimulationObject.objects.filter(id__in=deleted_object_ids),
147 is_deleted=True,
148 )
149 StreamDataEntry.objects.filter(unitop_id__in=deleted_object_ids).delete()
151 # Update remaining streams' property access
152 for stream in list_of_streams:
153 stream.reevaluate_properties_enabled()
155 return deleted_object_ids