ostinato.statemachine — django-ostinato 0.9 documentation

### Navigation

- index
- next |
- previous |
- django-ostinato 0.9 documentation »

# ostinato.statemachine

## Overview

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.

- `transitions` is a dict where the keys are transition/action names, and

    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'

- `state_map` is a dict where keys are unique id’s/names for the states;

    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

## Custom Action methods

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)

## Admin Integration

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.

## Custom `state_field`

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.

- Statemachine - `sm = NewsWorkflow(instance=obj, state_field='field_name')`
- Form Factory - `sm_form_factory(NewsWorkflow, state_field='field_name')`

### Navigation

- index
- next |
- previous |
- django-ostinato 0.9 documentation »