diff --git a/coaster/sqlalchemy/statemanager.py b/coaster/sqlalchemy/statemanager.py index 49fd53b4..8566f979 100644 --- a/coaster/sqlalchemy/statemanager.py +++ b/coaster/sqlalchemy/statemanager.py @@ -203,6 +203,7 @@ def redraft(self): import functools from sqlalchemy import and_, or_, column as column_constructor, CheckConstraint from werkzeug.exceptions import BadRequest +from ..utils import NameTitle from ..signals import coaster_signals __all__ = ['StateManager', 'StateTransitionError', @@ -601,7 +602,7 @@ def add_state_group(self, name, *states): setattr(self, name, mstate) setattr(self, 'is_' + name.lower(), mstate) - def add_conditional_state(self, name, state, validator, class_validator=None, cache_for=None): + def add_conditional_state(self, name, state, validator, class_validator=None, cache_for=None, label=None): """ Add a conditional state that combines an existing state with a validator that must also pass. The validator receives the object on which the property @@ -617,6 +618,7 @@ def add_conditional_state(self, name, state, validator, class_validator=None, ca result can be cached (not applicable to ``class_validator``). ``None`` implies no cache, ``0`` implies indefinite cache (until invalidated by a transition) and any other integer is the number of seconds for which to cache the assertion + :param label: Label for this state (string or 2-tuple) TODO: cache_for's implementation is currently pending a test case demonstrating how it will be used. @@ -626,7 +628,9 @@ def add_conditional_state(self, name, state, validator, class_validator=None, ca raise TypeError("Not a managed state: %s" % repr(state)) elif state.statemanager != self: raise ValueError("State %s is not associated with this state manager" % repr(state)) - self._add_state_internal(name, state.value, + if isinstance(label, tuple) and len(label) == 2: + label = NameTitle(*label) + self._add_state_internal(name, state.value, label=label, validator=validator, class_validator=class_validator, cache_for=cache_for) def transition(self, from_, to, if_=None, **data): diff --git a/tests/test_statemanager.py b/tests/test_statemanager.py index 4d0aea4b..4b8cb23a 100644 --- a/tests/test_statemanager.py +++ b/tests/test_statemanager.py @@ -59,7 +59,8 @@ class MyPost(BaseMixin, db.Model): # Conditional states (adds ManagedState instances) state.add_conditional_state('RECENT', state.PUBLISHED, - lambda post: post.datetime > datetime.utcnow() - timedelta(hours=1)) + lambda post: post.datetime > datetime.utcnow() - timedelta(hours=1), + label=('recent', "Recently published")) # State groups (apart from those in the LabeledEnum), used here to include the # conditional state in a group. Adds ManagedStateGroup instances @@ -144,6 +145,10 @@ def test_conditional_state_unmanaged_state(self): with self.assertRaises(ValueError): state.add_conditional_state('TEST_STATE2', reviewstate.UNSUBMITTED, lambda post: True) + def test_conditional_state_label(self): + """Conditional states can have labels""" + self.assertEqual(MyPost.__dict__['state'].RECENT.label.name, 'recent') + def test_transition_invalid_from_to(self): """ Adding a transition with an invalid `from_` or `to` state will raise an error