Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

Commit

Permalink
Support for Non-deterministic FSM? #129 and callble target state #61
Browse files Browse the repository at this point in the history
  • Loading branch information
kmmbvnr committed May 14, 2016
1 parent 0aa798c commit 56ec31d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 0 deletions.
41 changes: 41 additions & 0 deletions django_fsm/__init__.py
Expand Up @@ -65,6 +65,10 @@ def __init__(self, *args, **kwargs):
super(TransitionNotAllowed, self).__init__(*args, **kwargs)


class InvalidResultState(Exception):
"""Raised when we got invalid result state"""


class ConcurrentTransition(Exception):
"""
Raised when the transition cannot be executed because the
Expand Down Expand Up @@ -318,6 +322,10 @@ def change_state(self, instance, method, *args, **kwargs):
try:
result = method(instance, *args, **kwargs)
if next_state is not None:
if hasattr(next_state, 'get_state'):
next_state = next_state.get_state(
instance, transition, result,
args=args, kwargs=kwargs)
self.set_proxy(instance, next_state)
self.set_state(instance, next_state)
except Exception as exc:
Expand Down Expand Up @@ -547,3 +555,36 @@ def has_transition_perm(bound_method, user):
return (meta.has_transition(current_state) and
meta.conditions_met(im_self, current_state) and
meta.has_transition_perm(im_self, current_state, user))


class State(object):
def get_state(self, model, transition, result, args=[], kwargs={}):
raise NotImplementedError


class RETURN_VALUE(State):
def __init__(self, *allowed_states):
self.allowed_states = allowed_states if allowed_states else None

def get_state(self, model, transition, result, args=[], kwargs={}):
if self.allowed_states is not None:
if result not in self.allowed_states:
raise InvalidResultState(
'{} is not in list of allowed states\n{}'.format(
result, self.allowed_states))
return result


class GET_STATE(State):
def __init__(self, func, states=None):
self.func = func
self.allowed_states = states

def get_state(self, model, transition, result, args=[], kwargs={}):
result_state = self.func(model, *args, **kwargs)
if self.allowed_states is not None:
if result_state not in self.allowed_states:
raise InvalidResultState(
'{} is not in list of allowed states\n{}'.format(
result, self.allowed_states))
return result_state
40 changes: 40 additions & 0 deletions tests/testapp/tests/test_multi_resultstate.py
@@ -0,0 +1,40 @@
from django.db import models
from django.test import TestCase
from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE


class MultiResultTest(models.Model):
state = FSMField(default='new')

@transition(
field=state,
source='new',
target=RETURN_VALUE('for_moderators', 'published'))
def publish(self, is_public=False):
return 'published' if is_public else 'for_moderators'

@transition(
field=state,
source='for_moderators',
target=GET_STATE(
lambda self, allowed: 'published' if allowed else 'rejected',
states=['published', 'rejected']
)
)
def moderate(self, allowed):
pass

class Meta:
app_label = 'testapp'


class Test(TestCase):
def test_return_state_succeed(self):
instance = MultiResultTest()
instance.publish(is_public=True)
self.assertEqual(instance.state, 'published')

def test_get_state_succeed(self):
instance = MultiResultTest(state='for_moderators')
instance.moderate(allowed=False)
self.assertEqual(instance.state, 'rejected')

0 comments on commit 56ec31d

Please sign in to comment.