Plone workflow: publishing an object and all used / related objects

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

There are three different types of publications: "for all" (visible to all), "visible" (for authenticated users), and "restricted" (for members of certain groups). Which one is selected (first of all) is determined by the type of objects. The user only “approves” the object, and the type of publication is automatically selected.

To do this, I have a changestate browser: it extracts the UID of all used objects from the text/html fields and applies the corresponding transitions of the workflow to them.

This works for several years, but does not work anymore; perhaps due to some transaction problems?

However, perhaps my current solution is too complicated.

This should be a fairly common situation: when “news” is published, all “page details” (which are mentioned only, but not contained, since any image can be used by more than one news item) should also be published. There must be some kind of "standard solution", right?

If there is no "standard" or "best solution", I am also interested in possible transactional errors, etc.

+3
source share
2 answers

Assuming that you’re sure that implicit publication of links does not lead to unintended results (imagine that the editor looks like this: “I would put it in a draft state and decorate it with confidential comments, intended to be temporarily ready for publication, who the hell posted it? "), and that all content types have an assigned workflow, the code below is an implementation of the algorithm that you describe.

If you have content type elements that do not have an assigned workflow, an implicit publication will be required for the next top parent with the workflow assignment. But it also changes the inherited permissions of siblings or even cousins ​​and aunts if the first parent also does not have an assigned workflow. However, you can do this, look for “ruthless” in the code and follow the comment instructions in this case, but assigning a workflow for all content types seems more appropriate here.

In order to draw attention to backlinks when the link item changes to a lower public state than the current state, a warning will be given to the user that he may not be available to the audience of the link item, so automatic “down-publishing” is undesirable, as Luke points out Fabbry.

The definition of which state is considered more public than another lives in PublicRank.states , you need to configure it for the state of the workflow you are using.

Ok, so this concerns two related files, 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" /> 

Then add your.addon/your/addon/subscriber.py with the following contents:

 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 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') 
+4
source

Here is what I did at the end:

I reorganized my workflow as follows:

  • It no longer selects transitions (for publication levels), but allows the user to explicitly specify for all , visible or restricted .
  • I added transitions to switch publication levels (for cases of errors).
  • These transitions are called differently depending on the direction; for example, to switch the restricted element to visible , make_visible used, while the transition to creating the published visible element is called make_visible_again . Thus, the mentioned elements will be affected only in the right direction (not perfect, but improved).

The problem with broken publishing of reference objects was caused by transactions; moving them from /temp/ to their public location also included transaction.commit() .

Finally, it turned out a little: if the workflow is switched over, all view states are initialized anew - it does not matter that the variable name and states remain unchanged. Therefore, it does not work to rename the workflow about this. The initial state of the workflow can be restored by switching to the old workflow (with the same name, but with updated functionality).

0
source

Source: https://habr.com/ru/post/1494812/


All Articles