transmission
transmission
is the place where we store a finite state machine
design pattern built for Django.
It includes a decorator called transition
that is used to declaratively state
the conditions that the model can transition from and to. In the process of executing
a transition a transition_event
signal is triggered. Receivers that connect to
this signal can execute additional logic after the transition has occured.
Usage
Transitions
transmission.utils.transition
Signature transmission.utils.transition(state, from_state, to_state, [is_mutable=True, **extra_data])(model_method)
transition
is a model method decorator that requires the following args:
-
state: [str] The name of the field that holds the model's state
information. N.B. The field must be a choice field through use of the
choices
kwarg on the field declaration. -
from_state: [value or list of values] This is the declaration of states
that the field can be in when the transition begins else an
IntegrityError
is raised. - to_state: [value] This is the state the the model is transitioning to.
-
is_mutable: [bool] Optional flag that indicates whether or not the
state field should be updated to the value given by the
to_state
. Default value is True. - extra_data: [kwargs] This is optional keyword args that will be passed to the pre and post transition event handlers.
from django.db import models
from django.utils.translation import ugettext as _
from transmission.utils import transition
class MyModel(models.Model):
DRAFT = 'draft'
ACTIVE = 'active'
WHATEVER = 'whatever'
REMOVED = 'removed'
STATES = (
(DRAFT, 'draft'),
(ACTIVE, 'active'),
(WHATEVER, 'whatever'),
(REMOVED, 'fake deletion')
)
state = models.CharField(
_("the model's current state"), editable=False, max_length=256,
db_index=True, choices=STATES, default=DRAFT, blank=True
)
name = models.CharField(
_("An arbitrary name field for demo"), max_length=256
)
...
@transition('state', DRAFT, ACTIVE)
def activate(self):
# you can do whatever you want in here
# e.g.
print "hello suckers!!!! I'm polluting your terminal!!"
# perhaps you want a cascading transition
self.someothermodel.activate()
# ALSO NOTE WORTHY ... a `transmission.signals.transition_event`
# signal is emitted when this model method is hit
@transition('state', [DRAFT, ACTIVE, WHATEVER], REMOVED)
def remove(self):
# ALSO NOTE that you don't have to do anything in the transition
# method AND that you have multiple potential starting states but ONLY
# one final state
pass
...
my_instance = MyModel(name='my weird name')
print my_instance.state # prints default 'draft'
my_instance.activate() # returns None
instance = MyModel.objects.get(name='my weird name')
print instance.state # prints 'active'
transmission.utils.property_transition
Signature transmission.utils.transition(state, from_state, [**extra_data])(model_method)
property_transition
is a simple wrapper around transition
that provides a
simple interface for specifying transitions based on model properties.
- state: [str] The name of the property that holds the model's state information.
-
from_state: [value or list of values] This is the declaration of states
that the property can be in when the transition begins else an
IntegrityError
is raised. - extra_data: [kwargs] This is optional keyword args that will be passed to the pre and post transition event handlers.
Transition Events
transmission.signals.pre_transition_event
Signature
The pre_transition_event
signal is triggered just before the transition method
is attempted.
N.B. This signal will be sent be irrespective of any errors raised during the actual transition. As such this event signal should NOT be used to any logic that assumes the success of the transition.
Receivers that connect to pre_transition_event
should expect the following
keyword arguments to be passed them:
- instance: An instance of a Django model
- state: (str) The name of the field that is used to maintain state
- from_state: The value associated to the state of the model before the transition was executed. Note that the date type is dependent on the field data type chosen for the "state" field.
- to_state: The value associated to the state of the model after the transition was executed.
- transition_name: The name of the transition function that triggered the signal.
-
uuid: A uuid4 str unique to the whole transition. N.B. This value will be
the same for the
post_transition_event
. - decorator_kwargs: This contain extra data specified when defining the transition using the decorator.
transmission.signals.post_transition_event
Signature
The post_transition_event
signal is triggered after the transition method
has successfully completed and the model has been saved.
Receivers that connect to post_transition_event
should expect the following
keyword arguments to be passed them:
- instance: An instance of a Django model
- state: (str) The name of the field that is used to maintain state
- from_state: The value associated to the state of the model before the transition was executed. Note that the date type is dependent on the field data type chosen for the "state" field.
- to_state: The value associated to the state of the model after the transition was executed.
- transition_name: The name of the transition function that triggered the signal.
-
uuid: A uuid4 str unique to the whole transition. N.B. This value will be
the same for the
pre_transition_event
. - decorator_kwargs: This contain extra data specified when defining the transition using the decorator.
# in myapp/apps.py
from django.apps import AppConfig
from transmission.signals import post_transition_event
def on_mymodel_activated(
instance, state, from_state, to_state, transition_name, **kwargs
):
if to_state == instance.ACTIVE:
print 'oh well, hello mr. active!!!'
class TestAppConfig(AppConfig):
name = 'mpapp'
verbose_name = "My App"
def ready(self):
post_transition_event.connect(
on_mymodel_activated, dispatch_uid='something random'
)
my_instance = MyModel(name='my weird name')
print my_instance.state # prints 'draft'
my_instance.activate() # prints 'oh well, hello mr. active!!!'
Development
Installation
git clone git@github.com:hangarunderground/transmission.git
cd transmission
virtualenv venv --no-site-packages --distribute
pip install Setuptools --upgrade
pip install -e .[dev]
Testing
Test the Jeebuz out of it. JJEeeeeeeebbbuzz!
Just write your tests in transmission/tests/testproject/testapp/tests.py
then
in your terminal...
tox
See it's easy.
Misc
Reason behind the name
In a car the transmission is the place where the car's gear state is managed and as a result it is the place where the car's "power" is controlled.
... Also I was listening to Joy Division at the time. And yes it's funny that the song is about a radio broadcast, much like a signal broadcast...