Plone workflow: Publish an object, and all used/referred objects as well

馋奶兔 提交于 2019-12-12 15:43:32

问题


I have a Plone site with Archetypes objects which refer to other objects (by UID). When, say, a news object is published, all image objects which are referred in the text attribute, should automatically be published, too.

There are three different kinds of publication - "for all" (visible for anyone), "visible" (for authenticated users), and "restricted" (for members of certain groups). Which one of these is chosen (in the first place) is determined from the type of the objects. The user only "approves" the object, and the kind of publication is chosen automatically.

To achieve this, I have a changestate browser: It extracts the UIDs of all used objects from the text/html fields and applies the appropriate workflow transitions to them.

This has worked for some years but doesn't work anymore; perhaps because of some transaction problems?

However, perhaps my current solution is far too complicated.

It should be a quite common situation: When a "news" is published, all "page requisites" of it (which are only referred, rather than contained, since any image might be used by more than one news object) should be published as well. There must be some "standard solution", right?

If there is no "standard" or "best practice" solution yet, I'm interested in possible transaction gotchas etc. as well.


回答1:


Assuming you're sure, that implicitly publishing references doesn't lead to unintended results (imagine an editor go like: "I'd put this in draft-state and decorated it with confidential comments, meant to be temporary until ready for publication, who the heck published this?") and that all content-types have a workflow assigned, the code below is a realization of the algorithm you describe.

In case you have content-type-items which don't have a workflow assigned, an implicit publication would be necessary on the next upper parent with a workflow assigned. But that also changes the inherited permissions of the item's siblings or even cousins and aunts, if the first parent also doesn't have a workflow assigned. However, you could do that, search for "ruthless" in the code and follow the comment's instruction, in that case, but assigning a workflow to all content-types seems more recommendable, here.

To regard back-references, when changing a referenced item to a lower public state than the current state, the user will be informed with a warning that it might not be accessible anymore to the audience of the referencing item, thus an automatic "down-publishing" isn't desirable, as Luca Fabbri points out.

The definition of which state is considered to be more public than another, lives in PublicRank.states, you'd need to adjust that to the states of the workflow(s) you use.

Ok, so it's about two files involved, first register two event-handlers in your.addon/your/addon/configure.zcml:

  <!--  A state has changed, execute 'publishReferences': -->
  <subscriber for="Products.CMFCore.interfaces.IContentish
                   Products.CMFCore.interfaces.IActionSucceededEvent"
          handler=".subscriber.publishReferences" />

  <!--  A state is about to be changed, execute 'warnAbout...': -->
  <subscriber for="Products.CMFCore.interfaces.IContentish
                   Products.DCWorkflow.interfaces.IBeforeTransitionEvent"
          handler=".subscriber.warnAboutPossiblyInaccessibleBackReferences" />

And then add your.addon/your/addon/subscriber.py with the following content:

from Products.statusmessages.interfaces import IStatusMessage
from zope.globalrequest import getRequest


class PublicRank:
    """
    Define which state is to be considered more public than another,
    most public first. Assume for now, only Plone's default workflow
    'simple_publication_workflow' is used in the portal.
    """
    states = ['published', 'pending', 'private']

def isMorePublic(state_one, state_two):
    """
    Check if state_one has a lesser index in the rank than state_two.
    """
    states = PublicRank.states
    if states.index(state_one) < states.index(state_two): return True
    else: return False

def getState(obj):
    """
    Return workflow-state-id or None, if no workflow is assigned.
    Show possible error on the console and log it.
    """
    if hasWorkflow(obj):
        try: return obj.portal_workflow.getInfoFor(obj, 'review_state')
        except ExceptionError as err: obj.plone_log(err)
    else: return None

def getTransitions(obj):
    """
    Return the identifiers of the available transitions as a list.
    """
    transitions = []
    trans_dicts = obj.portal_workflow.getTransitionsFor(obj)
    for trans_dict in trans_dicts:
        transitions.append(trans_dict['id'])
    return transitions

def hasWorkflow(obj):
    """
    Return boolean, indicating whether obj has a workflow assigned, or not.
    """
    return len(obj.portal_workflow.getWorkflowsFor(obj)) > 0

def hasTransition(obj, transition):
    if transition in getTransitions(obj): return True
    else: return False

def isSite(obj):
    return len(obj.getPhysicalPath()) == 2

def publishReferences(obj, eve, RUHTLESS=False):
    """
    If an obj gets published, publish its references, too.
    If an item doesn't have a workflow assigned and RUHTLESS
    is passed to be True, publish next upper parent with a workflow.
    """
    states = PublicRank.states
    state = getState(obj)
    transition = eve.action

    if state in states:
        refs = obj.getRefs()
        for ref in refs:
            ref_state = getState(ref)
            if ref_state:
                if isMorePublic(state, ref_state):
                    setState(ref, transition)
            else: # no workflow assigned
                if RUTHLESS:
                    setStateRelentlessly(ref, transition)

def setState(obj, transition):
    """
    Execute transition, return possible error as an UI-message,
    instead of consuming the whole content-area with a raised Exeption.
    """
    path = '/'.join(obj.getPhysicalPath())
    messages = IStatusMessage(getRequest())
    if hasWorkflow(obj):
        if hasTransition(obj, transition):
            try:
                obj.portal_workflow.doActionFor(obj, transition)
            except Exception as error:
                messages.add(error, type=u'error')
        else:
            message = 'The transition "%s" is not available for "%s".'\
                       % (transition, path)
            messages.add(message, type=u'warning')
    else:
        message = 'No workflow retrievable for "%s".' % path
        messages.add(message, type=u'warning')

def setStateRelentlessly(obj, transition):
    """
    If obj has no workflow, change state of next
    upper parent which has a workflow, instead.
    """
    while not getState(obj, state):
        obj = obj.getParentNode()
        if isSite(obj): break
    setState(obj, transition)

def warnAboutPossiblyInaccessibleBackReferences(obj, eve):
    """
    If an obj is about to switch to a lesser public state than it
    has and is referenced of other item(s), show a warning message
    with the URL(s) of the referencing item(s), so the user can check,
    if the link is still accessible for the intended audience.
    """
    states = PublicRank.states
    item_path = '/'.join(obj.getPhysicalPath())[2:]
    target_state = str(eve.new_state).split(' ')[-1][:-1]
    refs = obj.getBackReferences()

    for ref in refs:
        ref_state = getState(ref)
        if isMorePublic(ref_state, target_state):
            ref_path = '/'.join(ref.getPhysicalPath())[2:]
            messages = IStatusMessage(getRequest())
            message = u'This item "%s" is now in a less published state than \
            item "%s" of which it is referenced by. You might want to check, \
            if this item can still be accessed by the intended audience.' \
            % (item_path, ref_path)
            messages.add(message, type=u'warning')



回答2:


Here is what I did in the end:

I refactored my workflow as follows:

  • It doesn't choose transitions automatically anymore (for publication levels) but lets the user explicitly specify for all, visible or restricted.
  • I added transitions to switch publication levels (for cases of mistakes).
  • These transitions are named differently depending on the direction; e.g., to switch a restricted item to visible, make_visible is used, while the transition to make a published item visible is called make_visible_again. This way, refered items will be affected in the right direction only (not perfect but an improvement).

The problem with non-working publications of refered objects was caused by transactions; moving those from /temp/ to their public location had involved transaction.commit() as well.

Finally, a little gotcha: If a workflow is switched, all review states are initialized anew - it doesn't matter that both the variable name and the states remained unchanged. Thus, it doesn't work to rename the workflow at this occasion. The original workflow states can be restored by switching back to the old workflow (with the same name but updated functionality).



来源:https://stackoverflow.com/questions/38440066/plone-workflow-publish-an-object-and-all-used-referred-objects-as-well

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!