[Checkins] SVN: z3c.davapp.zopeappfile/ Initial import of z3c.davapp.zopeappfile.

Michael Kerrin michael.kerrin at openapp.ie
Tue May 8 14:02:47 EDT 2007


Log message for revision 75627:
  Initial import of z3c.davapp.zopeappfile.
  
  This package defines and implements the WebDAV data model for
  all zope.app.file.file.File content objects.
  

Changed:
  A   z3c.davapp.zopeappfile/
  A   z3c.davapp.zopeappfile/trunk/
  A   z3c.davapp.zopeappfile/trunk/buildout.cfg
  A   z3c.davapp.zopeappfile/trunk/setup.py
  A   z3c.davapp.zopeappfile/trunk/src/
  A   z3c.davapp.zopeappfile/trunk/src/z3c/
  A   z3c.davapp.zopeappfile/trunk/src/z3c/__init__.py
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/__init__.py
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/__init__.py
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/configure.zcml
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/ftesting.zcml
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/properties.txt
  A   z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/tests.py

-=-
Added: z3c.davapp.zopeappfile/trunk/buildout.cfg
===================================================================
--- z3c.davapp.zopeappfile/trunk/buildout.cfg	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/buildout.cfg	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,10 @@
+[buildout]
+develop = . z3c.dav z3c.etree
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+working-directory = .
+defaults = ["--tests-pattern", "^f?tests$"]
+eggs = z3c.dav [test]
+       z3c.davapp.zopeappfile [test]

Added: z3c.davapp.zopeappfile/trunk/setup.py
===================================================================
--- z3c.davapp.zopeappfile/trunk/setup.py	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/setup.py	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,22 @@
+from setuptools import setup, find_packages
+
+setup(name = "z3c.davapp.zopeappfile",
+      version = "0.1",
+      author = "Michael Kerrin",
+      author_email = "michael.kerrin at openapp.ie",
+      url = "http://svn.zope.org/",
+      description = "WebDAV support for zope.app.file content objects",
+      license = "ZPL",
+
+      packages = find_packages("src"),
+      package_dir = {"": "src"},
+      namespace_packages = ["z3c", "z3c.davapp"],
+      install_requires = ["setuptools",
+                          "z3c.dav",
+                          "zope.app.file",
+                          ],
+
+      extras_require = dict(test = ["cElementTree"]),
+
+      include_package_data = True,
+      zip_safe = False)

Added: z3c.davapp.zopeappfile/trunk/src/z3c/__init__.py
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/__init__.py	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/__init__.py	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/__init__.py
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/__init__.py	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/__init__.py	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/__init__.py
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/__init__.py	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/__init__.py	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,43 @@
+import zope.component
+import zope.interface
+import zope.publisher.interfaces.http
+import zope.app.file.interfaces
+
+import z3c.dav.coreproperties
+
+class DAVFileGetSchema(object):
+    """
+    Storage adapter for the `{DAV:}getcontentlength` and `{DAV:}getcontenttype`
+    WebDAV properties for a content object implementing
+    zope.app.file.interfaces.IFile interface.
+
+      >>> 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(z3c.dav.coreproperties.IDAVGetcontentlength, adapter)
+      True
+      >>> verifyObject(z3c.dav.coreproperties.IDAVGetcontenttype, adapter)
+      True
+
+      >>> adapter.getcontentlength
+      22
+      >>> adapter.getcontenttype
+      'text/plain'
+
+    """
+    zope.interface.implements(z3c.dav.coreproperties.IDAVGetcontentlength,
+                              z3c.dav.coreproperties.IDAVGetcontenttype)
+    zope.component.adapts(zope.app.file.interfaces.IFile,
+                          zope.publisher.interfaces.http.IHTTPRequest)
+
+    def __init__(self, context, request):
+        self.context = context
+
+    @property
+    def getcontentlength(self):
+        return self.context.getSize()
+
+    @property
+    def getcontenttype(self):
+        return self.context.contentType

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/configure.zcml
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/configure.zcml	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/configure.zcml	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,25 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <adapter
+      for="zope.app.file.interfaces.IFile
+           zope.publisher.interfaces.http.IHTTPRequest"
+      factory="z3c.dav.adapters.DAVDublinCore"
+      />
+
+  <adapter
+      for="zope.app.file.interfaces.IFile"
+      factory="z3c.dav.adapters.OpaqueProperties"
+      trusted="1"
+      />
+
+  <adapter
+      factory=".DAVFileGetSchema"
+      provides="z3c.dav.coreproperties.IDAVGetcontentlength"
+      />
+
+  <adapter
+      factory=".DAVFileGetSchema"
+      provides="z3c.dav.coreproperties.IDAVGetcontenttype"
+      />
+
+</configure>

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/ftesting.zcml
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/ftesting.zcml	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/ftesting.zcml	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,8 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <include package="z3c.dav" file="ftesting.zcml" />
+  <include package="zope.app.file" />
+
+  <include package="z3c.davapp.zopeappfile" />
+
+</configure>

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/properties.txt
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/properties.txt	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/properties.txt	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,357 @@
+================================
+zope.app.file webdav integration
+================================
+
+  >>> import z3c.etree
+  >>> from zope.app.file.file import File
+
+  >>> etree = z3c.etree.getEngine()
+
+Create a content object called `textfile.txt` in the root folder.
+
+  >>> f = File('%s\n' %('x' * 10) * 5, 'text/plain')
+  >>> getRootFolder()['testfile.txt'] = f
+
+GET
+===
+
+The default view for the content object needs to be setup in order for WebDAV
+clients to be able to get the data for the DAV compliant resource.
+
+  >>> resp = http("""
+  ... GET /testfile.txt HTTP/1.1
+  ... """, handle_errors = False)
+  >>> resp.getStatus()
+  200
+  >>> resp.getHeader('content-type')
+  'text/plain'
+  >>> resp.getHeader('content-length')
+  '55'
+  >>> print resp.getBody()
+  xxxxxxxxxx
+  xxxxxxxxxx
+  xxxxxxxxxx
+  xxxxxxxxxx
+  xxxxxxxxxx
+
+PUT
+===
+
+???
+
+PROPFIND
+========
+
+Query the WebDAV data model for the file object. The following properties
+currently make up the data model for zope.file:
+
++ {DAV:}creationdate
+
++ {DAV:}displayname
+
++ {DAV:}getcontentlength
+
++ {DAV:}getcontenttype
+
++ {DAV:}getlastmodified
+
++ {DAV:}resourcetype
+
+Query all properties on our test file.
+
+  >>> resp = webdav("""
+  ... PROPFIND /testfile.txt HTTP/1.1
+  ... """, handle_errors = False)
+
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <creationdate />
+          <displayname />
+          <getlastmodified />
+          <getcontenttype>text/plain</getcontenttype>
+          <getcontentlength>55</getcontentlength>
+          <resourcetype />
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+The `{DAV:}getetag` and `{DAV:}getcontentlanguage` properties are not defined
+(yet) on a file object.
+
+  >>> resp.getMSProperty('http://localhost/testfile.txt', '{DAV:}getetag')
+  Traceback (most recent call last):
+  ...
+  KeyError: "'{DAV:}getetag' property not found for resource http://localhost/testfile.txt (200)"
+  >>> resp.getMSProperty(
+  ...    'http://localhost/testfile.txt', '{DAV:}getcontentlanguage')
+  Traceback (most recent call last):
+  ...
+  KeyError: "'{DAV:}getcontentlanguage' property not found for resource http://localhost/testfile.txt (200)"
+
+PROPPATCH
+=========
+
+We need to be logged in to update a property.
+
+  >>> reqbody = """<propertyupdate xmlns="DAV:">
+  ...  <set>
+  ...   <prop>
+  ...    <displayname>Test title</displayname>
+  ...   </prop>
+  ...  </set>
+  ... </propertyupdate>"""
+  >>> resp = webdav("""
+  ... PROPPATCH /testfile.txt HTTP/1.1
+  ... Content-Type: application/xml
+  ... Content-Length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody))
+  >>> resp.getStatus()
+  401
+
+Now update the title of the file.
+
+  >>> resp = webdav("""
+  ... PROPPATCH /testfile.txt HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-Type: application/xml
+  ... Content-Length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody), handle_errors = False)
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <displayname />
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+Retrieve the displayname property using PROPFIND and notice the difference.
+
+  >>> reqbody = """<propfind xmlns="DAV:">
+  ...   <prop>
+  ...     <displayname />
+  ...   </prop>
+  ... </propfind>
+  ... """
+  >>> resp = webdav("""
+  ... PROPFIND /testfile.txt HTTP/1.1
+  ... Content-type: application/xml
+  ... Content-Length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody), handle_errors = False)
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <displayname>Test title</displayname>
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+The `{DAV:}getcontentlength` property is a live property, and as such we
+cannot modify it.
+
+  >>> reqbody = """<propertyupdate xmlns="DAV:">
+  ...   <set>
+  ...     <prop>
+  ...       <getcontentlength>24</getcontentlength>
+  ...     </prop>
+  ...   </set>
+  ... </propertyupdate>"""
+  >>> resp = webdav("""
+  ... PROPPATCH /testfile.txt HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-type: application/xml 
+  ... Content-length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody))
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <getcontentlength />
+        </prop>
+        <status>HTTP/1.1 403 Forbidden</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+Opaque properties
+=================
+
+Set two dead properties on our resource.
+
+  >>> reqbody = """<propertyupdate xmlns="DAV:">
+  ...  <set>
+  ...    <prop>
+  ...      <E:prop1 xmlns:E="examplens:">PROPERTY 1</E:prop1>
+  ...      <E:prop2 xmlns:E="examplens:">PROPERTY 2</E:prop2>
+  ...    </prop>
+  ...  </set>
+  ... </propertyupdate>
+  ... """
+  >>> resp = webdav("""
+  ... PROPPATCH /testfile.txt HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-type: application/xml
+  ... Content-length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody), handle_errors = True)
+
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <ns1:prop1 xmlns:ns1="examplens:" />
+          <ns1:prop2 xmlns:ns1="examplens:" />
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+Query these new properties.
+
+  >>> reqbody = """<propfind xmlns="DAV:">
+  ...  <prop xmlns:E="examplens:" >
+  ...    <E:prop1 />
+  ...    <E:prop2 />
+  ...  </prop>
+  ... </propfind>
+  ... """
+  >>> resp = webdav("""
+  ... PROPFIND /testfile.txt HTTP/1.1
+  ... Content-type: application/xml
+  ... Content-length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody))
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <E:prop1 xmlns:E="examplens:">PROPERTY 1</E:prop1>
+          <E:prop2 xmlns:E="examplens:">PROPERTY 2</E:prop2>
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+Update the first property and remove the second.
+
+  >>> reqbody = """<propertyupdate xmlns="DAV:">
+  ...  <set>
+  ...    <prop>
+  ...      <E:prop1 xmlns:E="examplens:">Only opaque property</E:prop1>
+  ...    </prop>
+  ...  </set>
+  ...  <remove>
+  ...    <prop>
+  ...      <E:prop2 xmlns:E="examplens:" />
+  ...    </prop>
+  ...  </remove>
+  ... </propertyupdate>"""
+  >>> resp = webdav("""
+  ... PROPPATCH /testfile.txt HTTP/1.1
+  ... Authorization: Basic mgr:mgrpw
+  ... Content-type: application/xml
+  ... Content-length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody))
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <E:prop1 xmlns:E="examplens:" />
+          <E:prop2 xmlns:E="examplens:" />
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+    </response>
+  </multistatus>
+
+Now check that the opaque properties were updated successfully.
+
+  >>> reqbody = """<propfind xmlns="DAV:">
+  ...  <prop xmlns:E="examplens:" >
+  ...    <E:prop1 />
+  ...    <E:prop2 />
+  ...  </prop>
+  ... </propfind>
+  ... """
+  >>> resp = webdav("""
+  ... PROPFIND /testfile.txt HTTP/1.1
+  ... Content-type: application/xml
+  ... Content-length: %d
+  ...
+  ... %s""" %(len(reqbody), reqbody))
+  >>> resp.getStatus()
+  207
+  >>> resp.getHeader('content-type')
+  'application/xml'
+  >>> print resp.getBody() #doctest:+XMLDATA
+  <multistatus xmlns="DAV:">
+    <response>
+      <href>http://localhost/testfile.txt</href>
+      <propstat>
+        <prop>
+          <ns1:prop1 xmlns:ns1="examplens:">Only opaque property</ns1:prop1>
+        </prop>
+        <status>HTTP/1.1 200 OK</status>
+      </propstat>
+      <propstat>
+        <prop>
+          <ns1:prop2 xmlns:ns1="examplens:" />
+        </prop>
+        <status>HTTP/1.1 404 Not Found</status>
+      </propstat>
+    </response>
+  </multistatus>


Property changes on: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/properties.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/tests.py
===================================================================
--- z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/tests.py	2007-05-08 16:04:10 UTC (rev 75626)
+++ z3c.davapp.zopeappfile/trunk/src/z3c/davapp/zopeappfile/tests.py	2007-05-08 18:02:46 UTC (rev 75627)
@@ -0,0 +1,25 @@
+import os
+import unittest
+
+from zope.testing import doctest
+
+import z3c.etree.testing
+import z3c.dav.testing
+
+here = os.path.dirname(os.path.realpath(__file__))
+ZAFDavLayer = z3c.dav.testing.WebDAVLayerClass(
+    os.path.join(here, "ftesting.zcml"), __name__, "ZAFDavLayer")
+
+
+def test_suite():
+    properties = doctest.DocFileSuite(
+        "properties.txt",
+        setUp = z3c.dav.testing.functionalSetUp,
+        tearDown = z3c.dav.testing.functionalTearDown,
+        checker = z3c.etree.testing.xmlOutputChecker,
+        optionflags = doctest.REPORT_NDIFF | doctest.NORMALIZE_WHITESPACE)
+    properties.layer = ZAFDavLayer
+    return unittest.TestSuite((
+        doctest.DocTestSuite("z3c.davapp.zopeappfile"),
+        properties,
+        ))



More information about the Checkins mailing list