[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/dav/ Merged
isarsprint-dav-work changes r28063:28187 into the trunk;
final work on WebDAV properties making registered interfaces
supported now too.
Martijn Pieters
mj at zopatista.com
Thu Oct 14 10:27:04 EDT 2004
Log message for revision 28188:
Merged isarsprint-dav-work changes r28063:28187 into the trunk; final work on WebDAV properties making registered interfaces supported now too.
Changed:
U Zope3/trunk/src/zope/app/dav/ftests/test_proppatch.py
U Zope3/trunk/src/zope/app/dav/interfaces.py
U Zope3/trunk/src/zope/app/dav/opaquenamespaces.py
U Zope3/trunk/src/zope/app/dav/propfind.py
U Zope3/trunk/src/zope/app/dav/proppatch.py
U Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py
U Zope3/trunk/src/zope/app/dav/widget.py
-=-
Modified: Zope3/trunk/src/zope/app/dav/ftests/test_proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/ftests/test_proppatch.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/ftests/test_proppatch.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -65,6 +65,31 @@
self._assertOPropsEqual(pt,
{u'uri://foo': {u'bar': '<bar>spam</bar>'}})
+ def test_remove_dctitle(self):
+ self.addPage('/pt', u'<span />')
+ pt = traverse(self.getRootFolder(), '/pt')
+ adapted = IZopeDublinCore(pt)
+ adapted.title = u'Test'
+ transaction.commit()
+ # DC Title is a required field with no default, so a 409 is expected
+ expect = self._makePropstat(('http://purl.org/dc/1.1',),
+ '<title xmlns="a0"/>', 409)
+ self.verifyPropOK(path='/pt',
+ namespaces=(('DC', 'http://purl.org/dc/1.1'),),
+ rm=('<DC:title/>',), expect=expect)
+
+ def test_set_dctitle(self):
+ self.addPage('/pt', u'<span />')
+ pt = traverse(self.getRootFolder(), '/pt')
+ adapted = IZopeDublinCore(pt)
+ transaction.commit()
+ expect = self._makePropstat(('http://purl.org/dc/1.1',),
+ '<title xmlns="a0"/>')
+ self.verifyPropOK(path='/pt',
+ namespaces=(('DC', 'http://purl.org/dc/1.1'),),
+ set=('<DC:title>Test Title</DC:title>',), expect=expect)
+ self.assertEqual(IZopeDublinCore(pt).title, u'Test Title')
+
def _assertOPropsEqual(self, obj, expect):
oprops = IDAVOpaqueNamespaces(obj)
namespacesA = list(oprops.keys())
Modified: Zope3/trunk/src/zope/app/dav/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/interfaces.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/interfaces.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -19,7 +19,7 @@
from zope.interface import Interface
from zope.schema import Text
-from zope.app.form.interfaces import IWidget
+from zope.app.form.interfaces import IInputWidget
class IDAVNamespace(Interface):
@@ -40,9 +40,11 @@
resources. If present, it contains a
timestamp of the moment when the
resource was created (i.e., the moment
- it had non- null state).''')
+ it had non- null state).''',
+ readonly=True)
+
class IDAVDisplayName(Interface):
displayname = Text(title=u'''Provides a name for the resource that\
@@ -102,8 +104,10 @@
The getcontentlength property MUST be
defined on any DAV compliant resource
that returns the Content-Length header
- in response to a GET.''')
+ in response to a GET.''',
+ readonly=True)
+
getcontenttype = Text(title=u'''Contains the Content-Type header\
returned by a GET without accept\
headers''',
@@ -120,8 +124,10 @@
description=u'''\
The getetag property MUST be defined
on any DAV compliant resource that
- returns the Etag header.''')
+ returns the Etag header.''',
+ readonly=True)
+
getlastmodified = Text(title=u'''Contains the Last-Modified header\
returned by a GET method without\
accept headers''',
@@ -138,9 +144,11 @@
MUST be defined on any DAV compliant
resource that returns the
Last-Modified header in response to a
- GET.''')
+ GET.''',
+ readonly=True)
+
class IDAV1Schema(IGETDependentDAVSchema):
"""DAV properties required for Level 1 compliance"""
@@ -150,9 +158,11 @@
The resourcetype property MUST be
defined on all DAV compliant
resources. The default value is
- empty.''')
+ empty.''',
+ readonly=True)
+
class IDAV2Schema(IDAV1Schema):
"""DAV properties required for Level 2 compliance"""
@@ -169,8 +179,10 @@
of this information if the requesting
principal does not have sufficient
access rights to see the requested
- data.''')
+ data.''',
+ readonly=True)
+
supportedlock = Text(title=u'''To provide a listing of the lock\
capabilities supported by the\
resource''',
@@ -185,17 +197,32 @@
controlled by access controls so a
server is not required to provide
information the client is not
- authorized to see.''')
+ authorized to see.''',
+ readonly=True)
class IDAVSchema(IOptionalDAVSchema, IDAV2Schema):
"""Full DAV properties schema"""
-class IDAVWidget(IWidget):
- """A specialized widget used to render DAV properties output."""
+class IDAVWidget(IInputWidget):
+ """A specialized widget used to convert to and from DAV properties."""
+
+ def __call__():
+ """Render the widget.
+ Optionally, this method could return a minidom DOM Node as the value;
+ this node will then be inersted into the resulting DAV XML response.
+ Use a DocumentFragment if you want to include multiple nodes.
+ """
+
+ def setRenderedValue(value):
+ """Set the DAV value for the property
+ value can be a DOM Element node representing the value.
+ """
+
+
class ITextDAVWidget(IDAVWidget):
"""A DAV widget for text values."""
Modified: Zope3/trunk/src/zope/app/dav/opaquenamespaces.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/opaquenamespaces.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/opaquenamespaces.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -108,6 +108,7 @@
#
# Convenience methods; storing and retrieving properties through WebDAV
+ # It may be better to use specialised IDAWWidget implementatins for this.
#
def renderProperty(self, ns, nsprefix, prop, propel):
"""Render a property as DOM elements"""
Modified: Zope3/trunk/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/propfind.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/propfind.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -16,7 +16,7 @@
__docformat__ = 'restructuredtext'
from xml.dom import minidom
-from zope.schema import getFieldNamesInOrder
+from zope.schema import getFieldNamesInOrder, getFields
from zope.app import zapi
from zope.app.container.interfaces import IReadContainer
from zope.app.form.utility import setUpWidgets
@@ -220,31 +220,30 @@
# The registered namespace case
initial = {}
- adapter = iface(self.context, None)
- for name in avail.get(ns):
- value = getattr(adapter, name, None)
- if value is not None:
+ for name, field in getFields(iface).items():
+ value = field.get(iface(self.context))
+ if value is not field.missing_value:
initial[name] = value
- setUpWidgets(self, iface, IDAVWidget,
- ignoreStickyValues=True, initial=initial,
- names=avail.get(ns))
+ setUpWidgets(self, iface, IDAVWidget, ignoreStickyValues=True,
+ initial=initial, names=avail[ns])
for p in avail.get(ns):
el = resp.createElement('%s' % p )
if ns is not None and ns != self.default_ns:
el.setAttribute('xmlns', attr_name)
prop.appendChild(el)
- value = getattr(self, p+'_widget')()
+ value = getattr(self, p + '_widget')()
if isinstance(value, (unicode, str)):
# Get the widget value here
el.appendChild(resp.createTextNode(value))
else:
if zapi.isinstance(value, minidom.Node):
- el.appendChild(value)
+ el.appendChild(
+ el.ownerDocument.importNode(value, True))
else:
# Try to string-ify
- value = str(getattr(self, p+'_widget'))
+ value = str(getattr(self, p + '_widget'))
# Get the widget value here
el.appendChild(resp.createTextNode(value))
Modified: Zope3/trunk/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/proppatch.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/proppatch.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -19,11 +19,12 @@
import transaction
from zope.app import zapi
-from zope.schema import getFieldNamesInOrder
+from zope.schema import getFieldNamesInOrder, getFields
from zope.app.container.interfaces import IReadContainer
from zope.publisher.http import status_reasons
+from zope.app.form.utility import setUpWidget, no_value
-from interfaces import IDAVNamespace
+from interfaces import IDAVNamespace, IDAVWidget
from opaquenamespaces import IDAVOpaqueNamespaces
class PROPPATCH(object):
@@ -156,10 +157,32 @@
props.append(prop.localName)
return 200
return 403
-
- # XXX: Deal with registered ns interfaces here
- return 403
+
+ if not prop.localName in self.avail_props[ns]:
+ return 403 # Cannot add propeties to a registered schema
+
+ fields = getFields(iface)
+ field = fields[prop.localName]
+ if field.readonly:
+ return 409 # RFC 2518 specifies 409 for readonly props
+
+ value = field.get(iface(self.context))
+ if value is field.missing_value:
+ value = no_value
+ setUpWidget(self, prop.localName, field, IDAVWidget,
+ value=value, ignoreStickyValues=True)
+
+ widget = getattr(self, prop.localName + '_widget')
+ widget.setRenderedValue(prop)
+ if not widget.hasValidInput():
+ return 409 # Didn't match the widget validation
+
+ if widget.applyChanges(iface(self.context)):
+ return 200
+
+ return 422 # Field didn't accept the value
+
def _handleRemove(self, prop):
ns = prop.namespaceURI
if not prop.localName in self.avail_props.get(ns, []):
@@ -171,6 +194,20 @@
return 200
self.oprops.removeProperty(ns, prop.localName)
return 200
-
- # XXX: Deal with registered ns interfaces here
- return 403
+
+ # Registered interfaces
+ fields = getFields(iface)
+ field = fields[prop.localName]
+ if field.readonly:
+ return 409 # RFC 2518 specifies 409 for readonly props
+
+ if field.required:
+ if field.default is None:
+ return 409 # Clearing a required property is a conflict
+ # Reset the field to the default if a value is required
+ field.set(iface(self.context), field.default)
+ return 200
+
+ # Reset the field to it's defined missing_value
+ field.set(iface(self.context), field.missing_value)
+ return 200
Modified: Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -20,7 +20,10 @@
import unittest
from StringIO import StringIO
+import transaction
+
from zope.interface import Interface, implements, directlyProvides
+from zope.schema import Text
from zope.publisher.interfaces.http import IHTTPRequest
from zope.publisher.http import status_reasons
from zope.pagetemplate.tests.util import normalize_xml
@@ -35,8 +38,10 @@
from zope.app.traversing.browser import AbsoluteURL
from zope.app.dublincore.interfaces import IZopeDublinCore
from zope.app.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+from zope.app.dublincore.zopedublincore import ScalarProperty
from zope.app.annotation.interfaces import IAnnotatable, IAnnotations
from zope.app.annotation.attribute import AttributeAnnotations
+from zope.schema.interfaces import IText, ISequence
import zope.app.dav.tests
from zope.app.dav.tests.unitfixtures import File, Folder, FooZPT
@@ -44,6 +49,8 @@
from zope.app.dav import proppatch
from zope.app.dav.interfaces import IDAVSchema
from zope.app.dav.interfaces import IDAVNamespace
+from zope.app.dav.interfaces import IDAVWidget
+from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
@@ -86,6 +93,44 @@
request = TestRequest(StringIO(body), StringIO(), _environ)
return request
+
+class ITestSchema(Interface):
+ requiredNoDefault = Text(required=True, default=None)
+ requiredDefault = Text(required=True, default=u'Default Value')
+ unusualMissingValue = Text(required=False, missing_value=u'Missing Value')
+ constrained = Text(required=False, min_length=5)
+
+EmptyTestValue = object()
+TestKey = 'zope.app.dav.tests.test_proppatch'
+TestURI = 'uri://proppatch_tests'
+
+class TestSchemaAdapter(object):
+ implements(ITestSchema)
+ __used_for__ = IAnnotatable
+ annotations = None
+
+ def __init__(self, context):
+ annotations = IAnnotations(context)
+ data = annotations.get(TestKey)
+ if data is None:
+ self.annotations = annotations
+ data = {u'requiredNoDefault': (EmptyTestValue,),
+ u'requiredDefault': (EmptyTestValue,),
+ u'unusualMissingValue': (EmptyTestValue,),
+ u'constrained': (EmptyTestValue,)}
+ self._mapping = data
+
+ def _changed(self):
+ if self.annotations is not None:
+ self.annotations[TestKey] = self._mapping
+ self.annotations = None
+
+ requiredNoDefault = ScalarProperty(u'requiredNoDefault')
+ requiredDefault = ScalarProperty(u'requiredDefault')
+ unusualMissingValue = ScalarProperty(u'unusualMissingValue')
+ constrained = ScalarProperty(u'constrained')
+
+
class PropFindTests(PlacefulSetup, unittest.TestCase):
def setUp(self):
@@ -105,17 +150,22 @@
'absolute_url', AbsoluteURL)
ztapi.provideView(None, IHTTPRequest, Interface,
'PROPPATCH', proppatch.PROPPATCH)
+ ztapi.browserViewProviding(IText, TextDAVWidget, IDAVWidget)
+ ztapi.browserViewProviding(ISequence, SequenceDAVWidget, IDAVWidget)
ztapi.provideAdapter(IAnnotatable, IAnnotations, AttributeAnnotations)
ztapi.provideAdapter(IAnnotatable, IZopeDublinCore,
ZDCAnnotatableAdapter)
ztapi.provideAdapter(IAnnotatable, IDAVOpaqueNamespaces,
DAVOpaqueNamespacesAdapter)
+ ztapi.provideAdapter(IAnnotatable, ITestSchema, TestSchemaAdapter)
utils = zapi.getGlobalService('Utilities')
directlyProvides(IDAVSchema, IDAVNamespace)
utils.provideUtility(IDAVNamespace, IDAVSchema, 'DAV:')
directlyProvides(IZopeDublinCore, IDAVNamespace)
utils.provideUtility(IDAVNamespace, IZopeDublinCore,
'http://www.purl.org/dc/1.1')
+ directlyProvides(ITestSchema, IDAVNamespace)
+ utils.provideUtility(IDAVNamespace, ITestSchema, TestURI)
self.db = DB()
self.conn = self.db.open()
root = self.conn.root()
@@ -296,20 +346,90 @@
{u'uri://foo': {u'bar': '<bar>spam</bar>'}})
def test_proppatch_failure(self):
- # XXX: This relies on the fact that only opaque properties can be set
- # for now. As soon as registered interfaces support is implemented,
- # this test will need to be rewritten.
expect = self._makePropstat(
('uri://foo',), '<bar xmlns="a0"/>', 424)
expect += self._makePropstat(
- ('http://www.purl.org/dc/1.1',), '<title xmlns="a0"/>', 403)
+ ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
self._checkProppatch(self.zpt,
ns=(('foo', 'uri://foo'), ('DC', 'http://www.purl.org/dc/1.1')),
- set=('<foo:bar>spam</foo:bar>', '<DC:title>Test</DC:title>'),
+ set=('<foo:bar>spam</foo:bar>', '<DC:nonesuch>Test</DC:nonesuch>'),
expect=expect)
self._assertOPropsEqual(self.zpt, {})
+
+ def test_nonexistent_dc(self):
+ expect = self._makePropstat(
+ ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
+ self._checkProppatch(self.zpt,
+ ns=(('DC', 'http://www.purl.org/dc/1.1'),),
+ set=('<DC:nonesuch>Test</DC:nonesuch>',), expect=expect)
+
+ def test_set_readonly(self):
+ expect = self._makePropstat((), '<getcontentlength/>', 409)
+ self._checkProppatch(self.zpt,
+ set=('<getcontentlength>Test</getcontentlength>',), expect=expect)
+
+ def test_remove_readonly(self):
+ expect = self._makePropstat((), '<getcontentlength/>', 409)
+ self._checkProppatch(self.zpt, rm=('<getcontentlength/>',),
+ expect=expect)
-
+ def test_remove_required_no_default(self):
+ testprops = ITestSchema(self.zpt)
+ testprops.requiredNoDefault = u'foo'
+ transaction.commit()
+ expect = self._makePropstat((TestURI,),
+ '<requiredNoDefault xmlns="a0"/>', 409)
+ self._checkProppatch(self.zpt,
+ ns=(('tst', TestURI),), rm=('<tst:requiredNoDefault/>',),
+ expect=expect)
+ self.assertEqual(ITestSchema(self.zpt).requiredNoDefault, u'foo')
+
+ def test_remove_required_default(self):
+ testprops = ITestSchema(self.zpt)
+ testprops.requiredDefault = u'foo'
+ transaction.commit()
+ expect = self._makePropstat((TestURI,),
+ '<requiredDefault xmlns="a0"/>', 200)
+ self._checkProppatch(self.zpt,
+ ns=(('tst', TestURI),), rm=('<tst:requiredDefault/>',),
+ expect=expect)
+ self.assertEqual(testprops.requiredDefault, u'Default Value')
+
+ def test_remove_required_missing_value(self):
+ testprops = ITestSchema(self.zpt)
+ testprops.unusualMissingValue = u'foo'
+ transaction.commit()
+ expect = self._makePropstat((TestURI,),
+ '<unusualMissingValue xmlns="a0"/>', 200)
+ self._checkProppatch(self.zpt,
+ ns=(('tst', TestURI),), rm=('<tst:unusualMissingValue/>',),
+ expect=expect)
+ self.assertEqual(testprops.unusualMissingValue, u'Missing Value')
+
+ def test_set_dctitle(self):
+ dc = IZopeDublinCore(self.zpt)
+ dc.title = u'Test Title'
+ transaction.commit()
+ expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
+ '<title xmlns="a0"/>', 200)
+ self._checkProppatch(self.zpt,
+ ns=(('DC', 'http://www.purl.org/dc/1.1'),),
+ set=('<DC:title>Foo Bar</DC:title>',),
+ expect=expect)
+ self.assertEqual(dc.title, u'Foo Bar')
+
+ def test_set_dcsubjects(self):
+ dc = IZopeDublinCore(self.zpt)
+ dc.subjects = (u'Bla', u'Ble', u'Bli')
+ transaction.commit()
+ expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
+ '<subjects xmlns="a0"/>', 200)
+ self._checkProppatch(self.zpt,
+ ns=(('DC', 'http://www.purl.org/dc/1.1'),),
+ set=('<DC:subjects>Foo, Bar</DC:subjects>',),
+ expect=expect)
+ self.assertEqual(dc.subjects, (u'Foo', u'Bar'))
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(PropFindTests),
Modified: Zope3/trunk/src/zope/app/dav/widget.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/widget.py 2004-10-14 14:22:55 UTC (rev 28187)
+++ Zope3/trunk/src/zope/app/dav/widget.py 2004-10-14 14:27:03 UTC (rev 28188)
@@ -17,16 +17,16 @@
"""
__docformat__ = 'restructuredtext'
+from xml.dom import minidom
+
from zope.app.dav.interfaces import IDAVWidget
from zope.app.dav.interfaces import ITextDAVWidget
from zope.app.dav.interfaces import ISequenceDAVWidget
-from zope.app.form.interfaces import IWidget
-from zope.app.form import Widget
-from zope.component.interfaces import IViewFactory
+from zope.app.form import InputWidget
from zope.interface import implements
-class DAVWidget(Widget):
+class DAVWidget(InputWidget):
implements(IDAVWidget)
@@ -40,7 +40,18 @@
return str(self._data)
def __call__(self):
- return self.getInputValue()
+ return str(self)
+
+ def setRenderedValue(self, value):
+ if isinstance(value, minidom.Node):
+ text = u''
+ for node in value.childNodes:
+ if node.nodeType != node.TEXT_NODE:
+ continue
+ text += node.nodeValue
+ value = text
+
+ super(DAVWidget, self).setRenderedValue(value)
class TextDAVWidget(DAVWidget):
@@ -52,3 +63,6 @@
def __str__(self):
return u', '.join(self._data)
+
+ def getInputValue(self):
+ return [v.strip() for v in self._data.split(',')]
More information about the Zope3-Checkins
mailing list