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
« 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."""
3from collections.abc import Callable
5from django.db.models import Model
7import structlog
9logger = structlog.get_logger('inventree')
12class TransitionMethod:
13 """Base class for all transition classes.
15 Must implement a method called `transition` that takes both args and kwargs.
16 """
18 def __init__(self) -> None:
19 """Check that the method is defined correctly.
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 )
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.
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
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.
62 Returns:
63 result: bool - True if the transition method was successful (and no further transitions are attempted), False otherwise.
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
73class StateTransitionMixin:
74 """Mixin class to enable state transitions.
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
82 def action(self, *args, **kwargs):
83 self.handle_transition(0, 1, self, self.some_dummy_function)
84 ```
85 """
87 def handle_transition(
88 self, current_state, target_state, instance, default_action, **kwargs
89 ):
90 """Handle a state transition for an object.
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
101 transition_plugins = registry.with_mixin(PluginMixinEnum.STATE_TRANSITION)
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
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
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
124 # Call the transition method
125 result = handler.transition(
126 current_state, target_state, instance, default_action, **kwargs
127 )
129 if result:
130 return result
132 # Default action
133 return default_action(current_state, target_state, instance, **kwargs)