Cesar Canassa
Cesar Canassa

Reputation: 20173

Need some advice regarding writing an reusable app for Django

I need to implement a finite-state-machine in order to keep track of a few of my Django project models. I already have a similar app doing that but it's heavily coupled with the others apps models and not reusable in any way. So I decided to re-factor it.

After a few hours, this is what I came up with:

class StateMachine(models.Model):
    name = models.CharField(max_length=50, help_text="Must be an unique name")
    template = models.BooleanField(default=True)

    current_state = models.ForeignKey('State', blank=True, null=True, related_name='current_state')
    initial_state = models.ForeignKey('State', blank=True, null=True, related_name='initial_state')

    def get_valid_actions(self):
        return Action.objects.filter(machine=self, from_state=self.current_state)

    def copy(self):
        ...

class State(models.Model):
    name = models.CharField(max_length=50)

    machine = models.ForeignKey(StateMachine)

    def enter_hook(self, machine=None, action=None, state=None):
        pass

    def exit_hook(self, machine=None, action=None, state=None):
        pass

    def _copy(self, machine):
        ...

class Action(models.Model):
    name = models.CharField(max_length=50)

    machine = models.ForeignKey(StateMachine)

    from_state = models.ForeignKey(State, related_name='from_state')
    to_state = models.ForeignKey(State, blank=True, null=True, related_name='to_state')

    def is_valid(self):
        if self.machine.current_state == self.from_state:
            return True
        else:
            return False

    def act(self):
        if self.is_valid():
            self.from_state.exit_hook(machine=self.machine, action=self, state=self.from_state)
            self.machine.current_state = self.to_state
            self.machine.save()
            self.to_state.enter_hook(machine=self.machine, action=self, state=self.to_state)
        else:
            raise ActionNotApplicable()

        return self.machine

    def _copy(self, machine):
        ...

I am happy with the results. It does what a state machine is expected to do and nothing else. In my models I am using it like this:

class SampleModel(models.Model):
    machine = models.ForeignKey(StateMachine, null=True)

    def setup_machine(self):
        self.machine = StateMachine.objects.get(template=True, name='bla bla bla').copy()
        self.save()

I basically create a "template" machine using the admin interface and then I run copy method which copies the state machine model and sets the template to False.

Now, here comes my questions :)

Thanks!

Upvotes: 1

Views: 339

Answers (1)

sebpiq
sebpiq

Reputation: 7802

I implemented myself a finite-state machine in python ... The code of the machine module itself has no Django... However, this machine was used to manage a state attribute on a Django model.

I think that the only field you really need to have is a state field. The rest should be only python declarations (unless you have a particular reason to save everything in your database).

Here is this finite-state machine stuff, you can take ideas from it, or even take the whole code and refactor it. There is very good documentation, and I think it is pretty clean and simple : http://dl.dropbox.com/u/1869644/state_automaton.zip (and you can generate diagrams to dot format !!!)

EDIT :

I wanna be able to link a particular state to an user

In this case (if you want to keep it generic) put the users field in a subclass of your state automaton. For example :

class UserAlertStateAutomaton(FiniteStateAutomaton):
    """
    An automaton that alerts users when the state changes
    """
    users_to_alert = models.ManyToManyField(User)

    def change_state(self, new_state):
        """
        overrides the parent method to alert users that state has changed
        """
        super(UserAlertStateAutomaton, self).change_state(new_state)
        for user in self.users_to_alert:
            #do your thing
    def subscribe#... etc

Which would still mean that you don't need to save anything else than the state in the base state automaton class. And by implementing a system of hooks (execute a method X when transition from state A to state B), you can pretty much do anything, and very simply : check the code I sent you !

Upvotes: 1

Related Questions