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_s开发者_运维百科tate = 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 :)
Is using a ForeignKey in the SampleModel the best way to attach the StateMachine to it? I've read about Generic Relations in Django but I've never used it before. Using it would be beneficial in my scenario?
I am trying to follow the "Do one thing and do it well" philosophy, but I've other business requirements to implement. For example, I need to send to send an e-mail to a specific group every time a state changes, and this group varies from state to state. The first solution I came up was to add an "email" field in the state model. But this would violate what this app proposes to do which is simply to keep track of a state machine. What would be the best way to solve this problem?
I also include some hook functions in the model so that someone could attach custom behavior later, is this the best way to do it? (I think Django was already a signal system)
Any other ideas/comments? I am new to this reusable apps thing :)
Thanks!
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 !
精彩评论