src/pyams_workflow/interfaces.py
changeset 91 509f88791c41
parent 76 56f01a2bcb32
child 98 c69e5c38e92c
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")
       
   163 
       
   164     visible_states = Set(title="Visible staets",
       
   165                          description="States of contents which are visible in front-office")
       
   166 
       
   167     waiting_states = Set(title="Waiting states",
       
   168                          description="States of contents waiting for action")
       
   169 
       
   170     retired_states = Set(title="Retired states",
       
   171                          description="States of contents which are retired but not yet archived")
       
   172 
       
   173     auto_retired_state = Attribute("Auto-retired state")
       
   174 
       
   175     def initialize(self):
       
   176         """Do any needed initialization.
       
   177 
       
   178         Such as initialization with the workflow versions system.
       
   179         """
       
   180 
       
   181     def refresh(self, transitions):
       
   182         """Refresh workflow completely with new transitions."""
       
   183 
       
   184     def get_state_label(self, state):
       
   185         """Get given state label"""
       
   186 
       
   187     def get_transitions(self, source):
       
   188         """Get all transitions from source"""
       
   189 
       
   190     def get_transition(self, source, transition_id):
       
   191         """Get transition with transition_id given source state.
       
   192 
       
   193         If the transition is invalid from this source state,
       
   194         an InvalidTransitionError is raised.
       
   195         """
       
   196 
       
   197     def get_transition_by_id(self, transition_id):
       
   198         """Get transition with transition_id"""
       
   199 
       
   200 
       
   201 class IWorkflowInfo(Interface):
       
   202     """Get workflow info about workflowed object, and drive workflow.
       
   203 
       
   204     Defined as an adapter.
       
   205     """
       
   206 
       
   207     def set_initial_state(self, state, comment=None):
       
   208         """Set initial state for the context object.
       
   209 
       
   210         Fires a transition event.
       
   211         """
       
   212 
       
   213     def fire_transition(self, transition_id, comment=None, side_effect=None, check_security=True, principal=None):
       
   214         """Fire a transition for the context object.
       
   215 
       
   216         There's an optional comment parameter that contains some
       
   217         opaque object that offers a comment about the transition.
       
   218         This is useful for manual transitions where users can motivate
       
   219         their actions.
       
   220 
       
   221         There's also an optional side effect parameter which should
       
   222         be a callable which receives the object undergoing the transition
       
   223         as the parameter. This could do an editing action of the newly
       
   224         transitioned workflow object before an actual transition event is
       
   225         fired.
       
   226 
       
   227         If check_security is set to False, security is not checked
       
   228         and an application can fire a transition no matter what the
       
   229         user's permission is.
       
   230         """
       
   231 
       
   232     def fire_transition_toward(self, state, comment=None, side_effect=None, check_security=True, principal=None):
       
   233         """Fire transition toward state.
       
   234 
       
   235         Looks up a manual transition that will get to the indicated
       
   236         state.
       
   237 
       
   238         If no such transition is possible, NoTransitionAvailableError will
       
   239         be raised.
       
   240 
       
   241         If more than one manual transitions are possible,
       
   242         AmbiguousTransitionError will be raised.
       
   243         """
       
   244 
       
   245     def fire_transition_for_versions(self, state, transition_id, comment=None, principal=None):
       
   246         """Fire a transition for all versions in a state"""
       
   247 
       
   248     def fire_automatic(self):
       
   249         """Fire automatic transitions if possible by condition"""
       
   250 
       
   251     def has_version(self, state):
       
   252         """Return true if a version exists in given state"""
       
   253 
       
   254     def get_manual_transition_ids(self):
       
   255         """Returns list of valid manual transitions.
       
   256 
       
   257         These transitions have to have a condition that's True.
       
   258         """
       
   259 
       
   260     def get_manual_transition_ids_toward(self, state):
       
   261         """Returns list of manual transitions towards state"""
       
   262 
       
   263     def get_automatic_transition_ids(self):
       
   264         """Returns list of possible automatic transitions.
       
   265 
       
   266         Condition is not checked.
       
   267         """
       
   268 
       
   269     def has_automatic_transitions(self):
       
   270         """Return true if there are possible automatic outgoing transitions.
       
   271 
       
   272         Condition is not checked.
       
   273         """
       
   274 
       
   275 
       
   276 class IWorkflowStateHistoryItem(Interface):
       
   277     """Workflow state history item"""
       
   278 
       
   279     date = Datetime(title="State change datetime",
       
   280                     required=True)
       
   281 
       
   282     source_version = Int(title="Source version ID",
       
   283                          required=False)
       
   284 
       
   285     source_state = TextLine(title="Transition source state",
       
   286                             required=False)
       
   287 
       
   288     target_state = TextLine(title="Transition target state",
       
   289                             required=True)
       
   290 
       
   291     transition_id = TextLine(title="Transition ID",
       
   292                              required=True)
       
   293 
       
   294     transition = TextLine(title="Transition name",
       
   295                           required=True)
       
   296 
       
   297     principal = Principal(title="Transition principal",
       
   298                           required=False)
       
   299 
       
   300     comment = Text(title="Transition comment",
       
   301                    required=False)
       
   302 
       
   303 
       
   304 class IWorkflowState(Interface):
       
   305     """Store state on workflowed objects.
       
   306 
       
   307     Defined as an adapter.
       
   308     """
       
   309 
       
   310     version_id = Int(title=_("Version ID"))
       
   311 
       
   312     state = TextLine(title=_("Version state"))
       
   313 
       
   314     state_date = Datetime(title=_("State date"),
       
   315                           description=_("Date at which the current state was applied"))
       
   316 
       
   317     state_principal = Principal(title=_("State principal"),
       
   318                                 description=_("ID of the principal which defined current state"))
       
   319 
       
   320     state_urgency = Bool(title=_("Urgent request?"),
       
   321                          required=True,
       
   322                          default=False)
       
   323 
       
   324     history = List(title="Workflow states history",
       
   325                    value_type=Object(schema=IWorkflowStateHistoryItem))
       
   326 
       
   327     def get_first_state_date(self, states):
       
   328         """Get first date at which given state was set"""
       
   329 
       
   330 
       
   331 class IWorkflowVersions(Interface):
       
   332     """Interface to get information about versions of content in workflow"""
       
   333 
       
   334     last_version_id = Attribute("Last version ID")
       
   335 
       
   336     def get_version(self, version_id):
       
   337         """Get version matching given id"""
       
   338 
       
   339     def get_versions(self, states=None, sort=False, reverse=False):
       
   340         """Get all versions of object known for this (optional) state"""
       
   341 
       
   342     def get_last_versions(self, count=1):
       
   343         """Get last versions of this object. Set count=0 to get all versions."""
       
   344 
       
   345     def add_version(self, content, state, principal=None):
       
   346         """Return new unique version id"""
       
   347 
       
   348     def set_state(self, version_id, state, principal=None):
       
   349         """Set new state for given version"""
       
   350 
       
   351     def has_version(self, state):
       
   352         """Return true if a version exists with the specific workflow state"""
       
   353 
       
   354     def remove_version(self, version_id, state='deleted', comment=None, principal=None):
       
   355         """Remove version with given ID"""
       
   356 
       
   357 
       
   358 class IWorkflowStateLabel(Interface):
       
   359     """Workflow state label adapter interface"""
       
   360 
       
   361     def get_label(self, content, request=None, format=True):
       
   362         """Get state label for given content"""
       
   363 
       
   364 
       
   365 class IWorkflowManagedContent(IAttributeAnnotatable):
       
   366     """Workflow managed content"""
       
   367 
       
   368     content_class = Attribute("Content class")
       
   369 
       
   370     workflow_name = Choice(title=_("Workflow name"),
       
   371                            description=_("Name of workflow utility managing this content"),
       
   372                            required=True,
       
   373                            vocabulary='PyAMS workflows')
       
   374 
       
   375     view_permission = Choice(title=_("View permission"),
       
   376                              description=_("This permission will be required to display content"),
       
   377                              vocabulary='PyAMS permissions',
       
   378                              default=VIEW_PERMISSION,
       
   379                              required=False)
       
   380 
       
   381 
       
   382 class IWorkflowPublicationSupport(IAttributeAnnotatable):
       
   383     """Workflow publication support"""
       
   384 
       
   385 
       
   386 class IWorkflowVersion(IWorkflowPublicationSupport):
       
   387     """Workflow content version marker interface"""
       
   388 
       
   389 
       
   390 class IWorkflowTransitionInfo(Interface):
       
   391     """Workflow transition info"""
       
   392 
       
   393     transition_id = TextLine(title=_("Transition ID"),
       
   394                              required=True)
       
   395 
       
   396 
       
   397 DISPLAY_FIRST_VERSION = 'first'
       
   398 DISPLAY_CURRENT_VERSION = 'current'
       
   399 
       
   400 VERSION_DISPLAY = {DISPLAY_FIRST_VERSION: _("Display first version date"),
       
   401                    DISPLAY_CURRENT_VERSION: _("Display current version date")}
       
   402 
       
   403 VERSION_DISPLAY_VOCABULARY = SimpleVocabulary([SimpleTerm(v, title=t)
       
   404                                                for v, t in VERSION_DISPLAY.items()])
       
   405 
       
   406 
       
   407 class IWorkflowPublicationInfo(Interface):
       
   408     """Workflow content publication info"""
       
   409 
       
   410     publication_date = Datetime(title=_("Publication date"),
       
   411                                 description=_("Last date at which content was accepted for publication"),
       
   412                                 required=False)
       
   413 
       
   414     publisher = Principal(title=_("Publisher"),
       
   415                           description=_("Name of the manager who published the document"),
       
   416                           required=False)
       
   417 
       
   418     publication = TextLine(title=_("Publication"),
       
   419                            description=_("Last publication date and actor"),
       
   420                            required=False,
       
   421                            readonly=True)
       
   422 
       
   423     first_publication_date = Datetime(title=_("First publication date"),
       
   424                                       description=_("First date at which content was accepted for publication"),
       
   425                                       required=False)
       
   426 
       
   427     publication_effective_date = Datetime(title=_("Publication start date"),
       
   428                                           description=_("Date from which content will be visible"),
       
   429                                           required=False)
       
   430 
       
   431     push_end_date = Datetime(title=_("Push end date"),
       
   432                              description=_("Some contents can be pushed by components to front-office pages; if you "
       
   433                                            "set a date here, this content will not be pushed anymore passed this "
       
   434                                            "date, but will still be available via search engine or direct links"),
       
   435                              required=False)
       
   436 
       
   437     push_end_date_index = Attribute("Push end date value used by catalog indexes")
       
   438 
       
   439     @invariant
       
   440     def check_push_end_date(self):
       
   441         if self.push_end_date is not None:
       
   442             if self.publication_effective_date is None:
       
   443                 raise Invalid(_("Can't define push end date without publication start date!"))
       
   444             if self.publication_effective_date >= self.push_end_date:
       
   445                 raise Invalid(_("Push end date must be defined after publication start date!"))
       
   446             if self.publication_expiration_date is not None:
       
   447                 if self.publication_expiration_date < self.push_end_date:
       
   448                     raise Invalid(_("Push end date must be null or defined before publication end date!"))
       
   449 
       
   450     publication_expiration_date = Datetime(title=_("Publication end date"),
       
   451                                            description=_("Date past which content will not be visible"),
       
   452                                            required=False)
       
   453 
       
   454     @invariant
       
   455     def check_expiration_date(self):
       
   456         if self.publication_expiration_date is not None:
       
   457             if self.publication_effective_date is None:
       
   458                 raise Invalid(_("Can't define publication end date without publication start date!"))
       
   459             if self.publication_effective_date >= self.publication_expiration_date:
       
   460                 raise Invalid(_("Publication end date must be defined after publication start date!"))
       
   461 
       
   462     displayed_publication_date = Choice(title=_("Displayed publication date"),
       
   463                                         description=_("The matching date will be displayed in front-office"),
       
   464                                         vocabulary='PyAMS content publication date',
       
   465                                         default=DISPLAY_FIRST_VERSION,
       
   466                                         required=True)
       
   467 
       
   468     visible_publication_date = Attribute("Visible publication date")
       
   469 
       
   470     def reset(self, complete=True):
       
   471         """Reset all publication info (used by clone features)
       
   472 
       
   473         If 'complete' argument is True, all date fields are reset; otherwise, push and publication end dates are
       
   474         preserved in new versions.
       
   475         """
       
   476 
       
   477     def is_published(self, check_parent=True):
       
   478         """Is the content published?"""
       
   479 
       
   480     def is_visible(self, request=None, check_parent=True):
       
   481         """Is the content visible?"""
       
   482 
       
   483 
       
   484 class IWorkflowRequestUrgencyInfo(Interface):
       
   485     """Workflow request urgency info"""
       
   486 
       
   487     urgent_request = Bool(title=_("Urgent request?"),
       
   488                           description=_("Please use this option only when really needed..."),
       
   489                           required=True,
       
   490                           default=False)
       
   491 
       
   492 
       
   493 class IWorkflowCommentInfo(Interface):
       
   494     """Workflow comment info"""
       
   495 
       
   496     comment = Text(title=_("Comment"),
       
   497                    description=_("Comment associated with this operation"),
       
   498                    required=False)
       
   499 
       
   500 
       
   501 class IWorkflowManagementTask(Interface):
       
   502     """Workflow management task marker interface"""