diff --git a/browser/node_macros.pt b/browser/node_macros.pt
index 46b1d5b..4a422d9 100644
--- a/browser/node_macros.pt
+++ b/browser/node_macros.pt
@@ -226,7 +226,9 @@
diff --git a/resource.py b/resource.py
index 8279786..89454da 100644
--- a/resource.py
+++ b/resource.py
@@ -47,16 +47,17 @@ from cybertools.storage.interfaces import IExternalStorage
from cybertools.text.interfaces import ITextTransform
from cybertools.typology.interfaces import IType, ITypeManager
-from interfaces import IBaseResource, IResource
-from interfaces import IFile, IExternalFile, INote
-from interfaces import IDocument, ITextDocument, IDocumentSchema, IDocumentView
-from interfaces import IMediaAsset, IMediaAssetView
-from interfaces import IResourceManager, IResourceManagerContained
-from interfaces import ILoopsContained
-from interfaces import IIndexAttributes
-from concept import ResourceRelation
-from common import ResourceAdapterBase
-from view import TargetRelation
+from loops.interfaces import IBaseResource, IResource
+from loops.interfaces import IFile, IExternalFile, INote
+from loops.interfaces import IDocument, ITextDocument, IDocumentSchema, IDocumentView
+from loops.interfaces import IMediaAsset, IMediaAssetView
+from loops.interfaces import IResourceManager, IResourceManagerContained
+from loops.interfaces import ILoopsContained
+from loops.interfaces import IIndexAttributes
+from loops.concept import ResourceRelation
+from loops.common import ResourceAdapterBase
+from loops.versioning.util import getMaster
+from loops.view import TargetRelation
_ = MessageFactory('loops')
@@ -147,26 +148,31 @@ class Resource(Image, Contained):
def getClients(self, relationships=None):
if relationships is None:
relationships = [TargetRelation]
- # Versioning: obj = IVersionable(self).master
- rels = getRelations(second=self, relationships=relationships)
+ obj = getMaster(self) # use the master version for relations
+ rels = getRelations(second=obj, relationships=relationships)
return [r.first for r in rels]
# concept relations
+ # note: we always use the master version for relations, see getMaster()
def getConceptRelations (self, predicates=None, concept=None):
predicates = predicates is None and ['*'] or predicates
- relationships = [ResourceRelation(None, self, p) for p in predicates]
+ obj = getMaster(self)
+ relationships = [ResourceRelation(None, obj, p) for p in predicates]
# TODO: sort...
- return getRelations(first=concept, second=self, relationships=relationships)
+ return getRelations(first=concept, second=obj, relationships=relationships)
def getConcepts(self, predicates=None):
- return [r.first for r in self.getConceptRelations(predicates)]
+ obj = getMaster(self)
+ return [r.first for r in obj.getConceptRelations(predicates)]
def assignConcept(self, concept, predicate=None):
- concept.assignResource(self, predicate)
+ obj = getMaster(self)
+ concept.assignResource(obj, predicate)
def deassignConcept(self, concept, predicates=None):
- concept.deassignResource(self, predicates)
+ obj = getMaster(self)
+ concept.deassignResource(obj, predicates)
# ISized interface
diff --git a/versioning/README.txt b/versioning/README.txt
index f0cddca..01c7514 100644
--- a/versioning/README.txt
+++ b/versioning/README.txt
@@ -13,6 +13,7 @@ Setting up a loops Site and Utilities
Let's do some basic set up
>>> from zope import component, interface
+ >>> from zope.traversing.api import getName
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
@@ -42,6 +43,8 @@ We can access versioning information for an object by using an IVersionable
adapter on the object.
>>> d001 = resources['d001.txt']
+ >>> d001.title
+ u'Doc 001'
>>> vD001 = IVersionable(d001)
If there aren't any versions associated with the object we get the default
@@ -55,14 +58,70 @@ values:
{}
>>> vD001.currentVersion is d001
True
- >>> vD001.releasedVersion is d001
+ >>> vD001.releasedVersion is None
True
Now we can create a new version for our document:
- >>> d001v1_1 = vD001.createVersion()
- >>> sorted(resources)
+ >>> d001v1_2 = vD001.createVersion()
+ >>> getName(d001v1_2)
+ u'd001_1.2.txt'
+ >>> d001v1_2.title
+ u'Doc 001'
- >>> vD001v1_1 = IVersionable(d001v1_1)
- >>> vD001v1_1.versionId
+ >>> vD001v1_2 = IVersionable(d001v1_2)
+ >>> vD001v1_2.versionId
'1.2'
+
+ >>> vD001.currentVersion is d001v1_2
+ True
+ >>> vD001.master is d001
+ True
+ >>> vD001v1_2.master is d001
+ True
+
+ >>> sorted(vD001.versions)
+ ['1.1', '1.2']
+
+When we use a higer level (i.e. a lower number for level) to denote
+a major version change, the lower levels are reset to 1:
+
+ >>> d001v2_1 = vD001.createVersion(0)
+ >>> getName(d001v2_1)
+ u'd001_2.1.txt'
+
+
+Providing the Correct Version
+=============================
+
+When accessing resources as targets for view nodes, the node's traversal adapter
+(see loops.view.NodeTraverser) uses the versioning framework to retrieve
+the correct version of a resource by calling the getVersion() function.
+
+ >>> from loops.versioning.util import getVersion
+ >>> from zope.publisher.browser import TestRequest
+
+The default version is always the released or - if this is not available -
+the current version (i.e. the version created most recently):
+
+ >>> IVersionable(getVersion(d001, TestRequest())).versionId
+ '2.1'
+
+ >>> IVersionable(getVersion(d001v1_2, TestRequest())).versionId
+ '2.1'
+
+ >>> d002 = resources['d002.txt']
+ >>> IVersionable(getVersion(d002, TestRequest())).versionId
+ '1.1'
+
+When using the expression "version=this" as a URL parameter the object
+addressed will be returned without looking for a special version:
+
+ >>> IVersionable(getVersion(d001, TestRequest(form=dict(version='this')))).versionId
+ '1.1'
+
+In addition it is possible to explicitly retrieve a certain version:
+
+ >>> IVersionable(getVersion(d001v1_2, TestRequest(form=dict(version='1.1')))).versionId
+ '1.1'
+
diff --git a/versioning/interfaces.py b/versioning/interfaces.py
index a5080d7..0011c12 100644
--- a/versioning/interfaces.py
+++ b/versioning/interfaces.py
@@ -64,7 +64,3 @@ class IVersionable(Interface):
The level provides the position in the variantIds tuple.
"""
-
-class IVersionInfo(Interface):
- """ Versioning metadata, e.g. criteria for version selection.
- """
diff --git a/versioning/tests.py b/versioning/tests.py
index c77b101..198640f 100755
--- a/versioning/tests.py
+++ b/versioning/tests.py
@@ -3,7 +3,7 @@
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from zope.interface.verify import verifyClass
-from loops.versioning import versioninfo
+from loops.versioning import versionable
class Test(unittest.TestCase):
"Basic tests for the expert sub-package."
diff --git a/versioning/util.py b/versioning/util.py
new file mode 100644
index 0000000..61aad46
--- /dev/null
+++ b/versioning/util.py
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Utilities for managing version informations.
+
+$Id$
+"""
+
+from loops.versioning.interfaces import IVersionable
+
+
+def getVersion(obj, request):
+ """ Check if another version should be used for the object
+ provided and return it.
+ """
+ versionRequest = request.form.get('version')
+ if versionRequest == 'this':
+ # we really want this object, not another version
+ return obj
+ versionable = IVersionable(obj, None)
+ if versionable is None:
+ return obj
+ if not versionRequest:
+ # find and return a standard version
+ v = versionable.releasedVersion
+ if v is None:
+ v = versionable.currentVersion
+ return v
+ # we might have a versionId in the request
+ v = versionable.versions.get(versionRequest)
+ if v is not None:
+ return v
+ return obj
+
+
+def getMaster(obj):
+ versionable = IVersionable(obj, None)
+ if versionable is None:
+ return obj
+ return versionable.master
+
diff --git a/versioning/versionable.py b/versioning/versionable.py
index 8a28bc6..181fa9f 100644
--- a/versioning/versionable.py
+++ b/versioning/versionable.py
@@ -24,8 +24,9 @@ $Id$
from BTrees.OOBTree import OOBTree
from zope.component import adapts
-from zope.interface import implements
+from zope.interface import implements, Attribute
from zope.cachedescriptors.property import Lazy
+from zope.schema.interfaces import IField
from zope.traversing.api import getName, getParent
from cybertools.text.mimetypes import extensions
@@ -55,16 +56,19 @@ class VersionableResource(object):
return default
return value
- def initVersioningAttribute(self, attr, value):
- attrName = attrPattern % attr
- value = getattr(self.context, attrName, _not_found)
- if value is _not_found:
- setattr(self.context, attrName, value)
-
def setVersioningAttribute(self, attr, value):
attrName = attrPattern % attr
setattr(self.context, attrName, value)
+ def initVersions(self):
+ attrName = attrPattern % 'versions'
+ value = getattr(self.context, attrName, _not_found)
+ if value is _not_found:
+ versions = OOBTree()
+ versions['1.1'] = self.context
+ setattr(self.context, attrName, versions)
+ #self.versions['1.1'] = self.context
+
@Lazy
def versionNumbers(self):
return self.getVersioningAttribute('versionNumbers', (1, 1))
@@ -98,7 +102,7 @@ class VersionableResource(object):
@property
def releasedVersion(self):
m = self.versionableMaster
- return self.versionableMaster.getVersioningAttribute('releasedVersion', self.master)
+ return self.versionableMaster.getVersioningAttribute('releasedVersion', None)
def createVersion(self, level=1):
context = self.context
@@ -108,6 +112,9 @@ class VersionableResource(object):
while len(vn) <= level:
vn.append(1)
vn[level] += 1
+ for l in range(level+1, len(vn)):
+ # reset lower levels
+ vn[l] = 1
# create new object
cls = context.__class__
obj = cls()
@@ -123,18 +130,17 @@ class VersionableResource(object):
versionId)
getParent(context)[name] = obj
# set resource attributes
- obj.resourceType = context.resourceType
ti = IType(context).typeInterface
- if ti is not None:
- adaptedContext = ti(context)
- adaptedObj = ti(obj)
- for attr in ti:
- if attr not in ('resourceType',):
- setattr(adaptedObj, attr, getattr(adaptedContext, attr))
+ attrs = set((ti and list(ti) or [])
+ + ['title', 'description', 'data', 'contentType'])
+ adaptedContext = ti and ti(context) or context
+ adaptedObj = ti and ti(obj) or obj
+ for attr in attrs:
+ setattr(adaptedObj, attr, getattr(adaptedContext, attr))
# set attributes of the master version
- versionableMaster.initVersioningAttribute('versions', OOBTree())
- self.versions[versionId] = obj
versionableMaster.setVersioningAttribute('currentVersion', obj)
+ versionableMaster.initVersions()
+ self.versions[versionId] = obj
return obj
def generateName(self, name, ext, versionId):
diff --git a/versioning/versioninfo.py b/versioning/versioninfo.py
deleted file mode 100644
index a354a84..0000000
--- a/versioning/versioninfo.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-
-"""
-Utilities for managing version informations.
-
-$Id$
-"""
-
-from zope.interface import implements
-
-from loops.versioning.interfaces import IVersionInfo
-
-
-class VersionInfo(object):
- """ Collects and provides informations related to object versions.
- """
-
- implements(IVersionInfo)
-
-
-def getVersionInfo(obj, request):
- """ Check if a special version should be used for the object
- provided.
-
- In addition return meta information about the object versions
- so that this will not have to be retrieved later.
- """
- return obj, VersionInfo()
diff --git a/view.py b/view.py
index 7872168..e49e116 100644
--- a/view.py
+++ b/view.py
@@ -45,7 +45,7 @@ from loops.interfaces import IViewManager, INodeContained
from loops.interfaces import ILoopsContained
from loops.interfaces import ITargetRelation
from loops.interfaces import IConcept
-from loops.versioning.versioninfo import getVersionInfo
+from loops.versioning.util import getVersion
class View(object):
@@ -203,8 +203,6 @@ class NodeTraverser(ItemTraverser):
else:
target = self.context.target
if target is not None:
- # provide versioning info and switch to correct version if appropriate
- target, versionInfo = getVersionInfo(target, request)
# remember self.context in request
viewAnnotations = request.annotations.setdefault('loops.view', {})
viewAnnotations['node'] = self.context
@@ -212,9 +210,10 @@ class NodeTraverser(ItemTraverser):
# we have to use the target object directly
return target
else:
+ # switch to correct version if appropriate
+ target = getVersion(target, request)
# we'll use the target object in the node's context
viewAnnotations['target'] = target
- viewAnnotations['versionInfo'] = versionInfo
return self.context
return super(NodeTraverser, self).publishTraverse(request, name)
|