[Checkins] SVN: zope.webdav/trunk/src/zope/webdav/ Initial import
of zope.webdav.
Michael Kerrin
michael.kerrin at openapp.biz
Wed Aug 23 16:21:49 EDT 2006
Log message for revision 69736:
Initial import of zope.webdav.
It contains support for all WebDAV methods including
LOCK and UNLOCK, and all the properites defined in
draft-ietf-webdav-rfc2518bis-15.txt
It also contains a new mechanism for specifing the
properties defined on a resource - see datamodel.txt
for more info on this.
Changed:
A zope.webdav/trunk/src/zope/webdav/README.txt
A zope.webdav/trunk/src/zope/webdav/SETUP.cfg
A zope.webdav/trunk/src/zope/webdav/TODO.txt
A zope.webdav/trunk/src/zope/webdav/__init__.py
A zope.webdav/trunk/src/zope/webdav/adapters.py
A zope.webdav/trunk/src/zope/webdav/configure.zcml
A zope.webdav/trunk/src/zope/webdav/copymove.py
A zope.webdav/trunk/src/zope/webdav/coreproperties.py
A zope.webdav/trunk/src/zope/webdav/datamodel.txt
A zope.webdav/trunk/src/zope/webdav/deadproperties.py
A zope.webdav/trunk/src/zope/webdav/exceptions/
A zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
A zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt
A zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
A zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
A zope.webdav/trunk/src/zope/webdav/exceptions/ftests/
A zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
A zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
A zope.webdav/trunk/src/zope/webdav/exceptions/tests/
A zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
A zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
A zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
A zope.webdav/trunk/src/zope/webdav/ftesting.zcml
A zope.webdav/trunk/src/zope/webdav/ftests/
A zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
A zope.webdav/trunk/src/zope/webdav/ftests/dav.py
A zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
A zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
A zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
A zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
A zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
A zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
A zope.webdav/trunk/src/zope/webdav/ietree.py
A zope.webdav/trunk/src/zope/webdav/interfaces.py
A zope.webdav/trunk/src/zope/webdav/locking.py
A zope.webdav/trunk/src/zope/webdav/lockingutils.py
A zope.webdav/trunk/src/zope/webdav/mkcol.py
A zope.webdav/trunk/src/zope/webdav/properties.py
A zope.webdav/trunk/src/zope/webdav/propfind.py
A zope.webdav/trunk/src/zope/webdav/proppatch.py
A zope.webdav/trunk/src/zope/webdav/publisher.py
A zope.webdav/trunk/src/zope/webdav/testing.py
A zope.webdav/trunk/src/zope/webdav/tests/
A zope.webdav/trunk/src/zope/webdav/tests/__init__.py
A zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
A zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
A zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
A zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
A zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
A zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
A zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
A zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
A zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
A zope.webdav/trunk/src/zope/webdav/utils.py
A zope.webdav/trunk/src/zope/webdav/widgets.py
A zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
A zope.webdav/trunk/src/zope/webdav/zetree.py
A zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
A zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml
-=-
Added: zope.webdav/trunk/src/zope/webdav/README.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/README.txt 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/README.txt 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,88 @@
+============
+Introduction
+============
+
+The *zope.webdav* package is an implementation of the WebDAV protocol.
+It includes support for all the different methods, properties and error codes
+has defined in RFC2518 and draft-ietf-webdav-rfc2518bis-15.txt.
+
+Installation
+============
+
+This module should be installed like any other Zope3 module, that is it should
+be copied verbatim into your Python path and all zcml slugs should be installed
+into the *package-includes* directory. The zcml slugs for this package include
+*zope.webdav-configure.zcml* and *ietree-configure.zcml*. If you want to run
+the functional tests successfully then you must also install the
+*zope.webdav-ftesting.zcml* file.
+
+Now the *ietree-configure.zcml* file tells Zope what ElementTree engine you
+want to use to process the XML. You will probably want to edit this file to
+use the elementtree implementation that is installed on your system. There are
+two supported implementations, ElementTree and lxml. To use ElementTree
+just make sure that the utility declaration for *zope.webdav.zetree.EtreeEtree*
+is the only uncommented utility declaration. For lxml just make sure that
+*zope.webdav.zetree.LxmlEtree* is the only uncommented utility declaration.
+
+Data Model
+==========
+
+Read how to extend the `WebDAV Data Model`_ by defining your own properties.
+
+XML Processing
+==============
+
+WebDAV uses XML for conveying property names and values to and from the client.
+This package uses ElementTree API for processing this XML. Because of this all
+property names must be strings that conform to the syntax of ElementTree's
+element name (or tag). For example, for the property *getcontenttype* which
+belongs to the *DAV:* XML namespace will be referenced by the name
+*{DAV:}getcontenttype* within *zope.webdav*.
+
+Error Handling
+==============
+
+WebDAV defines new HTTP status codes in order convey more meaningful
+information about what just happened back to the client. The WebDAV
+specification also defines a multi-status response (with HTTP status code
+207) for situations where multiple status codes are more appropriate. For
+example the PROPPATCH method can modify multiple properties with multiple
+degrees of success. For each property we can say if the property update
+succeeded, failed, the property did not exists, or the user did not have the
+appropriate permissions to modify that property. To deal with this we have
+defined new exceptions that should be raised if a request can not be processed
+for a specific reason.
+
+When a multi-status response is desirable then *zope.webdav* will encapsulate
+all the errors that occurred during the request into either a
+*webdav.interfaces.WebDAVErrors* or a *webdav.interfaces.WebDAVPropstatErrors*
+exception and throw this error to the publisher. The publisher will then abort
+the current transaction and look up and render a *IHTTPException* view of this
+error.
+
+This view will generally be either *webdav.exceptions.MultiStatusErrorView* or
+*webdav.exceptions.WebDAVPropstatErrorView*. Both these views work by looping
+over all exceptions contained within the error and for each error it looks up
+a *webdav.interfaces.IDAVErrorWidget* which will contain all the information
+necessary to build up the final multi-status XML element which is sent back
+to the client.
+
+Each of these new exception implemented a derived interface of the
+*webdav.interfaces.IDAVException* interface (although this isn't necessary).
+As of writing the following exceptions can occur:
+
++ *webdav.interfaces.ConflictError*
+
++ *webdav.interfaces.ForbiddenError*
+
++ *webdav.interfaces.UnprocessableError*
+
++ *webdav.interfaces.PropertyNotFound*
+
++ *webdav.interfaces.PreconditionFailed*
+
++ *webdav.interfaces.FailedDependency*
+
++ *webdav.interfaces.AlreadyLocked*
+
+.. _WebDAV Data Model: zope.webdav.datamodel/@@show.html
Property changes on: zope.webdav/trunk/src/zope/webdav/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/SETUP.cfg
===================================================================
--- zope.webdav/trunk/src/zope/webdav/SETUP.cfg 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/SETUP.cfg 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,7 @@
+# Tell zpkg how to install the ZCML slugs.
+
+<data-files zopeskel/etc/package-includes>
+ zope.webdav-configure.zcml
+ zope.webdav-ftesting.zcml
+ ietree-configure.zcml
+</data-files>
Added: zope.webdav/trunk/src/zope/webdav/TODO.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/TODO.txt 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/TODO.txt 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,81 @@
+====
+TODO
+====
+
+Miscellaneous
+=============
+
+The *zope.webdav* package needs to be split up into *zope.webdav* and
+*zope.app.webdav*. This is because there are a lot of stuff in here that
+is Zope3 specific and when migrating this to Zope2 we don't need them. I
+started this process but gave up after a while. Hence we have the
+z3-configure.zcml file. This should be a basis for *zope.app.webdav*.
+
+The ElemenetTree support should to be moved out into its own project, has
+it is probable of interest to others.
+
+We need better integration with Zope3. That includes providing better support
+for caching (ETags), and all the "IF*" headers defined in the HTTP and WebDAV
+specs. Also the exceptions and there views are not neccessarly WebDAV specific
+so they could maybe be moved into there own package. So developers of other
+related packages can re-use them. Note that this will probable require some
+rewrite of the current exceptions if this is ever going to happen.
+
+Error Handling
+--------------
+
+- write a error widget for the Forbidden exception.
+
+- more documentation.
+
+- I added a `propertyname' to the IDAVException interface. This seems a bit
+ random now and should be taught over.
+
+- Add support for setting the pre/post condition XML elements within a
+ a error multi-status view. See section 16 in
+ draft-ietf-webdav-rfc2518bis-15.txt. Still will probable result in spliting
+ up the webdav exceptions. For example AlreadyLocked breaks up into two
+ exceptions. One for when the the object is locked and the other for when
+ the object is locked but the supplied lock request uri doesn't match the
+ current lock token request uri annotation.
+
+- Finish the support for setting the responsedescription XML element within
+ the multi-status elements.
+
+Locking
+-------
+
+- update the timeout header parsing to support multiple arguments.
+
+- shared lock tokens need testing.
+
+- test locking default timeout - this is an application specific problem. So
+ the IDAVLockmanager should be dealing with accepting or rejecting the
+ requested timeout.
+
+- lock on unmapped urls doesn't work yet. Zope3 will need to be changed
+ to get this to work.
+
+- more unit tests. Names on lockingutils.DAVActiveLock,
+ lockingutils.DAVLockdiscovery. In locking the UNLOCK, LOCK method needs
+ testing.
+
+Misc
+----
+
+- Unicode handling - make sure that this works has it is supposed to.
+
+- allow configuring certain parts of the WebDAV protocol like default timeouts
+ on locks, and allowing depth = infinity on PROPFIND requests via ZCML.
+
+- the option request handler needs to be fixed.
+
+- propfind, proppatch - if any property update, view goes horrible wrong then
+ a 500 status code needs to be updated.
+
+- egg support
+
+Finally
+=======
+
+XXX's - all these need to be removed.
Property changes on: zope.webdav/trunk/src/zope/webdav/TODO.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""WebDAV support for Zope.
+
+$Id$
+"""
Property changes on: zope.webdav/trunk/src/zope/webdav/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/adapters.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/adapters.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/adapters.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,189 @@
+##############################################################################
+# Copyright (c) 2003 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.
+##############################################################################
+"""Basic WebDAV storage adapters for Files and Folders content objects.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+import zope.publisher.interfaces.http
+from zope.dublincore.interfaces import IDCTimes, IDCDescriptiveProperties
+from zope.annotation.interfaces import IAnnotatable
+import zope.app.file.interfaces
+
+import zope.webdav.coreproperties
+
+
+class DAVSchemaAdapter(object):
+ """
+ >>> from zope.app.file.file import File
+ >>> file = File('some data for a file', 'text/plain')
+ >>> adapter = DAVSchemaAdapter(file, None)
+ >>> adapter.displayname is None
+ True
+ >>> adapter.creationdate is None
+ True
+ >>> adapter.displayname = u'Test File Title'
+ Traceback (most recent call last):
+ ...
+ ValueError
+ >>> adapter.getlastmodified is None
+ True
+
+ >>> from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+ >>> from zope.dublincore.interfaces import IWriteZopeDublinCore
+ >>> from zope.annotation.attribute import AttributeAnnotations
+ >>> from zope.annotation.interfaces import IAnnotations
+ >>> from zope.annotation.interfaces import IAttributeAnnotatable
+ >>> import datetime
+ >>> from zope.datetime import tzinfo
+
+ >>> interface.classImplements(File, IAttributeAnnotatable)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... AttributeAnnotations)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+
+ >>> file = File('some data for a file', 'text/plain')
+ >>> IWriteZopeDublinCore(file).created = now = datetime.datetime(
+ ... 2006, 5, 24, 0, 0, 58, tzinfo = tzinfo(60))
+ >>> IWriteZopeDublinCore(file).title = u'Example Test File'
+ >>> IWriteZopeDublinCore(file).modified = now = datetime.datetime(
+ ... 2006, 5, 24, 0, 0, 58, tzinfo = tzinfo(60))
+
+ >>> adapter = DAVSchemaAdapter(file, None)
+ >>> adapter.creationdate == now
+ True
+ >>> adapter.displayname
+ u'Example Test File'
+ >>> adapter.getlastmodified == now
+ True
+ >>> adapter.displayname = u'Changed Test File Title'
+ >>> IWriteZopeDublinCore(file).title
+ u'Changed Test File Title'
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... AttributeAnnotations, provided = IAnnotations)
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+ True
+
+ """
+ interface.implements(zope.webdav.coreproperties.IDAVCoreSchema)
+ component.adapts(IAnnotatable,
+ zope.publisher.interfaces.http.IHTTPRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ @property
+ def creationdate(self):
+ dc = IDCTimes(self.context, None)
+ if dc is None or dc.created is None:
+ return None
+ return dc.created
+
+ @apply
+ def displayname():
+ def get(self):
+ descproperties = IDCDescriptiveProperties(self.context, None)
+ if descproperties is None:
+ return None
+ return descproperties.title
+ def set(self, value):
+ descproperties = IDCDescriptiveProperties(self.context, None)
+ if descproperties is None:
+ raise ValueError("")
+ descproperties.title = value
+ return property(get, set)
+
+ @property
+ def getlastmodified(self):
+ dc = IDCTimes(self.context, None)
+ if dc is None or dc.modified is None:
+ return None
+ return dc.modified
+
+
+class DAVFileGetSchema(object):
+ """
+ >>> from zope.app.file.file import File
+ >>> from zope.interface.verify import verifyObject
+ >>> file = File('some data for the file', 'text/plain')
+ >>> adapter = DAVFileGetSchema(file, None)
+ >>> verifyObject(zope.webdav.coreproperties.IDAVGetSchema, adapter)
+ True
+ >>> adapter.getcontentlanguage is None
+ True
+ >>> adapter.getcontentlength
+ 22
+ >>> adapter.getcontenttype
+ 'text/plain'
+
+ >>> from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+ >>> from zope.dublincore.interfaces import IWriteZopeDublinCore
+ >>> from zope.annotation.attribute import AttributeAnnotations
+ >>> from zope.annotation.interfaces import IAnnotations
+ >>> from zope.annotation.interfaces import IAttributeAnnotatable
+ >>> from zope.datetime import tzinfo
+ >>> import datetime
+ >>> interface.classImplements(File, IAttributeAnnotatable)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... AttributeAnnotations)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+
+ >>> file = File('some data for the file', 'text/plain')
+ >>> adapter = DAVFileGetSchema(file, None)
+
+ >>> adapter.getcontentlanguage is None
+ True
+ >>> adapter.getcontentlength
+ 22
+ >>> adapter.getcontenttype
+ 'text/plain'
+ >>> adapter.getetag is None # etag is None for now.
+ True
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... AttributeAnnotations, provided = IAnnotations)
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... ZDCAnnotatableAdapter, provided = IWriteZopeDublinCore)
+ True
+
+ """
+ interface.implements(zope.webdav.coreproperties.IDAVGetSchema)
+ component.adapts(zope.app.file.interfaces.IFile,
+ zope.publisher.interfaces.http.IHTTPRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ @property
+ def getcontentlanguage(self):
+ return None
+
+ @property
+ def getetag(self):
+ return None
+
+ @property
+ def getcontentlength(self):
+ return self.context.getSize()
+
+ @property
+ def getcontenttype(self):
+ return self.context.contentType
Property changes on: zope.webdav/trunk/src/zope/webdav/adapters.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/configure.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/configure.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,248 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="zope.app.dav">
+
+ <include package="zope.webdav.exceptions" />
+
+ <!--
+ Support for integrating the protocol request handlers found in
+ the webdav package with the Zope3 publisher.
+ -->
+ <publisher
+ name="WEBDAV"
+ factory=".publisher.WebDAVRequestFactory"
+ methods="PROPFIND PROPPATCH MKCOL LOCK UNLOCK COPY MOVE"
+ priority="30"
+ />
+
+ <adapter
+ factory=".propfind.PROPFIND"
+ name="PROPFIND"
+ />
+
+ <adapter
+ factory=".proppatch.PROPPATCH"
+ name="PROPPATCH"
+ />
+
+ <adapter
+ factory=".mkcol.NullResource"
+ name="MKCOL"
+ />
+
+ <adapter
+ factory=".copymove.COPY"
+ name="COPY"
+ />
+
+ <adapter
+ factory=".copymove.MOVE"
+ name="MOVE"
+ />
+
+ <adapter
+ for="zope.interface.Interface
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IWebDAVMethod"
+ factory=".locking.LOCK"
+ name="LOCK"
+ />
+
+ <adapter
+ for="zope.interface.Interface
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IWebDAVMethod"
+ factory=".locking.UNLOCK"
+ name="UNLOCK"
+ />
+
+ <!--
+ Collection of display widget definitions.
+ -->
+
+ <adapter
+ for="zope.schema.interfaces.IText
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.TextDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.ITextLine
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.TextDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IInt
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.IntDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IFloat
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.IntDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IDatetime
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.DatetimeDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IDate
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.DatetimeDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.ISequence
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.ListDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IObject
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.ObjectDAVWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IURI
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.TextDAVWidget"
+ />
+
+ <adapter
+ for=".properties.DeadField
+ .interfaces.IWebDAVRequest"
+ factory=".properties.OpaqueWidget"
+ />
+
+ <!--
+ Collection of webdav input widgets definitions
+ -->
+
+ <adapter
+ for="zope.schema.interfaces.IText
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.TextDAVInputWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.ITextLine
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.TextDAVInputWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IInt
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.IntDAVInputWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IFloat
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.FloatDAVInputWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IDatetime
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.DatetimeDAVInputWidget"
+ />
+
+ <adapter
+ for="zope.schema.interfaces.IDate
+ .interfaces.IWebDAVRequest"
+ factory=".widgets.DateDAVInputWidget"
+ />
+
+ <!--
+ Declare all properties managed by this package.
+ -->
+ <utility
+ component=".coreproperties.creationdate"
+ name="{DAV:}creationdate"
+ />
+
+ <utility
+ component=".coreproperties.displayname"
+ name="{DAV:}displayname"
+ />
+
+ <utility
+ component=".coreproperties.getcontentlanguage"
+ name="{DAV:}getcontentlanguage"
+ />
+
+ <utility
+ component=".coreproperties.getcontentlength"
+ name="{DAV:}getcontentlength"
+ />
+
+ <utility
+ component=".coreproperties.getcontenttype"
+ name="{DAV:}getcontenttype"
+ />
+
+ <utility
+ component=".coreproperties.getetag"
+ name="{DAV:}getetag"
+ />
+
+ <utility
+ component=".coreproperties.getlastmodified"
+ name="{DAV:}getlastmodified"
+ />
+
+ <utility
+ component=".coreproperties.lockdiscovery"
+ name="{DAV:}lockdiscovery"
+ />
+
+ <utility
+ component=".coreproperties.resourcetype"
+ name="{DAV:}resourcetype"
+ />
+
+ <utility
+ component=".coreproperties.supportedlock"
+ name="{DAV:}supportedlock"
+ />
+
+ <!--
+ Mandatory minimum storage adapter
+ -->
+ <adapter
+ factory=".coreproperties.ResourceTypeAdapter"
+ />
+
+ <!--
+ Zope3 support for WebDAV
+ -->
+ <include file="z3-configure.zcml" />
+
+ <configure
+ xmlns:zcml="http://namespaces.zope.org/zcml"
+ zcml:condition="have apidoc"
+ xmlns="http://namespaces.zope.org/apidoc">
+
+ <bookchapter
+ id="zope.webdav"
+ title="WebDAV"
+ doc_path="README.txt"
+ />
+
+ <bookchapter
+ id="zope.webdav.datamodel"
+ title="WebDAV Data Model"
+ doc_path="datamodel.txt"
+ parent="zope.webdav"
+ />
+
+ </configure>
+
+</configure>
Property changes on: zope.webdav/trunk/src/zope/webdav/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/copymove.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/copymove.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,164 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""COPY and MOVE support for WebDAV.
+
+This needs to be cleaned up in order to be easily ported to Zope2.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import urlparse
+
+from zope import interface
+from zope import component
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.traversing.api import traverse, getRoot
+from zope.traversing.interfaces import TraversalError
+from zope.traversing.browser import absoluteURL
+from zope.app.publication.http import MethodNotAllowed
+
+import zope.webdav.interfaces
+
+class Base(object):
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def getOverwrite(self):
+ overwrite = self.request.getHeader("overwrite", "t").lower().strip()
+ if overwrite == "t":
+ overwrite = True
+ elif overwrite == "f":
+ overwrite = False
+ else:
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"Invalid overwrite header")
+
+ return overwrite
+
+ def getDestinationPath(self):
+ dest = self.request.getHeader("destination", None)
+ while dest and dest[-1] == "/":
+ dest = dest[:-1]
+ if not dest:
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"No `destination` header sent.")
+
+ scheme, location, destpath, query, fragment = urlparse.urlsplit(dest)
+ # XXX - this is likely to break under virtual hosting.
+ if location and self.request.get("HTTP_HOST", None) is not None and \
+ location != self.request.get("HTTP_HOST"):
+ # This may occur when the destination is on another
+ # server, repository or URL namespace. Either the source namespace
+ # does not support copying to the destination namespace, or the
+ # destination namespace refuses to accept the resource. The client
+ # may wish to try GET/PUT and PROPFIND/PROPPATCH instead.
+ raise zope.webdav.interfaces.BadGateway(self.context, self.request)
+
+ return destpath
+
+ def getDestinationNameAndParentObject(self):
+ """Returns a tuple for destionation name, the parent folder object
+ and a boolean flag indicating wheater the the destinatin object will
+ have to be created or not.
+ """
+ destpath = self.getDestinationPath()
+ try:
+ destob = traverse(getRoot(self.context), destpath)
+ except TraversalError:
+ destob = None
+
+ overwrite = self.getOverwrite()
+
+ parentpath = destpath.split('/')
+ destname = parentpath.pop()
+ try:
+ parent = traverse(getRoot(self.context), parentpath)
+ except TraversalError:
+ raise zope.webdav.interfaces.ConflictError(
+ self.context, message = u"Destination resource has no parent")
+
+ if destob is not None and not overwrite:
+ raise zope.webdav.interfaces.PreconditionFailed(
+ self.context,
+ message = "destination exists and OverWrite header is F")
+ elif destob is not None and destob == self.context:
+ raise zope.webdav.interfaces.ForbiddenError(
+ self.context,
+ message = "destionation and source objects are the same")
+ elif destob is not None:
+ del parent[destname]
+
+ return destname, destob, parent
+
+
+class COPY(Base):
+ """COPY handler for all objects."""
+
+ def COPY(self):
+ copier = IObjectCopier(self.context, None)
+ if copier is None or not copier.copyable():
+ raise MethodNotAllowed(self.context, self.request)
+
+ # get the destination
+ destname, destob, parent = self.getDestinationNameAndParentObject()
+
+ if not copier.copyableTo(parent, destname):
+ # Conflict
+ raise zope.webdav.interfaces.ConflictError(
+ self.context,
+ message = u"can not copy to the destionation folder")
+
+ newdestname = copier.copyTo(parent, destname)
+
+ if destob is not None:
+ self.request.response.setStatus(204)
+ else:
+ self.request.response.setStatus(201)
+ self.request.response.setHeader(
+ "Location", absoluteURL(parent[newdestname], self.request))
+
+ return ""
+
+
+class MOVE(Base):
+ """MOVE handler for all objects."""
+
+ def MOVE(self):
+ mover = IObjectMover(self.context, None)
+ if mover is None or not mover.moveable():
+ raise MethodNotAllowed(self.context, self.request)
+
+ # get the destination
+ destname, destob, parent = self.getDestinationNameAndParentObject()
+
+ if not mover.moveableTo(parent, destname):
+ raise zope.webdav.interfaces.ConflictError(
+ self.context,
+ message = u"can not copy to the destionation folder")
+
+ newdestname = mover.moveTo(parent, destname)
+
+ if destob is not None:
+ self.request.response.setStatus(204)
+ else:
+ self.request.response.setStatus(201)
+ self.request.response.setHeader(
+ "Location", absoluteURL(parent[newdestname], self.request))
+
+ return ""
Property changes on: zope.webdav/trunk/src/zope/webdav/copymove.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/coreproperties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/coreproperties.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/coreproperties.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,344 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Definition of all properties defined in the WebDAV specificiation.
+
+ o readonly -> that the property is protected.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope import schema
+from zope.app.i18n import ZopeMessageFactory as _
+import zope.app.container.interfaces
+import zope.publisher.interfaces.http
+
+from zope.webdav.properties import DAVProperty, DeadField
+import zope.webdav.widgets
+
+class IDAVCreationdate(interface.Interface):
+
+ creationdate = schema.Datetime(
+ title = _("Records the time and date the resource was created."),
+ description = _("""The DAV:creationdate property SHOULD be defined on
+ all DAV compliant resources. If present, it
+ contains a timestamp of the moment when the resource
+ was created. Servers that are incapable of
+ persistently recording the creation date SHOULD
+ instead leave it undefined (i.e. report
+ "Not Found" """),
+ readonly = True)
+
+
+class IDAVDisplayname(interface.Interface):
+
+ displayname = schema.TextLine(
+ title = _("Provides a name for the resource that is suitable for presentation to a user."),
+ description = _("""Contains a description of the resource that is
+ suitable for presentation to a user. This property
+ is defined on the resource, and hence SHOULD have
+ the same value independent of the Request-URI used
+ to retrieve it (thus computing this property based
+ on the Request-URI is deprecated). While generic
+ clients might display the property value to end
+ users, client UI designers must understand that the
+ method for identifying resources is still the URL.
+ Changes to DAV:displayname do not issue moves or
+ copies to the server, but simply change a piece of
+ meta-data on the individual resource. Two resources
+ can have the same DAV:displayname value even within
+ the same collection."""),
+ readonly = False)
+
+
+class IDAVGetcontentlanguage(interface.Interface):
+
+ getcontentlanguage = schema.TextLine(
+ title = _("GET Content-Language header."),
+ description = _("""Contains the Content-Language header value (from
+ Section 14.12 of [RFC2616]) as it would be returned
+ by a GET without accept headers.
+
+ The DAV:getcontentlanguage property MUST be defined
+ on any DAV compliant resource that returns the
+ Content-Language header on a GET."""),
+ readonly = False)
+
+
+class IDAVGetcontentlength(interface.Interface):
+
+ getcontentlength = schema.Int(
+ title = _("Contains the Content-Length header returned by a GET without accept headers."),
+ description = _("""The DAV:getcontentlength property MUST be defined on
+ any DAV compliant resource that returns the
+ Content-Length header in response to a GET."""),
+ readonly = True)
+
+
+class IDAVGetcontenttype(interface.Interface):
+
+ getcontenttype = schema.TextLine(
+ title = _("Contains the Content-Type header value as it would be returned by a GET without accept headers."),
+ description = _("""This property MUST be defined on any DAV compliant
+ resource that returns the Content-Type header in
+ response to a GET."""),
+ readonly = False)
+
+
+class IDAVGetetag(interface.Interface):
+
+ getetag = schema.TextLine(
+ title = _("Contains the ETag header value as it would be returned by a GET without accept headers."),
+ description = _("""The getetag property MUST be defined on any DAV
+ compliant resource that returns the Etag header.
+ Refer to Section 3.11 of RFC2616 for a complete
+ definition of the semantics of an ETag, and to
+ Section 8.6 for a discussion of ETags in WebDAV."""),
+ readonly = True)
+
+
+class IDAVGetlastmodified(interface.Interface):
+
+ getlastmodified = schema.Datetime(
+ title = _("Contains the Last-Modified header value as it would be returned by a GET method without accept headers."),
+ description = _("""Note that the last-modified date on a resource SHOULD
+ only reflect changes in the body (the GET responses)
+ of the resource. A change in a property only SHOULD
+ NOT cause the last-modified date to change, because
+ clients MAY rely on the last-modified date to know
+ when to overwrite the existing body. The
+ DAV:getlastmodified property MUST be defined on any
+ DAV compliant resource that returns the
+ Last-Modified header in response to a GET."""),
+ readonly = True)
+
+
+class ILockEntry(interface.Interface):
+ """A DAV Sub property of the supportedlock property.
+ """
+ lockscope = schema.List(
+ title = u"Describes the exclusivity of a lock",
+ description = u"""Specifies whether a lock is an exclusive lock, or a
+ shared lock.""")
+
+ locktype = schema.List(
+ 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.""")
+
+
+class IActiveLock(ILockEntry):
+ """A DAV Sub property of the lockdiscovery property.
+ """
+ depth = schema.Text(
+ title = u"Depth",
+ description = u"The value of the Depth header.",
+ required = False,
+ readonly = True)
+
+ owner = DeadField(
+ 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 created
+ a lock.
+
+ The value provided MUST be treated as a dead property
+ in terms of XML Information Item preservation. The
+ server MUST NOT alter the value unless the owner
+ value provided by the client is empty.
+ """,
+ required = False,
+ readonly = True)
+
+ timeout = schema.Text(
+ title = u"Timeout",
+ description = u"The timeout associated with a lock",
+ required = False,
+ readonly = True)
+
+ locktoken = schema.List(
+ 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).""",
+ value_type = schema.URI(
+ __name__ = u"href",
+ title = u"Href",
+ description = u"""The href contains a single lock token URI
+ which refers to the lock.""",
+ ),
+ required = False,
+ readonly = True)
+
+ lockroot = schema.URI(
+ title = u"Lock root",
+ description = u"""
+ """,
+ readonly = True,
+ required = True)
+
+
+class IDAVLockdiscovery(interface.Interface):
+
+ lockdiscovery = schema.List(
+ title = _("Describes the active locks on a resource"),
+ description = _("""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. If
+ there are no locks, but the server supports locks,
+ the property will be present but contain zero
+ 'activelock' elements. If there is one or more
+ lock, an 'activelock' element appears for each lock
+ on the resource. This property is NOT lockable with
+ respect to write locks (Section 7)."""),
+ value_type = schema.Object(
+ __name__ = "activelock",
+ title = _(""),
+ description = _(""),
+ schema = IActiveLock,
+ readonly = True),
+ readonly = True)
+
+
+class IDAVResourcetype(interface.Interface):
+
+ resourcetype = schema.List(
+ title = _("Specifies the nature of the resource."),
+ description = _("""MUST be defined on all DAV compliant resources. Each
+ child element identifies a specific type the
+ resource belongs to, such as 'collection', which is
+ the only resource type defined by this specification
+ (see Section 14.3). If the element contains the
+ 'collection' child element plus additional
+ unrecognized elements, it should generally be
+ treated as a collection. If the element contains no
+ recognized child elements, it should be treated as a
+ non-collection resource. The default value is empty.
+ This element MUST NOT contain text or mixed content.
+ Any custom child element is considered to be an
+ identifier for a resource type."""),
+ readonly = False)
+
+
+class IDAVSupportedlock(interface.Interface):
+
+ supportedlock = schema.List(
+ title = _("To provide a listing of the lock capabilities supported by the resource."),
+ description = _("""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.
+ This property is NOT lockable with respect to
+ write locks (Section 7)."""),
+ value_type = schema.Object(
+ __name__ = "lockentry",
+ title = _(""),
+ description = _(""),
+ schema = ILockEntry,
+ readonly = True),
+ readonly = True)
+
+
+class IDAVCoreSchema(IDAVCreationdate,
+ IDAVDisplayname,
+ IDAVGetlastmodified):
+ """Base core properties - note that resourcetype is complusory and is in
+ its own interface.
+ """
+
+
+class IDAVGetSchema(IDAVGetcontentlanguage,
+ IDAVGetcontentlength,
+ IDAVGetcontenttype,
+ IDAVGetetag):
+ """Extended properties that only apply to certain content.
+ """
+
+
+class IDAVLockSupport(IDAVLockdiscovery,
+ IDAVSupportedlock):
+ """
+ """
+
+
+################################################################################
+#
+# Collection of default properties has defined in Section 15.
+# subsection 1 -> 10 of draft-ietf-webdav-rfc2518bis-15.txt
+#
+################################################################################
+
+creationdate = DAVProperty("{DAV:}creationdate", IDAVCreationdate)
+creationdate.custom_widget = zope.webdav.widgets.ISO8601DatetimeDAVWidget
+
+displayname = DAVProperty("{DAV:}displayname", IDAVDisplayname)
+
+getcontentlanguage = DAVProperty("{DAV:}getcontentlanguage",
+ IDAVGetcontentlanguage)
+
+getcontentlength = DAVProperty("{DAV:}getcontentlength", IDAVGetcontentlength)
+
+getcontenttype = DAVProperty("{DAV:}getcontenttype", IDAVGetcontenttype)
+
+getetag = DAVProperty("{DAV:}getetag", IDAVGetetag)
+
+getlastmodified = DAVProperty("{DAV:}getlastmodified", IDAVGetlastmodified)
+
+resourcetype = DAVProperty("{DAV:}resourcetype", IDAVResourcetype)
+
+lockdiscovery = DAVProperty("{DAV:}lockdiscovery", IDAVLockdiscovery)
+
+supportedlock = DAVProperty("{DAV:}supportedlock", IDAVSupportedlock)
+
+################################################################################
+#
+# Default storage adapter for the only mandatory property.
+#
+################################################################################
+
+class ResourceTypeAdapter(object):
+ """
+ >>> from zope.app.file.file import File
+ >>> file = File('some data for a file', 'text/plain')
+ >>> adapter = ResourceTypeAdapter(file, None)
+ >>> adapter.resourcetype is None
+ True
+
+ >>> from zope.app.folder.folder import Folder
+ >>> folder = Folder()
+ >>> adapter = ResourceTypeAdapter(folder, None)
+ >>> adapter.resourcetype
+ [u'collection']
+
+ """
+ interface.implements(IDAVResourcetype)
+ component.adapts(interface.Interface,
+ zope.publisher.interfaces.http.IHTTPRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ @property
+ def resourcetype(self):
+ if zope.app.container.interfaces.IReadContainer.providedBy(
+ self.context):
+ return [u'collection']
+ return None
Property changes on: zope.webdav/trunk/src/zope/webdav/coreproperties.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/datamodel.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/datamodel.txt 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/datamodel.txt 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,380 @@
+=================
+WebDAV Properties
+=================
+
+.. contents::
+
+Introduction
+============
+
+The WebDAV data model consists of properties. Properties are name, value
+pairs whose name is a URI and whose value is a well formed XML fragment. All
+the WebDAV properties are split up into two categories, "live" and "dead"
+properties. Al live property is a property who has either some its semantics
+or syntax enforced by the server. A dead property has its syntax and semantics
+enforced by the client, and the server merely records the value of the property
+verbatim.
+
+Dead properties are just stored, edited or deleted through a adapter
+*zope.webdav.interfaces.IOpaquePropertyStorage* adapter. This just stores the
+data sent through a PROPPATCH request, performing no checks on the value of
+the data sent.
+
+Live properties are declared in advanace by declaring a utility which
+implements *zope.webdav.interfaces.IDAVProperty*. *IAVProperty* is just a data
+structure containing all the information neccessary to process a request.
+This information includes:
+
++ iface - a storage interface used to look up an adapter implementing
+
++ field - used to validate the data sent through PROPPATCH
+
++ restricted -
+
++ custom_widget / custom_input_widget -
+
++ restricted -
+
+This document will describe how Zope maintains and queries the WebDAV data
+model for all content objects within it, and how you has a developer can
+extend this model to include your own properties which can then be rendered
+or modified via the PROPFIND and PROPPATCH methods respectively.
+
+Live properties
+===============
+
+For example will define the live property `{examplens:}age` which might
+describe the age of a resource (or object in Zope). First we need to setup
+the environment and some content. This WebDAV package should do all this
+when the system starts up.
+
+ >>> from zope import interface
+ >>> from zope import schema
+ >>> import zope.schema.interfaces
+ >>> from zope import component
+ >>> import zope.webdav.properties
+ >>> import zope.webdav.interfaces
+ >>> import zope.webdav.publisher
+ >>> import zope.webdav.widgets
+ >>> from cStringIO import StringIO
+ >>> import zope.webdav.testing
+ >>> etree = zope.webdav.testing.etreeSetup()
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... zope.webdav.widgets.IntDAVWidget,
+ ... (zope.schema.interfaces.IInt, zope.webdav.interfaces.IWebDAVRequest),
+ ... zope.webdav.interfaces.IDAVWidget)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... zope.webdav.widgets.TextDAVWidget,
+ ... (zope.schema.interfaces.ITextLine,
+ ... zope.webdav.interfaces.IWebDAVRequest),
+ ... zope.webdav.interfaces.IDAVWidget)
+ >>> class IDemoContent(interface.Interface):
+ ... ageprop = schema.Int(title = u"Age of resource")
+ >>> class DemoContent(object):
+ ... interface.implements(IDemoContent)
+ ... ageprop = 10
+ >>> democontent = DemoContent()
+ >>> request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+
+First we must define an interface that defines how we must store and
+retrieve the property:
+
+ >>> class IAgeStorage(interface.Interface):
+ ... age = schema.Int(
+ ... title = u"Age",
+ ... description = u"Age of the resoure")
+
+Now we must declare and register a named utility implementing *IDAVProperty*.
+
+ >>> ageProp = zope.webdav.properties.DAVProperty("{examplens:}age",
+ ... IAgeStorage)
+ >>> component.getGlobalSiteManager().registerUtility(
+ ... ageProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}age")
+
+It order for it to be defined there must be exist a multi-adapter from the
+resource to *IAgeStorage*. This allows some properties to be defined for a
+one resource and not an other.
+
+Initially for our resource we have no defined properties, since we haven't
+yet implemented the storage adapter.
+
+ >>> list(zope.webdav.properties.getAllProperties(democontent, request))
+ []
+ >>> zope.webdav.properties.hasProperty(
+ ... democontent, request, "{examplens:}age")
+ False
+ >>> zope.webdav.properties.getProperty(
+ ... democontent, request, "{examplens:}age")
+ Traceback (most recent call last):
+ ...
+ PropertyNotFound: {examplens:}age
+
+Now define and register the storage adapter for our `age` property.
+
+ >>> class AgeStorage(object):
+ ... interface.implements(IAgeStorage)
+ ... component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+ ... def __init__(self, context, request):
+ ... self.context = context
+ ... def get_age(self):
+ ... return self.context.ageprop
+ ... age = property(get_age)
+ >>> component.getGlobalSiteManager().registerAdapter(AgeStorage,
+ ... (IDemoContent, zope.webdav.interfaces.IWebDAVRequest), IAgeStorage)
+
+Since we now defined a storage adapter for the `age` property on our
+DemoContent object, the property is defined on this resource.
+
+ >>> zope.webdav.properties.hasProperty(
+ ... democontent, request, "{examplens:}age")
+ True
+
+So now we can ask the *zope.webdav.interfaces.getProperty* method to return the
+*IDAVProperty* utility representing this property and the storage adapter.
+
+ >>> prop, adapter = zope.webdav.properties.getProperty(
+ ... democontent, request, "{examplens:}age")
+ >>> print prop #doctest:+ELLIPSIS
+ <zope.webdav.properties.DAVProperty ...
+ >>> isinstance(adapter, AgeStorage)
+ True
+
+Now the value of this property has stored in Zope is:
+
+ >>> prop.field.get(adapter)
+ 10
+
+In order to render this value has a WebDAV response we ask the
+*zope.webdav.properties.getWidget* method to return a widget that knows how to
+render this type of property.
+
+ >>> davwidget = zope.webdav.properties.getWidget(prop, adapter, request)
+ >>> zope.webdav.testing.assertXMLEqual(davwidget.render(),
+ ... """<E:age xmlns:E="examplens:">10</E:age>""")
+
+Finally the *zope.webdav.properties.getAllProperties* method contains one entry:
+
+ >>> ['%s, %s' %(prop.namespace, prop.__name__) for prop, adapter in
+ ... zope.webdav.properties.getAllProperties(democontent, request)]
+ ['examplens:, age']
+
+Clean up Live Properties Test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By removing the storage adapter the above `age` property disappears.
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(AgeStorage,
+ ... (IDemoContent, zope.webdav.interfaces.IWebDAVRequest), IAgeStorage)
+ True
+
+ >>> prop, adapter = zope.webdav.properties.getProperty(democontent, request,
+ ... "{examplens:}age")
+ Traceback (most recent call last):
+ ...
+ PropertyNotFound: {examplens:}age
+
+Dead Properties
+===============
+
+Dead properties are handled just slightly differently. We first need to setup
+a *zope.webdav.properties.IOpaquePropertyStorage* adapter for our demo content.
+
+ >>> import weakref
+ >>> deadprops = weakref.WeakKeyDictionary()
+ >>> class DeadProperties(object):
+ ... interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+ ... component.adapts(IDemoContent)
+ ... def __init__(self, context):
+ ... self._mapping = deadprops.setdefault(context, {})
+ ... def getAllProperties(self):
+ ... return self._mapping.keys()
+ ... def hasProperty(self, tag):
+ ... return tag in self._mapping
+ ... def getProperty(self, tag):
+ ... return self._mapping.get(tag, None)
+ ... def setProperty(self, tag, value):
+ ... self._mapping[tag] = value
+
+ >>> dpname = "{examplens:}deadprop"
+
+Firstly, we note that the dead property `dpname` doesn't exist because we
+have yet to register our DeadProperties class.
+
+ >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+ False
+ >>> [prop.__name__ for prop, adapter in
+ ... zope.webdav.properties.getAllProperties(democontent, request)]
+ []
+ >>> zope.webdav.properties.getProperty(democontent, request, dpname)
+ Traceback (most recent call last):
+ ...
+ PropertyNotFound: {examplens:}deadprop
+
+ >>> component.getGlobalSiteManager().registerAdapter(DeadProperties)
+
+Since we have defined a *zope.webdav.properties.IOpaquePropertyStorage* adapter,
+we can now set and get any dead properties.
+
+ >>> prop, adapter = zope.webdav.properties.getProperty(
+ ... democontent, request, dpname)
+ >>> prop #doctest:+ELLIPSIS
+ <zope.webdav.properties.OpaqueProperty object at ...
+ >>> prop.__name__
+ 'deadprop'
+ >>> prop.namespace
+ 'examplens:'
+
+But since no data has being stored for the `{examplens:}deadprop` property,
+it really doesn't exist yet even though we can get the property. If we want
+to call `getProperty` but not to generate the property we change the value
+of the keyword arguement `exists` to True.
+
+ >>> zope.webdav.properties.getProperty(
+ ... democontent, request, dpname, exists = True)
+ Traceback (most recent call last):
+ ...
+ PropertyNotFound: {examplens:}deadprop
+
+ >>> list(zope.webdav.properties.getAllProperties(democontent, request))
+ []
+ >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+ False
+
+Now set some data.
+
+ >>> field = prop.field.bind(adapter)
+ >>> field.set(adapter, """<E:deadprop xmlns:E="examplens:">This is some content</E:deadprop>""")
+
+ >>> ['%s, %s' %(prop.namespace, prop.__name__) for prop, adapter in
+ ... zope.webdav.properties.getAllProperties(democontent, request)]
+ ['examplens:, deadprop']
+ >>> zope.webdav.properties.hasProperty(democontent, request, dpname)
+ True
+ >>> prop, adapter = zope.webdav.properties.getProperty(
+ ... democontent, request, dpname)
+ >>> prop is not None
+ True
+
+Grouping Properties
+===================
+
+Instead of writing multiple storage adapters, we can group properties into
+one storage adapter. For example suppose that we have another two live
+properties, name and title and we want to write a single storage adapter
+implementing both these properties. This could be handy if there already
+exists an adapter providing the storage functionality.
+
+ >>> class INameStorage(interface.Interface):
+ ... name = schema.TextLine(
+ ... title = u"Name",
+ ... description = u"Name of the resource")
+ >>> class ITitleStorage(interface.Interface):
+ ... title = schema.TextLine(
+ ... title = u"Title",
+ ... description = u"Title of the resource")
+ >>> nameProp = zope.webdav.properties.DAVProperty(
+ ... "{examplens:}name", INameStorage)
+ >>> titleProp = zope.webdav.properties.DAVProperty("{examplens:}title",
+ ... ITitleStorage)
+ >>> component.getGlobalSiteManager().registerUtility(
+ ... nameProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}name")
+ >>> component.getGlobalSiteManager().registerUtility(
+ ... titleProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}title")
+
+Now write the storage adapter and register with the component architecture.
+
+ >>> class Storage(object):
+ ... component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+ ... interface.implements(INameStorage, ITitleStorage)
+ ... def __init__(self, context, request):
+ ... self.context, self.request = context, request
+ ... def name_get(self):
+ ... return getattr(self.context, 'name', '')
+ ... def name_set(self, value):
+ ... self.context.name = value
+ ... name = property(name_get, name_set)
+ ... def title_get(self):
+ ... return getattr(self.context, 'title', '')
+ ... def title_set(self, value):
+ ... self.contexxt.title = value
+ ... title = property(title_get, title_set)
+
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... Storage, provided = INameStorage)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... Storage, provided = ITitleStorage)
+
+Now when we call either the *getProperty* or the *hasProperty* method for only
+one of the properties, name or title.
+
+ >>> nprop, adapter = zope.webdav.properties.getProperty(democontent, request,
+ ... "{examplens:}name")
+ >>> titleprop, adapter = zope.webdav.properties.getProperty(
+ ... democontent, request, "{examplens:}title")
+
+ >>> zope.webdav.properties.hasProperty(democontent, request,
+ ... "{examplens:}name")
+ True
+
+Alternatively we can do the following, by extending the interfaces and then
+only registering the storage adapter once. First we need to clean up the
+previous tests.
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... Storage, provided = INameStorage)
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... Storage, provided = ITitleStorage)
+ True
+
+ >>> class INameTitleStorage(INameStorage, ITitleStorage):
+ ... """Merge the name and title storage interfaces."""
+ >>> class NameTitleStorage(object):
+ ... component.adapts(IDemoContent, zope.webdav.interfaces.IWebDAVRequest)
+ ... interface.implements(INameTitleStorage)
+ ... def __init__(self, context, request):
+ ... self.context, self.request = context, request
+ ... def name_get(self):
+ ... return getattr(self.context, 'name', '')
+ ... def name_set(self, value):
+ ... self.context.name = value
+ ... name = property(name_get, name_set)
+ ... def title_get(self):
+ ... return getattr(self.context, 'title', '')
+ ... def title_set(self, value):
+ ... self.contexxt.title = value
+ ... title = property(title_get, title_set)
+ >>> component.getGlobalSiteManager().registerAdapter(NameTitleStorage)
+
+ >>> zope.webdav.properties.hasProperty(
+ ... democontent, request, "{examplens:}name")
+ True
+ >>> prop, adapter = zope.webdav.properties.getProperty(democontent, request,
+ ... "{examplens:}name")
+ >>> props = [prop for prop, adapter in
+ ... zope.webdav.properties.getAllProperties(democontent, request)]
+ >>> props = [prop.__name__ for prop in props]
+ >>> props.sort()
+ >>> props
+ ['deadprop', 'name', 'title']
+
+Cleanup Test
+============
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... zope.webdav.widgets.IntDAVWidget,
+ ... (zope.schema.interfaces.IInt, zope.webdav.interfaces.IWebDAVRequest),
+ ... zope.webdav.interfaces.IDAVWidget)
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... zope.webdav.widgets.TextDAVWidget,
+ ... (zope.schema.interfaces.ITextLine,
+ ... zope.webdav.interfaces.IWebDAVRequest),
+ ... zope.webdav.interfaces.IDAVWidget)
+ True
+ >>> component.getGlobalSiteManager().unregisterUtility(
+ ... ageProp, zope.webdav.interfaces.IDAVProperty, "{examplens:}age")
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(DeadProperties)
+ True
+ >>> zope.webdav.testing.etreeTearDown()
Property changes on: zope.webdav/trunk/src/zope/webdav/datamodel.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/deadproperties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/deadproperties.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/deadproperties.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,148 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Support for dead properties.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from BTrees.OOBTree import OOBTree
+
+from zope import interface
+from zope import component
+from zope.annotation.interfaces import IAnnotatable, IAnnotations
+
+import zope.webdav.interfaces
+
+_opaque_namespace_key = "zope.webdav.deadproperties.DAVOpaqueProperties"
+
+class OpaqueProperties(object):
+ """
+ >>> from zope.annotation.attribute import AttributeAnnotations
+ >>> from zope.interface.verify import verifyObject
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... AttributeAnnotations, (IAnnotatable,), IAnnotations)
+
+ Initiial the object contains no dead properties.
+
+ >>> class DemoContent(object):
+ ... interface.implements(IAnnotatable)
+ >>> resource = DemoContent()
+ >>> opaqueProperties = OpaqueProperties(resource)
+ >>> verifyObject(zope.webdav.interfaces.IOpaquePropertyStorage,
+ ... opaqueProperties)
+ True
+ >>> annotations = IAnnotations(resource)
+ >>> list(annotations)
+ []
+ >>> list(opaqueProperties.getAllProperties())
+ []
+
+ `hasProperty` returns None since we haven't set any properties yet.
+
+ >>> opaqueProperties.hasProperty('{example:}testprop')
+ False
+ >>> opaqueProperties.getProperty('{example:}testprop') is None
+ True
+ >>> annotations = IAnnotations(resource)
+ >>> list(annotations)
+ []
+
+ Set the testprop property
+
+ >>> opaqueProperties.setProperty('{examplens:}testprop',
+ ... '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>')
+ >>> annotations = IAnnotations(resource)
+ >>> list(annotations[_opaque_namespace_key])
+ ['{examplens:}testprop']
+ >>> annotations[_opaque_namespace_key]['{examplens:}testprop']
+ '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>'
+ >>> opaqueProperties.hasProperty('{examplens:}testprop')
+ True
+ >>> opaqueProperties.getProperty('{examplens:}testprop')
+ '<E:testprop xmlns:E="examplens:">Test Property Value</E:testprop>'
+ >>> list(opaqueProperties.getAllProperties())
+ ['{examplens:}testprop']
+ >>> opaqueProperties.hasProperty('{examplens:}testbadprop')
+ False
+
+ Now we will remove the property we just set up.
+
+ >>> opaqueProperties.removeProperty('{examplens:}testprop')
+ >>> annotations = IAnnotations(resource)
+ >>> list(annotations[_opaque_namespace_key])
+ []
+
+ Test multiple sets to this value.
+
+ >>> opaqueProperties.setProperty('{examplens:}prop0',
+ ... '<E:prop0 xmlns:E="examplens:">PROP0</E:prop0>')
+ >>> opaqueProperties.setProperty('{examplens:}prop1',
+ ... '<E:prop1 xmlns:E="examplens:">PROP1</E:prop1>')
+ >>> opaqueProperties.setProperty('{examplens:}prop2',
+ ... '<E:prop2 xmlns:E="examplens:">PROP2</E:prop2>')
+ >>> list(opaqueProperties.getAllProperties())
+ ['{examplens:}prop0', '{examplens:}prop1', '{examplens:}prop2']
+
+ >>> opaqueProperties.removeProperty('{examplens:}prop0')
+ >>> opaqueProperties.removeProperty('{examplens:}prop1')
+ >>> list(opaqueProperties.getAllProperties())
+ ['{examplens:}prop2']
+
+ Cleanup this test.
+
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... AttributeAnnotations, (IAnnotatable,), IAnnotations)
+ True
+
+ """
+ interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+ component.adapts(IAnnotatable)
+
+ _annotations = None
+
+ def __init__(self, context):
+ # __parent__ must be set in order for the security to work
+ self.__parent__ = context
+ annotations = IAnnotations(context)
+ oprops = annotations.get(_opaque_namespace_key)
+ if oprops is None:
+ self._annotations = annotations
+ oprops = OOBTree()
+
+ self._mapping = oprops
+
+ def _changed(self):
+ if self._annotations is not None:
+ self._annotations[_opaque_namespace_key] = self._mapping
+ self._annotations = None
+
+ def getAllProperties(self):
+ for tag in self._mapping.keys():
+ yield tag
+
+ def hasProperty(self, tag):
+ return tag in self._mapping
+
+ def getProperty(self, tag):
+ """Returns None."""
+ return self._mapping.get(tag, None)
+
+ def setProperty(self, tag, value):
+ self._mapping[tag] = value
+ self._changed()
+
+ def removeProperty(self, tag):
+ del self._mapping[tag]
+ self._changed()
Property changes on: zope.webdav/trunk/src/zope/webdav/deadproperties.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,239 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Common WebDAV error handling code.
+
+There are two types of error views. Ones that get caught by the WebDAV protocol
+and the other which escapes to the publisher. Both these views implement
+different interface which we can control through the WebDAV package via the
+IPublication.handleException method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import schema
+from zope import component
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.app.http.interfaces import IHTTPException
+
+import zope.webdav.interfaces
+import zope.webdav.utils
+from zope.webdav.ietree import IEtree
+
+class DAVError(object):
+ interface.implements(zope.webdav.interfaces.IDAVErrorWidget)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ status = None
+
+ errors = []
+
+ propstatdescription = ""
+
+ responsedescription = ""
+
+
+class ConflictError(DAVError):
+ status = 409
+
+
+class ForbiddenError(DAVError):
+ status = 403
+
+
+class PropertyNotFoundError(DAVError):
+ status = 404
+
+
+class FailedDependencyError(DAVError):
+ # context is generally None for a failed dependency error.
+ status = 424
+
+
+class AlreadyLockedError(DAVError):
+ status = 423
+
+################################################################################
+#
+# Multi-status error view
+#
+################################################################################
+
+class MultiStatusErrorView(object):
+ component.adapts(zope.webdav.interfaces.IWebDAVErrors,
+ zope.webdav.interfaces.IWebDAVRequest)
+ interface.implements(IHTTPException)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ etree = component.getUtility(IEtree)
+ multistatus = zope.webdav.utils.MultiStatus()
+
+ seenContext = False
+ for error in self.error.errors:
+ if error.resource == self.error.context:
+ seenContext = True
+
+ davwidget = component.getMultiAdapter(
+ (error, self.request), zope.webdav.interfaces.IDAVErrorWidget)
+
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(error.resource, self.request))
+ response.status = davwidget.status
+ response.responsedescription += davwidget.responsedescription
+
+ multistatus.responses.append(response)
+
+ if not seenContext:
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(
+ self.error.context, self.request))
+ response.status = 424
+ multistatus.responses.append(response)
+
+ self.request.response.setStatus(207)
+ self.request.response.setHeader("content-type", "application/xml")
+ return etree.tostring(multistatus(), encoding = "utf-8")
+
+
+class WebDAVPropstatErrorView(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IWebDAVPropstatErrors,
+ zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ etree = component.getUtility(IEtree)
+ multistatus = zope.webdav.utils.MultiStatus()
+
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(self.error.context, self.request))
+ multistatus.responses.append(response)
+
+ for prop, error in self.error.items():
+ error_view = component.getMultiAdapter(
+ (error, self.request), zope.webdav.interfaces.IDAVErrorWidget)
+ propstat = response.getPropstat(error_view.status)
+
+ if zope.webdav.interfaces.IDAVProperty.providedBy(prop):
+ ## XXX - not tested - but is it needed?
+ prop = "{%s}%s" %(prop.namespace, prop.__name__)
+
+ propstat.properties.append(etree.Element(prop))
+ ## XXX - needs testing.
+ propstat.responsedescription += error_view.propstatdescription
+ response.responsedescription += error_view.responsedescription
+
+ self.request.response.setStatus(207)
+ self.request.response.setHeader("content-type", "application/xml")
+ return etree.tostring(multistatus(), encoding = "utf-8")
+
+################################################################################
+#
+# Some more generic exception view.
+#
+################################################################################
+
+class HTTPForbiddenError(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IForbiddenError,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(403)
+ return ""
+
+
+class HTTPConflictError(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IConflictError,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(409)
+ return ""
+
+
+class PreconditionFailed(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IPreconditionFailed,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(412)
+ return ""
+
+
+class HTTPUnsupportedMediaTypeError(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IUnsupportedMediaType,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(415)
+ return ""
+
+
+class UnprocessableError(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IUnprocessableError,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(422)
+ return ""
+
+
+class BadGateway(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IBadGateway,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, error, request):
+ self.error = error
+ self.request = request
+
+ def __call__(self):
+ self.request.response.setStatus(502)
+ return ""
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/badrequest.pt 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,18 @@
+<html>
+
+ <body>
+
+ <div>
+
+ <h1>Bad Request</h1>
+
+ <p tal:define="msg view/message"
+ tal:condition="msg"
+ tal:content="msg"
+ />
+
+ </div>
+
+ </body>
+
+</html>
Added: zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/browser.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/browser.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Common WebDAV error handling code.
+
+A lot of WebDAV requests can go badly wrong. If this is the case then return
+a snippet of HTML that could be displayed to the user describing what went
+left.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope.formlib import namedtemplate
+from zope.app.http.interfaces import IHTTPException
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+import zope.webdav.interfaces
+
+class BadRequest(object):
+ interface.implements(IHTTPException)
+ component.adapts(zope.webdav.interfaces.IBadRequest,
+ zope.webdav.interfaces.IHTTPRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def message(self):
+ return self.context.message
+
+ def __call__(self):
+ self.request.response.setStatus(400)
+ return self.template()
+
+ template = namedtemplate.NamedTemplate("default")
+
+
+default_template = namedtemplate.NamedTemplateImplementation(
+ ViewPageTemplateFile("badrequest.pt"), BadRequest)
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/browser.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,174 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser">
+
+ <!--
+ Error widgets
+ -->
+ <adapter
+ factory="zope.webdav.exceptions.ForbiddenError"
+ for="zope.webdav.interfaces.ForbiddenError
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.PropertyNotFoundError"
+ for="zope.webdav.interfaces.IPropertyNotFound
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.ConflictError"
+ for="zope.app.form.interfaces.IWidgetInputError
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.ConflictError"
+ for="zope.webdav.interfaces.IConflictError
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.AlreadyLockedError"
+ for="zope.webdav.interfaces.AlreadyLocked
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.FailedDependencyError"
+ for="zope.webdav.interfaces.IFailedDependency
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.interfaces.IDAVErrorWidget"
+ />
+
+ <!--
+ Some default errors that can make it back to the publisher.
+ -->
+ <adapter
+ factory="zope.webdav.exceptions.UnprocessableError"
+ name="index.html"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IUnprocessableError"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.MultiStatusErrorView"
+ name="index.html"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IWebDAVErrors"
+ type="zope.webdav.interfaces.IWebDAVRequest"
+ name="index.html"
+ />
+
+ <adapter
+ factory="zope.webdav.exceptions.WebDAVPropstatErrorView"
+ name="index.html"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IWebDAVPropstatErrors"
+ type="zope.webdav.interfaces.IWebDAVRequest"
+ name="index.html"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IPreconditionFailed"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.PreconditionFailed"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IPreconditionFailed"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IBadGateway"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.BadGateway"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IBadGateway"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IConflictError"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.HTTPConflictError"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IConflictError"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IForbiddenError"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.HTTPForbiddenError"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IForbiddenError"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IUnsupportedMediaType"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.HTTPUnsupportedMediaTypeError"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IUnsupportedMediaType"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+ <adapter
+ factory=".browser.default_template"
+ name="default"
+ />
+
+ <view
+ for="zope.webdav.interfaces.IBadRequest"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ permission="zope.Public"
+ factory="zope.webdav.exceptions.browser.BadRequest"
+ />
+
+ <defaultView
+ for="zope.webdav.interfaces.IBadRequest"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ name="index.html"
+ />
+
+</configure>
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,49 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the Bad Request view.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+
+from zope.webdav.ftests import dav
+import zope.webdav.interfaces
+import zope.webdav.exceptions.browser
+
+class TestBadRequest(dav.DAVTestCase):
+
+ def test_badrequest(self):
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.BadRequest(
+ request, u"Some bad content in the request")
+
+ view = zope.webdav.exceptions.browser.BadRequest(error, request)
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 400)
+ self.assertEqual(
+ request.response.getHeader("content-type"), "text/html")
+ self.assert_("Some bad content in the request" in result)
+
+
+def test_suite():
+ suite = unittest.TestSuite((
+ unittest.makeSuite(TestBadRequest)))
+ return suite
+
+if __name__ == "__main__":
+ unittest.main(defaultTest = "test_suite")
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/ftests/test_baseexceptions.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,249 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the multistatus views.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope.interface.verify import verifyObject
+from zope import schema
+from zope import component
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+import zope.webdav.publisher
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class IResource(interface.Interface):
+
+ text = schema.TextLine(
+ title = u"Example Text Property")
+
+ intprop = schema.Int(
+ title = u"Example Int Property")
+
+
+class Resource(object):
+ interface.implements(IResource)
+
+ def __init__(self, text = u"", intprop = 0):
+ self.text = text
+ self.intprop = intprop
+
+
+class DummyResourceURL(object):
+ interface.implements(IAbsoluteURL)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ def __str__(self):
+ if getattr(self.context, "__parent__", None) is not None:
+ path = DummyResourceURL(self.context.__parent__, None)()
+ else:
+ path = ""
+
+ if getattr(self.context, "__name__", None) is not None:
+ path += "/" + self.context.__name__
+ elif IResource.providedBy(self.context):
+ path += "/resource"
+## elif ICollection.providedBy(self.context):
+## path += "/collection"
+ else:
+ raise ValueError("unknown context type")
+
+ return path
+
+ __call__ = __str__
+
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+ def __init__(self, properties = None, environ = {}):
+ if properties is not None:
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+ %s
+</propfind>
+""" % properties
+ else:
+ body = ""
+
+ env = environ.copy()
+ env.setdefault("REQUEST_METHOD", "PROPFIND")
+ env.setdefault("CONTENT_TYPE", "text/xml")
+ env.setdefault("CONTENT_LENGTH", len(body))
+
+ super(TestRequest, self).__init__(StringIO(body), env)
+
+ # call processInputs now since we are in a unit test.
+ self.processInputs()
+
+
+class TestPropstatErrorView(unittest.TestCase):
+
+ def setUp(self):
+ super(TestPropstatErrorView, self).setUp()
+
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(DummyResourceURL,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.exceptions.ForbiddenError,
+ (zope.webdav.interfaces.IForbiddenError,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ super(TestPropstatErrorView, self).tearDown()
+
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(DummyResourceURL,
+ (IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.exceptions.ForbiddenError,
+ (zope.webdav.interfaces.IForbiddenError,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_propstat_interface(self):
+ resource = Resource()
+ error = zope.webdav.interfaces.WebDAVPropstatErrors(resource)
+ self.assertEqual(
+ verifyObject(zope.webdav.interfaces.IWebDAVPropstatErrors, error),
+ True)
+
+ def test_propstat_simple_errors(self):
+ resource = Resource()
+ error = zope.webdav.interfaces.WebDAVPropstatErrors(resource)
+ error["{DAV:}displayname"] = zope.webdav.interfaces.ForbiddenError(
+ resource, "{DAV:}displayname", message = u"readonly field")
+ request = TestRequest()
+
+ view = zope.webdav.exceptions.WebDAVPropstatErrorView(error, request)
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 207)
+ self.assertEqual(request.response.getHeader("content-type"), "application/xml")
+ assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+ <ns0:href>/resource</ns0:href>
+ <ns0:propstat>
+ <ns0:prop>
+ <ns0:displayname />
+ </ns0:prop>
+ <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+ </ns0:propstat>
+</ns0:response></ns0:multistatus>""")
+
+
+class TestMSErrorView(unittest.TestCase):
+
+ def setUp(self):
+ super(TestMSErrorView, self).setUp()
+
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(DummyResourceURL,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.exceptions.ForbiddenError,
+ (zope.webdav.interfaces.IForbiddenError,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+ (zope.webdav.interfaces.IPropertyNotFound,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ super(TestMSErrorView, self).tearDown()
+
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(DummyResourceURL,
+ (IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.exceptions.ForbiddenError,
+ (zope.webdav.interfaces.IForbiddenError,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+ (zope.webdav.interfaces.IPropertyNotFound,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_multi_resource_error_interface(self):
+ resource = Resource()
+ error = zope.webdav.interfaces.WebDAVErrors(resource)
+ self.assertEqual(
+ verifyObject(zope.webdav.interfaces.IWebDAVErrors, error), True)
+
+ def test_multi_resource_error(self):
+ resource = Resource()
+ error = zope.webdav.interfaces.WebDAVErrors(resource)
+ error.append(zope.webdav.interfaces.ForbiddenError(
+ resource, "{DAV:}displayname", message = u"readonly field"))
+ request = TestRequest()
+
+ view = zope.webdav.exceptions.MultiStatusErrorView(error, request)
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 207)
+ self.assertEqual(request.response.getHeader("content-type"),
+ "application/xml")
+
+ assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+ <ns0:href>/resource</ns0:href>
+ <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+</ns0:response></ns0:multistatus>""")
+
+ def test_simple_seen_context(self):
+ resource = Resource()
+ resource1 = Resource()
+ resource1.__name__ = "secondresource"
+ error = zope.webdav.interfaces.WebDAVErrors(resource)
+ error.append(zope.webdav.interfaces.ForbiddenError(
+ resource, "{DAV:}displayname", message = u"readonly field"))
+ error.append(zope.webdav.interfaces.PropertyNotFound(
+ resource1, "{DAV:}getcontentlength", message = u"readonly field"))
+ request = TestRequest()
+
+ view = zope.webdav.exceptions.MultiStatusErrorView(error, request)
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 207)
+ self.assertEqual(request.response.getHeader("content-type"),
+ "application/xml")
+
+ assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+ <ns0:href>/resource</ns0:href>
+ <ns0:status>HTTP/1.1 403 Forbidden</ns0:status>
+</ns0:response>
+<ns0:response>
+ <ns0:href>/secondresource</ns0:href>
+ <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
+</ns0:response></ns0:multistatus>""")
+
+
+def test_suite():
+ suite = unittest.TestSuite((
+ unittest.makeSuite(TestPropstatErrorView),
+ unittest.makeSuite(TestMSErrorView),
+ ))
+ return suite
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_multiviews.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the Bad Request view.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope import component
+from zope import interface
+from zope.formlib.namedtemplate import INamedTemplate
+
+import zope.webdav.interfaces
+import zope.webdav.exceptions
+import zope.webdav.exceptions.browser
+from zope.webdav.publisher import WebDAVRequest
+from test_multiviews import TestRequest
+
+class TestExceptionViews(PlacelessSetup, unittest.TestCase):
+
+ def test_unprocessable(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.UnprocessableError(None)
+ view = zope.webdav.exceptions.UnprocessableError(error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 422)
+ self.assertEqual(result, "")
+
+ def test_precondition(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.PreconditionFailed(None)
+ view = zope.webdav.exceptions.PreconditionFailed(error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 412)
+ self.assertEqual(result, "")
+
+ def test_badgateway(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.BadGateway(None, request)
+ view = zope.webdav.exceptions.BadGateway(error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 502)
+ self.assertEqual(result, "")
+
+ def test_conflicterror(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.ConflictError(None, request)
+ view = zope.webdav.exceptions.HTTPConflictError(error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 409)
+ self.assertEqual(result, "")
+
+ def test_forbiddenerror(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.ForbiddenError(None, request)
+ view = zope.webdav.exceptions.HTTPForbiddenError(error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 403)
+ self.assertEqual(result, "")
+
+ def test_unsupportedmediatype(self):
+ request = WebDAVRequest(StringIO(""), {})
+ error = zope.webdav.interfaces.UnsupportedMediaType(None, request)
+ view = zope.webdav.exceptions.HTTPUnsupportedMediaTypeError(
+ error, request)
+
+ result = view()
+
+ self.assertEqual(request.response.getStatus(), 415)
+ self.assertEqual(result, "")
+
+
+class TestDAVErrors(unittest.TestCase):
+
+ def test_conflict_error(self):
+ errorview = zope.webdav.exceptions.ConflictError(None, None)
+
+ self.assertEqual(errorview.status, 409)
+ self.assertEqual(errorview.errors, [])
+ self.assertEqual(errorview.propstatdescription, "")
+ self.assertEqual(errorview.responsedescription, "")
+
+ def test_forbidden_error(self):
+ errorview = zope.webdav.exceptions.ForbiddenError(None, None)
+
+ self.assertEqual(errorview.status, 403)
+ self.assertEqual(errorview.errors, [])
+ self.assertEqual(errorview.propstatdescription, "")
+ self.assertEqual(errorview.responsedescription, "")
+
+ def test_propertyNotFound_error(self):
+ errorview = zope.webdav.exceptions.PropertyNotFoundError(None, None)
+
+ self.assertEqual(errorview.status, 404)
+ self.assertEqual(errorview.errors, [])
+ self.assertEqual(errorview.propstatdescription, "")
+ self.assertEqual(errorview.responsedescription, "")
+
+ def test_failedDependency_error(self):
+ errorview = zope.webdav.exceptions.FailedDependencyError(None, None)
+
+ self.assertEqual(errorview.status, 424)
+ self.assertEqual(errorview.errors, [])
+ self.assertEqual(errorview.propstatdescription, "")
+ self.assertEqual(errorview.responsedescription, "")
+
+ def test_alreadlocked_error(self):
+ errorview = zope.webdav.exceptions.AlreadyLockedError(None, None)
+
+ self.assertEqual(errorview.status, 423)
+ self.assertEqual(errorview.errors, [])
+ self.assertEqual(errorview.propstatdescription, "")
+ self.assertEqual(errorview.responsedescription, "")
+
+
+class DummyTemplate(object):
+
+ def __init__(self, context):
+ self.context = context
+
+ component.adapts(zope.webdav.exceptions.browser.BadRequest)
+ interface.implements(INamedTemplate)
+
+ def __call__(self):
+ return "Errr... bad request"
+
+
+class TestBadRequestView(unittest.TestCase):
+
+ def setUp(self):
+ component.getGlobalSiteManager().registerAdapter(
+ DummyTemplate, name = "default")
+
+ def tearDown(self):
+ component.getGlobalSiteManager().unregisterAdapter(
+ DummyTemplate, name = "default")
+
+ def test_badrequestView(self):
+ error = zope.webdav.interfaces.BadRequest(
+ None, message = u"Bad request data")
+ request = TestRequest()
+ view = zope.webdav.exceptions.browser.BadRequest(error, request)
+
+ result = view()
+ self.assertEqual(request.response.getStatus(), 400)
+ self.assertEqual(result, "Errr... bad request")
+
+ def test_badrequestView_message(self):
+ error = zope.webdav.interfaces.BadRequest(
+ None, message = u"Bad request data")
+ request = TestRequest()
+ view = zope.webdav.exceptions.browser.BadRequest(error, request)
+
+ self.assertEqual(view.message(), "Bad request data")
+
+
+def test_suite():
+ suite = unittest.TestSuite((
+ unittest.makeSuite(TestExceptionViews),
+ unittest.makeSuite(TestDAVErrors),
+ unittest.makeSuite(TestBadRequestView),
+ ))
+ return suite
Property changes on: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftesting.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftesting.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftesting.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,42 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <class class=".ftests.dav.CollectionResource">
+ <require
+ permission="zope.View"
+ interface=".ftests.dav.ICollectionResource"
+ />
+ <require
+ permission="zope.ManageContent"
+ set_schema=".ftests.dav.ICollectionResource"
+ />
+ </class>
+
+ <class class=".ftests.dav.Resource">
+ <require
+ permission="zope.View"
+ interface=".ftests.dav.IResource"
+ />
+ <require
+ permission="zope.ManageContent"
+ set_schema=".ftests.dav.IResource"
+ />
+ </class>
+
+ <class class=".ftests.dav.EmptyCollectionResource">
+ <require
+ permission="zope.View"
+ interface="zope.app.container.interfaces.IReadContainer"
+ />
+ <require
+ permission="zope.ManageContent"
+ interface="zope.app.container.interfaces.IWriteContainer"
+ />
+ </class>
+
+ <adapter
+ for=".ftests.dav.EmptyCollectionResource"
+ provides="zope.filerepresentation.interfaces.IWriteDirectory"
+ factory=".ftests.dav.EmptyWriteDirectory"
+ />
+
+</configure>
Property changes on: zope.webdav/trunk/src/zope/webdav/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of functional tests for zope.webdav
+
+$Id$
+"""
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/dav.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/dav.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/dav.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,507 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Common utilities needed for writing WebDAV functional tests.
+
+XXX - This really needs some tidying up, also the setup should be moved to
+a global setup method so that individual tests can call it if they need to.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from BTrees.OOBTree import OOBTree
+
+import persistent
+import transaction
+
+from zope import interface
+from zope import component
+from zope import schema
+from zope.publisher.http import status_reasons
+from zope.app.testing.functional import HTTPTestCase, FunctionalTestSetup
+from zope.security.proxy import removeSecurityProxy
+from zope.app.folder.folder import Folder
+import zope.app.folder.interfaces
+from zope.app.file.file import File
+from zope.app.publication.http import HTTPPublication
+from zope.security.management import newInteraction, endInteraction
+from zope.security.testing import Principal, Participation
+
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.ietree import IEtree
+from zope.webdav.properties import DAVProperty
+from zope.webdav.testing import assertXMLEqual
+import zope.webdav.coreproperties
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+ exampleintprop = schema.Int(
+ title = u"Example Integer Property",
+ description = u"")
+
+ exampletextprop = schema.Text(
+ title = u"Example Text Property",
+ description = u"")
+
+exampleIntProperty = DAVProperty("{DAVtest:}exampleintprop",
+ IExamplePropertyStorage)
+
+exampleTextProperty = DAVProperty("{DAVtest:}exampletextprop",
+ IExamplePropertyStorage)
+
+
+ANNOT_KEY = "EXAMPLE_PROPERTY"
+class ExamplePropertyStorage(object):
+ interface.implements(IExamplePropertyStorage)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def _getproperty(name, default = None):
+ def get(self):
+ annots = getattr(removeSecurityProxy(self.context),
+ "exampleannots", {})
+ return annots.get("%s_%s" %(ANNOT_KEY, name), default)
+ def set(self, value):
+ annots = getattr(removeSecurityProxy(self.context),
+ "exampleannots", None)
+ if annots is None:
+ annots = removeSecurityProxy(
+ self.context).exampleannots = OOBTree()
+ annots["%s_%s" %(ANNOT_KEY, name)] = value
+ return property(get, set)
+
+ exampleintprop = _getproperty("exampleintprop", default = 0)
+
+ exampletextprop = _getproperty("exampletextprop", default = u"")
+
+
+class TestWebDAVRequest(WebDAVRequest):
+ """."""
+ def __init__(self, elem = None):
+ if elem is not None:
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:">
+ <D:set>
+ <D:prop />
+ </D:set>
+</D:propertyupdate>
+"""
+ f = StringIO(body)
+ else:
+ f = StringIO('')
+
+ super(TestWebDAVRequest, self).__init__(
+ f, {'CONTENT_TYPE': 'text/xml',
+ 'CONTENT_LENGTH': len(f.getvalue()),
+ })
+
+ # processInputs to test request
+ self.processInputs()
+
+ # if elem is given insert it into the proppatch request.
+ if elem is not None:
+ self.xmlDataSource[0][0].append(elem)
+
+
+class EmptyCollectionResource(Folder):
+ """This collection doesn't contain any subitems
+ """
+ pass
+
+
+def EmptyWriteDirectory(context):
+ return None
+
+class ICollectionResource(zope.app.folder.interfaces.IFolder):
+
+ title = schema.TextLine(
+ title = u"Title",
+ description = u"Title of resource")
+
+
+class CollectionResource(Folder):
+ interface.implements(ICollectionResource)
+
+ title = None
+
+
+class IResource(interface.Interface):
+ """ """
+
+ title = schema.TextLine(
+ title = u"Title",
+ description = u"Title of resource")
+
+ content = schema.Bytes(
+ title = u"Content",
+ description = u"Content of the resource")
+
+class Resource(persistent.Persistent):
+ interface.implements(IResource)
+
+ title = None
+
+ content = None
+
+
+class DisplayNameStorageAdapter(object):
+ interface.implements(zope.webdav.coreproperties.IDAVDisplayname)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ @apply
+ def displayname():
+ def get(self):
+ return self.context.title
+ def set(self, value):
+ self.context.title = value
+ return property(get, set)
+
+
+class GETContentLength(object):
+ component.adapts(IResource, zope.webdav.interfaces.IWebDAVRequest)
+ interface.implements(zope.webdav.coreproperties.IDAVGetcontentlength)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ @property
+ def getcontentlength(self):
+ return len(self.context.content)
+
+
+class DeadProperties(object):
+ interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+
+ def __init__(self, context):
+ self.context = context
+ # This is only a test so aren't that concerned with security at this
+ # point.
+ self.annots = getattr(removeSecurityProxy(self.context), "annots", None)
+
+ def getAllProperties(self):
+ if self.annots is not None:
+ for tag in self.annots:
+ yield tag
+
+ def hasProperty(self, tag):
+ if self.annots is not None and tag in self.annots:
+ return True
+ return False
+
+ def getProperty(self, tag):
+ if self.annots is not None:
+ return self.annots.get(tag, None)
+ return None
+
+ def setProperty(self, tag, value):
+ if self.annots is None:
+ self.annots = removeSecurityProxy(self.context).annots = OOBTree()
+ self.annots[tag] = value
+
+ def removeProperty(self, tag):
+ del self.annots[tag]
+
+
+class DAVTestCase(HTTPTestCase):
+
+ def setUp(self):
+ super(DAVTestCase, self).setUp()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.registerUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.registerUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ # this is to test the include and restricted allprop PROPFIND tests.
+ exampleTextProperty.restricted = False
+
+ gsm.registerAdapter(DisplayNameStorageAdapter,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(DisplayNameStorageAdapter,
+ (ICollectionResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(GETContentLength)
+
+ gsm.registerAdapter(DeadProperties, (IResource,))
+ gsm.registerAdapter(DeadProperties, (ICollectionResource,))
+
+ gsm.registerAdapter(ExamplePropertyStorage,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+
+ def tearDown(self):
+ gsm = component.getGlobalSiteManager()
+
+ gsm.unregisterUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+
+ gsm.unregisterAdapter(DisplayNameStorageAdapter,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(DisplayNameStorageAdapter,
+ (ICollectionResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(GETContentLength)
+
+ gsm.unregisterAdapter(DeadProperties, (IResource,))
+ gsm.unregisterAdapter(DeadProperties, (ICollectionResource,))
+
+ gsm.unregisterAdapter(ExamplePropertyStorage,
+ (IResource,
+ zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+
+ super(DAVTestCase, self).tearDown()
+
+ # logout just to make sure.
+ self.logout()
+
+ def login(self, principalid = "mgr"):
+ """Some locking methods new an interaction in order to lock a resource
+ """
+ principal = Principal(principalid)
+ participation = Participation(principal)
+ newInteraction(participation)
+
+ def logout(self):
+ """End the current interaction so we run the publish method.
+ """
+ endInteraction()
+
+ #
+ # Some methods for creating dummy content.
+ #
+ def createCollections(self, path):
+ collection = self.getRootFolder()
+ if path[0] == '/':
+ path = path[1:]
+ path = path.split('/')
+ for id in path[:-1]:
+ try:
+ collection = collection[id]
+ except KeyError:
+ collection[id] = CollectionResource()
+ collection = collection[id]
+ return collection, path[-1]
+
+ def createObject(self, path, obj):
+ collection, id = self.createCollections(path)
+ collection[id] = obj
+ transaction.commit()
+ return collection[id]
+
+ def addResource(self, path, content, title = None):
+ resource = Resource()
+ resource.content = content
+ resource.title = title
+ return self.createObject(path, resource)
+
+ def addCollection(self, path, title = None):
+ coll = CollectionResource()
+ coll.title = title
+ return self.createObject(path, coll)
+
+ def addFile(self, path, content, contentType):
+ resource = File(content, contentType)
+ return self.createObject(path, resource)
+
+ def createCollectionResourceStructure(self):
+ """ _____ rootFolder/ _____
+ / \ \
+ r1 __ a/ __ b/
+ / \
+ r2 r3
+ """
+ self.addResource("/r1", "first resource")
+ self.addResource("/a/r2", "second resource")
+ self.addResource("/a/r3", "third resource")
+ self.addCollection("/b")
+
+ def createFolderFileStructure(self):
+ """ _____ rootFolder/ _____
+ / \ \
+ r1 __ a/ __ b/
+ / \
+ r2 r3
+ """
+ self.addFile("/r1", "first resource", "test/plain")
+ self.addFile("/a/r2", "second resource", "text/plain")
+ self.addFile("/a/r3", "third resource", "text/plain")
+ self.createObject("/b", Folder())
+
+ #
+ # Now some methods for creating, and publishing request.
+ #
+ def makeRequest(self, path = "", basic = None, form = None, env = {},
+ instream = None):
+ """Create a new WebDAV request
+ """
+ if instream is None:
+ instream = ""
+ environment = {"HTTP_HOST": "localhost",
+ "HTTP_REFERER": "localhost"}
+ environment.update(env)
+
+ if instream and not environment.has_key("CONTENT_LENGTH"):
+ if getattr(instream, "getvalue", None) is not None:
+ instream = instream.getvalue()
+ environment["CONTENT_LENGTH"] = len(instream)
+
+ app = FunctionalTestSetup().getApplication()
+ request = app._request(path, instream, environment = environment,
+ basic = basic, form = form,
+ request = WebDAVRequest,
+ publication = HTTPPublication)
+ return request
+
+ def checkPropfind(self, path = "/", basic = None, env = {},
+ properties = None):
+ # - properties if set is a string containing the contents of the
+ # propfind XML element has specified in the WebDAV spec.
+ if properties is not None:
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+ %s
+</propfind>
+""" % properties
+ if not env.has_key("CONTENT_TYPE"):
+ env["CONTENT_TYPE"] = "application/xml"
+ env["CONTENT_LENGTH"] = len(body)
+ else:
+ body = ""
+ env["CONTENT_LENGTH"] = 0
+
+ if not env.has_key("REQUEST_METHOD"):
+ env["REQUEST_METHOD"] = "PROPFIND"
+
+ response = self.publish(path, basic = basic, env = env,
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 207)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+ respbody = response.getBody()
+ etree = component.getUtility(IEtree)
+ xmlbody = etree.fromstring(respbody)
+
+ return response, xmlbody
+
+ def checkProppatch(self, path = '/', basic = None, env = {},
+ set_properties = None, remove_properties = None,
+ handle_errors = True):
+ # - set_properties is None or a string that is the XML fragment
+ # that should be included within the <D:set><D:prop> section of
+ # a PROPPATCH request.
+ # - remove_properties is None or a string that is the XML fragment
+ # that should be included within the <D:remove><D:prop> section of
+ # a PROPPATCH request.
+ set_body = ""
+ if set_properties:
+ set_body = "<D:set><D:prop>%s</D:prop></D:set>" % set_properties
+
+ remove_body = ""
+ if remove_properties:
+ remove_body = "<D:remove><D:prop>%s</D:prop></D:remove>" % \
+ remove_properties
+
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ %s %s
+</D:propertyupdate>
+ """ %(set_body, remove_body)
+ body = body.encode("utf-8")
+
+ if not env.has_key("CONTENT_TYPE"):
+ env["CONTENT_TYPE"] = "application/xml"
+ env["CONTENT_LENGTH"] = len(body)
+
+ if not env.has_key("REQUEST_METHOD"):
+ env["REQUEST_METHOD"] = "PROPPATCH"
+
+ response = self.publish(path, basic = basic, env = env,
+ request_body = body,
+ handle_errors = handle_errors)
+
+ self.assertEqual(response.getStatus(), 207)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+ respbody = response.getBody()
+ etree = component.getUtility(IEtree)
+ xmlbody = etree.fromstring(respbody)
+
+ return response, xmlbody
+
+ def assertMSPropertyValue(self, response, proptag, status = 200,
+ tag = None, text_value = None,
+ prop_element = None):
+ # For the XML response element make sure that the proptag belongs
+ # to the propstat element that has the given status.
+ # - response - etree XML element
+ # - proptag - tag name of the property we are testing
+ # - status - integre status code
+ # - tag -
+ # - text_value -
+ # - propelement - etree Element that we compare with the property
+ # using zope.webdav.testing.assertXMLEqual
+ self.assertEqual(response.tag, "{DAV:}response")
+
+ # set to true if we found the property, under the correct status code
+ found_property = False
+
+ propstats = response.findall("{DAV:}propstat")
+ for propstat in propstats:
+ statusresp = propstat.findall("{DAV:}status")
+ self.assertEqual(len(statusresp), 1)
+
+ if statusresp[0].text == "HTTP/1.1 %d %s" %(
+ status, status_reasons[status]):
+ # make sure that proptag is in this propstat element
+ props = propstat.findall("{DAV:}prop/%s" % proptag)
+ self.assertEqual(len(props), 1)
+ prop = props[0]
+
+ # now test the the tag and text match this propstat element
+ if tag is not None:
+ ## XXX - this is not right.
+ ## self.assertEqual(len(prop), 1)
+ self.assertEqual(prop[0].tag, tag)
+ else:
+ self.assertEqual(len(prop), 0)
+ self.assertEqual(prop.text, text_value)
+
+ if prop_element is not None:
+ assertXMLEqual(prop, prop_element)
+
+ found_property = True
+ else:
+ # make sure that proptag is NOT in this propstat element
+ props = propstat.findall("{DAV:}prop/%s" % proptag)
+ self.assertEqual(len(props), 0)
+
+ self.assert_(
+ found_property,
+ "The property %s doesn't exist for the status code %d" %(proptag,
+ status))
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/dav.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,316 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of functional tests for the LOCK method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import dav
+
+class COPYTestCase(dav.DAVTestCase):
+
+ def test_copy_file(self):
+ file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/destfile"})
+
+ self.assertEqual(response.getStatus(), 201)
+ self.assertEqual(response.getHeader("location"),
+ "http://localhost/destfile")
+
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some file content")
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some file content")
+
+ def test_copy_file_nodest(self):
+ file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY"}, handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_copy_file_default_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/destfile"})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some source file")
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_copy_file_true_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/destfile",
+ "OVERWRITE": "T"})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some source file")
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_copy_file_false_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/destfile",
+ "OVERWRITE": "F"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 412)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some dest file")
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_copy_file_to_remove(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://www.remove-server.com/destfile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 502)
+
+ def test_copy_file_no_destparent(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/bla/destfile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 409)
+ self.assertEqual(list(self.getRootFolder().keys()), [u"sourcefile"])
+
+ def test_copy_to_same_file(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/sourcefile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 403)
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_bad_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/sourcefile",
+ "OVERWRITE": "X"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_copy_folder(self):
+ self.createCollectionResourceStructure()
+
+ response = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "COPY",
+ "DESTINATION": "http://localhost/c/"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 201)
+ self.assertEqual(response.getHeader("location"),
+ "http://localhost/c")
+ self.assertEqual(list(self.getRootFolder()["c"].keys()), [u"r2", u"r3"])
+
+
+class MOVETestCase(dav.DAVTestCase):
+ """These tests are very similar to the COPY tests. Actually I copied them
+ and modified them to work with MOVE.
+ """
+
+ def test_move_file(self):
+ file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/destfile"})
+
+ self.assertEqual(response.getStatus(), 201)
+ self.assertEqual(response.getHeader("location"),
+ "http://localhost/destfile")
+
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some file content")
+ self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+ def test_move_file_nodest(self):
+ file = self.addFile("/sourcefile", "some file content", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE"}, handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_move_file_default_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/destfile"})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some source file")
+ self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+ def test_move_file_true_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/destfile",
+ "OVERWRITE": "T"})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some source file")
+ self.assert_("sourcefile" not in self.getRootFolder().keys())
+
+ def test_move_file_false_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+ destfile = self.addFile("/destfile", "some dest file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/destfile",
+ "OVERWRITE": "F"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 412)
+ self.assertEqual(self.getRootFolder()["destfile"].data,
+ "some dest file")
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_move_file_to_remove(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://www.remove-server.com/destfile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 502)
+
+ def test_move_file_no_destparent(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/bla/destfile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 409)
+ self.assertEqual(list(self.getRootFolder().keys()), [u"sourcefile"])
+
+ def test_move_to_same_file(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/sourcefile",
+ "OVERWRITE": "T"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 403)
+ self.assertEqual(self.getRootFolder()["sourcefile"].data,
+ "some source file")
+
+ def test_bad_overwrite(self):
+ file = self.addFile("/sourcefile", "some source file", "text/plain")
+
+ response = self.publish(
+ "/sourcefile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/sourcefile",
+ "OVERWRITE": "X"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_move_folder(self):
+ self.createCollectionResourceStructure()
+
+ response = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "MOVE",
+ "DESTINATION": "http://localhost/c/"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 201)
+ self.assertEqual(response.getHeader("location"),
+ "http://localhost/c")
+ self.assertEqual(list(self.getRootFolder()["c"].keys()), [u"r2", u"r3"])
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(COPYTestCase),
+ unittest.makeSuite(MOVETestCase),
+ ))
+
+
+if __name__ == "__main__":
+ unittest.main(defaultTest = "test_suite")
+
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_copymove.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Functional tests for MKCOL.
+
+$Id$
+"""
+import unittest
+import dav
+
+class MKCOLTestCase(dav.DAVTestCase):
+
+ def test_mkcol_not_folderish(self):
+ self.addResource("/bar/pt", u"<span />")
+ self.verifyStatus(path = "/bar/pt/foo", body = "", basic = "mgr:mgrpw",
+ expected = 404)
+
+ def test_mkcol_not_folderish_existing(self):
+ self.addResource("/bar/pt", u"<span />")
+ self.verifyStatus(path = "/bar/pt", body = "", basic = "mgr:mgrpw",
+ expected = 405)
+
+ def test_mkcol_not_existing(self):
+ self.verifyStatus(path = "/mkcol_test", body = "", basic = "mgr:mgrpw",
+ expected = 201)
+
+ def test_mkcol_parent_not_existing(self):
+ self.verifyStatus(path = "/bar/mkcol_test", body = "",
+ basic = "mgr:mgrpw", expected = 409)
+
+ def test_mkcol_existing(self):
+ self.addCollection("/bar/mkcol_test")
+ self.verifyStatus(path = "/bar", body = "", basic = "mgr:mgrpw",
+ expected = 405)
+
+ def test_mkcol_with_body(self):
+ self.verifyStatus(path = "/mkcol_test", body = "bla",
+ basic = "mgr:mgrpw", expected = 415)
+
+ def test_mkcol_nowritedir(self):
+ self.createObject("/foo", dav.EmptyCollectionResource())
+ self.verifyStatus(path = "/foo/mkcol_test", body = "",
+ basic = "mgr:mgrpw", expected = 403)
+
+ def verifyStatus(self, path, body, basic, expected = 201):
+ clen = len(body)
+ result = self.publish(path, basic, env = {"REQUEST_METHOD":"MKCOL",
+ "CONTENT-LENGHT": clen},
+ request_body = body, handle_errors = True)
+ self.assertEquals(result.getStatus(), expected)
+
+
+def test_suite():
+ suite = unittest.TestSuite((
+ unittest.makeSuite(MKCOLTestCase),
+ ))
+ return suite
+
+
+if __name__ == '__main__':
+ unittest.main()
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_mkcol.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,489 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of functional tests for PROPFIND zope.webdav
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from cStringIO import StringIO
+import transaction
+
+import dav
+from zope import component
+import zope.webdav.interfaces
+
+class PROPFINDTests(dav.DAVTestCase):
+
+ def test_badcontent(self):
+ response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND"},
+ request_body = "some content",
+ handle_errors = True)
+ self.assertEqual(response.getStatus(), 400)
+ self.assert_("PROPFIND requires a valid XML request"
+ in response.getBody())
+
+ def test_invaliddepth(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+ <D:prop xmlns:R="http://ns.example.com/boxschema/">
+ <R:bigbox/>
+ <R:author/>
+ <R:DingALing/>
+ <R:Random/>
+ </D:prop>
+</D:propfind>"""
+ response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND",
+ "CONTENT_TYPE": "text/xml",
+ "DEPTH": "3",},
+ request_body = StringIO(body),
+ handle_errors = True)
+ self.assertEqual(response.getStatus(), 400)
+ self.assert_("Invalid Depth header supplied" in response.getBody())
+
+ def test_invalid_xml(self):
+ body = """<D:invalid xmlns:D="DAV:">Invalid</D:invalid>"""
+ response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND",
+ "CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body),
+ },
+ request_body = body,
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 422)
+
+ def test_simplepropfind_textxml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<ff0:propfind xmlns:ff0="DAV:">
+ <ff0:prop>
+ <ff0:resourcetype/>
+ </ff0:prop>
+</ff0:propfind>"""
+ httpresponse, xmlbody = self.checkPropfind(
+ "/", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+ properties = "<D:prop><D:resourcetype/></D:prop>")
+ hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/")
+
+ props = xmlbody.findall("{DAV:}response/{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+ propel = props[0]
+
+ self.assertEqual(len(propel), 1) # only one property defined
+ self.assertEqual(propel[0].tag, "{DAV:}resourcetype")
+ self.assertEqual(propel[0].text, None)
+ self.assertEqual(len(propel[0]), 1)
+ self.assertEqual(propel[0][0].tag, "{DAV:}collection")
+
+ def test_propnames(self):
+ collection = self.addCollection("/coll")
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/coll", env = {"DEPTH": "0"}, properties = "<D:propname />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1)
+ propel = props[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+ def test_propnames_on_resource(self):
+ self.addResource("/r1", "some content")
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r1", env = {"DEPTH": "0"}, properties = "<D:propname />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r1")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1)
+
+ ## See README.txt for a list of properties defined for these tests.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+ self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop")
+ self.assertMSPropertyValue(response, "{DAV:}getcontentlength")
+ self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop")
+
+ def test_allprop(self):
+ collection = self.addCollection("/coll", title = u"Test Collection")
+ httpresponse, xmlbody = self.checkPropfind(
+ "/coll", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+
+ ## See README.txt for a list of properties defined for these tests.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+ tag = "{DAV:}collection", text_value = None)
+ self.assertMSPropertyValue(response, "{DAV:}displayname",
+ text_value = "Test Collection")
+
+ def test_allprop_on_resource(self):
+ collection = self.addResource("/r1", "test resource content",
+ title = u"Test Resource")
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r1", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r1")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+
+ ## See README.txt for a list of properties defined for these tests.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+ self.assertMSPropertyValue(response, "{DAV:}displayname",
+ text_value = "Test Resource")
+ self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop")
+ self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+ text_value = "0")
+ self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+ text_value = "21")
+
+ def test_allprop_by_default(self):
+ self.addCollection("/coll")
+ httpresponse, xmlbody = self.checkPropfind("/coll",
+ env = {"DEPTH": "0"},
+ properties = "<D:prop />")
+ # the rest is copied from the previous code.
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+
+ ## See README.txt for a list of properties defined for these tests.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+ tag = "{DAV:}collection", text_value = None)
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+ def test_nobody_propfind(self):
+ self.addCollection("/coll", title = "Test Collection")
+
+ httpresponse, xmlbody = self.checkPropfind("/coll",
+ env = {"DEPTH": "0"})
+ # the rest is copied from the previous code.
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/coll/")
+
+ props = response.findall("{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+
+ ## See README.txt for a list of properties defined for these tests.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+ tag = "{DAV:}collection")
+ self.assertMSPropertyValue(response, "{DAV:}displayname",
+ text_value = "Test Collection")
+
+ def test_notfound_property(self):
+ httpresponse, xmlbody = self.checkPropfind(
+ "/", env = {"DEPTH": "0"},
+ properties = "<D:prop><D:resourcetype /><D:missingproperty /></D:prop>")
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/")
+
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype",
+ tag = "{DAV:}collection")
+ self.assertMSPropertyValue(response, "{DAV:}missingproperty",
+ status = 404)
+
+ def test_depthinf(self):
+ self.createCollectionResourceStructure()
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/", env = {"DEPTH": "infinity"},
+ properties = "<D:prop><D:resourcetype /></D:prop>")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 6)
+
+ # make sure we have all 200 status codes, and the hrefs differ
+ for response in responses:
+ propstats = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstats), 1)
+ statusresp = response.findall("{DAV:}propstat/{DAV:}status")
+ self.assertEqual(len(statusresp), 1)
+ self.assertEqual(statusresp[0].text, "HTTP/1.1 200 OK")
+
+ hrefs = [href.text for href in
+ xmlbody.findall("{DAV:}response/{DAV:}href")]
+ hrefs.sort()
+ self.assertEqual(hrefs, ['http://localhost/',
+ 'http://localhost/a/',
+ 'http://localhost/a/r2',
+ 'http://localhost/a/r3',
+ 'http://localhost/b/',
+ 'http://localhost/r1'])
+
+ def test_depthone(self):
+ self.createCollectionResourceStructure()
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/", env = {"DEPTH": "1"},
+ properties = "<D:prop><D:resourcetype /></D:prop>")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 4)
+
+ # make sure we have all 200 status codes, and the hrefs differ
+ for response in responses:
+ propstats = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstats), 1)
+ statusresp = response.findall("{DAV:}propstat/{DAV:}status")
+ self.assertEqual(len(statusresp), 1)
+ self.assertEqual(statusresp[0].text, "HTTP/1.1 200 OK")
+
+ hrefs = [href.text for href in
+ xmlbody.findall("{DAV:}response/{DAV:}href")]
+ hrefs.sort()
+ self.assertEqual(hrefs, ['http://localhost/', 'http://localhost/a/',
+ 'http://localhost/b/', 'http://localhost/r1'])
+
+ def test_opaque_properties(self):
+ file = self.addResource("/r", "some file content",
+ title = "Test resource")
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ opaqueProperties.setProperty(
+ "{examplens:}testdeadprop",
+ """<E:testdeadprop xmlns:E="examplens:">TEST</E:testdeadprop>""")
+ transaction.commit()
+
+ properties = """<D:prop xmlns:E="examplens:">
+<D:resourcetype /><E:testdeadprop />
+</D:prop>
+"""
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0"}, properties = properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ propstats = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstats), 1)
+ props = propstats[0].findall("{DAV:}prop")
+ self.assertEqual(len(props), 1)
+
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+ self.assertMSPropertyValue(response, "{examplens:}testdeadprop",
+ text_value = "TEST")
+
+ def test_allprop_with_opaque_properties(self):
+ file = self.addResource("/r", "some file content",
+ title = "Test Resource")
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ opaqueProperties.setProperty(
+ "{examplens:}testdeadprop",
+ """<E:testdeadprop xmlns:E="examplens:">TEST</E:testdeadprop>""")
+ transaction.commit()
+
+ properties = "<D:allprop />"
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0"}, properties = properties)
+
+ def test_unicode_title(self):
+ teststr = u"copyright \xa9 me"
+ file = self.addResource(u"/" + teststr, "some file content",
+ title = teststr)
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/" + teststr.encode("utf-8"), env = {"DEPTH": "0",
+ "CONTENT_TYPE": "text/xml"},
+ properties = "<D:prop><D:displayname /></D:prop>")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}displayname",
+ text_value = teststr)
+
+ def test_allprop_with_deadprops(self):
+ file = self.addResource("/r", "some content", title = "Test Resource")
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ opaqueProperties.setProperty("{deadprop:}deadprop",
+ """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+ transaction.commit()
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+ properties = "<D:allprop />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{deadprop:}deadprop",
+ text_value = """
+This is a dead property.""")
+
+ def test_allprop_with_restricted(self):
+ file = self.addResource("/r", "some content", title = "Test Resource")
+
+ examplePropStorage = component.getMultiAdapter(
+ (file, dav.TestWebDAVRequest()), dav.IExamplePropertyStorage)
+ examplePropStorage.exampletextprop = "EXAMPLE TEXT PROP"
+ transaction.commit()
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+ properties = "<D:allprop />")
+
+ hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+ text_value = "EXAMPLE TEXT PROP")
+
+ textprop = component.getUtility(zope.webdav.interfaces.IDAVProperty,
+ name = "{DAVtest:}exampletextprop")
+ textprop.restricted = True
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+ properties = "<D:allprop />")
+
+ hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ props = xmlbody.findall("{DAV:}response/{DAV:}propstat/{DAV:}prop")
+ self.assertEqual(len(props), 1) # only one prop element
+
+ self.assertEqual([prop.tag for prop in
+ props[0] if prop.tag == "{DAVtest:}exampletextprop"],
+ [])
+
+ def test_allprop_with_include(self):
+ file = self.addResource("/r", "some content", title = "Test Resource")
+
+ examplePropStorage = component.getMultiAdapter(
+ (file, dav.TestWebDAVRequest()), dav.IExamplePropertyStorage)
+ examplePropStorage.exampletextprop = "EXAMPLE TEXT PROP"
+ transaction.commit()
+
+ textprop = component.getUtility(zope.webdav.interfaces.IDAVProperty,
+ name = "{DAVtest:}exampletextprop")
+ textprop.restricted = True
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+ properties = """<D:allprop />
+<D:include>
+ <Dtest:exampletextprop xmlns:Dtest="DAVtest:" />
+</D:include>
+""")
+
+ hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+ text_value = "EXAMPLE TEXT PROP")
+
+ def test_propfind_onfile(self):
+ self.addFile("/testfile", "some file content", "text/plain")
+ httpresponse, xmlbody = self.checkPropfind(
+ "/testfile", env = {"DEPTH": "0"}, properties = "<D:allprop />")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+ propstats = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstats), 1)
+ props = propstats[0].findall("{DAV:}prop")
+ self.assertEqual(len(props), 1)
+
+ # all properties should be defined on a file.
+ self.assertMSPropertyValue(response, "{DAV:}resourcetype")
+ self.assertMSPropertyValue(response, "{DAV:}creationdate")
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+ self.assertMSPropertyValue(response, "{DAV:}getcontentlanguage")
+ self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+ text_value = "17")
+ self.assertMSPropertyValue(response, "{DAV:}getcontenttype",
+ text_value = "text/plain")
+ self.assertMSPropertyValue(response, "{DAV:}getetag")
+ self.assertMSPropertyValue(response, "{DAV:}getlastmodified")
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(PROPFINDTests),
+ ))
+
+if __name__ == "__main__":
+ unittest.main(defaultTest = "test_suite")
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,300 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of functional tests for PROPFIND zope.webdav
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import transaction
+
+from zope import component
+
+import dav
+
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.testing import assertXMLEqual
+
+class PROPPATCHTestCase(dav.DAVTestCase):
+
+ def test_badcontent(self):
+ response = self.publish("/", env = {"REQUEST_METHOD": "PROPPATCH"},
+ request_body = "some content",
+ handle_errors = True)
+ self.assertEqual(response.getStatus(), 400)
+ self.assert_(
+ "All PROPPATCH requests needs a XML body" in response.getBody())
+
+ def test_invalidxml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+ <D:prop />
+</D:propfind>
+ """
+ response = self.publish("/", env = {"REQUEST_METHOD": "PROPPATCH",
+ "CONTENT_TYPE": "application/xml",
+ "CONTENT_LENGTH": len(body)},
+ request_body = body,
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 422)
+ self.assertEqual(response.getBody(), "")
+
+ def test_setdisplayname(self):
+ set_properties = "<D:displayname>Test File</D:displayname>"
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ self.assertEqual(self.getRootFolder()["r"].title, "Test Resource")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+ self.assertEqual(self.getRootFolder()["r"].title, u"Test File")
+
+ def test_readonly_property(self):
+ set_properties = "<D:getcontentlength>10</D:getcontentlength>"
+ file = self.addResource("/r", "some file content", "Test Resource")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ self.assertMSPropertyValue(response, "{DAV:}getcontentlength",
+ status = 403)
+
+## def test_property_notfound(self):
+## set_properties = """
+## <E:notfound xmlns:E="example:">Not Existent Prop</E:notfound>
+## """
+## file = self.addFile("/testfile", "some file content", "text/plain")
+
+## httpresponse, xmlbody = self.checkProppatch(
+## "/testfile", basic = "mgr:mgrpw", set_properties = set_properties)
+
+## responses = xmlbody.findall("{DAV:}response")
+## self.assertEqual(len(responses), 1)
+## response = responses[0]
+## hrefs = response.findall("{DAV:}href")
+## self.assertEqual(len(hrefs), 1)
+## self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+## self.assertMSPropertyValue(response, "{example:}notfound",
+## status = 404)
+
+ def test_badinput(self):
+ set_properties = """
+ <E:exampleintprop xmlns:E="DAVtest:">BAD INT</E:exampleintprop>
+ """
+ resource = self.addResource("/testresource", "some resource content")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/testresource", basic = "mgr:mgrpw",
+ set_properties = set_properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/testresource")
+
+ self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+ status = 409)
+
+ def test_badinput_plus_faileddep(self):
+ set_properties = """
+ <E:exampleintprop xmlns:E="DAVtest:">BAD INT</E:exampleintprop>
+ <E:exampletextprop xmlns:E="DAVtest:">
+ Test Property
+ </E:exampletextprop>
+ """
+ resource = self.addResource("/testresource", "some resource content")
+
+ request = WebDAVRequest(StringIO(""), {})
+ exampleStorage = component.getMultiAdapter((resource, request),
+ dav.IExamplePropertyStorage)
+ # set up a default value to test later
+ exampleStorage.exampletextprop = u"Example Text Property"
+ transaction.commit()
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/testresource", basic = "mgr:mgrpw",
+ set_properties = set_properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/testresource")
+
+ self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+ status = 424)
+ self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+ 409)
+
+ exampleStorage = component.getMultiAdapter((resource, request),
+ dav.IExamplePropertyStorage)
+ self.assertEqual(exampleStorage.exampletextprop,
+ u"Example Text Property")
+
+ def test_proppatch_opaqueproperty(self):
+ set_properties = """<Z:Author xmlns:Z="http://ns.example.com/z39.50/">
+Jim Whitehead
+</Z:Author>
+ """
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ self.assertEqual(opaqueProperties.hasProperty(
+ "{http://ns.example.com/z39.50/}Author"), True)
+ assertXMLEqual(opaqueProperties.getProperty(
+ "{http://ns.example.com/z39.50/}Author"),
+ """<Z:Author xmlns:Z="http://ns.example.com/z39.50/">
+Jim Whitehead
+</Z:Author>""")
+
+ def test_set_multiple_dead_props(self):
+ set_properties = """<E:prop0 xmlns:E="example:">PROP0</E:prop0>
+<E:prop1 xmlns:E="example:">PROP0</E:prop1>
+<E:prop2 xmlns:E="example:">PROP0</E:prop2>
+<E:prop3 xmlns:E="example:">PROP0</E:prop3>
+ """
+
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ allprops = [tag for tag in opaqueProperties.getAllProperties()]
+ allprops.sort()
+ self.assertEqual(allprops, ["{example:}prop0", "{example:}prop1",
+ "{example:}prop2", "{example:}prop3"])
+
+ def test_unicode_title(self):
+ teststr = u"copyright \xa9 me"
+ set_properties = "<D:displayname>%s</D:displayname>" % teststr
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ self.assertEqual(file.title, "Test Resource")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw", set_properties = set_properties)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+
+ def test_remove_live_prop(self):
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ opaqueProperties.setProperty("{deadprop:}deadprop",
+ """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+ transaction.commit()
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw",
+ remove_properties = """<E:exampleintprop xmlns:E="DAVtest:" />""")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+ propstat = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstat), 1)
+ propstat = propstat[0]
+
+ self.assertEqual(len(propstat), 2)
+
+ props = propstat.findall("{DAV:}prop")
+ self.assertEqual(len(props), 1)
+ self.assertEqual(len(props[0]), 1) # there is only one property.
+
+ self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop",
+ status = 409)
+
+ def test_remove_dead_prop(self):
+ proptag = "{deadprop:}deadprop"
+ file = self.addResource("/r", "some content", "Test Resource")
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ opaqueProperties.setProperty(proptag,
+ """<X:deadprop xmlns:X="deadprop:">
+This is a dead property.</X:deadprop>""")
+ transaction.commit()
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/r", basic = "mgr:mgrpw",
+ remove_properties = """<X:deadprop xmlns:X="deadprop:" />""")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, proptag)
+
+ opaqueProperties = zope.webdav.interfaces.IOpaquePropertyStorage(file)
+ self.assertEqual(opaqueProperties.hasProperty(proptag), False)
+
+ def test_setting_unicode_title(self):
+ teststr = u"copyright \xa9 me"
+ file = self.addResource(u"/" + teststr, "some file content",
+ title = "Old title")
+
+ httpresponse, xmlbody = self.checkProppatch(
+ "/" + teststr.encode("utf-8"), basic = "mgr:mgrpw",
+ set_properties = "<D:displayname>%s</D:displayname>" % teststr)
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}displayname")
+ resource = self.getRootFolder()[teststr]
+ self.assertEqual(resource.title, teststr)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(PROPPATCHTestCase),
+ ))
+
+if __name__ == "__main__":
+ unittest.main(defaultTest = "test_suite")
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,782 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of functional tests for the LOCK method.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import datetime
+import transaction
+from cStringIO import StringIO
+
+import zope.webdav.ftests.dav
+
+from zope import component
+from zope.app.publication.http import MethodNotAllowed
+from zope.app.testing.setup import addUtility
+import zope.locking.interfaces
+from zope.locking.utility import TokenUtility
+from zope.locking import tokens
+import zope.locking.utils
+from zope.security.interfaces import Unauthorized
+
+from zope.webdav.testing import assertXMLEqual
+from zope.webdav.interfaces import IDAVLockmanager
+import zope.webdav.publisher
+from zope.webdav.ietree import IEtree
+
+class LOCKNotAllowedTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+ def test_lock_file(self):
+ file = self.addFile("/testfilenotallowed",
+ "some file content", "text/plain")
+ self.assertRaises(MethodNotAllowed, self.publish,
+ "/testfilenotallowed", basic = "mgr:mgrpw")
+
+ def test_lockingprops_noutility(self):
+ self.addFile("/testfile", "some file content", "text/plain")
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/testfile", env = {"DEPTH": "0"},
+ properties = """<D:prop>
+<D:supportedlock />
+<D:lockdiscovery />
+</D:prop>""")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ self.assertMSPropertyValue(response, "{DAV:}supportedlock",
+ status = 404)
+ self.assertMSPropertyValue(response, "{DAV:}lockdiscovery",
+ status = 404)
+
+
+class LOCKTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+ def _setup(self):
+ zope.webdav.ftests.dav.DAVTestCase.setUp(self)
+
+ self.oldnow = zope.locking.utils.now
+ def now():
+ return datetime.datetime(2006, 7, 27, 1, 9, 25)
+ zope.locking.utils.now = now
+
+ def setUp(self):
+ self._setup()
+
+ sitemanager = component.getSiteManager(self.getRootFolder())
+ self.utility = addUtility(sitemanager, "",
+ zope.locking.interfaces.ITokenUtility,
+ TokenUtility())
+ transaction.commit()
+
+ def _teardown(self):
+ zope.locking.utils.now = self.oldnow
+ del self.oldnow
+
+ zope.webdav.ftests.dav.DAVTestCase.tearDown(self)
+
+ def tearDown(self):
+ self._teardown()
+
+ sitemanager = component.getSiteManager(self.getRootFolder())
+ sitemanager.unregisterUtility(self.utility,
+ zope.locking.interfaces.ITokenUtility,
+ "")
+ del self.utility
+
+ def test_lock_file_unauthorized(self):
+ file = self.addFile("/testfile", "some file content", "text/plain")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ self.assertRaises(Unauthorized, self.publish, "/testfile",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "0",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ def test_invalid_xml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:invalid xmlns:D="DAV:">Invalid XML</D:invalid>
+ """
+
+ response = self.publish(
+ "/", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "0",
+ "TIMEOUT": "Infinite, Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body,
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 422)
+
+ def test_lock_file(self):
+ file = self.addFile("/testfile", "some file content", "text/plain")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/testfile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "0",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+ locktoken = response.getHeader("lock-token")
+ self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+ expectedbody = """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery>
+ <ns0:activelock>
+ <ns0:lockscope><ns0:exclusive /></ns0:lockscope>
+ <ns0:locktype><ns0:write /></ns0:locktype>
+ <ns0:depth>0</ns0:depth>
+ <ns0:owner>
+ <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+ </ns0:owner>
+ <ns0:timeout>Second-60800</ns0:timeout>
+ <ns0:locktoken>
+ <ns0:href>%s</ns0:href>
+ </ns0:locktoken>
+ <ns0:lockroot>http://localhost/testfile</ns0:lockroot>
+ </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken[1:-1]
+
+ respbody = response.getBody()
+ assertXMLEqual(respbody, expectedbody)
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_already_exclusive_locked_file(self):
+ file = self.addFile("/testlockedfile",
+ "some file content", "text/plain")
+
+ token = tokens.ExclusiveLock(file, "mgr")
+ token.duration = datetime.timedelta(seconds = 100)
+ self.utility.register(token)
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+ transaction.commit()
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/testlockedfile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "0",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body,
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 207)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+
+ expectedbody = """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response>
+ <ns0:href>http://localhost/testlockedfile</ns0:href>
+ <ns0:status>HTTP/1.1 423 Locked</ns0:status>
+</ns0:response></ns0:multistatus>"""
+
+ respbody = response.getBody()
+ assertXMLEqual(respbody, expectedbody)
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_lock_folder_depth_inf(self):
+ ## Test that when we lock a folder with depth infinity we the folder
+ ## and all sub resources lock tokens contain the same locktoken, and
+ ## lockroot.
+ self.createFolderFileStructure()
+
+ lockmanager = IDAVLockmanager(self.getRootFolder())
+ self.assertEqual(lockmanager.islocked(), False)
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "infinity",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+ locktoken = response.getHeader("lock-token")
+ self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+ locktoken = locktoken[1:-1] # remove the <> characters
+
+ token = self.utility.get(self.getRootFolder())
+ self.assertEqual(
+ token.annotations["zope.webdav.lockingutils.info"]["token"],
+ locktoken)
+ token = self.utility.get(self.getRootFolder()["a"])
+ self.assertEqual(
+ token.annotations["zope.webdav.lockingutils.info"]["token"],
+ locktoken)
+
+ root = self.getRootFolder()
+ self.assertEqual(
+ IDAVLockmanager(root).getActivelock().locktoken[0], locktoken)
+ self.assertEqual(
+ IDAVLockmanager(root["a"]["r2"]).getActivelock().locktoken[0],
+ locktoken)
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(""), {"HTTP_HOST": "localhost"})
+
+ lockroot = IDAVLockmanager(root).getActivelock(request).lockroot
+ self.assertEqual(lockroot, "http://localhost")
+ lockroot = IDAVLockmanager(root["a"]["r3"]).getActivelock(
+ request).lockroot
+ self.assertEqual(lockroot, "http://localhost")
+
+ def test_lock_collection_depth_inf_withlockedsubitem(self):
+ self.login()
+ self.createFolderFileStructure()
+
+ lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+ lockmanager.lock("exclusive", "write", """<D:owner>
+<D:href>http://webdav.org/</D:href></D:owner>""",
+ duration = datetime.timedelta(100), depth = "0")
+ transaction.commit()
+ self.logout()
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ httpresponse = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "infinity",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body,
+ handle_errors = True)
+
+ etree = component.getUtility(IEtree)
+ xmlbody = etree.fromstring(httpresponse.getBody())
+
+ self.assertEqual(httpresponse.getStatus(), 207)
+ self.assertEqual(
+ httpresponse.getHeader("content-type"), "application/xml")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 2)
+
+ for response in responses:
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ statusresp = response.findall("{DAV:}status")
+ self.assertEqual(len(statusresp), 1)
+ statusresp = statusresp[0].text
+
+ if hrefs[0].text == "http://localhost/a/":
+ self.assertEqual(
+ statusresp, "HTTP/1.1 424 Failed Dependency")
+ elif hrefs[0].text == "http://localhost/a/r2":
+ self.assertEqual(
+ statusresp, "HTTP/1.1 423 Locked")
+ else:
+ self.fail("unexpected reponse with href: %s" % hrefs[0].text)
+
+ lockmanager = IDAVLockmanager(self.getRootFolder()["a"])
+ self.assertEqual(lockmanager.islocked(), False)
+
+ # this object was already locked.
+ lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_lock_file_then_propfind(self):
+ ## Test that the locking properties get updated correctly whenever a
+ ## resource is locked. We do this by performing a PROPFIND on the
+ ## locked resource.
+ self.createFolderFileStructure()
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "infinity",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+ locktoken = response.getHeader("lock-token")
+ self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+ httpresponse, xmlbody = self.checkPropfind(
+ "/a/r2", env = {"DEPTH": "0", "CONTENT_TYPE": "text/xml"},
+ properties = "<D:allprop />")
+
+ hrefs = xmlbody.findall("{DAV:}response/{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/a/r2")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ propstat = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstat), 1)
+ propstat = propstat[0]
+
+ status = propstat.findall("{DAV:}status")
+ self.assertEqual(len(status), 1)
+ status = status[0]
+ self.assertEqual(status.text, "HTTP/1.1 200 OK")
+
+ props = propstat.findall("{DAV:}prop")
+ self.assertEqual(len(props), 1)
+ props = props[0]
+
+ supportedlock = props.findall("{DAV:}supportedlock")
+ self.assertEqual(len(supportedlock), 1)
+ assertXMLEqual(supportedlock[0], """<ns0:supportedlock xmlns:ns0="DAV:">
+<ns0:lockentry xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+</ns0:lockentry>
+<ns0:lockentry xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:shared xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+</ns0:lockentry></ns0:supportedlock>""")
+
+ lockdiscovery = props.findall("{DAV:}lockdiscovery")
+ self.assertEqual(len(lockdiscovery), 1)
+ assertXMLEqual(lockdiscovery[0], """<ns0:lockdiscovery xmlns:ns0="DAV:">
+<ns0:activelock xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+ <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+ <ns0:owner xmlns:D="DAV:">
+ <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+ </ns0:owner>
+ <ns0:timeout xmlns:ns0="DAV:">Second-60800</ns0:timeout>
+ <ns0:locktoken xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+ </ns0:locktoken>
+ <ns0:lockroot xmlns:ns0="DAV:">http://localhost/a</ns0:lockroot>
+</ns0:activelock></ns0:lockdiscovery>""" % locktoken[1:-1])
+
+ def test_recursive_lock(self):
+ self.createFolderFileStructure()
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "infinity",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+ locktoken = response.getHeader("lock-token")
+ self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+
+ rootfolder = self.getRootFolder()["a"]
+ subresource = rootfolder["r2"]
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(""), {"HTTP_HOST": "localhost"})
+
+ lockmanager = IDAVLockmanager(rootfolder)
+ self.assertEqual(lockmanager.getActivelock(request).lockroot,
+ "http://localhost/a")
+ self.assertEqual(
+ lockmanager.getActivelock().locktoken[0], locktoken[1:-1])
+
+ lockmanager = IDAVLockmanager(subresource)
+ self.assertEqual(lockmanager.getActivelock(request).lockroot,
+ "http://localhost/a")
+ self.assertEqual(
+ lockmanager.getActivelock().locktoken[0], locktoken[1:-1])
+
+ def test_lock_invalid_depth(self):
+ file = self.addResource("/testresource",
+ "some file content", "Test Resource")
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/testresource", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "1",
+ "TIMEOUT": "Infinite, Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body,
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+
+ def test_refresh_lock(self):
+ self.login()
+ self.createCollectionResourceStructure()
+
+ lockmanager = IDAVLockmanager(self.getRootFolder()["a"])
+ owner = """<D:owner xmlns:D="DAV:">
+<D:href>mailto:michael</D:href>
+</D:owner>"""
+ lockmanager.lock(u"exclusive", u"write", owner,
+ datetime.timedelta(seconds = 1000), "infinity")
+ locktoken = lockmanager.getActivelock().locktoken[0]
+ self.assertEqual(lockmanager.getActivelock().timeout, u"Second-1000")
+ transaction.commit()
+ self.logout()
+
+ response = self.publish("/a/r2", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "TIMEOUT": "Second-3600",
+ "IF": "<%s>" % locktoken})
+
+ self.assertEqual(response.getStatus(), 200)
+ assertXMLEqual(response.getBody(), """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+ <ns0:activelock xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+ <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+ <ns0:owner xmlns:D="DAV:">
+<ns0:href>mailto:michael</ns0:href>
+ </ns0:owner>
+ <ns0:timeout xmlns:ns0="DAV:">Second-3600</ns0:timeout>
+ <ns0:locktoken xmlns:ns0="DAV:"><ns0:href xmlns:ns0="DAV:">%s</ns0:href></ns0:locktoken>
+ <ns0:lockroot xmlns:ns0="DAV:">http://localhost/a</ns0:lockroot>
+ </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken)
+
+ def test_invalid_lock_request_uri(self):
+ self.login()
+ file = self.addResource("/testresource", "some file content",
+ "Test Resource")
+
+ lockmanager = IDAVLockmanager(file)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ datetime.timedelta(seconds = 3600), '0')
+ transaction.commit()
+ self.logout()
+
+ response = self.publish("/testresource", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "TIMEOUT": "Second-3600",
+ "IF": "<BADLOCKTOKEN>"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 412)
+
+ def test_no_lock_request_uri(self):
+ self.login()
+ file = self.addResource("/testresource", "some file content",
+ "Test Resource")
+
+ lockmanager = IDAVLockmanager(file)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ datetime.timedelta(seconds = 3600), '0')
+ transaction.commit()
+ self.logout()
+
+ response = self.publish("/testresource", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "TIMEOUT": "Second-3600"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 412)
+
+ def test_not_locked_resource(self):
+ file = self.addResource("/testresource", "some file content",
+ "Test Resource")
+
+ response = self.publish("/testresource", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "TIMEOUT": "Second-3600",
+ "IF": "<BADLOCKTOKEN>"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 412)
+
+
+class UNLOCKTestCase(zope.webdav.ftests.dav.DAVTestCase):
+
+ def setUp(self):
+ super(UNLOCKTestCase, self).setUp()
+
+ sitemanager = component.getSiteManager(self.getRootFolder())
+ self.utility = addUtility(sitemanager, "",
+ zope.locking.interfaces.ITokenUtility,
+ TokenUtility())
+
+ def tearDown(self):
+ del self.utility
+ super(UNLOCKTestCase, self).tearDown()
+
+ def test_unlock_file(self):
+ self.login()
+ file = self.addFile("/testfile", "some file content", "text/plain")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+ lockmanager.lock(scope = "exclusive", type = "write",
+ owner = """<D:owner xmlns:D="DAV:">
+ <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+ duration = datetime.timedelta(100),
+ depth = "0")
+ transaction.commit()
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+ locktoken = lockmanager.getActivelock().locktoken[0]
+
+ # end the current interaction
+ self.logout()
+
+ response = self.publish("/testfile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "UNLOCK",
+ "LOCK_TOKEN": "<%s>" % locktoken})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(response.getBody(), "")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+
+ def test_unlock_file_bad_token(self):
+ self.login()
+ file = self.addFile("/testfile", "some file content", "text/plain")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+ lockmanager.lock(scope = "exclusive", type = "write",
+ owner = """<D:owner xmlns:D="DAV:">
+ <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+ duration = datetime.timedelta(100),
+ depth = "0")
+ transaction.commit()
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+ locktoken = "badtoken"
+
+ # end the current interaction
+ self.logout()
+
+ response = self.publish("/testfile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "UNLOCK",
+ "LOCK_TOKEN": "<%s>" % locktoken},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 409)
+ self.assertEqual(response.getBody(), "")
+
+ # file should be still locked
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_unlock_file_no_token(self):
+ self.login()
+ file = self.addFile("/testfile", "some file content", "text/plain")
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), False)
+ lockmanager.lock(scope = "exclusive", type = "write",
+ owner = """<D:owner xmlns:D="DAV:">
+ <D:href>mailto:michael at linux</D:href>
+</D:owner>""",
+ duration = datetime.timedelta(100),
+ depth = "0")
+ transaction.commit()
+
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ # end the current interaction
+ self.logout()
+
+ response = self.publish("/testfile", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "UNLOCK"},
+ handle_errors = True)
+
+ self.assertEqual(response.getStatus(), 400)
+ self.assert_("No lock-token header supplied" in response.getBody())
+
+ # file should be still locked
+ lockmanager = IDAVLockmanager(file)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_lock_folder_depth_inf_then_unlock(self):
+ self.createFolderFileStructure()
+
+ body ="""<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D='DAV:'>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+
+ response = self.publish(
+ "/a", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "LOCK",
+ "DEPTH": "infinity",
+ "TIMEOUT": "Second-4100000000",
+ "CONTENT_TYPE": "text/xml"},
+ request_body = body)
+
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getHeader("content-type"), "application/xml")
+ locktoken = response.getHeader("lock-token")
+ self.assert_(locktoken and locktoken[0] == "<" and locktoken[-1] == ">")
+ locktoken = locktoken[1:-1] # remove the <> characters
+
+ response = self.publish("/a/r2", basic = "mgr:mgrpw",
+ env = {"REQUEST_METHOD": "UNLOCK",
+ "LOCK_TOKEN": "<%s>" % locktoken})
+
+ self.assertEqual(response.getStatus(), 204)
+ self.assertEqual(response.getBody(), "")
+
+ lockmanager = IDAVLockmanager(self.getRootFolder()["a"]["r2"])
+ self.assertEqual(lockmanager.islocked(), False)
+
+ lockmanager = IDAVLockmanager(self.getRootFolder())
+ self.assertEqual(lockmanager.islocked(), False)
+
+ def test_supportedlock_prop(self):
+ file = self.addFile("/testfile", "some file content", "text/plain")
+ httpresponse, xmlbody = self.checkPropfind(
+ "/testfile", properties = "<D:prop><D:supportedlock /></D:prop>")
+
+ responses = xmlbody.findall("{DAV:}response")
+ self.assertEqual(len(responses), 1)
+ response = responses[0]
+
+ hrefs = response.findall("{DAV:}href")
+ self.assertEqual(len(hrefs), 1)
+ self.assertEqual(hrefs[0].text, "http://localhost/testfile")
+
+ propstats = response.findall("{DAV:}propstat")
+ self.assertEqual(len(propstats), 1)
+ props = propstats[0].findall("{DAV:}prop")
+ self.assertEqual(len(props), 1)
+
+ self.assertEqual(len(props[0]), 1)
+
+ expected = """<D:supportedlock xmlns:D="DAV:">
+<D:lockentry>
+ <D:lockscope><D:exclusive /></D:lockscope>
+ <D:locktype><D:write /></D:locktype>
+</D:lockentry>
+<D:lockentry>
+ <D:lockscope><D:shared /></D:lockscope>
+ <D:locktype><D:write /></D:locktype>
+</D:lockentry></D:supportedlock>"""
+ self.assertMSPropertyValue(response, "{DAV:}supportedlock",
+ tag = "{DAV:}lockentry",
+ prop_element = expected)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(LOCKNotAllowedTestCase),
+ unittest.makeSuite(LOCKTestCase),
+ unittest.makeSuite(UNLOCKTestCase),
+ ))
+
+
+if __name__ == "__main__":
+ unittest.main(defaultTest = "test_suite")
Property changes on: zope.webdav/trunk/src/zope/webdav/ftests/test_z3_locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,35 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <!--
+ ElementTree support for Zope (you probable what to edit this)
+
+ This file also configures which ElementTree engine to use for all the
+ zope.webdav unit tests also. See zope.webdav.testing.
+
+ The following ElementTree engines are supported.
+
+ zope.webdav.zetree.EtreeEtree - original elementtree python module
+
+ zope.webdav.zetree.LxmlEtree - the fast lxml engine.
+
+ zope.webdav.zetree.EtreePy25 - the original elementtree has included in
+ python2.5 and above.
+ -->
+
+ <utility
+ factory="zope.webdav.zetree.EtreeEtree"
+ />
+
+<!--
+ <utility
+ factory="zope.webdav.zetree.LxmlEtree"
+ />
+-->
+
+<!--
+ <utility
+ factory="zope.webdav.zetree.EtreePy25"
+ />
+-->
+
+</configure>
Property changes on: zope.webdav/trunk/src/zope/webdav/ietree-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/ietree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ietree.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/ietree.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+XXX - add comments into this interface.
+ - extend this interface to each of the different element tree engines. So
+ some example lxml supports things that the original elementtree
+ implementation did and vice versa. This way developers can say give me
+ an elementtree implementation support feature X. But we still need to be
+ carefull that we still have a common base interface from which to work
+ off.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+
+class IEtree(interface.Interface):
+
+ def Comment(text = None):
+ """
+ """
+
+ def dump(elem):
+ """
+ """
+
+ def Element(tag, attrib = {}, **extra):
+ """
+ """
+
+ def ElementTree(element = None, file = None):
+ """
+ """
+
+ def XML(text):
+ """
+ """
+
+ def fromstring(text):
+ """
+ """
+
+ def iselement(element):
+ """
+ """
+
+ def iterparse(source, events = None):
+ """
+ """
+
+ def parse(source, parser = None):
+ """
+ """
+
+ def PI(target, text = None):
+ """
+ """
+
+ def ProcessingInstruction(target, text = None):
+ """
+ """
+
+ def QName(text_or_uri, tag = None):
+ """
+ """
+
+ def SubElement(parent, tag, attrib = {}, **extra):
+ """
+ """
+
+ def tostring(element, encoding = None):
+ """
+ """
+
+ def TreeBuilder(element_factory = None):
+ """
+ """
+
+ def XMLTreeBuilder(html = 0, target = None):
+ """
+ """
Property changes on: zope.webdav/trunk/src/zope/webdav/ietree.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/interfaces.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/interfaces.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/interfaces.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,583 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""WebDAV-specific interfaces
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import UserDict
+from zope import interface
+from zope.interface.interfaces import IInterface
+from zope.interface.common.interfaces import IException
+from zope.interface.common.mapping import IMapping
+from zope.interface.common.sequence import IFiniteSequence
+from zope import schema
+from zope.schema.interfaces import IField
+from zope.component.interfaces import IView
+
+from zope.publisher.interfaces import IPublication
+from zope.publisher.interfaces.http import IHTTPRequest, IHTTPResponse
+from zope.app.publication.interfaces import IRequestFactory
+
+################################################################################
+#
+# Common WebDAV specific errors
+#
+################################################################################
+
+class IBadRequest(IException):
+ """
+ Some information passed in the request is in invalid.
+ """
+
+ request = interface.Attribute("""The request in which the error occured.""")
+
+ message = interface.Attribute("""Message to send back to the user.""")
+
+class BadRequest(Exception):
+ interface.implements(IBadRequest)
+
+ def __init__(self, request, message = None):
+ self.request = request
+ self.message = message
+
+ def __str__(self):
+ return "%r, %r" %(self.request, self.message)
+
+
+class IUnsupportedMediaType(IException):
+ """
+ Unsupported media type.
+ """
+
+ context = interface.Attribute(""" """)
+
+ message = interface.Attribute(""" """)
+
+class UnsupportedMediaType(Exception):
+ interface.implements(IUnsupportedMediaType)
+
+ def __init__(self, context, message = u""):
+ self.context = context
+ self.message = message
+
+ def __str__(self):
+ return "%r, %r" %(self.context, self.message)
+
+
+class IBadGateway(IException):
+ """
+ The server, while acting as a gateway or proxy, received an invalid
+ response from the upstream server it accessed in attempting to
+ fulfill the request.
+ """
+
+ context = interface.Attribute(""" """)
+
+ request = interface.Attribute(""" """)
+
+class BadGateway(Exception):
+ interface.implements(IBadGateway)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __str__(self):
+ return "%r, %r" %(self.context, self.request)
+
+
+class IDAVException(IException):
+ """
+ Base interface for a common set of exceptions that should be raised
+ to inform the client that something has gone a bit to the left.
+ """
+
+ resource = interface.Attribute("""
+ Possible resource on which this error was raised.
+ """)
+
+ ## XXX - this attribute was added has an after-thought. It currently
+ ## isn't being used and has such it properly should be removed.
+ propertyname = interface.Attribute("""
+ Possible property name for which the error applies.
+ """)
+
+ message = interface.Attribute("""
+ Human readable message detailing what went wrong.
+ """)
+
+class DAVException(Exception):
+
+ def __init__(self, resource, propertyname = None, message = None):
+ self.resource = resource
+ self.propertyname = propertyname
+ self.message = message
+
+ def __str__(self):
+ return self.message
+
+
+class IConflictError(IDAVException):
+ """
+ The client has provided a value whose semantics are not appropriate for
+ current state of the resource.
+ """
+
+class ConflictError(DAVException):
+ interface.implements(IConflictError)
+
+
+class IForbiddenError(IDAVException):
+ """
+ The client, for reasons the server chooses not to specify, cannot alter
+ the resource.
+ """
+
+class ForbiddenError(DAVException):
+ interface.implements(IForbiddenError)
+
+
+class IUnprocessableError(IDAVException):
+ """
+ The entity body couldn't be parsed or is invalid.
+ """
+
+class UnprocessableError(DAVException):
+ interface.implements(IUnprocessableError)
+
+
+# XXX - PropertyNotFound should go away and instead we should
+# reuse the NotFound exception
+class IPropertyNotFound(IDAVException):
+ """
+ The requested property was not found.
+ """
+
+class PropertyNotFound(DAVException):
+ interface.implements(IPropertyNotFound)
+
+
+class IPreconditionFailed(IDAVException):
+ """
+ Some condition header failed to evalute to True.
+ """
+
+class PreconditionFailed(DAVException):
+ interface.implements(IPreconditionFailed)
+
+
+class IFailedDependency(IDAVException):
+ """
+ Method could not be performed on the resource because the requested
+ action depended on another action and that action failed.
+ """
+
+class FailedDependency(DAVException):
+ interface.implements(IFailedDependency)
+
+
+class IAlreadyLocked(IDAVException):
+ """
+ The resource is already locked.
+ """
+
+class AlreadyLocked(Exception):
+ interface.implements(IAlreadyLocked)
+
+ def __init__(self, context, message = None):
+ self.resource = context
+ self.propertyname = None
+ self.message = None
+
+
+class IWebDAVErrors(IFiniteSequence, IException):
+ """
+ List-like container of all exceptions that occured during the process
+ of a request. All the exceptions should be viewed and returned to the
+ client via a multi-status response.
+ """
+
+ def append(error):
+ """
+ Append a error to the collection of errors.
+ """
+
+class WebDAVErrors(Exception):
+ """
+ Collect has many errors has we can and then provide a view of all the
+ errors to the user.
+ """
+ interface.implements(IWebDAVErrors)
+
+ def __init__(self, context, errors = ()):
+ Exception.__init__(self)
+ self.errors = errors
+ self.context = context
+
+ def append(self, error):
+ self.errors += (error,)
+
+ def __len__(self):
+ return len(self.errors)
+
+ def __iter__(self):
+ return iter(self.errors)
+
+ def __getitem__(self, index):
+ return self.errors[index]
+
+
+class IWebDAVPropstatErrors(IMapping, IException):
+ """
+ Exception containing a mapping of property names to exceptions. This is
+ used to collect all the exceptions that occured when modifying the
+ properties on a resource.
+ """
+
+ context = interface.Attribute("""
+ The context on which all the properties are defined.
+ """)
+
+class WebDAVPropstatErrors(UserDict.IterableUserDict, Exception):
+ interface.implements(IWebDAVPropstatErrors)
+
+ def __init__(self, context):
+ ## Allowing users to pass the list of errors into this exception
+ ## is causing problems. This optional dictionary was remembering
+ ## the arguements from previous functional tests.
+ ## See the test_remove_live_prop in test_propfind.
+ UserDict.IterableUserDict.__init__(self)
+ Exception.__init__(self)
+ self.context = context
+
+################################################################################
+#
+# WebDAV related publication interfaces.
+#
+################################################################################
+
+class IWebDAVMethod(interface.Interface):
+ """
+ A object implementing this method is a callable object that implements
+ one of the WebDAV specific methods.
+ """
+
+
+class IWebDAVResponse(IHTTPResponse):
+ """
+ A WebDAV Response object.
+ """
+
+
+class IWebDAVRequest(IHTTPRequest):
+ """
+ A WebDAV Request.
+
+ This request should only be used to respond to a WebDAV specific request
+ that contains either XML or nothing has a payload.
+ """
+
+ xmlDataSource = interface.Attribute("""
+ xml.dom.Document instance representing the input stream has an XML document.
+
+ If there was no input or the input wasn't in XML then this attribute
+ is None.
+ """)
+
+ content_type = interface.Attribute("""
+ A string representing the content type of the input stream without any
+ parameters.
+ """)
+
+
+class IWebDAVRequestFactory(IRequestFactory):
+ """
+ WebDAV request factory.
+ """
+
+################################################################################
+#
+# WebDAV interfaces for widgets, properties and management of the widgets
+# and properties.
+#
+################################################################################
+
+class IBaseDAVWidget(IView):
+ """
+ DAVWidget are views of IDAVProperty objects, generally used by PROPFIND
+
+ - context is a IDAVProperty.
+
+ - request is a IHTTPRequest.
+
+ """
+
+ name = interface.Attribute("""
+ The local naem of the property.
+ """)
+
+ namespace = interface.Attribute("""
+ The XML namespace to which the property belongs.
+ """)
+
+
+class IIDAVWidget(IInterface):
+ """
+ Meta-interface marker for classes that when instanciated will implement
+ IDAVWidget.
+ """
+
+
+class IIDAVInputWidget(IInterface):
+ """
+ Meta-interface marker for classes that when instanciated will implement
+ IDAVInputWidget.
+ """
+
+
+class IDAVInputWidget(IBaseDAVWidget):
+ """
+ For use with in the PROPPATCH method.
+ """
+
+ def toFieldValue(element):
+ """
+ Convert the ElementTree element to a value which can be legally
+ assigned to the field.
+ """
+
+ 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 hasInput():
+ """
+ Returns ``True`` if the widget has input.
+
+ 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.
+
+ 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 IDAVWidget(IBaseDAVWidget):
+ """
+ For use in rendering dav properties.
+ """
+
+ def render():
+ """
+ Render the property to a XML fragment, according to the relevent
+ specification.
+ """
+
+
+class IDAVErrorWidget(interface.Interface):
+ """
+ Widget used to render any errors that should be included in a multi-status
+ response.
+ """
+
+ status = interface.Attribute("""
+ The HTTP status code.
+ """)
+
+ errors = interface.Attribute("""
+ List of etree elements that is a precondition or a postcondition code.
+ """)
+
+ propstatdescription = interface.Attribute("""
+ Contains readable information about an error that occured and applies
+ to all properties contained within the current propstat XML element.
+ """)
+
+ responsedescription = interface.Attribute("""
+ Contains readable information about an error that occured and applies to
+ all resources contained within the current response XML element.
+ """)
+
+################################################################################
+#
+# Namespace management
+#
+################################################################################
+
+class IDAVProperty(interface.Interface):
+ """
+ Data structure that holds information about a live WebDAV property.
+ """
+
+ __name__ = schema.ASCII(
+ title = u"Name",
+ description = u"Local name of the WebDAV property.",
+ required = True)
+
+ namespace = schema.URI(
+ title = u"Namespace",
+ description = u"WebDAV namespace to which this property belongs.",
+ required = True)
+
+ field = schema.Object(
+ title = u"Field",
+ description = u"""Zope schema field that defines the data of the live
+ WebDAV property.""",
+ schema = IField,
+ required = True)
+
+ custom_widget = schema.Object(
+ title = u"Custom Widget",
+ description = u"""Factory to use for widget construction. If None then
+ a multi-adapter implementing IDAVWidget.""",
+ default = None,
+ schema = IIDAVWidget,
+ required = False)
+
+ custom_input_widget = schema.Object(
+ title = u"Custom Input Widget",
+ description = u"""Factory to use for widget construction. If None then
+ a multi-adapter implementing IDAVInputWidget.""",
+ default = None,
+ schema = IIDAVInputWidget,
+ required = False)
+
+ restricted = schema.Bool(
+ title = u"Restricted property",
+ description = u"""If True then this property should not be included in
+ the response to an allprop PROPFIND request.""",
+ default = False,
+ required = False)
+
+ iface = schema.Object(
+ title = u"Storage interface",
+ description = u"""Interface which declares how to find, store
+ the property""",
+ schema = IInterface,
+ required = True)
+
+################################################################################
+#
+# Application specific interfaces.
+#
+################################################################################
+
+class IOpaquePropertyStorage(interface.Interface):
+ """
+ Declaration of an adapter that is used to store, remove and query all
+ dead properties on a resource.
+ """
+
+ def getAllProperties():
+ """
+ Return iterable of all IDAVProperty objects defined for the
+ current context.
+ """
+
+ def hasProperty(tag):
+ """
+ Return boolean indicating whether the named property exists has
+ a dead property for the current resource.
+ """
+
+ def getProperty(tag):
+ """
+ Return a IDAVProperty utility for the named property.
+ """
+
+ def setProperty(tag, value):
+ """
+ Set the value of the named property to value.
+ """
+
+ def removeProperty(tag):
+ """
+ Remove the named property from this property.
+ """
+
+
+class IDAVLockmanager(interface.Interface):
+ """
+ Helper adapter for manage locks in an independent manner. Different
+ Zope systems are going to have there own locking mechanisms for example
+ Zope3 and CPS have two conflicting locking mechanisms.
+ """
+
+ def islockable():
+ """
+ Return False when the current context can never be locked or locking
+ is not support in your setup.
+
+ For example in Zope3, zope.webdav.lockingutils.DAVLockmanager depends
+ on a persistent zope.locking.interfaces.ITokenUtility utility being
+ present and registered. If this utility can not be locked be then
+ we can never use this implementation of IDAVLockmanager.
+ """
+
+ def lock(scope, type, owner, duration, depth):
+ """
+ Lock the current context passing in all the possible information
+ neccessary for generating a lock token.
+
+ If the context can't be locked raise an exception that has a
+ corresponding IDAVErrorWidget implementation so that we can extract
+ what went wrong.
+
+ If `depth` has the value `infinity` and the context is a folder then
+ recurse through all the subitems locking them has you go.
+
+ Raise a AlreadyLockedError if some resource is already locked.
+ """
+
+ def getActivelock(request = None):
+ """
+ Return an implementation of zope.webdav.coreproperties.IActiveLock
+
+ If you are testing the lock manager then you don't need to pass
+ in the request - just don't access the lockroot attribute :-)
+ """
+
+ def refreshlock(timeout):
+ """
+ Refresh to lock token associated with this resource.
+ """
+
+ def unlock():
+ """
+ Unlock the current context.
+ """
+
+ def islocked():
+ """
+ Is the current context locked or not. Return True, otherwise False.
+ """
Property changes on: zope.webdav/trunk/src/zope/webdav/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/locking.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/locking.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,277 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""WebDAV LOCK and UNLOCK methods.
+
+Request contains a lockinfo XML element.
+
+ <!ELEMENT lockinfo (lockscope, locktype, owner?) >
+ <!ELEMENT lockscope (exclusive | shared) >
+ <!ELEMENT locktype (write) >
+ <!ELEMENT shared EMPTY >
+ <!ELEMENT exclusive EMPTY >
+ <!ELEMENT write EMPTY >
+ <!ELEMENT owner ANY >
+
+Response contains a lockdiscovery XML element.
+
+ <!ELEMENT lockdiscovery (activelock)* >
+ <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?,
+ locktoken?, lockroot)>
+ <!ELEMENT depth (#PCDATA) >
+ "0" | "1" | "infinity"
+ <!ELEMENT timeout (#PCDATA) >
+ TimeType (defined in Section 10.7).
+ <!ELEMENT locktoken (href) >
+ locktoken uri
+ <!ELEMENT lockroot (href) >
+ The href element contains the root of the lock.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import datetime
+
+from zope import component
+from zope import interface
+
+import zope.webdav.interfaces
+import zope.webdav.properties
+from zope.webdav.coreproperties import IActiveLock
+from zope.webdav.ietree import IEtree
+import zope.webdav.utils
+
+MAXTIMEOUT = (2L ** 32) - 1
+DEFAULTTIMEOUT = 12 * 60L
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(zope.webdav.interfaces.IWebDAVMethod)
+def LOCK(context, request):
+ """
+ If we can adapt the context to a lock manager then we should be able to
+ lock the resource.
+ """
+ lockmanager = zope.webdav.interfaces.IDAVLockmanager(context, None)
+ if lockmanager is None:
+ return None
+ if not lockmanager.islockable():
+ return None
+
+ return LOCKMethod(context, request)
+
+
+class LOCKMethod(object):
+ """
+ LOCK handler for all objects.
+ """
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def getDepth(self):
+ # default is infinity
+ return self.request.getHeader("depth", "infinity")
+
+ def getTimeout(self):
+ """
+ Return a datetime.timedelta object representing the duration of
+ the requested lock token.
+
+ XXX - this method is broken it needs to be able to parse
+ multiple timetypes.
+ """
+ timeoutheader = self.request.getHeader("timeout", "infinity")
+
+ timeoutheader = timeoutheader.strip().lower()
+ t = timeoutheader.split("-")
+
+ if len(t) == 2 and t[0].lower().lower() == "second":
+ th = t[1]
+ elif len(t) == 1 and t[0] == "infinity":
+ th = t[0]
+ else:
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"Invalid TIMEOUT header")
+
+ if th == "infinite" or th == "infinity":
+ timeout = DEFAULTTIMEOUT
+ else:
+ try:
+ timeout = long(th)
+ except ValueError:
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"Invalid TIMEOUT header")
+
+ if timeout > MAXTIMEOUT:
+ timeout = DEFAULTTIMEOUT
+
+ return datetime.timedelta(seconds = timeout)
+
+ def LOCK(self):
+ # The Lock-Token header is not returned in the response for a
+ # successful refresh LOCK request.
+ refreshlock = False
+
+ if self.request.xmlDataSource is None:
+ errors = self.handleLockRefresh()
+ refreshlock = True
+ else: # Body => try to lock the resource
+ errors = self.handleLock()
+
+ if errors:
+ raise zope.webdav.interfaces.WebDAVErrors(self.context, errors)
+
+ etree = component.getUtility(IEtree)
+
+ davprop, adapter = zope.webdav.properties.getProperty(
+ self.context, self.request, "{DAV:}lockdiscovery")
+ davwidget = zope.webdav.properties.getWidget(
+ davprop, adapter, self.request)
+ propel = etree.Element(etree.QName("DAV:", "prop"))
+ propel.append(davwidget.render())
+
+ activelock = component.getMultiAdapter((self.context, self.request),
+ IActiveLock)
+
+ self.request.response.setStatus(200)
+ self.request.response.setHeader("Content-Type", "application/xml")
+ if not refreshlock:
+ self.request.response.setHeader("Lock-Token",
+ "<%s>" % activelock.locktoken[0])
+
+ return etree.tostring(propel)
+
+ def handleLockRefresh(self):
+ lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+
+ if not lockmanager.islocked():
+ raise zope.webdav.interfaces.PreconditionFailed(
+ self.context, message = u"Context is not locked.")
+
+ locktoken = lockmanager.getActivelock().locktoken[0]
+ request_uri = self.request.getHeader("IF", "")
+ if not request_uri or \
+ request_uri[0] != "<" or request_uri[-1] != ">" or \
+ request_uri[1:-1] != locktoken:
+ raise zope.webdav.interfaces.PreconditionFailed(
+ self.context, message = u"Lock-Token doesn't match request uri")
+
+ timeout = self.getTimeout()
+ lockmanager.refreshlock(timeout)
+
+ def handleLock(self):
+ errors = []
+
+ xmlsource = self.request.xmlDataSource
+ if xmlsource.tag != "{DAV:}lockinfo":
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"LOCK request body must be a `lockinfo' XML element")
+
+ depth = self.getDepth()
+ if depth not in ("0", "infinity"):
+ raise zope.webdav.interfaces.BadRequest(
+ self.request,
+ u"Invalid depth header. Must be either '0' or 'infinity'")
+
+ etree = component.getUtility(IEtree)
+
+ timeout = self.getTimeout()
+
+ lockscope = xmlsource.find("{DAV:}lockscope")
+ if not lockscope:
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"No `{DAV:}lockscope' XML element in request")
+ lockscope_str = zope.webdav.utils.parseEtreeTag(lockscope[0].tag)[1]
+
+ locktype = xmlsource.find("{DAV:}locktype")
+ if not locktype:
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"No `{DAV:}locktype' XML element in request")
+ locktype_str = zope.webdav.utils.parseEtreeTag(locktype[0].tag)[1]
+
+ owner = xmlsource.find("{DAV:}owner")
+ owner_str = etree.tostring(owner)
+
+ lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+
+ roottoken = None
+ try:
+ lockmanager.lock(scope = lockscope_str,
+ type = locktype_str,
+ owner = owner_str,
+ duration = timeout,
+ depth = depth)
+ except zope.webdav.interfaces.AlreadyLocked, error:
+ errors.append(error)
+
+ return errors
+
+################################################################################
+#
+# UNLOCK method.
+#
+################################################################################
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(zope.webdav.interfaces.IWebDAVMethod)
+def UNLOCK(context, request):
+ """
+ If we can adapt the context to a lock manager then we should be able to
+ unlock the resource.
+ """
+ lockmanager = zope.webdav.interfaces.IDAVLockmanager(context, None)
+ if lockmanager is None:
+ return None
+ if not lockmanager.islockable():
+ return None
+
+ return UNLOCKMethod(context, request)
+
+
+class UNLOCKMethod(object):
+ """
+ UNLOCK handler for all objects.
+ """
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def UNLOCK(self):
+ locktoken = self.request.getHeader("lock-token", "")
+ if len(locktoken) > 1 and locktoken[0] == "<" and locktoken[-1] == ">":
+ locktoken = locktoken[1:-1]
+
+ if not locktoken:
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"No lock-token header supplied")
+
+ lockmanager = zope.webdav.interfaces.IDAVLockmanager(self.context)
+ if not lockmanager.islocked() or \
+ lockmanager.getActivelock(self.request).locktoken[0] != locktoken:
+ raise zope.webdav.interfaces.ConflictError(
+ self.context, message = "object is locked or the lock isn't" \
+ "in the scope the passed.")
+
+ lockmanager.unlock()
+
+ self.request.response.setStatus(204)
+ return ""
Property changes on: zope.webdav/trunk/src/zope/webdav/locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/lockingutils.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/lockingutils.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/lockingutils.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,862 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Support for using zope.locking has a locking mechanism for WebDAV locking.
+
+Note that we can't use zope.locking.utility.TokenUtility has a global utility.
+This is because if a recursive lock request fails half through then the
+utility has already been modified and since it is not persistent
+transaction.abort doesn't unlock the pervious successful locks. Since the
+utility gets into an inconsistent state.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import persistent
+import time
+import random
+import datetime
+from BTrees.OOBTree import OOBTree
+from zope import component
+from zope import interface
+from zope.locking import tokens
+import zope.locking.interfaces
+from zope.security.proxy import removeSecurityProxy
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.traversing.browser.absoluteurl import absoluteURL
+from zope.app.container.interfaces import IReadContainer
+
+from zope.webdav.coreproperties import ILockEntry, IDAVSupportedlock, \
+ IActiveLock
+import zope.webdav.interfaces
+
+
+INDIRECT_INDEX_KEY = 'zope.app.dav.lockingutils'
+
+_randGen = random.Random(time.time())
+
+class IIndirectToken(zope.locking.interfaces.IToken,
+ zope.locking.interfaces.IEndable):
+ """
+ """
+
+ roottoken = interface.Attribute("""
+ Return the root lock token against which this token is locked.
+ """)
+
+
+class IndirectToken(persistent.Persistent):
+ """
+
+ Most of these tests have being copied from the README.txt file in
+ zope.locking
+
+ Some initial setup including creating some demo content.
+
+ >>> from zope.locking import utility, utils
+ >>> util = utility.TokenUtility()
+ >>> component.getGlobalSiteManager().registerUtility(
+ ... util, zope.locking.interfaces.ITokenUtility)
+
+ Setup some content to test on.
+
+ >>> demofolder = DemoFolder(None, 'demofolderroot')
+ >>> demofolder['demo1'] = Demo()
+ >>> demofolder['demofolder1'] = DemoFolder()
+ >>> demofolder['demofolder1']['demo'] = Demo()
+
+ Lock the root folder with an exclusive lock.
+
+ >>> lockroot = tokens.ExclusiveLock(demofolder, 'michael')
+ >>> res = util.register(lockroot)
+
+ Now indirectly all the descended objects of the root folder against the
+ exclusive lock token we used to lock this folder with.
+
+ >>> lock1 = IndirectToken(demofolder['demo1'], lockroot)
+ >>> lock2 = IndirectToken(demofolder['demofolder1'], lockroot)
+ >>> lock3 = IndirectToken(demofolder['demofolder1']['demo'], lockroot)
+ >>> res1 = util.register(lock1)
+ >>> lock1 is util.get(demofolder['demo1'])
+ True
+ >>> res2 = util.register(lock2)
+ >>> lock2 is util.get(demofolder['demofolder1'])
+ True
+ >>> res3 = util.register(lock3)
+ >>> lock3 is util.get(demofolder['demofolder1']['demo'])
+ True
+
+ Make sure that the lockroot contains an index of all the toekns locked
+ against in its annotations
+
+ >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+ 3
+
+ Check that the IEndable properties are None
+
+ >>> res1.expiration == lockroot.expiration == None
+ True
+ >>> res1.duration == lockroot.duration == None
+ True
+ >>> res1.duration == lockroot.remaining_duration == None
+ True
+ >>> res1.started == lockroot.started
+ True
+ >>> lockroot.started is not None
+ True
+
+ All the indirect locktokens and the lookroot share the same annotations
+
+ >>> lockroot.annotations[u'webdav'] = u'test webdav indirect locking'
+ >>> res1.annotations[u'webdav']
+ u'test webdav indirect locking'
+
+ All the lock tokens have the same principals
+
+ >>> list(res1.principal_ids)
+ ['michael']
+ >>> list(lockroot.principal_ids)
+ ['michael']
+
+ None of the locks have ended yet, and they share the same utility.
+
+ >>> res1.ended is None
+ True
+ >>> lockroot.ended is None
+ True
+ >>> lockroot.utility is res1.utility
+ True
+
+ Expire the lock root
+
+ >>> now = utils.now()
+ >>> res3.end()
+
+ Now all the descendent objects of the lockroot and the lockroot itself
+ are unlocked.
+
+ >>> util.get(demofolder) is None
+ True
+ >>> util.get(demofolder['demo1']) is None
+ True
+ >>> util.get(demofolder['demofolder1']['demo']) is None
+ True
+
+ Also all the tokens has ended after now.
+
+ >>> lock1.ended is not None
+ True
+ >>> lock2.ended > now
+ True
+ >>> lock1.ended is lock2.ended
+ True
+ >>> lock3.ended is lockroot.ended
+ True
+
+ Test the event subscribers.
+
+ >>> ev = events[-1]
+ >>> zope.locking.interfaces.ITokenEndedEvent.providedBy(ev)
+ True
+ >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+ 3
+ >>> removeEndedTokens(ev)
+ >>> len(lockroot.annotations[INDIRECT_INDEX_KEY])
+ 0
+
+ Test all the endable attributes
+
+ >>> import datetime
+ >>> one = datetime.timedelta(hours = 1)
+ >>> two = datetime.timedelta(hours = 2)
+ >>> three = datetime.timedelta(hours = 3)
+ >>> four = datetime.timedelta(hours = 4)
+ >>> lockroot = tokens.ExclusiveLock(demofolder, 'john', three)
+ >>> dummy = util.register(lockroot)
+ >>> indirect1 = IndirectToken(demofolder['demo1'], lockroot)
+ >>> dummy = util.register(indirect1)
+ >>> indirect1.duration
+ datetime.timedelta(0, 10800)
+ >>> lockroot.duration == indirect1.duration
+ True
+ >>> indirect1.ended is None
+ True
+ >>> indirect1.expiration == indirect1.started + indirect1.duration
+ True
+
+ Now try to
+
+ >>> indirect1.expiration = indirect1.started + one
+ >>> indirect1.expiration == indirect1.started + one
+ True
+ >>> indirect1.expiration == lockroot.expiration
+ True
+ >>> indirect1.duration == one
+ True
+
+ Now test changing the duration attribute
+
+ >>> indirect1.duration = four
+ >>> indirect1.duration == lockroot.duration
+ True
+ >>> indirect1.duration
+ datetime.timedelta(0, 14400)
+
+ Now check the remain_duration code
+
+ >>> import pytz
+ >>> def hackNow():
+ ... return (datetime.datetime.now(pytz.utc) +
+ ... datetime.timedelta(hours=2))
+ ...
+ >>> import zope.locking.utils
+ >>> oldNow = zope.locking.utils.now
+ >>> zope.locking.utils.now = hackNow # make code think it's 2 hours later
+ >>> indirect1.duration
+ datetime.timedelta(0, 14400)
+ >>> two >= indirect1.remaining_duration >= one
+ True
+ >>> indirect1.remaining_duration -= one
+ >>> one >= indirect1.remaining_duration >= datetime.timedelta()
+ True
+ >>> three + datetime.timedelta(minutes = 1) >= indirect1.duration >= three
+ True
+
+ Since we modified the remaining_duration attribute a IExpirationChagedEvent
+ should have being fired.
+
+ >>> ev = events[-1]
+ >>> from zope.interface.verify import verifyObject
+ >>> from zope.locking.interfaces import IExpirationChangedEvent
+ >>> verifyObject(IExpirationChangedEvent, ev)
+ True
+ >>> ev.object is lockroot
+ True
+
+ Now pretend that it is a day later, the indirect token and the lock root
+ will have timed out sliently.
+
+ >>> def hackNow():
+ ... return (
+ ... datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1))
+ ...
+ >>> zope.locking.utils.now = hackNow # make code think it is a day later
+ >>> indirect1.ended == indirect1.expiration
+ True
+ >>> lockroot.ended == indirect1.ended
+ True
+ >>> util.get(demofolder['demo1']) is None
+ True
+ >>> util.get(demofolder['demo1'], util) is util
+ True
+ >>> indirect1.remaining_duration == datetime.timedelta()
+ True
+ >>> indirect1.end()
+ Traceback (most recent call last):
+ ...
+ EndedError
+
+ Once a lock has ended, the timeout can no longer be changed.
+
+ >>> indirect1.duration = datetime.timedelta(days=2)
+ Traceback (most recent call last):
+ ...
+ EndedError
+
+ Now undo our hack.
+
+ >>> zope.locking.utils.now = oldNow # undo the hack
+ >>> indirect1.end() # really end the token
+ >>> util.get(demofolder) is None
+ True
+
+ Now test the simple SharedLock with an indirect token.
+
+ >>> lockroot = tokens.SharedLock(demofolder, ('john', 'mary'))
+ >>> dummy = util.register(lockroot)
+ >>> sharedindirect = IndirectToken(demofolder['demo1'], lockroot)
+ >>> dummy = util.register(sharedindirect)
+ >>> sorted(sharedindirect.principal_ids)
+ ['john', 'mary']
+ >>> sharedindirect.add(('jane',))
+ >>> sorted(lockroot.principal_ids)
+ ['jane', 'john', 'mary']
+ >>> sorted(sharedindirect.principal_ids)
+ ['jane', 'john', 'mary']
+ >>> sharedindirect.remove(('mary',))
+ >>> sorted(sharedindirect.principal_ids)
+ ['jane', 'john']
+ >>> sorted(lockroot.principal_ids)
+ ['jane', 'john']
+ >>> lockroot.remove(('jane',))
+ >>> sorted(sharedindirect.principal_ids)
+ ['john']
+ >>> sorted(lockroot.principal_ids)
+ ['john']
+ >>> sharedindirect.remove(('john',))
+ >>> util.get(demofolder) is None
+ True
+ >>> util.get(demofolder['demo1']) is None
+ True
+
+ Test using the shared lock token methods on a non shared lock
+
+ >>> lockroot = tokens.ExclusiveLock(demofolder, 'john')
+ >>> dummy = util.register(lockroot)
+ >>> indirect1 = IndirectToken(demofolder['demo1'], lockroot)
+ >>> dummy = util.register(indirect1)
+ >>> dummy is indirect1
+ True
+ >>> dummy.add('john')
+ Traceback (most recent call last):
+ ...
+ TypeError: can't add a principal to a non-shared token
+ >>> dummy.remove('michael')
+ Traceback (most recent call last):
+ ...
+ TypeError: can't add a principal to a non-shared token
+
+ Setup with wrong utility.
+
+ >>> util2 = utility.TokenUtility()
+ >>> roottoken = tokens.ExclusiveLock(demofolder, 'michael2')
+ >>> roottoken = util2.register(roottoken)
+ >>> roottoken.utility == util2
+ True
+
+ >>> indirecttoken = IndirectToken(demofolder['demo1'], roottoken)
+ >>> indirecttoken = util2.register(indirecttoken)
+ >>> indirecttoken.utility is util2
+ True
+ >>> indirecttoken.utility = util
+ Traceback (most recent call last):
+ ...
+ ValueError: cannot reset utility
+ >>> indirecttoken = IndirectToken(demofolder['demo1'], roottoken)
+ >>> indirecttoken.utility = util
+ Traceback (most recent call last):
+ ...
+ ValueError: Indirect tokens must be registered withsame utility has the root token
+
+ Cleanup test.
+
+ >>> component.getGlobalSiteManager().unregisterUtility(
+ ... util, zope.locking.interfaces.ITokenUtility)
+ True
+
+ """
+ interface.implements(IIndirectToken)
+
+ def __init__(self, target, token):
+ self.context = self.__parent__ = target
+ self.roottoken = token
+
+ _utility = None
+ @apply
+ def utility():
+ # IAbstractToken - this is the only hook I can find since
+ # it represents the lock utility in charge of this lock.
+ def get(self):
+ return self._utility
+ def set(self, value):
+ if self._utility is not None:
+ if value is not self._utility:
+ raise ValueError("cannot reset utility")
+ else:
+ assert zope.locking.interfaces.ITokenUtility.providedBy(value)
+ root = self.roottoken
+ if root.utility != value:
+ raise ValueError("Indirect tokens must be registered with" \
+ "same utility has the root token")
+ index = root.annotations.get(INDIRECT_INDEX_KEY, None)
+ if index is None:
+ index = root.annotations[INDIRECT_INDEX_KEY] = \
+ tokens.AnnotationsMapping()
+ index.__parent__ = root
+ key_ref = IKeyReference(self.context)
+ assert index.get(key_ref, None) is None, \
+ "context is already locked"
+ index[key_ref] = self
+ self._utility = value
+ return property(get, set)
+
+ @property
+ def principal_ids(self):
+ # IAbstractToken
+ return self.roottoken.principal_ids
+
+ @property
+ def started(self):
+ # IAbstractToken
+ return self.roottoken.started
+
+ @property
+ def annotations(self):
+ # See IToken
+ return self.roottoken.annotations
+
+ def add(self, principal_ids):
+ # ISharedLock
+ if not zope.locking.interfaces.ISharedLock.providedBy(self.roottoken):
+ raise TypeError, "can't add a principal to a non-shared token"
+ return self.roottoken.add(principal_ids)
+
+ def remove(self, principal_ids):
+ # ISharedLock
+ if not zope.locking.interfaces.ISharedLock.providedBy(self.roottoken):
+ raise TypeError, "can't add a principal to a non-shared token"
+ return self.roottoken.remove(principal_ids)
+
+ @property
+ def ended(self):
+ # IEndable
+ return self.roottoken.ended
+
+ @apply
+ def expiration(): # XXX - needs testing
+ # IEndable
+ def get(self):
+ return self.roottoken.expiration
+ def set(self, value):
+ self.roottoken.expiration = value
+ return property(get, set)
+
+ @apply
+ def duration(): # XXX - needs testing
+ # IEndable
+ def get(self):
+ return self.roottoken.duration
+ def set(self, value):
+ self.roottoken.duration = value
+ return property(get, set)
+
+ @apply
+ def remaining_duration():
+ # IEndable
+ def get(self):
+ return self.roottoken.remaining_duration
+ def set(self, value):
+ self.roottoken.remaining_duration = value
+ return property(get, set)
+
+ def end(self):
+ # IEndable
+ return self.roottoken.end()
+
+
+def removeEndedTokens(event):
+ """subscriber handler for ITokenEndedEvent"""
+ assert zope.locking.interfaces.ITokenEndedEvent.providedBy(event)
+ roottoken = event.object
+ assert not IIndirectToken.providedBy(roottoken)
+ index = roottoken.annotations.get(INDIRECT_INDEX_KEY, {})
+ # read the whole index in memory so that we correctly loop over all the
+ # items in this list.
+ indexItems = list(index.items())
+ for key_ref, token in indexItems:
+ # token has ended so it should be removed via the register method
+ roottoken.utility.register(token)
+ del index[key_ref]
+
+# TODO - need subscriber incase a user tries to add a object has a
+# descendent to the lock object.
+
+################################################################################
+#
+# zope.locking adapters.
+#
+################################################################################
+
+class ExclusiveLockEntry(object):
+ interface.implements(ILockEntry)
+
+ lockscope = [u"exclusive"]
+ locktype = [u"write"]
+
+
+class SharedLockEntry(object):
+ interface.implements(ILockEntry)
+
+ lockscope = [u"shared"]
+ locktype = [u"write"]
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+ at interface.implementer(IDAVSupportedlock)
+def DAVSupportedlock(context, request):
+ utility = component.queryUtility(zope.locking.interfaces.ITokenUtility)
+ if utility is None:
+ return None
+ return DAVSupportedlockAdapter(context, request)
+
+
+class DAVSupportedlockAdapter(object):
+ """
+ >>> slock = DAVSupportedlockAdapter(None, None)
+ >>> exclusive, shared = slock.supportedlock
+
+ >>> exclusive.lockscope
+ [u'exclusive']
+ >>> exclusive.locktype
+ [u'write']
+
+ >>> shared.lockscope
+ [u'shared']
+ >>> shared.locktype
+ [u'write']
+
+ """
+ interface.implements(IDAVSupportedlock)
+ component.adapts(interface.Interface,
+ zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ pass
+
+ @property
+ def supportedlock(self):
+ return [ExclusiveLockEntry(), SharedLockEntry()]
+
+
+WEBDAV_LOCK_KEY = "zope.webdav.lockingutils.info"
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVMethod)
+ at interface.implementer(IActiveLock)
+def DAVActiveLock(context, request):
+ """
+ The activelock property only exists whenever the resource is locked.
+ """
+ try:
+ token = zope.locking.interfaces.ITokenBroker(context).get()
+ except TypeError:
+ token = None
+ if token is None:
+ return None
+ return DAVActiveLockAdapter(token, context, request)
+
+
+class DAVActiveLockAdapter(object):
+ component.adapts(interface.Interface,
+ zope.webdav.interfaces.IWebDAVRequest)
+ interface.implements(IActiveLock)
+
+ def __init__(self, token, context, request):
+ self.context = self.__parent__ = context
+ self.token = token
+ self.request = request
+
+ @property
+ def lockscope(self):
+ if IIndirectToken.providedBy(self.token):
+ roottoken = self.token.roottoken
+ else:
+ roottoken = self.token
+
+ if zope.locking.interfaces.IExclusiveLock.providedBy(roottoken):
+ return [u"exclusive"]
+ elif zope.locking.interfaces.ISharedLock.providedBy(roottoken):
+ return [u"shared"]
+
+ raise ValueError("Unknow lock token used for locking")
+
+ @property
+ def locktype(self):
+ return [u"write"]
+
+ @property
+ def depth(self):
+ return self.token.annotations[WEBDAV_LOCK_KEY]["depth"]
+
+ @property
+ def owner(self):
+ return self.token.annotations[WEBDAV_LOCK_KEY]["owner"]
+
+ @property
+ def timeout(self):
+ return u"Second-%d" % self.token.remaining_duration.seconds
+
+ @property
+ def locktoken(self):
+ return [self.token.annotations[WEBDAV_LOCK_KEY]["token"]]
+
+ @property
+ def lockroot(self):
+ if IIndirectToken.providedBy(self.token):
+ root = self.token.roottoken.context
+ else:
+ root = self.token.context
+
+ return absoluteURL(root, self.request)
+
+
+ at component.adapter(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+ at interface.implementer(zope.webdav.coreproperties.IDAVLockdiscovery)
+def DAVLockdiscovery(context, request):
+ utility = component.queryUtility(zope.locking.interfaces.ITokenUtility)
+ if utility is None:
+ return None
+ return DAVLockdiscoveryAdapter(context, request)
+
+
+class DAVLockdiscoveryAdapter(object):
+ interface.implements(zope.webdav.coreproperties.IDAVLockdiscovery)
+ component.adapts(interface.Interface,
+ zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ @property
+ def lockdiscovery(self):
+ adapter = component.queryMultiAdapter((self.context, self.request),
+ IActiveLock, default = None)
+ if adapter is None:
+ return None
+ return [adapter]
+
+
+class DAVLockmanager(object):
+ """
+
+ >>> from zope.interface.verify import verifyObject
+ >>> from zope.locking import utility, utils
+ >>> from zope.locking.adapters import TokenBroker
+
+ >>> file = Demo()
+
+ Before we register a ITokenUtility utility make sure that the DAVLockmanager
+ is not lockable.
+
+ >>> adapter = DAVLockmanager(file)
+ >>> adapter.islockable()
+ False
+
+ Now create and register a ITokenUtility utility.
+
+ >>> util = utility.TokenUtility()
+ >>> component.getGlobalSiteManager().registerUtility(
+ ... util, zope.locking.interfaces.ITokenUtility)
+ >>> component.getGlobalSiteManager().registerAdapter(
+ ... TokenBroker, (interface.Interface,),
+ ... zope.locking.interfaces.ITokenBroker)
+
+ >>> import pytz
+ >>> def hackNow():
+ ... return datetime.datetime(2006, 7, 25, 23, 49, 51)
+ >>> oldNow = utils.now
+ >>> utils.now = hackNow
+
+ Test the DAVLockmanager implements the descired interface.
+
+ >>> adapter = DAVLockmanager(file)
+ >>> verifyObject(zope.webdav.interfaces.IDAVLockmanager, adapter)
+ True
+
+ The adapter should also be lockable.
+
+ >>> adapter.islockable()
+ True
+
+ Lock with an exclusive lock token.
+
+ >>> roottoken = adapter.lock(u'exclusive', u'write',
+ ... u'Michael', datetime.timedelta(seconds = 3600), '0')
+ >>> util.get(file) == roottoken
+ True
+ >>> zope.locking.interfaces.IExclusiveLock.providedBy(roottoken)
+ True
+
+ >>> adapter.islocked()
+ True
+
+ >>> activelock = adapter.getActivelock()
+ >>> activelock.lockscope
+ [u'exclusive']
+ >>> activelock.locktype
+ [u'write']
+ >>> activelock.depth
+ '0'
+ >>> activelock.timeout
+ u'Second-3600'
+ >>> activelock.lockroot
+ '/dummy'
+ >>> activelock.owner
+ u'Michael'
+
+ >>> adapter.refreshlock(datetime.timedelta(seconds = 7200))
+ >>> adapter.getActivelock().timeout
+ u'Second-7200'
+
+ >>> adapter.unlock()
+ >>> util.get(file) is None
+ True
+ >>> adapter.islocked()
+ False
+ >>> adapter.getActivelock() is None
+ True
+
+ Shared locking support.
+
+ >>> roottoken = adapter.lock(u'shared', u'write', u'Michael',
+ ... datetime.timedelta(seconds = 3600), '0')
+ >>> util.get(file) == roottoken
+ True
+ >>> zope.locking.interfaces.ISharedLock.providedBy(roottoken)
+ True
+
+ >>> activelock = adapter.getActivelock()
+ >>> activelock.lockscope
+ [u'shared']
+ >>> activelock.locktoken #doctest:+ELLIPSIS
+ ['opaquelocktoken:...
+
+ >>> adapter.unlock()
+
+ Recursive lock suport.
+
+ >>> demofolder = DemoFolder()
+ >>> demofolder['demo'] = file
+
+ >>> adapter = DAVLockmanager(demofolder)
+ >>> roottoken = adapter.lock(u'exclusive', u'write', u'MichaelK',
+ ... datetime.timedelta(seconds = 3600), 'infinity')
+
+ >>> demotoken = util.get(file)
+ >>> IIndirectToken.providedBy(demotoken)
+ True
+
+ >>> activelock = adapter.getActivelock()
+ >>> activelock.lockroot
+ '/dummy/'
+ >>> DAVLockmanager(file).getActivelock().lockroot
+ '/dummy/'
+ >>> absoluteURL(file, None)
+ '/dummy/dummy'
+ >>> activelock.lockscope
+ [u'exclusive']
+
+ Already locked support.
+
+ >>> adapter.lock(u'exclusive', u'write', u'Michael',
+ ... datetime.timedelta(seconds = 100), 'infinity') #doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ AlreadyLocked...
+ >>> adapter.islocked()
+ True
+
+ >>> adapter.unlock()
+
+ Some error conditions.
+
+ >>> adapter.lock(u'exclusive', u'notwrite', u'Michael',
+ ... datetime.timedelta(seconds = 100), 'infinity') # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ UnprocessableError: ...
+
+ >>> adapter.lock(u'notexclusive', u'write', u'Michael',
+ ... datetime.timedelta(seconds = 100), 'infinity') # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ UnprocessableError: ...
+
+ >>> component.getGlobalSiteManager().unregisterUtility(
+ ... util, zope.locking.interfaces.ITokenUtility)
+ True
+ >>> component.getGlobalSiteManager().unregisterAdapter(
+ ... TokenBroker, (interface.Interface,),
+ ... zope.locking.interfaces.ITokenBroker)
+ True
+ >>> utils.now = oldNow
+
+ """
+ interface.implements(zope.webdav.interfaces.IDAVLockmanager)
+ component.adapts(interface.Interface)
+
+ def __init__(self, context):
+ self.context = self.__parent__ = context
+
+ def generateLocktoken(self):
+ return "opaquelocktoken:%s-%s-00105A989226:%.03f" % \
+ (_randGen.random(), _randGen.random(), time.time())
+
+ def islockable(self):
+ utility = component.queryUtility(zope.locking.interfaces.ITokenUtility,
+ context = self.context, default = None)
+ return utility is not None
+
+ def lock(self, scope, type, owner, duration, depth,
+ roottoken = None, context = None):
+ if type != u"write":
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = "Invalid locktype supplied to lock manager.")
+
+ if context is None:
+ context = self.context
+
+ tokenBroker = zope.locking.interfaces.ITokenBroker(context)
+ if tokenBroker.get():
+ raise zope.webdav.interfaces.AlreadyLocked(
+ context, message = u"Context or subitem is already locked.")
+
+ if roottoken is None:
+ if scope == u"exclusive":
+ roottoken = tokenBroker.lock(duration = duration)
+ elif scope == u"shared":
+ roottoken = tokenBroker.lockShared(duration = duration)
+ else:
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"Invalid lockscope supplied to the lock manager")
+
+ annots = roottoken.annotations.get(WEBDAV_LOCK_KEY, None)
+ if annots is None:
+ annots = roottoken.annotations[WEBDAV_LOCK_KEY] = OOBTree()
+ annots["owner"] = owner
+ annots["token"] = self.generateLocktoken()
+ annots["depth"] = depth
+ else:
+ indirecttoken = IndirectToken(context, roottoken)
+ ## XXX - using removeSecurityProxy - is this right, has
+ ## it seems wrong
+ removeSecurityProxy(roottoken).utility.register(indirecttoken)
+
+ if depth == "infinity" and IReadContainer.providedBy(context):
+ for subob in context.values():
+ self.lock(scope, type, owner, duration, depth,
+ roottoken, subob)
+
+ return roottoken
+
+ def getActivelock(self, request = None):
+ if self.islocked():
+ token = zope.locking.interfaces.ITokenBroker(self.context).get()
+ return DAVActiveLockAdapter(token, self.context, request)
+ return None
+
+ def refreshlock(self, timeout):
+ token = zope.locking.interfaces.ITokenBroker(self.context).get()
+ token.duration = timeout
+
+ def unlock(self):
+ tokenBroker = zope.locking.interfaces.ITokenBroker(self.context)
+ token = tokenBroker.get()
+ token.end()
+
+ def islocked(self):
+ tokenBroker = zope.locking.interfaces.ITokenBroker(self.context)
+ return tokenBroker.get() is not None
Property changes on: zope.webdav/trunk/src/zope/webdav/lockingutils.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/mkcol.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/mkcol.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/mkcol.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,63 @@
+##############################################################################
+# Copyright (c) 2003 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.
+##############################################################################
+"""DAV method MKCOL
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+from zope.filerepresentation.interfaces import IWriteDirectory
+from zope.filerepresentation.interfaces import IDirectoryFactory
+import zope.event
+from zope.lifecycleevent import ObjectCreatedEvent
+import zope.app.http.interfaces
+
+import zope.webdav.interfaces
+
+class NullResource(object):
+ """MKCOL handler for creating collections"""
+
+ # MKCOL is only supported on unmapped urls.
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(zope.app.http.interfaces.INullResource,
+ zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def MKCOL(self):
+ request = self.request
+ data = request.bodyStream.read()
+ if len(data):
+ # We don't (yet) support a request body on MKCOL.
+ raise zope.webdav.interfaces.UnsupportedMediaType(
+ self.context,
+ message = u"A request body is not supported for a MKCOL method")
+
+ container = self.context.container
+ name = self.context.name
+
+ dir = IWriteDirectory(container, None)
+ if dir is None:
+ raise zope.webdav.interfaces.ForbiddenError(
+ self.context, message = u"")
+
+ factory = IDirectoryFactory(container)
+ newdir = factory(name)
+ zope.event.notify(ObjectCreatedEvent(newdir))
+ dir[name] = newdir
+
+ request.response.setStatus(201)
+ return ""
Property changes on: zope.webdav/trunk/src/zope/webdav/mkcol.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/properties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/properties.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/properties.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,233 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Miscellanous helper methods for implementing the WebDAV data model. See
+datamodel.txt
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+from zope import schema
+from zope.schema.interfaces import IField
+from zope.schema.fieldproperty import FieldProperty
+from zope.app.form.interfaces import MissingInputError
+
+from zope.webdav.interfaces import IDAVProperty, IDAVWidget, IDAVInputWidget
+from zope.webdav.interfaces import IOpaquePropertyStorage
+import zope.webdav.widgets
+from zope.webdav.ietree import IEtree
+import zope.webdav.utils
+
+class DAVProperty(object):
+ """
+
+ >>> from zope.interface.verify import verifyObject
+ >>> from zope.schema import getFields
+ >>> from zope.interface.interfaces import IInterface
+ >>> from coreproperties import IDAVResourcetype
+ >>> prop = DAVProperty('{DAV:}resourcetype', IDAVResourcetype)
+ >>> verifyObject(IDAVProperty, prop)
+ True
+ >>> prop.namespace
+ 'DAV:'
+ >>> prop.__name__
+ 'resourcetype'
+ >>> verifyObject(IInterface, prop.iface)
+ True
+ >>> prop.field in getFields(prop.iface).values()
+ True
+ >>> verifyObject(IField, prop.field)
+ True
+ >>> prop.custom_widget is None
+ True
+ >>> prop.restricted
+ False
+
+ """
+ interface.implements(IDAVProperty)
+
+ namespace = FieldProperty(IDAVProperty['namespace'])
+ __name__ = FieldProperty(IDAVProperty['__name__'])
+ ## XXX - If a developer writes his own field and passes it into
+ ## DAVProperty then it is next to impossible to get the to validate
+ ## correctly.
+ ## field = FieldProperty(IDAVProperty['field'])
+ iface = FieldProperty(IDAVProperty['iface'])
+ custom_widget = FieldProperty(IDAVProperty['custom_widget'])
+ custom_input_widget = FieldProperty(IDAVProperty['custom_input_widget'])
+ restricted = FieldProperty(IDAVProperty['restricted'])
+
+ def __init__(self, tag, iface):
+ namespace, name = zope.webdav.utils.parseEtreeTag(tag)
+ self.namespace = namespace
+ self.__name__ = name
+ self.iface = iface
+ self.field = iface[name]
+ self.custom_widget = None
+ self.custom_input_widget = None
+ self.restricted = False
+
+
+_opaque_namespace_key = "zope.webdav.properties.DAVOpaqueProperties"
+
+class DeadField(schema.Field):
+ pass
+
+
+class OpaqueWidget(zope.webdav.widgets.DAVWidget):
+
+ def render(self):
+ etree = component.getUtility(IEtree)
+ el = etree.fromstring(self._value)
+ return el
+
+
+class OpaqueInputWidget(zope.webdav.widgets.DAVInputWidget):
+
+ def getInputValue(self):
+ el = self.getProppatchElement()
+
+ etree = component.getUtility(IEtree)
+ # XXX - ascii seems a bit wrong here
+ return etree.tostring(el[0], 'ascii')
+
+
+class IOpaqueField(IField):
+
+ namespace = schema.BytesLine( # should this be schema.URI
+ title = u"Namespace",
+ description = u"Namespace to which the value belongs",
+ required = True)
+
+
+class OpaqueField(schema.Field):
+ interface.implements(IOpaqueField)
+
+ namespace = FieldProperty(IOpaqueField['namespace'])
+
+ def __init__(self, namespace = None, **kw):
+ super(OpaqueField, self).__init__(**kw)
+ self.namespace = namespace
+ self.tag = "{%s}%s" %(self.namespace, self.__name__)
+
+ def get(self, obj):
+ return obj.getProperty(self.tag)
+
+ def set(self, obj, value):
+ obj.setProperty(self.tag, value)
+
+
+class OpaqueProperty(object):
+ """
+ >>> from zope.interface.verify import verifyObject
+ >>> prop = OpaqueProperty('{examplens:}testprop')
+ >>> verifyObject(IDAVProperty, prop)
+ True
+ """
+ interface.implements(IDAVProperty)
+
+ def __init__(self, tag):
+ namespace, name = zope.webdav.utils.parseEtreeTag(tag)
+ self.__name__ = name
+ self.namespace = namespace
+ self.iface = IOpaquePropertyStorage
+ self.field = OpaqueField(
+ __name__ = name,
+ namespace = namespace,
+ title = u"",
+ description = u"")
+ self.custom_widget = OpaqueWidget
+ self.custom_input_widget = OpaqueInputWidget
+ self.restricted = False
+
+
+def getAllProperties(context, request):
+ for name, prop in component.getUtilitiesFor(IDAVProperty):
+ adapter = component.queryMultiAdapter((context, request),
+ prop.iface,
+ default = None)
+ if adapter is None:
+ continue
+
+ yield prop, adapter
+
+ adapter = IOpaquePropertyStorage(context, None)
+ if adapter is None:
+ raise StopIteration
+
+ for tag in adapter.getAllProperties():
+ yield OpaqueProperty(tag), adapter
+
+
+def hasProperty(context, request, tag):
+ prop = component.queryUtility(IDAVProperty, name = tag, default = None)
+ if prop is None:
+ adapter = IOpaquePropertyStorage(context, None)
+ if adapter is not None and adapter.hasProperty(tag):
+ return True
+ return False
+
+ adapter = component.queryMultiAdapter((context, request), prop.iface,
+ default = None)
+ if adapter is None:
+ return False
+
+ return True
+
+
+def getProperty(context, request, tag, exists = False):
+ prop = component.queryUtility(IDAVProperty, name = tag, default = None)
+ if prop is None:
+ adapter = IOpaquePropertyStorage(context, None)
+ if adapter is None:
+ ## XXX - should we use the zope.publisher.interfaces.NotFound
+ ## exceptin here.
+ raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+ if exists and not adapter.hasProperty(tag):
+ ## XXX - should we use the zope.publisher.interfaces.NotFound
+ ## exceptin here.
+ raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+ return OpaqueProperty(tag), adapter
+
+ adapter = component.queryMultiAdapter((context, request), prop.iface,
+ default = None)
+ if adapter is None:
+ ## XXX - should we use the zope.publisher.interfaces.NotFound
+ ## exceptin here.
+ raise zope.webdav.interfaces.PropertyNotFound(context, tag, tag)
+
+ return prop, adapter
+
+
+def getWidget(prop, adapter, request, type = IDAVWidget):
+ """prop.field describes the data we want to render.
+ """
+ if type is IDAVWidget and prop.custom_widget is not None:
+ widget = prop.custom_widget(prop.field, request)
+ elif type is IDAVInputWidget and prop.custom_input_widget is not None:
+ widget = prop.custom_input_widget(prop.field, request)
+ else:
+ widget = component.getMultiAdapter((prop.field, request), type)
+
+ if IDAVWidget.providedBy(widget):
+ field = prop.field.bind(adapter)
+ widget.setRenderedValue(field.get(adapter))
+
+ widget.namespace = prop.namespace
+
+ return widget
Property changes on: zope.webdav/trunk/src/zope/webdav/properties.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/propfind.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/propfind.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,205 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""WebDAV method PROPFIND
+
+The propfind XML element conforms to the following DTD snippet
+
+ <!ELEMENT propfind ( propname | (allprop, include?) | prop ) >
+ <!ELEMENT propname EMPTY >
+ <!ELEMENT allprop EMPTY >
+ <!ELEMENT include ANY >
+ <!ELEMENT prop ANY >
+
+All the render*(ob, req, extra) know how to render the requested properties
+requested by the PROPFIND method.
+
+ renderPropnames(ob, req, ignore) - extra argument is ignored.
+
+ renderAllProperties(ob, req, include) - extra argument is a list of all
+ the properties that must be rendered.
+
+ renderSelectedProperties(ob, req, props) - extra argument is a list of all
+ the properties to render.
+
+And all these methods return a zope.webdav.utils.IResponse implementation.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import sys
+
+from zope import interface
+from zope import component
+from zope.app.container.interfaces import IReadContainer
+
+from ietree import IEtree
+import zope.webdav.utils
+import zope.webdav.interfaces
+import zope.webdav.properties
+
+DEFAULT_NS = "DAV:"
+
+class PROPFIND(object):
+ """
+ PROPFIND handler for all objects.
+
+ The PROPFIND method handles parsing of the XML body and then calls the
+
+ """
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def getDepth(self):
+ # default is infinity.
+ return self.request.getHeader("depth", "infinity")
+
+ def PROPFIND(self):
+ if len(self.request.bodyStream.getCacheStream().read()) > 0 and \
+ self.request.content_type not in ("text/xml", "application/xml"):
+ raise zope.webdav.interfaces.BadRequest(
+ self.request,
+ message = u"PROPFIND requires a valid XML request")
+
+ depth = self.getDepth()
+ if depth not in ("0", "1", "infinity"):
+ raise zope.webdav.interfaces.BadRequest(
+ self.request, message = u"Invalid Depth header supplied")
+
+ propertiesFactory = None
+ extraArg = None
+
+ propfind = self.request.xmlDataSource
+ if propfind is not None:
+ if propfind.tag != "{DAV:}propfind":
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"Request is not a `propfind' XML element.")
+ properties = propfind[0]
+ if properties.tag == "{DAV:}propname":
+ propertiesFactory = self.renderPropnames
+ elif properties.tag == "{DAV:}allprop":
+ propertiesFactory = self.renderAllProperties
+ includes = propfind.findall("{DAV:}include")
+ if includes: # we have "DAV:include" properties
+ extraArg = includes[0]
+ elif properties.tag == "{DAV:}prop":
+ if len(properties) == 0:
+ ## XXX - does this code correspond to the protocol.
+ propertiesFactory = self.renderAllProperties
+ else:
+ propertiesFactory = self.renderSelectedProperties
+ extraArg = properties
+ else:
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"Unknown propfind property element.")
+ else:
+ propertiesFactory = self.renderAllProperties
+
+ multistatus = zope.webdav.utils.MultiStatus()
+ responses = self.handlePropfindResource(
+ self.context, self.request, depth, propertiesFactory, extraArg)
+ multistatus.responses.extend(responses)
+
+ etree = component.getUtility(IEtree)
+
+ self.request.response.setStatus(207)
+ self.request.response.setHeader("content-type", "application/xml")
+ ## Is UTF-8 encoding ok here or is there a better way of doing this.
+ return etree.tostring(multistatus(), encoding = "utf-8")
+
+ def handlePropfindResource(self, ob, req, depth, \
+ propertiesFactory, extraArg):
+ """
+ Recursive method that collects all the `response' XML elements for
+ the current PROPFIND request.
+
+ `propertiesFactory' is the method that is used to generated the
+ `response' XML element for one resource. It takes the resource,
+ request and `extraArg' used to pass in specific information about
+ the properties we want to return.
+ """
+ responses = [propertiesFactory(ob, req, extraArg)]
+
+ if depth in ("1", "infinity") and IReadContainer.providedBy(ob):
+ subdepth = (depth == "1") and "0" or "infinity"
+
+ for subob in ob.values():
+ responses.extend(self.handlePropfindResource(
+ subob, req, subdepth, propertiesFactory, extraArg))
+
+ return responses
+
+ def renderPropnames(self, ob, req, ignore):
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(ob, req))
+
+ for davprop, adapter in \
+ zope.webdav.properties.getAllProperties(ob, req):
+ davwidget = zope.webdav.properties.getWidget(davprop, adapter, req)
+ response.addProperty(200, davwidget.renderName())
+
+ return response
+
+ def renderAllProperties(self, ob, req, include):
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(ob, req))
+
+ for davprop, adapter in \
+ zope.webdav.properties.getAllProperties(ob, req):
+ if davprop.restricted:
+ if include is None or \
+ include.find("{%s}%s" %(davprop.namespace,
+ davprop.__name__)) is None:
+ continue
+
+ davwidget = zope.webdav.properties.getWidget(davprop, adapter, req)
+ response.addProperty(200, davwidget.render())
+
+ return response
+
+ def renderSelectedProperties(self, ob, req, props):
+ response = zope.webdav.utils.Response(
+ zope.webdav.utils.getObjectURL(ob, req))
+
+ etree = component.getUtility(IEtree)
+
+ for prop in props:
+ try:
+ davprop, adapter = zope.webdav.properties.getProperty(
+ ob, req, prop.tag, exists = True)
+ davwidget = zope.webdav.properties.getWidget(
+ davprop, adapter, req)
+ propstat = response.getPropstat(200)
+ propstat.properties.append(davwidget.render())
+ except Exception, error:
+ exc_info = sys.exc_info()
+
+ error_view = component.queryMultiAdapter(
+ (error, req), zope.webdav.interfaces.IDAVErrorWidget)
+ if error_view is None:
+ ## XXX - needs testing
+ raise exc_info[0], exc_info[1], exc_info[2]
+
+ propstat = response.getPropstat(error_view.status)
+ ## XXX - needs testing
+ propstat.responsedescription += error_view.propstatdescription
+ response.responsedescription += error_view.responsedescription
+
+ propstat.properties.append(etree.Element(prop.tag))
+
+ return response
Property changes on: zope.webdav/trunk/src/zope/webdav/propfind.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/proppatch.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/proppatch.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,147 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""WebDAV PROPPATCH method.
+
+ <!ELEMENT propertyupdate (remove | set)+ >
+ <!ELEMENT remove (prop) >
+ <!ELEMENT set (prop) >
+ <!ELEMENT prop ANY >
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+
+import zope.webdav.utils
+import zope.webdav.interfaces
+import zope.webdav.properties
+from zope.webdav.ietree import IEtree
+
+
+class PROPPATCH(object):
+ """PROPPATCH handler for all objects"""
+ interface.implements(zope.webdav.interfaces.IWebDAVMethod)
+ component.adapts(interface.Interface, zope.webdav.interfaces.IWebDAVRequest)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def PROPPATCH(self):
+ if self.request.content_type not in ("text/xml", "application/xml"):
+ raise zope.webdav.interfaces.BadRequest(
+ self.request,
+ message = "All PROPPATCH requests needs a XML body")
+
+ xmldata = self.request.xmlDataSource
+ if xmldata.tag != "{DAV:}propertyupdate":
+ raise zope.webdav.interfaces.UnprocessableError(
+ self.context,
+ message = u"PROPPATCH request body must be a "
+ "`propertyupdate' XML element.")
+
+ etree = component.getUtility(IEtree)
+
+ # isError - an error occurred during the processing of the request.
+ isError = False
+ # propErrors - list of (property tag, error). error is None if no
+ # error occurred in setting / removing the property.
+ propErrors = []
+ # properties - list of all the properties that we handled correctly.
+ properties = []
+ for update in xmldata:
+ if update.tag not in ("{DAV:}set", "{DAV:}remove"):
+ continue
+
+ props = update.findall("{DAV:}prop")
+ if not props:
+ continue
+
+ props = props[0]
+
+ for prop in props:
+ try:
+ if update.tag == "{DAV:}set":
+ self.handleSet(prop)
+ else:
+ self.handleRemove(prop)
+ except Exception, error:
+ isError = True
+ propErrors.append((prop.tag, error))
+ else:
+ properties.append(prop.tag)
+
+ if isError:
+ errors = zope.webdav.interfaces.WebDAVPropstatErrors(self.context)
+
+ for prop, error in propErrors:
+ errors[prop] = error
+
+ for proptag in properties:
+ errors[proptag] = zope.webdav.interfaces.FailedDependency(
+ self.context, proptag)
+
+ raise errors # this kills the current transaction.
+
+ url = zope.webdav.utils.getObjectURL(self.context, self.request)
+ response = zope.webdav.utils.Response(url)
+ propstat = response.getPropstat(200)
+
+ for proptag in properties:
+ propstat.properties.append(etree.Element(proptag))
+
+ multistatus = zope.webdav.utils.MultiStatus()
+ multistatus.responses.append(response)
+
+ self.request.response.setStatus(207)
+ self.request.response.setHeader("content-type", "application/xml")
+ return etree.tostring(multistatus())
+
+ def handleSet(self, prop):
+ davprop, adapter = zope.webdav.properties.getProperty(
+ self.context, self.request, prop.tag)
+
+ widget = zope.webdav.properties.getWidget(
+ davprop, adapter, self.request,
+ type = zope.webdav.interfaces.IDAVInputWidget)
+
+ field = davprop.field.bind(adapter)
+
+ if field.readonly:
+ raise zope.webdav.interfaces.ForbiddenError(
+ self.context, prop.tag, message = u"readonly field")
+
+ value = widget.getInputValue()
+ field.validate(value)
+
+ if field.get(adapter) != value:
+ field.set(adapter, value)
+
+ def handleRemove(self, prop):
+ davprop = component.queryUtility(
+ zope.webdav.interfaces.IDAVProperty, prop.tag, None)
+
+ if davprop is not None:
+ raise zope.webdav.interfaces.ConflictError(
+ self.context, prop.tag,
+ message = u"cannot remove a live property")
+
+ deadproperties = zope.webdav.interfaces.IOpaquePropertyStorage(
+ self.context, None)
+
+ if deadproperties is None or not deadproperties.hasProperty(prop.tag):
+ raise zope.webdav.interfaces.ConflictError(
+ self.context, prop.tag, message = u"property doesn't exist")
+
+ deadproperties.removeProperty(prop.tag)
Property changes on: zope.webdav/trunk/src/zope/webdav/proppatch.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/publisher.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/publisher.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/publisher.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,92 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""WebDAV publishing objects including the request and response.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import time
+import sys
+from xml.parsers.expat import ExpatError
+try:
+ from lxml.etree import XMLSyntaxError
+except ImportError:
+ # no lxml - so create a dummy excpetion
+ class XMLSyntaxError(Exception):
+ pass
+
+from zope import component
+from zope.interface import implements
+from zope.publisher.http import HTTPResponse, HTTPRequest
+from zope.app.publication.http import HTTPPublication
+from zope.app.publication.interfaces import IRequestPublicationFactory
+
+from ietree import IEtree
+import interfaces
+
+
+class WebDAVResponse(HTTPResponse):
+ implements(interfaces.IWebDAVResponse)
+
+
+class WebDAVRequest(HTTPRequest):
+ implements(interfaces.IWebDAVRequest)
+
+ __slot__ = (
+ 'xmlDataSource', # holds xml.dom representation of the input stream
+ # if it is XML otherwise it is None.
+ 'content_type', #
+ )
+
+ def __init__(self, body_instream, environ, reponse = None,
+ positional = None, outstream = None):
+ super(WebDAVRequest, self).__init__(body_instream, environ)
+
+ self.xmlDataSource = None
+ self.content_type = None
+
+ def processInputs(self):
+ """See IPublisherRequest."""
+ content_type = self.getHeader('content-type', '')
+ content_type_params = None
+ if ';' in content_type:
+ parts = content_type.split(';', 1)
+ content_type = parts[0].strip().lower()
+ content_type_params = parts[1].strip()
+
+ self.content_type = content_type
+
+ if content_type in ("text/xml", "application/xml") and \
+ self.getHeader("content-length", 0) > 0:
+ etree = component.getUtility(IEtree)
+ try:
+ self.xmlDataSource = etree.parse(self.bodyStream).getroot()
+ except (ExpatError, XMLSyntaxError):
+ raise interfaces.BadRequest(
+ self, u"Invalid xml data passed")
+
+ def _createResponse(self):
+ """Create a specific WebDAV response object."""
+ return WebDAVResponse()
+
+
+class WebDAVRequestFactory(object):
+ implements(IRequestPublicationFactory)
+
+ def canHandle(self, environment):
+ return True
+
+ def __call__(self):
+ request_class = component.queryUtility(
+ interfaces.IWebDAVRequestFactory, default = WebDAVRequest)
+ return request_class, HTTPPublication
Property changes on: zope.webdav/trunk/src/zope/webdav/publisher.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/testing.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/testing.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/testing.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,144 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.component
+import zope.app.component
+from zope.configuration import xmlconfig
+from zope.app.testing import setup
+
+from ietree import IEtree
+import zope.webdav
+
+#
+# Setup for Zope etree.
+#
+
+def etreeSetup(test = None):
+ setup.placelessSetUp()
+ context = xmlconfig.file("meta.zcml", package = zope.app.component)
+ xmlconfig.file("ietree-configure.zcml", package = zope.webdav,
+ context = context)
+
+ etreeEtree = zope.component.getUtility(IEtree)
+
+ if test is not None:
+ test.globs["etree"] = etreeEtree
+ test.globs["assertXMLEqual"] = assertXMLEqual
+ return etreeEtree
+
+def etreeTearDown(test = None):
+ if test is not None:
+ del test.globs["etree"]
+ del test.globs["assertXMLEqual"]
+ etreeEtree = zope.component.getUtility(IEtree)
+ zope.component.getGlobalSiteManager().unregisterUtility(etreeEtree)
+
+ setup.placelessTearDown()
+
+#
+# Handy methods for testing if two xml fragmenets are equal.
+#
+
+def _assertTextEqual(got, expected):
+ """
+ >>> _assertTextEqual(None, "\\n")
+ True
+
+ >>> _assertTextEqual("\\n", "\\n")
+ True
+
+ >>> _assertTextEqual("test", "test")
+ True
+
+ """
+ tgot = got and got.strip()
+ texpected = expected and expected.strip()
+
+ error_msg = "'%r != %r' have different element content." %(
+ got, expected)
+
+ if not tgot:
+ assert not texpected, error_msg
+ return True
+
+ if not texpected:
+ assert not tgot, error_msg
+ return True
+
+ assert isinstance(tgot, (str, unicode)), error_msg
+ assert isinstance(texpected, (str, unicode)), error_msg
+
+ assert tgot == texpected, error_msg
+
+ return True
+
+def _assertXMLElementEqual(got, expected):
+ etree = zope.component.getUtility(IEtree)
+
+ assert got.tag == expected.tag, \
+ "'%r != %r' different tag name." %(got.tag, expected.tag)
+ assert len(got) == len(expected), \
+ "'%d != %d' different number of subchildren on %r." %(
+ len(got), len(expected), got.tag)
+ _assertTextEqual(got.text, expected.text)
+
+ for index in range(0, len(got)):
+ _assertXMLElementEqual(got[index], expected[index])
+
+
+def assertXMLEqual(got, expected):
+ """
+ >>> etree = etreeSetup()
+ >>> assertXMLEqual('<test>xml</test>', '<test>xml</test>')
+
+ >>> assertXMLEqual('<test>xml</test>', '<test>xml1</test>')
+ Traceback (most recent call last):
+ ...
+ AssertionError: ''xml' != 'xml1'' have different element content.
+
+ >>> assertXMLEqual('<test><subtest>Test</subtest></test>',
+ ... '<test>Test</test>')
+ Traceback (most recent call last):
+ ...
+ AssertionError: '1 != 0' different number of subchildren on 'test'.
+
+ >>> assertXMLEqual('<test1/>', '<test2/>')
+ Traceback (most recent call last):
+ ...
+ AssertionError: ''test1' != 'test2'' different tag name.
+
+ >>> assertXMLEqual('<a><b><c /></b></a>', '<a><b><c/></b></a>')
+ >>> etreeTearDown()
+
+ """
+ etree = zope.component.getUtility(IEtree)
+
+ if isinstance(got, (str, unicode)):
+ got = etree.fromstring(got)
+ if isinstance(expected, (str, unicode)):
+ expected = etree.fromstring(expected)
+
+ if getattr(got, "getroot", None) is not None:
+ # XXX - is this all neccessary.
+ got = got.getroot()
+
+ assert getattr(expected, "getroot", None) is not None
+ expected = expected.getroot()
+
+ _assertXMLElementEqual(got, expected)
Property changes on: zope.webdav/trunk/src/zope/webdav/testing.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/__init__.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/__init__.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Collection of tests for zope.webdav
+
+$Id$
+"""
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,450 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test WebDAV COPY and MOVE methods.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope import component
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.location.traversing import LocationPhysicallyLocatable
+from zope.traversing.adapters import Traverser, DefaultTraversable
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.publication.http import MethodNotAllowed
+from zope.traversing.interfaces import IContainmentRoot
+
+import zope.webdav.publisher
+from zope.webdav.copymove import COPY, MOVE
+
+import test_locking
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+ def __init__(self, environ = {}):
+ env = environ.copy()
+ env.setdefault("HTTP_HOST", "localhost")
+ super(TestRequest, self).__init__(StringIO(""), env)
+
+ self.processInputs()
+
+
+def baseSetUp():
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(LocationPhysicallyLocatable,
+ (test_locking.IResource,))
+ gsm.registerAdapter(LocationPhysicallyLocatable,
+ (test_locking.ICollectionResource,))
+ gsm.registerAdapter(Traverser, (test_locking.IResource,))
+ gsm.registerAdapter(Traverser, (test_locking.ICollectionResource,))
+ gsm.registerAdapter(DefaultTraversable, (test_locking.IResource,))
+ gsm.registerAdapter(DefaultTraversable,
+ (test_locking.ICollectionResource,))
+
+
+def baseTearDown():
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(LocationPhysicallyLocatable,
+ (test_locking.IResource,))
+ gsm.unregisterAdapter(LocationPhysicallyLocatable,
+ (test_locking.ICollectionResource,))
+ gsm.unregisterAdapter(Traverser, (test_locking.IResource,))
+ gsm.unregisterAdapter(Traverser, (test_locking.ICollectionResource,))
+ gsm.unregisterAdapter(DefaultTraversable, (test_locking.IResource,))
+ gsm.unregisterAdapter(DefaultTraversable,
+ (test_locking.ICollectionResource,))
+
+
+class COPYMOVEParseHeadersTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.root = test_locking.RootCollectionResource()
+
+ baseSetUp()
+
+ def tearDown(self):
+ del self.root
+
+ baseTearDown()
+
+ def test_no_overwrite(self):
+ request = TestRequest()
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getOverwrite(), True)
+
+ def test_T_overwrite(self):
+ request = TestRequest(environ = {"OVERWRITE": "t"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getOverwrite(), True)
+
+ def test_t_overwrite(self):
+ request = TestRequest(environ = {"OVERWRITE": "T"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getOverwrite(), True)
+
+ def test_F_overwrite(self):
+ request = TestRequest(environ = {"OVERWRITE": "F"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getOverwrite(), False)
+
+ def test_f_overwrite(self):
+ request = TestRequest(environ = {"OVERWRITE": "f"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getOverwrite(), False)
+
+ def test_bad_overwrite(self):
+ request = TestRequest(environ = {"OVERWRITE": "x"})
+ copy = COPY(None, request)
+
+ self.assertRaises(zope.webdav.interfaces.BadRequest, copy.getOverwrite)
+
+ def test_default_destination_path(self):
+ request = TestRequest()
+ copy = COPY(None, request)
+
+ self.assertRaises(
+ zope.webdav.interfaces.BadRequest, copy.getDestinationPath)
+
+ def test_destination_path(self):
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/testpath"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getDestinationPath(), "/testpath")
+
+ def test_destination_path_slash(self):
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/testpath/"})
+ copy = COPY(None, request)
+
+ self.assertEqual(copy.getDestinationPath(), "/testpath")
+
+ def test_getDestinationPath_wrong_server(self):
+ request = TestRequest(
+ environ = {"DESTINATION": "http://www.server.com/testpath"})
+ copy = COPY(None, request)
+
+ self.assertRaises(zope.webdav.interfaces.BadGateway,
+ copy.getDestinationPath)
+
+ def test_getDestinationNameAndParentObject(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/testpath"})
+
+ copy = COPY(resource, request)
+ destname, destob, parent = copy.getDestinationNameAndParentObject()
+ self.assertEqual(destname, "testpath")
+ self.assertEqual(destob, None)
+ self.assertEqual(parent, self.root)
+
+ def test_getDestinationNameAndParentObject_destob_overwrite(self):
+ destresource = self.root["destresource"] = test_locking.Resource()
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/destresource",
+ "OVERWRITE": "T"})
+
+ copy = COPY(resource, request)
+ destname, destob, parent = copy.getDestinationNameAndParentObject()
+ self.assertEqual(destname, "destresource")
+ self.assertEqual(destob, destresource)
+ self.assert_("destresource" not in self.root)
+ self.assertEqual(parent, self.root)
+
+ def test_getDestinationNameAndParentObject_destob_overwrite_failed(self):
+ destresource = self.root["destresource"] = test_locking.Resource()
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/destresource",
+ "OVERWRITE": "F"})
+
+ copy = COPY(resource, request)
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+ copy.getDestinationNameAndParentObject)
+ self.assert_("destresource" in self.root)
+
+ def test_getDestinationNameAndParentObject_noparent(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/noparent/testpath"})
+
+ copy = COPY(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError,
+ copy.getDestinationNameAndParentObject)
+
+ def test_getDestinationNameAndParentObject_destob_sameob(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/resource",
+ "OVERWRITE": "T"})
+
+ copy = COPY(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ForbiddenError,
+ copy.getDestinationNameAndParentObject)
+
+ def test_nocopier(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ copy = COPY(resource, request)
+ self.assertRaises(MethodNotAllowed, copy.COPY)
+
+ def test_nomovier(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ copy = MOVE(resource, request)
+ self.assertRaises(MethodNotAllowed, copy.MOVE)
+
+
+class DummyResourceURL(object):
+ interface.implements(IAbsoluteURL)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ def __str__(self):
+ if getattr(self.context, "__parent__", None) is not None:
+ path = DummyResourceURL(self.context.__parent__, None)()
+ elif IContainmentRoot.providedBy(self.context):
+ return ""
+ else:
+ path = ""
+
+ if getattr(self.context, "__name__", None) is not None:
+ path += "/" + self.context.__name__
+ elif test_locking.IResource.providedBy(self.context):
+ path += "/resource"
+ elif test_locking.ICollectionResource.providedBy(self.context):
+ path += "/collection"
+ else:
+ raise ValueError("unknown context type")
+
+ return path
+
+ __call__ = __str__
+
+
+class Copier(object):
+ interface.implements(IObjectCopier)
+
+ iscopyable = True
+ canCopyableTo = True
+
+ def __init__(self, context):
+ self.context = context
+
+ def copyable(self):
+ return self.iscopyable
+
+ def copyTo(self, target, new_name):
+ target[new_name] = self.context
+
+ return new_name
+
+ def copyableTo(self, parent, destname):
+ return self.canCopyableTo
+
+
+class COPYObjectTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.root = test_locking.RootCollectionResource()
+
+ baseSetUp()
+
+ Copier.iscopyable = True
+ Copier.canCopyableTo = True
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(Copier, (test_locking.IResource,))
+ gsm.registerAdapter(DummyResourceURL,
+ (test_locking.IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ del self.root
+
+ baseTearDown()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(Copier, (test_locking.IResource,))
+ gsm.unregisterAdapter(DummyResourceURL,
+ (test_locking.IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_copy(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ copy = COPY(resource, request)
+ result = copy.COPY()
+
+ self.assertEqual(request.response.getStatus(), 201)
+ self.assertEqual(request.response.getHeader("Location"),
+ "/copy_of_resource")
+ self.assertEqual(result, "")
+ self.assertEqual(self.root["copy_of_resource"] is resource, True)
+ self.assertEqual(self.root["resource"] is resource, True)
+
+ def test_copy_overwrite(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ resource2 = self.root["resource2"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/resource2",
+ "OVERWRITE": "T"})
+
+ copy = COPY(resource, request)
+ result = copy.COPY()
+
+ self.assertEqual(request.response.getStatus(), 204)
+ self.assertEqual(result, "")
+ self.assertEqual(self.root["resource"] is resource, True)
+ self.assertEqual(self.root["resource2"] is resource, True)
+
+ def test_copy_not_copyable(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ Copier.iscopyable = False
+
+ copy = COPY(resource, request)
+ self.assertRaises(MethodNotAllowed, copy.COPY)
+
+ def test_copy_not_copyableto(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ Copier.canCopyableTo = False
+
+ copy = COPY(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError, copy.COPY)
+
+
+class Movier(object):
+ interface.implements(IObjectMover)
+
+ isMoveable = True
+ isMoveableTo = True
+
+ def __init__(self, context):
+ self.context = context
+
+ def moveTo(self, target, new_name):
+ del self.context.__parent__[self.context.__name__]
+ target[new_name] = self.context
+
+ return new_name
+
+ def moveable(self):
+ return self.isMoveable
+
+ def moveableTo(self, target, name = None):
+ return self.isMoveableTo
+
+
+class MOVEObjectTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.root = test_locking.RootCollectionResource()
+
+ baseSetUp()
+
+ Movier.isMoveable = True
+ Movier.isMoveableTo = True
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(Movier, (test_locking.IResource,))
+ gsm.registerAdapter(DummyResourceURL,
+ (test_locking.IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ del self.root
+
+ baseTearDown()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(Movier, (test_locking.IResource,))
+ gsm.unregisterAdapter(DummyResourceURL,
+ (test_locking.IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_move(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ move = MOVE(resource, request)
+ result = move.MOVE()
+
+ self.assertEqual(request.response.getStatus(), 201)
+ self.assertEqual(request.response.getHeader("Location"),
+ "/copy_of_resource")
+ self.assertEqual("resource" not in self.root, True)
+ self.assertEqual(self.root["copy_of_resource"], resource)
+
+ def test_move_overwrite(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ resource2 = self.root["resource2"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/resource2",
+ "OVERWRITE": "T"})
+
+ move = MOVE(resource, request)
+ result = move.MOVE()
+
+ self.assertEqual(request.response.getStatus(), 204)
+ self.assertEqual("resource" not in self.root, True)
+ self.assertEqual(self.root["resource2"] is resource, True)
+
+ def test_move_not_moveable(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ Movier.isMoveable = False
+
+ move = MOVE(resource, request)
+ self.assertRaises(MethodNotAllowed, move.MOVE)
+
+ def test_move_not_moveableTo(self):
+ resource = self.root["resource"] = test_locking.Resource()
+ request = TestRequest(
+ environ = {"DESTINATION": "http://localhost/copy_of_resource"})
+
+ Movier.isMoveableTo = False
+
+ move = MOVE(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError, move.MOVE)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(COPYMOVEParseHeadersTestCase),
+ unittest.makeSuite(COPYObjectTestCase),
+ unittest.makeSuite(MOVEObjectTestCase),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_copymove.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Run all doctests contained within zope.webdav.
+
+$Id$
+"""
+import unittest
+import UserDict
+
+from zope.testing import doctest
+
+import zope.event
+from zope import component
+from zope import interface
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.testing import placelesssetup
+import zope.app.keyreference.interfaces
+from zope.component.interfaces import IComponentLookup
+from zope.app.component.site import SiteManagerAdapter
+from zope.security.testing import Principal, Participation
+from zope.security.management import newInteraction, endInteraction, \
+ queryInteraction
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+from zope.webdav.testing import etreeSetup, etreeTearDown
+
+
+class IDemo(IContained):
+ "a demonstration interface for a demonstration class"
+
+
+class IDemoFolder(IContained, IContainer):
+ "a demostration interface for a demostration folder class"
+
+
+class Demo(object):
+ interface.implements(IDemo, IAttributeAnnotatable)
+
+ __parent__ = __name__ = None
+
+
+class DemoFolder(UserDict.UserDict):
+ interface.implements(IDemoFolder)
+
+ __parent__ = __name__ = None
+
+ def __init__(self, parent = None, name = u''):
+ UserDict.UserDict.__init__(self)
+ self.__parent__ = parent
+ self.__name__ = name
+
+ def __setitem__(self, key, value):
+ value.__name__ = key
+ value.__parent__ = self
+ self.data[key] = value
+
+
+class DemoKeyReference(object):
+ _class_counter = 0
+ interface.implements(zope.app.keyreference.interfaces.IKeyReference)
+
+ def __init__(self, context):
+ self.context = context
+ class_ = type(self)
+ self._id = getattr(context, "__demo_key_reference__", None)
+ if self._id is None:
+ self._id = class_._class_counter
+ context.__demo_key_reference__ = self._id
+ class_._class_counter += 1
+
+ key_type_id = "zope.app.dav.lockingutils.DemoKeyReference"
+
+ def __call__(self):
+ return self.context
+
+ def __hash__(self):
+ return (self.key_type_id, self._id)
+
+ def __cmp__(self, other):
+ if self.key_type_id == other.key_type_id:
+ return cmp(self._id, other._id)
+ return cmp(self.key_type_id, other.key_type_id)
+
+
+class DemoAbsoluteURL(object):
+ interface.implements(IAbsoluteURL)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ def __str__(self):
+ ob = self.context
+ url = ""
+ while ob is not None:
+ url += "/dummy"
+ ob = ob.__parent__
+ if IDemoFolder.providedBy(self.context):
+ url += "/"
+ return url
+
+ __call__ = __str__
+
+
+def contentSetup(test):
+ test.globs["Demo"] = Demo
+ test.globs["DemoFolder"] = DemoFolder
+
+
+def contentTeardown(test):
+ del test.globs["Demo"]
+ del test.globs["DemoFolder"]
+
+
+def lockingSetUp(test):
+ placelesssetup.setUp(test)
+
+ # create principal
+ participation = Participation(Principal('michael'))
+ if queryInteraction() is not None:
+ queryInteraction().add(participation)
+ else:
+ newInteraction(participation)
+
+ events = test.globs["events"] = []
+ zope.event.subscribers.append(events.append)
+
+ component.getGlobalSiteManager().registerAdapter(
+ DemoKeyReference, (IDemo,),
+ zope.app.keyreference.interfaces.IKeyReference)
+ component.getGlobalSiteManager().registerAdapter(
+ DemoKeyReference, (IDemoFolder,),
+ zope.app.keyreference.interfaces.IKeyReference)
+ component.getGlobalSiteManager().registerAdapter(
+ SiteManagerAdapter, (interface.Interface,), IComponentLookup)
+ component.getGlobalSiteManager().registerAdapter(
+ DemoAbsoluteURL,
+ (IDemo, interface.Interface),
+ IAbsoluteURL)
+ component.getGlobalSiteManager().registerAdapter(
+ DemoAbsoluteURL,
+ (IDemoFolder, interface.Interface),
+ IAbsoluteURL)
+
+ # expose these classes to the test
+ test.globs["Demo"] = Demo
+ test.globs["DemoFolder"] = DemoFolder
+
+
+def lockingTearDown(test):
+ placelesssetup.tearDown(test)
+
+ events = test.globs.pop("events")
+ assert zope.event.subscribers.pop().__self__ is events
+ del events[:] # being paranoid
+
+ del test.globs["Demo"]
+ del test.globs["DemoFolder"]
+
+ component.getGlobalSiteManager().unregisterAdapter(
+ DemoKeyReference, (IDemo,),
+ zope.app.keyreference.interfaces.IKeyReference)
+ component.getGlobalSiteManager().unregisterAdapter(
+ DemoKeyReference, (IDemoFolder,),
+ zope.app.keyreference.interfaces.IKeyReference)
+ component.getGlobalSiteManager().unregisterAdapter(
+ SiteManagerAdapter, (interface.Interface,), IComponentLookup)
+ component.getGlobalSiteManager().unregisterAdapter(
+ DemoAbsoluteURL,
+ (IDemo, interface.Interface), IAbsoluteURL)
+ component.getGlobalSiteManager().unregisterAdapter(
+ DemoAbsoluteURL,
+ (IDemoFolder, interface.Interface),
+ IAbsoluteURL)
+
+ endInteraction()
+
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocTestSuite("zope.webdav.properties",
+ setUp = contentSetup, tearDown = contentTeardown),
+ doctest.DocTestSuite("zope.webdav.utils",
+ setUp = etreeSetup, tearDown = etreeTearDown),
+## doctest.DocTestSuite("zope.webdav.zetree", optionflags = \
+## doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE),
+ doctest.DocTestSuite("zope.webdav.testing",
+ optionflags = doctest.ELLIPSIS + \
+ doctest.NORMALIZE_WHITESPACE),
+ doctest.DocTestSuite("zope.webdav.coreproperties"),
+ doctest.DocFileSuite("datamodel.txt", package = "zope.webdav"),
+ doctest.DocTestSuite("zope.webdav.lockingutils",
+ setUp = lockingSetUp, tearDown = lockingTearDown),
+ doctest.DocTestSuite("zope.webdav.deadproperties"),
+ doctest.DocTestSuite("zope.webdav.adapters"),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,250 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the WebDAV widget framework.
+
+$Id$
+"""
+
+import unittest
+import datetime
+from cStringIO import StringIO
+
+from zope import component
+from zope import schema
+from zope import interface
+from zope.schema.interfaces import RequiredMissing
+from zope.interface.verify import verifyObject
+from zope.app.form.interfaces import ConversionError, MissingInputError
+from zope.datetime import tzinfo
+
+from zope.webdav import widgets
+from zope.webdav.interfaces import IDAVInputWidget
+from zope.webdav.ietree import IEtree
+from zope.webdav.testing import etreeSetup, etreeTearDown
+
+from test_widgets import TestWebDAVRequest
+
+class _WebDAVWidgetTest(unittest.TestCase):
+
+ namespace = u'testns:'
+ name = u'foo'
+ missing_name = u'someotherproperty'
+
+ field_content = u'' # field value assigned to the demo content.
+ rendered_content = None # string value of the some value in ...
+ bad_rendered_content = None # if not None try to parse this data.
+
+ _FieldFactory = None
+ _WidgetFactory = None
+
+ def tearDown(self):
+ etreeTearDown()
+ del self.etree
+
+ def setUp(self):
+ etreeSetup()
+ self.etree = component.getUtility(IEtree)
+
+ def setUpContent(self, desc = u'', title = u'Foo Title', element = None):
+ ## setup the field first to stop some really weird errors
+ foofield = self._FieldFactory(
+ title = title,
+ description = desc)
+ class IDemoContent(interface.Interface):
+ foo = foofield
+
+ class DemoContent(object):
+ interface.implements(IDemoContent)
+
+ self.content = DemoContent()
+ field = IDemoContent['foo']
+ self.field = field.bind(self.content)
+ self.setUpWidget(element)
+
+ def setUpWidget(self, element = None):
+ request = TestWebDAVRequest(element)
+ self.widget = self._WidgetFactory(self.field, request)
+ self.widget.namespace = self.namespace
+
+
+class WebDAVBaseInputWidgetTest(_WebDAVWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.DAVInputWidget
+
+ def setUp(self):
+ super(WebDAVBaseInputWidgetTest, self).setUp()
+ self.setUpContent()
+
+ def test_dontuseDAVWidgetToFieldValue(self):
+ self.assertRaises(NotImplementedError, self.widget.toFieldValue,
+ self.etree.Element(self.etree.QName(self.namespace,
+ self.name)))
+
+
+class WebDAVInputWidgetTest(_WebDAVWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.TextDAVInputWidget
+
+ def setUp(self):
+ super(WebDAVInputWidgetTest, self).setUp()
+
+ self.element = self.etree.Element(
+ self.etree.QName(self.namespace, self.name))
+ if self.rendered_content is not None:
+ self.element.text = self.rendered_content
+ self.setUpContent(element = self.element)
+
+ def test_interface(self):
+ self.assertEqual(verifyObject(IDAVInputWidget, self.widget), True)
+
+ def test_hasInput(self):
+ self.assertEqual(self.widget.hasInput(), True)
+
+ def test_convert(self):
+ self.assertEqual(self.widget.toFieldValue(self.element),
+ self.field_content)
+
+ def test_getInputValue(self):
+ value = self.widget.getInputValue()
+ self.assertEqual(value, self.field_content)
+ self.field.validate(value) # this will raise an exception if false
+
+ def test_noInput(self):
+ element = self.etree.Element(
+ self.etree.QName(self.namespace, self.missing_name))
+ element.text = self.rendered_content
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ self.assertEqual(widget.hasInput(), False)
+
+ def test_getInputValue_NoInput(self):
+ element = self.etree.Element(
+ self.etree.QName(self.namespace, self.missing_name))
+ element.text = self.rendered_content
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ self.assertRaises(MissingInputError, widget.getInputValue)
+
+ def _test_badinput(self, bad_rendered_content = None):
+ # Only run this test if the self.bad_rendered_content is not None.
+ # The test case must expility call this method.
+ if bad_rendered_content is None:
+ bad_rendered_content = self.bad_rendered_content
+ element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+ element.text = bad_rendered_content
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ self.assertRaises(ConversionError, widget.getInputValue)
+
+
+class TextWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.TextDAVInputWidget
+
+ field_content = u"Foo Value"
+ rendered_content = u"Foo Value"
+
+ def test_noinput(self):
+ element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ value = widget.getInputValue()
+ self.field.validate(value)
+
+
+class IntWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+ _FieldFactory = schema.Int
+ _WidgetFactory = widgets.IntDAVInputWidget
+
+ field_content = 10
+ rendered_content = u"10"
+ bad_rendered_content = u"X10"
+
+ def test_badinput(self):
+ super(IntWebDAVInputWidgetTest, self)._test_badinput()
+
+ def test_noinput(self):
+ element = self.etree.Element(self.etree.QName(self.namespace, self.name))
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ self.assertEquals(widget.hasInput(), True)
+ value = widget.getInputValue()
+ self.assertEquals(value, None)
+ self.assertRaises(RequiredMissing, self.field.validate, value)
+
+
+class FloatWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+ _FieldFactory = schema.Float
+ _WidgetFactory = widgets.FloatDAVInputWidget
+
+ field_content = 10.4
+ rendered_content = u"10.4"
+ bad_rendered_content = u"X10.4"
+
+ def test_badinput(self):
+ super(FloatWebDAVInputWidgetTest, self)._test_badinput()
+
+ def test_noinput(self):
+ element = self.etree.Element(
+ self.etree.QName(self.namespace, self.name))
+ request = TestWebDAVRequest(element)
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ self.assertEquals(widget.hasInput(), True)
+ value = widget.getInputValue()
+ self.assertEquals(value, None)
+ self.assertRaises(RequiredMissing, self.field.validate, value)
+
+
+class DatetimeWebDAVInputWidgetTest(WebDAVInputWidgetTest):
+
+ _FieldFactory = schema.Datetime
+ _WidgetFactory = widgets.DatetimeDAVInputWidget
+
+ rendered_content = u'Wed, 24 May 2006 00:00:58 +0100'
+ field_content = datetime.datetime(2006, 5, 24, 0, 0, 58,
+ tzinfo = tzinfo(60))
+
+ def test_badinput(self):
+ super(DatetimeWebDAVInputWidgetTest, self)._test_badinput(
+ u'NODAY, 24 May 2006 00:00:58 +0100')
+
+
+class DateWebDAVInputWidgetTest(DatetimeWebDAVInputWidgetTest):
+
+ _FieldFactory = schema.Date
+ _WidgetFactory = widgets.DateDAVInputWidget
+
+ field_content = datetime.date(2006, 5, 24)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(WebDAVBaseInputWidgetTest),
+ unittest.makeSuite(TextWebDAVInputWidgetTest),
+ unittest.makeSuite(IntWebDAVInputWidgetTest),
+ unittest.makeSuite(FloatWebDAVInputWidgetTest),
+ unittest.makeSuite(DatetimeWebDAVInputWidgetTest),
+ unittest.makeSuite(DateWebDAVInputWidgetTest),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_inputwidgets.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_locking.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_locking.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,688 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test WebDAV propfind method.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import UserDict
+import datetime
+import random
+import time
+
+from zope import interface
+from zope import schema
+import zope.schema.interfaces
+from zope import component
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.api import getPath, traverse, getRoot
+from zope.traversing.interfaces import IContainmentRoot
+from zope.location.traversing import LocationPhysicallyLocatable
+from zope.app.container.interfaces import IReadContainer
+from zope.webdav.locking import DEFAULTTIMEOUT, MAXTIMEOUT
+from zope.webdav.locking import UNLOCKMethod, LOCKMethod
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+import zope.webdav.publisher
+import zope.webdav.interfaces
+
+_randGen = random.Random(time.time())
+
+class TestWebDAVRequest(zope.webdav.publisher.WebDAVRequest):
+
+ def __init__(self, lockinfo = {}, body = "", environ = {}):
+ env = environ.copy()
+
+ if body:
+ env.setdefault("CONTENT_TYPE", "text/xml")
+ env.setdefault("CONTENT_LENGTH", len(body))
+
+ super(TestWebDAVRequest, self).__init__(StringIO(body), env)
+
+ self.processInputs()
+
+
+class LOCKINGHeaders(unittest.TestCase):
+
+ def test_depth_empty(self):
+ lock = LOCKMethod(None, TestWebDAVRequest())
+ self.assertEqual(lock.getDepth(), "infinity")
+
+ def test_depth_zero(self):
+ lock = LOCKMethod(None, TestWebDAVRequest(environ = {"DEPTH": "0"}))
+ self.assertEqual(lock.getDepth(), "0")
+
+ def test_timeout_default(self):
+ lock = LOCKMethod(None, TestWebDAVRequest(environ = {}))
+ self.assertEqual(lock.getTimeout(),
+ datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+ def test_timeout_infinity(self):
+ request = TestWebDAVRequest(environ = {"TIMEOUT": "infinity"})
+ lock = LOCKMethod(None, request)
+ self.assertEqual(lock.getTimeout(),
+ datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+ def test_timeout_second_500(self):
+ request = TestWebDAVRequest(environ = {"TIMEOUT": "Second-500"})
+ lock = LOCKMethod(None, request)
+ self.assertEqual(lock.getTimeout(),
+ datetime.timedelta(seconds = 500))
+
+ def test_invalid_second(self):
+ request = TestWebDAVRequest(environ = {"TIMEOUT": "XXX-500"})
+ lock = LOCKMethod(None, request)
+ self.assertRaises(zope.webdav.interfaces.BadRequest, lock.getTimeout)
+
+ def test_invalid_second_value(self):
+ request = TestWebDAVRequest(environ = {"TIMEOUT": "Second-500x"})
+ lock = LOCKMethod(None, request)
+ self.assertRaises(zope.webdav.interfaces.BadRequest, lock.getTimeout)
+
+ def test_big_second(self):
+ request = TestWebDAVRequest(
+ environ = {"TIMEOUT": "Second-%d" %(MAXTIMEOUT + 100)})
+ lock = LOCKMethod(None, request)
+ self.assertEqual(lock.getTimeout(),
+ datetime.timedelta(seconds = DEFAULTTIMEOUT))
+
+
+class DAVLockmanager(object):
+ interface.implements(zope.webdav.interfaces.IDAVLockmanager)
+
+ def __init__(self, context):
+ self.context = context
+
+ def generateLocktoken(self):
+ return "opaquelocktoken:%s-%s-00105A989226:%.03f" % \
+ (_randGen.random(), _randGen.random(), time.time())
+
+ def lock(self, scope, type, owner, duration, depth,
+ context = None):
+ if context is None:
+ if self.islockedObject(self.context):
+ raise zope.webdav.interfaces.AlreadyLocked(self.context)
+
+ ob = removeSecurityProxy(self.context)
+ setattr(ob, "_is_locked", {"scope": scope,
+ "type": type,
+ "owner": owner,
+ "duration": duration,
+ "depth": depth,
+ "token": self.generateLocktoken(),
+ "indirectlylocked": [],
+ "lockroot": getPath(self.context)})
+ else:
+ if self.islockedObject(context):
+ raise zope.webdav.interfaces.AlreadyLocked(context)
+
+ ob = removeSecurityProxy(context)
+ setattr(ob, "_is_indirectly_locked",
+ {"lockroot": getPath(self.context),
+ "rootinfo": removeSecurityProxy(self.context)._is_locked,
+ })
+ root = removeSecurityProxy(self.context)
+ root._is_locked["indirectlylocked"].append(getPath(ob))
+
+ if context is None:
+ context = self.context
+
+ if depth == "infinity" and IReadContainer.providedBy(context):
+ for subob in context.values():
+ self.lock(scope, type, owner, duration, depth, subob)
+
+ def getActivelock(self, request = None):
+ return getActiveLock(self.context, request)
+
+ def refreshlock(self, timeout):
+ if not self.islockedObject(self.context):
+ raise zope.webdav.interfaces.ConflictError(
+ self.context, u"The context is not locked")
+
+ ob = removeSecurityProxy(self.context)
+ root = getRoot(self.context)
+
+ if getattr(ob, "_is_indirectly_locked", None) is not None:
+ lockroot = traverse(root, ob._is_indirectly_locked["lockroot"])
+ else:
+ lockroot = ob
+
+ removeSecurityProxy(lockroot)._is_locked["duration"] = timeout
+
+ def unlock(self):
+ if not self.islockedObject(self.context):
+ raise Exception("object is not locked")
+
+ ob = removeSecurityProxy(self.context)
+ root = getRoot(self.context)
+
+ if getattr(ob, "_is_indirectly_locked", None) is not None:
+ lockroot = traverse(root, ob._is_indirectly_locked["lockroot"])
+ else:
+ lockroot = ob
+
+ lockroot = removeSecurityProxy(lockroot)
+ for path in lockroot._is_locked["indirectlylocked"]:
+ ob = removeSecurityProxy(traverse(root, path))
+ delattr(ob, "_is_indirectly_locked")
+ delattr(lockroot, "_is_locked")
+
+ def islocked(self):
+ return self.islockedObject(self.context)
+
+ def islockedObject(self, context):
+ ob = removeSecurityProxy(context)
+ return getattr(ob, "_is_locked", None) is not None or \
+ getattr(ob, "_is_indirectly_locked", None) is not None
+
+
+ at interface.implementer(zope.webdav.coreproperties.IActiveLock)
+def getActiveLock(context, request):
+ ob = removeSecurityProxy(context)
+
+ data = getattr(ob, "_is_indirectly_locked", None)
+ if data is not None:
+ root = traverse(getRoot(context), data["lockroot"])
+ root = removeSecurityProxy(root)
+ return ActiveLock(root._is_locked)
+
+ data = getattr(ob, "_is_locked", None)
+ if data is not None:
+ return ActiveLock(data)
+
+ return None
+
+
+class ActiveLock(object):
+ interface.implements(zope.webdav.coreproperties.IActiveLock)
+
+ def __init__(self, data):
+ self.data = data
+
+ @property
+ def lockscope(self):
+ return [u"exclusive"]
+
+ @property
+ def locktype(self):
+ return [u"write"]
+
+ @property
+ def depth(self):
+ return self.data["depth"]
+
+ @property
+ def owner(self):
+ return self.data["owner"]
+
+ @property
+ def timeout(self):
+ seconds = self.data["duration"]
+ if not isinstance(seconds, int):
+ seconds = seconds.seconds
+ return u"Second-%d" % seconds
+
+ @property
+ def locktoken(self):
+ return [self.data["token"]]
+
+ @property
+ def lockroot(self):
+ return "http://localhost%s" % self.data["lockroot"]
+
+
+class Lockdiscovery(object):
+ interface.implements(zope.webdav.coreproperties.IDAVLockdiscovery)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ @property
+ def lockdiscovery(self):
+ activelock = getActiveLock(self.context, self.request)
+ if activelock is not None:
+ return [activelock]
+ return None
+
+
+class IResource(interface.Interface):
+
+ text = schema.TextLine(
+ title = u"Example Text Property")
+
+ intprop = schema.Int(
+ title = u"Example Int Property")
+
+
+class Resource(object):
+ interface.implements(IResource)
+
+ def __init__(self, text = u"", intprop = 0):
+ self.text = text
+ self.intprop = intprop
+
+
+class ICollectionResource(IReadContainer):
+
+ title = schema.TextLine(
+ title = u"Title",
+ description = u"Title of resource")
+
+
+class CollectionResource(UserDict.UserDict):
+ interface.implements(ICollectionResource)
+
+ title = None
+
+ def __setitem__(self, key, val):
+ val.__parent__ = self
+ val.__name__ = key
+
+ self.data[key] = val
+
+class RootCollectionResource(CollectionResource):
+ interface.implements(IContainmentRoot)
+
+
+class LOCKTestCase(unittest.TestCase):
+
+ def setUp(self):
+ etreeSetup()
+
+ self.root = RootCollectionResource()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.registerAdapter(DAVLockmanager, (IResource,))
+ gsm.registerAdapter(DAVLockmanager, (ICollectionResource,))
+ gsm.registerAdapter(LocationPhysicallyLocatable, (IResource,))
+ gsm.registerAdapter(LocationPhysicallyLocatable, (ICollectionResource,))
+
+ gsm.registerAdapter(Lockdiscovery,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerUtility(zope.webdav.coreproperties.lockdiscovery,
+ name = "{DAV:}lockdiscovery")
+ gsm.registerAdapter(getActiveLock,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+
+ gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+ (zope.schema.interfaces.IText,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+ (zope.schema.interfaces.IURI,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.properties.OpaqueWidget,
+ (zope.webdav.properties.DeadField,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.widgets.ListDAVWidget,
+ (zope.schema.interfaces.IList,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.widgets.ObjectDAVWidget,
+ (zope.schema.interfaces.IObject,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+ gsm.unregisterAdapter(DAVLockmanager, (IResource,))
+ gsm.unregisterAdapter(DAVLockmanager, (ICollectionResource,))
+ gsm.unregisterAdapter(LocationPhysicallyLocatable, (IResource,))
+ gsm.unregisterAdapter(
+ LocationPhysicallyLocatable, (ICollectionResource,))
+
+ del self.root
+
+ def test_handleLock_notlockinfo(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:notlockinfo xmlns:D="DAV:">
+ Not a lockinfo.
+</D:notlockinfo>"""
+ request = TestWebDAVRequest(body = body)
+
+ lock = LOCKMethod(None, request)
+ self.assertRaises(
+ zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+ def test_handleLock_invalidDepth(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ Not a lockinfo.
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body, environ = {"DEPTH": "1"})
+
+ lock = LOCKMethod(None, request)
+ self.assertRaises(
+ zope.webdav.interfaces.BadRequest, lock.handleLock)
+
+ def test_handleLock_nolockscope(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ Not a lockinfo.
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+
+ lock = LOCKMethod(None, request)
+ self.assertRaises(
+ zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+ def test_handleLock_nolocktype(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ <D:lockscope><D:exculsive/></D:lockscope>
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+
+ lock = LOCKMethod(None, request)
+ self.assertRaises(
+ zope.webdav.interfaces.UnprocessableError, lock.handleLock)
+
+ def test_handleLock(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ errors = lock.handleLock()
+
+ lockmanager = DAVLockmanager(resource)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ def test_handleLock_alreadLocked(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ errors = lock.handleLock()
+ self.assertEqual(errors, [])
+
+ lockmanager = DAVLockmanager(resource)
+ self.assertEqual(lockmanager.islocked(), True)
+
+ lock = LOCKMethod(resource, request)
+ errors = lock.handleLock()
+ self.assertEqual(len(errors), 1)
+ self.assert_(zope.webdav.interfaces.IAlreadyLocked.providedBy(errors[0]))
+
+ def test_handleLockRefresh_notlocked(self):
+ request = TestWebDAVRequest()
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+ lock.handleLockRefresh)
+
+ def test_handleLockRefresh_noifheader(self):
+ request = TestWebDAVRequest()
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+ lock.handleLockRefresh)
+
+ def test_handleLockRefresh_ifbadheader(self):
+ request = TestWebDAVRequest(environ = {"IF": "<xxx>"})
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+ lock.handleLockRefresh)
+
+ def test_handleLockRefresh_ifbadheader2(self):
+ request = TestWebDAVRequest(environ = {"IF": "xxx"})
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed,
+ lock.handleLockRefresh)
+
+ def test_handleLockRefresh_defaulttimeout(self):
+ request = TestWebDAVRequest(environ = {"IF": "xxx"})
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ self.assertEqual(lockmanager.getActivelock().timeout,
+ u"Second-100")
+
+ locktoken = lockmanager.getActivelock().locktoken[0]
+ request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken})
+ lock = LOCKMethod(resource, request)
+
+ lock.handleLockRefresh()
+
+ self.assertEqual(lockmanager.getActivelock().timeout,
+ u"Second-720")
+
+ def test_handleLockRefresh_timeout(self):
+ request = TestWebDAVRequest(environ = {"IF": "xxx"})
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ self.assertEqual(lockmanager.getActivelock().timeout,
+ u"Second-100")
+
+ locktoken = lockmanager.getActivelock().locktoken[0]
+ request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken,
+ "TIMEOUT": "Second-500"})
+ lock = LOCKMethod(resource, request)
+
+ lock.handleLockRefresh()
+
+ self.assertEqual(lockmanager.getActivelock().timeout,
+ u"Second-500")
+
+ def test_lock_alreadLocked(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ lock = LOCKMethod(resource, request)
+ self.assertRaises(zope.webdav.interfaces.WebDAVErrors, lock.LOCK)
+
+ def test_lock(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:lockinfo xmlns:D="DAV:">
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ <D:owner>
+ <D:href>http://example.org/~ejw/contact.html</D:href>
+ </D:owner>
+</D:lockinfo>"""
+ request = TestWebDAVRequest(body = body)
+ resource = self.root["resource"] = Resource()
+
+ lock = LOCKMethod(resource, request)
+ result = lock.LOCK()
+
+ locktoken = request.response.getHeader("lock-token")
+ lockmanager = DAVLockmanager(resource)
+ currentlocktoken = lockmanager.getActivelock().locktoken[0]
+ self.assert_(locktoken[0], "<")
+ self.assert_(locktoken[-1], ">")
+ self.assertEqual(currentlocktoken, locktoken[1:-1])
+
+ assertXMLEqual(result, """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+ <ns0:activelock xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+ <ns0:depth xmlns:ns0="DAV:">infinity</ns0:depth>
+ <ns0:owner xmlns:D="DAV:">
+ <ns0:href>http://example.org/~ejw/contact.html</ns0:href>
+ </ns0:owner>
+ <ns0:timeout xmlns:ns0="DAV:">Second-720</ns0:timeout>
+ <ns0:locktoken xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+ </ns0:locktoken>
+ <ns0:lockroot xmlns:ns0="DAV:">http://localhost/resource</ns0:lockroot>
+ </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken[1:-1])
+
+ def test_refreshlock_alreadLocked(self):
+ request = TestWebDAVRequest()
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write", u"Michael",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ lock = LOCKMethod(resource, request)
+ self.assertRaises(zope.webdav.interfaces.PreconditionFailed, lock.LOCK)
+
+ def test_refreshlock(self):
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write",
+ u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+ locktoken = lockmanager.getActivelock().locktoken[0]
+
+ request = TestWebDAVRequest(environ = {"IF": "<%s>" % locktoken})
+ lock = LOCKMethod(resource, request)
+ result = lock.LOCK()
+
+ assertXMLEqual(result, """<ns0:prop xmlns:ns0="DAV:">
+<ns0:lockdiscovery xmlns:ns0="DAV:">
+ <ns0:activelock xmlns:ns0="DAV:">
+ <ns0:lockscope xmlns:ns0="DAV:"><ns0:exclusive xmlns:ns0="DAV:"/></ns0:lockscope>
+ <ns0:locktype xmlns:ns0="DAV:"><ns0:write xmlns:ns0="DAV:"/></ns0:locktype>
+ <ns0:depth xmlns:ns0="DAV:">0</ns0:depth>
+ <ns0:owner xmlns:D="DAV:">Michael</ns0:owner>
+ <ns0:timeout xmlns:ns0="DAV:">Second-720</ns0:timeout>
+ <ns0:locktoken xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">%s</ns0:href>
+ </ns0:locktoken>
+ <ns0:lockroot xmlns:ns0="DAV:">http://localhost/resource</ns0:lockroot>
+ </ns0:activelock>
+</ns0:lockdiscovery></ns0:prop>""" % locktoken)
+
+ def test_unlock_nolocktoken(self):
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write",
+ u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ unlock = UNLOCKMethod(resource, TestWebDAVRequest())
+ self.assertRaises(zope.webdav.interfaces.BadRequest, unlock.UNLOCK)
+
+ def test_unlock_badlocktoken(self):
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write",
+ u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ request = TestWebDAVRequest(environ = {"LOCK_TOKEN": "XXX"})
+ unlock = UNLOCKMethod(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError, unlock.UNLOCK)
+
+ def test_unlock_badlocktoken2(self):
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write",
+ u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+
+ request = TestWebDAVRequest(environ = {"LOCK_TOKEN": "<XXX>"})
+ unlock = UNLOCKMethod(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError, unlock.UNLOCK)
+
+ def test_unlock(self):
+ resource = self.root["resource"] = Resource()
+
+ lockmanager = DAVLockmanager(resource)
+ lockmanager.lock(u"exclusive", u"write",
+ u"""<D:owner xmlns:D="DAV:">Michael</D:owner>""",
+ duration = datetime.timedelta(seconds = 100),
+ depth = "0")
+ locktoken = lockmanager.getActivelock().locktoken[0]
+
+ request = TestWebDAVRequest(
+ environ = {"LOCK_TOKEN": "<%s>" % locktoken})
+ unlock = UNLOCKMethod(resource, request)
+ result = unlock.UNLOCK()
+
+ self.assertEqual(result, "")
+ self.assertEqual(request.response.getStatus(), 204)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(LOCKINGHeaders),
+ unittest.makeSuite(LOCKTestCase),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_locking.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,570 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test WebDAV propfind method.
+
+It is easier to do this has a unit test has we have complete control over
+what properties are defined or not.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+import UserDict
+
+from zope import interface
+from zope import component
+from zope import schema
+import zope.schema.interfaces
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.container.interfaces import IReadContainer
+
+import zope.webdav.properties
+import zope.webdav.publisher
+import zope.webdav.widgets
+import zope.webdav.exceptions
+import zope.webdav.coreproperties
+from zope.webdav.propfind import PROPFIND
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+ def __init__(self, properties = None, environ = {}):
+ if properties is not None:
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+ %s
+</propfind>
+""" % properties
+ else:
+ body = ""
+
+ env = environ.copy()
+ env.setdefault("REQUEST_METHOD", "PROPFIND")
+ env.setdefault("CONTENT_TYPE", "text/xml")
+ env.setdefault("CONTENT_LENGTH", len(body))
+
+ super(TestRequest, self).__init__(StringIO(body), env)
+
+ # call processInputs now since we are in a unit test.
+ self.processInputs()
+
+
+class PROPFINDBodyParsed(PROPFIND):
+
+ propertiesFactory = extraArg = depth = None
+
+ def handlePropfindResource(self, ob, req,
+ depth, propertiesFactory, extraArg):
+ self.propertiesFactory = propertiesFactory
+ self.extraArg = extraArg
+ self.depth = depth
+
+ return []
+
+
+class PROPFINDBodyTestCase(unittest.TestCase):
+ # Using PROPFINDBodyParsed test that the correct method and arguements
+ # get set up.
+
+ def setUp(self):
+ etreeSetup()
+
+ def tearDown(self):
+ etreeTearDown()
+
+ def checkPropfind(self, properties = None, environ = {}):
+ request = TestRequest(properties = properties, environ = environ)
+ propfind = PROPFINDBodyParsed(None, request)
+ propfind.PROPFIND()
+
+ return propfind
+
+ def test_notxml(self):
+ self.assertRaises(zope.webdav.interfaces.BadRequest, self.checkPropfind,
+ "<propname />", {"CONTENT_TYPE": "text/plain"})
+
+ def test_bad_depthheader(self):
+ self.assertRaises(zope.webdav.interfaces.BadRequest, self.checkPropfind,
+ "<propname />", {"DEPTH": "2"})
+
+ def test_depth_header(self):
+ propf = self.checkPropfind("<propname />", {"DEPTH": "0"})
+ self.assertEqual(propf.depth, "0")
+ propf = self.checkPropfind("<propname />", {"DEPTH": "1"})
+ self.assertEqual(propf.depth, "1")
+ propf = self.checkPropfind("<propname />", {"DEPTH": "infinity"})
+ self.assertEqual(propf.depth, "infinity")
+
+ def test_xml_propname(self):
+ propf = self.checkPropfind("<propname />")
+ self.assertEqual(propf.propertiesFactory, propf.renderPropnames)
+ self.assertEqual(propf.extraArg, None)
+
+ def test_xml_allprop(self):
+ propf = self.checkPropfind("<allprop />")
+ self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+ self.assertEqual(propf.extraArg, None)
+
+ def test_xml_allprop_with_include(self):
+ includes = """<include xmlns="DAV:"><davproperty /></include>"""
+ propf = self.checkPropfind("<allprop/>%s" % includes)
+ self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+ assertXMLEqual(propf.extraArg, includes)
+
+ def test_xml_emptyprop(self):
+ propf = self.checkPropfind("<prop />")
+ self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+ self.assertEqual(propf.extraArg, None)
+
+ def test_xml_someprops(self):
+ props = """<prop xmlns="DAV:"><someprop/></prop>"""
+ propf = self.checkPropfind(props)
+ self.assertEqual(propf.propertiesFactory,
+ propf.renderSelectedProperties)
+ assertXMLEqual(propf.extraArg, props)
+
+ def test_emptybody(self):
+ propf = self.checkPropfind()
+ self.assertEqual(propf.propertiesFactory, propf.renderAllProperties)
+ self.assertEqual(propf.extraArg, None)
+
+ def test_xml_nopropfind_element(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<nopropfind xmlns:D="DAV:" xmlns="DAV:">
+ invalid xml
+</nopropfind>
+ """
+ env = {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)}
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(body), env)
+ request.processInputs()
+
+ propf = PROPFINDBodyParsed(None, request)
+ self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+ propf.PROPFIND)
+
+ def test_xml_propfind_bad_content(self):
+ self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+ self.checkPropfind, properties = "<noproperties />")
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+ exampleintprop = schema.Int(
+ title = u"Example Integer Property")
+
+ exampletextprop = schema.Text(
+ title = u"Example Text Property")
+
+class IExtraPropertyStorage(interface.Interface):
+
+ extratextprop = schema.Text(
+ title = u"Property with no storage")
+
+exampleIntProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}exampleintprop", IExamplePropertyStorage)
+exampleTextProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}exampletextprop", IExamplePropertyStorage)
+extraTextProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}extratextprop", IExtraPropertyStorage)
+
+class ExamplePropertyStorage(object):
+ interface.implements(IExamplePropertyStorage)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def _getproperty(name, default = None):
+ def get(self):
+ return getattr(self.context, name, default)
+ def set(self, value):
+ setattr(self.context, name, value)
+ return property(get, set)
+
+ exampleintprop = _getproperty("intprop", default = 0)
+
+ exampletextprop = _getproperty("text", default = u"")
+
+
+class IResource(interface.Interface):
+
+ text = schema.TextLine(
+ title = u"Example Text Property")
+
+ intprop = schema.Int(
+ title = u"Example Int Property")
+
+
+class Resource(object):
+ interface.implements(IResource)
+
+ def __init__(self, text = u"", intprop = 0):
+ self.text = text
+ self.intprop = intprop
+
+
+class ICollection(IReadContainer):
+ pass
+
+
+class Collection(UserDict.UserDict):
+ interface.implements(ICollection)
+
+ def __setitem__(self, key, value):
+ self.data[key] = value
+ value.__parent__ = self
+ value.__name__ = key
+
+
+class DummyResourceURL(object):
+ interface.implements(IAbsoluteURL)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ def __str__(self):
+ if getattr(self.context, "__parent__", None) is not None:
+ path = DummyResourceURL(self.context.__parent__, None)()
+ else:
+ path = ""
+
+ if getattr(self.context, "__name__", None) is not None:
+ path += "/" + self.context.__name__
+ elif IResource.providedBy(self.context):
+ path += "/resource"
+ elif ICollection.providedBy(self.context):
+ path += "/collection"
+ else:
+ raise ValueError("unknown context type")
+
+ return path
+
+ __call__ = __str__
+
+
+def propfindSetUp():
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.registerUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.registerUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ exampleTextProperty.restricted = False
+ gsm.registerUtility(extraTextProperty,
+ name = "{DAVtest:}extratextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.registerUtility(zope.webdav.coreproperties.resourcetype,
+ name = "{DAV:}resourcetype")
+
+ gsm.registerAdapter(ExamplePropertyStorage,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+ gsm.registerAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
+
+ gsm.registerAdapter(DummyResourceURL,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(DummyResourceURL,
+ (ICollection, zope.webdav.interfaces.IWebDAVRequest))
+
+ gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+ (zope.schema.interfaces.IText,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.widgets.IntDAVWidget,
+ (zope.schema.interfaces.IInt,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.registerAdapter(zope.webdav.widgets.ListDAVWidget,
+ (zope.schema.interfaces.IList,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ gsm.registerAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+ (zope.webdav.interfaces.IPropertyNotFound,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+def propfindTearDown():
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.unregisterUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(extraTextProperty,
+ name = "{DAVtest:}extratextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(zope.webdav.coreproperties.resourcetype,
+ name = "{DAV:}resourcetype")
+
+ gsm.unregisterAdapter(ExamplePropertyStorage,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+ gsm.unregisterAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
+
+ gsm.unregisterAdapter(DummyResourceURL,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(DummyResourceURL,
+ (ICollection, zope.webdav.interfaces.IWebDAVRequest))
+
+ gsm.unregisterAdapter(zope.webdav.widgets.TextDAVWidget,
+ (zope.schema.interfaces.IText,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.widgets.IntDAVWidget,
+ (zope.schema.interfaces.IInt,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.exceptions.PropertyNotFoundError,
+ (zope.webdav.interfaces.IPropertyNotFound,
+ zope.webdav.interfaces.IWebDAVRequest))
+ gsm.unregisterAdapter(zope.webdav.widgets.ListDAVWidget,
+ (zope.schema.interfaces.IList,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+
+class PROPFINDTestRender(unittest.TestCase):
+ # Test all the methods that render a resource into a `response' XML
+ # element. We are going to need to register the DAV widgets for
+ # text and int properties.
+
+ def setUp(self):
+ propfindSetUp()
+
+ def tearDown(self):
+ propfindTearDown()
+
+ def test_renderPropnames(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+
+ propf = PROPFIND(None, None)
+ response = propf.renderPropnames(resource, request, None)
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:"/>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:"/>
+ <ns0:resourcetype />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+ def test_renderSelected(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ etree = component.getUtility(zope.webdav.ietree.IEtree)
+ props = etree.fromstring("""<prop xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+<D:exampleintprop />
+</prop>""")
+ response = propf.renderSelectedProperties(resource, request, props)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+ def test_renderSelected_notfound(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ etree = component.getUtility(zope.webdav.ietree.IEtree)
+ props = etree.fromstring("""<prop xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+<D:extratextprop />
+</prop>""")
+ response = propf.renderSelectedProperties(resource, request, props)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:extratextprop xmlns:ns0="DAVtest:" />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 404 Not Found</ns0:status>
+</ns0:propstat>
+</ns0:response>""")
+
+ def test_renderAllProperties(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ response = propf.renderAllProperties(resource, request, None)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+ <ns0:resourcetype />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+ def test_renderAllProperties_withInclude(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ etree = component.getUtility(zope.webdav.ietree.IEtree)
+ include = etree.fromstring("""<include xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+</include>""")
+ response = propf.renderAllProperties(resource, request, include)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+ <ns0:resourcetype />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+ def test_renderAllProperties_withRestrictedProp(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ exampleTextProperty.restricted = True
+ response = propf.renderAllProperties(resource, request, None)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+ <ns0:resourcetype />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+ def test_renderAllProperties_withRestrictedProp_include(self):
+ resource = Resource("some text", 10)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propf = PROPFIND(None, None)
+
+ exampleTextProperty.restricted = True
+ etree = component.getUtility(zope.webdav.ietree.IEtree)
+ include = etree.fromstring("""<include xmlns="DAV:" xmlns:D="DAVtest:">
+<D:exampletextprop />
+</include>""")
+ response = propf.renderAllProperties(resource, request, include)
+
+ assertXMLEqual(response(), """<ns0:response xmlns:ns0="DAV:">
+<ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+<ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">10</ns01:exampleintprop>
+ <ns0:resourcetype />
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+</ns0:propstat></ns0:response>""")
+
+
+class PROPFINDRecuseTest(unittest.TestCase):
+
+ def setUp(self):
+ propfindSetUp()
+
+ def tearDown(self):
+ propfindTearDown()
+
+ def test_handlePropfindResource(self):
+ collection = Collection()
+ collection["r1"] = Resource("some text - r1", 2)
+ collection["c"] = Collection()
+ collection["c"]["r2"] = Resource("some text - r2", 4)
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ request.processInputs()
+ propf = PROPFIND(collection, request)
+
+ result = propf.PROPFIND()
+ etree = component.getUtility(zope.webdav.ietree.IEtree)
+ etree.fromstring(result)
+
+ assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">/collection/</ns0:href>
+ <ns0:propstat xmlns:ns0="DAV:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns0:resourcetype xmlns:ns0="DAV:"><ns0:collection xmlns:ns0="DAV:"/></ns0:resourcetype>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">/collection/c/</ns0:href>
+ <ns0:propstat xmlns:ns0="DAV:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns0:resourcetype xmlns:ns0="DAV:"><ns0:collection xmlns:ns0="DAV:"/></ns0:resourcetype>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:href xmlns:ns0="DAV:">/collection/c/r2</ns0:href>
+ <ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text - r2</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">4</ns01:exampleintprop>
+ <ns0:resourcetype xmlns:ns0="DAV:"/>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+</ns0:response>
+<ns0:response xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:href xmlns:ns0="DAV:">/collection/r1</ns0:href>
+ <ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns01:exampletextprop xmlns:ns0="DAVtest:">some text - r1</ns01:exampletextprop>
+ <ns01:exampleintprop xmlns:ns0="DAVtest:">2</ns01:exampleintprop>
+ <ns0:resourcetype xmlns:ns0="DAV:"/>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+</ns0:response></ns0:multistatus>
+ """)
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(PROPFINDBodyTestCase),
+ unittest.makeSuite(PROPFINDTestRender),
+ unittest.makeSuite(PROPFINDRecuseTest),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,591 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test WebDAV propfind method.
+
+It is easier to do this has a unit test has we have complete control over
+what properties are defined or not.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope import interface
+from zope import component
+from zope import schema
+import zope.schema.interfaces
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+import zope.webdav.proppatch
+import zope.webdav.publisher
+import zope.webdav.interfaces
+from zope.webdav.ietree import IEtree
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+class TestRequest(zope.webdav.publisher.WebDAVRequest):
+
+ def __init__(self, set_properties = None, remove_properties = None,
+ environ = {}):
+ set_body = ""
+ if set_properties is not None:
+ set_body = "<set><prop>%s</prop></set>" % set_properties
+
+ remove_body = ""
+ if remove_properties is not None:
+ remove_body = "<remove><prop>%s</prop></remove>" % remove_properties
+
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ %s %s
+</D:propertyupdate>
+ """ %(set_body, remove_body)
+ body = body.encode("utf-8")
+
+ env = environ.copy()
+ env.setdefault("REQUEST_METHOD", "PROPPATCH")
+ env.setdefault("CONTENT_TYPE", "text/xml")
+ env.setdefault("CONTENT_LENGTH", len(body))
+
+ super(TestRequest, self).__init__(StringIO(body), env)
+
+ # call processInputs now since we are in a unit test.
+ self.processInputs()
+
+
+class IResource(interface.Interface):
+
+ text = schema.TextLine(
+ title = u"Example Text Property")
+
+ intprop = schema.Int(
+ title = u"Example Int Property")
+
+
+class Resource(object):
+ interface.implements(IResource)
+
+ def __init__(self, text = u"", intprop = 0):
+ self.text = text
+ self.intprop = intprop
+
+
+class DummyResourceURL(object):
+ interface.implements(IAbsoluteURL)
+
+ def __init__(self, context, request):
+ self.context = context
+
+ def __str__(self):
+ if getattr(self.context, "__parent__", None) is not None:
+ path = DummyResourceURL(self.context.__parent__, None)()
+ else:
+ path = ""
+
+ if getattr(self.context, "__name__", None) is not None:
+ path += "/" + self.context.__name__
+ elif IResource.providedBy(self.context):
+ path += "/resource"
+## elif ICollection.providedBy(self.context):
+## path += "/collection"
+ else:
+ raise ValueError("unknown context type")
+
+ return path
+
+ __call__ = __str__
+
+
+class PROPPATCHHandler(zope.webdav.proppatch.PROPPATCH):
+
+ def __init__(self, context, request):
+ super(PROPPATCHHandler, self).__init__(context, request)
+
+ self.setprops = []
+ self.removeprops = []
+
+ def handleSet(self, prop):
+ self.setprops.append(prop.tag)
+
+ def handleRemove(self, prop):
+ self.removeprops.append(prop.tag)
+
+
+class PROPPATCHXmlParsing(unittest.TestCase):
+
+ def setUp(self):
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.registerAdapter(DummyResourceURL,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.unregisterAdapter(DummyResourceURL,
+ (IResource,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_noxml(self):
+ request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+ propp = PROPPATCHHandler(Resource(), request)
+ self.assertRaises(zope.webdav.interfaces.BadRequest, propp.PROPPATCH)
+
+ def test_notxml(self):
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO("content"), {"CONTENT_TYPE": "text/plain",
+ "CONTENT_LENGTH": 7})
+ propp = PROPPATCHHandler(Resource(), request)
+ request.processInputs()
+ self.assertRaises(zope.webdav.interfaces.BadRequest, propp.PROPPATCH)
+
+ def test_notproppatch(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:notpropertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ Not a propertyupdate element.
+</D:notpropertyupdate>
+ """
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(body), {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ propp = PROPPATCHHandler(Resource(), request)
+ self.assertRaises(zope.webdav.interfaces.UnprocessableError,
+ propp.PROPPATCH)
+
+ def test_not_set_element(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ <notset><prop><displayname>Display name</displayname></prop></notset>
+</propertyupdate>
+ """
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(body), {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_not_prop_element(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ <set><notprop><displayname>Display name</displayname></notprop></set>
+</propertyupdate>
+ """
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(body), {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_not_remove_element(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+ <notremove><prop><displayname>Display name</displayname></prop></notremove>
+</propertyupdate>
+ """
+
+ request = zope.webdav.publisher.WebDAVRequest(
+ StringIO(body), {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_set_none_prop(self):
+ request = TestRequest()
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_set_one_prop(self):
+ request = TestRequest(
+ set_properties = "<displayname>Display name</displayname>")
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, ["{DAV:}displayname"])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_remove_one_prop(self):
+ request = TestRequest(
+ remove_properties = "<displayname>Display name</displayname>")
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, ["{DAV:}displayname"])
+
+ def test_multiset(self):
+ request = TestRequest(
+ set_properties = "<displayname>Display name</displayname><getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, ["{DAV:}displayname",
+ "{DAV:}getcontenttype"])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_multiremove(self):
+ request = TestRequest(
+ remove_properties = "<displayname>Display name</displayname><getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, ["{DAV:}displayname",
+ "{DAV:}getcontenttype"])
+
+ def test_set_remove_prop(self):
+ request = TestRequest(
+ remove_properties = "<displayname>Display name</displayname>",
+ set_properties = "<getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandler(Resource(), request)
+ propp.PROPPATCH()
+
+ self.assertEqual(propp.setprops, ["{DAV:}getcontenttype"])
+ self.assertEqual(propp.removeprops, ["{DAV:}displayname"])
+
+ def test_error_set_prop(self):
+ class PROPPATCHHandlerError(PROPPATCHHandler):
+ def handleSet(self, prop):
+ raise zope.webdav.interfaces.PropertyNotFound(
+ self.context, "getcontenttype", u"property is missing")
+
+ request = TestRequest(
+ set_properties = "<getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandlerError(Resource(), request)
+ self.assertRaises(zope.webdav.interfaces.WebDAVPropstatErrors,
+ propp.PROPPATCH)
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, [])
+
+ def test_error_set_prop_with_remove(self):
+ class PROPPATCHHandlerError(PROPPATCHHandler):
+ def handleSet(self, prop):
+ raise zope.webdav.interfaces.PropertyNotFound(
+ self.context, "getcontenttype", u"property is missing")
+
+ request = TestRequest(
+ remove_properties = "<displayname>Test Name</displayname>",
+ set_properties = "<getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandlerError(Resource(), request)
+ self.assertRaises(zope.webdav.interfaces.WebDAVPropstatErrors,
+ propp.PROPPATCH)
+
+ self.assertEqual(propp.setprops, [])
+ self.assertEqual(propp.removeprops, ['{DAV:}displayname'])
+
+ def test_response(self):
+ request = TestRequest(
+ remove_properties = "<displayname>Display name</displayname>",
+ set_properties = "<getcontenttype>text/plain</getcontenttype>")
+ propp = PROPPATCHHandler(Resource(), request)
+ result = propp.PROPPATCH()
+
+ assertXMLEqual(result, """<ns0:multistatus xmlns:ns0="DAV:">
+<ns0:response xmlns:ns0="DAV:">
+ <ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
+ <ns0:propstat xmlns:ns0="DAV:">
+ <ns0:prop xmlns:ns0="DAV:">
+ <ns0:getcontenttype xmlns:ns0="DAV:"/>
+ <ns0:displayname xmlns:ns0="DAV:"/>
+ </ns0:prop>
+ <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+</ns0:response></ns0:multistatus>""")
+
+
+class IExamplePropertyStorage(interface.Interface):
+
+ exampleintprop = schema.Int(
+ title = u"Example Integer Property")
+
+ exampletextprop = schema.Text(
+ title = u"Example Text Property")
+
+class IExtraPropertyStorage(interface.Interface):
+
+ extratextprop = schema.Text(
+ title = u"Property with no storage")
+
+exampleIntProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}exampleintprop", IExamplePropertyStorage)
+exampleTextProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}exampletextprop", IExamplePropertyStorage)
+extraTextProperty = zope.webdav.properties.DAVProperty(
+ "{DAVtest:}extratextprop", IExtraPropertyStorage)
+
+class ExamplePropertyStorage(object):
+ interface.implements(IExamplePropertyStorage)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def _getproperty(name, default = None):
+ def get(self):
+ return getattr(self.context, name, default)
+ def set(self, value):
+ setattr(self.context, name, value)
+ return property(get, set)
+
+ exampleintprop = _getproperty("intprop", default = 0)
+
+ exampletextprop = _getproperty("text", default = u"")
+
+
+class PROPPATCHHandlePropertyModification(unittest.TestCase):
+
+ def setUp(self):
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.registerUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.registerUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ exampleTextProperty.field.readonly = False
+ gsm.registerUtility(extraTextProperty,
+ name = "{DAVtest:}extratextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+
+ gsm.registerAdapter(ExamplePropertyStorage,
+ (IResource, zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+
+ gsm.registerAdapter(zope.webdav.widgets.TextDAVInputWidget,
+ (zope.schema.interfaces.IText,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.unregisterUtility(exampleIntProperty,
+ name = "{DAVtest:}exampleintprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(exampleTextProperty,
+ name = "{DAVtest:}exampletextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+ gsm.unregisterUtility(extraTextProperty,
+ name = "{DAVtest:}extratextprop",
+ provided = zope.webdav.interfaces.IDAVProperty)
+
+ gsm.unregisterAdapter(ExamplePropertyStorage,
+ (IResource,
+ zope.webdav.interfaces.IWebDAVRequest),
+ provided = IExamplePropertyStorage)
+
+ gsm.unregisterAdapter(zope.webdav.widgets.TextDAVInputWidget,
+ (zope.schema.interfaces.IText,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def test_handleSetProperty(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{DAVtest:}exampletextprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ propp.handleSet(propel)
+
+ self.assertEqual(resource.text, "Example Text Prop")
+
+ def test_handleSet_forbidden_property(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{DAVtest:}exampletextprop")
+ propel.text = "Example Text Prop"
+
+ exampleTextProperty.field.readonly = True
+
+ request = TestRequest(
+ set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ForbiddenError,
+ propp.handleSet,
+ propel)
+
+ def test_handleSet_property_notfound(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{DAVtest:}exampletextpropmissing")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ set_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.PropertyNotFound,
+ propp.handleSet,
+ propel)
+
+ def test_handleRemove_live_property(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{DAVtest:}exampletextprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError,
+ propp.handleRemove,
+ propel)
+
+ def test_handleRemove_no_dead_properties(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{example:}exampledeadprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError,
+ propp.handleRemove,
+ propel)
+
+
+class DEADProperties(object):
+ interface.implements(zope.webdav.interfaces.IOpaquePropertyStorage)
+
+ def __init__(self, context):
+ self.data = context.props = getattr(context, "props", {})
+
+ def getAllProperties(self):
+ for tag in self.data:
+ yield tag
+
+ def hasProperty(self, tag):
+ return tag in self.data
+
+ def getProperty(self, tag):
+ return self.data[tag]
+
+ def setProperty(self, tag, value):
+ self.data[tag] = value
+
+ def removeProperty(self, tag):
+ del self.data[tag]
+
+
+class PROPPATCHHandlePropertyRemoveDead(unittest.TestCase):
+
+ def setUp(self):
+ etreeSetup()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.registerAdapter(DEADProperties, (IResource,))
+
+ def tearDown(self):
+ etreeTearDown()
+
+ gsm = component.getGlobalSiteManager()
+
+ gsm.unregisterAdapter(DEADProperties, (IResource,))
+
+ def test_remove_no_storage(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{example:}exampledeadprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ remove_properties = """<Dt:exampledeadprop xmlns:Dt="example:">Example Text Prop</Dt:exampledeadprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError,
+ propp.handleRemove,
+ propel)
+
+ def test_remove_not_there(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{example:}exampledeadprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+ self.assertRaises(zope.webdav.interfaces.ConflictError,
+ propp.handleRemove,
+ propel)
+
+ def test_remove_prop(self):
+ etree = component.getUtility(IEtree)
+ propel = etree.Element("{example:}exampledeadprop")
+ propel.text = "Example Text Prop"
+
+ request = TestRequest(
+ remove_properties = """<Dt:exampletextprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:exampletextprop>""")
+ resource = Resource("Text Prop", 10)
+
+ testprop = "{example:}exampledeadprop"
+
+ deadprops = DEADProperties(resource)
+ deadprops.setProperty(testprop, "Example Text Prop")
+ self.assertEqual(deadprops.hasProperty(testprop), True)
+ self.assertEqual(deadprops.getProperty(testprop), "Example Text Prop")
+
+ propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+
+ propp.handleRemove(propel)
+
+ self.assertEqual(deadprops.hasProperty(testprop), False)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(PROPPATCHXmlParsing),
+ unittest.makeSuite(PROPPATCHHandlePropertyModification),
+ unittest.makeSuite(PROPPATCHHandlePropertyRemoveDead),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test utility for interfacing with the zope.locking package from ZC
+
+$Id$
+"""
+
+import unittest
+import types
+from cStringIO import StringIO
+
+from zope.interface.verify import verifyObject
+
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.interfaces import IWebDAVRequest, IWebDAVResponse, BadRequest
+
+def create_request(body = None, env = {}):
+ if isinstance(body, types.StringTypes):
+ body = StringIO(body)
+ elif body is None:
+ body = StringIO('')
+ return WebDAVRequest(body, env)
+
+
+class TestWebDAVPublisher(unittest.TestCase):
+
+ def setUp(self):
+ from zope.webdav.testing import etreeSetup
+ self.etree = etreeSetup()
+
+ def tearDown(self):
+ from zope.webdav.testing import etreeTearDown
+ etreeTearDown()
+
+ def test_noinput(self):
+ request = create_request()
+ self.assert_(verifyObject(IWebDAVRequest, request))
+ self.assertEqual(request.content_type, None)
+ self.assertEqual(request.xmlDataSource, None)
+
+ def test_textinput(self):
+ body = "This is some text"
+ request = create_request(body, {"CONTENT_TYPE": "text/plain",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+ self.assertEqual(request.content_type, "text/plain")
+ self.assertEqual(request.xmlDataSource, None)
+
+ def test_unicodeInput(self):
+ body = "This is some text"
+ request = create_request(body,
+ {"CONTENT_TYPE": "text/plain;charset=cp1252",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+ self.assertEqual(request.content_type, "text/plain")
+ self.assertEqual(request.xmlDataSource, None)
+
+ def test_textxml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+ <somedoc>This is some xml document</somedoc>
+ """
+ request = create_request(body, {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ self.assertEqual(request.content_type, "text/xml")
+ self.assert_(request.xmlDataSource is not None)
+
+ def test_applicationxml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+ <somedoc>This is some xml document</somedoc>
+ """
+ request = create_request(body, {"CONTENT_TYPE": "application/xml",
+ "CONTENT_LENGTH": len(body)})
+ request.processInputs()
+
+ self.assertEqual(request.content_type, "application/xml")
+ self.assert_(request.xmlDataSource is not None)
+
+ def test_xml_nobody(self):
+ request = create_request("", {"CONTENT_TYPE": "text/xml"})
+ request.processInputs()
+ self.assertEqual(request.xmlDataSource, None)
+
+ def test_response(self):
+ request = create_request()
+ self.assert_(verifyObject(IWebDAVResponse, request.response))
+
+ def test_invalidxml(self):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+ <somedoc>Bad End Tag</anotherdoc>
+ """
+ request = create_request(body, {"CONTENT_TYPE": "application/xml",
+ "CONTENT_LENGTH": len(body)})
+ self.assertRaises(BadRequest, request.processInputs)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(TestWebDAVPublisher),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_publisher.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,336 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the WebDAV widget framework.
+
+$Id$
+"""
+
+import unittest
+import datetime
+from cStringIO import StringIO
+
+from zope import schema
+from zope import component
+from zope.schema.interfaces import ITextLine
+from zope.interface import Interface, implements
+from zope.interface.verify import verifyObject
+from zope.datetime import tzinfo
+
+from zope.webdav import widgets
+import zope.webdav.interfaces
+from zope.webdav.publisher import WebDAVRequest
+from zope.webdav.testing import etreeSetup, etreeTearDown, assertXMLEqual
+
+
+class TestWebDAVRequest(WebDAVRequest):
+ """."""
+ def __init__(self, elem = None):
+ if elem is not None:
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:">
+ <D:set>
+ <D:prop />
+ </D:set>
+</D:propertyupdate>
+"""
+ f = StringIO(body)
+ else:
+ f = StringIO("")
+
+ super(TestWebDAVRequest, self).__init__(
+ f, {"CONTENT_TYPE": "text/xml",
+ "CONTENT_LENGTH": len(f.getvalue()),
+ })
+
+ # processInputs to test request
+ self.processInputs()
+
+ # if elem is given insert it into the proppatch request.
+ if elem is not None:
+ self.xmlDataSource[0][0].append(elem)
+
+
+class _WebDAVWidgetTest(unittest.TestCase):
+
+ namespace = u"testns:"
+ name = u"foo"
+ missing_name = u"someotherproperty"
+
+ field_content = u"" # field value assigned to the demo content.
+ rendered_content = u"" # string value of the some value in ...
+
+ _FieldFactory = None
+ _WidgetFactory = None
+
+ def tearDown(self):
+ etreeTearDown()
+
+ def setUp(self):
+ self.etree = etreeSetup()
+
+ def setUpContent(self, desc = u'', title = u'Foo Title', element = None):
+ ## setup the field first to stop some really weird errors
+ foofield = self._FieldFactory(
+ __name__ = self.name,
+ title = title,
+ description = desc)
+ class IDemoContent(Interface):
+ foo = foofield
+
+ class DemoContent(object):
+ implements(IDemoContent)
+
+ self.content = DemoContent()
+ field = IDemoContent['foo']
+ self.field = field.bind(self.content)
+ self.setUpWidget(element)
+
+ def setUpWidget(self, element = None):
+ request = TestWebDAVRequest(element)
+ self.widget = self._WidgetFactory(self.field, request)
+ self.widget.namespace = self.namespace
+
+
+class WebDAVBaseWidgetTest(_WebDAVWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.DAVWidget
+
+ def setUp(self):
+ super(WebDAVBaseWidgetTest, self).setUp()
+ self.setUpContent()
+
+ def test_dontuseDAVWidgetRender(self):
+ self.assertRaises(NotImplementedError, self.widget.render)
+
+ def test_dontuseDAVWidgetToDAVValue(self):
+ self.assertRaises(NotImplementedError, self.widget.toDAVValue, u"test")
+
+
+class WebDAVWidgetTest(_WebDAVWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.TextDAVWidget
+
+ def test_interface(self):
+ self.assertEqual(
+ verifyObject(zope.webdav.interfaces.IDAVWidget, self.widget), True)
+
+ def test_render(self):
+ self.widget.setRenderedValue(self.field_content)
+ self.content.foo = self.field_content
+ element = self.widget.render()
+ assertXMLEqual(self.etree.tostring(element),
+ '<ns0:foo xmlns:ns0="testns:">%s</ns0:foo>' % self.rendered_content)
+
+ def test_nofieldValue(self):
+ request = TestWebDAVRequest()
+ widget = self._WidgetFactory(self.field, request)
+ widget.namespace = self.namespace
+ element = widget.render()
+ assertXMLEqual(self.etree.tostring(element),
+ '<ns0:foo xmlns:ns0="testns:" />')
+
+
+class TextWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.Text
+ _WidgetFactory = widgets.TextDAVWidget
+
+ field_content = u'Foo Value'
+ rendered_content = u'Foo Value'
+
+ def setUp(self):
+ super(TextWebDAVWidgetTest, self).setUp()
+ self.setUpContent()
+
+
+class IntWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.Int
+ _WidgetFactory = widgets.IntDAVWidget
+
+ field_content = 10
+ rendered_content = u'10'
+
+ def setUp(self):
+ super(IntWebDAVWidgetTest, self).setUp()
+ self.setUpContent()
+
+
+class FloatWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.Float
+ _WidgetFactory = widgets.IntDAVWidget
+
+ field_content = 10.0
+ rendered_content = u"10.0"
+
+ def setUp(self):
+ super(FloatWebDAVWidgetTest, self).setUp()
+ self.setUpContent()
+
+
+class DatetimeWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.Datetime
+ _WidgetFactory = widgets.DatetimeDAVWidget
+
+ rendered_content = u"Wed, 24 May 2006 00:00:58 +0100"
+ field_content = datetime.datetime(2006, 5, 24, 0, 0, 58,
+ tzinfo = tzinfo(60))
+
+ def setUp(self):
+ super(DatetimeWebDAVWidgetTest, self).setUp()
+ self.setUpContent()
+
+
+class DateWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+ _FieldFactory = schema.Date
+ _WidgetFactory = widgets.DatetimeDAVWidget
+
+ field_content = datetime.date(2006, 5, 24)
+ rendered_content = u"Wed, 24 May 2006 00:00:00"
+
+
+class ISO8601DatetimeWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+ _FieldFactory = schema.Datetime
+ _WidgetFactory = widgets.ISO8601DatetimeDAVWidget
+
+ rendered_content = u"2006-05-24T00:00:58Z"
+
+
+class ISO8601DateWebDAVWidgetTest(DatetimeWebDAVWidgetTest):
+
+ _FieldFactory = schema.Date
+ _WidgetFactory = widgets.ISO8601DatetimeDAVWidget
+
+ field_content = datetime.date(2006, 5, 24)
+ rendered_content = u"2006-05-24T00:00:00Z"
+
+
+class ListWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.List
+ _WidgetFactory = widgets.ListDAVWidget
+
+ field_content = [u'collection']
+ rendered_content = '<ns0:collection />'
+
+ def setUp(self):
+ super(ListWebDAVWidgetTest, self).setUp()
+ self.setUpContent()
+
+
+class ListTextWebDAVWidgetTest(WebDAVWidgetTest):
+
+ _FieldFactory = schema.List
+ _WidgetFactory = widgets.ListDAVWidget
+
+ rendered_content = "<ns0:name>firstitem</ns0:name><ns0:name>seconditem</ns0:name>"
+
+ def setUp(self):
+ self.etree = etreeSetup()
+ component.getGlobalSiteManager().registerAdapter(
+ widgets.TextDAVWidget,
+ (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+
+ foofield = schema.List(__name__ = self.name,
+ title = u"Foo Title",
+ description = u"Foo field",
+ value_type = schema.TextLine(
+ __name__ = "name",
+ title = u"Foo Title",
+ description = u"Foo field"))
+
+ class IDemoContent(Interface):
+ foo = foofield
+
+ class DemoContent(object):
+ implements(IDemoContent)
+
+ self.field_content = [u"firstitem", u"seconditem"]
+ self.content = DemoContent()
+ field = IDemoContent['foo']
+ self.field = field.bind(self.content)
+ self.setUpWidget()
+
+ def tearDown(self):
+ component.getGlobalSiteManager().unregisterAdapter(
+ widgets.TextDAVWidget,
+ (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+ super(ListTextWebDAVWidgetTest, self).tearDown()
+
+
+class ObjectDAVWidgetTest(WebDAVWidgetTest):
+
+ _WidgetFactory = widgets.ObjectDAVWidget
+
+ rendered_content = '<ns0:name>Michael Kerrin</ns0:name>'
+
+ def setUp(self):
+ self.etree = etreeSetup()
+
+ class ISimpleInterface(Interface):
+ name = schema.TextLine(
+ title = u"Named subproperty",
+ description = u"")
+
+ class SimpleObject(object):
+ name = u"Michael Kerrin"
+
+ foofield = schema.Object(__name__ = self.name,
+ title = u"Foo Title",
+ description = u"Foo field",
+ schema = ISimpleInterface)
+ class IDemoContent(Interface):
+ foo = foofield
+
+ class DemoContent(object):
+ implements(IDemoContent)
+
+ self.field_content = SimpleObject()
+ self.content = DemoContent()
+ field = IDemoContent['foo']
+ self.field = field.bind(self.content)
+ self.setUpWidget()
+
+ component.getGlobalSiteManager().registerAdapter(
+ widgets.TextDAVWidget,
+ (ITextLine,
+ zope.webdav.interfaces.IWebDAVRequest))
+
+ def tearDown(self):
+ component.getGlobalSiteManager().unregisterAdapter(
+ widgets.TextDAVWidget,
+ (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+ super(ObjectDAVWidgetTest, self).tearDown()
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(WebDAVBaseWidgetTest),
+ unittest.makeSuite(TextWebDAVWidgetTest),
+ unittest.makeSuite(IntWebDAVWidgetTest),
+ unittest.makeSuite(FloatWebDAVWidgetTest),
+ unittest.makeSuite(DatetimeWebDAVWidgetTest),
+ unittest.makeSuite(DateWebDAVWidgetTest),
+ unittest.makeSuite(ISO8601DatetimeWebDAVWidgetTest),
+ unittest.makeSuite(ISO8601DateWebDAVWidgetTest),
+ unittest.makeSuite(ListWebDAVWidgetTest),
+ unittest.makeSuite(ListTextWebDAVWidgetTest),
+ unittest.makeSuite(ObjectDAVWidgetTest),
+ ))
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,169 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test the ElementTree support within WebDAV. These aren't really tests but
+more of an assertion that I spelt things, like variable names correctly. By
+ust calling the methods here I have managed to find a bunch of bugs :-)
+
+Otherwise I just assume that underlying engine does its job correctly.
+
+$Id$
+"""
+
+import unittest
+from cStringIO import StringIO
+
+from zope.interface.verify import verifyObject
+import zope.webdav.zetree
+from zope.webdav.ietree import IEtree
+
+
+class BaseEtreeTestCase(unittest.TestCase):
+
+ def test_interface(self):
+ self.assertEqual(verifyObject(IEtree, self.etree), True)
+
+ def test_comment(self):
+ comment = self.etree.Comment(u"some text")
+
+ def test_etree(self):
+ etree = self.etree.ElementTree()
+
+ def test_XML(self):
+ xml = self.etree.XML(u"<p>some text</p>")
+
+ def test_fromstring(self):
+ xml = self.etree.fromstring(u"<p>some text</p>")
+
+ def test_element(self):
+ elem = self.etree.Element(u"testtag")
+
+ def test_iselement(self):
+ elem = self.etree.Element(u"testtag")
+ iselem = self.etree.iselement(elem)
+ self.assert_(iselem, "Not an element")
+
+ def test_parse(self):
+ f = StringIO("<b>Test Source String</b>")
+ self.etree.parse(f)
+
+ def test_qname(self):
+ qname = self.etree.QName("http://example.namespace.org", "test")
+
+ def test_tostring(self):
+ elem = self.etree.Element(u"testtag")
+ string = self.etree.tostring(elem, "ascii")
+ self.assert_(isinstance(string, str), "Not a string")
+
+ def test_treeBuilder(self):
+ self.assertRaises(NotImplementedError, self.etree.TreeBuilder)
+
+ def test_subelement(self):
+ elem = self.etree.Element(u"testtag")
+ subel = self.etree.SubElement(elem, "foo")
+
+ def test_PI(self):
+ pi = self.etree.PI("sometarget")
+
+ def test_processinginstructions(self):
+ pi = self.etree.ProcessingInstruction("sometarget")
+
+ def test_xmltreebulider(self):
+ builder = self.etree.XMLTreeBuilder()
+
+
+class OrigElementTreeTestCase(BaseEtreeTestCase):
+
+ def setUp(self):
+ self.etree = zope.webdav.zetree.EtreeEtree()
+
+ def tearDown(self):
+ del self.etree
+
+
+class LXMLElementTreeTestCase(BaseEtreeTestCase):
+
+ def setUp(self):
+ self.etree = zope.webdav.zetree.LxmlEtree()
+
+ def tearDown(self):
+ del self.etree
+
+ def test_PI(self):
+ self.assertRaises(NotImplementedError, self.etree.PI, "sometarget")
+
+ def test_processinginstructions(self):
+ self.assertRaises(NotImplementedError,
+ self.etree.ProcessingInstruction, "sometarget")
+
+ def test_xmltreebulider(self):
+ self.assertRaises(NotImplementedError, self.etree.XMLTreeBuilder)
+
+ def test_namespaces(self):
+ # When we have a element whoes namespace declaration is declared
+ # in a parent element lxml doesn't print out the namespace
+ # declaration by default.
+ multinselemstr = """<D:prop xmlns:D="DAV:"><D:owner><H:href xmlns:H="examplens">http://example.org</H:href></D:owner></D:prop>"""
+ multinselem = self.etree.fromstring(multinselemstr)
+ self.assertEqual(self.etree.tostring(multinselem[0]),
+ """<D:owner xmlns:D="DAV:"><H:href xmlns:H="examplens">http://example.org</H:href></D:owner>""")
+
+
+class Python25ElementTreeTestCase(BaseEtreeTestCase):
+
+ def setUp(self):
+ self.etree = zope.webdav.zetree.EtreePy25()
+
+ def tearDown(self):
+ del self.etree
+
+
+class NoElementTreePresentTestCase(unittest.TestCase):
+ # If no element tree engine exists then run this test case. Which will
+ # mark the current instance has broken.
+
+ def test_warn(self):
+ self.fail("""
+ WARNING: zope.webdav needs elementtree installed in order to run.
+ """)
+
+def test_suite():
+ suite = unittest.TestSuite()
+
+ # Only run the tests for each elementtree that is installed.
+ foundetree = False
+ try:
+ import elementtree
+ suite.addTest(unittest.makeSuite(OrigElementTreeTestCase))
+ foundetree = True
+ except ImportError:
+ pass
+
+ try:
+ import lxml.etree
+ suite.addTest(unittest.makeSuite(LXMLElementTreeTestCase))
+ foundetree = True
+ except ImportError:
+ pass
+
+ try:
+ import xml.etree
+ suite.addTest(unittest.makeSuite(Python25ElementTreeTestCase))
+ foundetree = True
+ except ImportError:
+ pass
+
+ if not foundetree:
+ suite.addTest(unittest.makeSuite(NoElementTreePresentTestCase))
+
+ return suite
Property changes on: zope.webdav/trunk/src/zope/webdav/tests/test_zetree.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/utils.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/utils.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/utils.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,469 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""A collection of useful classes used for generating common XML fragments
+for use within zope.webdav
+
+MultiStatus
+
+ <!ELEMENT multistatus (response*, responsedescription?) >
+ <!ELEMENT response (href, ((href*, status)|(propstat+)),
+ error?, responsedescription? , location?) >
+ <!ELEMENT propstat (prop, status, error?, responsedescription?) >
+ <!ELEMENT prop ANY >
+ <!ELEMENT href (#PCDATA)>
+ <!ELEMENT status (#PCDATA) >
+ <!ELEMENT responsedescription (#PCDATA) >
+ <!ELEMENT error ANY >
+ <!ELEMENT location (href)>
+
+Also contains some usefully methods like
+
++ getObjectURL
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import types
+
+from zope import component
+from zope import interface
+from zope.publisher.http import status_reasons
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.app.container.interfaces import IReadContainer
+from ietree import IEtree
+
+class IPropstat(interface.Interface):
+ """Helper interface to render a response XML element.
+ """
+
+ properties = interface.Attribute("""List of etree elements that make up
+ the prop element.
+ """)
+
+ status = interface.Attribute("""Integer status code of all the properties
+ belonging to this propstat XML element.
+ """)
+
+ error = interface.Attribute("""List of etree elements describing the
+ error condition of the properties within this propstat XML element.
+ """)
+
+ responsedescription = interface.Attribute("""String containing readable
+ information about the status of all the properties belonging to this
+ propstat element.
+ """)
+
+ def __call__():
+ """Render this propstat object to a etree XML element.
+ """
+
+class IResponse(interface.Interface):
+ """Helper object to render a response XML element.
+ """
+
+ href = interface.Attribute("""String representation of the HTTP URL
+ pointing to the resource that this response element represents.
+ """)
+
+ status = interface.Attribute("""Optional integer status code for this
+ response element.
+ """)
+
+ error = interface.Attribute("""List of etree elements describing the
+ error conditions of all the properties contained within an instance of
+ this response XML element.
+ """)
+
+ responsedescription = interface.Attribute("""String containing readable
+ information about this response relative to the request or result.
+ """)
+
+ location = interface.Attribute("""The "Location" HTTP header for use with
+ some status codes like 201 and 300. This value should be set if any of
+ status codes contained within this response XML element are required to
+ have a HTTP "Location" header.
+ """)
+
+ def addPropstats(status, propstat):
+ """Add a IPropstat instance to this response. This propstat will
+ rendered with a status of 'status'.
+ """
+
+ def addProperty(status, element):
+ """Add a etree.Element object to this response XML element. The property
+ will be added within the propstat element that registered under the
+ status code.
+ """
+
+ def __call__():
+ """Render this response object to an etree element.
+ """
+
+
+class IMultiStatus(interface.Interface):
+
+ responses = interface.Attribute("""List of IResponse objects that make
+ all the responses contained within this multistatus XML element.
+ """)
+
+ responsedescription = interface.Attribute("""String containing a readable
+ informatino about the status of all the responses belonging to this
+ multistatus element.
+ """)
+
+ def __call__():
+ """Render this multistatus object to an etree element.
+ """
+
+################################################################################
+#
+# Some helper methods. Which includes:
+#
+# - makeelement(namespace, tagname, text_or_el = None)
+#
+# - makedavelement(tagname, text_or_el = None)
+#
+# - makestatuselement(status)
+#
+# - parseEtreeTag(tag)
+#
+################################################################################
+
+def makeelement(namespace, tagname, text_or_el = None):
+ etree = component.getUtility(IEtree)
+ el = etree.Element(etree.QName(namespace, tagname))
+ if isinstance(text_or_el, (str, unicode)):
+ el.text = text_or_el
+ elif text_or_el is not None:
+ el.append(text_or_el)
+
+ return el
+
+
+def makedavelement(tagname, text_or_el = None):
+ """
+ >>> assertXMLEqual(etree.tostring(makedavelement('foo')),
+ ... '<ns0:foo xmlns:ns0="DAV:" />')
+ >>> assertXMLEqual(etree.tostring(makedavelement('foo', 'foo content')),
+ ... '<ns0:foo xmlns:ns0="DAV:">foo content</ns0:foo>')
+ """
+ return makeelement('DAV:', tagname, text_or_el)
+
+
+def makestatuselement(status):
+ """
+ >>> etree.tostring(makestatuselement(200))
+ '<ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>'
+ """
+ if isinstance(status, types.IntType):
+ status = 'HTTP/1.1 %d %s' %(status, status_reasons[status])
+
+ return makedavelement('status', status)
+
+
+def parseEtreeTag(tag):
+ """Return namespace, tagname pair.
+
+ >>> parseEtreeTag('{DAV:}prop')
+ ['DAV:', 'prop']
+
+ >>> parseEtreeTag('prop')
+ (None, 'prop')
+
+ """
+ if tag[0] == '{':
+ return tag[1:].split('}')
+ return None, tag
+
+################################################################################
+#
+#
+#
+################################################################################
+
+class Propstat(object):
+ """Simple propstat xml handler.
+
+ >>> from zope.interface.verify import verifyObject
+ >>> pstat = Propstat()
+ >>> verifyObject(IPropstat, pstat)
+ True
+ >>> pstat.status = 200
+
+ >>> pstat.properties.append(makedavelement(u'testprop', u'Test Property'))
+ >>> assertXMLEqual(etree.tostring(pstat()),
+ ... '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat>')
+
+ >>> pstat.properties.append(makedavelement(u'test2', u'Second Test'))
+ >>> assertXMLEqual(etree.tostring(pstat()),
+ ... '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat>')
+
+ >>> pstat.responsedescription = u'This is ok'
+ >>> assertXMLEqual(etree.tostring(pstat()),
+ ... '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status><ns0:responsedescription>This is ok</ns0:responsedescription></ns0:propstat>')
+
+ >>> pstat.error = [makedavelement(u'precondition-error')]
+ >>> assertXMLEqual(etree.tostring(pstat()),
+ ... '<ns0:propstat xmlns:ns0="DAV:"><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:test2>Second Test</ns0:test2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status><ns0:error><ns0:precondition-error /></ns0:error><ns0:responsedescription>This is ok</ns0:responsedescription></ns0:propstat>')
+
+ The status must be set.
+
+ >>> pstat = Propstat()
+ >>> pstat()
+ Traceback (most recent call last):
+ ...
+ ValueError: Must set status before rendering a propstat.
+
+ """
+ interface.implements(IPropstat)
+
+ def __init__(self):
+ # etree.Element
+ self.properties = []
+ # int or string
+ self.status = None
+
+ # etree.Element
+ self.error = []
+ # text
+ self.responsedescription = ""
+
+ def __call__(self):
+ if self.status is None:
+ raise ValueError("Must set status before rendering a propstat.")
+
+ propstatel = makedavelement('propstat')
+ propel = makedavelement('prop')
+ propstatel.append(propel)
+
+ for prop in self.properties:
+ propel.append(prop)
+
+ propstatel.append(makestatuselement(self.status))
+
+ for error in self.error:
+ propstatel.append(makedavelement('error', error))
+
+ if self.responsedescription:
+ propstatel.append(makedavelement('responsedescription',
+ self.responsedescription))
+
+ return propstatel
+
+
+class Response(object):
+ """WebDAV response XML element
+
+ We need a URL to initialize the Response object, /container is a good
+ choice.
+
+ >>> from zope.interface.verify import verifyObject
+ >>> response = Response('/container')
+ >>> verifyObject(IResponse, response)
+ True
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href></ns0:response>')
+ >>> response.status = 200
+ >>> response.href.append('/container2')
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:href>/container2</ns0:href><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:response>')
+
+ The response XML element can contain a number of Propstat elements
+ organized by status code.
+
+ >>> response = Response('/container')
+ >>> pstat1 = Propstat()
+ >>> pstat1.status = 200
+ >>> pstat1.properties.append(makedavelement(u'test1', u'test one'))
+ >>> response.addPropstats(200, pstat1)
+ >>> pstat2 = Propstat()
+ >>> pstat2.status = 404
+ >>> pstat2.properties.append(makedavelement(u'test2'))
+ >>> response.addPropstats(404, pstat2)
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response>')
+
+ >>> response.error = [makedavelement(u'precondition-failed')]
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error></ns0:response>')
+
+ >>> response.responsedescription = u'webdav description'
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error><ns0:responsedescription>webdav description</ns0:responsedescription></ns0:response>')
+
+ >>> response.location = '/container2'
+ >>> assertXMLEqual(etree.tostring(response()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat><ns0:error><ns0:precondition-failed /></ns0:error><ns0:responsedescription>webdav description</ns0:responsedescription><ns0:location><ns0:href>/container2</ns0:href></ns0:location></ns0:response>')
+
+ >>> response = Response('/container1')
+ >>> response.href.append('/container2')
+ >>> response.addPropstats(200, Propstat())
+ >>> etree.tostring(response())
+ Traceback (most recent call last):
+ ...
+ ValueError: Response object is in an invalid state.
+
+ >>> response = Response('/container1')
+ >>> response.status = 200
+ >>> response.addPropstats(200, Propstat())
+ >>> etree.tostring(response())
+ Traceback (most recent call last):
+ ...
+ ValueError: Response object is in an invalid state.
+
+ Now the must handly method of all the addProperty mehtod:
+
+ >>> resp = Response('/container')
+ >>> resp.addProperty(200, makedavelement(u'testprop', u'Test Property'))
+ >>> assertXMLEqual(etree.tostring(resp()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response>')
+ >>> resp.addProperty(200, makedavelement(u'testprop2',
+ ... u'Test Property Two'))
+ >>> assertXMLEqual(etree.tostring(resp()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:testprop2>Test Property Two</ns0:testprop2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response>')
+ >>> resp.addProperty(404, makedavelement(u'missing'))
+ >>> assertXMLEqual(etree.tostring(resp()),
+ ... '<ns0:response xmlns:ns0="DAV:"><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:testprop>Test Property</ns0:testprop><ns0:testprop2>Test Property Two</ns0:testprop2></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat><ns0:propstat><ns0:prop><ns0:missing /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response>')
+
+ """
+ interface.implements(IResponse)
+
+ def __init__(self, href):
+ self.href = [href]
+ self.status = None
+ self._propstats = {} # status -> list of propstat Propstat object
+
+ self.error = []
+ self.responsedescription = ""
+ self.location = None
+
+ def getPropstat(self, status):
+ if status not in self._propstats:
+ self._propstats[status] = Propstat()
+ return self._propstats[status]
+
+ def addPropstats(self, status, propstat): # use getPropstats instead.
+ self._propstats[status] = propstat
+
+ def addProperty(self, status, element):
+ try:
+ propstat = self._propstats[status]
+ except KeyError:
+ propstat = self._propstats[status] = Propstat()
+
+ propstat.properties.append(element)
+
+ def __call__(self):
+ if (len(self.href) > 1 or self.status is not None) and self._propstats:
+ raise ValueError, "Response object is in an invalid state."
+
+ respel = makedavelement('response')
+ respel.append(makedavelement('href', self.href[0]))
+
+ if self.status is not None:
+ for href in self.href[1:]:
+ respel.append(makedavelement('href', href))
+
+ respel.append(makestatuselement(self.status))
+ else:
+ for status, propstat in self._propstats.items():
+ propstat.status = status
+ respel.append(propstat())
+
+ for error in self.error:
+ respel.append(makedavelement('error', error))
+
+ if self.responsedescription:
+ respel.append(makedavelement('responsedescription',
+ self.responsedescription))
+
+ if self.location is not None:
+ respel.append(makedavelement('location',
+ makedavelement('href', self.location)))
+
+ return respel
+
+
+class MultiStatus(object):
+ """Multistatus element generation
+
+ >>> from zope.interface.verify import verifyObject
+ >>> ms = MultiStatus()
+ >>> verifyObject(IMultiStatus, ms)
+ True
+ >>> assertXMLEqual(etree.tostring(ms()),
+ ... '<ns0:multistatus xmlns:ns0="DAV:" />')
+
+ >>> ms.responsedescription = u'simple description'
+ >>> assertXMLEqual(etree.tostring(ms()),
+ ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+ >>> response = Response('/container')
+ >>> ms.responses.append(response)
+ >>> assertXMLEqual(etree.tostring(ms()),
+ ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+ >>> pstat1 = Propstat()
+ >>> pstat1.status = 200
+ >>> pstat1.properties.append(makedavelement(u'test1', u'test one'))
+ >>> response.addPropstats(200, pstat1)
+ >>> assertXMLEqual(etree.tostring(ms()),
+ ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href><ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>')
+
+ >>> response2 = Response('/container2')
+ >>> pstat2 = Propstat()
+ >>> pstat2.status = 404
+ >>> pstat2.properties.append(makedavelement(u'test2'))
+ >>> response2.addPropstats(404, pstat2)
+ >>> ms.responses.append(response2)
+ >>> assertXMLEqual(etree.tostring(ms()),
+ ... '<ns0:multistatus xmlns:ns0="DAV:"><ns0:response><ns0:href>/container</ns0:href> <ns0:propstat><ns0:prop><ns0:test1>test one</ns0:test1></ns0:prop><ns0:status>HTTP/1.1 200 OK</ns0:status></ns0:propstat></ns0:response><ns0:response><ns0:href>/container2</ns0:href><ns0:propstat><ns0:prop><ns0:test2 /></ns0:prop><ns0:status>HTTP/1.1 404 Not Found</ns0:status></ns0:propstat></ns0:response><ns0:responsedescription>simple description</ns0:responsedescription></ns0:multistatus>''')
+
+ """
+ interface.implements(IMultiStatus)
+
+ def __init__(self):
+ # list of Response objects
+ self.responses = []
+ # text
+ self.responsedescription = ""
+
+ def __call__(self):
+ etree = component.getUtility(IEtree)
+ el = etree.Element(etree.QName('DAV:', 'multistatus'))
+ for response in self.responses:
+ el.append(response())
+
+ if self.responsedescription:
+ el.append(makedavelement('responsedescription',
+ self.responsedescription))
+
+ return el
+
+################################################################################
+#
+# Some other miscellanous helpful methods
+#
+################################################################################
+
+def getObjectURL(ob, req):
+ """Return the URL for the object `ob`.
+
+ If the object is a container and the url doesn't end in slash '/' then
+ append a slash to the url.
+ """
+ url = component.getMultiAdapter((ob, req), IAbsoluteURL)()
+ if IReadContainer.providedBy(ob) and url[-1] != "/":
+ url += "/"
+
+ return url
Property changes on: zope.webdav/trunk/src/zope/webdav/utils.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/widgets.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/widgets.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,268 @@
+##############################################################################
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""A collection of usefull classes and methods related to WebDAV.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope import interface
+from zope.schema import getFieldsInOrder
+
+from zope.webdav.ietree import IEtree
+import interfaces
+
+import zope.datetime
+from zope.app.form.interfaces import ConversionError, MissingInputError
+
+DEFAULT_NS = 'DAV:'
+
+
+class DAVWidget(object):
+ """Base class for rendering WebDAV properties through implementations like
+ PROPFIND and PROPPATCH.
+ """
+ interface.implements(interfaces.IDAVWidget)
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ self.name = self.context.__name__
+ self.namespace = None
+
+ # value to render.
+ self._value = None
+
+ def setRenderedValue(self, value):
+ self._value = value
+
+ def toDAVValue(self, value):
+ """Override this method in the base class. This method should either
+ a string, a list or tuple
+ """
+ raise NotImplementedError, \
+ "please implemented this method in a subclass of DAVWidget."
+
+ def renderName(self):
+ etree = component.getUtility(IEtree)
+ return etree.Element(etree.QName(self.namespace, self.name))
+
+ def render(self):
+ etree = component.getUtility(IEtree)
+ el = etree.Element(etree.QName(self.namespace, self.name))
+
+ rendered_value = self.toDAVValue(self._value)
+ el.text = rendered_value
+
+ return el
+
+
+class TextDAVWidget(DAVWidget):
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def toDAVValue(self, value):
+ if value is None:
+ return None
+ return value
+
+
+class IntDAVWidget(DAVWidget):
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def toDAVValue(self, value):
+ if value is not None:
+ return str(value)
+ return None
+
+
+class DatetimeDAVWidget(DAVWidget):
+ """Same widget can be used for a date field also."""
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def toDAVValue(self, value):
+ # datetime object
+ if value is None:
+ return None
+
+ return value.strftime("%a, %d %b %Y %H:%M:%S %z").strip()
+
+
+class ISO8601DatetimeDAVWidget(DAVWidget):
+ """Same widget can be used for a date field also."""
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def toDAVValue(self, value):
+ if value is None:
+ return None
+
+ return value.strftime('%Y-%m-%dT%TZ')
+
+
+class ObjectDAVWidget(DAVWidget):
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def render(self):
+ etree = component.getUtility(IEtree)
+ el = etree.Element(etree.QName(self.namespace, self.name))
+
+ if self._value is None:
+ return el
+
+ interface = self.context.schema
+ for name, field in getFieldsInOrder(interface):
+ field = field.bind(self._value)
+
+ widget = component.getMultiAdapter((field, self.request),
+ interfaces.IDAVWidget)
+ widget.namespace = self.namespace
+ widget.setRenderedValue(field.get(self._value))
+ el.append(widget.render())
+
+ return el
+
+
+class ListDAVWidget(DAVWidget):
+ interface.classProvides(interfaces.IIDAVWidget)
+
+ def render(self):
+ etree = component.getUtility(IEtree)
+ el = etree.Element(etree.QName(self.namespace, self.name))
+
+ if self._value is None:
+ return el
+
+ value_type = self.context.value_type
+ if value_type is None:
+ for value in self._value:
+ el.append(etree.Element(etree.QName(self.namespace, value)))
+ else:
+ # value_type is not None so render each item in the sequence
+ # according to the widget register for this field.
+ for value in self._value:
+ widget = component.getMultiAdapter((value_type, self.request),
+ interfaces.IDAVWidget)
+ widget.setRenderedValue(value)
+ widget.namespace = self.namespace
+ el.append(widget.render())
+ return el
+
+
+################################################################################
+#
+# Now for a collection of input widgets.
+#
+################################################################################
+
+
+class DAVInputWidget(object):
+ interface.implements(interfaces.IDAVInputWidget)
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ self.name = self.context.__name__
+ self.namespace = None
+
+ def getProppatchElement(self):
+ """NOTE that the latest specification does NOT specify that a client
+ can't update a property only once during a PROPPATCH request -> this
+ method and implementation is meaningless.
+ """
+ return self.request.xmlDataSource.findall(
+ '{DAV:}set/{DAV:}prop/{%s}%s' % (self.namespace, self.name))[-1:]
+
+ def hasInput(self):
+ if self.getProppatchElement():
+ return True
+ return False
+
+ def toFieldValue(self, element):
+ raise NotImplementedError(
+ "Please implement the toFieldValue as a subclass of DAVInputWidget")
+
+ def getInputValue(self):
+ el = self.getProppatchElement()
+
+ # form input is required, otherwise raise an error
+ if not el:
+ raise MissingInputError(self.name, None, None)
+
+ # convert input to suitable value - may raise conversion error
+ value = self.toFieldValue(el[0])
+ return value
+
+
+class TextDAVInputWidget(DAVInputWidget):
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def toFieldValue(self, element):
+ value = element.text
+ if value is None:
+ return u"" # toFieldValue must validate against the
+ if not isinstance(value, unicode):
+ return value.decode("utf-8")
+ return value
+
+
+class IntDAVInputWidget(DAVInputWidget):
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def toFieldValue(self, element):
+ value = element.text
+ # XXX - should this be happening - has then the field doesn't validate
+ # in the default case against the corresponding field.
+ if not value:
+ return self.context.missing_value
+
+ try:
+ return int(value)
+ except ValueError, e:
+ raise ConversionError("Invalid int", e)
+
+
+class FloatDAVInputWidget(DAVInputWidget):
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def toFieldValue(self, element):
+ value = element.text
+ if not value:
+ # XXX - should this be the case?
+ return self.context.missing_value
+
+ try:
+ return float(value)
+ except ValueError, e:
+ raise ConversionError("Invalid float", e)
+
+
+class DatetimeDAVInputWidget(DAVInputWidget):
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def toFieldValue(self, element):
+ value = element.text
+ try:
+ return zope.datetime.parseDatetimetz(value)
+ except (zope.datetime.DateTimeError, ValueError, IndexError), e:
+ raise ConversionError("Invalid datetime date", e)
+
+
+class DateDAVInputWidget(DatetimeDAVInputWidget):
+ interface.classProvides(interfaces.IIDAVInputWidget)
+
+ def toFieldValue(self, element):
+ value = super(DateDAVInputWidget, self).toFieldValue(element)
+ return value.date()
Property changes on: zope.webdav/trunk/src/zope/webdav/widgets.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/z3-configure.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/z3-configure.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,88 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <!--
+ Zope3 webdav support - this should all be in a seperate Zope3 package.
+ -->
+
+ <adapter
+ factory=".adapters.DAVSchemaAdapter"
+ />
+
+ <adapter
+ factory=".adapters.DAVFileGetSchema"
+ />
+
+ <adapter
+ factory=".deadproperties.OpaqueProperties"
+ trusted="1"
+ />
+
+ <class class=".deadproperties.OpaqueProperties">
+ <require
+ permission="zope.Public"
+ attributes="getAllProperties hasProperty getProperty"
+ />
+
+ <require
+ permission="zope.ManageContent"
+ attributes="setProperty removeProperty"
+ />
+ </class>
+
+ <!--
+ Support for using zope.locking for locking support.
+ -->
+ <adapter
+ factory=".lockingutils.DAVSupportedlock"
+ />
+
+ <adapter
+ factory=".lockingutils.DAVLockdiscovery"
+ />
+
+ <adapter
+ factory=".lockingutils.DAVLockmanager"
+ trusted="1"
+ />
+
+ <class class=".lockingutils.DAVLockmanager">
+ <require
+ permission="zope.View"
+ attributes="getActivelock islocked islockable"
+ />
+
+ <require
+ permission="zope.ManageContent"
+ attributes="lock refreshlock unlock"
+ />
+ </class>
+
+ <adapter
+ factory=".lockingutils.DAVActiveLock"
+ for="zope.interface.Interface
+ zope.webdav.interfaces.IWebDAVRequest"
+ provides="zope.webdav.coreproperties.IActiveLock"
+ />
+
+ <class class=".lockingutils.DAVActiveLockAdapter">
+ <require
+ permission="zope.View"
+ interface="zope.webdav.coreproperties.IActiveLock"
+ />
+ </class>
+
+ <class class=".lockingutils.IndirectToken">
+ <require
+ permission="zope.View"
+ attributes="context utility principal_ids started annotations roottoken"
+ />
+
+ <require permission="zope.View"
+ attributes="ended expiration duration remaining_duration" />
+
+ <require permission="zope.Security"
+ attributes="end"
+ set_attributes="expiration duration remaining_duration" />
+ </class>
+
+</configure>
Property changes on: zope.webdav/trunk/src/zope/webdav/z3-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/zetree.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zetree.py 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zetree.py 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1,305 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Zope Element Tree Support
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import copy
+from zope.interface import implements
+
+from ietree import IEtree
+
+class BaseEtree(object):
+ def Comment(self, text = None):
+ return self.etree.Comment(text)
+
+ # XXX - not tested
+ def dump(self, elem):
+ return self.etree.dump(elem)
+
+ def Element(self, tag, attrib = {}, **extra):
+ return self.etree.Element(tag, attrib, **extra)
+
+ def ElementTree(self, element = None, file = None):
+ return self.etree.ElementTree(element, file)
+
+ def XML(self, text):
+ return self.etree.fromstring(text)
+
+ fromstring = XML
+
+ def iselement(self, element):
+ return self.etree.iselement(element)
+
+ # XXX - not tested
+ def iterparse(self, source, events = None):
+ return self.etree.iterparse(source, events)
+
+ def parse(self, source, parser = None):
+ return self.etree.parse(source, parser)
+
+ def PI(self, target, text = None):
+ raise NotImplementedError, "lxml doesn't implement PI"
+
+ ProcessingInstruction = PI
+
+ def QName(self, text_or_uri, tag = None):
+ return self.etree.QName(text_or_uri, tag)
+
+ def SubElement(self, parent, tag, attrib = {}, **extra):
+ return self.etree.SubElement(parent, tag, attrib, **extra)
+
+ def tostring(self, element, encoding = None):
+ return self.etree.tostring(element, encoding)
+
+ def TreeBuilder(self, element_factory = None):
+ raise NotImplementedError, "lxml doesn't implement TreeBuilder"
+
+ def XMLTreeBuilder(self, html = 0, target = None):
+ raise NotImplementedError, "lxml doesn't implement XMLTreeBuilder"
+
+
+class EtreeEtree(BaseEtree):
+ """
+ Support for ElementTree
+
+ >>> from cStringIO import StringIO
+ >>> from zope.interface.verify import verifyObject
+ >>> letree = EtreeEtree()
+ >>> verifyObject(IEtree, letree)
+ True
+
+ >>> letree.Comment(u'some text') #doctest:+ELLIPSIS
+ <Element <function Comment at ...
+
+ >>> letree.Element(u'testtag')
+ <Element...
+
+ >>> letree.ElementTree() #doctest:+ELLIPSIS
+ <elementtree.ElementTree.ElementTree instance at ...
+
+ >>> letree.XML(u'<p>some text</p>')
+ <Element p ...
+
+ >>> letree.fromstring(u'<p>some text</p>')
+ <Element p ...
+
+ >>> elem = letree.Element(u'testtag')
+ >>> letree.iselement(elem)
+ 1
+
+ >>> f = StringIO('<b>Test Source String</b>')
+ >>> letree.parse(f) #doctest:+ELLIPSIS
+ <elementtree.ElementTree.ElementTree instance at ...
+
+ >>> letree.QName('http://example.namespace.org', 'test')#doctest:+ELLIPSIS
+ <elementtree.ElementTree.QName instance at...
+
+ >>> print letree.tostring(elem, 'ascii')
+ <?xml version='1.0' encoding='ascii'?>
+ <testtag />
+
+ >>> letree.TreeBuilder()
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement TreeBuilder
+
+ >>> subel = letree.SubElement(elem, 'foo')
+ >>> letree.tostring(elem)
+ '<testtag><foo /></testtag>'
+
+ >>> letree.PI('sometarget') #doctest:+ELLIPSIS
+ <Element <function ProcessingInstruction at ...
+
+ >>> letree.ProcessingInstruction('sometarget') #doctest:+ELLIPSIS
+ <Element <function ProcessingInstruction at ...
+
+ >>> letree.XMLTreeBuilder()
+ <elementtree.ElementTree.XMLTreeBuilder instance at ...
+
+ """
+ implements(IEtree)
+
+ def __init__(self):
+ from elementtree import ElementTree
+ self.etree = ElementTree
+
+ def XMLTreeBuilder(self, html = 0, target = None):
+ return self.etree.XMLTreeBuilder(html, target)
+
+ def PI(self, target, text = None):
+ return self.etree.PI(target, text)
+
+ ProcessingInstruction = PI
+
+
+class EtreePy25(BaseEtree):
+ """
+ Support for ElementTree
+
+ >>> from cStringIO import StringIO
+ >>> from zope.interface.verify import verifyObject
+ >>> letree = EtreePy25()
+ >>> verifyObject(IEtree, letree)
+ True
+
+ >>> letree.Comment(u'some text') #doctest:+ELLIPSIS
+ <Element <function Comment at ...
+
+ >>> letree.Element(u'testtag')
+ <Element...
+
+ >>> letree.ElementTree() #doctest:+ELLIPSIS
+ <xml.etree.ElementTree.ElementTree instance at ...
+
+ >>> letree.XML(u'<p>some text</p>')
+ <Element p ...
+
+ >>> letree.fromstring(u'<p>some text</p>')
+ <Element p ...
+
+ >>> elem = letree.Element(u'testtag')
+ >>> letree.iselement(elem)
+ 1
+
+ >>> f = StringIO('<b>Test Source String</b>')
+ >>> letree.parse(f) #doctest:+ELLIPSIS
+ <xml.etree.ElementTree.ElementTree instance at ...
+
+ >>> letree.QName('http://example.namespace.org', 'test')#doctest:+ELLIPSIS
+ <xml.etree.ElementTree.QName instance at...
+
+ >>> print letree.tostring(elem, 'ascii')
+ <?xml version='1.0' encoding='ascii'?>
+ <testtag />
+
+ >>> letree.TreeBuilder()
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement TreeBuilder
+
+ >>> subel = letree.SubElement(elem, 'foo')
+ >>> letree.tostring(elem)
+ '<testtag><foo /></testtag>'
+
+ >>> letree.PI('sometarget') #doctest:+ELLIPSIS
+ <Element <function ProcessingInstruction at ...
+
+ >>> letree.ProcessingInstruction('sometarget') #doctest:+ELLIPSIS
+ <Element <function ProcessingInstruction at ...
+
+ >>> letree.XMLTreeBuilder()
+ <xml.etree.ElementTree.XMLTreeBuilder instance at ...
+
+ """
+ implements(IEtree)
+
+ def __init__(self):
+ from xml.etree import ElementTree
+ self.etree = ElementTree
+
+ def XMLTreeBuilder(self, html = 0, target = None):
+ return self.etree.XMLTreeBuilder(html, target)
+
+ def PI(self, target, text = None):
+ return self.etree.PI(target, text)
+
+ ProcessingInstruction = PI
+
+
+class LxmlEtree(BaseEtree):
+ """
+ Support for lxml.
+
+ >>> from cStringIO import StringIO
+ >>> from zope.interface.verify import verifyObject
+ >>> letree = LxmlEtree()
+ >>> verifyObject(IEtree, letree)
+ True
+
+ >>> letree.Comment(u'some text')
+ <Comment[some text]>
+
+ >>> letree.Element(u'testtag')
+ <Element...
+
+ >>> letree.ElementTree()
+ <etree._ElementTree...
+
+ >>> letree.XML(u'<p>some text</p>')
+ <Element p ...
+
+ >>> letree.fromstring(u'<p>some text</p>')
+ <Element p ...
+
+ When we have a element whoes namespace declaration is declared in a parent
+ element lxml doesn't print out the namespace declaration by default.
+
+ >>> multinselemstr = '<D:prop xmlns:D="DAV:"><D:owner><H:href xmlns:H="examplens">http://example.org</H:href></D:owner></D:prop>'
+ >>> multinselem = letree.fromstring(multinselemstr)
+ >>> letree.tostring(multinselem[0])
+ '<D:owner xmlns:D="DAV:"><H:href xmlns:H="examplens">http://example.org</H:href></D:owner>'
+
+ >>> elem = letree.Element(u'testtag')
+ >>> letree.iselement(elem)
+ 1
+
+ >>> f = StringIO('<b>Test Source String</b>')
+ >>> letree.parse(f)
+ <etree._ElementTree object at ...
+
+ >>> letree.QName('http://example.namespace.org', 'test')
+ <etree.QName object at...
+
+ >>> letree.tostring(elem, 'ascii')
+ '<testtag/>'
+
+ >>> letree.TreeBuilder()
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement TreeBuilder
+
+ >>> subel = letree.SubElement(elem, 'foo')
+ >>> subel.getparent() is elem
+ True
+
+ >>> letree.PI('sometarget')
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement PI
+
+ >>> letree.ProcessingInstruction('sometarget')
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement PI
+
+ >>> letree.XMLTreeBuilder()
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: lxml doesn't implement XMLTreeBuilder
+
+ """
+ implements(IEtree)
+
+ def __init__(self):
+ from lxml import etree
+ self.etree = etree
+
+ def tostring(self, element, encoding = None):
+ """LXML loses the namespace information whenever we print out an
+ element who namespace was defined in
+ """
+ return self.etree.tostring(copy.copy(element), encoding)
Property changes on: zope.webdav/trunk/src/zope/webdav/zetree.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1 @@
+<include package="zope.webdav" />
Property changes on: zope.webdav/trunk/src/zope/webdav/zope.webdav-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml 2006-08-23 19:37:43 UTC (rev 69735)
+++ zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml 2006-08-23 20:21:36 UTC (rev 69736)
@@ -0,0 +1 @@
+<include package="zope.webdav" file="ftesting.zcml" />
Property changes on: zope.webdav/trunk/src/zope/webdav/zope.webdav-ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Checkins
mailing list