Ostinato includes a statemachine that will allow you to create complex workflows for your models. A common workflow, for example, is a publishing workflow where an item can be either private or public. The change from the one state to the next is called a transition.
In ostinato our main aim was to have the ability to “attach” a statemachine to a model, without having to change any fields on that model. So you can create your States and StateMachines completely independent of your models, and just attach it when needed.
Ok, lets build an actual statemachine so you can see how it works. For this example we will create the following statemachine:
For our example we will assume you are creating a statemachine for the following model:
class NewsItem(models.Model):
title = models.CharField(max_length=150)
content = models.TextField()
publish_date = models.DateTimeField(null=True, blank=True)
state = models.CharField(max_length=50, default='private')
We start by creating our States...
1 2 3 4 5 6 7 8 9 10 11 12 13 | from ostinato.statemachine import State, StateMachine
class Private(State):
verbose_name = 'Private'
transitions = {'publish': 'public'}
class Public(State):
verbose_name = 'Public'
transitions = {'retract': 'private', 'archive': 'archived'}
class Archived(State):
verbose_name = 'Archived'
transitions = {}
|
This is simple enough. Every state is a subclass of ostinato.statemachine.core.State and each of these states specifies two attributes.
verbose_name is just a nice human readable name.
the values is the target state for the transition.
Now we have to glue these states together into a statemachine.
1 2 3 | class NewsWorkflow(StateMachine):
state_map = {'private': Private, 'public': Public, 'archived': Archived}
initial_state = 'private'
|
values are the actual State subclass
initial_state is the starting state key
Thats all you need to set up a fully functioning statemachine.
Lets have a quick look at what this allows you to do:
>>> from odemo.news.models import NewsItem, NewsWorkflow
# We need an instance to work with. We just get one from the db in this case
>>> item = NewsItem.objects.get(id=1)
>>> item.state
u'public'
# Create a statemachine for our instance
>>> sm = NewsWorkflow(instance=item)
# We can see that the statemachine automatically takes on the state of the
# newsitem instance.
>>> sm.state
'Public'
# We can view available actions based on the current state
>>> sm.actions
['retract', 'archive']
# We can tell the statemachine to take action
>>> sm.take_action('retract')
# State is now changed in the statemachine ...
>>> sm.state
'Private'
# ... and we can see that our original instance was also updated.
>>> item.state
'private'
>>> item.save() # Now we save our news item
You can create custom action methods for states, which allows you to do extra stuff, like updating the publish_date.
Our example NewsItem already has a empty publish_date field, so lets create a method that will update the publish date when the publish action is performed.
1 2 3 4 5 6 7 8 9 | from django.utils import timezone
class Private(State):
verbose_name = 'Private'
transitions = {'publish': 'public'}
def publish(self, **kwargs):
if self.instance:
self.instance.publish_date = timezone.now()
|
Now, whenever the publish action is called on our statemachine, it will update the publish_date for the instance that was passed to the StateMachine when it was created.
Note
The name of the method is important. The State class tries to look for a method with the same name as the transition key.
You can use the kwargs to pass extra arguments to your custom methods. These arguments are passed through from the StateMachine.take_action() method eg.
sm.take_action('publish', author=request.user)
Integrating your statemachine into the admin is quite simple. You just need to use the statemachine form factory function that generates the form for your model, and then use that form in your ModelAdmin.
1 2 3 4 5 6 7 8 9 10 11 12 13 | from odemo.news.models import NewsItem, NewsWorkflow
from ostinato.statemachine.forms import sm_form_factory
class NewsItemAdmin(admin.ModelAdmin):
form = sm_form_factory(NewsWorkflow)
list_display = ('title', 'state', 'publish_date')
list_filter = ('state',)
date_hierarchy = 'publish_date'
admin.site.register(NewsItem, NewsItemAdmin)
|
Lines 2 and 6 are all that you need. sm_form_factory takes as it’s first argument your Statemachine Class.
The statemachine assumes by default that the model field that stores the state is called, state, but you can easilly tell the statemachine (and the statemachine form factory function) what the field name for the state will be.