我有一个带有Archetypes对象的Plone站点,这些对象引用其他对象(通过UID)。例如,当发布news
个对象时,image
属性中引用的所有text
个对象也应自动发布。
有三种不同的出版物 - “为所有人”(对任何人都可见),“可见”(对于经过身份验证的用户)和“受限制”(对于某些群组的成员)。选择哪一个(首先)是根据对象的类型确定的。用户只“批准”该对象,并自动选择发布类型。
为实现这一目标,我有一个changestate
浏览器:它从text/html
字段中提取所有已使用对象的UID,并将适当的工作流转换应用于它们。
这已经工作了几年但不再起作用了; 也许是因为一些交易问题?
然而,也许我目前的解决方案太复杂了。
应该是一个非常常见的情况:当发布“新闻”时,它的所有“页面必需品”(仅被引用,而不是包含,因为任何图像可能被多个新闻使用对象)也应该发布。必须有一些“标准解决方案”,对吧?
如果还没有“标准”或“最佳实践”解决方案,我也对可能的交易陷阱等感兴趣。
答案 0 :(得分:4)
假设您确定,隐式发布引用不会导致意外结果(想象一下编辑器就像:“我将其置于草稿状态并用机密注释装饰它,在准备好之前是暂时的对于发布,谁发布了这个?“)并且所有内容类型都分配了工作流程,下面的代码是您描述的算法的实现。
如果您的内容类型项目未分配工作流程,则在分配了工作流程的下一个上级父级上需要隐式发布。但是,如果第一个父级也没有分配工作流,那么这也会更改项目的兄弟姐妹甚至表兄弟和阿姨的继承权限。但是,您可以这样做,在代码中搜索“无情”并遵循注释的说明,在这种情况下,但是在这里为所有内容类型分配工作流似乎更值得推荐。
要考虑反向引用,当将引用的项目更改为比当前状态更低的公共状态时,将通知用户警告它可能不再可访问引用项目的受众,因此是自动的正如Luca Fabbri指出的那样,“向下出版”是不可取的。
哪个州被认为比另一个更公开的定义存在于PublicRank.states
,您需要根据您使用的工作流程的状态进行调整。
好的,所以涉及两个文件,首先在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" />
然后添加your.addon/your/addon/subscriber.py
以下内容:
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')
答案 1 :(得分:0)
这是我最后所做的:
我重构了我的工作流程如下:
for all
,visible
或restricted
。restricted
项切换为visible
,使用make_visible
,而调用published
项visible
的过渡称为make_visible_again
。这样,所提到的项目只会在正确的方向上受到影响(不完美而是改进)。提交对象的非工作出版物的问题是由交易引起的;将那些从/temp/
移动到其公共位置的内容也涉及transaction.commit()
。
最后,一点点问题:如果切换工作流程,所有审核状态都会重新初始化 - 变量名称和状态都保持不变并不重要。因此,在这种情况下重命名工作流程并不起作用。可以通过切换回旧工作流程(具有相同名称但更新的功能)来恢复原始工作流程状态。