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

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 

7 

8 

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

23 

24 @classmethod 

25 def delete_object(cls, obj): 

26 factory = DeleteFactory([obj]) 

27 factory.run_delete() 

28 

29 @classmethod 

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

31 factory = DeleteFactory(objs) 

32 factory.run_delete() 

33 

34 def run_delete(self) -> None: 

35 

36 list_of_streams: list[SimulationObject] = [] 

37 streams_to_update: list[SimulationObject] = [] 

38 

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) 

46 

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) 

51 

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

85 

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

101 

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

108 

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) 

113 

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

117 

118 # Update remaining streams' property access 

119 for stream in list_of_streams: 

120 stream.reevaluate_properties_enabled() 

121 

122 @classmethod 

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

124 """ 

125 Restore simulation objects by setting is_deleted=False. 

126  

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) 

137 

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. 

141 

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