Transition Options

Source

Specify one or multiple source states:

@state_field.transition(source={States.NEW, States.DONE}, target=States.CANCELED)
def cancel(self):
    pass

Use State.ANY to allow transition from any state except the target:

@state_field.transition(source=State.ANY, target=States.CANCELED)
def cancel(self):
    pass

Target

The state changes before the method runs. This lets you chain transitions:

@state_field.transition(source=States.NEW, target=States.IN_PROCESS)
def pay(self):
    try:
        self.perform_payment()
    except:
        self.error()

@state_field.transition(source=States.IN_PROCESS, target=States.DONE)
def perform_payment(self):
    pass

@state_field.transition(source=States.IN_PROCESS, target=States.ERROR)
def payment_error(self):
    pass

If you omit the target, no state change happens:

@state_field.transition(source=States.NEW)
def notify(self):
    mail_admins('New flow is waiting', self.text)

Stack multiple decorators for different source/target combinations:

@state_field.transition(source=States.NEW, target=States.DONE)
@state_field.transition(source=States.DONE, target=States.NEW)
def toggle(self):
    pass

Label

Add a human-readable label:

@state_field.transition(source=States.NEW, target=States.DONE)
@state_field.transition(source=States.DONE, target=States.NEW, label=_("Toggle back to New"))
def toggle(self):
    pass
toggle.label = _("Toggle report state")

Conditions

Require conditions to be met before a transition can happen. Conditions are functions that return True or False. They should not have side effects.

def can_publish(instance):
    # No publishing after 17 hours
    if datetime.datetime.now().hour > 17:
        return False
    return True

Or use a method on the flow class:

def can_destroy(self):
    return self.is_under_investigation()

Apply conditions:

@state_field.transition(States.NEW, target=States.DONE, conditions=[this.can_publish])
def publish(self):
    pass

Permissions

Attach permission checks to transitions. Use a callable that takes the flow instance and user:

@state_field.transition(
    source=States.NEW,
    target=States.DONE,
    permission=lambda flow, user: user.has_perm('myapp.delete_review', obj=flow.report)
)
def remove(self):
    pass

@state_field.transition(source=States.NEW, target=States.DONE, permission=this.is_owner)
def hide(self):
    pass

def is_owner(self, user):
    return self.author == user

Check permissions in your code:

if not flow.remove.has_perm(request.user):
    raise PermissionDenied

Custom Properties

Add custom metadata to transitions:

@state.transition(
    field=state,
    source=STATE.ANY,
    target=States.ON_HOLD,
    custom=dict(verbose='Hold for legal reasons'))
def legal_hold(self):
    """
    Side effects galore
    """