[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