[Python-modules-commits] [django-fsm] 01/06: Import django-fsm_2.4.0.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Fri May 20 08:39:36 UTC 2016


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository django-fsm.

commit db9e9746e83da3f9dff579fecd3b8cb3193066b2
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Fri May 20 10:14:10 2016 +0200

    Import django-fsm_2.4.0.orig.tar.gz
---
 .checkignore                                       |   2 +
 .travis.yml                                        |  17 ++-
 CHANGELOG.rst                                      | 101 +++++++++++++++
 README.rst                                         | 137 ++++++---------------
 django_fsm/__init__.py                             |  62 +++++++---
 .../management/commands/graph_transitions.py       |  11 +-
 django_fsm/tests/test_basic_transitions.py         |  87 ++++++++++++-
 setup.py                                           |   8 +-
 tests/testapp/models.py                            |   8 +-
 tests/testapp/tests/test_multi_resultstate.py      |  40 ++++++
 tests/testapp/tests/test_string_field_parameter.py |  32 +++++
 tox.ini                                            |  64 +++++-----
 12 files changed, 407 insertions(+), 162 deletions(-)

diff --git a/.checkignore b/.checkignore
new file mode 100644
index 0000000..e73d656
--- /dev/null
+++ b/.checkignore
@@ -0,0 +1,2 @@
+tests/*
+django_fsm/tests/*
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 0f4a34f..9497fc2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,15 @@
 language: python
-env:
-    - $TOX_ENV=py26
-    - $TOX_ENV=py27
-    - $TOX_ENV=py33
-    - $TOX_ENV=py34
+sudo: false
+
+python:
+  - 2.6
+  - 2.7
+  - 3.3
+  - 3.4
+  - 3.5
+
 install:
     - pip install tox
+
 script:
-    - tox -e $TOX_ENV
+    - tox --skip-missing-interpreters
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..a804f97
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,101 @@
+Changelog
+=========
+
+django-fsm 2.4.0 2016-05-14
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- graph_transition commnad now works with multiple  FSM's per model
+- Add ability to set target state from transition return value or callable
+
+
+django-fsm 2.3.0 2015-10-15
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Add source state shortcut '+' to specify transitions from all states except the target
+- Add object-level permission checks
+- Fix translated labels for graph of FSMIntegerField
+- Fix multiple signals for several transition decorators
+
+
+django-fsm 2.2.1 2015-04-27
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Improved exception message for unmet transition conditions.
+- Don't send post transition signal in case of no state changes on
+  exception
+- Allow empty string as correct state value
+- Improved graphviz fsm visualisation
+- Clean django 1.8 warnings
+
+django-fsm 2.2.0 2014-09-03
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Support for `class
+  substitution <http://schinckel.net/2013/06/13/django-proxy-model-state-machine/>`__
+  to proxy classes depending on the state
+- Added ConcurrentTransitionMixin with optimistic locking support
+- Default db\_index=True for FSMIntegerField removed
+- Graph transition code migrated to new graphviz library with python 3
+  support
+- Ability to change state on transition exception
+
+django-fsm 2.1.0 2014-05-15
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Support for attaching permission checks on model transitions
+
+django-fsm 2.0.0 2014-03-15
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Backward incompatible release
+- All public code import moved directly to django\_fsm package
+- Correct support for several @transitions decorator with different
+  source states and conditions on same method
+- save parameter from transition decorator removed
+- get\_available\_FIELD\_transitions return Transition data object
+  instead of tuple
+- Models got get\_available\_FIELD\_transitions, even if field
+  specified as string reference
+- New get\_all\_FIELD\_transitions method contributed to class
+
+django-fsm 1.6.0 2014-03-15
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- FSMIntegerField and FSMKeyField support
+
+django-fsm 1.5.1 2014-01-04
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Ad-hoc support for state fields from proxy and inherited models
+
+django-fsm 1.5.0 2013-09-17
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Python 3 compatibility
+
+django-fsm 1.4.0 2011-12-21
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Add graph\_transition command for drawing state transition picture
+
+django-fsm 1.3.0 2011-07-28
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Add direct field modification protection
+
+django-fsm 1.2.0 2011-03-23
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Add pre\_transition and post\_transition signals
+
+django-fsm 1.1.0 2011-02-22
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Add support for transition conditions
+- Allow multiple FSMField in one model
+- Contribute get\_available\_FIELD\_transitions for model class
+
+django-fsm 1.0.0 2010-10-12
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Initial public release
diff --git a/README.rst b/README.rst
index 7cbe137..1d52913 100644
--- a/README.rst
+++ b/README.rst
@@ -3,7 +3,13 @@ Django friendly finite state machine support
 
 |Build Status| |Downloads| |Gitter|
 
-django-fsm adds declarative states management for django models.
+django-fsm adds simple declarative states management for django models.
+
+If you need parallel task execution, view and background task code reuse
+over different flows - check my new project django-viewflow:
+
+https://github.com/viewflow/viewflow
+
 
 Instead of adding some state field to a django model, and managing its
 values by hand, you could use FSMState field and mark model methods with
@@ -28,10 +34,6 @@ FSM really helps to structure the code, especially when a new developer
 comes to the project. FSM is most effective when you use it for some
 sequential steps.
 
-If you need parallel task execution, view and background task code reuse
-over different flows - check my new project django-viewflow
-
-https://github.com/kmmbvnr/django-viewflow
 
 Installation
 ------------
@@ -74,7 +76,8 @@ Use the ``transition`` decorator to annotate model methods
 
 ``source`` parameter accepts a list of states, or an individual state.
 You can use ``*`` for source, to allow switching to ``target`` from any
-state.
+state. The ``field`` parameter accepts both a string attribute name or an
+actual field instance.
 
 If calling publish() succeeds without raising an exception, the state
 field will be changed, but not written to the database.
@@ -149,6 +152,30 @@ Note that calling
 ```refresh_from_db`` <https://docs.djangoproject.com/en/1.8/ref/models/instances/#django.db.models.Model.refresh_from_db>`__
 on a model instance with a protected FSMField will cause an exception.
 
+`target`
+~~~~~~~~
+
+`target` state parameter could points to the specific state or `django_fsm.State` implementation
+
+.. code:: python
+          
+    from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE
+    @transition(field=state,
+                source='*',
+                target=RETURN_VALUE('for_moderators', 'published'))
+    def publish(self, is_public=False):
+        return 'need_moderation' if is_public else 'published'
+
+    @transition(
+        field=state,
+        source='for_moderators',
+        target=GET_STATE(
+            lambda self, allowed: 'published' if allowed else 'rejected',
+            states=['published', 'rejected']))
+    def moderate(self, allowed):
+        self.allowed=allowed
+
+
 ``custom`` properties
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -177,7 +204,7 @@ specific target state
     @transition(field=state, source='new', target='published', on_error='failed')
     def publish(self):
        """
-       Some exceptio could happends here
+       Some exception could happen here
        """
 
 ``state_choices``
@@ -208,7 +235,7 @@ perform the transition
 .. code:: python
 
     @transition(field=state, source='*', target='publish',
-                permission=lambda user: not user.has_perm('myapp.can_make_mistakes'))
+                permission=lambda instance, user: not user.has_perm('myapp.can_make_mistakes'))
     def publish(self):
         pass
 
@@ -323,7 +350,7 @@ Arguments sent with these signals:
 
 **sender** The model class.
 
-**instance** The actual instance being procceed
+**instance** The actual instance being proceed
 
 **name** Transition name
 
@@ -334,7 +361,7 @@ Arguments sent with these signals:
 Optimistic locking
 ------------------
 
-``django-fsm`` provides optimistic locking mixin, to avoid concurent
+``django-fsm`` provides optimistic locking mixin, to avoid concurrent
 model state changes. If model state was changed in database
 ``django_fsm.ConcurrentTransition`` exception would be raised on
 model.save()
@@ -384,97 +411,13 @@ your ``INSTALLED_APPS``:
 Changelog
 ---------
 
-django-fsm 2.3.0 2015-10-15
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Add source state shortcut '+' to specify transitions from all states except the target
--  Add object-level permission checks
--  Fix translated labels for graph of FSMIntegerField
--  Fix multiple signals for several transition decorators
-
-
-django-fsm 2.2.1 2015-04-27
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Improved exception message for unmet transition conditions.
--  Don't send post transiton signal in case of no state chnages on
-   exception
--  Allow empty string as correct state value
--  Imporved graphviz fsm visualisation
--  Clean django 1.8 warnings
-
-django-fsm 2.2.0 2014-09-03
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Support for `class
-   substitution <http://schinckel.net/2013/06/13/django-proxy-model-state-machine/>`__
-   to proxy classes depending on the state
--  Added ConcurrentTransitionMixin with optimistic locking support
--  Default db\_index=True for FSMIntegerField removed
--  Graph transition code migrated to new graphviz library with python 3
-   support
--  Ability to change state on transition exception
-
-django-fsm 2.1.0 2014-05-15
+django-fsm 2.4.0 2016-05-14
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
--  Support for attaching permission checks on model transitions
+- graph_transition commnad now works with multiple  FSM's per model
+- Add ability to set target state from transition return value or callable
 
-django-fsm 2.0.0 2014-03-15
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Backward incompatible release
--  All public code import moved directly to django\_fsm package
--  Correct support for several @transitions decorator with different
-   source states and conditions on same method
--  save parameter from transition decorator removed
--  get\_available\_FIELD\_transitions return Transition data object
-   instead of tuple
--  Models got get\_available\_FIELD\_transitions, even if field
-   specified as string reference
--  New get\_all\_FIELD\_transitions method contributed to class
-
-django-fsm 1.6.0 2014-03-15
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  FSMIntegerField and FSMKeyField support
-
-django-fsm 1.5.1 2014-01-04
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Ad-hoc support for state fields from proxy and inherited models
-
-django-fsm 1.5.0 2013-09-17
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Python 3 compatibility
-
-django-fsm 1.4.0 2011-12-21
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Add graph\_transition command for drawing state transition picture
-
-django-fsm 1.3.0 2011-07-28
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Add direct field modification protection
-
-django-fsm 1.2.0 2011-03-23
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Add pre\_transition and post\_transition signals
-
-django-fsm 1.1.0 2011-02-22
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
--  Add support for transition conditions
--  Allow multiple FSMField in one model
--  Contribute get\_available\_FIELD\_transitions for model class
-
-django-fsm 1.0.0 2010-10-12
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
--  Initial public release
 
 .. |Build Status| image:: https://travis-ci.org/kmmbvnr/django-fsm.svg?branch=master
    :target: https://travis-ci.org/kmmbvnr/django-fsm
diff --git a/django_fsm/__init__.py b/django_fsm/__init__.py
index 56c8457..74e0274 100644
--- a/django_fsm/__init__.py
+++ b/django_fsm/__init__.py
@@ -4,7 +4,6 @@ State tracking functionality for django models
 """
 import inspect
 import sys
-import warnings
 from functools import wraps
 
 from django.db import models
@@ -65,6 +64,10 @@ class TransitionNotAllowed(Exception):
         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
@@ -91,11 +94,7 @@ class Transition(object):
         if not self.permission:
             return True
         elif callable(self.permission):
-            try:
-                return bool(self.permission(instance, user))
-            except TypeError:
-                warnings.warn('Callable permissions without instance parameter is depricated')
-                return bool(self.permission(user))
+            return bool(self.permission(instance, user))
         elif user.has_perm(self.permission, instance):
             return True
         elif user.has_perm(self.permission):
@@ -114,12 +113,8 @@ def get_available_FIELD_transitions(instance, field):
 
     for name, transition in transitions.items():
         meta = transition._django_fsm
-
-        for state in [curr_state, '*', '+']:
-            if state in meta.transitions:
-                transition = meta.transitions[state]
-                if all(map(lambda condition: condition(instance), transition.conditions)):
-                    yield transition
+        if meta.has_transition(curr_state) and meta.conditions_met(instance, curr_state):
+            yield meta.get_transition(curr_state)
 
 
 def get_all_FIELD_transitions(instance, field):
@@ -322,6 +317,10 @@ class FSMFieldMixin(object):
         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:
@@ -548,6 +547,39 @@ def has_transition_perm(bound_method, user):
     im_self = getattr(bound_method, 'im_self', getattr(bound_method, '__self__'))
     current_state = meta.field.get_state(im_self)
 
-    return (meta.has_transition(current_state)
-            and meta.conditions_met(im_self, current_state)
-            and meta.has_transition_perm(im_self, current_state, 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
diff --git a/django_fsm/management/commands/graph_transitions.py b/django_fsm/management/commands/graph_transitions.py
index 0a2b493..bf4a636 100644
--- a/django_fsm/management/commands/graph_transitions.py
+++ b/django_fsm/management/commands/graph_transitions.py
@@ -42,8 +42,8 @@ def generate_dot(fields_data):
             elif transition.source == '+':
                 any_except_targets.add((transition.target, transition.name))
             else:
+                source_name = node_name(field, transition.source)
                 if transition.target is not None:
-                    source_name = node_name(field, transition.source)
                     target_name = node_name(field, transition.target)
                     if isinstance(transition.source, int):
                         source_label = [smart_text(name[1]) for name in field.choices if name[0] == transition.source][0]
@@ -78,8 +78,8 @@ def generate_dot(fields_data):
         # construct subgraph
         opts = field.model._meta
         subgraph = graphviz.Digraph(
-            name="cluster_%s_%s" % (opts.app_label, opts.object_name),
-            graph_attr={'label': "%s.%s" % (opts.app_label, opts.object_name)})
+            name="cluster_%s_%s_%s" % (opts.app_label, opts.object_name, field.name),
+            graph_attr={'label': "%s.%s.%s" % (opts.app_label, opts.object_name, field.name)})
 
         final_states = targets - sources
         for name, label in final_states:
@@ -88,8 +88,9 @@ def generate_dot(fields_data):
             subgraph.node(name, label=label, shape='circle')
             if field.default:  # Adding initial state notation
                 if label == field.default:
-                    subgraph.node('.', shape='point')
-                    subgraph.edge('.', name)
+                    initial_name = node_name(field, '_initial')
+                    subgraph.node(name=initial_name, label='', shape='point')
+                    subgraph.edge(initial_name, name)
         for source_name, target_name, attrs in edges:
             subgraph.edge(source_name, target_name, **dict(attrs))
 
diff --git a/django_fsm/tests/test_basic_transitions.py b/django_fsm/tests/test_basic_transitions.py
index 026f8aa..db8e3ac 100644
--- a/django_fsm/tests/test_basic_transitions.py
+++ b/django_fsm/tests/test_basic_transitions.py
@@ -32,6 +32,10 @@ class BlogPost(models.Model):
     def moderate(self):
         pass
 
+    @transition(source='+', target='blocked', field=state)
+    def block(self):
+        pass
+
     @transition(source='*', target='', field=state)
     def empty(self):
         pass
@@ -87,6 +91,22 @@ class FSMFieldTest(TestCase):
         self.model.moderate()
         self.assertEqual(self.model.state, 'moderated')
 
+    def test_plus_shortcut_succeeds_for_other_source(self):
+        """Tests that the '+' shortcut succeeds for a source
+        other than the target.
+        """
+        self.assertTrue(can_proceed(self.model.block))
+        self.model.block()
+        self.assertEqual(self.model.state, 'blocked')
+
+    def test_plus_shortcut_fails_for_same_source(self):
+        """Tests that the '+' shortcut fails if the source
+        equals the target.
+        """
+        self.model.block()
+        self.assertFalse(can_proceed(self.model.block))
+        self.assertRaises(TransitionNotAllowed, self.model.block)
+
     def test_empty_string_target(self):
         self.model.empty()
         self.assertEqual(self.model.state, '')
@@ -123,8 +143,68 @@ class TestFieldTransitionsInspect(TestCase):
     def setUp(self):
         self.model = BlogPost()
 
-    def test_available_conditions(self):
-        pass
+    def test_available_conditions_from_new(self):
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('new', 'published'),
+                        ('new', 'removed'),
+                        ('*', ''),
+                        ('+', 'blocked')])
+        self.assertEqual(actual, expected)
+
+    def test_available_conditions_from_published(self):
+        self.model.publish()
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('published', None),
+                        ('published', 'hidden'),
+                        ('published', 'stolen'),
+                        ('*', ''),
+                        ('+', 'blocked')])
+        self.assertEqual(actual, expected)
+
+    def test_available_conditions_from_hidden(self):
+        self.model.publish()
+        self.model.hide()
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('hidden', 'stolen'),
+                        ('*', ''),
+                        ('+', 'blocked')])
+        self.assertEqual(actual, expected)
+
+    def test_available_conditions_from_stolen(self):
+        self.model.publish()
+        self.model.steal()
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('*', ''),
+                        ('+', 'blocked')])
+        self.assertEqual(actual, expected)
+
+    
+    def test_available_conditions_from_blocked(self):
+        self.model.block()
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('*', '')])
+        self.assertEqual(actual, expected)
+        
+
+    def test_available_conditions_from_empty(self):
+        self.model.empty()
+        transitions = self.model.get_available_state_transitions()
+        actual = set((transition.source, transition.target) for transition in transitions)
+        expected = set([('*', 'moderated'),
+                        ('*', ''),
+                        ('+', 'blocked')])
+        self.assertEqual(actual, expected)
+        
 
     def test_all_conditions(self):
         transitions = self.model.get_all_state_transitions()
@@ -137,5 +217,6 @@ class TestFieldTransitionsInspect(TestCase):
                         ('published', 'hidden'),
                         ('published', 'stolen'),
                         ('hidden', 'stolen'),
-                        ('*', '')])
+                        ('*', ''),
+                        ('+', 'blocked')])
         self.assertEqual(actual, expected)
diff --git a/setup.py b/setup.py
index c031b1d..68eb170 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ except IOError:
 
 setup(
     name='django-fsm',
-    version='2.3.0',
+    version='2.4.0',
     description='Django friendly finite state machine support.',
     author='Mikhail Podgurskiy',
     author_email='kmmbvnr at gmail.com',
@@ -24,10 +24,16 @@ setup(
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
         'Operating System :: OS Independent',
+        "Framework :: Django",
+        "Framework :: Django :: 1.8",
+        "Framework :: Django :: 1.9",
         'Programming Language :: Python',
         'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
         'Framework :: Django',
+        'Topic :: Software Development :: Libraries :: Python Modules',
     ]
 )
diff --git a/tests/testapp/models.py b/tests/testapp/models.py
index 4582164..49ce152 100644
--- a/tests/testapp/models.py
+++ b/tests/testapp/models.py
@@ -100,8 +100,12 @@ class BlogPost(models.Model):
     def hide(self):
         pass
 
-    @transition(field=state, source='new', target='removed',
-                on_error='failed', permission=lambda u: u.has_perm('testapp.can_remove_post'))
+    @transition(
+        field=state,
+        source='new',
+        target='removed',
+        on_error='failed',
+        permission=lambda self, u: u.has_perm('testapp.can_remove_post'))
     def remove(self):
         raise Exception('No rights to delete %s' % self)
 
diff --git a/tests/testapp/tests/test_multi_resultstate.py b/tests/testapp/tests/test_multi_resultstate.py
new file mode 100644
index 0000000..e7b385d
--- /dev/null
+++ b/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')
diff --git a/tests/testapp/tests/test_string_field_parameter.py b/tests/testapp/tests/test_string_field_parameter.py
new file mode 100644
index 0000000..9ba6e85
--- /dev/null
+++ b/tests/testapp/tests/test_string_field_parameter.py
@@ -0,0 +1,32 @@
+from django.db import models
+from django.test import TestCase
+from django_fsm import FSMField, transition
+
+
+class BlogPostWithStringField(models.Model):
+    state = FSMField(default='new')
+
+    @transition(field='state', source='new', target='published', conditions=[])
+    def publish(self):
+        pass
+
+    @transition(field='state', source='published', target='destroyed')
+    def destroy(self):
+        pass
+
+    @transition(field='state', source='published', target='review')
+    def review(self):
+        pass
+
+    class Meta:
+        app_label = 'testapp'
+
+
+class StringFieldTestCase(TestCase):
+    def setUp(self):
+        self.model = BlogPostWithStringField()
+
+    def test_initial_state(self):
+        self.assertEqual(self.model.state, 'new')
+        self.model.publish()
+        self.assertEqual(self.model.state, 'published')
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
index 6a99377..53e4e4b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,37 +1,35 @@
 [tox]
-envlist = py26, py27, py33, py34
+envlist =
+    py26-{dj16}
+    py27-{dj16,dj18,dj19}
+    py33-{dj16,dj18}
+    py{34,35}-{dj18,dj19}
+skipsdist = True
 
 [testenv]
-usedevelop = True
+basepython =
+    py26: python2.6
+    py27: python2.7
+    py33: python3.3
+    py34: python3.4
+    py35: python3.5
+deps =
+    py26: ipython==2.1.0
+    {py27,py32,py33,py34,py35}: ipython==4.1.1    
+    dj16: Django==1.6.11
+    dj16: django-jenkins==0.17.0
+    dj16: coverage<=3.999
+    dj16: django-guardian==1.3.2
+    dj18: Django==1.8.9
+    dj18: django-jenkins==0.18.1
+    dj18: coverage==4.0.3
+    dj18: django-guardian==1.4.1
+    dj19: Django==1.9.2
+    dj19: django-jenkins==0.18.1
+    dj19: coverage==4.0.3
+    dj19: django-guardian==1.4.1
+    graphviz==0.4.10
+    pep8==1.7.0
+    pyflakes==1.0.0
+    ipdb==0.8.1
 commands = python tests/manage.py {posargs:jenkins --pep8-max-line-length=150 --output-dir=reports/{envname}}
-deps = -r{toxinidir}/requirements.txt
-       graphviz>=0.4
-       django-jenkins
-       django-guardian
-       coverage
-       pep8
-       pyflakes
-       ipdb
-
-
-[testenv:py26]
-deps = django==1.6.5
-       ipython==2.1.0
-       graphviz>=0.4
-       django-jenkins==0.17.0
-       django-guardian
-       coverage
-       pep8
-       pyflakes
-       ipdb
-
-[testenv:alpha]
-basepython = python3.4
-deps = git+https://github.com/django/django.git
-       graphviz>=0.4
-       django-jenkins
-       django-guardian
-       coverage
-       pep8
-       pyflakes
-       ipdb

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-fsm.git



More information about the Python-modules-commits mailing list