Coverage for src/backend/InvenTree/generic/states/transition.py: 34%

32 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""Classes and functions for plugin controlled object state transitions.""" 

2 

3from collections.abc import Callable 

4 

5from django.db.models import Model 

6 

7import structlog 

8 

9logger = structlog.get_logger('inventree') 

10 

11 

12class TransitionMethod: 

13 """Base class for all transition classes. 

14 

15 Must implement a method called `transition` that takes both args and kwargs. 

16 """ 

17 

18 def __init__(self) -> None: 

19 """Check that the method is defined correctly. 

20 

21 This checks that: 

22 - The needed functions are implemented 

23 """ 

24 # Check if a sending fnc is defined 

25 if not hasattr(self, 'transition'): # pragma: no cover 

26 raise NotImplementedError( 

27 'A TransitionMethod must define a `transition` method' 

28 ) 

29 

30 def transition( 

31 self, 

32 current_state: int, 

33 target_state: int, 

34 instance: Model, 

35 default_action: Callable, 

36 **kwargs, 

37 ) -> bool: 

38 """Perform a state transition. 

39 

40 Success: 

41 - The custom transition logic succeeded 

42 - Return True result 

43 - No further transitions are attempted 

44 Ignore: 

45 - The custom transition logic did not apply 

46 - Return False result 

47 - Further transitions are attempted (if available) 

48 - Default action is called if no transition was successful 

49 Failure: 

50 - The custom transition logic failed 

51 - Raise a ValidationError 

52 - No further transitions are attempted 

53 - Default action is not called 

54 

55 Arguments: 

56 current_state: int - Current state of the instance. 

57 target_state: int - Target state to transition to. 

58 instance: Model - The object instance to transition. 

59 default_action: callable - Default action to be taken if no transition is successful. 

60 **kwargs: Additional keyword arguments for custom logic. 

61 

62 Returns: 

63 result: bool - True if the transition method was successful (and no further transitions are attempted), False otherwise. 

64 

65 Raises: 

66 ValidationError: Alert the user that the transition failed 

67 """ 

68 raise NotImplementedError( 

69 'TransitionMethod.transition must be implemented' 

70 ) # pragma: no cover 

71 

72 

73class StateTransitionMixin: 

74 """Mixin class to enable state transitions. 

75 

76 This mixin is used to add state transitions handling to a model. With this you can apply custom logic to state transitions via plugins. 

77 ```python 

78 class MyModel(StateTransitionMixin, models.Model): 

79 def some_dummy_function(self, *args, **kwargs): 

80 pass 

81 

82 def action(self, *args, **kwargs): 

83 self.handle_transition(0, 1, self, self.some_dummy_function) 

84 ``` 

85 """ 

86 

87 def handle_transition( 

88 self, current_state, target_state, instance, default_action, **kwargs 

89 ): 

90 """Handle a state transition for an object. 

91 

92 Args: 

93 current_state: Current state of instance 

94 target_state: Target state of instance 

95 instance: Object instance 

96 default_action: Default action to be taken if none of the transitions returns a boolean true value 

97 """ 

98 from InvenTree.exceptions import log_error 

99 from plugin import PluginMixinEnum, registry 

100 

101 transition_plugins = registry.with_mixin(PluginMixinEnum.STATE_TRANSITION) 

102 

103 for plugin in transition_plugins: 103 ↛ 104line 103 didn't jump to line 104 because the loop on line 103 never started

104 try: 

105 handlers = plugin.get_transition_handlers() 

106 except Exception: 

107 log_error('get_transition_handlers', plugin=plugin) 

108 continue 

109 

110 if type(handlers) is not list: 

111 logger.error( 

112 'INVE-E9: Plugin %s returned invalid type for transition handlers', 

113 plugin.slug, 

114 ) 

115 continue 

116 

117 for handler in handlers: 

118 if not isinstance(handler, TransitionMethod): 

119 logger.error( 

120 'INVE-E9: Invalid transition handler type: %s', handler 

121 ) 

122 continue 

123 

124 # Call the transition method 

125 result = handler.transition( 

126 current_state, target_state, instance, default_action, **kwargs 

127 ) 

128 

129 if result: 

130 return result 

131 

132 # Default action 

133 return default_action(current_state, target_state, instance, **kwargs)