[Zope3-checkins]
SVN: Zope3/branches/mkerrin-webdav/src/zope/app/dav/
Added support for dead properties, and updated the PROPFIND
Michael Kerrin
michael.kerrin at openapp.biz
Sun Feb 26 14:10:31 EST 2006
Log message for revision 65493:
Added support for dead properties, and updated the PROPFIND
and PROPPATCH method implementation to take advantage of the
dead properties.
Implemented the MOVE method.
Changed:
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_proppatch.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/metaconfigure.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/namespaces.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davduplicateproperty.zcml
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davnamespace.zcml
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_doctests.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_move.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_namespace.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_propfind.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_proppatch.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/xmldiff.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py
-=-
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -20,8 +20,8 @@
from xml.dom import minidom
from zope.interface import Interface, implements
+from zope.app.traversing.api import getName
-from zope.app import zapi
from zope.app.dav.interfaces import IDAVSchema, IActiveLock, ILockEntry, \
IDAVLockSchema, IDAVResourceSchema
from zope.app.dublincore.interfaces import IDCTimes
@@ -71,7 +71,7 @@
self.context = object
def displayname(self):
- value = zapi.name(self.context)
+ value = getName(self.context)
if IReadDirectory(self.context, None) is not None:
value = value + '/'
return value
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -23,11 +23,63 @@
from zope.publisher.http import status_reasons
from zope.security.proxy import removeSecurityProxy
-from zope.app import zapi
from zope import component
+from zope.app.traversing.browser.absoluteurl import absoluteURL
from zope.app.container.interfaces import IReadContainer
+################################################################################
+#
+# Common exceptions.
+#
+################################################################################
+class DAVError(Exception):
+ # override this value
+ status = None
+
+ def __init__(self, field_name, error_message):
+ self.field_name = field_name
+ self.error_message = error_message
+
+
+class PreConditionFailedError(DAVError):
+ """ """
+ status = 412
+
+
+class AlreadyLockedError(DAVError):
+ """ """
+ status = 423
+
+
+class DAVConflictError(DAVError):
+ """ """
+ status = 409
+
+
+class UnprocessableEntityError(DAVError):
+ """ """
+ status = 422
+
+
+class ForbiddenError(DAVError):
+ """ """
+ status = 403
+
+
+class BadDAVRequestError(DAVError):
+ status = 400
+
+ def __init__(self, error_message):
+ self.field_name = None
+ self.error_message = error_message
+
+################################################################################
+#
+# Helper classes for generating MultiSatus responses.
+#
+################################################################################
+
class IMultiStatus(Interface):
""" """
@@ -76,7 +128,7 @@
self.ms.appendChild(resp)
href = body.createElementNS(self.default_ns, 'href')
resp.appendChild(href)
- resource_url = zapi.absoluteURL(object, request)
+ resource_url = absoluteURL(object, request)
if IReadContainer.providedBy(object):
resource_url += '/'
href.appendChild(body.createTextNode(resource_url))
@@ -244,3 +296,122 @@
return lnr
raise NotFound(self.context, name, request)
+
+
+################################################################################
+#
+# Some XML help methods
+#
+################################################################################
+
+def makeDOMStandalone(element):
+ """Make a DOM Element Node standalone
+
+ The DOM tree starting at element is copied to a new DOM tree where:
+
+ - Any prefix used for the element namespace is removed from the element
+ and all attributes and decendant nodes.
+ - Any other namespaces used on in the DOM tree is explcitly declared on
+ the root element.
+
+ So, if the root element to be transformed is defined with a prefix, that
+ prefix is removed from the whole tree:
+
+ >>> dom = minidom.parseString('''<?xml version="1.0"?>
+ ... <foo xmlns:bar="http://bar.com">
+ ... <bar:spam><bar:eggs /></bar:spam>
+ ... </foo>''')
+ >>> element = dom.documentElement.getElementsByTagName('bar:spam')[0]
+ >>> standalone = makeDOMStandalone(element)
+ >>> standalone.toxml()
+ u'<spam><eggs/></spam>'
+
+ Prefixes are of course also removed from attributes:
+
+ >>> element.setAttributeNS(element.namespaceURI, 'bar:vikings',
+ ... 'singing')
+ >>> standalone = makeDOMStandalone(element)
+ >>> standalone.toxml()
+ u'<spam vikings="singing"><eggs/></spam>'
+
+ Any other namespace used will be preserved, with the prefix definitions
+ for these renamed and moved to the root element:
+
+ >>> dom = minidom.parseString('''<?xml version="1.0"?>
+ ... <foo xmlns:bar="http://bar.com" xmlns:mp="uri://montypython">
+ ... <bar:spam>
+ ... <bar:eggs mp:song="vikings" />
+ ... <mp:holygrail xmlns:c="uri://castle">
+ ... <c:camelot place="silly" />
+ ... </mp:holygrail>
+ ... <lancelot xmlns="uri://montypython" />
+ ... </bar:spam>
+ ... </foo>''')
+ >>> element = dom.documentElement.getElementsByTagName('bar:spam')[0]
+ >>> standalone = makeDOMStandalone(element)
+ >>> print standalone.toxml()
+ <spam xmlns:p0="uri://montypython" xmlns:p1="uri://castle">
+ <eggs p0:song="vikings"/>
+ <p0:holygrail>
+ <p1:camelot place="silly"/>
+ </p0:holygrail>
+ <p0:lancelot/>
+ </spam>
+ """
+
+ return DOMTransformer(element).makeStandalone()
+
+
+def _numberGenerator(i=0):
+ while True:
+ yield i
+ i += 1
+
+
+class DOMTransformer(object):
+ def __init__(self, el):
+ self.source = el
+ self.ns = el.namespaceURI
+ self.prefix = el.prefix
+ self.doc = minidom.getDOMImplementation().createDocument(
+ self.ns, el.localName, None)
+ self.dest = self.doc.documentElement
+ self.prefixes = {}
+ self._seq = _numberGenerator()
+
+ def seq(self): return self._seq.next()
+ seq = property(seq)
+
+ def _prefixForURI(self, uri):
+ if not uri or uri == self.ns:
+ return ''
+ if not self.prefixes.has_key(uri):
+ self.prefixes[uri] = 'p%d' % self.seq
+ return self.prefixes[uri] + ':'
+
+ def makeStandalone(self):
+ self._copyElement(self.source, self.dest)
+ for ns, prefix in self.prefixes.items():
+ self.dest.setAttribute('xmlns:%s' % prefix, ns)
+ return self.dest
+
+ def _copyElement(self, source, dest):
+ for i in range(source.attributes.length):
+ attr = source.attributes.item(i)
+ if attr.prefix == 'xmlns' or attr.nodeName == 'xmlns':
+ continue
+ ns = attr.prefix and attr.namespaceURI or source.namespaceURI
+ qname = attr.localName
+ if ns != dest.namespaceURI:
+ qname = '%s%s' % (self._prefixForURI(ns), qname)
+ dest.setAttributeNS(ns, qname, attr.value)
+
+ for node in source.childNodes:
+ if node.nodeType == node.ELEMENT_NODE:
+ ns = node.namespaceURI
+ qname = '%s%s' % (self._prefixForURI(ns), node.localName)
+ copy = self.doc.createElementNS(ns, qname)
+ self._copyElement(node, copy)
+ else:
+ copy = self.doc.importNode(node, True)
+ dest.appendChild(copy)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml 2006-02-26 19:10:29 UTC (rev 65493)
@@ -1,6 +1,9 @@
<configure
xmlns="http://namespaces.zope.org/zope"
- xmlns:dav="http://namespaces.zope.org/dav">
+ xmlns:dav="http://namespaces.zope.org/dav"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="zope.app.dav"
+ >
<publisher
name="WEBDAV"
@@ -78,15 +81,14 @@
allowed_attributes="UNLOCK"
/>
- <!-- Disabled for now. Need to write tests before checking in.
<view
- for="*"
- name="MOVE"
- type="zope.publisher.interfaces.http.IHTTPRequest"
- factory=".move.MOVE"
- permission="zope.ManageContent"
- allowed_attributes="MOVE" />
- -->
+ for="*"
+ name="MOVE"
+ type=".interfaces.IWebDAVRequest"
+ factory=".copy.MOVE"
+ permission="zope.ManageContent"
+ allowed_attributes="MOVE"
+ />
<view
for="zope.interface.Interface"
@@ -143,7 +145,7 @@
permissions for reading and writing -->
<adapter
factory=".opaquenamespaces.DAVOpaqueNamespacesAdapter"
- provides=".opaquenamespaces.IDAVOpaqueNamespaces"
+ provides=".interfaces.IDAVOpaqueNamespaces"
for="zope.app.annotation.interfaces.IAnnotatable"
permission="zope.Public"
trusted="true"
@@ -271,12 +273,41 @@
for="DAV:"
interface="zope.app.dav.interfaces.IDAVSchema" />
- <dav:namespace
- namespace="http://purl.org/dc/1.1"
- schemas="zope.app.dublincore.interfaces.IZopeDublinCore"
- interfaceType=".interfaces.IDCDAVNamespaceType"
+ <!--
+ Utility registration for use with dead properties.
+ -->
+ <utility
+ component=".namespaces.namespaceRegistry"
+ provides=".interfaces.INamespaceRegistry"
/>
+ <localUtility class=".namespaces.LocalNamespaceRegistry">
+ <factory
+ id="zope.app.dav.namespaces.LocalNamespaceRegistry"
+ />
+
+ <require
+ permission="zope.Public"
+ interface=".interfaces.INamespaceRegistry"
+ />
+ </localUtility>
+
+ <browser:tool
+ interface=".interfaces.INamespaceRegistry"
+ title="WebDAV Dead Namespace"
+ description="Need for enabling dead namespace support"
+ />
+
+ <browser:addMenuItem
+ title="WebDAV Dead Namespace"
+ description="Need for enabling dead namespace support"
+ class=".namespaces.LocalNamespaceRegistry"
+ permission="zope.ManageSite"
+ />
+
+ <!--
+ Register the WebDAV namespace.
+ -->
<dav:namespace
namespace="DAV:"
interfaceType=".interfaces.IDAVNamespaceType"
@@ -286,6 +317,7 @@
.interfaces.IGETDependentDAVSchema
.interfaces.IDAVLockSchema
.interfaces.IOptionalDAVSchema
+ .interfaces.IDAVSchema
">
<widget
@@ -295,4 +327,18 @@
</dav:namespace>
+ <!--
+ Some more optional schemas, and namespaces
+ -->
+ <dav:namespace
+ namespace="http://purl.org/dc/1.1"
+ schemas="zope.app.dublincore.interfaces.IZopeDublinCore"
+ interfaceType=".interfaces.IDCDAVNamespaceType"
+ />
+
+ <dav:schemas
+ namespace="DAV:"
+ schemas=".interfaces.IDAVSource"
+ />
+
</configure>
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -19,94 +19,152 @@
from urlparse import urlsplit
-from zope.app import zapi
-from zope.app.copypastemove.interfaces import IObjectMover, IObjectCopier
+from zope import component
+from zope.app.copypastemove.interfaces import IObjectCopier, IObjectMover
from zope.app.publication.http import MethodNotAllowed
-from zope.app.traversing.api import traverse, getRoot
+from zope.app.traversing.api import traverse, getRoot, getParent
from zope.app.traversing.interfaces import TraversalError
from interfaces import IIfHeader
+from common import DAVError, DAVConflictError, ForbiddenError, \
+ BadDAVRequestError, PreConditionFailedError, AlreadyLockedError
-class COPY(object):
+
+class Base(object):
+ """Base class for copying and moving objects. Contains some helper
+ methods shared between both methods implementations.
+ """
+
def __init__(self, context, request):
self.context = context
self.request = request
- def COPY(self):
- #
- # get and verify data send in the HTTP request
- #
+ def getDepth(self):
depth = self.request.getHeader('depth', 'infinity')
if depth not in ('0', 'infinity'):
- self.request.response.setStatus(400)
- return ''
+ raise BadDAVRequestError, "invalid depth"
+ return depth
- # can't copy method
- copier = IObjectCopier(self.context)
- if not copier.copyable():
- raise MethodNotAllowed(self.context, self.request)
-
- # find the destination
- dest = self.request.getHeader('destination', '')
+ def getDestinationPath(self):
+ # find the destination path
+ dest = self.request.getHeader('destination', None)
while dest and dest[-1] == '/':
dest = dest[:-1]
if not dest:
- self.request.response.setStatus(400)
- return ''
+ raise BadDAVRequestError, "invalid destination header"
- # find the overwrite header
- overwrite = self.request.getHeader('overwrite', 't').lower().strip()
- if overwrite == 't':
- overwrite = True
- elif overwrite == 'f':
- overwrite = False
- else:
- self.request.response.setStatus(400)
- return ''
+ scheme, location, destpath, query, fragment = urlsplit(dest)
+ return destpath
+
+ def getDestinationAndParentObject(self, default = None):
# find destination if it exists and if it
# dest is always treated has an absoluteURI has in rfc2396
- scheme, location, destpath, query, fragment = urlsplit(dest)
+ destpath = self.getDestinationPath()
try:
destob = traverse(getRoot(self.context), destpath)
- exists = True
except TraversalError:
destob = None
- exists = False
+ overwrite = self.getOverwrite()
+
+ parentpath = destpath.split('/')
+ destname = parentpath.pop()
+ try:
+ parent = traverse(getRoot(self.context), parentpath)
+ except TraversalError:
+ raise DAVConflictError(None, "failed to find destinations parent")
+
if destob is not None and not overwrite:
- self.request.response.setStatus(412)
- return ''
+ raise PreConditionFailedError(None,
+ "can't overwrite destination object")
elif destob is not None and destob is self.context:
- self.request.response.setStatus(403)
- return ''
+ raise ForbiddenError(None, "objects are the same")
elif destob is not None:
- ifparser = zapi.queryMultiAdapter((destob, self.request), IIfHeader)
+ ifparser = component.queryMultiAdapter((destob, self.request),
+ IIfHeader)
if ifparser is not None and not ifparser():
- self.request.response.setStatus(423)
- return ''
+ raise AlreadyLockedError(None,
+ "destination object is already locked")
# we need to delete this object
parent = destob.__parent__
del parent[destob.__name__]
- # check parent
- parentpath = destpath.split('/')
- destname = parentpath.pop()
- try:
- parent = traverse(getRoot(self.context), parentpath)
- except TraversalError:
- parent = None
- if parent is None:
+ return destob, parent
+
+ def getOverwrite(self):
+ # find the overwrite header
+ overwrite = self.request.getHeader('overwrite', 't').lower().strip()
+ if overwrite == 't':
+ overwrite = True
+ elif overwrite == 'f':
+ overwrite = False
+ else:
+ raise BadDAVRequestError, "invalid overwrite header"
+
+ return overwrite
+
+
+class COPY(Base):
+
+ def handleCopy(self):
+ # can't copy method
+ copier = IObjectCopier(self.context)
+ if not copier.copyable():
+ raise MethodNotAllowed(self.context, self.request)
+
+ # overwrite header
+ overwrite = self.getOverwrite()
+
+ # get the destination if it exists
+ destob, parent = self.getDestinationAndParentObject()
+
+ destname = self.getDestinationPath().split('/').pop()
+
+ if not copier.copyableTo(parent, destname):
self.request.response.setStatus(409)
return ''
- if not copier.copyableTo(parent):
+ copier.copyTo(parent, destname)
+
+ self.request.response.setStatus(destob is not None and 204 or 201)
+ return ''
+
+ def COPY(self):
+ try:
+ return self.handleCopy()
+ except DAVError, e:
+ self.request.response.setStatus(e.status)
+ return ''
+
+
+class MOVE(Base):
+
+ def handleMove(self):
+ # can't copy method
+ mover = IObjectMover(self.context)
+ if not mover.moveable():
+ raise MethodNotAllowed(self.context, self.request)
+
+ # get the destination if it exists
+ destob, parent = self.getDestinationAndParentObject()
+
+ destname = self.getDestinationPath().split('/').pop()
+
+ if not mover.moveableTo(parent, destname):
self.request.response.setStatus(409)
return ''
- copier.copyTo(parent, destname)
+ mover.moveTo(parent, destname)
- self.request.response.setStatus(exists and 204 or 201)
+ self.request.response.setStatus(destob is not None and 204 or 201)
return ''
+
+ def MOVE(self):
+ try:
+ return self.handleMove()
+ except DAVError, e:
+ self.request.response.setStatus(e.status)
+ return ''
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -22,11 +22,12 @@
from unittest import TestSuite, makeSuite, main
from zope.interface import Interface
-from zope.app import zapi
+from zope import component
from zope.app.testing import setup, ztapi
from zope.app.locking.interfaces import ILockStorage, ILockable
from zope.app.locking.storage import PersistentLockStorage
from zope.app.locking.adapter import LockingAdapterFactory, LockingPathAdapter
+from zope.app.traversing.api import traverse
from zope.app.traversing.interfaces import IPathAdapter
from zope.app.file.file import File
@@ -110,7 +111,7 @@
def setUp(self):
super(TestLOCK, self).setUp()
- sm = zapi.getSiteManager(self.getRootFolder())
+ sm = component.getSiteManager(self.getRootFolder())
self.storage = storage = PersistentLockStorage()
setup.addUtility(sm, '', ILockStorage, storage)
@@ -152,7 +153,7 @@
self.assertEqual(result.getStatus(), 200)
## ILockable doesn't work in this context.
- file = zapi.traverse(self.getRootFolder(), '/file')
+ file = traverse(self.getRootFolder(), '/file')
lock = self.storage.getLock(file)
self.assert_(lock is not None)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_proppatch.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_proppatch.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_proppatch.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -20,13 +20,25 @@
from zope.pagetemplate.tests.util import normalize_xml
from zope.publisher.http import status_reasons
-from zope.app.dav.ftests.dav import DAVTestCase
+from zope import component
+from zope.app.testing import setup
from zope.app.dublincore.interfaces import IZopeDublinCore
-from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
from zope.app.traversing.api import traverse
+from zope.app.dav.ftests.dav import DAVTestCase
+from zope.app.dav.interfaces import IDAVOpaqueNamespaces
+from zope.app.dav.interfaces import INamespaceRegistry
+from zope.app.dav.namespaces import LocalNamespaceRegistry
+
class TestPROPPATCH(DAVTestCase):
+ def setUp(self):
+ super(TestPROPPATCH, self).setUp()
+
+ sm = component.getSiteManager(self.getRootFolder())
+ self.registry = registry = LocalNamespaceRegistry()
+ setup.addUtility(sm, '', INamespaceRegistry, registry)
+
def test_set(self):
self.addPage('/pt', u'<span />')
transaction.commit()
@@ -47,7 +59,7 @@
self.verifyPropOK(path='/pt', namespaces=(('foo', 'uri://foo'),),
rm=('<foo:bar/>',), expect=expect)
self._assertOPropsEqual(pt, {})
-
+
def test_complex(self):
self.addPage('/pt', u'<span />')
pt = traverse(self.getRootFolder(), '/pt')
@@ -84,7 +96,7 @@
transaction.commit()
expect = self._makePropstat(('http://purl.org/dc/1.1',),
'<title xmlns="a0"/>')
- self.verifyPropOK(path='/pt',
+ self.verifyPropOK(path='/pt',
namespaces=(('DC', 'http://purl.org/dc/1.1'),),
set=('<DC:title>Test Title</DC:title>',), expect=expect)
self.assertEqual(IZopeDublinCore(pt).title, u'Test Title')
@@ -98,7 +110,7 @@
self.assertEqual(namespacesA, namespacesB,
'available opaque namespaces were %s, '
'expected %s' % (namespacesA, namespacesB))
-
+
for ns in namespacesA:
propnamesA = list(oprops[ns].keys())
propnamesA.sort()
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -19,7 +19,7 @@
from zope.interface import implements
-from zope.app import zapi
+from zope.app.traversing.browser.absoluteurl import absoluteURL
from zope.app.container.interfaces import IReadContainer
from zope.app.locking.interfaces import ILockable
@@ -121,7 +121,7 @@
tags = self.ifParser(ifhdr)
- resource_url = zapi.absoluteURL(self.context, self.request)
+ resource_url = absoluteURL(self.context, self.request)
if IReadContainer.providedBy(self.context):
resource_url += '/'
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -20,6 +20,7 @@
from zope.interface import Interface, Attribute
from zope.interface.interfaces import IInterface
from zope.interface.common.mapping import IEnumerableMapping
+from zope.interface.common.mapping import IMapping
from zope.schema import Int, Text, TextLine, Dict, Datetime, Date, List, Bool
from zope.schema.interfaces import IText, IList
from zope.app.form.interfaces import IWidget
@@ -56,6 +57,30 @@
"""
+class IDAVOpaqueNamespaces(IMapping):
+ """Opaque storage for non-registered DAV namespace properties.
+
+ Any unknown (no interface registered) DAV properties are stored opaquely
+ keyed on their namespace URI, so we can return them later when requested.
+ """
+
+ def getProperty(namespace, name):
+ """
+ """
+
+ def setProperty(namespace, name, prop):
+ """
+ """
+
+ def hasProperty(namespace, name):
+ """
+ """
+
+ def removeProperty(namespace, name):
+ """
+ """
+
+
class IDAVResourceSchema(Interface):
"""DAV properties required for Level 1 compliance"""
@@ -363,7 +388,6 @@
based on the field constraints.
"""
- ## this shouldn't be used ???
def setRenderedValue(value):
"""Set the value of the field associated with this widget.
@@ -390,11 +414,7 @@
WebDAV namespace other then the default DAV: namespace.
"""
-## def removeProperty(self, ns, prop):
-## """
-## """
-
class IIfHeader(Interface):
"""RFC 2518 Section ...
"""
@@ -419,37 +439,16 @@
properties, and finding what widgets are used to render these properties.
"""
- #
- # Registration methods
- #
-
- def registerSchema(schema, restricted_properties = ()):
- """Register the interface, schema, with the namespace manager. Each
- field defined in the interface will define a property within the
- namespace.
-
- restricted_properties is a list of field names defined in schema
- which whose corresponding property should not be rendered in
- response to a PROPFIND request for all properties.
+ def isLiveNamespace():
+ """Return boolean saying whether this namespace is live or not.
"""
- def registerWidget(propname, widget):
- """Register a custom widget for the property named propname.
-
- The widget is a callable taking the current bound field representing
- the property named propname and the current request, On being called
- widget must return an object implementing IDAVWidget.
+ def queryProperty(object, name, default = None):
+ """Return property if it exists similar to getProperty or else
+ return default.
"""
- #
- # namespace management methods
- #
-
- def hasProperty(object, propname):
- """This namespace has a property called propname defined for object.
- """
-
- def getProperty(object, propname):
+ def getProperty(object, name):
"""Get the field, propname, for this object, is it exists.
The returned value will implement zope.schema.interfaces.IField and will
@@ -459,7 +458,12 @@
is not defined for object.
"""
- def getWidget(object, request, propname, ns_prefix):
+ def removeProperty(object, name):
+ """Remove property or in the case of a live namespace just set to
+ default namespace.
+ """
+
+ def getWidget(object, request, name, ns_prefix):
"""Get the WebDAV widget to be used to render / store the property
propname.
@@ -488,11 +492,61 @@
registered has a restricted property.
"""
- def isRestrictedProperty(object, propname):
+ def isRestrictedProperty(object, name):
"""Is the named property a restricted property for this object.
"""
+class ILiveNamespaceManager(INamespaceManager):
+ """Define two methods use to register information about a namespace
+ containing live properties.
+ """
+
+ def registerSchema(schema, restricted_properties = ()):
+ """Register the interface, schema, with the namespace manager. Each
+ field defined in the interface will define a property within the
+ namespace.
+
+ restricted_properties is a list of field names defined in schema
+ which whose corresponding property should not be rendered in
+ response to a PROPFIND request for all properties.
+ """
+
+ def registerWidget(propname, widget):
+ """Register a custom widget for the property named propname.
+
+ The widget is a callable taking the current bound field representing
+ the property named propname and the current request, On being called
+ widget must return an object implementing IDAVWidget.
+ """
+
+class INamespaceRegistry(Interface):
+ """
+ """
+
+ def getNamespaceManager(namespace):
+ """Return an utility providing the INamespaceManager interface. In the
+ case of a dead namespace - return a special implementaion of
+ INamespaceManager that knows how to handle dead properties.
+
+ If the implementation of this utility doesn't know how to handle
+ namespaces then we should raise a ComponentLookupError.
+ """
+
+ def queryNamespaceManager(namespace, default = None):
+ """
+ """
+
+ def hasNamespaceManager(namespace):
+ """Can this utility find an implementation of INamespaceManager
+ """
+
+ def getAllNamespaceManagers():
+ """Returns a iterable of all utilities providing INamepsaceManager
+ that this utility knows about.
+ """
+
+
class IWebDAVRequest(IHTTPRequest):
"""
"""
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -23,10 +23,11 @@
from xml.parsers import expat
from zope.interface import Interface, implements, implementer
+from zope import component
from zope.component import adapter
from zope.publisher.interfaces.http import IHTTPRequest
-from zope.app import zapi
+from zope.app.traversing.browser.absoluteurl import absoluteURL
from zope.app.form.utility import setUpWidget
from zope.app.locking.interfaces import ILockable, ILockStorage, LockingError
from zope.app.container.interfaces import IReadContainer
@@ -35,6 +36,8 @@
from interfaces import IDAVWidget, IActiveLock, ILockEntry, \
IDAVLockSchema, IIfHeader, IWebDAVRequest
from common import MultiStatus
+from common import DAVError, PreConditionFailedError, AlreadyLockedError, \
+ DAVConflictError, UnprocessableEntityError
MAXTIMEOUT = (2L**32)-1
DEFAULTTIMEOUT = 12 * 60L
@@ -42,35 +45,6 @@
_randGen = random.Random(time.time())
-class DAVLockingError(Exception):
- # override this value
- status = None
-
- def __init__(self, field_name, error_message):
- self.field_name = field_name
- self.error_message = error_message
-
-
-class PreConditionFailedError(DAVLockingError):
- """ """
- status = 412
-
-
-class AlreadyLockedError(DAVLockingError):
- """ """
- status = 423
-
-
-class DAVConflictError(DAVLockingError):
- """ """
- status = 409
-
-
-class UnprocessableEntityError(DAVLockingError):
- """ """
- stauts = 422
-
-
@adapter(Interface, IWebDAVRequest)
@implementer(Interface)
def LOCKMethodFactory(context, request):
@@ -84,6 +58,7 @@
class LOCK(object):
+
def __init__(self, context, request):
self.context = context
self.request = request
@@ -154,7 +129,7 @@
self.handleRefreshLockedObject(self.context)
else:
self.handleLockObject(xmldocBody, self.context, None)
- except DAVLockingError, e:
+ except DAVError, e:
self.request.response.setStatus(e.status)
return ''
@@ -171,11 +146,12 @@
lockable = ILockable(object)
lockinfo = lockable.getLockInfo()
- resource_url = zapi.absoluteURL(object, self.request)
+ resource_url = absoluteURL(object, self.request)
if IReadContainer.providedBy(object):
resource_url += '/'
- ifparser = zapi.queryMultiAdapter((object, self.request), IIfHeader)
+ ifparser = component.queryMultiAdapter((object, self.request),
+ IIfHeader)
if ifparser is not None and not ifparser():
raise PreConditionFailedError(None, "if header match failed")
@@ -242,7 +218,7 @@
for id, obj in object.items():
try:
self.handleLockObject(xmldoc, obj, token)
- except DAVLockingError, error:
+ except DAVError, error:
self._addError(obj, error.status)
def _renderResponse(self):
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/metaconfigure.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/metaconfigure.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/metaconfigure.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -41,10 +41,6 @@
#
################################################################################
-def _addWidgetToRegistry(registry, propertyname, class_):
- registry.registerWidget(propertyname, class_)
-
-
class namespace(object):
def __init__(self, _context, namespace, schemas = [],
restricted_properties = [], interfaceType = None):
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/namespaces.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/namespaces.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/namespaces.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -1,13 +1,40 @@
-from zope.interface import Interface, implements, Attribute
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""WebDAV namespace management utilities
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from persistent import Persistent
+from BTrees.OOBTree import OOBTree
+from zope.interface import implements
from zope import component
-from zope.schema import getFieldNamesInOrder, getFieldNames
+from zope.schema import getFieldNames
+from zope.schema import Field
+from zope.app.container.contained import Contained
-from interfaces import IDAVNamespaceType, IDAVWidget
-from interfaces import INamespaceManager
+from interfaces import IDAVWidget
+from interfaces import INamespaceManager, ILiveNamespaceManager, \
+ INamespaceRegistry
+from interfaces import IDAVOpaqueNamespaces
+from common import DAVConflictError
+from widget import DAVOpaqueWidget
class NamespaceManager(object):
- implements(INamespaceManager)
+ implements(ILiveNamespaceManager)
def __init__(self, namespace, schemas = [], restricted_properties = [],
interfaceType = None):
@@ -57,7 +84,7 @@
self.properties[propname] = props[propname]
for propname in restricted_properties:
- if not self.restricted_properties.has_key(propname):
+ if propname not in self.restricted_properties:
self.restricted_properties.append(propname)
def registerWidget(self, propname, widget):
@@ -66,33 +93,60 @@
"There must exist a property for the widget you are registering"
self.widgets[propname] = widget
- def _getAdapter(self, object, propname):
- schema = self.properties.get(propname, None)
+ def _getStorageAdapter(self, object, name, default = None):
+ schema = self.properties.get(name, None)
if schema is not None:
- return component.queryAdapter(object, schema, default = None)
+ return component.queryAdapter(object, schema, default = default)
- return None
+ return default
- def hasProperty(self, object, propname):
- adapter = self._getAdapter(object, propname)
- if adapter is None:
- return False
+ def isLiveNamespace(self):
return True
+ def queryProperty(self, object, name, default = None):
+ adapter = self._getStorageAdapter(object, name, None)
+ if adapter is None:
+ return default
+
+ return self.getProperty(object, name)
+
def getProperty(self, object, name):
- adapter = self._getAdapter(object, name)
+ adapter = self._getStorageAdapter(object, name)
if adapter is None:
- raise TypeError, "no property found"
+ raise TypeError, \
+ "Property %s doesn't exist for the context object" % name
field = self.properties[name][name]
field = field.bind(adapter)
return field
+ def removeProperty(self, object, name):
+ # XXX should this level of support for the WebDAV protocol be supported
+ # in this utility.
+ adapter = self._getStorageAdapter(object, name)
+ if adapter is None:
+ return # nothing to do :-)
+ prop = self.getProperty(object, name)
+
+ if prop.readonly:
+ raise DAVConflictError(name, "property is readonly")
+
+ if prop.required:
+ if prop.default is None:
+ # Clearing a required property is a conflict
+ raise DAVConflictError(name, "the property is required")
+ # Reset the field to the default if a value is required
+ prop.set(adapter, prop.default)
+ else:
+ prop.set(adapter, prop.missing_value)
+
def getWidget(self, object, request, name, ns_prefix):
- adapter = self._getAdapter(object, name)
+ adapter = self._getStorageAdapter(object, name)
if adapter is None:
- raise TypeError, "no property found"
+ raise TypeError, \
+ "Failed to find the property %s for the current context" \
+ " object" % name
field = self.properties[name][name]
field = field.bind(adapter)
@@ -128,7 +182,178 @@
yield field
def isRestrictedProperty(self, object, name):
- if self.hasProperty(object, name):
+ if self.queryProperty(object, name, None) is not None:
if name in self.restricted_properties:
return True
return False
+
+################################################################################
+#
+# Utility to handle all namespaces including dead property namespaces.
+#
+################################################################################
+
+class DeadPropertyField(Field):
+ """Dead properties are stored in dictionary-like objects. This are the
+ storage adapters that implement the IDAVOpaqueNamespace interface.
+ """
+
+ def __init__(self, namespace, **kw):
+ super(DeadPropertyField, self).__init__(**kw)
+ self.namespace = namespace
+
+ def get(self, object):
+ return object.getProperty(self.namespace, self.__name__)
+
+ def query(self, object, default = None):
+ if object.hasProperty(self.namespace, self.__name__):
+ return self.get(object)
+ return default
+
+ def set(self, object, value):
+ object.setProperty(self.namespace, self.__name__, value)
+
+
+class DeadNamespaceManager(Persistent, Contained):
+ implements(INamespaceManager)
+
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+ def isLiveNamespace(self):
+ return False
+
+ def queryProperty(self, object, name, default = None):
+ adapter = IDAVOpaqueNamespaces(object, None)
+ if adapter is None:
+ return default
+
+ if adapter.hasProperty(self.namespace, name):
+ return self.getProperty(object, name)
+
+ return default
+
+ def getProperty(self, object, name):
+ adapter = IDAVOpaqueNamespaces(object)
+
+ field = DeadPropertyField(namespace = self.namespace,
+ title = u'Dead Property for %s' % name,
+ __name__ = name, required = False,
+ readonly = False, default = None)
+ field = field.bind(adapter)
+
+ return field
+
+ def removeProperty(self, object, name):
+ adapter = IDAVOpaqueNamespaces(object, None)
+ if adapter is None or not adapter.hasProperty(self.namespace, name):
+ return # nothing to do
+
+ adapter.removeProperty(self.namespace, name)
+
+ def getWidget(self, object, request, name, ns_prefix):
+ adapter = IDAVOpaqueNamespaces(object)
+ prop = self.getProperty(object, name)
+
+ value = None
+ if adapter.hasProperty(self.namespace, name):
+ value = adapter.getProperty(self.namespace, name)
+
+ widget = DAVOpaqueWidget(prop, request)
+ widget.setRenderedValue(value)
+ widget.setNamespace(self.namespace, ns_prefix)
+
+ return widget
+
+ def getAllPropertyNames(self, object, restricted = True):
+ adapter = IDAVOpaqueNamespaces(object, None)
+ if adapter is None:
+ return []
+
+ names = []
+ for propname in adapter.get(self.namespace, {}):
+ names.append(propname)
+
+ return names
+
+ def getAllProperties(self, object, restricted = True):
+ for propname in self.getAllPropertyNames(object):
+ yield DeadPropertyField(namespace = self.namespace,
+ title = u'Dead Property for %s' % propname,
+ __name__ = propname,
+ required = False,
+ readonly = False,
+ default = None)
+
+ def isRestrictedProperty(self, object, name):
+ return False
+
+
+class LocalNamespaceRegistry(Persistent, Contained):
+ """Supports dead namespaces.
+ """
+ implements(INamespaceRegistry)
+
+ def __init__(self):
+ self.namespaces = OOBTree()
+
+ def getNamespaceManager(self, namespace):
+ nsmanager = self.namespaces.get(namespace, None)
+ if nsmanager is not None:
+ return nsmanager
+
+ nsmanager = component.queryUtility(INamespaceManager, namespace,
+ default = None)
+ if nsmanager is not None:
+ return nsmanager
+
+ nsmanager = DeadNamespaceManager(namespace)
+ self.namespaces[namespace] = nsmanager
+ return nsmanager
+
+ def queryNamespaceManager(self, namespace, default = None):
+ nsmanager = self.namespaces.get(namespace, None)
+ if nsmanager is not None:
+ return nsmanager
+
+ nsmanager = component.queryUtility(INamespaceManager, namespace,
+ default = None)
+ if nsmanager is not None:
+ return nsmanager
+
+ nsmanager = DeadNamespaceManager(namespace)
+ self.namespaces[namespace] = nsmanager
+ return nsmanager
+
+ def hasNamespaceManager(self, namespace):
+ nsmanager = self.queryNamespaceManager(namespace, None)
+ if nsmanager is None:
+ return False
+ return True
+
+ def getAllNamespaceManagers(self):
+ for nsmanager in self.namespaces.values():
+ yield nsmanager
+ for ns, nsmanager in component.getUtilitiesFor(INamespaceManager):
+ yield nsmanager
+
+
+class NamespaceRegistry(object):
+ implements(INamespaceRegistry)
+
+ def getNamespaceManager(self, namespace):
+ return component.getUtility(INamespaceManager, namespace)
+
+ def queryNamespaceManager(self, namespace, default = None):
+ return component.queryUtility(INamespaceManager, namespace,
+ default = default)
+
+ def hasNamespaceManager(self, namespace):
+ return self.queryNamespaceManager(namespace, None) is not None
+
+ def getAllNamespaceManagers(self):
+ for namespace, nsmanager in \
+ component.getUtilitiesFor(INamespaceManager):
+ yield nsmanager
+
+namespaceRegistry = NamespaceRegistry()
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -21,49 +21,17 @@
__docformat__ = 'restructuredtext'
from UserDict import DictMixin
-from xml.dom import minidom
from zope.interface import implements
-from zope.interface.common.mapping import IMapping
from zope.app.annotation.interfaces import IAnnotations, IAnnotatable
from zope.app.location import Location
+from interfaces import IDAVOpaqueNamespaces
+
from BTrees.OOBTree import OOBTree
DANkey = "zope.app.dav.DAVOpaqueProperties"
-
-class IDAVOpaqueNamespaces(IMapping):
- """Opaque storage for non-registered DAV namespace properties.
-
- Any unknown (no interface registered) DAV properties are stored opaquely
- keyed on their namespace URI, so we can return them later when requested.
- Thus this is a mapping of a mapping.
-
- """
-
- def renderProperty(ns, nsprefix, prop):
- """Render the named property to a DOM subtree
-
- ns and prop are keys defining the property, nsprefix is the namespace
- prefix used in the DOM for the namespace of the property, and propel
- is the <prop> element in the target DOM to which the property DOM
- elements need to be added.
-
- """
-
- def setProperty(propel):
- """Store a DOM property in the opaque storage
-
- propel is expected to be a DOM element from which the namespace and
- property name are taken to be stored.
-
- """
-
- def removeProperty(ns, prop):
- """Remove the indicated property altogether"""
-
-
class DAVOpaqueNamespacesAdapter(DictMixin, Location):
"""Adapt annotatable objects to DAV opaque property storage."""
@@ -104,145 +72,26 @@
self._changed()
#
- # Convenience methods; storing and retrieving properties through WebDAV
- # It may be better to use specialised IDAWWidget implementatins for this.
+ # methods that simplify the reteriving of dead properties.
#
- def renderProperty(self, ns, nsprefix, prop):
- """Render a property as DOM elements"""
- value = self.get(ns, {}).get(prop)
- if value is None:
- return
- value = minidom.parseString(value)
- ## el = propel.ownerDocument.importNode(value.documentElement, True)
- el = value.documentElement
- el.setAttributeNS(ns, 'xmlns', nsprefix)
- ## propel.appendChild(el)
+ def hasProperty(self, namespace, name):
+ if self._mapping.has_key(namespace):
+ return self._mapping[namespace].has_key(name)
+ return False
- return el
+ def getProperty(self, namespace, name):
+ return self._mapping[namespace][name]
- def setProperty(self, propel):
- ns = propel.namespaceURI
- props = self.setdefault(ns, OOBTree())
- propel = makeDOMStandalone(propel)
- props[propel.nodeName] = propel.toxml('utf-8')
-
- def removeProperty(self, ns, prop):
- if self.get(ns, {}).get(prop) is None:
- return
- del self[ns][prop]
- if not self[ns]:
- del self[ns]
+ def setProperty(self, namespace, name, propval):
+ if not self.hasProperty(namespace, name):
+ if not self.has_key(namespace):
+ self[namespace] = OOBTree()
+ ns = self[namespace]
+ ns[name] = propval
-
-def makeDOMStandalone(element):
- """Make a DOM Element Node standalone
-
- The DOM tree starting at element is copied to a new DOM tree where:
-
- - Any prefix used for the element namespace is removed from the element
- and all attributes and decendant nodes.
- - Any other namespaces used on in the DOM tree is explcitly declared on
- the root element.
-
- So, if the root element to be transformed is defined with a prefix, that
- prefix is removed from the whole tree:
-
- >>> dom = minidom.parseString('''<?xml version="1.0"?>
- ... <foo xmlns:bar="http://bar.com">
- ... <bar:spam><bar:eggs /></bar:spam>
- ... </foo>''')
- >>> element = dom.documentElement.getElementsByTagName('bar:spam')[0]
- >>> standalone = makeDOMStandalone(element)
- >>> standalone.toxml()
- u'<spam><eggs/></spam>'
-
- Prefixes are of course also removed from attributes:
-
- >>> element.setAttributeNS(element.namespaceURI, 'bar:vikings',
- ... 'singing')
- >>> standalone = makeDOMStandalone(element)
- >>> standalone.toxml()
- u'<spam vikings="singing"><eggs/></spam>'
-
- Any other namespace used will be preserved, with the prefix definitions
- for these renamed and moved to the root element:
-
- >>> dom = minidom.parseString('''<?xml version="1.0"?>
- ... <foo xmlns:bar="http://bar.com" xmlns:mp="uri://montypython">
- ... <bar:spam>
- ... <bar:eggs mp:song="vikings" />
- ... <mp:holygrail xmlns:c="uri://castle">
- ... <c:camelot place="silly" />
- ... </mp:holygrail>
- ... <lancelot xmlns="uri://montypython" />
- ... </bar:spam>
- ... </foo>''')
- >>> element = dom.documentElement.getElementsByTagName('bar:spam')[0]
- >>> standalone = makeDOMStandalone(element)
- >>> print standalone.toxml()
- <spam xmlns:p0="uri://montypython" xmlns:p1="uri://castle">
- <eggs p0:song="vikings"/>
- <p0:holygrail>
- <p1:camelot place="silly"/>
- </p0:holygrail>
- <p0:lancelot/>
- </spam>
- """
-
- return DOMTransformer(element).makeStandalone()
-
-
-def _numberGenerator(i=0):
- while True:
- yield i
- i += 1
-
-
-class DOMTransformer(object):
- def __init__(self, el):
- self.source = el
- self.ns = el.namespaceURI
- self.prefix = el.prefix
- self.doc = minidom.getDOMImplementation().createDocument(
- self.ns, el.localName, None)
- self.dest = self.doc.documentElement
- self.prefixes = {}
- self._seq = _numberGenerator()
-
- def seq(self): return self._seq.next()
- seq = property(seq)
-
- def _prefixForURI(self, uri):
- if not uri or uri == self.ns:
- return ''
- if not self.prefixes.has_key(uri):
- self.prefixes[uri] = 'p%d' % self.seq
- return self.prefixes[uri] + ':'
-
- def makeStandalone(self):
- self._copyElement(self.source, self.dest)
- for ns, prefix in self.prefixes.items():
- self.dest.setAttribute('xmlns:%s' % prefix, ns)
- return self.dest
-
- def _copyElement(self, source, dest):
- for i in range(source.attributes.length):
- attr = source.attributes.item(i)
- if attr.prefix == 'xmlns' or attr.nodeName == 'xmlns':
- continue
- ns = attr.prefix and attr.namespaceURI or source.namespaceURI
- qname = attr.localName
- if ns != dest.namespaceURI:
- qname = '%s%s' % (self._prefixForURI(ns), qname)
- dest.setAttributeNS(ns, qname, attr.value)
-
- for node in source.childNodes:
- if node.nodeType == node.ELEMENT_NODE:
- ns = node.namespaceURI
- qname = '%s%s' % (self._prefixForURI(ns), node.localName)
- copy = self.doc.createElementNS(ns, qname)
- self._copyElement(node, copy)
- else:
- copy = self.doc.importNode(node, True)
- dest.appendChild(copy)
+ def removeProperty(self, namespace, name):
+ if self.hasProperty(namespace, name):
+ del self[namespace][name]
+ if not self[namespace]:
+ del self[namespace]
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -18,15 +18,10 @@
from xml.dom import minidom
from xml.parsers import expat
-from zope.schema import getFieldNamesInOrder, getFields
-from zope.publisher.http import status_reasons
-
from zope import component
from zope.app.container.interfaces import IReadContainer
-from zope.app.form.utility import setUpWidget
-from interfaces import IDAVWidget, IDAVNamespace, INamespaceManager
-from opaquenamespaces import IDAVOpaqueNamespaces
+from interfaces import INamespaceRegistry
from common import MultiStatus
class PROPFIND(object):
@@ -80,21 +75,23 @@
resp = self.resp = \
self.responsedoc.addResponse(self.context, self.request)
+ nr = component.getUtility(INamespaceRegistry, context = self.context)
+
if xmldoc is not None:
propname = xmldoc.getElementsByTagNameNS(
self.default_ns, 'propname')
if propname:
- self._renderPropnameResponse(resp)
+ self._renderPropnameResponse(nr, resp)
else:
source = xmldoc.getElementsByTagNameNS(self.default_ns, 'prop')
if len(source) == 0:
- self._renderAllProperties(resp)
+ self._renderAllProperties(nr, resp)
elif len(source) == 1:
- self._renderSelectedProperties(resp, source[0])
+ self._renderSelectedProperties(nr, resp, source[0])
else:
raise Exception, "something has gone wrong here"
else:
- self._renderAllProperties(resp)
+ self._renderAllProperties(nr, resp)
self._depthRecurse(xmldoc)
@@ -116,22 +113,23 @@
for r in responses:
self.responsedoc.appendResponse(r)
- def _renderAllProperties(self, response):
+ def _renderAllProperties(self, nsregistry, response):
count = 0
- for namespace, nsmanager in \
- component.getUtilitiesFor(INamespaceManager):
+ for nsmanager in nsregistry.getAllNamespaceManagers():
+ namespace = nsmanager.namespace
ns_prefix = None
if namespace != self.default_ns:
ns_prefix = 'a%s' % count
count += 1
+
for propname in nsmanager.getAllPropertyNames(self.context):
widget = nsmanager.getWidget(self.context, self.request,
propname, ns_prefix)
el = widget.renderProperty()
response.addPropertyByStatus(namespace, ns_prefix, el, 200)
- def _renderSelectedProperties(self, response, source):
+ def _renderSelectedProperties(self, nsregistry, response, source):
count = 0
renderedns = {}
@@ -151,29 +149,32 @@
elif namespace != self.default_ns:
ns_prefix = renderedns[namespace]
- nsmanager = component.queryUtility(INamespaceManager, namespace,
- default = None)
+ nsmanager = nsregistry.queryNamespaceManager(namespace,
+ default = None)
- if nsmanager is not None:
- if nsmanager.hasProperty(self.context, propname):
- widget = nsmanager.getWidget(self.context, self.request,
- propname, ns_prefix)
- el = widget.renderProperty()
+ if nsmanager is not None and \
+ nsmanager.queryProperty(self.context,
+ propname, None) is not None:
+ widget = nsmanager.getWidget(self.context, self.request,
+ propname, ns_prefix)
+ el = widget.renderProperty()
else:
+ status = 404
el = response.createEmptyElement(namespace, ns_prefix,
propname)
- status = 404
response.addPropertyByStatus(namespace, ns_prefix, el, status)
- def _renderPropnameResponse(self, response):
+ def _renderPropnameResponse(self, nsregistry, response):
count = 0
- for namespace, manager in component.getUtilitiesFor(INamespaceManager):
+ for nsmanager in nsregistry.getAllNamespaceManagers():
+ namespace = nsmanager.namespace
if namespace != self.default_ns:
ns_prefix = 'a%s' % count
count += 1
else:
ns_prefix = None
- for propname in manager.getAllPropertyNames(self.context):
+
+ for propname in nsmanager.getAllPropertyNames(self.context):
el = response.createEmptyElement(namespace, ns_prefix, propname)
response.addPropertyByStatus(namespace, ns_prefix, el, 200)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -18,15 +18,15 @@
from xml.dom import minidom
import transaction
-from zope.app import zapi
-from zope.schema import getFieldNamesInOrder, getFields
+from zope import component
from zope.app.container.interfaces import IReadContainer
-from zope.publisher.http import status_reasons
-from zope.app.form.utility import setUpWidget, no_value
+from zope.app.traversing.browser.absoluteurl import absoluteURL
-from interfaces import IDAVNamespace, IDAVWidget
-from opaquenamespaces import IDAVOpaqueNamespaces
+from common import MultiStatus
+from common import DAVError, UnprocessableEntityError, ForbiddenError
+from interfaces import INamespaceManager, INamespaceRegistry
+
class PROPPATCH(object):
"""PROPPATCH handler for all objects"""
@@ -41,78 +41,78 @@
else:
self.content_type = ct.lower()
self.content_type_params = None
+
self.default_ns = 'DAV:'
- self.oprops = IDAVOpaqueNamespaces(self.context, None)
- _avail_props = {}
- # List all *registered* DAV interface namespaces and their properties
- for ns, iface in zapi.getUtilitiesFor(IDAVNamespace):
- _avail_props[ns] = getFieldNamesInOrder(iface)
-
- # List all opaque DAV namespaces and the properties we know of
- if self.oprops:
- for ns, oprops in self.oprops.items():
- _avail_props[ns] = list(oprops.keys())
-
- self.avail_props = _avail_props
-
def PROPPATCH(self):
- if self.content_type not in ['text/xml', 'application/xml']:
+ if self.content_type not in ('text/xml', 'application/xml'):
self.request.response.setStatus(400)
return ''
- resource_url = zapi.absoluteURL(self.context, self.request)
+ resource_url = absoluteURL(self.context, self.request)
if IReadContainer.providedBy(self.context):
resource_url += '/'
xmldoc = minidom.parse(self.request.bodyStream)
- resp = minidom.Document()
- ms = resp.createElement('multistatus')
- ms.setAttribute('xmlns', self.default_ns)
- resp.appendChild(ms)
- ms.appendChild(resp.createElement('response'))
- ms.lastChild.appendChild(resp.createElement('href'))
- ms.lastChild.lastChild.appendChild(resp.createTextNode(resource_url))
- updateel = xmldoc.getElementsByTagNameNS(self.default_ns,
- 'propertyupdate')
- if not updateel:
+ responsedoc = MultiStatus()
+ propertyupdate = xmldoc.documentElement
+ if propertyupdate.namespaceURI != self.default_ns or \
+ propertyupdate.localName != 'propertyupdate':
self.request.response.setStatus(422)
return ''
- updates = [node for node in updateel[0].childNodes
- if node.nodeType == node.ELEMENT_NODE and
- node.localName in ('set', 'remove')]
- if not updates:
- self.request.response.setStatus(422)
+
+ try:
+ self._handlePropertyUpdate(responsedoc, propertyupdate)
+ except DAVError, error:
+ self.request.response.setStatus(error.status)
return ''
- self._handlePropertyUpdate(resp, updates)
- body = resp.toxml('utf-8')
+ body = responsedoc.body.toxml('utf-8')
self.request.response.setResult(body)
self.request.response.setStatus(207)
return body
- def _handlePropertyUpdate(self, resp, updates):
+ def _handlePropertyUpdate(self, responsedoc, source):
_propresults = {}
- for update in updates:
- prop = update.getElementsByTagNameNS(self.default_ns, 'prop')
- if not prop:
+
+ nr = component.getUtility(INamespaceRegistry, context = self.context)
+
+ for update in source.childNodes:
+ if update.nodeType != update.ELEMENT_NODE:
continue
- for node in prop[0].childNodes:
- if not node.nodeType == node.ELEMENT_NODE:
+ if update.localName not in ('set', 'remove'):
+ continue
+
+ props = update.getElementsByTagNameNS(self.default_ns, 'prop')
+ if not props:
+ continue
+
+ for prop in props[0].childNodes:
+ if not prop.nodeType == prop.ELEMENT_NODE:
continue
+
+ namespace = prop.namespaceURI
+
+ nsmanager = nr.getNamespaceManager(namespace)
+ if nsmanager is None:
+ raise ForbiddenError(prop.localName,
+ "namespace %s doesn't exist" % namespace)
+
if update.localName == 'set':
- status = self._handleSet(node)
+ status = self._handleSet(nsmanager, prop)
else:
- status = self._handleRemove(node)
- ## _propresults doesn't seems to be set correctly.
+ status = self._handleRemove(nsmanager, prop)
+
results = _propresults.setdefault(status, {})
- props = results.setdefault(node.namespaceURI, [])
- if node.localName not in props:
- props.append(node.localName)
+ props = results.setdefault(namespace, [])
+ if prop.localName not in props:
+ props.append(prop.localName)
+ if not _propresults:
+ raise UnprocessableEntityError(None, "")
+
if _propresults.keys() != [200]:
- # At least some props failed, abort transaction
transaction.abort()
# Move 200 succeeded props to the 424 status
if _propresults.has_key(200):
@@ -122,95 +122,58 @@
failed_props.extend(props)
del _propresults[200]
- # Create the response document
- re = resp.lastChild.lastChild
- for status, results in _propresults.items():
- re.appendChild(resp.createElement('propstat'))
- prop = resp.createElement('prop')
- re.lastChild.appendChild(prop)
- count = 0
- for ns in results.keys():
- attr_name = 'a%s' % count
- if ns is not None and ns != self.default_ns:
+ renderedns = {}
+ count = 0
+ resp = responsedoc.addResponse(self.context, self.request)
+ for status, namespaces in _propresults.items():
+ for namespace, properties in namespaces.items():
+ if renderedns.has_key(namespace):
+ ns_prefix = renderedns[namespace]
+ elif namespace != self.default_ns:
+ ns_prefix = renderedns[namespace] = 'a%s' % count
count += 1
- prop.setAttribute('xmlns:%s' % attr_name, ns)
- for p in results.get(ns, []):
- el = resp.createElement(p)
- prop.appendChild(el)
- if ns is not None and ns != self.default_ns:
- el.setAttribute('xmlns', attr_name)
- reason = status_reasons.get(status, '')
- re.lastChild.appendChild(resp.createElement('status'))
- re.lastChild.lastChild.appendChild(
- resp.createTextNode('HTTP/1.1 %d %s' % (status, reason)))
+ else: # default_ns
+ ns_prefix = renderedns[namespace] = None
- def _handleSet(self, prop):
- ns = prop.namespaceURI
- iface = zapi.queryUtility(IDAVNamespace, ns)
- if not iface:
- # opaque DAV properties
- if self.oprops is not None:
- self.oprops.setProperty(prop)
- # Register the new available property, because we need to be
- # able to remove it again in the same request!
- props = self.avail_props.setdefault(ns, [])
- if prop.localName not in props:
- props.append(prop.localName)
- return 200
+ for propname in properties:
+ el = resp.createEmptyElement(namespace, ns_prefix,
+ propname)
+ resp.addPropertyByStatus(namespace, ns_prefix, el, status)
+
+ def _handleSet(self, nsmanager, prop):
+ propname = prop.localName
+
+ # we can't add a property to a live namespace - forbidden
+ if nsmanager.isLiveNamespace() and \
+ nsmanager.queryProperty(self.context, propname, None) is None:
return 403
- if not prop.localName in self.avail_props[ns]:
- return 403 # Cannot add propeties to a registered schema
+ # we aren't rendereding the property so setting ns_prefix to None is ok
+ widget = nsmanager.getWidget(self.context, self.request,
+ propname, None)
+ field = nsmanager.getProperty(self.context, propname)
- fields = getFields(iface)
- field = fields[prop.localName]
if field.readonly:
- return 409 # RFC 2518 specifies 409 for readonly props
+ # XXX - RFC 2518 specifies 409 for readonly props but the next
+ # version of the spec says it is 403 since it isn't allowed.
+ return 409
- adapted = iface(self.context)
-
- value = field.get(adapted)
- if value is field.missing_value:
- value = no_value
- setUpWidget(self, prop.localName, field, IDAVWidget,
- value = value, ignoreStickyValues = True)
-
- widget = getattr(self, prop.localName + '_widget')
widget.setProperty(prop)
if not widget.hasValidInput():
- return 409 # Didn't match the widget validation
+ return 409 # Didn't match the field validation
- if widget.applyChanges(adapted):
+ if widget.applyChanges(field.context):
return 200
- return 422 # Field didn't accept the value
+ return 422 # Field didn't accept the value - is the correct value.
- def _handleRemove(self, prop):
- ns = prop.namespaceURI
- if not prop.localName in self.avail_props.get(ns, []):
- return 200
- iface = zapi.queryUtility(IDAVNamespace, ns)
- if not iface:
- # opaque DAV properties
- if self.oprops is None:
- return 200
- self.oprops.removeProperty(ns, prop.localName)
- return 200
+ def _handleRemove(self, nsmanager, prop):
+ # XXX - should the INamespaceManager utility be managing the removal
+ # of properties at this level.
+ try:
+ nsmanager.removeProperty(self.context, prop.localName)
+ except DAVError, error:
+ return error.status
- # Registered interfaces
- fields = getFields(iface)
- field = fields[prop.localName]
- if field.readonly:
- return 409 # RFC 2518 specifies 409 for readonly props
-
- if field.required:
- if field.default is None:
- return 409 # Clearing a required property is a conflict
- # Reset the field to the default if a value is required
- field.set(iface(self.context), field.default)
- return 200
-
- # Reset the field to it's defined missing_value
- field.set(iface(self.context), field.missing_value)
return 200
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davduplicateproperty.zcml
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davduplicateproperty.zcml 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davduplicateproperty.zcml 2006-02-26 19:10:29 UTC (rev 65493)
@@ -5,7 +5,7 @@
<dav:schemas
namespace="http://examplenamespace.org/dav/schema"
- schemas=".test_namespace.IExampleDuplicateSchema"
+ schemas=".test_namespace.ITestDirectiveDuplicateSchema"
/>
</configure>
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davnamespace.zcml
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davnamespace.zcml 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/davnamespace.zcml 2006-02-26 19:10:29 UTC (rev 65493)
@@ -4,23 +4,30 @@
<include package="zope.app.dav" file="meta.zcml"/>
<dav:namespace
- namespace="http://examplenamespace.org/dav/schema"
- interfaceType=".test_namespace.IExampleNamespaceType"
- />
-
- <dav:schemas
- namespace="http://examplenamespace.org/dav/schema"
- schemas=".test_namespace.IExampleSchema"
- />
+ namespace="http://examplenamespace.org/dav/directives/schema"
+ interfaceType=".test_namespace.ITestDirectiveNamespaceType"
+ schemas=".test_namespace.ITestDirectivesSchema
+ .test_namespace.ITestDirectivesContactSchema">
+ <!-- we just want to test the widget directive works - not to render the
+ widget.
+ -->
+ <widget
+ propname="name"
+ class="zope.app.dav.widget.TextDAVWidget"
+ />
+ </dav:namespace>
+
<dav:schemas
- namespace="http://examplenamespace.org/dav/schema"
- schemas=".test_namespace.IExampleSchemaExtended"
+ namespace="http://examplenamespace.org/dav/directives/schema"
+ schemas=".test_namespace.ITestDirectivesExtendedSchema"
+ restricted_properties="job"
/>
- <dav:schemas
- namespace="http://examplenamespace.org/dav/schema"
- schemas=".test_namespace.IExampleContactSchema"
+ <dav:widget
+ namespace="http://examplenamespace.org/dav/directives/schema"
+ propname="phoneNo"
+ class="zope.app.dav.widget.IntDAVWidget"
/>
</configure>
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_doctests.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_doctests.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_doctests.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -20,7 +20,7 @@
def test_suite():
return unittest.TestSuite((
- DocTestSuite('zope.app.dav.opaquenamespaces'),
+ DocTestSuite('zope.app.dav.common'),
))
if __name__ == '__main__':
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -26,10 +26,10 @@
from zope.app.component.testing import PlacefulSetup
from zope.app.traversing.api import traverse
+from zope import component
from zope.interface import Interface
from zope.pagetemplate.tests.util import normalize_xml
from zope.schema.interfaces import IText, ITextLine, IDatetime, ISequence, IInt
-from zope.app import zapi
from zope.app.testing import ztapi
from zope.app.locking.interfaces import ILockable, ILockTracker
from zope.app.locking.adapter import LockingAdapterFactory, LockingPathAdapter
@@ -211,7 +211,7 @@
{'DEPTH': 'infinity'})
self.assertEqual(response.getStatus(), 200)
# assert that the depth infinity locked any subobjects
- locktracker = zapi.getUtility(ILockTracker)
+ locktracker = component.getUtility(ILockTracker)
self.assert_(locktracker.getAllLocks() > 1)
def test_depthinf_conflict(self):
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_move.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_move.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_move.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -0,0 +1,268 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Test Moving
+
+$Id$
+"""
+import unittest
+
+from zope.interface import Interface
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.security.testing import Principal, Participation
+from zope.security.management import newInteraction, endInteraction, \
+ queryInteraction
+from zope.app.testing import ztapi
+from zope.app.traversing.api import traverse
+from zope.app.traversing.interfaces import TraversalError
+from zope.app.component.testing import PlacefulSetup
+
+from zope.app.locking.interfaces import ILockable, ILockStorage
+from zope.app.locking.storage import PersistentLockStorage
+from zope.app.locking.adapter import LockingAdapterFactory
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.app.copypastemove import ObjectMover
+from zope.app.copypastemove.interfaces import IObjectMover
+
+from zope.app.dav.copy import MOVE
+from zope.app.dav.interfaces import IIfHeader
+from zope.app.dav.ifhandler import IfParser
+
+from test_locking import FakeKeyReference
+from unitfixtures import File, Folder, ConstraintFolder
+
+class TestDAVMove(PlacefulSetup, unittest.TestCase):
+
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+ PlacefulSetup.buildFolders(self)
+
+ ztapi.provideAdapter(Interface, IObjectMover, ObjectMover)
+
+ # locking
+ ztapi.provideAdapter(Interface, IKeyReference, FakeKeyReference)
+ ztapi.provideAdapter(Interface, ILockable, LockingAdapterFactory)
+
+ storage = self.storage = PersistentLockStorage()
+ ztapi.provideUtility(ILockStorage, storage)
+
+ ztapi.provideAdapter((Interface, IHTTPRequest), IIfHeader, IfParser)
+
+ def tearDown(self):
+ PlacefulSetup.tearDown(self)
+ del self.storage
+
+ def test_move_file(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ content = 'this is some content'
+ file = File('bla', 'text/plain', content, container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ self.assertRaises(TraversalError, traverse, container, 'move_bla')
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/folder1/move_bla'})
+ response = request.response
+ copier = MOVE(file, request)
+ copier.MOVE()
+ # check for 201 status since the new file will be created.
+ self.assertEqual(response.getStatus(), 201)
+ newfile = traverse(container, 'move_bla')
+ self.assertEqual(newfile.data, content)
+ self.assertRaises(TraversalError, traverse, container, 'bla')
+
+ def test_move_file_overwrite(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ file = File('bla', 'text/plain', 'this is some content', container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ move_file = File('move', 'text/plain', 'this is the second file',
+ container)
+ container['move_bla'] = move_file
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/folder1/move_bla',
+ 'OVERWRITE': 'T'})
+ response = request.response
+ copier = MOVE(file, request)
+ copier.MOVE()
+ # check for 204 status since the no file is created.
+ self.assertEqual(response.getStatus(), 204)
+ newfile = traverse(container, 'move_bla')
+ self.assertEqual(newfile.data, 'this is some content')
+ self.assertRaises(TraversalError, traverse, container, 'bla')
+
+ def test_move_file_no_overwrite(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ file = File('bla', 'text/plain', 'this is some content', container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ move_file = File('move_bla', 'text/plain', 'this is the second file',
+ container)
+ container['move_bla'] = move_file
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/folder1/move_bla',
+ 'OVERWRITE': 'F'})
+ response = request.response
+ copier = MOVE(file, request)
+ copier.MOVE()
+
+ # 412 - precondition failed since overwrite is False.
+ self.assertEqual(response.getStatus(), 412)
+ newfile = traverse(container, 'move_bla')
+ self.assertEqual(newfile.data, 'this is the second file')
+
+ def test_invalid_move_request(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ content = 'this is some content'
+ file = File('bla', 'text/plain', content, container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/blafolder/move_bla',
+ 'OVERWRITE': 'X'})
+ response = request.response
+ mover = MOVE(file, request)
+ mover.MOVE()
+ # check for 201 status since the new file will be created.
+ self.assertEqual(response.getStatus(), 400)
+
+ # now test a missing destination header.
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'OVERWRITE': 'T'})
+ response = request.response
+ mover = MOVE(file, request)
+ mover.MOVE()
+ # check for 201 status since the new file will be created.
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_no_destination_parent(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ content = 'this is some content'
+ file = File('bla', 'text/plain', content, container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ self.assertRaises(TraversalError, traverse, container, 'move_bla')
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/blafolder/move_bla'})
+ response = request.response
+ mover = MOVE(file, request)
+ mover.MOVE()
+ # check for 201 status since the new file will be created.
+ self.assertEqual(response.getStatus(), 409)
+
+ def test_destination_file_locked(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ file = File('bla', 'text/plain', 'this is some content', container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ dest_file = File('move_bla', 'text/plain', 'this is the second file',
+ container)
+ container['move_bla'] = dest_file
+ dest_file = traverse(container, 'move_bla')
+
+ # now lock dest_file
+ mparticipation = Participation(Principal('michael'))
+ if queryInteraction():
+ endInteraction()
+ newInteraction(mparticipation)
+ lockable = ILockable(dest_file)
+ lockable.lock()
+ endInteraction()
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/folder1/move_bla',
+ 'OVERWRITE': 'T'})
+ response = request.response
+
+ mover = MOVE(file, request)
+ mover.MOVE()
+
+ # 423 - locked
+ self.assertEqual(response.getStatus(), 423)
+ newfile = traverse(container, 'move_bla')
+ self.assertEqual(newfile.data, 'this is the second file')
+
+ def test_not_moveableTo(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ file = File('bla', 'text/plain', 'this is some content', container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ # the ConstraintFolder only allows implementations of IFolder to be
+ # added to it - so by moveing a file into this folder we should get
+ # a conflict and hence a 409 response status.
+ folder = ConstraintFolder()
+ root['nofilefolder'] = folder
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/nofilefolder/bla',
+ 'OVERWRITE': 'T'})
+ response = request.response
+ mover = MOVE(file, request)
+ mover.MOVE()
+
+ # conflict - a resource can't be created at the destination.
+ self.assertEqual(response.getStatus(), 409)
+
+ def test_source_dest_same(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ file = File('bla', 'text/plain', 'this is some content', container)
+ container['bla'] = file
+ file = traverse(container, 'bla')
+
+ request = TestRequest('/folder1/bla',
+ environ = {'REQUEST_METHOD': 'MOVE',
+ 'DESTINATION': '/folder1/bla',
+ })
+ response = request.response
+ mover = MOVE(file, request)
+ mover.MOVE()
+
+ # 403 (Forbidden) _ The source and destination URIs are the same.
+ self.assertEqual(response.getStatus(), 403)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(TestDAVMove),
+ ))
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest = 'test_suite')
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_move.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_namespace.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_namespace.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_namespace.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -13,7 +13,7 @@
##############################################################################
"""Test the Zope WebDAV namespace registry.
-$Id:$
+$Id$
"""
import unittest
from cStringIO import StringIO
@@ -23,6 +23,7 @@
from zope.interface.interfaces import IInterface
from zope.interface.verify import verifyObject
from zope.interface import Interface, implements
+from zope.component.interfaces import ComponentLookupError
import zope.app.dav.tests
from zope.app.component.testing import PlacefulSetup
@@ -30,12 +31,18 @@
from zope.interface.declarations import directlyProvides
from zope.schema import Int, TextLine
from zope.schema.interfaces import IInt, ITextLine
-from zope.app.dav.namespaces import NamespaceManager
-from zope.app.dav.interfaces import INamespaceManager, IWebDAVRequest, \
- IDAVWidget
+from zope.app.annotation.interfaces import IAnnotatable, IAnnotations
+from zope.app.annotation.attribute import AttributeAnnotations
+
+from zope.app.dav.namespaces import NamespaceManager, NamespaceRegistry, \
+ LocalNamespaceRegistry
+from zope.app.dav.interfaces import INamespaceManager, INamespaceRegistry, \
+ IWebDAVRequest, IDAVWidget, IDAVOpaqueNamespaces
from zope.app.dav.common import WebDAVRequest
from zope.app.dav.widget import IntDAVWidget, TextDAVWidget
+from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
+
namespace = 'http://examplenamespace.org/dav/schema'
#
@@ -57,6 +64,11 @@
company = TextLine(title = u'Place of employment')
+class IExampleJobSchema(IExampleSchema):
+ """
+ """
+ job = TextLine(title = u'Duplicate Job')
+
class IExampleContactSchema(Interface):
""" """
phoneNo = TextLine(title = u'Phone Number')
@@ -87,7 +99,7 @@
class Content(object):
- implements(IExampleContent)
+ implements(IExampleContent, IAnnotatable)
def __init__(self, parent, name):
self.__parent__ = parent
@@ -142,23 +154,57 @@
return '01 1234567'
+#
+# Namepsace and interface used to test the new webdav directives.
+#
+
+directiveNamespace = 'http://examplenamespace.org/dav/directives/schema'
+
+class ITestDirectiveNamespaceType(IInterface):
+ """
+ """
+
+class ITestDirectivesSchema(Interface):
+ """Test the namespace directives
+ """
+ age = Int(title = u'Age')
+
+ name = TextLine(title = u'Name')
+
+class ITestDirectivesExtendedSchema(ITestDirectivesSchema):
+ job = TextLine(title = u'Job Title')
+
+ company = TextLine(title = u'Place of employment')
+
+class ITestDirectivesContactSchema(Interface):
+ phoneNo = TextLine(title = u'Phone Number')
+
+class ITestDirectiveDuplicateSchema(Interface):
+ job = TextLine(title = u'Duplicate Job Title')
+
+
class TestNamespaceDirectives(unittest.TestCase):
def test_namespace_directives(self):
self.assertEqual(
- component.queryUtility(INamespaceManager, namespace), None)
+ component.queryUtility(INamespaceManager, directiveNamespace), None)
xmlconfig.XMLConfig("davnamespace.zcml", zope.app.dav.tests)()
- nmanager = component.getUtility(INamespaceManager, namespace)
+ nmanager = component.getUtility(INamespaceManager, directiveNamespace)
verifyObject(INamespaceManager, nmanager)
# quick check to see that schemas work
self.assert_(len(nmanager.properties) > 0)
+ self.assert_('age' in nmanager.properties)
# check that we correctly catch duplicate declarations of properties
self.assertRaises(ConfigurationExecutionError,
xmlconfig.XMLConfig("davduplicateproperty.zcml",
zope.app.dav.tests))
+ def test_namespace_duplication(self):
+ nmanager = component.getUtility(INamespaceManager, directiveNamespace)
+ self.assertRaises(TypeError, nmanager.registerSchema,
+ ITestDirectiveDuplicateSchema)
-class TestNamespaceRegistry(unittest.TestCase):
+class TestNamespaceSetup(unittest.TestCase):
def setUp(self):
davnamespace = NamespaceManager(namespace,
@@ -183,6 +229,21 @@
component.provideAdapter(TextDAVWidget, (ITextLine, IWebDAVRequest),
IDAVWidget)
+ component.provideAdapter(AttributeAnnotations, (IAnnotatable,),
+ IAnnotations)
+ component.provideAdapter(DAVOpaqueNamespacesAdapter, (IAnnotatable,),
+ IDAVOpaqueNamespaces)
+
+
+class TestNamespaceManager(TestNamespaceSetup):
+
+ def setUp(self):
+ super(TestNamespaceManager, self).setUp()
+
+ def test_correct_interface_impl(self):
+ nr = component.getUtility(INamespaceManager, namespace)
+ verifyObject(INamespaceManager, nr)
+
def test_correct_properties(self):
nr = component.getUtility(INamespaceManager, namespace)
expected = ['age', 'name', 'job', 'company', 'phoneNo']
@@ -251,17 +312,141 @@
self.assertEqual(xmlel.toxml(),
'<name xmlns="a0">The Other Michael Kerrin</name>')
- def test_has_property(self):
- nr = component.getUtility(INamespaceManager, namespace)
- context = Content(None, 'contenttype')
- self.assert_(nr.hasProperty(context, 'age'))
- self.assert_(nr.hasProperty(context, 'job') is False)
+class TestNamespaceRegistry(TestNamespaceSetup):
+ def setUp(self):
+ super(TestNamespaceRegistry, self).setUp()
+ nr = self.nr = NamespaceRegistry()
+ component.provideUtility(nr, INamespaceRegistry)
+
+ def tearDown(self):
+ del self.nr
+
+ def test_correct_interface(self):
+ nr = component.getUtility(INamespaceRegistry)
+ verifyObject(INamespaceRegistry, nr)
+
+ def test_namespace(self):
+ nr = component.getUtility(INamespaceRegistry)
+ davnamespacemanager = nr.getNamespaceManager(namespace)
+ davnamespacemanagerUtility = component.getUtility(INamespaceManager,
+ namespace)
+ # should be the same object
+ self.assert_(davnamespacemanager is davnamespacemanagerUtility)
+ self.assertRaises(ComponentLookupError, nr.getNamespaceManager,
+ 'uri:doesnotexist')
+
+ def test_query_namespace(self):
+ nr = component.getUtility(INamespaceRegistry)
+ self.assertEqual(nr.queryNamespaceManager(namespace).namespace,
+ namespace)
+ self.assertEqual(
+ nr.queryNamespaceManager('uri:doesnotexist', 'default'), 'default')
+
+
+ def test_all_managers(self):
+ nr = component.getUtility(INamespaceRegistry)
+ namespaces = [nsm.namespace for nsm in nr.getAllNamespaceManagers()]
+ namespaces.sort()
+ nsutilities = [ns for ns, nsm in \
+ component.getUtilitiesFor(INamespaceManager)]
+ nsutilities.sort()
+ self.assertEqual(namespaces, nsutilities)
+
+ def test_has_manager(self):
+ nr = component.getUtility(INamespaceRegistry)
+ self.assertEqual(nr.hasNamespaceManager(namespace), True)
+ self.assertEqual(nr.hasNamespaceManager('uri:doesnotexist'), False)
+
+
+class TestPlacefulNamespaceRegistry(TestNamespaceSetup):
+
+ def setUp(self):
+ super(TestPlacefulNamespaceRegistry, self).setUp()
+ self.nr = nr = LocalNamespaceRegistry()
+ component.provideUtility(nr, INamespaceRegistry)
+
+ def tearDown(self):
+ super(TestPlacefulNamespaceRegistry, self).tearDown()
+ del self.nr
+
+ def test_correct_interface(self):
+ nr = component.getUtility(INamespaceRegistry)
+ verifyObject(INamespaceRegistry, nr)
+
+ def test_namespace(self):
+ nr = component.getUtility(INamespaceRegistry)
+ davnamespacemanager = nr.getNamespaceManager(namespace)
+ davnamespacemanagerUtility = component.getUtility(INamespaceManager,
+ namespace)
+ # should be the same object
+ self.assert_(davnamespacemanager is davnamespacemanagerUtility)
+ somedeadprops = nr.getNamespaceManager('uri:doesnotexist')
+ self.assertEqual(somedeadprops.namespace, 'uri:doesnotexist')
+ verifyObject(INamespaceManager, somedeadprops)
+
+ def test_query_namespace(self):
+ nr = component.getUtility(INamespaceRegistry)
+ self.assertEqual(nr.queryNamespaceManager(namespace).namespace,
+ namespace)
+ self.assertEqual(nr.queryNamespaceManager('uri:doesnotexist',
+ 'default').namespace,
+ 'uri:doesnotexist')
+
+ def test_all_managers(self):
+ nr = component.getUtility(INamespaceRegistry)
+ namespaces = [nsm.namespace for nsm in nr.getAllNamespaceManagers()]
+ namespaces.sort()
+ nsutilities = [ns for ns, nsm in \
+ component.getUtilitiesFor(INamespaceManager)]
+ nsutilities.sort()
+ self.assertEqual(namespaces, nsutilities)
+ nr.getNamespaceManager('uri:doesnotexist')
+ namespaces = [nsm.namespace for nsm in nr.getAllNamespaceManagers()]
+ namespaces.sort()
+ self.assertEqual(namespaces, nsutilities + ['uri:doesnotexist'])
+
+ def test_properties(self):
+ context = Content(None, 'allDeadProperties')
+ nr = component.getUtility(INamespaceRegistry)
+ ns = 'uri:testallproperties'
+ nsmanager = nr.getNamespaceManager(ns)
+ oprops = IDAVOpaqueNamespaces(context)
+ foopropvalue = '<foo>Foo Property</foo>'
+ oprops.setProperty(ns, 'foo', foopropvalue)
+ oprops.setProperty(ns, 'bar', '<bar>Bar Property</bar>')
+ allpropnames = list(nsmanager.getAllPropertyNames(context))
+ allpropnames.sort()
+ self.assertEqual(allpropnames, ['bar', 'foo'])
+ allprops = list(nsmanager.getAllProperties(context))
+ allprops.sort()
+ self.assertEqual(len(allprops), 2)
+ fooprop = nsmanager.getProperty(context, 'foo')
+ # pass in the storage adapter
+ self.assertEqual(fooprop.get(oprops), foopropvalue)
+
+ def test_allproperty_two_namespaces(self):
+ context = Content(None, 'allproperty_two_namespaces')
+ nr = component.getUtility(INamespaceRegistry)
+ ns1 = 'uri:testallpropertyOne'
+ ns2 = 'uri:testallpropertyTwo'
+ nsmanager1 = nr.getNamespaceManager(ns1)
+ nsmanager2 = nr.getNamespaceManager(ns2)
+ oprops = IDAVOpaqueNamespaces(context)
+ oprops.setProperty(ns1, 'foo', '<foo>again</foo>')
+ propnames = nsmanager1.getAllPropertyNames(context)
+ self.assertEquals(propnames, ['foo'])
+ propnames = nsmanager2.getAllPropertyNames(context)
+ self.assertEquals(propnames, [])
+
def test_suite():
suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestNamespaceManager))
suite.addTest(unittest.makeSuite(TestNamespaceRegistry))
+ suite.addTest(unittest.makeSuite(TestPlacefulNamespaceRegistry))
+ suite.addTest(unittest.makeSuite(TestNamespaceDirectives))
return suite
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_propfind.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_propfind.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_propfind.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -20,7 +20,7 @@
from unittest import TestCase, TestSuite, main, makeSuite
from datetime import datetime
-from zope.interface import Interface, directlyProvides, implements
+from zope.interface import Interface, implements
from zope.publisher.interfaces.http import IHTTPRequest
from zope.pagetemplate.tests.util import normalize_xml
@@ -53,11 +53,12 @@
XMLEmptyElementListDAVWidget, SequenceDAVWidget, ListDAVWidget, \
DAVXMLSubPropertyWidget, DAVOpaqueWidget, ISO8601DateDAVWidget
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
-from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
+from zope.app.dav.interfaces import IDAVOpaqueNamespaces
from zope.app.dav.adapter import DAVSchemaAdapter
from zope.app.dav.fields import IDAVXMLSubProperty, IDAVOpaqueField
-from zope.app.dav.interfaces import INamespaceManager
-from zope.app.dav.namespaces import NamespaceManager
+from zope.app.dav.interfaces import INamespaceManager, INamespaceRegistry
+from zope.app.dav.namespaces import NamespaceManager, namespaceRegistry, \
+ LocalNamespaceRegistry
from unitfixtures import File, Folder, FooZPT
import xmldiff
@@ -145,7 +146,12 @@
DAVSchemaAdapter)
ztapi.provideAdapter(IFile, ISized, FileSized)
+ self._setUpNamespaces()
+
+ def _setUpNamespaces(self):
# new webdav configuration
+ component.provideUtility(namespaceRegistry, INamespaceRegistry)
+
davnamespace = NamespaceManager('DAV:', schemas = (IDAVSchema,))
davnamespace.registerWidget('creationdate', ISO8601DateDAVWidget)
component.provideUtility(davnamespace, INamespaceManager, 'DAV:')
@@ -155,6 +161,13 @@
component.provideUtility(dcnamespace, INamespaceManager,
'http://www.purl.org/dc/1.1')
+ def tearDown(self):
+ super(TestPlacefulPROPFINDBase, self).tearDown()
+ root = self.rootFolder
+ del root['zpt']
+ del root['folder1']
+ del root['file']
+
def _checkPropfind(self, obj, req, expect, depth='0', resp=None):
if req:
body = '''<?xml version="1.0" ?>
@@ -513,12 +526,30 @@
<multistatus xmlns="DAV:">%s</multistatus>'''
self._checkPropfind(folder, req, expect, depth='infinity', resp=resp)
+
#
# opaque property support is now broken
#
-class TestPlacefulDeadPropsPROPFIND(PlacefulSetup, TestCase):
+class TestPlacefulDeadPropsPROPFIND(TestPlacefulPROPFINDBase):
+ def _setUpNamespaces(self):
+ self.nr = nr = LocalNamespaceRegistry()
+ component.provideUtility(nr, INamespaceRegistry)
+
+ davnamespace = NamespaceManager('DAV:', schemas = (IDAVSchema,))
+ davnamespace.registerWidget('creationdate', ISO8601DateDAVWidget)
+ component.provideUtility(davnamespace, INamespaceManager, 'DAV:')
+
+ dcnamespace = NamespaceManager('http://www.purl.org/dc/1.1',
+ schemas = (IZopeDublinCore,))
+ component.provideUtility(dcnamespace, INamespaceManager,
+ 'http://www.purl.org/dc/1.1')
+
+ def tearDown(self):
+ super(TestPlacefulDeadPropsPROPFIND, self).tearDown()
+ del self.nr
+
def test_davemptybodyallpropzptdepth0(self):
# RFC 2518, Section 8.1: A client may choose not to submit a
# request body. An empty PROPFIND request body MUST be
@@ -534,7 +565,8 @@
expect = ''
props = getFieldNamesInOrder(IZopeDublinCore)
## XXX - '%s+00:00' % now}
- pvalues = {'created': now.strftime('%a, %d %b %Y %H:%M:%S %z').rstrip()}
+ pvalues = {'created':
+ now.strftime('%a, %d %b %Y %H:%M:%S +0000').rstrip()}
for p in props:
if pvalues.has_key(p):
expect += '<%s xmlns="a0">%s</%s>' % (p, pvalues[p], p)
@@ -588,11 +620,14 @@
self.assertEqual(pfind.getDepth(), depth)
s1 = normalize_xml(request.response.consumeBody())
s2 = normalize_xml(resp)
- self.assertEqual(s1, s2)
+ xmldiff.compareMultiStatus(self, s1, s2)
+ ## self.assertEqual(s1, s2)
def test_propfind_opaque_simple(self):
root = self.rootFolder
zpt = traverse(root, 'zpt')
+
+ self.nr.getNamespaceManager(u'http://foo/bar')
oprops = IDAVOpaqueNamespaces(zpt)
oprops[u'http://foo/bar'] = {u'egg': '<egg>spam</egg>'}
req = '<prop xmlns:foo="http://foo/bar"><foo:egg /></prop>'
@@ -604,6 +639,8 @@
def test_propfind_opaque_complex(self):
root = self.rootFolder
zpt = traverse(root, 'zpt')
+
+ self.nr.getNamespaceManager(u'http://foo/bar')
oprops = IDAVOpaqueNamespaces(zpt)
oprops[u'http://foo/bar'] = {u'egg':
'<egg xmlns:bacon="http://bacon">\n'
@@ -622,7 +659,7 @@
return TestSuite((
makeSuite(TestPlacefulPROPFIND),
## XXX - fix deab properties support in zope.app.dav
- ## makeSuite(TestPlacefulDeadPropsPROPFIND),
+ makeSuite(TestPlacefulDeadPropsPROPFIND),
))
if __name__ == '__main__':
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_proppatch.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_proppatch.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_proppatch.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -29,8 +29,9 @@
from zope.pagetemplate.tests.util import normalize_xml
from ZODB.tests.util import DB
-from zope.app import zapi
+from zope import component
from zope.app.testing import ztapi
+from zope.app.traversing.browser.absoluteurl import absoluteURL
from zope.app.traversing.api import traverse
from zope.publisher.browser import TestRequest
@@ -41,19 +42,25 @@
from zope.app.dublincore.zopedublincore import ScalarProperty
from zope.app.annotation.interfaces import IAnnotatable, IAnnotations
from zope.app.annotation.attribute import AttributeAnnotations
-from zope.schema.interfaces import IText, ISequence, ITuple
+from zope.schema.interfaces import IText, ISequence, ITuple, IInt
import zope.app.dav.tests
from zope.app.dav.tests.unitfixtures import File, Folder, FooZPT
+from zope.app.dav.tests import xmldiff
from zope.app.dav import proppatch
+from zope.app.dav.adapter import DAVSchemaAdapter
from zope.app.dav.interfaces import IDAVSchema
from zope.app.dav.interfaces import IDAVNamespace
from zope.app.dav.interfaces import IDAVWidget
-from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget, TupleDAVWidget
+from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget, \
+ TupleDAVWidget, ISO8601DateDAVWidget, IntDAVWidget
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
-from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
+from zope.app.dav.interfaces import IDAVOpaqueNamespaces
+from zope.app.dav.interfaces import INamespaceManager, INamespaceRegistry
+from zope.app.dav.namespaces import NamespaceManager, LocalNamespaceRegistry
+
def _createRequest(body=None, headers=None, skip_headers=None,
namespaces=(('Z', 'http://www.w3.com/standards/z39.50/'),),
set=('<Z:authors>\n<Z:Author>Jim Whitehead</Z:Author>\n',
@@ -131,7 +138,7 @@
constrained = ScalarProperty(u'constrained')
-class PropFindTests(PlacefulSetup, unittest.TestCase):
+class PropFindTestsBase(PlacefulSetup, unittest.TestCase):
def setUp(self):
PlacefulSetup.setUp(self)
@@ -142,6 +149,8 @@
file = File('spam', 'text/plain', self.content)
folder = Folder('bla')
root['file'] = file
+ from zope.interface import directlyProvides
+ directlyProvides(file, IAnnotatable)
root['zpt'] = zpt
root['folder'] = folder
self.zpt = traverse(root, 'zpt')
@@ -151,6 +160,8 @@
'absolute_url', AbsoluteURL)
ztapi.provideView(None, IHTTPRequest, Interface,
'PROPPATCH', proppatch.PROPPATCH)
+
+ ztapi.browserViewProviding(IInt, IntDAVWidget, IDAVWidget)
ztapi.browserViewProviding(IText, TextDAVWidget, IDAVWidget)
ztapi.browserViewProviding(ISequence, SequenceDAVWidget, IDAVWidget)
ztapi.browserViewProviding(ITuple, TupleDAVWidget, IDAVWidget)
@@ -161,15 +172,24 @@
DAVOpaqueNamespacesAdapter)
ztapi.provideAdapter(IAnnotatable, ITestSchema, TestSchemaAdapter)
- sm = zapi.getGlobalSiteManager()
- directlyProvides(IDAVSchema, IDAVNamespace)
- sm.provideUtility(IDAVNamespace, IDAVSchema, 'DAV:')
- directlyProvides(IZopeDublinCore, IDAVNamespace)
- sm.provideUtility(IDAVNamespace, IZopeDublinCore,
- 'http://www.purl.org/dc/1.1')
- directlyProvides(ITestSchema, IDAVNamespace)
- sm.provideUtility(IDAVNamespace, ITestSchema, TestURI)
+ ztapi.provideAdapter(None, IDAVSchema, DAVSchemaAdapter)
+ # new webdav configuration
+ namespaceRegistry = LocalNamespaceRegistry()
+ component.provideUtility(namespaceRegistry, INamespaceRegistry)
+
+ davnamespace = NamespaceManager('DAV:', schemas = (IDAVSchema,))
+ davnamespace.registerWidget('creationdate', ISO8601DateDAVWidget)
+ component.provideUtility(davnamespace, INamespaceManager, 'DAV:')
+
+ dcnamespace = NamespaceManager('http://www.purl.org/dc/1.1',
+ schemas = (IZopeDublinCore,))
+ component.provideUtility(dcnamespace, INamespaceManager,
+ 'http://www.purl.org/dc/1.1')
+
+ testnamespace = NamespaceManager(TestURI, schemas = (ITestSchema,))
+ component.provideUtility(testnamespace, INamespaceManager, TestURI)
+
self.db = DB()
self.conn = self.db.open()
root = self.conn.root()
@@ -179,6 +199,65 @@
def tearDown(self):
self.db.close()
+ def _checkProppatch(self, obj, ns=(), set=(), rm=(), extra='', expect=''):
+ request = _createRequest(namespaces=ns, set=set, remove=rm,
+ extra=extra)
+ resource_url = absoluteURL(obj, request)
+ expect = '''<?xml version="1.0" encoding="utf-8"?>
+ <multistatus xmlns="DAV:"><response>
+ <href>%%(resource_url)s</href>
+ %s
+ </response></multistatus>
+ ''' % expect
+ expect = expect % {'resource_url': resource_url}
+ ppatch = proppatch.PROPPATCH(obj, request)
+ ppatch.PROPPATCH()
+ # Check HTTP Response
+ self.assertEqual(request.response.getStatus(), 207)
+ s1 = normalize_xml(request.response.consumeBody())
+ s2 = normalize_xml(expect)
+ ## self.assertEqual(s1, s2)
+ xmldiff.compareMultiStatus(self, s1, s2)
+
+ def _makePropstat(self, ns, properties, status=200):
+ nsattrs = ''
+ count = 0
+ for uri in ns:
+ nsattrs += ' xmlns:a%d="%s"' % (count, uri)
+ count += 1
+ reason = status_reasons[status]
+ return '''<propstat>
+ <prop%s>%s</prop>
+ <status>HTTP/1.1 %d %s</status>
+ </propstat>''' % (nsattrs, properties, status, reason)
+
+ def _assertOPropsEqual(self, obj, expect):
+ oprops = IDAVOpaqueNamespaces(obj)
+ namespacesA = list(oprops.keys())
+ namespacesA.sort()
+ namespacesB = expect.keys()
+ namespacesB.sort()
+ self.assertEqual(namespacesA, namespacesB,
+ 'available opaque namespaces were %s, '
+ 'expected %s' % (namespacesA, namespacesB))
+
+ for ns in namespacesA:
+ propnamesA = list(oprops[ns].keys())
+ propnamesA.sort()
+ propnamesB = expect[ns].keys()
+ propnamesB.sort()
+ self.assertEqual(propnamesA, propnamesB,
+ 'props for opaque namespaces %s were %s, '
+ 'expected %s' % (ns, propnamesA, propnamesB))
+ for prop in propnamesA:
+ valueA = oprops[ns][prop]
+ valueB = expect[ns][prop]
+ self.assertEqual(valueA, valueB,
+ 'opaque prop %s:%s was %s, '
+ 'expected %s' % (ns, prop, valueA, valueB))
+
+class PropFindTests(PropFindTestsBase):
+
def test_contenttype1(self):
file = self.file
request = _createRequest(headers={'Content-type':'text/xml'})
@@ -235,125 +314,16 @@
# Check HTTP Response
self.assertEqual(request.response.getStatus(), 422)
- def _checkProppatch(self, obj, ns=(), set=(), rm=(), extra='', expect=''):
- request = _createRequest(namespaces=ns, set=set, remove=rm,
- extra=extra)
- resource_url = zapi.absoluteURL(obj, request)
- expect = '''<?xml version="1.0" encoding="utf-8"?>
- <multistatus xmlns="DAV:"><response>
- <href>%%(resource_url)s</href>
- %s
- </response></multistatus>
- ''' % expect
- expect = expect % {'resource_url': resource_url}
- ppatch = proppatch.PROPPATCH(obj, request)
- ppatch.PROPPATCH()
- # Check HTTP Response
- self.assertEqual(request.response.getStatus(), 207)
- s1 = normalize_xml(request.response.consumeBody())
- s2 = normalize_xml(expect)
- self.assertEqual(s1, s2)
-
- def _makePropstat(self, ns, properties, status=200):
- nsattrs = ''
- count = 0
- for uri in ns:
- nsattrs += ' xmlns:a%d="%s"' % (count, uri)
- count += 1
- reason = status_reasons[status]
- return '''<propstat>
- <prop%s>%s</prop>
- <status>HTTP/1.1 %d %s</status>
- </propstat>''' % (nsattrs, properties, status, reason)
-
- def _assertOPropsEqual(self, obj, expect):
- oprops = IDAVOpaqueNamespaces(obj)
- namespacesA = list(oprops.keys())
- namespacesA.sort()
- namespacesB = expect.keys()
- namespacesB.sort()
- self.assertEqual(namespacesA, namespacesB,
- 'available opaque namespaces were %s, '
- 'expected %s' % (namespacesA, namespacesB))
-
- for ns in namespacesA:
- propnamesA = list(oprops[ns].keys())
- propnamesA.sort()
- propnamesB = expect[ns].keys()
- propnamesB.sort()
- self.assertEqual(propnamesA, propnamesB,
- 'props for opaque namespaces %s were %s, '
- 'expected %s' % (ns, propnamesA, propnamesB))
- for prop in propnamesA:
- valueA = oprops[ns][prop]
- valueB = expect[ns][prop]
- self.assertEqual(valueA, valueB,
- 'opaque prop %s:%s was %s, '
- 'expected %s' % (ns, prop, valueA, valueB))
-
def test_removenonexisting(self):
expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
rm=('<foo:bar />'), expect=expect)
- def test_opaque_set_simple(self):
- expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
- self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
- set=('<foo:bar>spam</foo:bar>'), expect=expect)
- self._assertOPropsEqual(self.zpt,
- {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
-
- def test_opaque_remove_simple(self):
- oprops = IDAVOpaqueNamespaces(self.zpt)
- oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
- expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
- self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
- rm=('<foo:bar>spam</foo:bar>'), expect=expect)
- self._assertOPropsEqual(self.zpt, {})
-
- def test_opaque_add_and_replace(self):
- oprops = IDAVOpaqueNamespaces(self.zpt)
- oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
- expect = self._makePropstat(
- ('uri://castle', 'uri://foo'),
- '<camelot xmlns="a0"/><bar xmlns="a1"/>')
- self._checkProppatch(self.zpt,
- ns=(('foo', 'uri://foo'), ('c', 'uri://castle')),
- set=('<foo:bar>spam</foo:bar>',
- '<c:camelot place="silly" xmlns:k="uri://knights">'
- ' <k:roundtable/>'
- '</c:camelot>'),
- expect=expect)
- self._assertOPropsEqual(self.zpt, {
- u'uri://foo': {u'bar': '<bar>spam</bar>'},
- u'uri://castle': {u'camelot':
- '<camelot place="silly" xmlns:p0="uri://knights">'
- ' <p0:roundtable/></camelot>'}})
-
- def test_opaque_set_and_remove(self):
- expect = self._makePropstat(
- ('uri://foo',), '<bar xmlns="a0"/>')
- self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
- set=('<foo:bar>eggs</foo:bar>',), rm=('<foo:bar/>',),
- expect=expect)
- self._assertOPropsEqual(self.zpt, {})
-
- def test_opaque_complex(self):
- # PROPPATCH allows us to set, remove and set the same property, ordered
- expect = self._makePropstat(
- ('uri://foo',), '<bar xmlns="a0"/>')
- self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
- set=('<foo:bar>spam</foo:bar>',), rm=('<foo:bar/>',),
- extra='<set><prop><foo:bar>spam</foo:bar></prop></set>',
- expect=expect)
- self._assertOPropsEqual(self.zpt,
- {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
-
def test_proppatch_failure(self):
expect = self._makePropstat(
('uri://foo',), '<bar xmlns="a0"/>', 424)
expect += self._makePropstat(
- ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
+ ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a1"/>', 403)
self._checkProppatch(self.zpt,
ns=(('foo', 'uri://foo'), ('DC', 'http://www.purl.org/dc/1.1')),
set=('<foo:bar>spam</foo:bar>', '<DC:nonesuch>Test</DC:nonesuch>'),
@@ -434,9 +404,67 @@
expect=expect)
self.assertEqual(dc.subjects, (u'Foo', u'Bar'))
+
+class PropFindOpaqueTests(PropFindTestsBase):
+
+ def test_opaque_set_simple(self):
+ expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
+ self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
+ set=('<foo:bar>spam</foo:bar>'), expect=expect)
+ self._assertOPropsEqual(self.zpt,
+ {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
+
+ def test_opaque_remove_simple(self):
+ oprops = IDAVOpaqueNamespaces(self.zpt)
+ oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
+ expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
+ self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
+ rm=('<foo:bar>spam</foo:bar>'), expect=expect)
+ self._assertOPropsEqual(self.zpt, {})
+
+ def test_opaque_add_and_replace(self):
+ oprops = IDAVOpaqueNamespaces(self.zpt)
+ oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
+ expect = self._makePropstat(
+ ('uri://castle', 'uri://foo'),
+ '<camelot xmlns="a0"/><bar xmlns="a1"/>')
+ self._checkProppatch(self.zpt,
+ ns=(('foo', 'uri://foo'), ('c', 'uri://castle')),
+ set=('<foo:bar>spam</foo:bar>',
+ '<c:camelot place="silly" xmlns:k="uri://knights">'
+ ' <k:roundtable/>'
+ '</c:camelot>'),
+ expect=expect)
+ self._assertOPropsEqual(self.zpt, {
+ u'uri://foo': {u'bar': '<bar>spam</bar>'},
+ u'uri://castle': {u'camelot':
+ '<camelot place="silly" xmlns:p0="uri://knights">'
+ ' <p0:roundtable/></camelot>'}})
+
+ def test_opaque_set_and_remove(self):
+ expect = self._makePropstat(
+ ('uri://foo',), '<bar xmlns="a0"/>')
+ self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
+ set=('<foo:bar>eggs</foo:bar>',), rm=('<foo:bar/>',),
+ expect=expect)
+ self._assertOPropsEqual(self.zpt, {})
+
+ def test_opaque_complex(self):
+ # PROPPATCH allows us to set, remove and set the same property, ordered
+ expect = self._makePropstat(
+ ('uri://foo',), '<bar xmlns="a0"/>')
+ self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
+ set=('<foo:bar>spam</foo:bar>',), rm=('<foo:bar/>',),
+ extra='<set><prop><foo:bar>spam</foo:bar></prop></set>',
+ expect=expect)
+ self._assertOPropsEqual(self.zpt,
+ {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
+
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(PropFindTests),
+ unittest.makeSuite(PropFindOpaqueTests),
))
if __name__ == '__main__':
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/xmldiff.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/xmldiff.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/xmldiff.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -13,7 +13,7 @@
##############################################################################
"""XML differences for use in testing the WebDAV code base
-$Id:$
+$Id$
"""
__docformat__ = 'restructuredtext'
@@ -82,7 +82,10 @@
status2 = resp2[href]
for status, namespaces1 in status1.items():
- self.assert_(status2.has_key(status))
+ self.assert_(status2.has_key(status),
+ "the expected result is missing a status for the" \
+ " %s object\n" \
+ "'%s' != '%s'" %(href, status1, status2))
namespaces2 = status2[status]
self.assertEqual(len(namespaces1), len(namespaces2),
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py 2006-02-26 09:03:55 UTC (rev 65492)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py 2006-02-26 19:10:29 UTC (rev 65493)
@@ -25,7 +25,7 @@
from zope.app.datetimeutils import parseDatetimetz, DateTimeError
from zope.app.dav.interfaces import IDAVWidget
-from zope.app.dav.opaquenamespaces import makeDOMStandalone
+from zope.app.dav.common import makeDOMStandalone
from zope.app.form import InputWidget
from zope.app.form.utility import setUpWidget
More information about the Zope3-Checkins
mailing list