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

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 

8 

9 

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 )) 

24 

25 @classmethod 

26 def delete_object(cls, obj): 

27 factory = DeleteFactory([obj]) 

28 factory.run_delete() 

29 

30 @classmethod 

31 def delete_multiple_objects(cls, objs: list[SimulationObject]) -> None: 

32 factory = DeleteFactory(objs) 

33 factory.run_delete() 

34 

35 def run_delete(self) -> None: 

36 

37 list_of_streams: list[SimulationObject] = [] 

38 streams_to_update: list[SimulationObject] = [] 

39 

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) 

47 

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) 

52 

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() 

86 

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() 

102 

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) 

106 

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() 

112 

113 # Disconnect ports before deletion 

114 Port.objects.filter( 

115 unitOp__id__in=deleted_object_ids 

116 ).update(stream=None) 

117 

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() 

121 

122 # Update remaining streams' property access 

123 for stream in list_of_streams: 

124 stream.reevaluate_properties_enabled() 

125 

126 @classmethod 

127 def _restore_object_ids(cls, object_ids: list[int]) -> None: 

128 """ 

129 Restore simulation objects by setting is_deleted=False. 

130  

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) 

141 

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. 

145 

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