[Python-modules-commits] [django-fsm] 01/01: Import django-fsm_2.5.0.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Mon Mar 6 11:27:44 UTC 2017
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch upstream
in repository django-fsm.
commit ea76bf56c8d55449bc65ca6ccd5fec3e234191b1
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Mon Mar 6 11:26:41 2017 +0100
Import django-fsm_2.5.0.orig.tar.gz
---
CHANGELOG.rst | 9 +++
README.rst | 30 ++++-----
django_fsm/__init__.py | 20 +++---
.../management/commands/graph_transitions.py | 71 +++++++++++++++-------
setup.cfg | 2 +
setup.py | 3 +-
tests/testapp/tests/test_multi_resultstate.py | 29 +++++++++
tox.ini | 38 ++++++------
8 files changed, 139 insertions(+), 63 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a804f97..9d38bd5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,15 @@
Changelog
=========
+django-fsm 2.5.0 2017-03-04
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- graph_transition command fix for django 1.10
+- graph_transition command supports GET_STATE targets
+- signal data extended with method args/kwargs and field
+- sets allowed to be passed to the transition decorator
+
+
django-fsm 2.4.0 2016-05-14
~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/README.rst b/README.rst
index 1d52913..31cca8b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
Django friendly finite state machine support
============================================
-|Build Status| |Downloads| |Gitter|
+|Build Status| |Gitter|
django-fsm adds simple declarative states management for django models.
@@ -149,13 +149,13 @@ direct state field modification.
model.state = 'invalid' # Raises AttributeError
Note that calling
-```refresh_from_db`` <https://docs.djangoproject.com/en/1.8/ref/models/instances/#django.db.models.Model.refresh_from_db>`__
+`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
+`target` state parameter could point to a specific state or `django_fsm.State` implementation
.. code:: python
@@ -164,7 +164,7 @@ on a model instance with a protected FSMField will cause an exception.
source='*',
target=RETURN_VALUE('for_moderators', 'published'))
def publish(self, is_public=False):
- return 'need_moderation' if is_public else 'published'
+ return 'for_moderators' if is_public else 'published'
@transition(
field=state,
@@ -229,8 +229,8 @@ Permissions
It is common to have permissions attached to each model transition.
``django-fsm`` handles this with ``permission`` keyword on the
``transition`` decorator. ``permission`` accepts a permission string, or
-callable that expects ``user`` argument and returns True if user can
-perform the transition
+callable that expects ``instance`` and ``user`` arguments and returns
+True if user can perform the transition.
.. code:: python
@@ -374,10 +374,10 @@ model.save()
state = FSMField(default='new')
For guaranteed protection against race conditions caused by concurrently
-executed transitions, make sure: \* Your transitions do not have any
-side effects except for changes in the database, \* You always run the
-save() method on the object within ``django.db.transaction.atomic()``
-block.
+executed transitions, make sure:
+
+- Your transitions do not have any side effects except for changes in the database,
+- You always run the save() method on the object within ``django.db.transaction.atomic()`` block.
Following these recommendations, you can rely on
ConcurrentTransitionMixin to cause a rollback of all the changes that
@@ -411,17 +411,17 @@ your ``INSTALLED_APPS``:
Changelog
---------
-django-fsm 2.4.0 2016-05-14
+django-fsm 2.5.0 2017-03-04
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- graph_transition commnad now works with multiple FSM's per model
-- Add ability to set target state from transition return value or callable
+- graph_transition command fix for django 1.10
+- graph_transition command supports GET_STATE targets
+- signal data extended with method args/kwargs and field
+- sets allowed to be passed to the transition decorator
.. |Build Status| image:: https://travis-ci.org/kmmbvnr/django-fsm.svg?branch=master
:target: https://travis-ci.org/kmmbvnr/django-fsm
-.. |Downloads| image:: https://img.shields.io/pypi/dm/django-fsm.svg
- :target: https://pypi.python.org/pypi/django-fsm
.. |Gitter| image:: https://badges.gitter.im/Join%20Chat.svg
:target: https://gitter.im/kmmbvnr/django-fsm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
diff --git a/django_fsm/__init__.py b/django_fsm/__init__.py
index 74e0274..d93cff9 100644
--- a/django_fsm/__init__.py
+++ b/django_fsm/__init__.py
@@ -245,7 +245,7 @@ class FSMFieldMixin(object):
state_choices = kwargs.pop('state_choices', None)
choices = kwargs.get('choices', None)
if state_choices is not None and choices is not None:
- raise ValueError('Use one of choices or state_choces value')
+ raise ValueError('Use one of choices or state_choices value')
if state_choices is not None:
choices = []
@@ -308,8 +308,11 @@ class FSMFieldMixin(object):
'sender': instance.__class__,
'instance': instance,
'name': method_name,
+ 'field': meta.field,
'source': current_state,
- 'target': next_state
+ 'target': next_state,
+ 'method_args' : args,
+ 'method_kwargs' : kwargs
}
pre_transition.send(**signal_kwargs)
@@ -321,6 +324,7 @@ class FSMFieldMixin(object):
next_state = next_state.get_state(
instance, transition, result,
args=args, kwargs=kwargs)
+ signal_kwargs['target'] = next_state
self.set_proxy(instance, next_state)
self.set_state(instance, next_state)
except Exception as exc:
@@ -349,10 +353,10 @@ class FSMFieldMixin(object):
for transition in meta.transitions.values():
yield transition
- def contribute_to_class(self, cls, name, virtual_only=False):
+ def contribute_to_class(self, cls, name, **kwargs):
self.base_cls = cls
- super(FSMFieldMixin, self).contribute_to_class(cls, name, virtual_only=virtual_only)
+ super(FSMFieldMixin, self).contribute_to_class(cls, name, **kwargs)
setattr(cls, self.name, self.descriptor_class(self))
setattr(cls, 'get_all_{0}_transitions'.format(self.name),
curry(get_all_FIELD_transitions, field=self))
@@ -500,7 +504,7 @@ def transition(field, source='*', target=None, on_error=None, conditions=[], per
fsm_meta = FSMMeta(field=field, method=func)
setattr(func, '_django_fsm', fsm_meta)
- if isinstance(source, (list, tuple)):
+ if isinstance(source, (list, tuple, set)):
for state in source:
func._django_fsm.add_transition(func, state, target, on_error, conditions, permission, custom)
else:
@@ -526,7 +530,8 @@ def can_proceed(bound_method, check_conditions=True):
conditions.
"""
if not hasattr(bound_method, '_django_fsm'):
- raise TypeError('%s method is not transition' % bound_method.im_func.__name__)
+ im_func = getattr(bound_method, 'im_func', getattr(bound_method, '__func__'))
+ raise TypeError('%s method is not transition' % im_func.__name__)
meta = bound_method._django_fsm
im_self = getattr(bound_method, 'im_self', getattr(bound_method, '__self__'))
@@ -541,7 +546,8 @@ def has_transition_perm(bound_method, user):
Returns True if model in state allows to call bound_method and user have rights on it
"""
if not hasattr(bound_method, '_django_fsm'):
- raise TypeError('%s method is not transition' % bound_method.im_func.__name__)
+ im_func = getattr(bound_method, 'im_func', getattr(bound_method, '__func__'))
+ raise TypeError('%s method is not transition' % im_func.__name__)
meta = bound_method._django_fsm
im_self = getattr(bound_method, 'im_self', getattr(bound_method, '__self__'))
diff --git a/django_fsm/management/commands/graph_transitions.py b/django_fsm/management/commands/graph_transitions.py
index bf4a636..8eea6bf 100644
--- a/django_fsm/management/commands/graph_transitions.py
+++ b/django_fsm/management/commands/graph_transitions.py
@@ -5,7 +5,7 @@ from optparse import make_option
from django.core.management.base import BaseCommand
from django.utils.encoding import smart_text
-from django_fsm import FSMFieldMixin
+from django_fsm import FSMFieldMixin, GET_STATE, RETURN_VALUE
try:
from django.db.models import get_apps, get_app, get_models, get_model
@@ -14,6 +14,10 @@ except ImportError:
from django.apps import apps
NEW_META_API = True
+from django import VERSION
+
+HAS_ARGPARSE = VERSION >= (1, 10)
+
def all_fsm_fields_data(model):
if NEW_META_API:
@@ -44,18 +48,13 @@ def generate_dot(fields_data):
else:
source_name = node_name(field, transition.source)
if transition.target is not None:
- 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]
+ if isinstance(transition.target, GET_STATE) or isinstance(transition.target, RETURN_VALUE):
+ for transition_target_index, transition_target in enumerate(transition.target.allowed_states):
+ add_transition(transition.source, transition_target, transition.name,
+ source_name, field, sources, targets, edges)
else:
- source_label = transition.source
- sources.add((source_name, source_label))
- if isinstance(transition.target, int):
- target_label = [smart_text(name[1]) for name in field.choices if name[0] == transition.target][0]
- else:
- target_label = transition.target
- targets.add((target_name, target_label))
- edges.add((source_name, target_name, (('label', transition.name),)))
+ add_transition(transition.source, transition.target, transition.name,
+ source_name, field, sources, targets, edges)
if transition.on_error:
on_error_name = node_name(field, transition.on_error)
targets.add((on_error_name, transition.on_error))
@@ -99,21 +98,49 @@ def generate_dot(fields_data):
return result
+def add_transition(transition_source, transition_target, transition_name, source_name, field, sources, targets, edges):
+ 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]
+ else:
+ source_label = transition_source
+ sources.add((source_name, source_label))
+ if isinstance(transition_target, int):
+ target_label = [smart_text(name[1]) for name in field.choices if name[0] == transition_target][0]
+ else:
+ target_label = transition_target
+ targets.add((target_name, target_label))
+ edges.add((source_name, target_name, (('label', transition_name),)))
+
+
class Command(BaseCommand):
requires_system_checks = True
- option_list = BaseCommand.option_list + (
- make_option('--output', '-o', action='store', dest='outputfile',
- help=('Render output file. Type of output dependent on file extensions. '
- 'Use png or jpg to render graph to image.')),
- # NOQA
- make_option('--layout', '-l', action='store', dest='layout', default='dot',
- help=('Layout to be used by GraphViz for visualization. '
- 'Layouts: circo dot fdp neato nop nop1 nop2 twopi')),
- )
+ if not HAS_ARGPARSE:
+ option_list = BaseCommand.option_list + (
+ make_option('--output', '-o', action='store', dest='outputfile',
+ help=('Render output file. Type of output dependent on file extensions. '
+ 'Use png or jpg to render graph to image.')),
+ # NOQA
+ make_option('--layout', '-l', action='store', dest='layout', default='dot',
+ help=('Layout to be used by GraphViz for visualization. '
+ 'Layouts: circo dot fdp neato nop nop1 nop2 twopi')),
+ )
+ args = "[appname[.model[.field]]]"
+ else:
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--output', '-o', action='store', dest='outputfile',
+ help=('Render output file. Type of output dependent on file extensions. '
+ 'Use png or jpg to render graph to image.'))
+ parser.add_argument(
+ '--layout', '-l', action='store', dest='layout', default='dot',
+ help=('Layout to be used by GraphViz for visualization. '
+ 'Layouts: circo dot fdp neato nop nop1 nop2 twopi'))
+ parser.add_argument('args', nargs='*',
+ help=('[appname[.model[.field]]]'))
help = ("Creates a GraphViz dot file with transitions for selected fields")
- args = "[appname[.model[.field]]]"
def render_output(self, graph, **options):
filename, format = options['outputfile'].rsplit('.', 1)
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..2a9acf1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index 68eb170..61eb245 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ except IOError:
setup(
name='django-fsm',
- version='2.4.0',
+ version='2.5.0',
description='Django friendly finite state machine support.',
author='Mikhail Podgurskiy',
author_email='kmmbvnr at gmail.com',
@@ -27,6 +27,7 @@ setup(
"Framework :: Django",
"Framework :: Django :: 1.8",
"Framework :: Django :: 1.9",
+ "Framework :: Django :: 1.10",
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
diff --git a/tests/testapp/tests/test_multi_resultstate.py b/tests/testapp/tests/test_multi_resultstate.py
index e7b385d..ebdb9c4 100644
--- a/tests/testapp/tests/test_multi_resultstate.py
+++ b/tests/testapp/tests/test_multi_resultstate.py
@@ -1,6 +1,7 @@
from django.db import models
from django.test import TestCase
from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE
+from django_fsm.signals import pre_transition, post_transition
class MultiResultTest(models.Model):
@@ -38,3 +39,31 @@ class Test(TestCase):
instance = MultiResultTest(state='for_moderators')
instance.moderate(allowed=False)
self.assertEqual(instance.state, 'rejected')
+
+
+class TestSignals(TestCase):
+ def setUp(self):
+ self.pre_transition_called = False
+ self.post_transition_called = False
+ pre_transition.connect(self.on_pre_transition, sender=MultiResultTest)
+ post_transition.connect(self.on_post_transition, sender=MultiResultTest)
+
+ def on_pre_transition(self, sender, instance, name, source, target, **kwargs):
+ self.assertEqual(instance.state, source)
+ self.pre_transition_called = True
+
+ def on_post_transition(self, sender, instance, name, source, target, **kwargs):
+ self.assertEqual(instance.state, target)
+ self.post_transition_called = True
+
+ def test_signals_called_with_get_state(self):
+ instance = MultiResultTest(state='for_moderators')
+ instance.moderate(allowed=False)
+ self.assertTrue(self.pre_transition_called)
+ self.assertTrue(self.post_transition_called)
+
+ def test_signals_called_with_return_value(self):
+ instance = MultiResultTest()
+ instance.publish(is_public=True)
+ self.assertTrue(self.pre_transition_called)
+ self.assertTrue(self.post_transition_called)
diff --git a/tox.ini b/tox.ini
index 53e4e4b..6856c4b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,33 +1,35 @@
[tox]
envlist =
- py26-{dj16}
- py27-{dj16,dj18,dj19}
- py33-{dj16,dj18}
- py{34,35}-{dj18,dj19}
+ py26-dj{16}
+ py27-dj{16,18,19,110,111}
+ py33-dj{16,18}
+ py{34,35}-dj{18,19,110,111}
skipsdist = True
[testenv]
-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
+ {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==1.8.13
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
+ dj18: coverage==4.1
+ dj18: django-guardian==1.4.4
+ dj19: Django==1.9.7
+ dj19: django-jenkins==0.19.0
+ dj19: coverage==4.1
+ dj19: django-guardian==1.4.4
+ dj110: Django==1.10.5
+ dj110: django-jenkins==0.19.0
+ dj110: coverage==4.1
+ dj110: django-guardian==1.4.4
+ dj111: Django==1.11b1
+ dj111: django-jenkins==0.110.0
+ dj111: coverage==4.3.4
+ dj111: django-guardian==1.4.6
graphviz==0.4.10
pep8==1.7.0
pyflakes==1.0.0
--
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