You can specify a single transition source or list, tuple, set of source states.
There is the special State.ANY
marker, allows
to specify a transition from any state except current to a selected target
@stage.transition(source={Stage.NEW, Stage.DONE}, target=Stage.CANCELED)
def cancel(self):
pass
A target could be a single value. State change happens before the actual function call, that makes possible to chain transition methods execution.
@stage.transition(source=Stage.NEW, target=Stage.IN_PROCESS)
def pay(self):
try:
self.perform_payment()
except:
self.error()
@stage.transition(source=Stage.IN_PROCESS, target=Stage.DONE)
def perform_payment(self):
pass
@stage.transition(source=State.IN_PROCESS, target=Stage.ERROR)
def payment_error(self):
pass
If target is not provided, no state change would happen.
@stage.transition(source=Stage.NEW)
def notify(self):
mail_admins('New flow is waiting', self.text)
To specify different source/target on a single method, multiple transition decorators could be used
@stage.transition(source=Stage.NEW, target=State.DONE)
@stage.transition(source=Stage.DONE, target=State.NEW)
def toggle(self):
pass
Human readable transition label could be specified as .label attribute of a transition method, or passed as parameter to @State.transition decorator
@stage.transition(source=Stage.NEW, target=State.DONE)
@stage.transition(source=Stage.DONE, target=State.NEW, label=_("Toggle back to New"))
def toggle(self):
pass
toggle.label = _("Toggle report state")
If some conditions are required to be met before changing the state, use the conditions argument to transition. conditions must be a list of functions taking one argument, the model instance. The function must return either True or False or a value that evaluates to True or False. If all functions return True, all conditions are considered to be met and the transition is allowed to happen. If one of the functions returns False, the transition will not happen. These functions should not have any side effects.
You can use ordinary functions
def can_publish(instance):
# No publishing after 17 hours
if datetime.datetime.now().hour > 17:
return False
return True
Or model methods
def can_destroy(self):
return self.is_under_investigation()
Use the conditions like this:
@state.transition(Stage.NEW, target=State.DONE, conditions=[this.can_publish])
def publish(self):
pass
It is common to have permissions attached to each model transition.
State
handles this with permission keyword on the
transition decorator. permission accepts a this-reference or callable that
expects instance and user arguments and returns True if the user can perform the
transition.
@stage.transition(
source=Stage.NEW,
target=State.DONE
permission=lambda flow, user: user.has_perm('myapp.delete_review', obj=flow.report)
)
def remove(self):
pass
@stage.transition(source=Stage.NEW, target=State.DONE, permission=this.is_owner)
def hide(self):
pass
def is_owner(self, user):
return self.author == user
Usage:
if not flow.remove.has_permission(request.user):
raise PermissionDenied
Custom properties can be added by providing a dictionary to the custom keyword on the transition decorator.
@state.transition(
field=state,
source=STATE.ANY,
target=STATE.ON_HOLD,
custom=dict(verbose='Hold for legal reasons'))
def legal_hold(self):
"""
Side effects galore
"""