[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