Skip to content

Commit

Permalink
StateManager conditional states can have labels (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Feb 7, 2018
1 parent 460b2a4 commit cde8875
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 3 deletions.
8 changes: 6 additions & 2 deletions coaster/sqlalchemy/statemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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):
Expand Down
7 changes: 6 additions & 1 deletion tests/test_statemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit cde8875

Please sign in to comment.