Added workflow visible states attribute
authorThierry Florac <thierry.florac@onf.fr>
Thu, 10 Jan 2019 17:27:02 +0100
changeset 91 509f88791c41
parent 90 69194c2ab7bc
child 92 79aec02dddcc
Added workflow visible states attribute
src/pyams_workflow/content.py
src/pyams_workflow/interfaces.py
src/pyams_workflow/interfaces/__init__.py
src/pyams_workflow/workflow.py
--- a/src/pyams_workflow/content.py	Wed Jan 09 14:02:56 2019 +0100
+++ b/src/pyams_workflow/content.py	Thu Jan 10 17:27:02 2019 +0100
@@ -157,18 +157,18 @@
             self.push_end_date = None
 
     def is_published(self, check_parent=True):
-        # check is parent is published
+        # check is parent is published and visible in front-office
         if check_parent:
             parent = get_parent(self.__parent__, IWorkflowPublicationSupport, allow_context=False)
             if (parent is not None) and not IWorkflowPublicationInfo(parent).is_published():
                 return False
         # associated workflow?
         workflow = IWorkflow(self.__parent__, None)
-        if (workflow is not None) and not workflow.published_states:
+        if (workflow is not None) and not workflow.visible_states:
             return False
         # check content versions
         versions = IWorkflowVersions(self.__parent__, None)
-        if (versions is not None) and not versions.get_versions(workflow.published_states):
+        if (versions is not None) and not versions.get_versions(workflow.visible_states):
             return False
         now = tztime(datetime.utcnow())
         return (self.publication_effective_date is not None) and \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_workflow/interfaces.py	Thu Jan 10 17:27:02 2019 +0100
@@ -0,0 +1,502 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_utils.interfaces import VIEW_PERMISSION
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.interface.interfaces import IObjectEvent, ObjectEvent
+from zope.lifecycleevent.interfaces import IObjectCreatedEvent
+
+# import packages
+from pyams_security.schema import Principal
+from zope.interface import implementer, invariant, Interface, Attribute, Invalid
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.schema import Choice, Datetime, Set, TextLine, Text, List, Object, Int, Bool
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+from pyams_workflow import _
+
+
+MANUAL = 0
+AUTOMATIC = 1
+SYSTEM = 2
+
+
+class InvalidTransitionError(Exception):
+    """Base transition error"""
+
+    def __init__(self, source):
+        self.source = source
+
+    def __str__(self):
+        return 'source: "%s"' % self.source
+
+
+class NoTransitionAvailableError(InvalidTransitionError):
+    """Exception raised when there is not available transition"""
+
+    def __init__(self, source, destination):
+        super(NoTransitionAvailableError, self).__init__(source)
+        self.destination = destination
+
+    def __str__(self):
+        return 'source: "%s" destination: "%s"' % (self.source, self.destination)
+
+
+class AmbiguousTransitionError(InvalidTransitionError):
+    """Exception raised when required transition is ambiguous"""
+
+    def __init__(self, source, destination):
+        super(AmbiguousTransitionError, self).__init__(source)
+        self.destination = destination
+
+    def __str__(self):
+        return 'source: "%s" destination: "%s"' % (self.source, self.destination)
+
+
+class VersionError(Exception):
+    """Versions management error"""
+
+
+class ConditionFailedError(Exception):
+    """Exception raised when transition condition failed"""
+
+
+class IWorkflowTransitionEvent(IObjectEvent):
+    """Workflow transition event interface"""
+
+    wokflow = Attribute("Workflow utility")
+
+    principal = Attribute("Event principal")
+
+    source = Attribute('Original state or None if initial state')
+
+    destination = Attribute('New state')
+
+    transition = Attribute('Transition that was fired or None if initial state')
+
+    comment = Attribute('Comment that went with state transition')
+
+
+@implementer(IWorkflowTransitionEvent)
+class WorkflowTransitionEvent(ObjectEvent):
+    """Workflow transition event"""
+
+    def __init__(self, object, workflow, principal, source, destination, transition, comment):
+        super(WorkflowTransitionEvent, self).__init__(object)
+        self.workflow = workflow
+        self.principal = principal
+        self.source = source
+        self.destination = destination
+        self.transition = transition
+        self.comment = comment
+
+
+class IWorkflowVersionTransitionEvent(IWorkflowTransitionEvent):
+    """Workflow version transition event interface"""
+
+    old_object = Attribute('Old version of object')
+
+
+@implementer(IWorkflowVersionTransitionEvent)
+class WorkflowVersionTransitionEvent(WorkflowTransitionEvent):
+    """Workflow version transition event"""
+
+    def __init__(self, object, workflow, principal, old_object, source, destination, transition, comment):
+        super(WorkflowVersionTransitionEvent, self).__init__(object, workflow, principal, source,
+                                                             destination, transition, comment)
+        self.old_object = old_object
+
+
+class IObjectClonedEvent(IObjectCreatedEvent):
+    """Object cloned event interface"""
+
+    source = Attribute("Cloned object source")
+
+
+@implementer(IObjectClonedEvent)
+class ObjectClonedEvent(ObjectCreatedEvent):
+    """Object cloned event"""
+
+    def __init__(self, object, source):
+        super(ObjectClonedEvent, self).__init__(object)
+        self.source = source
+
+
+class IWorkflow(Interface):
+    """Defines workflow in the form of transition objects.
+
+    Defined as a utility.
+    """
+
+    initial_state = Attribute("Initial state")
+
+    update_states = Set(title="Updatable states",
+                        description="States of contents which are updatable by standard contributors")
+
+    readonly_states = Set(title="Read-only states",
+                          description="States of contents which can't be modified by anybody")
+
+    protected_states = Set(title="Protected states",
+                           description="States of contents which can only be modified by site administrators")
+
+    manager_states = Set(title="Manager states",
+                         description="States of contents which can be modified by site administrators and content "
+                                     "managers")
+
+    published_states = Set(title="Published states",
+                           description="States of contents which are published")
+
+    visible_states = Set(title="Visible staets",
+                         description="States of contents which are visible in front-office")
+
+    waiting_states = Set(title="Waiting states",
+                         description="States of contents waiting for action")
+
+    retired_states = Set(title="Retired states",
+                         description="States of contents which are retired but not yet archived")
+
+    auto_retired_state = Attribute("Auto-retired state")
+
+    def initialize(self):
+        """Do any needed initialization.
+
+        Such as initialization with the workflow versions system.
+        """
+
+    def refresh(self, transitions):
+        """Refresh workflow completely with new transitions."""
+
+    def get_state_label(self, state):
+        """Get given state label"""
+
+    def get_transitions(self, source):
+        """Get all transitions from source"""
+
+    def get_transition(self, source, transition_id):
+        """Get transition with transition_id given source state.
+
+        If the transition is invalid from this source state,
+        an InvalidTransitionError is raised.
+        """
+
+    def get_transition_by_id(self, transition_id):
+        """Get transition with transition_id"""
+
+
+class IWorkflowInfo(Interface):
+    """Get workflow info about workflowed object, and drive workflow.
+
+    Defined as an adapter.
+    """
+
+    def set_initial_state(self, state, comment=None):
+        """Set initial state for the context object.
+
+        Fires a transition event.
+        """
+
+    def fire_transition(self, transition_id, comment=None, side_effect=None, check_security=True, principal=None):
+        """Fire a transition for the context object.
+
+        There's an optional comment parameter that contains some
+        opaque object that offers a comment about the transition.
+        This is useful for manual transitions where users can motivate
+        their actions.
+
+        There's also an optional side effect parameter which should
+        be a callable which receives the object undergoing the transition
+        as the parameter. This could do an editing action of the newly
+        transitioned workflow object before an actual transition event is
+        fired.
+
+        If check_security is set to False, security is not checked
+        and an application can fire a transition no matter what the
+        user's permission is.
+        """
+
+    def fire_transition_toward(self, state, comment=None, side_effect=None, check_security=True, principal=None):
+        """Fire transition toward state.
+
+        Looks up a manual transition that will get to the indicated
+        state.
+
+        If no such transition is possible, NoTransitionAvailableError will
+        be raised.
+
+        If more than one manual transitions are possible,
+        AmbiguousTransitionError will be raised.
+        """
+
+    def fire_transition_for_versions(self, state, transition_id, comment=None, principal=None):
+        """Fire a transition for all versions in a state"""
+
+    def fire_automatic(self):
+        """Fire automatic transitions if possible by condition"""
+
+    def has_version(self, state):
+        """Return true if a version exists in given state"""
+
+    def get_manual_transition_ids(self):
+        """Returns list of valid manual transitions.
+
+        These transitions have to have a condition that's True.
+        """
+
+    def get_manual_transition_ids_toward(self, state):
+        """Returns list of manual transitions towards state"""
+
+    def get_automatic_transition_ids(self):
+        """Returns list of possible automatic transitions.
+
+        Condition is not checked.
+        """
+
+    def has_automatic_transitions(self):
+        """Return true if there are possible automatic outgoing transitions.
+
+        Condition is not checked.
+        """
+
+
+class IWorkflowStateHistoryItem(Interface):
+    """Workflow state history item"""
+
+    date = Datetime(title="State change datetime",
+                    required=True)
+
+    source_version = Int(title="Source version ID",
+                         required=False)
+
+    source_state = TextLine(title="Transition source state",
+                            required=False)
+
+    target_state = TextLine(title="Transition target state",
+                            required=True)
+
+    transition_id = TextLine(title="Transition ID",
+                             required=True)
+
+    transition = TextLine(title="Transition name",
+                          required=True)
+
+    principal = Principal(title="Transition principal",
+                          required=False)
+
+    comment = Text(title="Transition comment",
+                   required=False)
+
+
+class IWorkflowState(Interface):
+    """Store state on workflowed objects.
+
+    Defined as an adapter.
+    """
+
+    version_id = Int(title=_("Version ID"))
+
+    state = TextLine(title=_("Version state"))
+
+    state_date = Datetime(title=_("State date"),
+                          description=_("Date at which the current state was applied"))
+
+    state_principal = Principal(title=_("State principal"),
+                                description=_("ID of the principal which defined current state"))
+
+    state_urgency = Bool(title=_("Urgent request?"),
+                         required=True,
+                         default=False)
+
+    history = List(title="Workflow states history",
+                   value_type=Object(schema=IWorkflowStateHistoryItem))
+
+    def get_first_state_date(self, states):
+        """Get first date at which given state was set"""
+
+
+class IWorkflowVersions(Interface):
+    """Interface to get information about versions of content in workflow"""
+
+    last_version_id = Attribute("Last version ID")
+
+    def get_version(self, version_id):
+        """Get version matching given id"""
+
+    def get_versions(self, states=None, sort=False, reverse=False):
+        """Get all versions of object known for this (optional) state"""
+
+    def get_last_versions(self, count=1):
+        """Get last versions of this object. Set count=0 to get all versions."""
+
+    def add_version(self, content, state, principal=None):
+        """Return new unique version id"""
+
+    def set_state(self, version_id, state, principal=None):
+        """Set new state for given version"""
+
+    def has_version(self, state):
+        """Return true if a version exists with the specific workflow state"""
+
+    def remove_version(self, version_id, state='deleted', comment=None, principal=None):
+        """Remove version with given ID"""
+
+
+class IWorkflowStateLabel(Interface):
+    """Workflow state label adapter interface"""
+
+    def get_label(self, content, request=None, format=True):
+        """Get state label for given content"""
+
+
+class IWorkflowManagedContent(IAttributeAnnotatable):
+    """Workflow managed content"""
+
+    content_class = Attribute("Content class")
+
+    workflow_name = Choice(title=_("Workflow name"),
+                           description=_("Name of workflow utility managing this content"),
+                           required=True,
+                           vocabulary='PyAMS workflows')
+
+    view_permission = Choice(title=_("View permission"),
+                             description=_("This permission will be required to display content"),
+                             vocabulary='PyAMS permissions',
+                             default=VIEW_PERMISSION,
+                             required=False)
+
+
+class IWorkflowPublicationSupport(IAttributeAnnotatable):
+    """Workflow publication support"""
+
+
+class IWorkflowVersion(IWorkflowPublicationSupport):
+    """Workflow content version marker interface"""
+
+
+class IWorkflowTransitionInfo(Interface):
+    """Workflow transition info"""
+
+    transition_id = TextLine(title=_("Transition ID"),
+                             required=True)
+
+
+DISPLAY_FIRST_VERSION = 'first'
+DISPLAY_CURRENT_VERSION = 'current'
+
+VERSION_DISPLAY = {DISPLAY_FIRST_VERSION: _("Display first version date"),
+                   DISPLAY_CURRENT_VERSION: _("Display current version date")}
+
+VERSION_DISPLAY_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t)
+                                               for v, t in VERSION_DISPLAY.items()])
+
+
+class IWorkflowPublicationInfo(Interface):
+    """Workflow content publication info"""
+
+    publication_date = Datetime(title=_("Publication date"),
+                                description=_("Last date at which content was accepted for publication"),
+                                required=False)
+
+    publisher = Principal(title=_("Publisher"),
+                          description=_("Name of the manager who published the document"),
+                          required=False)
+
+    publication = TextLine(title=_("Publication"),
+                           description=_("Last publication date and actor"),
+                           required=False,
+                           readonly=True)
+
+    first_publication_date = Datetime(title=_("First publication date"),
+                                      description=_("First date at which content was accepted for publication"),
+                                      required=False)
+
+    publication_effective_date = Datetime(title=_("Publication start date"),
+                                          description=_("Date from which content will be visible"),
+                                          required=False)
+
+    push_end_date = Datetime(title=_("Push end date"),
+                             description=_("Some contents can be pushed by components to front-office pages; if you "
+                                           "set a date here, this content will not be pushed anymore passed this "
+                                           "date, but will still be available via search engine or direct links"),
+                             required=False)
+
+    push_end_date_index = Attribute("Push end date value used by catalog indexes")
+
+    @invariant
+    def check_push_end_date(self):
+        if self.push_end_date is not None:
+            if self.publication_effective_date is None:
+                raise Invalid(_("Can't define push end date without publication start date!"))
+            if self.publication_effective_date >= self.push_end_date:
+                raise Invalid(_("Push end date must be defined after publication start date!"))
+            if self.publication_expiration_date is not None:
+                if self.publication_expiration_date < self.push_end_date:
+                    raise Invalid(_("Push end date must be null or defined before publication end date!"))
+
+    publication_expiration_date = Datetime(title=_("Publication end date"),
+                                           description=_("Date past which content will not be visible"),
+                                           required=False)
+
+    @invariant
+    def check_expiration_date(self):
+        if self.publication_expiration_date is not None:
+            if self.publication_effective_date is None:
+                raise Invalid(_("Can't define publication end date without publication start date!"))
+            if self.publication_effective_date >= self.publication_expiration_date:
+                raise Invalid(_("Publication end date must be defined after publication start date!"))
+
+    displayed_publication_date = Choice(title=_("Displayed publication date"),
+                                        description=_("The matching date will be displayed in front-office"),
+                                        vocabulary='PyAMS content publication date',
+                                        default=DISPLAY_FIRST_VERSION,
+                                        required=True)
+
+    visible_publication_date = Attribute("Visible publication date")
+
+    def reset(self, complete=True):
+        """Reset all publication info (used by clone features)
+
+        If 'complete' argument is True, all date fields are reset; otherwise, push and publication end dates are
+        preserved in new versions.
+        """
+
+    def is_published(self, check_parent=True):
+        """Is the content published?"""
+
+    def is_visible(self, request=None, check_parent=True):
+        """Is the content visible?"""
+
+
+class IWorkflowRequestUrgencyInfo(Interface):
+    """Workflow request urgency info"""
+
+    urgent_request = Bool(title=_("Urgent request?"),
+                          description=_("Please use this option only when really needed..."),
+                          required=True,
+                          default=False)
+
+
+class IWorkflowCommentInfo(Interface):
+    """Workflow comment info"""
+
+    comment = Text(title=_("Comment"),
+                   description=_("Comment associated with this operation"),
+                   required=False)
+
+
+class IWorkflowManagementTask(Interface):
+    """Workflow management task marker interface"""
--- a/src/pyams_workflow/interfaces/__init__.py	Wed Jan 09 14:02:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,495 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from pyams_utils.interfaces import VIEW_PERMISSION
-from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.interface.interfaces import IObjectEvent, ObjectEvent
-from zope.lifecycleevent.interfaces import IObjectCreatedEvent
-
-# import packages
-from pyams_security.schema import Principal
-from zope.interface import implementer, invariant, Interface, Attribute, Invalid
-from zope.lifecycleevent import ObjectCreatedEvent
-from zope.schema import Choice, Datetime, Set, TextLine, Text, List, Object, Int, Bool
-from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
-
-from pyams_workflow import _
-
-
-MANUAL = 0
-AUTOMATIC = 1
-SYSTEM = 2
-
-
-class InvalidTransitionError(Exception):
-    """Base transition error"""
-
-    def __init__(self, source):
-        self.source = source
-
-    def __str__(self):
-        return 'source: "%s"' % self.source
-
-
-class NoTransitionAvailableError(InvalidTransitionError):
-    """Exception raised when there is not available transition"""
-
-    def __init__(self, source, destination):
-        super(NoTransitionAvailableError, self).__init__(source)
-        self.destination = destination
-
-    def __str__(self):
-        return 'source: "%s" destination: "%s"' % (self.source, self.destination)
-
-
-class AmbiguousTransitionError(InvalidTransitionError):
-    """Exception raised when required transition is ambiguous"""
-
-    def __init__(self, source, destination):
-        super(AmbiguousTransitionError, self).__init__(source)
-        self.destination = destination
-
-    def __str__(self):
-        return 'source: "%s" destination: "%s"' % (self.source, self.destination)
-
-
-class VersionError(Exception):
-    """Versions management error"""
-
-
-class ConditionFailedError(Exception):
-    """Exception raised when transition condition failed"""
-
-
-class IWorkflowTransitionEvent(IObjectEvent):
-    """Workflow transition event interface"""
-
-    wokflow = Attribute("Workflow utility")
-
-    principal = Attribute("Event principal")
-
-    source = Attribute('Original state or None if initial state')
-
-    destination = Attribute('New state')
-
-    transition = Attribute('Transition that was fired or None if initial state')
-
-    comment = Attribute('Comment that went with state transition')
-
-
-@implementer(IWorkflowTransitionEvent)
-class WorkflowTransitionEvent(ObjectEvent):
-    """Workflow transition event"""
-
-    def __init__(self, object, workflow, principal, source, destination, transition, comment):
-        super(WorkflowTransitionEvent, self).__init__(object)
-        self.workflow = workflow
-        self.principal = principal
-        self.source = source
-        self.destination = destination
-        self.transition = transition
-        self.comment = comment
-
-
-class IWorkflowVersionTransitionEvent(IWorkflowTransitionEvent):
-    """Workflow version transition event interface"""
-
-    old_object = Attribute('Old version of object')
-
-
-@implementer(IWorkflowVersionTransitionEvent)
-class WorkflowVersionTransitionEvent(WorkflowTransitionEvent):
-    """Workflow version transition event"""
-
-    def __init__(self, object, workflow, principal, old_object, source, destination, transition, comment):
-        super(WorkflowVersionTransitionEvent, self).__init__(object, workflow, principal, source,
-                                                             destination, transition, comment)
-        self.old_object = old_object
-
-
-class IObjectClonedEvent(IObjectCreatedEvent):
-    """Object cloned event interface"""
-
-    source = Attribute("Cloned object source")
-
-
-@implementer(IObjectClonedEvent)
-class ObjectClonedEvent(ObjectCreatedEvent):
-    """Object cloned event"""
-
-    def __init__(self, object, source):
-        super(ObjectClonedEvent, self).__init__(object)
-        self.source = source
-
-
-class IWorkflow(Interface):
-    """Defines workflow in the form of transition objects.
-
-    Defined as a utility.
-    """
-
-    initial_state = Attribute("Initial state")
-
-    update_states = Set(title="Updatable states",
-                        description="States of contents which are updatable by standard contributors")
-
-    readonly_states = Set(title="Read-only states",
-                          description="States of contents which can't be modified by anybody")
-
-    protected_states = Set(title="Protected states",
-                           description="States of contents which can only be modified by site administrators")
-
-    manager_states = Set(title="Manager states",
-                         description="States of contents which can be modified by site administrators and content "
-                                     "managers")
-
-    published_states = Set(title="Published states",
-                           description="States of contents which are published and visible")
-
-    waiting_states = Set(title="Waiting states",
-                         description="States of contents waiting for action")
-
-    retired_states = Set(title="Retired states",
-                         description="States of contents which are retired but not yet archived")
-
-    auto_retired_state = Attribute("Auto-retired state")
-
-    def initialize(self):
-        """Do any needed initialization.
-
-        Such as initialization with the workflow versions system.
-        """
-
-    def refresh(self, transitions):
-        """Refresh workflow completely with new transitions."""
-
-    def get_state_label(self, state):
-        """Get given state label"""
-
-    def get_transitions(self, source):
-        """Get all transitions from source"""
-
-    def get_transition(self, source, transition_id):
-        """Get transition with transition_id given source state.
-
-        If the transition is invalid from this source state,
-        an InvalidTransitionError is raised.
-        """
-
-    def get_transition_by_id(self, transition_id):
-        """Get transition with transition_id"""
-
-
-class IWorkflowInfo(Interface):
-    """Get workflow info about workflowed object, and drive workflow.
-
-    Defined as an adapter.
-    """
-
-    def set_initial_state(self, state, comment=None):
-        """Set initial state for the context object.
-
-        Fires a transition event.
-        """
-
-    def fire_transition(self, transition_id, comment=None, side_effect=None, check_security=True, principal=None):
-        """Fire a transition for the context object.
-
-        There's an optional comment parameter that contains some
-        opaque object that offers a comment about the transition.
-        This is useful for manual transitions where users can motivate
-        their actions.
-
-        There's also an optional side effect parameter which should
-        be a callable which receives the object undergoing the transition
-        as the parameter. This could do an editing action of the newly
-        transitioned workflow object before an actual transition event is
-        fired.
-
-        If check_security is set to False, security is not checked
-        and an application can fire a transition no matter what the
-        user's permission is.
-        """
-
-    def fire_transition_toward(self, state, comment=None, side_effect=None, check_security=True, principal=None):
-        """Fire transition toward state.
-
-        Looks up a manual transition that will get to the indicated
-        state.
-
-        If no such transition is possible, NoTransitionAvailableError will
-        be raised.
-
-        If more than one manual transitions are possible,
-        AmbiguousTransitionError will be raised.
-        """
-
-    def fire_transition_for_versions(self, state, transition_id, comment=None, principal=None):
-        """Fire a transition for all versions in a state"""
-
-    def fire_automatic(self):
-        """Fire automatic transitions if possible by condition"""
-
-    def has_version(self, state):
-        """Return true if a version exists in given state"""
-
-    def get_manual_transition_ids(self):
-        """Returns list of valid manual transitions.
-
-        These transitions have to have a condition that's True.
-        """
-
-    def get_manual_transition_ids_toward(self, state):
-        """Returns list of manual transitions towards state"""
-
-    def get_automatic_transition_ids(self):
-        """Returns list of possible automatic transitions.
-
-        Condition is not checked.
-        """
-
-    def has_automatic_transitions(self):
-        """Return true if there are possible automatic outgoing transitions.
-
-        Condition is not checked.
-        """
-
-
-class IWorkflowStateHistoryItem(Interface):
-    """Workflow state history item"""
-
-    date = Datetime(title="State change datetime",
-                    required=True)
-
-    source_version = Int(title="Source version ID",
-                         required=False)
-
-    source_state = TextLine(title="Transition source state",
-                            required=False)
-
-    target_state = TextLine(title="Transition target state",
-                            required=True)
-
-    transition_id = TextLine(title="Transition ID",
-                             required=True)
-
-    transition = TextLine(title="Transition name",
-                          required=True)
-
-    principal = Principal(title="Transition principal",
-                          required=False)
-
-    comment = Text(title="Transition comment",
-                   required=False)
-
-
-class IWorkflowState(Interface):
-    """Store state on workflowed objects.
-
-    Defined as an adapter.
-    """
-
-    version_id = Int(title=_("Version ID"))
-
-    state = TextLine(title=_("Version state"))
-
-    state_date = Datetime(title=_("State date"),
-                          description=_("Date at which the current state was applied"))
-
-    state_principal = Principal(title=_("State principal"),
-                                description=_("ID of the principal which defined current state"))
-
-    state_urgency = Bool(title=_("Urgent request?"),
-                         required=True,
-                         default=False)
-
-    history = List(title="Workflow states history",
-                   value_type=Object(schema=IWorkflowStateHistoryItem))
-
-    def get_first_state_date(self, states):
-        """Get first date at which given state was set"""
-
-
-class IWorkflowVersions(Interface):
-    """Interface to get information about versions of content in workflow"""
-
-    last_version_id = Attribute("Last version ID")
-
-    def get_version(self, version_id):
-        """Get version matching given id"""
-
-    def get_versions(self, states=None, sort=False, reverse=False):
-        """Get all versions of object known for this (optional) state"""
-
-    def get_last_versions(self, count=1):
-        """Get last versions of this object. Set count=0 to get all versions."""
-
-    def add_version(self, content, state, principal=None):
-        """Return new unique version id"""
-
-    def set_state(self, version_id, state, principal=None):
-        """Set new state for given version"""
-
-    def has_version(self, state):
-        """Return true if a version exists with the specific workflow state"""
-
-    def remove_version(self, version_id, state='deleted', comment=None, principal=None):
-        """Remove version with given ID"""
-
-
-class IWorkflowStateLabel(Interface):
-    """Workflow state label adapter interface"""
-
-    def get_label(self, content, request=None, format=True):
-        """Get state label for given content"""
-
-
-class IWorkflowManagedContent(IAttributeAnnotatable):
-    """Workflow managed content"""
-
-    content_class = Attribute("Content class")
-
-    workflow_name = Choice(title=_("Workflow name"),
-                           description=_("Name of workflow utility managing this content"),
-                           required=True,
-                           vocabulary='PyAMS workflows')
-
-    view_permission = Choice(title=_("View permission"),
-                             description=_("This permission will be required to display content"),
-                             vocabulary='PyAMS permissions',
-                             default=VIEW_PERMISSION,
-                             required=False)
-
-
-class IWorkflowPublicationSupport(IAttributeAnnotatable):
-    """Workflow publication support"""
-
-
-class IWorkflowVersion(IWorkflowPublicationSupport):
-    """Workflow content version marker interface"""
-
-
-class IWorkflowTransitionInfo(Interface):
-    """Workflow transition info"""
-
-    transition_id = TextLine(title=_("Transition ID"),
-                             required=True)
-
-
-DISPLAY_FIRST_VERSION = 'first'
-DISPLAY_CURRENT_VERSION = 'current'
-
-VERSION_DISPLAY = {DISPLAY_FIRST_VERSION: _("Display first version date"),
-                   DISPLAY_CURRENT_VERSION: _("Display current version date")}
-
-VERSION_DISPLAY_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t)
-                                               for v, t in VERSION_DISPLAY.items()])
-
-
-class IWorkflowPublicationInfo(Interface):
-    """Workflow content publication info"""
-
-    publication_date = Datetime(title=_("Publication date"),
-                                description=_("Last date at which content was accepted for publication"),
-                                required=False)
-
-    publisher = Principal(title=_("Publisher"),
-                          description=_("Name of the manager who published the document"),
-                          required=False)
-
-    publication = TextLine(title=_("Publication"),
-                           description=_("Last publication date and actor"),
-                           required=False,
-                           readonly=True)
-
-    first_publication_date = Datetime(title=_("First publication date"),
-                                      description=_("First date at which content was accepted for publication"),
-                                      required=False)
-
-    publication_effective_date = Datetime(title=_("Publication start date"),
-                                          description=_("Date from which content will be visible"),
-                                          required=False)
-
-    push_end_date = Datetime(title=_("Push end date"),
-                             description=_("Some contents can be pushed by components to front-office pages; if you "
-                                           "set a date here, this content will not be pushed anymore passed this "
-                                           "date, but will still be available via search engine or direct links"),
-                             required=False)
-
-    push_end_date_index = Attribute("Push end date value used by catalog indexes")
-
-    @invariant
-    def check_push_end_date(self):
-        if self.push_end_date is not None:
-            if self.publication_effective_date is None:
-                raise Invalid(_("Can't define push end date without publication start date!"))
-            if self.publication_effective_date >= self.push_end_date:
-                raise Invalid(_("Push end date must be defined after publication start date!"))
-            if self.publication_expiration_date is not None:
-                if self.publication_expiration_date < self.push_end_date:
-                    raise Invalid(_("Push end date must be null or defined before publication end date!"))
-
-    publication_expiration_date = Datetime(title=_("Publication end date"),
-                                           description=_("Date past which content will not be visible"),
-                                           required=False)
-
-    @invariant
-    def check_expiration_date(self):
-        if self.publication_expiration_date is not None:
-            if self.publication_effective_date is None:
-                raise Invalid(_("Can't define publication end date without publication start date!"))
-            if self.publication_effective_date >= self.publication_expiration_date:
-                raise Invalid(_("Publication end date must be defined after publication start date!"))
-
-    displayed_publication_date = Choice(title=_("Displayed publication date"),
-                                        description=_("The matching date will be displayed in front-office"),
-                                        vocabulary='PyAMS content publication date',
-                                        default=DISPLAY_FIRST_VERSION,
-                                        required=True)
-
-    visible_publication_date = Attribute("Visible publication date")
-
-    def reset(self, complete=True):
-        """Reset all publication info (used by clone features)
-
-        If 'complete' argument is True, all date fields are reset; otherwise, push and publication end dates are
-        preserved in new versions.
-        """
-
-    def is_published(self, check_parent=True):
-        """Is the content published?"""
-
-    def is_visible(self, request=None, check_parent=True):
-        """Is the content visible?"""
-
-
-class IWorkflowRequestUrgencyInfo(Interface):
-    """Workflow request urgency info"""
-
-    urgent_request = Bool(title=_("Urgent request?"),
-                          description=_("Please use this option only when really needed..."),
-                          required=True,
-                          default=False)
-
-
-class IWorkflowCommentInfo(Interface):
-    """Workflow comment info"""
-
-    comment = Text(title=_("Comment"),
-                   description=_("Comment associated with this operation"),
-                   required=False)
--- a/src/pyams_workflow/workflow.py	Wed Jan 09 14:02:56 2019 +0100
+++ b/src/pyams_workflow/workflow.py	Thu Jan 10 17:27:02 2019 +0100
@@ -85,6 +85,7 @@
                  protected_states=None,
                  manager_states=None,
                  published_states=None,
+                 visible_states=None,
                  waiting_states=None,
                  retired_states=None,
                  archived_states=None,
@@ -92,14 +93,15 @@
         self.refresh(transitions)
         self.states = states
         self.initial_state = initial_state
-        self.update_states = set(update_states) if update_states else set()
-        self.readonly_states = set(readonly_states) if readonly_states else set()
-        self.protected_states = set(protected_states) if protected_states else set()
-        self.manager_states = set(manager_states) if manager_states else set()
-        self.published_states = set(published_states) if published_states else set()
-        self.waiting_states = set(waiting_states) if waiting_states else set()
-        self.retired_states = set(retired_states) if retired_states else set()
-        self.archived_states = set(archived_states) if archived_states else set()
+        self.update_states = set(update_states or ())
+        self.readonly_states = set(readonly_states or ())
+        self.protected_states = set(protected_states or ())
+        self.manager_states = set(manager_states or ())
+        self.published_states = set(published_states or ())
+        self.visible_states = set(visible_states) if visible_states else self.published_states
+        self.waiting_states = set(waiting_states or ())
+        self.retired_states = set(retired_states or ())
+        self.archived_states = set(archived_states or ())
         self.auto_retired_state = auto_retired_state
 
     def _register(self, transition):