[CMF-checkins] CVS: CMF/CMFStaging - CHANGES.txt:1.1
staging_utils.py:1.1 LockTool.py:1.13 StagingTool.py:1.13
VersionsTool.py:1.12 WorkflowRepository.py:1.2
WorkflowWithRepositoryTool.py:1.2 __init__.py:1.4
Shane Hathaway
cvs-admin at zope.org
Mon Oct 27 15:22:24 EST 2003
Update of /cvs-repository/CMF/CMFStaging
In directory cvs.zope.org:/tmp/cvs-serv11975
Modified Files:
LockTool.py StagingTool.py VersionsTool.py
WorkflowRepository.py WorkflowWithRepositoryTool.py
__init__.py
Added Files:
CHANGES.txt staging_utils.py
Log Message:
Added support for staging references. See CHANGES.txt for details.
=== Added File CMF/CMFStaging/CHANGES.txt ===
Unreleased version
- Staging and versioning of reference objects now works.
To stage a reference, the tool copies both the reference and the thing
it points to.
- Stages now have titles in addition to names
- Added a method to the staging tool, updateStages2(), which does the same
thing as updateStages(), but without the from_stage argument. This
removes some ambiguity about which stage the object should come from.
- Made the URLs of stages customizable. The way this is set up may change.
- The versions tool now gets objects unstuck before checking in. I don't
remember exactly how objects get in the stuck state, but this fixes it.
- checkin() and checkout() now return None, emphasizing
the fact that objects now retain identity after version control
operations.
*NOTE* that this change broke compatibility with existing staging tool
instances. Existing portal_staging objects will need to be deleted and
re-created.
Tidied up:
- Docstring formatting
- Use "obj" instead of "object" to avoid confusion
- Check permissions on objects rather than on tools
- Exceptions
=== Added File CMF/CMFStaging/staging_utils.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Utilities for CMFStaging.
$Id: staging_utils.py,v 1.1 2003/10/27 20:21:53 shane Exp $
"""
import types
from cStringIO import StringIO
from cPickle import Pickler, Unpickler
from Acquisition import aq_inner, aq_parent
from AccessControl.PermissionRole import rolesForPermissionOn
from AccessControl import getSecurityManager
_marker = [] # Create a new marker.
def getPortal(context, default=_marker):
portal = context
while not getattr(portal, '_isPortalRoot', 0):
portal = aq_parent(aq_inner(portal))
if portal is None:
if default is _marker:
raise ValueError("Object is not in context of a portal")
return default
return portal
def verifyPermission(permission, obj):
roles = rolesForPermissionOn(permission, obj)
if type(roles) is types.StringType:
roles=[roles]
# C implementation of validate does not take keyword arguments
accessed, container, name, value = obj, obj, '', obj
getSecurityManager().validate(accessed, container, name, value, roles)
def unproxied(obj):
"""Removes proxy wrappers, returning the target, which might be unwrapped.
The References product generates proxies of this sort.
"""
try:
d = obj.__dict__
except AttributeError:
return obj
return d.get('_Proxy__target', obj)
def getProxyTarget(obj):
"""Returns the target of a proxy, with the target wrapped in acquisition.
If the argument is not a proxy, an error will occur.
"""
ref = obj.__dict__["_Proxy__reference"]
return ref.getTarget(obj)
def getProxyReference(obj):
"""Returns the reference that created a proxy.
If the argument is not a proxy, an error will occur.
"""
return obj.__dict__["_Proxy__reference"]
def cloneByPickle(obj):
"""Makes a copy of a ZODB object, loading ghosts as needed.
"""
def persistent_id(o):
if getattr(o, '_p_changed', 0) is None:
o._p_changed = 0
return None
stream = StringIO()
p = Pickler(stream, 1)
p.persistent_id = persistent_id
p.dump(obj)
stream.seek(0)
u = Unpickler(stream)
return u.load()
=== CMF/CMFStaging/LockTool.py 1.12 => 1.13 ===
--- CMF/CMFStaging/LockTool.py:1.12 Thu May 22 11:24:03 2003
+++ CMF/CMFStaging/LockTool.py Mon Oct 27 15:21:53 2003
@@ -27,10 +27,11 @@
SimpleItemWithProperties, _checkPermission
from Products.CMFCore.CMFCorePermissions import ManagePortal, \
ModifyPortalContent
-
from webdav.WriteLockInterface import WriteLockInterface
from webdav.LockItem import LockItem
+from staging_utils import verifyPermission
+
# Permission names
LockObjects = 'WebDAV Lock items'
UnlockObjects = 'WebDAV Unlock items'
@@ -38,8 +39,8 @@
_wwwdir = os.path.join(os.path.dirname(__file__), 'www')
-def pathOf(object):
- return '/'.join(object.getPhysicalPath())
+def pathOf(obj):
+ return '/'.join(obj.getPhysicalPath())
class LockingError(Exception):
@@ -82,72 +83,72 @@
#
security.declarePublic('lock')
- def lock(self, object):
- '''Locks an object'''
- if not _checkPermission(LockObjects, object):
- raise LockingError, 'Inadequate permissions to lock %s' % object
- locker = self.locker(object)
+ def lock(self, obj):
+ """Locks an object.
+ """
+ verifyPermission(LockObjects, obj)
+ locker = self.locker(obj)
if locker:
- raise LockingError, '%s is already locked' % pathOf(object)
+ raise LockingError, '%s is already locked' % pathOf(obj)
if self.auto_version:
vt = getToolByName(self, 'portal_versions', None)
if vt is not None:
- if (vt.isUnderVersionControl(object)
- and not vt.isCheckedOut(object)):
- object = vt.checkout(object)
+ if (vt.isUnderVersionControl(obj)
+ and not vt.isCheckedOut(obj)):
+ vt.checkout(obj)
user = getSecurityManager().getUser()
lockitem = LockItem(user, timeout=(self.timeout_days * 86400))
- object.wl_setLock(lockitem.getLockToken(), lockitem)
+ obj.wl_setLock(lockitem.getLockToken(), lockitem)
security.declarePublic('breaklock')
- def breaklock(self, object, message=''):
- """emergency breaklock...."""
- locker = self.locker(object)
- if not _checkPermission(UnlockObjects, object):
- raise LockingError, ("You cannot unlock %s: lock is held by %s" %
- (pathOf(object), locker))
- object.wl_clearLocks()
+ def breaklock(self, obj, message=''):
+ """Breaks the lock in an emergency.
+ """
+ locker = self.locker(obj)
+ verifyPermission(UnlockObjects, obj)
+ obj.wl_clearLocks()
if self.auto_version:
vt = getToolByName(self, 'portal_versions', None)
if vt is not None:
- vt.checkin(object, message)
+ vt.checkin(obj, message)
- security.declarePublic('unlock')
- def unlock(self, object, message=''):
- '''Unlocks an object'''
- if not _checkPermission(UnlockObjects, object):
- raise LockingError, "Inadequate permissions to unlock %s" % object
- locker = self.locker(object)
+ security.declarePublic('unlock')
+ def unlock(self, obj, message=''):
+ """Unlocks an object.
+ """
+ verifyPermission(UnlockObjects, obj)
+ locker = self.locker(obj)
if not locker:
raise LockingError, ("Unlocking an unlocked item: %s" %
- pathOf(object))
+ pathOf(obj))
user = getSecurityManager().getUser()
if user.getId() != locker:
raise LockingError, ("Cannot unlock %s: lock is held by %s" %
- (pathOf(object), locker))
+ (pathOf(obj), locker))
# According to WriteLockInterface, we shouldn't call
# wl_clearLocks(), but it seems like the right thing to do anyway.
- object.wl_clearLocks()
+ obj.wl_clearLocks()
if self.auto_version:
vt = getToolByName(self, 'portal_versions', None)
if vt is not None:
- vt.checkin(object, message)
+ vt.checkin(obj, message)
security.declarePublic('locker')
- def locker(self, object):
- '''Returns the locker of an object'''
- if not WriteLockInterface.isImplementedBy(object):
- raise LockingError, "%s is not lockable" % pathOf(object)
+ def locker(self, obj):
+ """Returns the locker of an object.
+ """
+ if not WriteLockInterface.isImplementedBy(obj):
+ raise LockingError, "%s is not lockable" % pathOf(obj)
- values = object.wl_lockValues()
+ values = obj.wl_lockValues()
if not values:
return ''
for lock in values:
@@ -160,9 +161,10 @@
security.declarePublic('isLockedOut')
- def isLockedOut(self, object):
- '''Returns a true value if the current user is locked out.'''
- locker_id = self.locker(object)
+ def isLockedOut(self, obj):
+ """Returns a true value if the current user is locked out.
+ """
+ locker_id = self.locker(obj)
if locker_id:
uid = getSecurityManager().getUser().getId()
if uid != locker_id:
@@ -171,56 +173,64 @@
security.declarePublic('locked')
- def locked(self, object):
- '''Returns true if an object is locked.
+ def locked(self, obj):
+ """Returns true if an object is locked.
- Also accepts non-lockable objects, always returning 0.'''
- if not WriteLockInterface.isImplementedBy(object):
+ Also accepts non-lockable objects, always returning 0.
+ """
+ if not WriteLockInterface.isImplementedBy(obj):
return 0
- return not not self.locker(object)
+ return not not self.locker(obj)
+
security.declarePublic('isLockable')
- def isLockable(self, object):
- """Return true if object supports locking, regardless of lock
- state or whether the current user can actually lock."""
- return WriteLockInterface.isImplementedBy(object)
+ def isLockable(self, obj):
+ """Return true if object supports locking.
+
+ Does not examine lock state or whether the current user can
+ actually lock.
+ """
+ return WriteLockInterface.isImplementedBy(obj)
+
security.declarePublic('canLock')
- def canLock(self, object):
- """Returns true if the current user can lock the given object."""
- if self.locked(object):
+ def canLock(self, obj):
+ """Returns true if the current user can lock the given object.
+ """
+ if self.locked(obj):
return 0
- if not WriteLockInterface.isImplementedBy(object):
+ if not WriteLockInterface.isImplementedBy(obj):
return 0
- if _checkPermission(LockObjects, object):
+ if _checkPermission(LockObjects, obj):
return 1
return 0
security.declarePublic('canUnlock')
- def canUnlock(self, object):
+ def canUnlock(self, obj):
"""Returns true if the current user can unlock the given object."""
- if not self.locked(object):
+ if not self.locked(obj):
return 0
- if self.isLockedOut(object):
+ if self.isLockedOut(obj):
return 0
- if _checkPermission(UnlockObjects, object):
+ if _checkPermission(UnlockObjects, obj):
return 1
return 0
security.declarePublic('canChange')
- def canChange(self, object):
- """Returns true if the current user can change the given object."""
- if not WriteLockInterface.isImplementedBy(object):
- if self.isLockedOut(object):
+ def canChange(self, obj):
+ """Returns true if the current user can change the given object.
+ """
+ if not WriteLockInterface.isImplementedBy(obj):
+ if self.isLockedOut(obj):
return 0
- if not _checkPermission(ModifyPortalContent, object):
+ if not _checkPermission(ModifyPortalContent, obj):
return 0
vt = getToolByName(self, 'portal_versions', None)
if vt is not None:
- if (vt.isUnderVersionControl(object)
- and not vt.isCheckedOut(object)):
+ if (vt.isUnderVersionControl(obj)
+ and not vt.isCheckedOut(obj)):
return 0
return 1
=== CMF/CMFStaging/StagingTool.py 1.12 => 1.13 ===
--- CMF/CMFStaging/StagingTool.py:1.12 Mon Aug 5 12:17:30 2002
+++ CMF/CMFStaging/StagingTool.py Mon Oct 27 15:21:53 2003
@@ -20,7 +20,7 @@
import os
-from Acquisition import aq_inner, aq_parent, aq_acquire
+from Acquisition import aq_base, aq_inner, aq_parent, aq_acquire
from Globals import InitializeClass, DTMLFile
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
@@ -29,6 +29,10 @@
SimpleItemWithProperties
from Products.CMFCore.CMFCorePermissions import ManagePortal
+from staging_utils import getPortal, verifyPermission, unproxied
+from staging_utils import getProxyTarget, getProxyReference, cloneByPickle
+
+
# Permission name
StageObjects = 'Use version control'
@@ -64,12 +68,12 @@
'label': 'Unlock and checkin before staging'},
)
- # _stages maps stage names to relative paths.
- _stages = {
- 'dev': 'Stages/Development',
- 'review': 'Stages/Review',
- 'prod': 'Stages/Production'
- }
+ # _stages maps stage names to paths relative to the portal.
+ _stages = (
+ ('dev', 'Development', '.'),
+ ('review', 'Review', '../Review'),
+ ('prod', 'Production', '../Production'),
+ )
security.declareProtected(ManagePortal, 'manage_overview' )
manage_overview = DTMLFile('explainStagingTool', _wwwdir)
@@ -78,30 +82,40 @@
repo = aq_acquire(self, self.repository_name, containment=1)
return repo
+ def _getStage(self, portal, path):
+ if not path or path == ".":
+ return portal
+ else:
+ return portal.restrictedTraverse(path, None)
- def _getObjectStages(self, object, get_container=0):
+ def _getObjectStages(self, obj, get_container=0):
"""Returns a mapping from stage name to object in that stage.
- Objects not in a stage are represented as None."""
- root = aq_parent(aq_inner(self))
+ Objects not in a stage are represented as None.
+ """
+ portal = aq_parent(aq_inner(self))
stages = {}
rel_path = None
- ob_path = object.getPhysicalPath()
- for stage_name, path in self._stages.items():
- stage = root.restrictedTraverse(path, None)
+ ob_path = obj.getPhysicalPath()
+ for stage_name, stage_title, path in self._stages:
+ stage = self._getStage(portal, path)
stages[stage_name] = stage
- if stage is not None and object.aq_inContextOf(stage, 1):
+ try:
+ obj.aq_inContextOf
+ except:
+ import pdb; pdb.set_trace()
+ if stage is not None and obj.aq_inContextOf(stage, 1):
if rel_path is not None:
# Can't tell what stage the object is in!
- raise StagingError, "The stages overlap"
+ raise StagingError("The stages overlap")
# The object is from this stage.
stage_path = stage.getPhysicalPath()
assert ob_path[:len(stage_path)] == stage_path
rel_path = ob_path[len(stage_path):]
if rel_path is None:
- raise StagingError, "Object %s is not in any stage" % (
- '/'.join(ob_path))
+ raise StagingError("Object %s is not in any stage" % (
+ '/'.join(ob_path)))
if get_container:
# Get the container of the object instead of the object itself.
@@ -109,25 +123,26 @@
rel_path = rel_path[:-1]
res = {}
- for stage_name, path in self._stages.items():
+ for stage_name, stage_title, path in self._stages:
stage = stages[stage_name]
if stage is not None:
- object = stage.restrictedTraverse(rel_path, None)
+ obj = stage.restrictedTraverse(rel_path, None)
else:
- object = None
- res[stage_name] = object
+ obj = None
+ res[stage_name] = obj
return res
- def _getObjectVersionIds(self, object, include_status=0):
+ def _getObjectVersionIds(self, obj, include_status=0):
repo = self._getVersionRepository()
- stages = self._getObjectStages(object)
+ stages = self._getObjectStages(obj)
res = {}
- for stage_name, object in stages.items():
- if object is None or not repo.isUnderVersionControl(object):
+ for stage_name, obj in stages.items():
+ u_obj = unproxied(obj)
+ if obj is None or not repo.isUnderVersionControl(u_obj):
res[stage_name] = None
else:
- info = repo.getVersionInfo(object)
+ info = repo.getVersionInfo(u_obj)
v = info.version_id
if include_status and info.status == info.CHECKED_OUT:
v = str(v) + '+'
@@ -135,83 +150,118 @@
return res
- def _autoCheckin(self, object, message=''):
+ def _autoCheckin(self, obj, message=''):
lt = getToolByName(self, 'portal_lock', None)
if lt is not None:
- if lt.locked(object):
- lt.unlock(object)
+ if lt.locked(obj):
+ lt.unlock(obj)
vt = getToolByName(self, 'portal_versions', None)
if vt is not None:
- if vt.isCheckedOut(object):
- vt.checkin(object, message)
+ if vt.isCheckedOut(obj):
+ vt.checkin(obj, message)
- security.declareProtected(StageObjects, 'isStageable')
- def isStageable(self, object):
+ security.declarePublic('isStageable')
+ def isStageable(self, obj):
"""Returns a true value if the object can be staged."""
+ verifyPermission(StageObjects, obj)
repo = self._getVersionRepository()
- if not repo.isAVersionableResource(object):
+ if not repo.isAVersionableResource(unproxied(obj)):
return 0
- if not getattr(object, '_stageable', 1):
+ if not getattr(obj, '_stageable', 1):
return 0
# An object is stageable only if it is located in one of the stages.
- root = aq_parent(aq_inner(self))
- for stage_name, path in self._stages.items():
- stage = root.restrictedTraverse(path, None)
- if stage is not None and object.aq_inContextOf(stage, 1):
+ portal = aq_parent(aq_inner(self))
+ for stage_name, stage_title, path in self._stages:
+ stage = self._getStage(portal, path)
+ if stage is not None and obj.aq_inContextOf(stage, 1):
return 1
- return 0
+ return 0
- security.declareProtected(StageObjects, 'getStageOf')
- def getStageOf(self, object):
- """Returns the stage ID the object is in the context of.
+ security.declarePublic('getStageOf')
+ def getStageOf(self, obj):
+ """Returns the stage name the object is in the context of.
"""
- root = aq_parent(aq_inner(self))
- for stage_name, path in self._stages.items():
- stage = root.restrictedTraverse(path, None)
- if stage is not None and object.aq_inContextOf(stage, 1):
+ verifyPermission(StageObjects, obj)
+ portal = aq_parent(aq_inner(self))
+ for stage_name, stage_title, path in self._stages:
+ stage = self._getStage(portal, path)
+ if stage is not None and obj.aq_inContextOf(stage, 1):
return stage_name
return None
- security.declareProtected(StageObjects, 'getObjectInStage')
- def getObjectInStage(self, object, stage):
+ security.declarePublic('getObjectInStage')
+ def getObjectInStage(self, obj, stage_name):
"""Returns the version of the object in the given stage.
"""
- stages = self._getObjectStages(object)
- return stages[stage]
-
+ verifyPermission(StageObjects, obj)
+ stages = self._getObjectStages(obj)
+ return stages[stage_name]
- security.declareProtected(StageObjects, 'updateStages')
- def updateStages(self, object, from_stage, to_stages, message=''):
- """Updates corresponding objects to match the version
- in the specified stage."""
- if from_stage and (
- from_stage in to_stages or not self._stages.has_key(from_stage)):
- raise StagingError, "Invalid from_stages or to_stages parameter."
- repo = self._getVersionRepository()
- container_map = self._getObjectStages(object, get_container=1)
+ security.declarePublic('updateStages')
+ def updateStages(self, obj, from_stage, to_stages, message=''):
+ """Backward compatibility wrapper.
+
+ Calls updateStages2(). Note that the source stage is
+ specified twice, first in the context of obj, second in
+ from_stage. updateStages2() eliminates the potential
+ ambiguity by eliminating from_stage.
+ """
+ s = self.getStageOf(obj) # Checks the StageObjects permission
+ if s != from_stage:
+ raise StagingError("Ambiguous source stage")
+ self.updateStages2(obj, to_stages, message)
- self._checkContainers(object, to_stages, container_map)
- if self.auto_checkin:
- self._autoCheckin(object, message)
- object_map = self._getObjectStages(object)
- if from_stage:
- source_object = object_map[from_stage]
+ security.declarePublic('updateStages2')
+ def updateStages2(self, obj, to_stages, message=''):
+ """Updates objects to match the version in the source stage.
+ """
+ from_stage = self.getStageOf(obj) # Checks the StageObjects permission
+ if from_stage is None:
+ raise StagingError("Object %s is not in any stage" %
+ '/'.join(obj.getPhysicalPath()))
+ if from_stage in to_stages or not to_stages:
+ raise StagingError("Invalid to_stages parameter")
+
+ if aq_base(unproxied(obj)) is not aq_base(obj):
+ # obj is a proxy. Update both the reference and the target.
+ proxy = obj
+ obj = getProxyTarget(proxy)
else:
- # The default source stage is the stage where the object is now.
- source_object = object
+ proxy = None
+
+ # Check containers first.
+ cmap = self._getObjectStages(obj, get_container=1)
+ self._checkContainers(obj, to_stages, cmap)
+ proxy_cmap = None
+ if proxy is not None:
+ # Check the containers of the reference also.
+ proxy_cmap = self._getObjectStages(proxy, get_container=1)
+ self._checkContainers(proxy, to_stages, proxy_cmap)
+
+ # Update the stages.
+ self._updateObjectStates(obj, cmap, to_stages, message)
+ if proxy is not None:
+ # Create and update the reference objects also.
+ self._updateReferences(proxy, proxy_cmap, to_stages)
+
+
+ def _updateObjectStates(self, source_object, container_map,
+ to_stages, message):
+ """Internal: updates the state of an object in specified stages.
+ """
+ repo = self._getVersionRepository()
+ if self.auto_checkin:
+ self._autoCheckin(source_object, message)
+ object_map = self._getObjectStages(source_object)
version_info = repo.getVersionInfo(source_object)
version_id = version_info.version_id
history_id = version_info.history_id
- # Make sure we copy the current data.
- # XXX ZopeVersionControl tries to do this but isn't quite correct yet.
- get_transaction().commit(1)
-
# Update and/or copy the object to the different stages.
for stage_name, ob in object_map.items():
if stage_name in to_stages:
@@ -224,7 +274,7 @@
# This can happen if a site doesn't yet exist on
# the stage.
p = '/'.join(source_object.getPhysicalPath())
- raise StagingError, (
+ raise StagingError(
'The container for "%s" does not exist on "%s"'
% (p, stage_name))
# Make a copy and put it in the new place.
@@ -234,88 +284,155 @@
else:
if not repo.isUnderVersionControl(ob):
p = '/'.join(ob.getPhysicalPath())
- raise StagingError, (
+ raise StagingError(
'The object "%s", not under version control, '
'is in the way.' % p)
if repo.getVersionInfo(ob).history_id != history_id:
p = '/'.join(ob.getPhysicalPath())
p2 = '/'.join(source_object.getPhysicalPath())
- raise StagingError, (
+ raise StagingError(
'The object "%s", backed by a different '
'version history than "%s", '
'is in the way.' % (p, p2))
repo.updateResource(ob, version_id)
- security.declareProtected(StageObjects, 'removeStages')
- def removeStages(self, object, stages):
- """Removes the copies on the given stages."""
- object_map = self._getObjectStages(object)
- container_map = self._getObjectStages(object, get_container=1)
- id = object.getId()
+ def _updateReferences(self, proxy, container_map, to_stages):
+ """Internal: creates and updates references.
+ """
+ # Note that version control is not used when staging
+ # reference objects.
+ ref = getProxyReference(proxy)
+ object_map = self._getObjectStages(proxy)
+ ref_id = ref.getId()
+ for stage_name, ob in object_map.items():
+ if stage_name in to_stages:
+ if ob is not None:
+ # There is an object at the reference target.
+ if type(aq_base(ob)) is not type(aq_base(proxy)):
+ p = '/'.join(ob.getPhysicalPath())
+ raise StagingError(
+ 'The object "%s", which is not a reference, '
+ 'is in the way.' % p)
+ # Delete the reference.
+ container = container_map[stage_name]
+ container._delObject(ref_id)
+
+ # Copy the reference from the source stage.
+ container = container_map.get(stage_name, None)
+ if container is None:
+ # This can happen if a site doesn't yet exist on
+ # the stage.
+ p = '/'.join(proxy.getPhysicalPath())
+ raise StagingError(
+ 'The container for "%s" does not exist on "%s"'
+ % (p, stage_name))
+ # Duplicate the reference.
+ ob = cloneByPickle(aq_base(ref))
+ container._setObject(ob.getId(), ob)
+
+
+ security.declarePublic('removeStages')
+ def removeStages(self, obj, stages):
+ """Removes the copies on the given stages.
+ """
+ # If the object is a reference or proxy, this removes only the
+ # reference or proxy; this is probably the right thing to do.
+ verifyPermission(StageObjects, obj)
+ object_map = self._getObjectStages(obj)
+ container_map = self._getObjectStages(obj, get_container=1)
+ id = obj.getId()
for stage_name, container in container_map.items():
if object_map.get(stage_name) is not None:
if container is not None and stage_name in stages:
container._delObject(id)
- security.declareProtected(StageObjects, 'getVersionIds')
- def getVersionIds(self, object, include_status=0):
- """Retrieves object version identifiers in the different stages."""
- return self._getObjectVersionIds(object, include_status)
+ security.declarePublic('getVersionIds')
+ def getVersionIds(self, obj, include_status=0):
+ """Retrieves object version identifiers in the different stages.
+ """
+ verifyPermission(StageObjects, obj)
+ return self._getObjectVersionIds(obj, include_status)
- def _checkContainers(self, object, stages, containers):
+ def _checkContainers(self, obj, stages, containers):
for stage in stages:
if containers.get(stage) is None:
- p = '/'.join(object.getPhysicalPath())
- raise StagingError, (
+ p = '/'.join(obj.getPhysicalPath())
+ raise StagingError(
'The container for "%s" does not exist on "%s"'
% (p, stage))
- security.declareProtected(StageObjects, 'checkContainers')
- def checkContainers(self, object, stages):
- """Verifies that the container exists for the object on the
- given stages. If not, an exception is raised.
+ security.declarePublic('checkContainers')
+ def checkContainers(self, obj, stages):
+ """Verifies that the container exists on the given stages.
+
+ If the container is missing on one of the stages, an exception
+ is raised.
"""
- containers = self._getObjectStages(object, get_container=1)
- self._checkContainers(object, stages, containers)
+ verifyPermission(StageObjects, obj)
+ containers = self._getObjectStages(obj, get_container=1)
+ self._checkContainers(obj, stages, containers)
return 1
- security.declareProtected(StageObjects, 'getURLForStage')
- def getURLForStage(self, object, stage, relative=0):
- """Returns the URL of the object on the given stage."""
- stages = self._getObjectStages(object)
+ security.declarePublic('getURLForStage')
+ def getURLForStage(self, source, stage, relative=0):
+ """Returns the URL of the object on the given stage.
+
+ Besides using absolute_url(), also looks for public_url
+ properties on portal objects. The public_url property is
+ useful for generating a public URL even if the current user is
+ accessing Zope through a private URL.
+
+ This method is particularly useful when generating URLs for
+ inclusion in an email notification regarding staging.
+ """
+ verifyPermission(StageObjects, source)
+ stages = self._getObjectStages(source)
ob = stages[stage]
if ob is not None:
- return ob.absolute_url(relative)
+ url = ob.absolute_url(relative)
+ p = getPortal(ob, None)
+ if p is not None:
+ # Modify the start of the URL according to the portal's
+ # public_url property.
+ public_url = getattr(aq_base(p), 'public_url', None)
+ if public_url:
+ orig_url = p.absolute_url(relative)
+ if url.startswith(orig_url):
+ url = public_url + url[len(orig_url):]
+ return url
else:
return None
security.declareProtected(ManagePortal, 'manage_stagesForm')
manage_stagesForm = PageTemplateFile('stagesForm', _wwwdir)
+ manage_stagesForm.__name__ = "manage_stagesForm"
security.declareProtected(ManagePortal, 'getStageItems')
def getStageItems(self):
- lst = self._stages.items()
- lst.sort()
- return lst
+ """Returns the stage declarations (for the management UI.)
+ """
+ return self._stages
security.declareProtected(ManagePortal, 'manage_editStages')
def manage_editStages(self, stages=(), RESPONSE=None):
- """Edits the stages."""
- ss = {}
+ """Edits the stages.
+ """
+ ss = []
for stage in stages:
name = str(stage.name)
+ title = str(stage.title)
path = str(stage.path)
- if name and path:
- ss[name] = path
- self._stages = ss
+ if name:
+ ss.append((name, title, path))
+ self._stages = tuple(ss)
if RESPONSE is not None:
RESPONSE.redirect(
'%s/manage_stagesForm?manage_tabs_message=Stages+changed.'
@@ -323,3 +440,4 @@
InitializeClass(StagingTool)
+
=== CMF/CMFStaging/VersionsTool.py 1.11 => 1.12 ===
--- CMF/CMFStaging/VersionsTool.py:1.11 Mon Feb 24 19:57:16 2003
+++ CMF/CMFStaging/VersionsTool.py Mon Oct 27 15:21:53 2003
@@ -26,6 +26,9 @@
from Products.CMFCore.utils import UniqueObject, SimpleItemWithProperties
from Products.CMFCore.CMFCorePermissions import ManagePortal
+from staging_utils import getPortal, verifyPermission, unproxied
+
+
# Permission name
UseVersionControl = 'Use version control'
@@ -68,45 +71,58 @@
repo = aq_acquire(self, self.repository_name, containment=1)
return repo
- # unprotected methods
+ def _getBranchName(self, info):
+ parts = info.version_id.split('.')
+ if len(parts) > 1:
+ return parts[-2]
+ return 'mainline'
+
+ # public methods
security.declarePublic('isUnderVersionControl')
- def isUnderVersionControl(self, object):
- """Returns a true value if the object is under version control."""
+ def isUnderVersionControl(self, obj):
+ """Returns a true value if the object is under version control.
+ """
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- return repo.isUnderVersionControl(object)
-
+ return repo.isUnderVersionControl(obj)
security.declarePublic('isCheckedOut')
- def isCheckedOut(self, object):
- """Returns a true value if the object is checked out."""
+ def isCheckedOut(self, obj):
+ """Returns a true value if the object is checked out.
+ """
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- if not repo.isUnderVersionControl(object):
+ if not repo.isUnderVersionControl(obj):
return 0
- info = repo.getVersionInfo(object)
+ info = repo.getVersionInfo(obj)
return (info.status == info.CHECKED_OUT)
security.declarePublic('isResourceUpToDate')
- def isResourceUpToDate(self, object, require_branch=0):
- """Return true if a version-controlled resource is up to date."""
+ def isResourceUpToDate(self, obj, require_branch=0):
+ """Return true if a version-controlled resource is up to date.
+ """
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- return repo.isResourceUpToDate(object, require_branch)
+ return repo.isResourceUpToDate(obj, require_branch)
# protected methods
- security.declareProtected(UseVersionControl, 'checkout')
- def checkout(self, object):
+ security.declarePublic('checkout')
+ def checkout(self, obj):
"""Opens the object for development.
Returns the object, which might be different from what was passed to
- the method if the object was replaced."""
+ the method if the object was replaced.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
old_state = None
- if not repo.isUnderVersionControl(object):
- get_transaction().commit(1) # Get _p_jar attributes set.
- repo.applyVersionControl(object)
+ if not repo.isUnderVersionControl(obj):
+ repo.applyVersionControl(obj)
elif self.auto_copy_forward:
- info = repo.getVersionInfo(object)
+ info = repo.getVersionInfo(obj)
stuck = (info.sticky and info.sticky[0] != 'B')
if stuck:
# The object has a sticky tag. Get it unstuck by
@@ -114,55 +130,71 @@
# has been checked out.
old_state = repo.getVersionOfResource(
info.history_id, info.version_id)
- # Momentarily revert to the mainline.
- object = repo.updateResource(object, 'mainline')
- repo.checkoutResource(object)
+ # Momentarily revert to the branch.
+ obj = repo.updateResource(obj, self._getBranchName(info))
+ obj = repo.checkoutResource(obj)
- # Copy the old state into the mainline object,
- # minus __vc_info__.
+ # Copy the old state into the object, minus __vc_info__.
# XXX There ought to be some way to do this more cleanly.
- object._p_changed = 1
- for key in object.__dict__.keys():
+ obj._p_changed = 1
+ for key in obj.__dict__.keys():
if key != '__vc_info__':
if not old_state.__dict__.has_key(key):
- del object.__dict__[key]
- for key in old_state.__dict__.keys():
+ del obj.__dict__[key]
+ for key, value in old_state.__dict__.items():
if key != '__vc_info__':
- object.__dict__[key] = old_state.__dict__[key]
+ obj.__dict__[key] = value
# Check in as a copy.
- repo.checkinResource(
- object, 'Copied from revision %s' % info.version_id)
-
- repo.checkoutResource(object)
+ obj = repo.checkinResource(
+ obj, 'Copied from revision %s' % info.version_id)
+ repo.checkoutResource(obj)
+ return None
- return object
-
- security.declareProtected(UseVersionControl, 'checkin')
- def checkin(self, object, message=''):
- """Checks in a new version."""
- # Make sure we copy the current data.
- # XXX ZopeVersionControl tries to do this but isn't quite correct yet.
- get_transaction().commit(1)
-
- # Check in or add the object to the repository.
+ security.declarePublic('checkin')
+ def checkin(self, obj, message=''):
+ """Checks in a new version.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- if not repo.isUnderVersionControl(object):
- repo.applyVersionControl(object)
+ if not repo.isUnderVersionControl(obj):
+ repo.applyVersionControl(obj)
else:
- info = repo.getVersionInfo(object)
- if info.status == info.CHECKED_OUT:
- repo.checkinResource(object, message)
+ if (not repo.isResourceUpToDate(obj, require_branch=1)
+ and self.isCheckedOut(obj)):
+ # This is a strange state, but it can be fixed.
+ # Revert the object to the branch, replace the
+ # reverted state with the new state, and check in.
+ new_dict = obj.__dict__.copy()
+ # Uncheckout
+ obj = repo.uncheckoutResource(obj)
+ info = repo.getVersionInfo(obj)
+ obj = repo.updateResource(obj, self._getBranchName(info))
+ # Checkout
+ obj = repo.checkoutResource(obj)
+ # Restore the new state
+ for key in obj.__dict__.keys():
+ if key != '__vc_info__':
+ if not new_dict.has_key(key):
+ del obj.__dict__[key]
+ for key, value in new_dict.items():
+ if key != '__vc_info__':
+ obj.__dict__[key] = value
+ repo.checkinResource(obj, message)
+ return None
- security.declareProtected(UseVersionControl, 'getLogEntries')
- def getLogEntries(self, object, only_checkins=0):
- """Returns the log entries for an object as a sequence of
- mappings."""
+ security.declarePublic('getLogEntries')
+ def getLogEntries(self, obj, only_checkins=0):
+ """Returns the log entries for an object as a sequence of mappings.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- if not repo.isUnderVersionControl(object):
+ if not repo.isUnderVersionControl(obj):
return []
- entries = repo.getLogEntries(object)
+ entries = repo.getLogEntries(obj)
res = []
for entry in entries:
a = entry.action
@@ -188,47 +220,57 @@
return res
- security.declareProtected(UseVersionControl, 'getVersionId')
- def getVersionId(self, object):
- """Returns the version ID of the current revision."""
+ security.declarePublic('getVersionId')
+ def getVersionId(self, obj):
+ """Returns the version ID of the current revision.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- if repo.isUnderVersionControl(object):
- return repo.getVersionInfo(object).version_id
+ if repo.isUnderVersionControl(obj):
+ return repo.getVersionInfo(obj).version_id
else:
return ''
- security.declareProtected(UseVersionControl, 'getVersionIds')
- def getVersionIds(self, object):
- """Return the available version ids for an object."""
- # yuck :(
+ security.declarePublic('getVersionIds')
+ def getVersionIds(self, obj):
+ """Returns the version IDs of all revisions for an object.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- ids = repo.getVersionIds(object)
+ ids = repo.getVersionIds(obj)
ids = map(int, ids)
ids.sort()
return map(str, ids)
- security.declareProtected(UseVersionControl, 'getHistoryId')
- def getHistoryId(self, object):
- """Returns the version history ID of the object."""
+ security.declarePublic('getHistoryId')
+ def getHistoryId(self, obj):
+ """Returns the version history ID of the object.
+ """
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
- return repo.getVersionInfo(object).history_id
+ return repo.getVersionInfo(obj).history_id
- security.declareProtected(UseVersionControl, 'revertToVersion')
- def revertToVersion(self, object, version_id):
+ security.declarePublic('revertToVersion')
+ def revertToVersion(self, obj, version_id):
"""Reverts the object to the given version.
If make_new_revision, a new revision is created, so that
the object's state can progress along a new line without
making the user deal with branches, labels, etc.
"""
+ verifyPermission(UseVersionControl, obj)
+ obj = unproxied(obj)
repo = self._getVersionRepository()
# Verify the object is under version control.
- repo.getVersionInfo(object)
- if self.isCheckedOut(object):
+ repo.getVersionInfo(obj)
+ if self.isCheckedOut(obj):
# Save the current data.
- self.checkin(object, 'Auto-saved')
- return repo.updateResource(object, version_id)
+ self.checkin(obj, 'Auto-saved')
+ repo.updateResource(obj, version_id)
InitializeClass(VersionsTool)
=== CMF/CMFStaging/WorkflowRepository.py 1.1 => 1.2 ===
=== CMF/CMFStaging/WorkflowWithRepositoryTool.py 1.1 => 1.2 ===
=== CMF/CMFStaging/__init__.py 1.3 => 1.4 ===
More information about the CMF-checkins
mailing list