[Zope3-checkins] SVN: Zope3/branches/mkerrin-webdav/src/zope/app/
Modified the DAV widgets to handle the rendering and parsing
Michael Kerrin
michael.kerrin at openapp.biz
Tue Jan 10 16:37:02 EST 2006
Log message for revision 41259:
Modified the DAV widgets to handle the rendering and parsing
of all webdav properties to and from Python xml.dom elements.
Clarified some of the fields in the DAV schema in order to
take advantage of the new widgets. Also reworked a big chunk
of PROPFIND to take advantage tof the new widgets.
Also added a basic implementation of the COPY, LOCK and
UNLOCK methods.
Make the ILockStorage utility in zope.app.locking addable
through the site management interface.
Changed:
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/DEPENDENCIES.cfg
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/fields.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_propfind.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.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/test_adapter.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_common_if.py
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.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
A Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_widget.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/unitfixtures.py
U Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py
U Zope3/branches/mkerrin-webdav/src/zope/app/http/options.py
U Zope3/branches/mkerrin-webdav/src/zope/app/locking/configure.zcml
U Zope3/branches/mkerrin-webdav/src/zope/app/locking/storage.py
U Zope3/branches/mkerrin-webdav/src/zope/app/publication/methodnotallowed.txt
-=-
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/DEPENDENCIES.cfg
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/DEPENDENCIES.cfg 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/DEPENDENCIES.cfg 2006-01-10 21:36:58 UTC (rev 41259)
@@ -1,6 +1,7 @@
persistent
transaction
zope.app
+zope.app.locking
zope.component
zope.configuration
zope.interface
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/adapter.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -19,16 +19,17 @@
from xml.dom import minidom
-from zope.interface import implements
-from zope.i18n import translate
+from zope.interface import Interface, implements
from zope.app import zapi
-from zope.app.dav.interfaces import IDAVSchema
+from zope.app.dav.interfaces import IDAVSchema, IDAVActiveLock, IDAVLockEntry
from zope.app.dublincore.interfaces import IDCTimes
from zope.app.filerepresentation.interfaces import IReadDirectory
from zope.app.size.interfaces import ISized
from zope.app.file.interfaces import IFile
+from zope.app.locking.interfaces import ILockable
+
class DAVSchemaAdapter(object):
"""An adapter for all content objects that provides the basic DAV
schema/namespace."""
@@ -47,43 +48,121 @@
def creationdate(self):
dc = IDCTimes(self.context, None)
if dc is None or dc.created is None:
- return ''
- return dc.created.strftime('%Y-%m-%d %TZ')
+ return None
+ return dc.created
creationdate = property(creationdate)
def resourcetype(self):
value = IReadDirectory(self.context, None)
- xml = minidom.Document()
if value is not None:
- node = xml.createElement('collection')
- return node
- return ''
+ return [u'collection']
+ return None
resourcetype = property(resourcetype)
+ def getcontentlanguage(self):
+ return None
+ getcontentlanguage = property(getcontentlanguage)
+
def getcontentlength(self):
sized = ISized(self.context, None)
if sized is None:
- return ''
+ return None
units, size = sized.sizeForSorting()
if units == 'byte':
- return str(size)
- return ''
+ return size
+ return None
getcontentlength = property(getcontentlength)
- def getlastmodified(self):
- dc = IDCTimes(self.context, None)
- if dc is None or dc.created is None:
- return ''
- return dc.modified.strftime('%a, %d %b %Y %H:%M:%S GMT')
- getlastmodified = property(getlastmodified)
-
def executable(self):
- return ''
+ return None
executable = property(executable)
def getcontenttype(self):
file = IFile(self.context, None)
if file is not None:
- return file.contentType
- return ''
+ return file.contentType.decode('utf-8')
+ return None
getcontenttype = property(getcontenttype)
+
+ getetag = None
+
+ def getlastmodified(self):
+ dc = IDCTimes(self.context, None)
+ if dc is None or dc.created is None:
+ return None
+ return dc.modified
+ getlastmodified = property(getlastmodified)
+
+ def supportedlock(self):
+ return IDAVLockEntry(self.context, None)
+ supportedlock = property(supportedlock)
+
+ def lockdiscovery(self):
+ lockable = ILockable(self.context, None)
+ if lockable is None or not lockable.locked():
+ return None
+ return IDAVActiveLock(self.context, None)
+ lockdiscovery = property(lockdiscovery)
+
+ source = None
+
+
+class LockEntry(object):
+ implements(IDAVLockEntry)
+
+ def __init__(self, context):
+ pass
+
+ lockscope = [u'exclusive']
+
+ locktype = [u'write']
+
+
+class ActiveLock(object):
+ """represent a locked object that is the context of this adapter should
+ be locked otherwise a TypeError is raised
+ """
+ implements(IDAVActiveLock)
+
+ def __init__(self, context):
+ self.context = context
+ lockable = ILockable(self.context)
+ if not lockable.locked():
+ raise TypeError, "There are no active locks for this object"
+ self.lockinfo = lockable.getLockInfo()
+
+ def locktype_get(self):
+ return self.lockinfo.get('locktype', None)
+ def locktype_set(self, value):
+ if len(value) != 1 or value[0] != u'write':
+ raise ValueError, "invalid lock type"
+ self.lockinfo['locktype'] = value
+ locktype = property(locktype_get, locktype_set)
+
+ def lockscope_get(self):
+ return self.lockinfo.get('lockscope', None)
+ def lockscope_set(self, value):
+ if len(value) != 1 or value[0] != u'exclusive':
+ raise ValueError, "unsupport lock scope value"
+ self.lockinfo['lockscope'] = value
+ lockscope = property(lockscope_get, lockscope_set)
+
+ def depth_get(self):
+ return u'0'
+ def depth_set(self, value):
+ self.lockinfo['depth'] = value
+ depth = property(depth_get, depth_set)
+
+ def owner_get(self):
+ return self.lockinfo.get('owner', None)
+ def owner_set(self, value):
+ self.lockinfo['owner'] = value
+ owner = property(owner_get, owner_set)
+
+ def timeout_get(self):
+ return u'Second-%d' % self.lockinfo.timeout
+ timeout = property(timeout_get)
+
+ def locktoken_get(self):
+ return self.lockinfo['locktoken']
+ locktoken = property(locktoken_get)
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,157 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Common utilities used by the different parts of WebDAV.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from xml.dom import minidom
+
+from zope.interface import Interface, implements, Attribute
+from zope.publisher.http import status_reasons
+from zope.security.proxy import removeSecurityProxy
+
+from zope.app import zapi
+from zope.app.container.interfaces import IReadContainer
+
+
+class IMultiStatus(Interface):
+ """ """
+
+ body = Attribute(u"""
+ The XML DOM Document representing the currently generated by ...
+ """)
+
+ def addResponse(object, request):
+ """Maybe this should be addResource
+ """
+
+
+class IMultiStatusResponse(Interface):
+ """ """
+
+ def addPropertyByStatus(ns, ns_prefix, property, status = 200):
+ """Insert the the property element into the current response in the
+ correct propstat element specified by the status.
+ """
+
+ def createEmptyElement(ns, ns_prefix, name):
+ """Create an empty `name' xml element within the specific namespace
+ """
+
+
+class MultiStatus(object):
+ implements(IMultiStatus)
+
+ def __init__(self):
+ dns = self.default_ns = 'DAV:'
+ body = self.body = minidom.Document()
+ ms = self.ms = body.createElementNS(dns, 'multistatus')
+ ms.setAttributeNS(dns, 'xmlns', dns)
+ body.appendChild(ms)
+
+ def appendResponse(self, response):
+ """
+ """
+ self.ms.appendChild(self.body.importNode(response, True))
+
+ def addResponse(self, object, request):
+ """Return instance of MultiStatusResponse
+ """
+ body = self.body
+ resp = body.createElementNS(self.default_ns, 'response')
+ self.ms.appendChild(resp)
+ href = body.createElementNS(self.default_ns, 'href')
+ resp.appendChild(href)
+ resource_url = zapi.absoluteURL(object, request)
+ if IReadContainer.providedBy(object):
+ resource_url += '/'
+ href.appendChild(body.createTextNode(resource_url))
+
+ return MultiStatusResponse(resp, self.default_ns)
+
+
+class MultiStatusResponse(object):
+ """Simple utility to simplfy the creatation and modification of a
+ multistatus response.
+ """
+ implements(IMultiStatusResponse)
+
+ def __init__(self, resp, default_ns):
+ """ resp is the current response object to manage"""
+ self.status = {}
+ self.response = resp
+ self.default_ns = default_ns
+
+ def addPropertyByStatus(self, ns, ns_prefix, property, status = 200):
+ """
+ property is a DOM element that we want to add to the response.
+
+ namespace is a 2-tuple of attribute name - must be of the form
+ xmlns:XXXX where XXXX is a string and the uri representing the
+ namespacce.
+
+ status is a integer representing the HTTP code for this property.
+ """
+ if property is None:
+ raise TypeError, "the property xml.dom element must not be None"
+
+ if self.status.has_key(status):
+ prop = self.status[status]
+ else:
+ doc = self.response.ownerDocument
+
+ propstat = doc.createElementNS(self.default_ns, 'propstat')
+ self.response.appendChild(propstat)
+
+ prop = doc.createElementNS(self.default_ns, 'prop')
+ propstat.appendChild(prop)
+
+ sprop = doc.createElementNS(self.default_ns, 'status')
+ propstat.appendChild(sprop)
+ sprop.appendChild(
+ doc.createTextNode('HTTP/1.1 %d %s' %(status,
+ status_reasons[status])))
+ self.status[status] = prop
+
+ if ns is not None and ns_prefix is not None:
+ xmlns = None
+ if prop.attributes is not None:
+ xmlns = prop.attributes.getNamedItemNS(ns,
+ 'xmlns:%s' % ns_prefix)
+
+ if xmlns is None:
+ prop.setAttributeNS(ns, 'xmlns:%s' % ns_prefix, ns)
+
+ if property is not None:
+ ## The DAVOpaqueNamespace adapter security proxies the returned
+ ## XML DOM Element. In this case remove the security proxy.
+
+ ## This should be fixed by using a one of the DAV Widgets for
+ ## opaque properties.
+ property = removeSecurityProxy(property)
+
+ prop.appendChild(
+ prop.ownerDocument.importNode(property, True))
+
+ return prop
+
+ def createEmptyElement(self, ns, ns_prefix, name):
+ """Create an empty `name' xml element within the specific namespace
+ """
+ el = self.response.ownerDocument.createElementNS(ns, name)
+ if ns_prefix is not None:
+ el.setAttributeNS(ns, 'xmlns', ns_prefix)
+ return el
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/common.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/configure.zcml 2006-01-10 21:36:58 UTC (rev 41259)
@@ -33,8 +33,28 @@
factory=".mkcol.MKCOL"
permission="zope.ManageContent"
allowed_attributes="MKCOL" />
-
-
+
+ <!--
+ locking
+ -->
+ <view
+ for="zope.interface.Interface"
+ name="LOCK"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory=".locking.LOCKMethodFactory"
+ permission="zope.ManageContent"
+ allowed_attributes="LOCK"
+ />
+
+ <view
+ for="zope.interface.Interface"
+ name="UNLOCK"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory=".locking.UNLOCKMethodFactory"
+ permission="zope.ManageContent"
+ allowed_attributes="UNLOCK"
+ />
+
<!-- Disabled for now. Need to write tests before checking in.
<view
for="*"
@@ -43,87 +63,54 @@
factory=".move.MOVE"
permission="zope.ManageContent"
allowed_attributes="MOVE" />
-
- <view
- for="*"
- name="COPY"
- type="zope.publisher.interfaces.http.IHTTPRequest"
- factory=".copy.COPY"
- permission="zope.ManageContent"
- allowed_attributes="COPY" />
-
-->
-
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.IText"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.TextDAVWidget"
- permission="zope.Public"
- />
-
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for=".interfaces.IXMLText"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.XMLDAVWidget"
- permission="zope.Public"
- />
<view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.IBytes"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.TextDAVWidget"
- permission="zope.Public"
- />
-
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.IInt"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.TextDAVWidget"
- permission="zope.Public"
- />
+ for="zope.interface.Interface"
+ name="COPY"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory=".copy.COPY"
+ permission="zope.ManageContent"
+ allowed_attributes="COPY"
+ />
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.IFloat"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.TextDAVWidget"
- permission="zope.Public"
- />
+ <adapter
+ provides="zope.app.dav.interfaces.IDAVSchema"
+ for="zope.interface.Interface"
+ permission="zope.Public"
+ factory=".adapter.DAVSchemaAdapter"
+ />
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.IDatetime"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.TextDAVWidget"
- permission="zope.Public"
- />
-
- <view
- type="zope.publisher.interfaces.http.IHTTPRequest"
- for="zope.schema.interfaces.ISequence"
- provides="zope.app.dav.interfaces.IDAVWidget"
- factory="zope.app.dav.widget.SequenceDAVWidget"
- permission="zope.Public"
- />
-
<adapter
- provides="zope.app.dav.interfaces.IDAVSchema"
- for="*"
- permission="zope.Public"
- factory=".adapter.DAVSchemaAdapter" />
+ provides="zope.app.dav.interfaces.IDAVActiveLock"
+ for="zope.interface.Interface"
+ permission="zope.Public"
+ factory=".adapter.ActiveLock"
+ />
+ <adapter
+ provides="zope.app.dav.interfaces.IDAVLockEntry"
+ for="zope.interface.Interface"
+ permission="zope.Public"
+ factory=".adapter.LockEntry"
+ />
+
+ <adapter
+ for="zope.interface.Interface
+ zope.publisher.interfaces.http.IHTTPRequest"
+ provides=".interfaces.IIfHeader"
+ factory=".ifhandler.IfParser"
+ />
+
<!-- TODO: This interface needs to be split up so we can apply seperate
permissions for reading and writing -->
<adapter
- factory=".opaquenamespaces.DAVOpaqueNamespacesAdapter"
- provides=".opaquenamespaces.IDAVOpaqueNamespaces"
- for="zope.app.annotation.interfaces.IAnnotatable"
- trusted="true"
- />
+ factory=".opaquenamespaces.DAVOpaqueNamespacesAdapter"
+ provides=".opaquenamespaces.IDAVOpaqueNamespaces"
+ for="zope.app.annotation.interfaces.IAnnotatable"
+ permission="zope.Public"
+ trusted="true"
+ />
<class class=".opaquenamespaces.DAVOpaqueNamespacesAdapter">
<require
@@ -132,12 +119,111 @@
/>
</class>
+ <!--
+ Define all the DAV widgets.
+ -->
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IText"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.TextDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IBytes"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.TextDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IInt"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.TextDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IFloat"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.TextDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IDatetime"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.DatetimeDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IDate"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.DateDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.ITuple"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.TupleDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.ISequence"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.SequenceDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.schema.interfaces.IList"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.ListDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.app.dav.fields.IXMLEmptyElementList"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.XMLEmptyElementListDAVWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.app.dav.fields.IDAVXMLSubProperty"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.DAVXMLSubPropertyWidget"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ for="zope.app.dav.fields.DAVOpaqueField"
+ provides="zope.app.dav.interfaces.IDAVWidget"
+ factory="zope.app.dav.widget.DAVOpaqueWidget"
+ permission="zope.Public"
+ />
+
<dav:provideInterface
- for="http://purl.org/dc/1.1"
- interface="zope.app.dublincore.interfaces.IZopeDublinCore" />
+ for="http://purl.org/dc/1.1"
+ interface="zope.app.dublincore.interfaces.IZopeDublinCore" />
<dav:provideInterface
- for="DAV:"
- interface="zope.app.dav.interfaces.IDAVSchema" />
+ for="DAV:"
+ interface="zope.app.dav.interfaces.IDAVSchema" />
</configure>
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""WebDAV COPY method support
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from urlparse import urlsplit
+
+from zope.publisher.interfaces import NotFound
+from zope.app import zapi
+from zope.app.copypastemove.interfaces import IObjectMover, IObjectCopier
+from zope.app.publication.http import MethodNotAllowed
+
+from zope.app.traversing.api import traverse, getRoot
+from zope.app.traversing.interfaces import TraversalError
+
+class COPY(object):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def COPY(self):
+ #
+ # get and verify data send in the HTTP request
+ #
+ depth = self.request.getHeader('depth', 'infinity')
+ if depth not in ('0', 'infinity'):
+ self.request.response.setStatus(400)
+ return ''
+
+ # 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', '')
+ while dest and dest[-1] == '/':
+ dest = dest[:-1]
+ if not dest:
+ self.request.response.setStatus(400)
+ return ''
+
+ # find the overwrite header
+ overwrite = self.request.getHeader('overwrite', 't').lower().strip()
+ if overwrite == 't':
+ overwrite = True
+ elif overwrite == 'f':
+ overwrite = False
+ else:
+ self.requset.response.setStatus(400)
+ return ''
+
+ # 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)
+ try:
+ destob = traverse(getRoot(self.context), destpath)
+ exists = True
+ except NotFound:
+ destob = None
+ exists = False
+ except TraversalError:
+ destob = None
+ exists = False
+
+ if destob is not None and not overwrite:
+ self.request.response.setStatus(412)
+ return ''
+ elif destob is not None:
+ ifparser = zapi.queryMultiAdapter((destob, self.request), IIfHeader)
+ if ifparser is not None and not ifparser():
+ self.request.response.setStatus(423)
+ return ''
+ # we need to delete this object
+ raise NotImplementedError, "please delete the destination object"
+
+ # check parent
+ parentpath = destpath.split('/')
+ destname = parentpath.pop()
+ try:
+ parent = traverse(getRoot(self.context), parentpath)
+ except NotFound:
+ parent = None
+ except TraversalError:
+ parent = None
+ if parent is None:
+ self.request.response.setStatus(409)
+ return ''
+
+ if not copier.copyableTo(parent):
+ # XXX - should this be 405 ???
+ self.request.response.setStatus(409)
+ return ''
+
+ copier.copyTo(parent, destname)
+
+ self.request.response.setStatus(exists and 204 or 201)
+ return ''
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/copy.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/fields.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/fields.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/fields.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,87 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""WebDAV-specific fields
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import Interface, implements, Attribute
+from zope.schema import List, Field, Object, TextLine
+from zope.schema.interfaces import IList, IField, IObject
+from zope.configuration.fields import GlobalInterface
+
+
+class IXMLEmptyElementList(IList):
+ """XXX - a List field should be used instead of this field.
+
+ Marker interface for the XMLEmptyElementList. This field is very similar
+ to the List field but allows users to restricted the values contained
+ within the corresponding list.
+
+ It is called this since it is mainly used within WebDAV to restrict the
+ values of certain fields.
+ """
+
+
+class XMLEmptyElementList(List):
+ implements(IXMLEmptyElementList)
+
+ def __init__(self, value_type = None, unique = True, values = (), **kw):
+ """`unique' is False is the base class.
+ """
+ super(XMLEmptyElementList, self).__init__(value_type, unique, **kw)
+
+ self.values = values
+
+ def _validate(self, value):
+ super(XMLEmptyElementList, self)._validate(value)
+
+ if not self.values or not value:
+ return
+
+ for item in value:
+ if item not in self.values:
+ raise WrongContainedType(item)
+
+
+class IDAVXMLSubProperty(IObject):
+ """Sub DAV property.
+
+ schema - specifies the
+ """
+
+ prop_name = TextLine(title = u'Property Name',
+ description = u'Specify the name of a subproperty',
+ required = False)
+
+class DAVXMLSubProperty(Object):
+ implements(IDAVXMLSubProperty)
+
+ prop_name = None
+
+ def __init__(self, prop_interface = Interface, prop_name = None, **kw):
+ super(DAVXMLSubProperty, self).__init__(**kw)
+
+ self.prop_name = prop_name
+
+
+class IDAVOpaqueField(IField):
+ """ """
+
+
+class DAVOpaqueField(Field):
+ """
+ """
+ implements(IDAVOpaqueField)
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/fields.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: 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-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,210 @@
+##############################################################################
+#
+# 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 Zope's WebDAV locking support.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from xml.dom import minidom
+
+from unittest import TestSuite, makeSuite, main
+
+from zope.interface import Interface
+from zope.app import zapi
+from zope.app.testing import setup, ztapi, functional
+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.interfaces import IPathAdapter
+from zope.app.file.file import File
+
+from zope.app.security.adapter import LocatingTrustedAdapterFactory
+
+
+def makeLockBody(data):
+ body = '''<?xml version="1.0" encoding="utf-8"?>
+ <lockinfo xmlns="DAV:">
+ <locktype><%s/></locktype>
+ <lockscope><%s/></lockscope>
+ <owner>%s</owner>
+ </lockinfo>
+ ''' %(data['locktype'], data['lockscope'], data['owner'])
+ return body
+
+
+class TestAllowBefore(functional.HTTPTestCase):
+ # Test for the LOCK and UNLOCK in the Allow header. I use both a OPTIONS
+ # request and a 'FROGS' (undefined) request to test for this. The reason
+ # for two tests is that I ran into problems getting this to work with the
+ # OPTIONS request, has the LOCK, UNLOCK methods are only available when
+ # a ILockStorage utility is present.
+
+ def _checkAllowed(self, allowed, expected):
+ allowed = [allow.strip() for allow in allowed.split(',')]
+ if expected:
+ for m in ('LOCK', 'UNLOCK'):
+ self.assert_(m in allowed,
+ "%s is NOT in %s" %(m, ', '.join(allowed)))
+ else:
+ for m in ('LOCK', 'UNLOCK'):
+ self.assert_(m not in allowed,
+ "%s is in %s" %(m, ', '.join(allowed)))
+
+ def test_allow_publish(self):
+ self._test_allow_publish(False)
+
+ def _test_allow_publish(self, expected):
+ result = self.publish('/', env = {'REQUEST_METHOD': 'FROGS'},
+ basic='mgr:mgrpw',
+ handle_errors = True)
+ allowed = result.getHeader('Allow')
+ self._checkAllowed(allowed, expected)
+
+ def test_allow_options(self):
+ self._test_allow_options(False)
+
+ def _test_allow_options(self, expected):
+ result = self.publish('/', env = {'REQUEST_METHOD': 'OPTIONS'},
+ basic='mgr:mgrpw')
+ allowed = result.getHeader('Allow')
+ self._checkAllowed(allowed, expected)
+
+ def test_lock_file_simple(self):
+ file = File('some content', 'text/plain')
+ self.getRootFolder()['file'] = file
+ self.commit()
+
+ body = makeLockBody(
+ {'locktype': 'write',
+ 'lockscope': 'exclusive',
+ 'owner': '<href>mailto:michael at linux</href>'})
+ basic='mgr:mgrpw'
+ result = self.publish('/file', basic,
+ env = {'REQUEST_METHOD': 'LOCK',
+ 'CONTENT-LENGTH': len(body)},
+ request_body = body, handle_errors = True)
+ respbody = result.getBody()
+ self.assertEqual(result.getStatus(), 405)
+ allowed = result.getHeader('Allow')
+ self._checkAllowed(allowed, False)
+
+
+class TestAllowAfter(TestAllowBefore):
+ pass
+
+
+class TestLOCK(TestAllowBefore):
+
+ def setUp(self):
+ functional.HTTPTestCase.setUp(self)
+ sm = zapi.getSiteManager(self.getRootFolder())
+
+ self.storage = storage = PersistentLockStorage()
+ setup.addUtility(sm, '', ILockStorage, storage)
+
+ ## create a trusted adapter.
+ ztapi.provideAdapter(Interface, ILockable,
+ LocatingTrustedAdapterFactory(LockingAdapterFactory))
+## ztapi.provideAdapter(None, IPathAdapter, LockingPathAdapter,
+## "locking")
+ self.commit()
+
+ def tearDown(self):
+ super(TestLOCK, self).tearDown()
+ del self.storage
+
+ def test_allow_options(self):
+ super(TestLOCK, self)._test_allow_options(True)
+
+ def test_allow_publish(self):
+ ## XXX - the LOCK, and UNLOCK methods should show up in the allow header
+ ## for this test but I can't get this work.
+ super(TestLOCK, self)._test_allow_publish(True)
+
+ def test_lock_file_simple(self):
+ file = File('some content', 'text/plain')
+ self.getRootFolder()['file'] = file
+ self.commit()
+
+ body = makeLockBody(
+ {'locktype': 'write',
+ 'lockscope': 'exclusive',
+ 'owner': '<href>mailto:michael at linux</href>'})
+ basic='mgr:mgrpw'
+ result = self.publish('/file', basic,
+ env = {'REQUEST_METHOD': 'LOCK',
+ 'CONTENT-LENGTH': len(body)},
+ request_body = body)
+ respbody = result.getBody()
+ self.assertEqual(result.getStatus(), 200)
+
+ ## ILockable doesn't work in this context.
+ file = zapi.traverse(self.getRootFolder(), '/file')
+ lock = self.storage.getLock(file)
+ self.assert_(lock is not None)
+
+ def test_lock_file(self):
+ file = File('some content', 'text/plain')
+ self.getRootFolder()['file'] = file
+ self.commit()
+
+ body = makeLockBody(
+ {'locktype': 'write',
+ 'lockscope': 'exclusive',
+ 'owner': '<href>mailto:michael at linux</href>'})
+ basic='mgr:mgrpw'
+ result = self.publish('/file', basic,
+ env = {'REQUEST_METHOD': 'LOCK',
+ 'CONTENT-LENGTH': len(body),
+ },
+ request_body = body)
+ respbody = result.getBody()
+
+ token = result.getHeader('lock-token')
+ result = self.publish('/file', basic,
+ env = {'REQUEST_METHOD': 'LOCK',
+ 'CONTENT-LENGTH': 0,
+ 'TIMEOUT': 'Second-1400',
+ 'IF': '(%s)' % token,
+ },
+ )
+ respbody = result.getBody()
+ xmlresp = minidom.parseString(respbody)
+ timeout = xmlresp.getElementsByTagNameNS('DAV:', 'timeout')
+ self.assertEqual(len(timeout), 1)
+ self.assertEqual(timeout[0].toxml(), u'<timeout>Second-1400</timeout>')
+
+ token = result.getHeader('lock-token')
+ result = self.publish('/file', basic,
+ env = {'REQUEST_METHOD': 'LOCK',
+ 'CONTENT-LENGTH': 0,
+ 'TIMEOUT': 'Second-1400',
+ 'IF': '(<xxx>)',
+ },
+ )
+ respbody = result.getBody()
+ self.assertEqual(result.getStatus(), 412)
+
+
+def test_suite():
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestAllowBefore))
+ suite.addTest(makeSuite(TestLOCK))
+ suite.addTest(makeSuite(TestAllowAfter))
+
+ return suite
+
+if __name__ == '__main__':
+ main(defaultTest = 'test_suite')
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_propfind.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_propfind.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ftests/test_propfind.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -42,12 +42,14 @@
prop='title', expect='Test Title', basic='mgr:mgrpw')
def test_dccreated(self):
+ # XXX - this test is failing because the creatation property is not
+ # being rendered correctly to the specifications
self.addPage('/pt', u'<span />')
pt = traverse(self.getRootFolder(), '/pt')
adapted = IZopeDublinCore(pt)
adapted.created = datetime.utcnow()
transaction.commit()
- expect = str(adapted.created)
+ expect = adapted.created.strftime('%a, %d %b %Y %H:%M:%S %z').rstrip()
self.verifyPropOK(path='/pt', ns='http://purl.org/dc/1.1',
prop='created', expect=expect, basic='mgr:mgrpw')
@@ -84,18 +86,32 @@
request_body=body)
self.assertEquals(result.getStatus(), 207)
s1 = normalize_xml(result.getBody())
- s2 = normalize_xml("""<?xml version="1.0" encoding="utf-8"?>
- <multistatus xmlns="DAV:">
- <response>
- <href>http://localhost/pt</href>
- <propstat>
- <prop xmlns:a0="%(ns)s">
- <%(prop)s xmlns="a0">%(expect)s</%(prop)s>
- </prop>
- <status>HTTP/1.1 200 OK</status>
- </propstat>
- </response>
- </multistatus>""" % {'ns':ns, 'prop':prop, 'expect':expect})
+ if expect:
+ s2 = normalize_xml("""<?xml version="1.0" encoding="utf-8"?>
+ <multistatus xmlns="DAV:">
+ <response>
+ <href>http://localhost/pt</href>
+ <propstat>
+ <prop xmlns:a0="%(ns)s">
+ <%(prop)s xmlns="a0">%(expect)s</%(prop)s>
+ </prop>
+ <status>HTTP/1.1 200 OK</status>
+ </propstat>
+ </response>
+ </multistatus>""" % {'ns':ns, 'prop':prop, 'expect':expect})
+ else:
+ s2 = normalize_xml("""<?xml version="1.0" encoding="utf-8"?>
+ <multistatus xmlns="DAV:">
+ <response>
+ <href>http://localhost/pt</href>
+ <propstat>
+ <prop xmlns:a0="%(ns)s">
+ <%(prop)s xmlns="a0"/>
+ </prop>
+ <status>HTTP/1.1 200 OK</status>
+ </propstat>
+ </response>
+ </multistatus>""" % {'ns':ns, 'prop':prop})
self.assertEquals(s1, s2)
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,182 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Multiadapter to handle the parsing of HTTP IF headers.
+
+$Id$
+"""
+import re, urllib
+
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.container.interfaces import IReadContainer
+from zope.app.locking.interfaces import ILockable
+
+from interfaces import IIfHeader
+
+### If: header handling support. IfParser returns a sequence of
+### TagList objects in the order they were parsed which can then
+### be used in WebDAV methods to decide whether an operation can
+### proceed or to raise HTTP Error 412 (Precondition failed)
+ifheaderre = re.compile(
+ r"(?P<resource><.+?>)?\s*\((?P<listitem>[^)]+)\)"
+ )
+
+listitemre = re.compile(
+ r"(?P<not>not)?\s*(?P<listitem><[a-zA-Z]+:[^>]*>|\[.*?\])",
+ re.I)
+
+class TagList:
+ def __init__(self):
+ self.resource = None
+ self.list = []
+ self.notted = 0
+
+class IfParser(object):
+ """Note that I just ignore any etags passed in the IF header. This is
+ because basically etags are not supported under Zope3 yet has far has I
+ know.
+ """
+ implements(IIfHeader)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def ifParser(self, hdr):
+ out = []
+ i = 0
+ while 1:
+ m = ifheaderre.search(hdr[i:])
+ if not m:
+ break
+
+ i = i + m.end()
+ tag = TagList()
+ tag.resource = m.group('resource')
+ if tag.resource: # We need to delete < >
+ tag.resource = tag.resource[1:-1]
+ listitem = m.group('listitem')
+ tag.notted, tag.list = self.listParser(listitem)
+ out.append(tag)
+
+ return out
+
+ def listParser(self, listitem):
+ out = []
+ notted = 0
+ i = 0
+ while 1:
+ m = listitemre.search(listitem[i:])
+ if not m:
+ break
+
+ i = i + m.end()
+ out.append(m.group('listitem'))
+
+ if m.group('not'):
+ notted = 1
+
+ return notted, out
+
+ def tokenFinder(self, token):
+ """return lock tokens
+ """
+ # takes a string like '<opaquelocktoken:afsdfadfadf> and returns the
+ # token part.
+ if not token:
+ return None # An empty string was passed in
+
+ if token[0] == '[':
+ return None # An Etag was passed in
+
+ if token[0] == '<':
+ token = token[1:-1]
+
+ ## return token[token.find(':') + 1:]
+ return token
+
+ def __call__(self):
+ ifhdr = self.request.getHeader('if', None)
+ if ifhdr is None:
+ return True
+
+ lockable = ILockable(self.context)
+ if not lockable.locked():
+ return True
+
+ lockinfo = lockable.getLockInfo()
+
+ tags = self.ifParser(ifhdr)
+
+ resource_url = zapi.absoluteURL(self.context, self.request)
+ if IReadContainer.providedBy(self.context):
+ resource_url += '/'
+
+ # found is a boolean indicated whether or not we have found the current
+ # locktoken
+ found = False
+ # resourcetagged is a boolean indicating that the current context
+ # has being referenced in the
+ resourcetagged = False
+ # resources is a boolean indicating that the if header has specified
+ # some resources
+ resources = False
+ for tag in tags:
+ if not tag.resource:
+ # if resources is True then we have a problem with the logic of
+ # this method - also I think this is against the rfc2518 proto
+ token_list = map(self.tokenFinder, tag.list)
+ wehavetokens = filter(
+ lambda token: token == lockinfo.get('lockuri', ''),
+ token_list)
+
+ if not wehavetokens:
+ continue
+
+ if tag.notted:
+ continue
+
+ found = True
+ break
+ else:
+ resources = True
+
+ url = tag.resource
+ if url[0] != '/':
+ type, url = urllib.splittype(url)
+ home, url = urllib.splithost(url)
+
+ if resource_url == url:
+ resourcetagged = True
+
+ token_list = map(self.tokenFinder, tag.list)
+ wehavetoken = filter(
+ lambda token: token == lockinfo.get('lockuri', ''),
+ token_list)
+
+ if not wehavetoken:
+ continue
+
+ if tag.notted:
+ continue
+
+ found = True
+ break
+
+ if found:
+ return True
+ if resources and not resourcetagged:
+ return True
+ return False
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/ifhandler.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/interfaces.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -17,20 +17,14 @@
"""
__docformat__ = 'restructuredtext'
-from zope.interface import Interface, implements
-from zope.schema import Text
-from zope.schema.interfaces import IText
-from zope.app.form.interfaces import IInputWidget
+from zope.interface import Interface
+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
-class IXMLText(IText):
- """A Text field that can optionally contain has its value a
- minidom DOM Node.
- """
+from fields import IXMLEmptyElementList, XMLEmptyElementList
+from fields import IDAVXMLSubProperty, DAVXMLSubProperty, DAVOpaqueField
-class XMLText(Text):
- implements(IXMLText)
-
-
class IDAVNamespace(Interface):
"""Represents a namespace available in WebDAV XML documents.
@@ -38,50 +32,55 @@
provide this interface
"""
+
class IDAVCreationDate(Interface):
- creationdate = Text(
- title=u'''Records the time and date the resource was created''',
+ creationdate = Datetime(title=u'''Records the time and date the resource\
+ was created''',
- description=u'''The creationdate property should be defined on all
- DAV compliant resources. If present, it contains a
- timestamp of the moment when the resource was
- created (i.e., the moment it had non- null state).''',
+ description=u'''\
+ The creationdate property should be
+ defined on all DAV compliant
+ resources. If present, it contains a
+ timestamp of the moment when the
+ resource was created (i.e., the moment
+ it had non- null state).''',
- readonly=True)
+ readonly = True)
class IDAVDisplayName(Interface):
- displayname = Text(
- title=u'''Provides a name for the resource that is suitable for\
- presentation to a user''',
+ displayname = TextLine(title=u'''Provides a name for the resource that\
+ is suitable for presentation to a\
+ user''',
- description=u'''The displayname property should be
- defined on all DAV compliant
- resources. If present, the property
- contains a description of the resource
- that is suitable for presentation to a
- user.''')
+ description=u'''\
+ The displayname property should be
+ defined on all DAV compliant
+ resources. If present, the property
+ contains a description of the resource
+ that is suitable for presentation to a
+ user.''')
class IDAVSource(Interface):
- source = Text(
- title=u'''The destination of the source link\
- identifies the resource that contains\
- the unprocessed source of the link\
- source''',
+ source = DAVOpaqueField(title=u'''The destination of the source link\
+ identifies the resource that contains\
+ the unprocessed source of the link\
+ source''',
- description=u'''The source of the link (src) is
- typically the URI of the output
- resource on which the link is defined,
- and there is typically only one
- destination (dst) of the link, which
- is the URI where the unprocessed
- source of the resource may be
- accessed. When more than one link
- destination exists, this specification
- asserts no policy on ordering.''')
+ description=u'''\
+ The source of the link (src) is
+ typically the URI of the output
+ resource on which the link is defined,
+ and there is typically only one
+ destination (dst) of the link, which
+ is the URI where the unprocessed
+ source of the resource may be
+ accessed. When more than one link
+ destination exists, this specification
+ asserts no policy on ordering.''')
class IOptionalDAVSchema(IDAVCreationDate, IDAVDisplayName, IDAVSource):
@@ -91,156 +90,305 @@
class IGETDependentDAVSchema(Interface):
"""DAV properties that are dependent on GET support of the resource"""
- getcontentlanguage = Text(
- title=u'''Contains the Content-Language\
- header returned by a GET without\
- accept headers''',
+ getcontentlanguage = TextLine(title=u'''Contains the Content-Language\
+ header returned by a GET without\
+ accept headers''',
- description=u'''The getcontentlanguage property MUST
- be defined on any DAV compliant
- resource that returns the
- Content-Language header on a GET.''')
+ description=u'''\
+ The getcontentlanguage property MUST
+ be defined on any DAV compliant
+ resource that returns the
+ Content-Language header on a GET.''')
- getcontentlength = Text(
- title=u'''Contains the Content-Length header\
- returned by a GET without accept\
- headers''',
+ getcontentlength = Int(title=u'''Contains the Content-Length header\
+ returned by a GET without accept\
+ headers''',
- description=u'''The getcontentlength property MUST be
- defined on any DAV compliant resource
- that returns the Content-Length header
- in response to a GET.''',
+ description=u'''\
+ The getcontentlength property MUST be
+ defined on any DAV compliant resource
+ that returns the Content-Length header
+ in response to a GET.''',
- readonly=True)
+ readonly = True)
- getcontenttype = Text(
- title=u'''Contains the Content-Type header\
- returned by a GET without accept\
- headers''',
+ getcontenttype = TextLine(title=u'''Contains the Content-Type header\
+ returned by a GET without accept\
+ headers''',
- description=u'''This getcontenttype property MUST be
- defined on any DAV compliant resource
- that returns the Content-Type header
- in response to a GET.''')
+ description=u'''\
+ This getcontenttype property MUST be
+ defined on any DAV compliant resource
+ that returns the Content-Type header
+ in response to a GET.''')
- getetag = Text(
- title=u'''Contains the ETag header returned by a GET\
- without accept headers''',
+ getetag = TextLine(title=u'''Contains the ETag header returned by a GET\
+ without accept headers''',
- description=u'''The getetag property MUST be defined
- on any DAV compliant resource that
- returns the Etag header.''',
+ description=u'''\
+ The getetag property MUST be defined
+ on any DAV compliant resource that
+ returns the Etag header.''',
- readonly=True)
+ readonly = True)
- getlastmodified = Text(
- title=u'''Contains the Last-Modified header\
- returned by a GET method without\
- accept headers''',
+ getlastmodified = Datetime(title=u'''Contains the Last-Modified header\
+ returned by a GET method without\
+ accept headers''',
- description=u'''Note that the last-modified date on a
- resource may reflect changes in any
- part of the state of the resource, not
- necessarily just a change to the
- response to the GET method. For
- example, a change in a property may
- cause the last-modified date to
- change. The getlastmodified property
- MUST be defined on any DAV compliant
- resource that returns the
- Last-Modified header in response to a
- GET.''',
+ description=u'''\
+ Note that the last-modified date on a
+ resource may reflect changes in any
+ part of the state of the resource, not
+ necessarily just a change to the
+ response to the GET method. For
+ example, a change in a property may
+ cause the last-modified date to
+ change. The getlastmodified property
+ MUST be defined on any DAV compliant
+ resource that returns the
+ Last-Modified header in response to a
+ GET.''',
- readonly=True)
+ readonly = True)
-class IDAV1Schema(IGETDependentDAVSchema):
+class IDAVResourceSchema(Interface):
"""DAV properties required for Level 1 compliance"""
- resourcetype = XMLText(title=u'''Specifies the nature of the resource''',
+ resourcetype = XMLEmptyElementList(
+ title = u'''Specifies the nature of the resource''',
- description=u'''
- The resourcetype property MUST be
- defined on all DAV compliant
- resources. The default value is
- empty.''',
+ description = u'''\
+ The resourcetype property MUST be
+ defined on all DAV compliant
+ resources. The default value is
+ empty.''',
- readonly=True)
+ readonly = True,
+ value_type = TextLine(title = u'resource type')
+ )
-class IDAV2Schema(IDAV1Schema):
+class IDAVLockEntry(Interface):
+ """A DAV Sub property of the supportedlock property.
+ """
+ lockscope = XMLEmptyElementList(title = u'''\
+ Describes the exclusivity of a lock''',
+
+ description = u'''\
+ Specifies whether a lock is an exclusive lock, or a
+ shared lock.''',
+
+ readonly = True,
+
+ value_type = TextLine(title = u''))
+
+ locktype = XMLEmptyElementList(title = u'''\
+ Describes the access type of the lock''',
+
+ description = u'''\
+ Specifies the access type of a lock. At present,
+ this specification only defines one lock type, the
+ write lock.''',
+
+ readonly = True,
+
+ value_type = TextLine(title = u''))
+
+
+class IDAVActiveLock(Interface):
+ """A DAV Sub property of the lockdiscovery property.
+ """
+ lockscope = XMLEmptyElementList(title = u'''\
+ Describes the exclusivity of a lock''',
+
+ description = u'''\
+ Specifies whether a lock is an exclusive lock, or a
+ shared lock.''',
+
+ value_type = TextLine(title = u''))
+
+ locktype = XMLEmptyElementList(title = u'''\
+ Describes the access type of the lock''',
+
+ description = u'''\
+ Specifies the access type of a lock. At present,
+ this specification only defines one lock type, the
+ write lock.''',
+
+ value_type = TextLine(title = u''))
+
+ depth = Text(title = u'Depth',
+ description = u'The value of the Depth header.')
+
+ ## this is the wrong field. After reading RFC3744 extra semantics are
+ ## supplied to this field. Further research into this and other RFC3744
+ ## properties is required.
+ owner = DAVOpaqueField(title = u'Owner',
+ description = u'''\
+ The owner XML element provides information sufficient
+ for either directly contacting a principal (such as a
+ telephone number or Email URI), or for discovering the
+ principal (such as the URL of a homepage) who owns a
+ lock.''')
+
+ timeout = Text(title = u'Timeout',
+ description = u'The timeout associated with a lock')
+
+ locktoken = DAVOpaqueField(title = u'Lock Token',
+ description = u'''\
+ The href contains one or more opaque lock token URIs
+ which all refer to the same lock (i.e., the
+ OpaqueLockToken-URI production in section 6.4).''')
+
+
+class IDAVLockSchema(Interface):
"""DAV properties required for Level 2 compliance"""
- lockdiscovery = Text(
- title=u'''Describes the active locks on a resource''',
+ lockdiscovery = DAVXMLSubProperty(title=u'''Describes the active locks on a\
+ resource''',
- description=u'''The lockdiscovery property returns a
- listing of who has a lock, what type
- of lock he has, the timeout type and
- the time remaining on the timeout,
- and the associated lock token. The
- server is free to withhold any or all
- of this information if the requesting
- principal does not have sufficient
- access rights to see the requested
- data.''',
+ description=u'''\
+ The lockdiscovery property returns a
+ listing of who has a lock, what type
+ of lock he has, the timeout type and
+ the time remaining on the timeout,
+ and the associated lock token. The
+ server is free to withhold any or all
+ of this information if the requesting
+ principal does not have sufficient
+ access rights to see the requested
+ data.''',
- readonly=True)
+ readonly = True,
- supportedlock = Text(
- title=u'''To provide a listing of the lock\
- capabilities supported by the resource''',
+ prop_name = 'activelock',
- description=u'''The supportedlock property of a
- resource returns a listing of the
- combinations of scope and access types
- which may be specified in a lock
- request on the resource. Note that
- the actual contents are themselves
- controlled by access controls so a
- server is not required to provide
- information the client is not
- authorized to see.''',
+ schema = IDAVActiveLock)
- readonly=True)
+ supportedlock = DAVXMLSubProperty(title = u'''\
+ To provide a listing of the lock\
+ capabilities supported by the\
+ resource''',
-class IDAVSchema(IOptionalDAVSchema, IDAV2Schema):
+ description=u'''\
+ The supportedlock property of a
+ resource returns a listing of the
+ combinations of scope and access types
+ which may be specified in a lock
+ request on the resource. Note that
+ the actual contents are themselves
+ controlled by access controls so a
+ server is not required to provide
+ information the client is not
+ authorized to see.''',
+
+ readonly = True,
+
+ schema = IDAVLockEntry,
+
+ prop_name = 'lockentry')
+
+class IDAVCollectionSchema(IOptionalDAVSchema, IDAVLockSchema,
+ IDAVResourceSchema):
+ """DAV properties schema that applies to a collection.
+
+ Not that all the get* properties don't apply to collections.
+ """
+
+
+class IDAVSchema(IOptionalDAVSchema, IGETDependentDAVSchema, IDAVLockSchema,
+ IDAVResourceSchema):
"""Full DAV properties schema"""
-class IDAVWidget(IInputWidget):
+class IDAVWidget(IWidget):
"""A specialized widget used to convert to and from DAV properties."""
- def __call__():
- """Render the widget.
+ required = Bool(
+ title=u"Required",
+ description=u"""If True, widget should be displayed as requiring input.
- This method should not contain a minidom DOM Node as its value; if its
- value is a minidom DOM Node then its value will be normalized to a
- string.
+ By default, this value is the field's 'required' attribute. This
+ field can be set to False for widgets that always provide input (e.g.
+ a checkbox) to avoid unnecessary 'required' UI notations.
+ """)
- If a value should be a minidom DOM Node then use the XMLDAVWidget for
- inserting its value into the DAV XML response.
+ def getInputValue():
+ """Return value suitable for the widget's field.
+
+ The widget must return a value that can be legally assigned to
+ its bound field or otherwise raise ``WidgetInputError``.
+
+ The return value is not affected by `setRenderedValue()`.
"""
- def setRenderedValue(value):
- """Set the DAV value for the property
+ def applyChanges(content):
+ """Validate the user input data and apply it to the content.
+
+ Return a boolean indicating whether a change was actually applied.
+
+ This raises an error if there is no user input.
"""
+ def hasInput():
+ """Returns ``True`` if the widget has input.
-class ITextDAVWidget(IDAVWidget):
- """A DAV widget for text values."""
+ Input is used by the widget to calculate an 'input value', which is
+ a value that can be legally assigned to a field.
+ Note that the widget may return ``True``, indicating it has input, but
+ still be unable to return a value from `getInputValue`. Use
+ `hasValidInput` to determine whether or not `getInputValue` will return
+ a valid value.
-class ISequenceDAVWidget(IDAVWidget):
- """A DAV widget for sequences."""
+ A widget that does not have input should generally not be used
+ to update its bound field. Values set using
+ `setRenderedValue()` do not count as user input.
+ A widget that has been rendered into a form which has been
+ submitted must report that it has input. If the form
+ containing the widget has not been submitted, the widget
+ shall report that it has no input.
-class IXMLDAVWidget(IDAVWidget):
- """A DAV widget for rendering XML values.
+ """
- This widget should be used if you want to insert any minidom DOM Nodes
- into the DAV XML response.
+ def hasValidInput():
+ """Returns ``True`` is the widget has valid input.
- It it receives something other then a minidom DOM Node has its value then
- it just renders has an empty string.
+ This method is similar to `hasInput` but it also confirms that the
+ input provided by the user can be converted to a valid field value
+ based on the field constraints.
+ """
+
+ def setRenderedValue(value):
+ """Set the value of the field associated with this widget.
+
+ This value must validate to the type expected by the associated field.
+ """
+
+ def setProperty(propel):
+ """Parse the DOM element ``propel`` and store the extracted value in
+ the widget.
+
+ The extracted value must validate against the associated field.
+ """
+
+ def renderProperty(ns, ns_prefix):
+ """Render a property has a DOM elements.
+ """
+
+## def removeProperty(self, ns, prop):
+## """
+## """
+
+
+class IIfHeader(Interface):
+ """RFC 2518 Section ...
"""
+
+ def __call__():
+ """Return True / False wheather the current context and request
+ matches the the IF HTTP header has specified in RFC 2518
+ """
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,334 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Locking support for WebDAV
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import time
+import random
+from xml.dom import minidom
+from xml.parsers import expat
+
+from zope.interface import Interface, implements, implementer
+from zope.component import adapter
+from zope.publisher.interfaces.http import IHTTPRequest
+
+from zope.app import zapi
+from zope.app.form.utility import setUpWidget
+from zope.app.locking.interfaces import ILockable, ILockStorage, LockingError
+from zope.app.container.interfaces import IReadContainer
+
+from interfaces import IDAVWidget, IDAVActiveLock, IDAVLockEntry, \
+ IDAVLockSchema, IIfHeader
+from common import MultiStatus
+
+MAXTIMEOUT = (2L**32)-1
+
+_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
+
+
+ at adapter(Interface, IHTTPRequest)
+ at implementer(Interface)
+def LOCKMethodFactory(context, request):
+ lockable = ILockable(context, None)
+ if lockable is None:
+ return None
+ return LOCK(context, request)
+
+
+class LOCK(object):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ self._depth = request.getHeader('depth', '0')
+ ct = request.getHeader('content-type', 'text/xml')
+ if ';' in ct:
+ parts = ct.split(';', 1)
+ self.content_type = parts[0].strip().lower()
+ self.content_type_params = parts[1].strip()
+ else:
+ self.content_type = ct.lower()
+ self.content_type_params = None
+ self.default_ns = 'DAV:'
+ self.default_ns_prefix = None
+
+ self.defaulttimeout = 12 * 60L
+
+ self.errorBody = None
+
+ def generateLockToken(self):
+ # is it interesting to provide this has a utility. I don't think so.
+ return 'opaquelocktoken:%s-%s-00105A989226:%.03f' % \
+ (_randGen.random(), _randGen.random(), time.time())
+
+ def getDepth(self):
+ return self._depth
+
+ def setDepth(self, depth):
+ self._depth = depth
+
+ def getTimeout(self):
+ timeoutheader = self.request.getHeader('timeout', 'infinity')
+
+ timeoutheader = timeoutheader.strip().lower()
+ t = str(timeoutheader).split('-')[-1]
+ if t == 'infinite' or t == 'infinity':
+ timeout = self.defaulttimeout
+ else:
+ timeout = long(t)
+
+ if timeout > MAXTIMEOUT:
+ timeout = self.defaulttimeout
+
+ return timeout
+
+ def LOCK(self):
+ if self.content_type not in ('text/xml', 'application/xml'):
+ self.request.response.setStatus(400)
+ return ''
+ if self.getDepth() not in ('0', 'infinity'):
+ self.request.response.setStatus(400)
+ return ''
+
+ try:
+ xmldoc = minidom.parse(self.request.bodyStream)
+ except expat.ExpatError:
+ # In this case assume that the bodyStream is empty. We may want to
+ # actually test for that case here.
+ xmldoc = None
+
+ if xmldoc is None:
+ xmldocBody = None
+ else:
+ xmldocBody = xmldoc.childNodes[0]
+
+ lockable = ILockable(self.context)
+
+ # get lock token from request and pass to handleLockObject
+ try:
+ if lockable.locked() and xmldoc is None:
+ self.handleRefreshLockedObject(self.context)
+ else:
+ self.handleLockObject(xmldocBody, self.context, None)
+ except DAVLockingError, e:
+ self.request.response.setStatus(e.status)
+ return ''
+
+ if self.errorBody is not None:
+ self.request.response.setStatus(207)
+ body = self.errorBody.body.toxml('utf-8')
+ self.request.response.setResult(body)
+ return body
+
+ return self._renderResponse()
+
+ def handleRefreshLockedObject(self, object):
+ # request body is empty and the current object is locked.
+ lockable = ILockable(object)
+ lockinfo = lockable.getLockInfo()
+
+ resource_url = zapi.absoluteURL(object, self.request)
+ if IReadContainer.providedBy(object):
+ resource_url += '/'
+
+ ifparser = zapi.queryMultiAdapter((object, self.request), IIfHeader)
+ if ifparser is not None and not ifparser():
+ raise PreConditionFailedError(None, "if header match failed")
+
+ ## the next two lines should be handled in the if parser.
+## if lockinfo.get('lockuri', '') != token:
+## raise PreConditionFailedException, "lock tokens don't match"
+ timeout = self.getTimeout()
+ lockinfo.timeout = timeout
+
+ if IReadContainer.providedBy(object):
+ for id, obj in object.items():
+ self.handleRefreshLockedObject(obj)
+
+ def handleLockObject(self, xmldoc, object, token):
+ lockable = ILockable(object)
+ if lockable.locked():
+ raise AlreadyLockedError(None, "object is already locked")
+
+ timeout = self.getTimeout()
+ lockinfo = lockable.lock(timeout)
+ if not token:
+ token = self.generateLockToken()
+
+ lockinfo['lockuri'] = token
+ lockinfo['locktoken'] = \
+ '<locktoken><href>%s</href></locktoken>' % token
+
+ adapted = IDAVActiveLock(object)
+ for node in xmldoc.childNodes:
+ if node.nodeType != node.ELEMENT_NODE:
+ continue
+
+ name = node.localName
+
+ field = IDAVActiveLock[name]
+
+ setUpWidget(self, name, field, IDAVWidget, value = None,
+ ignoreStickyValues = False)
+ widget = getattr(self, name + '_widget')
+ widget.setProperty(node)
+
+ if not widget.hasValidInput():
+ raise DAVConflictError(name, "invalid value supplied")
+
+ if not widget.applyChanges(adapted):
+ raise UnprocessableEntityError(name, "value failed")
+
+ self.depthRecurse(xmldoc, object, token)
+
+ def _addError(self, object, status):
+ if self.errorBody is None:
+ self.errorBody = MultiStatus()
+
+ response = self.errorBody.addResponse(object, self.request)
+ el = self.errorBody.body.createElementNS(self.default_ns,
+ 'lockdiscovery')
+ response.addPropertyByStatus(self.default_ns, None, el, status)
+
+ def depthRecurse(self, xmldoc, object, token):
+ depth = self.getDepth()
+ if depth == '0' or not IReadContainer.providedBy(object):
+ return
+
+ for id, obj in object.items():
+ try:
+ self.handleLockObject(xmldoc, obj, token)
+ except DAVLockingError, error:
+ self._addError(obj, error.status)
+
+ def _renderResponse(self):
+ """ Render the response for a successful lock operation """
+ lockable = ILockable(self.context)
+ assert lockable.locked()
+ lockinfo = lockable.getLockInfo()
+
+ ns = self.default_ns
+ ns_prefix = self.default_ns_prefix
+
+ resp = minidom.Document()
+ prop = resp.createElementNS(ns, 'prop')
+ prop.setAttributeNS(ns, 'xmlns', ns)
+ resp.appendChild(prop)
+
+ fieldname = 'lockdiscovery'
+
+ adapted = IDAVLockSchema(self.context)
+
+ field = IDAVLockSchema[fieldname]
+
+ setUpWidget(self, fieldname, field, IDAVWidget,
+ value = field.get(adapted),
+ ignoreStickyValues = False)
+ widget = getattr(self, fieldname + '_widget', None)
+ assert widget is not None
+ el = widget.renderProperty(ns, ns_prefix)
+ prop.appendChild(el)
+
+ body = resp.toxml('utf-8')
+
+ response = self.request.response
+ response.setStatus(200)
+ response.setHeader('lock-token', '<%s>' % lockinfo['lockuri'])
+ response.setHeader('content-type', 'text/xml')
+ response.setResult(body)
+
+ return body
+
+
+ at adapter(Interface, IHTTPRequest)
+ at implementer(Interface)
+def UNLOCKMethodFactory(context, request):
+ lockable = ILockable(context, None)
+ if lockable is None:
+ return None
+ return UNLOCK(context, request)
+
+
+class UNLOCK(object):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def UNLOCK(self):
+ lock_token = self.request.getHeader('lock-token', '')
+ if lock_token[0] == '<' and lock_token[-1] == '>':
+ lock_token = lock_token[1:-1]
+
+ if not lock_token:
+ self.request.response.setStatus(412)
+ return
+
+ self.handleUnlock(self.context, lock_token)
+
+ self.request.response.setStatus(204)
+ return ''
+
+ def handleUnlock(self, object, token):
+ lockable = ILockable(object)
+
+ if not lockable.locked():
+ # should we raise an unlocked error?? here
+ return
+
+ # XXX - this is wrong. We should use an adaption of ILockTracker to
+ # find all locks with the lockuri set to token and unlock those tokens
+ # has this will fail on a newly created locked item.
+ lockinfo = lockable.getLockInfo()
+ if lockinfo.get('lockuri', '') != token:
+ raise PreConditionFailedError('lockuri',
+ "lock tokens are not equal")
+
+ lockable.unlock()
+
+ # recurise into subfolders if we are a folder.
+ if IReadContainer.providedBy(object):
+ for id, obj in object.items():
+ self.handleUnlock(obj, token)
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/opaquenamespaces.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -39,34 +39,34 @@
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, propel):
+
+ 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."""
-
+
implements(IDAVOpaqueNamespaces)
__used_for__ = IAnnotatable
@@ -80,7 +80,7 @@
oprops = OOBTree()
self._mapping = oprops
-
+
def _changed(self):
if self.annotations is not None:
self.annotations[DANkey] = self._mapping
@@ -91,10 +91,10 @@
def __getitem__(self, key):
return self._mapping[key]
-
+
def keys(self):
return self._mapping.keys()
-
+
def __setitem__(self, key, value):
self._mapping[key] = value
self._changed()
@@ -102,21 +102,25 @@
def __delitem__(self, key):
del self._mapping[key]
self._changed()
-
+
#
# Convenience methods; storing and retrieving properties through WebDAV
# It may be better to use specialised IDAWWidget implementatins for this.
#
- def renderProperty(self, ns, nsprefix, prop, propel):
+ 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.setAttribute('xmlns', nsprefix)
- propel.appendChild(el)
+ ## el = propel.ownerDocument.importNode(value.documentElement, True)
+ el = value.documentElement
+ el.setAttributeNS(ns, 'xmlns', nsprefix)
+ ## propel.appendChild(el)
+ return el
+
def setProperty(self, propel):
ns = propel.namespaceURI
props = self.setdefault(ns, OOBTree())
@@ -133,9 +137,9 @@
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
@@ -143,7 +147,7 @@
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>
@@ -152,18 +156,18 @@
>>> 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>
@@ -185,7 +189,7 @@
<p0:lancelot/>
</spam>
"""
-
+
return DOMTransformer(element).makeStandalone()
@@ -205,10 +209,10 @@
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 ''
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/propfind.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -17,13 +17,17 @@
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.app import zapi
from zope.app.container.interfaces import IReadContainer
-from zope.app.form.utility import setUpWidgets
+from zope.app.form.utility import setUpWidget
from interfaces import IDAVWidget, IDAVNamespace
from opaquenamespaces import IDAVOpaqueNamespaces
+from common import MultiStatus
class PROPFIND(object):
"""PROPFIND handler for all objects"""
@@ -41,20 +45,20 @@
self.content_type = ct.lower()
self.content_type_params = None
self.default_ns = 'DAV:'
- self.oprops = IDAVOpaqueNamespaces(self.context, None)
+ 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
- # The xmldoc attribute will be set later, if needed.
- self.xmldoc = None
+ self.responsedoc = MultiStatus()
def getDepth(self):
return self._depth
@@ -62,34 +66,33 @@
def setDepth(self, depth):
self._depth = depth.lower()
- def PROPFIND(self, xmldoc=None):
- if self.content_type not in ['text/xml', 'application/xml']:
+ def PROPFIND(self, xmldoc = None):
+ if self.content_type not in ('text/xml', 'application/xml'):
self.request.response.setStatus(400)
return ''
if self.getDepth() not in ['0', '1', 'infinity']:
self.request.response.setStatus(400)
return ''
- resource_url = zapi.absoluteURL(self.context, self.request)
- if IReadContainer.providedBy(self.context):
- resource_url += '/'
-
if xmldoc is None:
try:
xmldoc = minidom.parse(self.request.bodyStream)
except expat.ExpatError:
pass
- self.xmldoc = xmldoc
-
- 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))
+ self.handlePropfind(xmldoc)
+ body = self.responsedoc.body.toxml('utf-8')
+ self.request.response.setResult(body)
+ self.request.response.setStatus(207)
+ self.request.response.setHeader('content-type', 'text/xml')
+
+ return body
+
+ def handlePropfind(self, xmldoc):
+ resp = self.resp = \
+ self.responsedoc.addResponse(self.context, self.request)
+
if xmldoc is not None:
propname = xmldoc.getElementsByTagNameNS(
self.default_ns, 'propname')
@@ -97,196 +100,140 @@
self._handlePropname(resp)
else:
source = xmldoc.getElementsByTagNameNS(self.default_ns, 'prop')
- self._handlePropvalues(source, resp)
+ self._handlePropvalues(source)
else:
- self._handlePropvalues(None, resp)
+ self._handlePropvalues(None)
- self._depthRecurse(ms)
+ self._depthRecurse(xmldoc)
- body = resp.toxml('utf-8')
- self.request.response.setResult(body)
- self.request.response.setStatus(207)
- self.request.response.setHeader('content-type', 'text/xml')
- return body
-
- def _depthRecurse(self, ms):
+ def _depthRecurse(self, xmldoc):
depth = self.getDepth()
if depth == '0' or not IReadContainer.providedBy(self.context):
return
+
subdepth = (depth == '1') and '0' or 'infinity'
+
for id, obj in self.context.items():
- pfind = zapi.queryMultiAdapter((obj, self.request), name='PROPFIND')
- if pfind is None:
- continue
+ pfind = PROPFIND(obj, self.request)
pfind.setDepth(subdepth)
- value = pfind.PROPFIND(self.xmldoc)
- parsed = minidom.parseString(value)
- responses = parsed.getElementsByTagNameNS(
- self.default_ns, 'response')
+ pfind.handlePropfind(xmldoc)
+
+ subrespdoc = pfind.responsedoc.body
+ responses = subrespdoc.getElementsByTagNameNS(self.default_ns,
+ 'response')
for r in responses:
- ms.appendChild(ms.ownerDocument.importNode(r, True))
+ ## print "obj: %s, %s" %(zapi.getPath(obj), r.toxml('utf-8'))
+ self.responsedoc.appendResponse(r)
def _handleProp(self, source):
props = {}
source = source[0]
- childs = [e for e in source.childNodes
- if e.nodeType == e.ELEMENT_NODE]
- for node in childs:
+
+ for node in source.childNodes:
+ if node.nodeType != node.ELEMENT_NODE:
+ continue
+
ns = node.namespaceURI
iface = zapi.queryUtility(IDAVNamespace, ns)
value = props.get(ns, {'iface': iface, 'props': []})
value['props'].append(node.localName)
props[ns] = value
+
return props
def _handleAllprop(self):
props = {}
+
for ns, properties in self.avail_props.items():
iface = zapi.queryUtility(IDAVNamespace, ns)
props[ns] = {'iface': iface, 'props': properties}
+
return props
def _handlePropname(self, resp):
- re = resp.lastChild.lastChild
- re.appendChild(resp.createElement('propstat'))
- prop = resp.createElement('prop')
- re.lastChild.appendChild(prop)
count = 0
for ns, props in self.avail_props.items():
attr_name = 'a%s' % count
+ ns_prefix = None
if ns is not None and ns != self.default_ns:
count += 1
- prop.setAttribute('xmlns:%s' % attr_name, ns)
+ ns_prefix = attr_name
for p in props:
- el = resp.createElement(p)
- prop.appendChild(el)
- if ns is not None and ns != self.default_ns:
- el.setAttribute('xmlns', attr_name)
- re.lastChild.appendChild(resp.createElement('status'))
- re.lastChild.lastChild.appendChild(
- resp.createTextNode('HTTP/1.1 200 OK'))
+ el = resp.createEmptyElement(ns, ns_prefix, p)
+ resp.addPropertyByStatus(ns, ns_prefix, el)
- def _handlePropvalues(self, source, resp):
+ def _handlePropvalues(self, source):
if not source:
_props = self._handleAllprop()
else:
_props = self._handleProp(source)
- avail, not_avail = self._propertyResolver(_props)
- if avail:
- self._renderAvail(avail, resp, _props)
- if not_avail:
- self._renderNotAvail(not_avail, resp)
+ self._renderResponse(self.resp, _props)
- def _propertyResolver(self, _props):
- avail = {}
- not_avail = {}
- for ns in _props.keys():
- iface = _props[ns]['iface']
- for p in _props[ns]['props']:
- if iface is None:
- # The opaque property case
- if (self.oprops is not None and
- self.oprops.get(ns, {}).has_key(p)):
- l = avail.setdefault(ns, [])
- l.append(p)
- else:
- l = not_avail.setdefault(ns, [])
- l.append(p)
- continue
- # The registered namespace case
- adapter = iface(self.context, None)
- if adapter is None:
- # Registered interface but no adapter? Maybe log this?
- l = not_avail.setdefault(ns, [])
- l.append(p)
- continue
- l = avail.setdefault(ns, [])
- l.append(p)
-
- return avail, not_avail
-
- def _renderAvail(self, avail, resp, _props):
- re = resp.lastChild.lastChild
- re.appendChild(resp.createElement('propstat'))
- prop = resp.createElement('prop')
- re.lastChild.appendChild(prop)
- re.lastChild.appendChild(resp.createElement('status'))
- re.lastChild.lastChild.appendChild(
- resp.createTextNode('HTTP/1.1 200 OK'))
+ def _renderResponse(self, re, _props):
count = 0
- for ns, props in avail.items():
+ # ns - the full namespace for this object.
+ for ns, ifaceprops in _props.items():
attr_name = 'a%s' % count
+ ns_prefix = None
if ns is not None and ns != self.default_ns:
count += 1
- prop.setAttribute('xmlns:%s' % attr_name, ns)
- iface = _props[ns]['iface']
+ ns_prefix = attr_name
+ iface = ifaceprops['iface']
+ props = ifaceprops['props']
+
+ # adapted - the current view through which all properties are
+ # reterived this should be moved to using the widget framework.
if not iface:
- # The opaque properties case, hand it off
for name in props:
- self.oprops.renderProperty(ns, attr_name, name, prop)
+ if self.oprops:
+ el = self.oprops.renderProperty(ns, ns_prefix, name)
+ re.addPropertyByStatus(ns, ns_prefix, el, 200)
+ else:
+ el = re.createEmptyElement(ns, ns_prefix, name)
+ re.addPropertyByStatus(ns, ns_prefix, el, 404)
continue
- # The registered namespace case
- initial = {}
- adapted = iface(self.context)
- for name, field in getFields(iface).items():
- try:
- value = field.get(adapted)
- except AttributeError:
- # Interface says the attribute exists but it
- # couldn't be found on the adapted object.
- value = field.missing_value
- if value is not field.missing_value:
- initial[name] = value
- setUpWidgets(self, iface, IDAVWidget, ignoreStickyValues=True,
- initial=initial, names=initial.keys())
+ adapted = iface(self.context, None)
+ if adapted is None:
+ # XXX - maybe these properties are unavailable for a reason.
+ # render unavailable properties
+ for propname in props:
+ el = re.createEmptyElement(ns, ns_prefix, propname)
+ re.addPropertyByStatus(ns, ns_prefix, el, 404)
+ continue
- for p in props:
- el = resp.createElement('%s' % p )
- if ns is not None and ns != self.default_ns:
- el.setAttribute('xmlns', attr_name)
- prop.appendChild(el)
- widget = getattr(self, p + '_widget', None)
- if widget is None:
+ for propname in props:
+ status = 200
+ el = None # property DOM fragment
+ field = None
+
+ try:
+ field = iface[propname]
+ except KeyError:
# A widget wasn't generated for this property
# because the attribute was missing on the adapted
# object, which actually means that the adapter
# didn't fully implement the interface ;(
- el.appendChild(resp.createTextNode(''))
+ el = re.createEmptyElement(ns, ns_prefix, propname)
+ status = 404
+
+ re.addPropertyByStatus(ns, ns_prefix, el, status)
continue
- value = widget()
- if isinstance(value, (unicode, str)):
- # Get the widget value here
- el.appendChild(resp.createTextNode(value))
- else:
- if zapi.isinstance(value, minidom.Node):
- el.appendChild(
- el.ownerDocument.importNode(value, True))
- else:
- # Try to string-ify
- value = str(widget)
- # Get the widget value here
- el.appendChild(resp.createTextNode(value))
+ try:
+ setUpWidget(self, propname, field, IDAVWidget,
+ value = field.get(adapted),
+ ignoreStickyValues = False)
+ widget = getattr(self, propname + '_widget', None)
+ assert widget is not None
+ el = widget.renderProperty(ns, ns_prefix)
+ if widget.getErrors():
+ status = 500
+ except:
+ # Internal Server Error - status 500
+ el = re.createEmptyElement(ns, ns_prefix, propname)
+ status = 500
- def _renderNotAvail(self, not_avail, resp):
- re = resp.lastChild.lastChild
- re.appendChild(resp.createElement('propstat'))
- prop = resp.createElement('prop')
- re.lastChild.appendChild(prop)
- re.lastChild.appendChild(resp.createElement('status'))
- re.lastChild.lastChild.appendChild(
- resp.createTextNode('HTTP/1.1 404 Not Found'))
- count = 0
- for ns, props in not_avail.items():
- attr_name = 'a%s' % count
- if ns is not None and ns != self.default_ns:
- count += 1
- prop.setAttribute('xmlns:%s' % attr_name, ns)
- for p in props:
- el = resp.createElement('%s' % p )
- prop.appendChild(el)
- if ns is not None and ns != self.default_ns:
- el.setAttribute('xmlns', attr_name)
+ re.addPropertyByStatus(ns, ns_prefix, el, status)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/proppatch.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -48,10 +48,12 @@
# 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):
@@ -103,6 +105,7 @@
status = self._handleSet(node)
else:
status = self._handleRemove(node)
+ ## _propresults doesn't seems to be set correctly.
results = _propresults.setdefault(status, {})
props = results.setdefault(node.namespaceURI, [])
if node.localName not in props:
@@ -164,19 +167,21 @@
if field.readonly:
return 409 # RFC 2518 specifies 409 for readonly props
- value = field.get(iface(self.context))
+ 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)
+ value = value, ignoreStickyValues = True)
widget = getattr(self, prop.localName + '_widget')
- widget.setRenderedValue(prop)
+ widget.setProperty(prop)
if not widget.hasValidInput():
return 409 # Didn't match the widget validation
- if widget.applyChanges(iface(self.context)):
+ if widget.applyChanges(adapted):
return 200
return 422 # Field didn't accept the value
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_adapter.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_adapter.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_adapter.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -83,9 +83,9 @@
>>> dav.displayname
u'bender'
>>> dav.creationdate, dav.resourcetype, dav.getcontentlength
- ('', '', '')
+ (None, None, None)
>>> dav.getlastmodified, dav.executable
- ('', '')
+ (None, None)
Now, after that dull test, let's provide some actual meta-data.
First, we have to set up the necessary adapter:
@@ -107,11 +107,11 @@
>>> dav.displayname
u'bender'
- >>> dav.creationdate == y2k.strftime('%Y-%m-%d %TZ')
+ >>> dav.creationdate.strftime('%Y-%m-%d %TZ') == y2k.strftime('%Y-%m-%d %TZ')
True
>>> dav.resourcetype, dav.getcontentlength
- ('', '')
- >>> dav.getlastmodified == y3k.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ (None, None)
+ >>> dav.getlastmodified.strftime('%a, %d %b %Y %H:%M:%S GMT') == y3k.strftime('%a, %d %b %Y %H:%M:%S GMT')
True
To make `getcontentlength` work, we can provide our adapter to
@@ -119,7 +119,6 @@
>>> ztapi.provideAdapter(IRobot, ISized, RobotSize)
>>> dav.getcontentlength
- ''
And if robots were directories:
@@ -130,11 +129,12 @@
>>> dav.displayname
u'bender/'
- >>> import xml.dom.minidom
- >>> isinstance(dav.resourcetype, xml.dom.minidom.Element)
+ >>> isinstance(dav.resourcetype, list)
True
- >>> dav.resourcetype.localName
- 'collection'
+ >>> len(dav.resourcetype) == 1
+ True
+ >>> dav.resourcetype[0] == u'collection'
+ True
All there is now to it is cleaning up after ourselves:
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_common_if.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_common_if.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_common_if.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,262 @@
+##############################################################################
+#
+# 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 the if header handling code
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import urllib
+
+from zope.interface import Interface
+from zope.interface.verify import verifyObject
+from zope.publisher.browser import TestRequest
+from zope.security.testing import Principal, Participation
+from zope.security.management import newInteraction, endInteraction, \
+ queryInteraction
+
+from zope.app.testing import ztapi
+from zope.app.container.interfaces import IReadContainer
+from zope.app.file.file import File
+from zope.app.locking.interfaces import ILockable, ILockStorage, ILockTracker
+from zope.app.locking.adapter import LockingAdapterFactory, LockingPathAdapter
+from zope.app.locking.storage import PersistentLockStorage
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.app.traversing.interfaces import IPathAdapter
+from zope.app.traversing.browser import AbsoluteURL, SiteAbsoluteURL
+from zope.app.traversing.browser.interfaces import IAbsoluteURL
+
+from zope.app.dav.common import MultiStatus, MultiStatusResponse, \
+ IMultiStatusResponse
+from zope.app.dav.interfaces import IIfHeader
+from zope.app.dav.ifhandler import IfParser
+
+
+class FakeAbsoluteURL(object):
+ # If the context is a folder return '/folder' else return '/file'
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __unicode__(self):
+ return urllib.unquote(self.__str__()).decode('utf-8')
+
+ def __str__(self):
+ context = self.context
+ request = self.request
+
+ if IReadContainer.providedBy(context):
+ return '/folder'
+ return '/file'
+
+ __call__ = __str__
+
+ def breadcrumbs(self):
+ if IReadContainer.providedBy(context):
+ name = 'folder'
+ else:
+ name = 'file'
+
+ return ({'name': name,
+ 'url': 'http://localhost:8080/%s' % name,
+ },
+ )
+
+
+class FakeKeyReference(object):
+ """Fake keyref for testing"""
+ def __init__(self, object):
+ self.object = object
+
+ def __call__(self):
+ return self.object
+
+ def __hash__(self):
+ return id(self.object)
+
+ def __cmp__(self, other):
+ return cmp(id(self.object), id(other.object))
+
+
+class TestIfParser(unittest.TestCase):
+
+ def setUp(self):
+ super(TestIfParser, self).setUp()
+
+ ztapi.provideAdapter(Interface, IKeyReference, FakeKeyReference)
+ ztapi.provideAdapter(Interface, ILockable, LockingAdapterFactory)
+ ztapi.provideAdapter(None, IPathAdapter, LockingPathAdapter,
+ "locking")
+
+ self.storage = storage = PersistentLockStorage()
+ ztapi.provideUtility(ILockStorage, storage)
+ ztapi.provideUtility(ILockTracker, storage)
+
+ ztapi.browserView(None, 'absolute_url', FakeAbsoluteURL)
+ ztapi.browserView(None, '', FakeAbsoluteURL, providing = IAbsoluteURL)
+
+ self.token = 'opaquelocktoken:somelocktoken'
+
+ def tearDown(self):
+ super(TestIfParser, self).tearDown()
+
+ # I do a lot of locking - make sure that I remember to end the
+ # current interaction
+ if queryInteraction() is not None:
+ endInteraction()
+
+ del self.storage
+
+ def _lockcontent(self, content, locktoken = None):
+ # simply lock content with locktoken if set and return the lockinfo
+ participation = Participation(Principal('michael'))
+ newInteraction(participation)
+ lockable = ILockable(content)
+ lockable.lock()
+ lockinfo = lockable.getLockInfo()
+ if locktoken is not None:
+ lockinfo['lockuri'] = locktoken
+ return lockinfo
+
+ def test_basic(self):
+ request = TestRequest()
+ context = File('some content', 'text/plain')
+ ifparser = IfParser(context, request)
+ self.assert_(verifyObject(IIfHeader, ifparser))
+ # not locked -> true
+ self.assertEqual(ifparser(), True)
+ request = TestRequest(**{'IF': '(<some lock token>)'})
+ ifparser = IfParser(context, request)
+ # context not locked -> true
+ self.assertEqual(ifparser(), True)
+
+ def test_on_locked_file_no_token(self):
+ request = TestRequest(**{'IF': '(<%s>)' % self.token})
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context)
+ ifparser = IfParser(context, request)
+ # lock tokens don't match since it is empty so this should fail.
+ self.assertEqual(ifparser(), False)
+
+ def test_on_locked_file_with_token(self):
+ request = TestRequest(**{'IF': '(<%s>)' % self.token})
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context, self.token)
+ ifparser = IfParser(context, request)
+ # tokens match so this is true
+ self.assertEqual(ifparser(), True)
+
+ def test_resource_correct(self):
+ request = TestRequest(**{
+ 'IF': '<http://localhost:8080/file> (<%s>)' % self.token,
+ })
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context, self.token)
+ ifparser = IfParser(context, request)
+ # resource and token match
+ self.assertEqual(ifparser(), True)
+
+ def test_resource_empty_token(self):
+ # test correct resource with wrong token
+ request = TestRequest(**{
+ 'IF': '<http://localhost:8080/file> (<%s>)' % self.token,
+ })
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context)
+ ifparser = IfParser(context, request)
+ # resrouce matches but the token doesn't
+ self.assertEqual(ifparser(), False)
+
+ def test_wrong_resource_empty_token(self):
+ request = TestRequest(**{
+ 'IF': '<http://localhost:8080/folder/> (<%s>)' % self.token,
+ })
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context, self.token)
+ ifparser = IfParser(context, request)
+ # resource don't match but either does the tokens -> True ???
+ self.assertEqual(ifparser(), True)
+
+ def test_resource_wrong_wrong_token(self):
+ request = TestRequest(**{
+ 'IF': '<http://localhost:8080/folder/> (<%s>)' % self.token,
+ })
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context, self.token)
+ ifparser = IfParser(context, request)
+ # resources don't match so this passes since
+ self.assertEqual(ifparser(), True)
+
+ def test_multiple_resources_correct_token(self):
+ request = TestRequest(**{
+ 'IF': """<http://localhost:8080/folder/> (<dummylock:token>)"""
+ """<http://localhost:8080/file> (<%s>)"""
+ %(self.token),
+ })
+ context = File('some content', 'text/plain')
+ lockinfo = self._lockcontent(context, self.token)
+ ifparser = IfParser(context, request)
+ # one of the resources and tokens match -> good
+ self.assertEqual(ifparser(), True)
+
+
+class TestMultiStatus(unittest.TestCase):
+
+ def test_no_properties(self):
+ ms = MultiStatus()
+ asstr = ms.body.toxml('utf-8')
+ self.assertEqual(asstr, '<?xml version="1.0" encoding="utf-8"?>\n<multistatus xmlns="DAV:"/>')
+
+## def test_one_resource_no_props(self):
+## ms = MultiStatus()
+## context = File('some content', 'text/plain')
+## request = TestRequest()
+## msr = ms.addResponse(context, request)
+## self.assert_(verifyObject(IMultiStatusResponse, msr))
+
+## asstr = ms.body.toxml('utf-8')
+## self.assertEqual(asstr, '<?xml version="1.0" encoding="utf-8"?>\n<multistatus xmlns="DAV:"><response><href>/file</href><status>200 OK</status></response></multistatus>')
+
+ def test_one_resource_one_none_prop(self):
+ ms = MultiStatus()
+ context = File('some content', 'text/plain')
+ request = TestRequest()
+ msr = ms.addResponse(context, request)
+
+ self.assertRaises(TypeError, msr.addPropertyByStatus,
+ ('DAV:', None, None, 200))
+
+## def test_one_resource_one_prop(self):
+## ms = MultiStatus()
+## context = File('some content', 'text/plain')
+## request = TestRequest()
+## msr = ms.addResponse(context, request)
+
+## msr = ms.addResponse(context, request)
+## el = ms.body.createElementNS('DAV:', 'foo')
+## el.appendChild(ms.body.createTextNode('foo content'))
+## msr.addPropertyByStatus('DAV', None, el, 200)
+## self.assertEqual(ms.body.toxml('utf-8'), '')
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(TestIfParser),
+ unittest.makeSuite(TestMultiStatus),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_common_if.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: 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-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,247 @@
+##############################################################################
+#
+# 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 Locking
+
+$Id$
+"""
+from cStringIO import StringIO
+from xml.dom import minidom
+from unittest import TestCase, TestSuite, main, makeSuite
+
+import transaction
+from ZODB.tests.util import DB
+
+from zope.publisher.browser import TestRequest
+from zope.app.component.testing import PlacefulSetup
+from zope.app.traversing.api import traverse
+
+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
+from zope.app.locking.storage import ILockStorage, PersistentLockStorage
+from zope.app.traversing.interfaces import IPathAdapter
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.security.management import newInteraction, endInteraction
+from zope.security.testing import Principal, Participation
+
+from zope.app.dav.locking import LOCK
+from zope.app.dav import interfaces
+from zope.app.dav import widget
+from zope.app.dav.fields import IDAVXMLSubProperty, IDAVOpaqueField
+from zope.app.dav.adapter import DAVSchemaAdapter, ActiveLock
+
+from unitfixtures import File, Folder, FooZPT
+
+# copied from zope.app.locking.tests
+class FakeKeyReference(object):
+ """Fake keyref for testing"""
+ def __init__(self, object):
+ self.object = object
+
+ def __call__(self):
+ return self.object
+
+ def __hash__(self):
+ return id(self.object)
+
+ def __cmp__(self, other):
+ return cmp(id(self.object), id(other.object))
+
+
+def _createRequest(data, headers = None, skip_headers = None):
+ if headers is None:
+ headers = {'Content-type': 'text/xml',
+ 'Depth': '0'}
+
+ body = '''<?xml version="1.0" encoding="utf-8"?>
+ <lockinfo xmlns="DAV:">
+ <locktype><%s/></locktype>
+ <lockscope><%s/></lockscope>
+ <owner>%s</owner>
+ </lockinfo>
+ ''' %(data['locktype'], data['lockscope'], data['owner'])
+
+ _environ = {'CONTENT_TYPE': 'text/xml',
+ 'CONTENT_LENGTH': str(len(body))}
+
+ if headers is not None:
+ for key, value in headers.items():
+ _environ[key.upper().replace('-', '_')] = value
+
+ if skip_headers is not None:
+ for key in skip_headers:
+ if _environ.has_key(key.upper()):
+ del _environ[key.upper()]
+
+ request = TestRequest(StringIO(body), _environ)
+
+ return request
+
+
+class TestPlacefulLOCK(PlacefulSetup, TestCase):
+
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+ PlacefulSetup.buildFolders(self)
+
+ root = self.rootFolder
+ zpt = FooZPT()
+ self.content = "some content\n for testing"
+ file = File('spam', 'text/plain', self.content)
+ folder = Folder('bla')
+ root['file'] = file
+ root['zpt'] = zpt
+ root['folder'] = folder
+ self.zpt = traverse(root, 'zpt')
+ self.file = traverse(root, 'file')
+ self.folder = traverse(root, 'folder')
+
+ ztapi.provideAdapter(Interface, IKeyReference, FakeKeyReference)
+ ztapi.provideAdapter(Interface, ILockable, LockingAdapterFactory)
+ ztapi.provideAdapter(None, IPathAdapter, LockingPathAdapter,
+ "locking")
+ storage = PersistentLockStorage()
+ ztapi.provideUtility(ILockStorage, storage)
+ ztapi.provideUtility(ILockTracker, storage)
+
+ ztapi.provideAdapter(Interface, interfaces.IDAVActiveLock,
+ ActiveLock)
+ ztapi.provideAdapter(Interface, interfaces.IDAVLockSchema,
+ DAVSchemaAdapter)
+
+ ztapi.browserViewProviding(IText, widget.TextDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(IInt, widget.TextDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(ITextLine, widget.TextDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(IDatetime, widget.DatetimeDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(ISequence, widget.SequenceDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(interfaces.IXMLEmptyElementList,
+ widget.XMLEmptyElementListDAVWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(interfaces.IDAVXMLSubProperty,
+ widget.DAVXMLSubPropertyWidget,
+ interfaces.IDAVWidget)
+ ztapi.browserViewProviding(IDAVOpaqueField,
+ widget.DAVOpaqueWidget,
+ interfaces.IDAVWidget)
+
+ self.db = DB()
+ self.conn = self.db.open()
+ root = self.conn.root()
+ root['Application'] = self.rootFolder
+ transaction.commit()
+
+ def tearDown(self):
+ PlacefulSetup.tearDown(self)
+ self.db.close()
+
+ def _simpleLock(self, object, username = 'michael', headers = None):
+ michael = Principal(username)
+ mparticipation = Participation(michael)
+ endInteraction()
+ newInteraction(mparticipation)
+ request = _createRequest(
+ data = {'lockscope': 'exclusive',
+ 'locktype': 'write',
+ 'owner': '<href>mailto:michael at linux</href>'},
+ headers = headers)
+ lock = LOCK(object, request)
+ lock.LOCK()
+ endInteraction()
+ return request.response
+
+ def test_non_webdav_but_locked_file(self):
+ michael = Principal('michael')
+ mparticipation = Participation(michael)
+ endInteraction()
+ newInteraction(mparticipation)
+ file = self.file
+ lockable = ILockable(file)
+ lock = lockable.lock()
+ lockinfo = lockable.getLockInfo()
+ endInteraction()
+
+ self._simpleLock(self.file, 'michael')
+
+ def test_file_is_locked(self):
+ self._simpleLock(self.file, 'michael')
+ file = self.file
+
+ lockable = ILockable(file)
+ self.assertEqual(lockable.locked(), True)
+ lockinfo = lockable.getLockInfo()
+ self.assert_(lockinfo.target is file)
+ lockscope = lockinfo['lockscope']
+ self.assert_(len(lockscope) == 1)
+ self.assert_(lockscope[0] == u'exclusive')
+ locktype = lockinfo['locktype']
+ self.assert_(len(locktype) == 1)
+ self.assert_(locktype[0] == u'write')
+
+ def test_alreadylocked(self):
+ self._simpleLock(self.file, 'michael')
+ file = self.file
+ lockable = ILockable(file)
+ self.assertEqual(lockable.locked(), True)
+
+ response = self._simpleLock(self.file, 'michael')
+ self.assertEqual(response.getStatus(), 423)
+
+ def test_depthinf(self):
+ response = self._simpleLock(self.folder, 'michael',
+ {'DEPTH': 'infinity'})
+ self.assertEqual(response.getStatus(), 200)
+ # assert that the depth infinity locked any subobjects
+ locktracker = zapi.getUtility(ILockTracker)
+ self.assert_(locktracker.getAllLocks() > 1)
+
+ def test_depthinf_conflict(self):
+ file1 = self.folder.items()[0][1]
+ response = self._simpleLock(file1, 'michael')
+ self.assertEqual(response.getStatus(), 200)
+ response = self._simpleLock(self.folder, 'michael',
+ {'DEPTH': 'infinity'})
+ self.assertEqual(response.getStatus(), 207)
+
+ expected = '''<?xml version="1.0" encoding="utf-8"?>
+ <multistatus xmlns="DAV:">
+ <response>
+ <href>http://127.0.0.1/folder/1</href>
+ <propstat>
+ <prop><lockdiscovery/></prop>
+ <status>HTTP/1.1 423 Locked</status>
+ </propstat>
+ </response>
+ </multistatus>'''
+
+ s1 = normalize_xml(response.consumeBody())
+ s2 = normalize_xml(expected)
+ self.assertEqual(s1, s2)
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(TestPlacefulLOCK),
+ ))
+
+if __name__ == '__main__':
+ main(defaultTest = 'test_suite')
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
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-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_propfind.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -25,7 +25,7 @@
from zope.pagetemplate.tests.util import normalize_xml
from zope.schema import getFieldNamesInOrder
-from zope.schema.interfaces import IText, ITextLine, IDatetime, ISequence
+from zope.schema.interfaces import IText, ITextLine, IDatetime, ISequence, IInt
from zope.app import zapi
from zope.app.testing import ztapi
@@ -46,14 +46,14 @@
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.interfaces import IXMLDAVWidget
-from zope.app.dav.interfaces import IXMLText
-from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget, \
- XMLDAVWidget
-from zope.app.dav.interfaces import IXMLText
+from zope.app.dav.interfaces import IXMLEmptyElementList, XMLEmptyElementList
+from zope.app.dav.widget import TextDAVWidget, DatetimeDAVWidget, \
+ XMLEmptyElementListDAVWidget, SequenceDAVWidget, DAVXMLSubPropertyWidget, \
+ DAVOpaqueWidget
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
from zope.app.dav.adapter import DAVSchemaAdapter
+from zope.app.dav.fields import IDAVXMLSubProperty, IDAVOpaqueField
from unitfixtures import File, Folder, FooZPT
@@ -118,11 +118,18 @@
'absolute_url', AbsoluteURL)
ztapi.provideView(None, IHTTPRequest, Interface,
'PROPFIND', propfind.PROPFIND)
+
ztapi.browserViewProviding(IText, TextDAVWidget, IDAVWidget)
+ ztapi.browserViewProviding(IInt, TextDAVWidget, IDAVWidget)
ztapi.browserViewProviding(ITextLine, TextDAVWidget, IDAVWidget)
- ztapi.browserViewProviding(IDatetime, TextDAVWidget, IDAVWidget)
+ ztapi.browserViewProviding(IDatetime, DatetimeDAVWidget, IDAVWidget)
ztapi.browserViewProviding(ISequence, SequenceDAVWidget, IDAVWidget)
- ztapi.browserViewProviding(IXMLText, XMLDAVWidget, IXMLDAVWidget)
+ ztapi.browserViewProviding(IXMLEmptyElementList,
+ XMLEmptyElementListDAVWidget, IDAVWidget)
+ ztapi.browserViewProviding(IDAVXMLSubProperty, DAVXMLSubPropertyWidget,
+ IDAVWidget)
+ ztapi.browserViewProviding(IDAVOpaqueField, DAVOpaqueWidget, IDAVWidget)
+
ztapi.provideAdapter(IAnnotatable, IAnnotations, AttributeAnnotations)
ztapi.provideAdapter(IAnnotatable, IZopeDublinCore,
ZDCAnnotatableAdapter)
@@ -279,7 +286,7 @@
</prop>
'''
expect_file = '''<prop>
- <resourcetype></resourcetype>
+ <resourcetype/>
</prop>
'''
expect_folder = '''<prop>
@@ -301,7 +308,7 @@
</prop>
''' % len(file.data)
expected_folder = '''<prop>
- <getcontentlength></getcontentlength>
+ <getcontentlength/>
</prop>
'''
self._checkPropfind(file, req, expected_file)
@@ -320,7 +327,7 @@
</prop>
'''
expected_folder = '''<prop>
- <getcontenttype></getcontenttype>
+ <getcontenttype/>
</prop>
'''
self._checkPropfind(file, req, expected_file)
@@ -346,8 +353,11 @@
dc.created = datetime.utcnow()
req = '''<prop xmlns:DC="http://www.purl.org/dc/1.1">
<DC:created /></prop>'''
+ ## the format for the created date below is '%s' dc.created - changing
+ ## to the format generated by the new date time dav widget
expect = '''<prop xmlns:a0="http://www.purl.org/dc/1.1">
- <created xmlns="a0">%s</created></prop>''' % dc.created
+ <created xmlns="a0">%s</created></prop>''' % \
+ dc.created.strftime('%a, %d %b %Y %H:%M:%S %z').rstrip()
self._checkPropfind(zpt, req, expect)
def test_davpropdcsubjects(self):
@@ -452,6 +462,9 @@
self._checkPropfind(folder, req, expect, depth='infinity', resp=resp)
def test_davemptybodyallpropzptdepth0(self):
+ # XXX - this test is failing since the creationdate property is
+ # currently not implemented properly.
+
# RFC 2518, Section 8.1: A client may choose not to submit a
# request body. An empty PROPFIND request body MUST be
# treated as a request for the names and values of all
@@ -465,20 +478,24 @@
req = ''
expect = ''
props = getFieldNamesInOrder(IZopeDublinCore)
- pvalues = {'created': '%s+00:00' % now}
+ ## XXX - The created date below used to take the format defined in this
+ ## comment. I need to need the date time specification for both DC and
+ ## standard WebDAV.
+ ## '%s+00:00' % now}
+ pvalues = {'created': now.strftime('%a, %d %b %Y %H:%M:%S %z').rstrip()}
for p in props:
if pvalues.has_key(p):
expect += '<%s xmlns="a0">%s</%s>' % (p, pvalues[p], p)
else:
- expect += '<%s xmlns="a0"></%s>' % (p, p)
+ expect += '<%s xmlns="a0"/>' % p
props = getFieldNamesInOrder(IDAVSchema)
pvalues = {'displayname':'zpt',
- 'creationdate':now.strftime('%Y-%m-%d %TZ')}
+ 'creationdate':now.strftime('%Y-%m-%dT%TZ')}
for p in props:
if pvalues.has_key(p):
expect += '<%s>%s</%s>' % (p, pvalues[p], p)
else:
- expect += '<%s></%s>' % (p, p)
+ expect += '<%s/>' % p
expect = '''<prop xmlns:a0="http://www.purl.org/dc/1.1">
%s</prop>''' % expect
self._checkPropfind(zpt, req, expect)
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-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_proppatch.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -41,7 +41,7 @@
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
+from zope.schema.interfaces import IText, ISequence, ITuple
import zope.app.dav.tests
from zope.app.dav.tests.unitfixtures import File, Folder, FooZPT
@@ -50,7 +50,7 @@
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
+from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget, TupleDAVWidget
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
@@ -146,18 +146,21 @@
root['folder'] = folder
self.zpt = traverse(root, 'zpt')
self.file = traverse(root, 'file')
+
ztapi.provideView(None, IHTTPRequest, Interface,
'absolute_url', AbsoluteURL)
ztapi.provideView(None, IHTTPRequest, Interface,
'PROPPATCH', proppatch.PROPPATCH)
ztapi.browserViewProviding(IText, TextDAVWidget, IDAVWidget)
ztapi.browserViewProviding(ISequence, SequenceDAVWidget, IDAVWidget)
+ ztapi.browserViewProviding(ITuple, TupleDAVWidget, IDAVWidget)
ztapi.provideAdapter(IAnnotatable, IAnnotations, AttributeAnnotations)
ztapi.provideAdapter(IAnnotatable, IZopeDublinCore,
ZDCAnnotatableAdapter)
ztapi.provideAdapter(IAnnotatable, IDAVOpaqueNamespaces,
DAVOpaqueNamespacesAdapter)
ztapi.provideAdapter(IAnnotatable, ITestSchema, TestSchemaAdapter)
+
sm = zapi.getGlobalSiteManager()
directlyProvides(IDAVSchema, IDAVNamespace)
sm.provideUtility(IDAVNamespace, IDAVSchema, 'DAV:')
@@ -166,6 +169,7 @@
'http://www.purl.org/dc/1.1')
directlyProvides(ITestSchema, IDAVNamespace)
sm.provideUtility(IDAVNamespace, ITestSchema, TestURI)
+
self.db = DB()
self.conn = self.db.open()
root = self.conn.root()
Added: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_widget.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_widget.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_widget.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -0,0 +1,133 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from datetime import datetime
+from xml.dom import minidom
+from unittest import TestSuite, main, makeSuite, TestCase
+
+from zope.interface import Interface, implements
+from zope.interface.verify import verifyClass
+from zope.schema import Text, Datetime, List
+from zope.schema import ValidationError
+from zope.publisher.browser import TestRequest
+from zope.testing.doctest import DocTestSuite
+
+from zope.app.form.interfaces import WidgetInputError
+from zope.app.testing import placelesssetup
+
+from zope.app.dav.interfaces import IDAVWidget
+from zope.app.dav.widget import DAVWidget, TextDAVWidget, DatetimeDAVWidget, \
+ XMLEmptyElementListDAVWidget
+
+
+class DAVWidgetTest(placelesssetup.PlacelessSetup, TestCase):
+
+ _FieldFactory = Text
+ _WidgetFactory = DAVWidget
+
+ def setUp(self):
+ desc = u''
+ title = u'Foo Title'
+ foofield = self._FieldFactory(title = title, description = desc)
+ class ITestContent(Interface):
+ foo = foofield
+
+ class TestObject(object):
+ implements(ITestContent)
+
+ self.content = TestObject()
+ field = ITestContent['foo']
+ field = field.bind(self.content)
+ request = TestRequest(HTTP_ACCEPT_LANGUAGE = 'pl')
+ request.form['field.foo'] = u'Foo Value'
+ self._widget = self._WidgetFactory(field, request)
+
+ def test_base_interface(self):
+ self.failUnless(verifyClass(IDAVWidget, DAVWidget))
+
+ def test_widget_input(self):
+ content = self.test_content
+ # try multiple bad content
+ bad_contents = self.test_bad_contents
+ if not isinstance(bad_contents, list):
+ bad_contents = [bad_contents]
+
+ self.failIf(self._widget.hasInput())
+ self._widget.setRenderedValue(content)
+ self.assert_(self._widget.hasInput())
+ self.assert_(self._widget.hasValidInput())
+ self.assertEqual(self._widget.getInputValue(), content)
+
+ for bad_content in bad_contents:
+ self._widget.setRenderedValue(bad_content)
+ self.assert_(self._widget.hasInput())
+ self.failIf(self._widget.hasValidInput())
+ self.assertRaises(WidgetInputError, self._widget.getInputValue)
+
+ def test_widget_apply_content(self):
+ content = self.test_content
+
+ self._widget.setRenderedValue(content)
+ self.assert_(self._widget.hasValidInput())
+
+ self.assert_(self._widget.applyChanges(self.content))
+
+
+class TextDAVWidgetTest(DAVWidgetTest):
+ _WidgetFactory = TextDAVWidget
+
+ test_content = u'This is some text content'
+ test_bad_contents = 10
+
+
+class DatetimeDAVWidgetTest(DAVWidgetTest):
+ _WidgetFactory = DatetimeDAVWidget
+ _FieldFactory = Datetime
+
+ test_content = datetime.fromtimestamp(1131234842)
+ test_bad_contents = [10, u'This is bad content']
+
+ def test_widget_input(self):
+ date = datetime(1999, 12, 31, 23, 59, 59)
+ doc = minidom.Document()
+ propel = doc.createElement('foo')
+ # date.strftime = '1999-12-31 23:59:59Z'
+ propel.appendChild(doc.createTextNode(date.strftime('%F')))
+ self._widget.setProperty(propel)
+ ## self._widget._value == date
+ ## TypeError: can't compare offset-naive and offset-aware datetimes
+
+
+class XMLEmptyElementListDAVWidgetTest(DAVWidgetTest):
+ _WidgetFactory = XMLEmptyElementListDAVWidget
+ _FieldFactory = List
+
+ test_content = [u'hello', u'there']
+ test_bad_contents = [10, u'hello']
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(TextDAVWidgetTest),
+ makeSuite(DatetimeDAVWidgetTest),
+ makeSuite(XMLEmptyElementListDAVWidgetTest),
+ DocTestSuite('zope.app.dav.widget'),
+ ))
+
+if __name__ == '__main__':
+ main(defaultTest = 'test_suite')
Property changes on: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/test_widget.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/unitfixtures.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/unitfixtures.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/tests/unitfixtures.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -17,6 +17,7 @@
"""
__docformat__ = 'restructuredtext'
+from BTrees.OOBTree import OOBTree
from persistent import Persistent
from zope.interface import implements
@@ -37,17 +38,25 @@
self.level=level
self.__parent__ = parent
- def items(self):
- if self.level == 2:
- return (('last', File('last', 'text/plain', 'blablabla', self)),)
- result = []
+ self.data = OOBTree()
+ if level in (0, 1):
+ self._setUp()
+ else:
+ self.data['last'] = File('last', 'text/plain', 'blablabla', self)
+
+ def _setUp(self):
for i in range(1, 3):
- result.append((str(i),
- File(str(i), 'text/plain', 'blablabla', self)))
- result.append(('sub1',
- Folder('sub1', level=self.level+1, parent=self)))
- return tuple(result)
+ self.data[str(i)] = File(str(i), 'text/plain', 'blablabla', self)
+ sub1 = Folder('sub1', level = self.level + 1, parent = self)
+ self.data['sub1'] = sub1
+ def items(self):
+ items = list(self.data.items())
+ items.sort()
+
+ return tuple(items)
+
+
class File(zope.app.location.Location, Persistent):
implements(IWriteFile, IFile)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/dav/widget.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -19,69 +19,398 @@
from xml.dom import minidom
+from zope.interface import implements
+from zope.schema import getFieldNamesInOrder
+from zope.schema.interfaces import ValidationError
+
+from zope.app.datetimeutils import parseDatetimetz, DateTimeError
from zope.app.dav.interfaces import IDAVWidget
-from zope.app.dav.interfaces import ITextDAVWidget
-from zope.app.dav.interfaces import ISequenceDAVWidget
-from zope.app.dav.interfaces import IXMLDAVWidget
+from zope.app.dav.opaquenamespaces import makeDOMStandalone
from zope.app.form import InputWidget
-from zope.interface import implements
+from zope.app.form.utility import setUpWidget
+from zope.app.form.interfaces import MissingInputError, ConversionError, \
+ WidgetsError, WidgetInputError
+from zope.publisher.browser import TestRequest
+
class DAVWidget(InputWidget):
-
+ """ DAV Widget """
implements(IDAVWidget)
+ _missing_value = object()
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.name = context.__name__
+
+ self._xmldoc = minidom.Document()
+ # field value
+ self._value = self._missing_value
+
+ self._error = None
+
+ self.namespace = None
+ self.ns_prefix = None
+
+ def getErrors(self):
+ return self._error
+
+ def setNamespace(self, ns, ns_prefix):
+ self.namespace = ns
+ self.ns_prefix = ns_prefix
+
+ def setRenderedValue(self, value):
+ self._value = value
+
+ def applyChanges(self, content):
+ return super(DAVWidget, self).applyChanges(content)
+
def hasInput(self):
- return True
+ return self._value is not self._missing_value
+ def _getAndValidateInput(self):
+ """get the input value contained within this widget in a valid format
+ """
+ field = self.context
+
+ # form input is required, otherwise raise an error
+ if not self.hasInput():
+ raise MissingInputError(self.name, self.label, None)
+
+ value = self._value
+
+ # allow missing values only for non-required fields
+ if value == field.missing_value: ## and not field.required:
+ ## XXX - required is set all my fields and hence this is causing
+ ## a problem.
+ return value
+
+ # value must be valid per the field constraints
+ field.validate(value)
+
+ return value
+
+ def hasValidInput(self):
+ try:
+ self._getAndValidateInput()
+ return True
+ except (ConversionError, ValidationError):
+ return False
+
def getInputValue(self):
- return self._data
+ self._error = None
- def __str__(self):
- return str(self._data)
+ try:
+ value = self._getAndValidateInput()
+ except ConversionError, error:
+ self._error = error
+ raise self._error
+ except ValidationError, error:
+ self._error = WidgetInputError(
+ self.context.__name__, self.label, error)
+ raise self._error
- def __call__(self):
- return str(self)
-
- def setRenderedValue(self, value):
- if isinstance(value, minidom.Node):
- text = u''
- for node in value.childNodes:
- if node.nodeType != node.TEXT_NODE:
- continue
- text += node.nodeValue
- value = text
-
- super(DAVWidget, self).setRenderedValue(value)
+ return value
+ def _toDAVValue(self, value):
+ """Converts a field value to a string or a DOM Fragment to be used
+ within the response of a WebDAV Request. This method can aslo return
+ a lsit or tuple of DOM Fragments which can be used to
+
+ This method should be extended in order to correctly generate and
+ take action on WebDAV requests.
+ """
+ if value:
+ return str(value)
+ return None
+
+ def renderProperty(self, ns, ns_prefix):
+ self.setNamespace(ns, ns_prefix)
+
+ el = self._xmldoc.createElementNS(ns, self.name)
+ if ns_prefix is not None:
+ el.setAttributeNS(ns, 'xmlns', ns_prefix)
+
+ # this is commented out because it cased some problems with values
+ # being security proxied when they they are returned from the adapters
+ ## value = self._toDAVValue(self.getInputValue())
+ value = self._toDAVValue(self._value)
+
+ if not value:
+ return el
+ elif not isinstance(value, tuple) and not isinstance(value, list):
+ value = [value]
+
+ for val in value:
+ if isinstance(val, str) or isinstance(val, unicode):
+ val = self._xmldoc.createTextNode(val)
+ elif not isinstance(val, minidom.Node):
+ raise WidgetsError(self._error, {self.name: val})
+
+ el.appendChild(
+ el.ownerDocument.importNode(val, True))
+
+ return el
+
+ def _setFieldValue(self, value):
+ text = u''
+ for node in value.childNodes:
+ if node.nodeType != node.TEXT_NODE:
+ continue
+ text += node.nodeValue
+ return text
+
+ def setProperty(self, propel):
+ self._value = self._setFieldValue(propel)
+
+
class TextDAVWidget(DAVWidget):
+ """
+ Renders a WebDAV property that contains text.
- implements(ITextDAVWidget)
+ >>> from zope.schema import Text
+ >>> field = Text(__name__ = 'foo', title = u'Foo Title')
+ >>> request = TestRequest()
+ >>> widget = TextDAVWidget(field, request)
+ Set up the value stored in the widget. In reality this is done in the
+ setUpWidget method
+
+ >>> widget.setRenderedValue(u'This is some content')
+ >>> rendered = widget.renderProperty(None, None)
+ >>> rendered #doctest:+ELLIPSIS
+ <DOM Element: foo at 0x...>
+ >>> rendered.toxml()
+ '<foo>This is some content</foo>'
+
+ """
+ pass
+
+
+class DatetimeDAVWidget(DAVWidget):
+ """Render a WebDAV date property
+
+ >>> from zope.schema import Datetime
+ >>> field = Datetime(__name__ = 'foo', title = u'Foo Date Title')
+ >>> request = TestRequest()
+ >>> widget = DatetimeDAVWidget(field, request)
+
+ Set the value of the widget to that of the current time. Use
+ utcfromtimestamp in order for the test to pass in difference time zones.
+
+ >>> from datetime import datetime
+ >>> date = datetime.utcfromtimestamp(1131233651)
+ >>> widget.setRenderedValue(date)
+ >>> rendered = widget.renderProperty(None, None)
+ >>> rendered #doctest:+ELLIPSIS
+ <DOM Element: foo at ...>
+ >>> rendered.toxml() # this was '<foo>2005-11-05 23:34:11Z</foo>'
+ '<foo>Sat, 05 Nov 2005 23:34:11</foo>'
+
+ """
+
+ def _setFieldValue(self, value):
+ if not value.childNodes:
+ return self.context.missing_value
+
+ value = value.childNodes[0]
+
+ try:
+ return parseDatetimetz(value.nodeValue)
+ except (DateTimeError, ValueError, IndexError), v:
+ raise ConversionError("Invalid datetime data", v)
+
+ def _toDAVValue(self, value):
+ if value is None:
+ return None
+ return value.strftime('%a, %d %b %Y %H:%M:%S %z').rstrip()
+
+
+class CreatationDateDAVWidget(DAVWidget):
+ """Render a WebDAV date property
+
+ >>> from zope.schema import Datetime
+ >>> field = Datetime(__name__ = 'foo', title = u'Foo Date Title')
+ >>> request = TestRequest()
+ >>> widget = CreatationDateDAVWidget(field, request)
+
+ Set the value of the widget to that of the current time.
+
+ >>> from datetime import datetime
+ >>> date = datetime.utcfromtimestamp(1131233651)
+ >>> widget.setRenderedValue(date)
+ >>> rendered = widget.renderProperty(None, None)
+ >>> rendered #doctest:+ELLIPSIS
+ <DOM Element: foo at ...>
+ >>> rendered.toxml()
+ '<foo>2005-11-05T23:34:11Z</foo>'
+
+ """
+
+ def _setFieldValue(self, value):
+ raise TypeError, "the creetion date is read-only"
+
+ def _toDAVValue(self, value):
+ if value is None:
+ return None
+ return value.strftime('%Y-%m-%dT%TZ')
+
+
+class DateDAVWidget(DatetimeDAVWidget):
+ """Render a WebDAV date property
+
+ >>> from zope.schema import Datetime
+ >>> field = Datetime(__name__ = 'foo', title = u'Foo Date Title')
+ >>> request = TestRequest()
+ >>> widget = DatetimeDAVWidget(field, request)
+
+ Set the value of the widget to that of the current time.
+
+ >>> from datetime import datetime
+ >>> date = datetime.utcfromtimestamp(1131233651)
+ >>> widget.setRenderedValue(date)
+ >>> rendered = widget.renderProperty(None, None)
+ >>> rendered #doctest:+ELLIPSIS
+ <DOM Element: foo at ...>
+ >>> rendered.toxml() # this was '<foo>2005-11-05 23:34:11Z</foo>'
+ '<foo>Sat, 05 Nov 2005 23:34:11</foo>'
+
+ """
+
+ def _setFieldValue(self, value):
+ if not value.childNodes:
+ return self.context.missing_value
+
+ value = value.childNodes[0]
+
+ try:
+ return parseDatetimetz(value.nodeValue).date()
+ except (DateTimeError, ValueError, IndexError), v:
+ raise ConversionError("Invalid date data", v)
+
+
class SequenceDAVWidget(DAVWidget):
- implements(ISequenceDAVWidget)
+ def _toDAVValue(self, value):
+ if not value:
+ return None
+ string = ', '.join(value)
+ return self._xmldoc.createTextNode(string)
- def __str__(self):
- return u', '.join(self._data)
+ def _setFieldValue(self, value):
+ text = u''
+ for node in value.childNodes:
+ if node.nodeType != node.TEXT_NODE:
+ continue
+ text += node.nodeValue
+ return text.split(', ')
+
+
+class ListDAVWidget(SequenceDAVWidget):
+ pass
+
+
+class TupleDAVWidget(SequenceDAVWidget):
+
+ def _setFieldValue(self, value):
+ value = super(TupleDAVWidget, self)._setFieldValue(value)
+
+ return tuple(value)
+
+
+class XMLEmptyElementListDAVWidget(DAVWidget):
+ """
+ This is a read-only widget.
- def getInputValue(self):
- return [v.strip() for v in self._data.split(',')]
+ >>> from zope.schema import List
+ >>> field = List(__name__ = 'foo', title = u'Foo Title')
+ >>> request = TestRequest()
+ >>> widget = XMLEmptyElementListDAVWidget(field, request)
+ >>> widget.setRenderedValue(['first', 'second'])
+ >>> rendered = widget.renderProperty(None, None)
+ >>> rendered #doctest:+ELLIPSIS
+ <DOM Element: foo at ...>
+ >>> rendered.toxml()
+ '<foo><first/><second/></foo>'
+ """
-class XMLDAVWidget(DAVWidget):
+ def _toDAVValue(self, value):
+ if not value:
+ return None
- implements(IXMLDAVWidget)
+ res = []
+ for item in value:
+ el = self._xmldoc.createElementNS(self.namespace, str(item))
+ res.append(el)
+ return res
- def getInputValue(self):
- return self._data
+ def _setFieldValue(self, value):
+ fvalue = []
+ for node in value.childNodes:
+ if node.nodeType != node.ELEMENT_NODE:
+ continue
+ fvalue.append(node.localName)
+ return fvalue
- def __str__(self):
- raise ValueError("xmldavwidget is not a string.")
- def __call__(self):
- return self._data
+class DAVXMLSubPropertyWidget(DAVWidget):
- def setRenderedValue(self, value):
- if not isinstance(value, minidom.Node):
- value = ''
- self._data = value
+ def _toDAVValue(self, value):
+ if value == None:
+ return None
+
+ mfield = self.context
+
+ iface = mfield.schema
+ pname = mfield.prop_name
+ if pname:
+ res = self._xmldoc.createElementNS(self.namespace, pname)
+ else:
+ res = []
+
+ for fieldname in getFieldNamesInOrder(iface):
+ field = iface[fieldname]
+ setUpWidget(self, fieldname, field, IDAVWidget,
+ value = field.get(value),
+ ignoreStickyValues = False)
+ widget = getattr(self, fieldname + '_widget', None)
+ assert widget is not None
+ ## namespace information is not needed here since we set the
+ ## default namespace on elements
+ el = widget.renderProperty(self.namespace, self.ns_prefix)
+ if pname:
+ res.appendChild(el)
+ else:
+ res.append(el)
+
+ return res
+
+
+class DAVOpaqueWidget(DAVWidget):
+
+ def renderProperty(self, ns, ns_prefix):
+ self.setNamespace(ns, ns_prefix)
+
+ value = self.getInputValue()
+ if value == self.context.missing_value:
+ el = self._xmldoc.createElementNS(ns, self.name)
+ if ns_prefix is not None:
+ el.setAttributeNS(ns, 'xmlns', ns_prefix)
+ return el
+ el = minidom.parseString(value)
+
+ el = el.documentElement
+
+ if ns_prefix is not None and el.attributes is not None:
+ xmlns = el.attributes.getNamedItem('xmlns')
+ if xmlns is None:
+ el.setAttributeNS(ns, 'xmlns', ns_prefix)
+
+ return el
+
+ def _setFieldValue(self, value):
+ el = makeDOMStandalone(value)
+
+ return el.toxml()
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/http/options.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/http/options.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/http/options.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -37,6 +37,7 @@
# are not in the lists above.
for m in _allowed_methods:
view = zapi.queryMultiAdapter((self.context, self.request), name=m)
+ view = getattr(view, m, None)
if view is not None:
allowed.append(m)
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/locking/configure.zcml
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/locking/configure.zcml 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/locking/configure.zcml 2006-01-10 21:36:58 UTC (rev 41259)
@@ -1,4 +1,5 @@
<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="zope.app.locking">
<!-- Registering documentation with API doc -->
@@ -27,13 +28,18 @@
<class class=".lockinfo.LockInfo">
<allow attributes="getLockInfo" />
+
<require permission="zope.View"
- attributes="principal_id created timeout" />
+ attributes="principal_id created timeout" />
+
+ <require permission="zope.ManageContent"
+ set_attributes="timeout" />
+
<require permission="zope.View"
- interface="zope.interface.common.mapping.IEnumerableMapping"
+ interface="zope.interface.common.mapping.IEnumerableMapping"
/>
<require permission="zope.View"
- interface="zope.interface.common.mapping.IWriteMapping"
+ interface="zope.interface.common.mapping.IWriteMapping"
/>
</class>
@@ -65,4 +71,28 @@
/>
</class>
+ <localUtility class=".storage.PersistentLockStorage">
+ <factory
+ id="zope.app.locking.storage.PersistentLockStorage"
+ />
+
+ <require
+ permission="zope.ManageContent"
+ interface=".interfaces.ILockStorage"
+ />
+ </localUtility>
+
+ <browser:tool
+ interface="zope.app.locking.interfaces.ILockStorage"
+ title="Lock Storage"
+ description=""
+ />
+
+ <browser:addMenuItem
+ title="Lock Storage"
+ description=""
+ class="zope.app.locking.storage.PersistentLockStorage"
+ permission="zope.ManageSite"
+ />
+
</configure>
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/locking/storage.py
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/locking/storage.py 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/locking/storage.py 2006-01-10 21:36:58 UTC (rev 41259)
@@ -28,6 +28,7 @@
from zope.app.keyreference.interfaces import IKeyReference
from zope.app.locking import interfaces
+from zope.app.location.interfaces import ILocation
# for backwards compatibility:
from zope.app.locking.interfaces import ILockStorage
from zope.app.size.interfaces import ISized
@@ -176,3 +177,7 @@
# This is the class that should generally be used with Zope 3.
# Alternate subclasses can be used, but LockStorage can't be used
# directly. Subclasses are responsible for providing persistence.
+
+ interface.implements(ILocation)
+
+ __parent__ = __name__ = None
Modified: Zope3/branches/mkerrin-webdav/src/zope/app/publication/methodnotallowed.txt
===================================================================
--- Zope3/branches/mkerrin-webdav/src/zope/app/publication/methodnotallowed.txt 2006-01-10 20:16:15 UTC (rev 41258)
+++ Zope3/branches/mkerrin-webdav/src/zope/app/publication/methodnotallowed.txt 2006-01-10 21:36:58 UTC (rev 41259)
@@ -8,7 +8,7 @@
... FROG / HTTP/1.1
... """)
HTTP/1.1 405 Method Not Allowed
- Allow: DELETE, MKCOL, OPTIONS, PROPFIND, PROPPATCH, PUT
+ Allow: COPY, DELETE, MKCOL, OPTIONS, PROPFIND, PROPPATCH, PUT
Content-Length: 18
<BLANKLINE>
Method Not Allowed
More information about the Zope3-Checkins
mailing list