src/pyams_workflow/interfaces/__init__.py
changeset 91 509f88791c41
parent 90 69194c2ab7bc
child 92 79aec02dddcc
equal deleted inserted replaced
90:69194c2ab7bc 91:509f88791c41
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 
       
    18 # import interfaces
       
    19 from pyams_utils.interfaces import VIEW_PERMISSION
       
    20 from zope.annotation.interfaces import IAttributeAnnotatable
       
    21 from zope.interface.interfaces import IObjectEvent, ObjectEvent
       
    22 from zope.lifecycleevent.interfaces import IObjectCreatedEvent
       
    23 
       
    24 # import packages
       
    25 from pyams_security.schema import Principal
       
    26 from zope.interface import implementer, invariant, Interface, Attribute, Invalid
       
    27 from zope.lifecycleevent import ObjectCreatedEvent
       
    28 from zope.schema import Choice, Datetime, Set, TextLine, Text, List, Object, Int, Bool
       
    29 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
       
    30 
       
    31 from pyams_workflow import _
       
    32 
       
    33 
       
    34 MANUAL = 0
       
    35 AUTOMATIC = 1
       
    36 SYSTEM = 2
       
    37 
       
    38 
       
    39 class InvalidTransitionError(Exception):
       
    40     """Base transition error"""
       
    41 
       
    42     def __init__(self, source):
       
    43         self.source = source
       
    44 
       
    45     def __str__(self):
       
    46         return 'source: "%s"' % self.source
       
    47 
       
    48 
       
    49 class NoTransitionAvailableError(InvalidTransitionError):
       
    50     """Exception raised when there is not available transition"""
       
    51 
       
    52     def __init__(self, source, destination):
       
    53         super(NoTransitionAvailableError, self).__init__(source)
       
    54         self.destination = destination
       
    55 
       
    56     def __str__(self):
       
    57         return 'source: "%s" destination: "%s"' % (self.source, self.destination)
       
    58 
       
    59 
       
    60 class AmbiguousTransitionError(InvalidTransitionError):
       
    61     """Exception raised when required transition is ambiguous"""
       
    62 
       
    63     def __init__(self, source, destination):
       
    64         super(AmbiguousTransitionError, self).__init__(source)
       
    65         self.destination = destination
       
    66 
       
    67     def __str__(self):
       
    68         return 'source: "%s" destination: "%s"' % (self.source, self.destination)
       
    69 
       
    70 
       
    71 class VersionError(Exception):
       
    72     """Versions management error"""
       
    73 
       
    74 
       
    75 class ConditionFailedError(Exception):
       
    76     """Exception raised when transition condition failed"""
       
    77 
       
    78 
       
    79 class IWorkflowTransitionEvent(IObjectEvent):
       
    80     """Workflow transition event interface"""
       
    81 
       
    82     wokflow = Attribute("Workflow utility")
       
    83 
       
    84     principal = Attribute("Event principal")
       
    85 
       
    86     source = Attribute('Original state or None if initial state')
       
    87 
       
    88     destination = Attribute('New state')
       
    89 
       
    90     transition = Attribute('Transition that was fired or None if initial state')
       
    91 
       
    92     comment = Attribute('Comment that went with state transition')
       
    93 
       
    94 
       
    95 @implementer(IWorkflowTransitionEvent)
       
    96 class WorkflowTransitionEvent(ObjectEvent):
       
    97     """Workflow transition event"""
       
    98 
       
    99     def __init__(self, object, workflow, principal, source, destination, transition, comment):
       
   100         super(WorkflowTransitionEvent, self).__init__(object)
       
   101         self.workflow = workflow
       
   102         self.principal = principal
       
   103         self.source = source
       
   104         self.destination = destination
       
   105         self.transition = transition
       
   106         self.comment = comment
       
   107 
       
   108 
       
   109 class IWorkflowVersionTransitionEvent(IWorkflowTransitionEvent):
       
   110     """Workflow version transition event interface"""
       
   111 
       
   112     old_object = Attribute('Old version of object')
       
   113 
       
   114 
       
   115 @implementer(IWorkflowVersionTransitionEvent)
       
   116 class WorkflowVersionTransitionEvent(WorkflowTransitionEvent):
       
   117     """Workflow version transition event"""
       
   118 
       
   119     def __init__(self, object, workflow, principal, old_object, source, destination, transition, comment):
       
   120         super(WorkflowVersionTransitionEvent, self).__init__(object, workflow, principal, source,
       
   121                                                              destination, transition, comment)
       
   122         self.old_object = old_object
       
   123 
       
   124 
       
   125 class IObjectClonedEvent(IObjectCreatedEvent):
       
   126     """Object cloned event interface"""
       
   127 
       
   128     source = Attribute("Cloned object source")
       
   129 
       
   130 
       
   131 @implementer(IObjectClonedEvent)
       
   132 class ObjectClonedEvent(ObjectCreatedEvent):
       
   133     """Object cloned event"""
       
   134 
       
   135     def __init__(self, object, source):
       
   136         super(ObjectClonedEvent, self).__init__(object)
       
   137         self.source = source
       
   138 
       
   139 
       
   140 class IWorkflow(Interface):
       
   141     """Defines workflow in the form of transition objects.
       
   142 
       
   143     Defined as a utility.
       
   144     """
       
   145 
       
   146     initial_state = Attribute("Initial state")
       
   147 
       
   148     update_states = Set(title="Updatable states",
       
   149                         description="States of contents which are updatable by standard contributors")
       
   150 
       
   151     readonly_states = Set(title="Read-only states",
       
   152                           description="States of contents which can't be modified by anybody")
       
   153 
       
   154     protected_states = Set(title="Protected states",
       
   155                            description="States of contents which can only be modified by site administrators")
       
   156 
       
   157     manager_states = Set(title="Manager states",
       
   158                          description="States of contents which can be modified by site administrators and content "
       
   159                                      "managers")
       
   160 
       
   161     published_states = Set(title="Published states",
       
   162                            description="States of contents which are published and visible")
       
   163 
       
   164     waiting_states = Set(title="Waiting states",
       
   165                          description="States of contents waiting for action")
       
   166 
       
   167     retired_states = Set(title="Retired states",
       
   168                          description="States of contents which are retired but not yet archived")
       
   169 
       
   170     auto_retired_state = Attribute("Auto-retired state")
       
   171 
       
   172     def initialize(self):
       
   173         """Do any needed initialization.
       
   174 
       
   175         Such as initialization with the workflow versions system.
       
   176         """
       
   177 
       
   178     def refresh(self, transitions):
       
   179         """Refresh workflow completely with new transitions."""
       
   180 
       
   181     def get_state_label(self, state):
       
   182         """Get given state label"""
       
   183 
       
   184     def get_transitions(self, source):
       
   185         """Get all transitions from source"""
       
   186 
       
   187     def get_transition(self, source, transition_id):
       
   188         """Get transition with transition_id given source state.
       
   189 
       
   190         If the transition is invalid from this source state,
       
   191         an InvalidTransitionError is raised.
       
   192         """
       
   193 
       
   194     def get_transition_by_id(self, transition_id):
       
   195         """Get transition with transition_id"""
       
   196 
       
   197 
       
   198 class IWorkflowInfo(Interface):
       
   199     """Get workflow info about workflowed object, and drive workflow.
       
   200 
       
   201     Defined as an adapter.
       
   202     """
       
   203 
       
   204     def set_initial_state(self, state, comment=None):
       
   205         """Set initial state for the context object.
       
   206 
       
   207         Fires a transition event.
       
   208         """
       
   209 
       
   210     def fire_transition(self, transition_id, comment=None, side_effect=None, check_security=True, principal=None):
       
   211         """Fire a transition for the context object.
       
   212 
       
   213         There's an optional comment parameter that contains some
       
   214         opaque object that offers a comment about the transition.
       
   215         This is useful for manual transitions where users can motivate
       
   216         their actions.
       
   217 
       
   218         There's also an optional side effect parameter which should
       
   219         be a callable which receives the object undergoing the transition
       
   220         as the parameter. This could do an editing action of the newly
       
   221         transitioned workflow object before an actual transition event is
       
   222         fired.
       
   223 
       
   224         If check_security is set to False, security is not checked
       
   225         and an application can fire a transition no matter what the
       
   226         user's permission is.
       
   227         """
       
   228 
       
   229     def fire_transition_toward(self, state, comment=None, side_effect=None, check_security=True, principal=None):
       
   230         """Fire transition toward state.
       
   231 
       
   232         Looks up a manual transition that will get to the indicated
       
   233         state.
       
   234 
       
   235         If no such transition is possible, NoTransitionAvailableError will
       
   236         be raised.
       
   237 
       
   238         If more than one manual transitions are possible,
       
   239         AmbiguousTransitionError will be raised.
       
   240         """
       
   241 
       
   242     def fire_transition_for_versions(self, state, transition_id, comment=None, principal=None):
       
   243         """Fire a transition for all versions in a state"""
       
   244 
       
   245     def fire_automatic(self):
       
   246         """Fire automatic transitions if possible by condition"""
       
   247 
       
   248     def has_version(self, state):
       
   249         """Return true if a version exists in given state"""
       
   250 
       
   251     def get_manual_transition_ids(self):
       
   252         """Returns list of valid manual transitions.
       
   253 
       
   254         These transitions have to have a condition that's True.
       
   255         """
       
   256 
       
   257     def get_manual_transition_ids_toward(self, state):
       
   258         """Returns list of manual transitions towards state"""
       
   259 
       
   260     def get_automatic_transition_ids(self):
       
   261         """Returns list of possible automatic transitions.
       
   262 
       
   263         Condition is not checked.
       
   264         """
       
   265 
       
   266     def has_automatic_transitions(self):
       
   267         """Return true if there are possible automatic outgoing transitions.
       
   268 
       
   269         Condition is not checked.
       
   270         """
       
   271 
       
   272 
       
   273 class IWorkflowStateHistoryItem(Interface):
       
   274     """Workflow state history item"""
       
   275 
       
   276     date = Datetime(title="State change datetime",
       
   277                     required=True)
       
   278 
       
   279     source_version = Int(title="Source version ID",
       
   280                          required=False)
       
   281 
       
   282     source_state = TextLine(title="Transition source state",
       
   283                             required=False)
       
   284 
       
   285     target_state = TextLine(title="Transition target state",
       
   286                             required=True)
       
   287 
       
   288     transition_id = TextLine(title="Transition ID",
       
   289                              required=True)
       
   290 
       
   291     transition = TextLine(title="Transition name",
       
   292                           required=True)
       
   293 
       
   294     principal = Principal(title="Transition principal",
       
   295                           required=False)
       
   296 
       
   297     comment = Text(title="Transition comment",
       
   298                    required=False)
       
   299 
       
   300 
       
   301 class IWorkflowState(Interface):
       
   302     """Store state on workflowed objects.
       
   303 
       
   304     Defined as an adapter.
       
   305     """
       
   306 
       
   307     version_id = Int(title=_("Version ID"))
       
   308 
       
   309     state = TextLine(title=_("Version state"))
       
   310 
       
   311     state_date = Datetime(title=_("State date"),
       
   312                           description=_("Date at which the current state was applied"))
       
   313 
       
   314     state_principal = Principal(title=_("State principal"),
       
   315                                 description=_("ID of the principal which defined current state"))
       
   316 
       
   317     state_urgency = Bool(title=_("Urgent request?"),
       
   318                          required=True,
       
   319                          default=False)
       
   320 
       
   321     history = List(title="Workflow states history",
       
   322                    value_type=Object(schema=IWorkflowStateHistoryItem))
       
   323 
       
   324     def get_first_state_date(self, states):
       
   325         """Get first date at which given state was set"""
       
   326 
       
   327 
       
   328 class IWorkflowVersions(Interface):
       
   329     """Interface to get information about versions of content in workflow"""
       
   330 
       
   331     last_version_id = Attribute("Last version ID")
       
   332 
       
   333     def get_version(self, version_id):
       
   334         """Get version matching given id"""
       
   335 
       
   336     def get_versions(self, states=None, sort=False, reverse=False):
       
   337         """Get all versions of object known for this (optional) state"""
       
   338 
       
   339     def get_last_versions(self, count=1):
       
   340         """Get last versions of this object. Set count=0 to get all versions."""
       
   341 
       
   342     def add_version(self, content, state, principal=None):
       
   343         """Return new unique version id"""
       
   344 
       
   345     def set_state(self, version_id, state, principal=None):
       
   346         """Set new state for given version"""
       
   347 
       
   348     def has_version(self, state):
       
   349         """Return true if a version exists with the specific workflow state"""
       
   350 
       
   351     def remove_version(self, version_id, state='deleted', comment=None, principal=None):
       
   352         """Remove version with given ID"""
       
   353 
       
   354 
       
   355 class IWorkflowStateLabel(Interface):
       
   356     """Workflow state label adapter interface"""
       
   357 
       
   358     def get_label(self, content, request=None, format=True):
       
   359         """Get state label for given content"""
       
   360 
       
   361 
       
   362 class IWorkflowManagedContent(IAttributeAnnotatable):
       
   363     """Workflow managed content"""
       
   364 
       
   365     content_class = Attribute("Content class")
       
   366 
       
   367     workflow_name = Choice(title=_("Workflow name"),
       
   368                            description=_("Name of workflow utility managing this content"),
       
   369                            required=True,
       
   370                            vocabulary='PyAMS workflows')
       
   371 
       
   372     view_permission = Choice(title=_("View permission"),
       
   373                              description=_("This permission will be required to display content"),
       
   374                              vocabulary='PyAMS permissions',
       
   375                              default=VIEW_PERMISSION,
       
   376                              required=False)
       
   377 
       
   378 
       
   379 class IWorkflowPublicationSupport(IAttributeAnnotatable):
       
   380     """Workflow publication support"""
       
   381 
       
   382 
       
   383 class IWorkflowVersion(IWorkflowPublicationSupport):
       
   384     """Workflow content version marker interface"""
       
   385 
       
   386 
       
   387 class IWorkflowTransitionInfo(Interface):
       
   388     """Workflow transition info"""
       
   389 
       
   390     transition_id = TextLine(title=_("Transition ID"),
       
   391                              required=True)
       
   392 
       
   393 
       
   394 DISPLAY_FIRST_VERSION = 'first'
       
   395 DISPLAY_CURRENT_VERSION = 'current'
       
   396 
       
   397 VERSION_DISPLAY = {DISPLAY_FIRST_VERSION: _("Display first version date"),
       
   398                    DISPLAY_CURRENT_VERSION: _("Display current version date")}
       
   399 
       
   400 VERSION_DISPLAY_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t)
       
   401                                                for v, t in VERSION_DISPLAY.items()])
       
   402 
       
   403 
       
   404 class IWorkflowPublicationInfo(Interface):
       
   405     """Workflow content publication info"""
       
   406 
       
   407     publication_date = Datetime(title=_("Publication date"),
       
   408                                 description=_("Last date at which content was accepted for publication"),
       
   409                                 required=False)
       
   410 
       
   411     publisher = Principal(title=_("Publisher"),
       
   412                           description=_("Name of the manager who published the document"),
       
   413                           required=False)
       
   414 
       
   415     publication = TextLine(title=_("Publication"),
       
   416                            description=_("Last publication date and actor"),
       
   417                            required=False,
       
   418                            readonly=True)
       
   419 
       
   420     first_publication_date = Datetime(title=_("First publication date"),
       
   421                                       description=_("First date at which content was accepted for publication"),
       
   422                                       required=False)
       
   423 
       
   424     publication_effective_date = Datetime(title=_("Publication start date"),
       
   425                                           description=_("Date from which content will be visible"),
       
   426                                           required=False)
       
   427 
       
   428     push_end_date = Datetime(title=_("Push end date"),
       
   429                              description=_("Some contents can be pushed by components to front-office pages; if you "
       
   430                                            "set a date here, this content will not be pushed anymore passed this "
       
   431                                            "date, but will still be available via search engine or direct links"),
       
   432                              required=False)
       
   433 
       
   434     push_end_date_index = Attribute("Push end date value used by catalog indexes")
       
   435 
       
   436     @invariant
       
   437     def check_push_end_date(self):
       
   438         if self.push_end_date is not None:
       
   439             if self.publication_effective_date is None:
       
   440                 raise Invalid(_("Can't define push end date without publication start date!"))
       
   441             if self.publication_effective_date >= self.push_end_date:
       
   442                 raise Invalid(_("Push end date must be defined after publication start date!"))
       
   443             if self.publication_expiration_date is not None:
       
   444                 if self.publication_expiration_date < self.push_end_date:
       
   445                     raise Invalid(_("Push end date must be null or defined before publication end date!"))
       
   446 
       
   447     publication_expiration_date = Datetime(title=_("Publication end date"),
       
   448                                            description=_("Date past which content will not be visible"),
       
   449                                            required=False)
       
   450 
       
   451     @invariant
       
   452     def check_expiration_date(self):
       
   453         if self.publication_expiration_date is not None:
       
   454             if self.publication_effective_date is None:
       
   455                 raise Invalid(_("Can't define publication end date without publication start date!"))
       
   456             if self.publication_effective_date >= self.publication_expiration_date:
       
   457                 raise Invalid(_("Publication end date must be defined after publication start date!"))
       
   458 
       
   459     displayed_publication_date = Choice(title=_("Displayed publication date"),
       
   460                                         description=_("The matching date will be displayed in front-office"),
       
   461                                         vocabulary='PyAMS content publication date',
       
   462                                         default=DISPLAY_FIRST_VERSION,
       
   463                                         required=True)
       
   464 
       
   465     visible_publication_date = Attribute("Visible publication date")
       
   466 
       
   467     def reset(self, complete=True):
       
   468         """Reset all publication info (used by clone features)
       
   469 
       
   470         If 'complete' argument is True, all date fields are reset; otherwise, push and publication end dates are
       
   471         preserved in new versions.
       
   472         """
       
   473 
       
   474     def is_published(self, check_parent=True):
       
   475         """Is the content published?"""
       
   476 
       
   477     def is_visible(self, request=None, check_parent=True):
       
   478         """Is the content visible?"""
       
   479 
       
   480 
       
   481 class IWorkflowRequestUrgencyInfo(Interface):
       
   482     """Workflow request urgency info"""
       
   483 
       
   484     urgent_request = Bool(title=_("Urgent request?"),
       
   485                           description=_("Please use this option only when really needed..."),
       
   486                           required=True,
       
   487                           default=False)
       
   488 
       
   489 
       
   490 class IWorkflowCommentInfo(Interface):
       
   491     """Workflow comment info"""
       
   492 
       
   493     comment = Text(title=_("Comment"),
       
   494                    description=_("Comment associated with this operation"),
       
   495                    required=False)