[Checkins] SVN: zope.webdav/trunk/src/zope/webdav/ Fixed bug in rendering the DAV:lockdiscovery property. We were expecting a

Michael Kerrin michael.kerrin at openapp.biz
Fri Apr 6 07:29:33 EDT 2007


Log message for revision 74032:
  Fixed bug in rendering the DAV:lockdiscovery property. We were expecting a
  value for the owner XML element but under certain circumstances it can be
  omitted.
  

Changed:
  U   zope.webdav/trunk/src/zope/webdav/coreproperties.py
  U   zope.webdav/trunk/src/zope/webdav/locking.txt
  U   zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
  U   zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
  U   zope.webdav/trunk/src/zope/webdav/widgets.py

-=-
Modified: zope.webdav/trunk/src/zope/webdav/coreproperties.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/coreproperties.py	2007-04-05 20:14:40 UTC (rev 74031)
+++ zope.webdav/trunk/src/zope/webdav/coreproperties.py	2007-04-06 11:29:31 UTC (rev 74032)
@@ -132,13 +132,17 @@
     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.""")
+                          shared lock.""",
+        required = True,
+        readonly = True)
 
     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.""")
+                          write lock.""",
+        required = True,
+        readonly = True)
 
 
 class IActiveLock(ILockEntry):
@@ -279,6 +283,95 @@
     """
 
 
+class LockdiscoveryDAVWidget(zope.webdav.widgets.ListDAVWidget):
+    """
+    Custom widget for the `{DAV:}lockdiscovery` property. This is basically
+    a list widget but it doesn't display any sub XML element whose value
+    is equal to its field missing_value.
+
+      >>> import zope.schema.interfaces
+      >>> from zope.webdav.tests import test_widgets
+
+    Setup some adapters for rendering the widget.
+
+      >>> gsm = component.getGlobalSiteManager()
+      >>> gsm.registerAdapter(zope.webdav.widgets.TextDAVWidget,
+      ...                     (zope.schema.interfaces.IText, None))
+      >>> gsm.registerAdapter(zope.webdav.widgets.IntDAVWidget,
+      ...                     (zope.schema.interfaces.IInt, None))
+
+    Setup a field and test object to render. While this is not the same field
+    as the `{DAV:}lockdiscovery` property but it is lot easier to work with
+    during the tests.
+
+      >>> field = schema.List(__name__ = 'testelement',
+      ...    title = u'Test field',
+      ...    value_type = schema.Object(
+      ...        __name__ = u'testsubelement',
+      ...        title = u'Test sub element',
+      ...        schema = test_widgets.ISimpleInterface))
+      >>> objectvalue = test_widgets.SimpleObject(name = None, age = 26)
+
+      >>> widget = LockdiscoveryDAVWidget(field, None)
+      >>> widget.namespace = 'DAV:'
+      >>> widget.setRenderedValue([objectvalue])
+
+    The objectvalue name is None which is equal the Text field missing_value
+    so the name sub XML element doesn't show up.
+
+      >>> print etree.tostring(widget.render()) #doctest:+XMLDATA
+      <testelement xmlns='DAV:'>
+        <testsubelement>
+          <age>26</age>
+        </testsubelement>
+      </testelement>
+
+    By setting the name attribute it now shows up in the output.
+
+      >>> objectvalue.name = u'Michael Kerrin'
+      >>> print etree.tostring(widget.render()) #doctest:+XMLDATA
+      <testelement xmlns='DAV:'>
+        <testsubelement>
+          <name>Michael Kerrin</name>
+          <age>26</age>
+        </testsubelement>
+      </testelement>
+
+    But the content object needs to be locked for the `{DAV:}lockdiscovery`
+    element to have a value.
+
+      >>> widget.setRenderedValue(None)
+      >>> print etree.tostring(widget.render()) #doctest:+XMLDATA
+      <testelement xmlns='DAV:' />
+
+    Clean up the component registration.
+
+      >>> gsm.unregisterAdapter(zope.webdav.widgets.TextDAVWidget,
+      ...                       (zope.schema.interfaces.IText, None))
+      True
+      >>> gsm.unregisterAdapter(zope.webdav.widgets.IntDAVWidget,
+      ...                       (zope.schema.interfaces.IInt, None))
+      True
+
+    """
+    interface.classProvides(zope.webdav.interfaces.IIDAVWidget)
+
+    def render(self):
+        etree = zope.etree.getEngine()
+        el = etree.Element(etree.QName(self.namespace, self.name))
+
+        if self._value is not self.context.missing_value:
+            for value in self._value:
+                widget = zope.webdav.widgets.ObjectDAVWidget(
+                    self.context.value_type, self.request)
+                widget.render_missing_values = False
+                widget.setRenderedValue(value)
+                widget.namespace = self.namespace
+                el.append(widget.render())
+
+        return el
+
+
 ################################################################################
 #
 # Collection of default properties has defined in Section 15.
@@ -305,6 +398,7 @@
 resourcetype = DAVProperty("{DAV:}resourcetype", IDAVResourcetype)
 
 lockdiscovery = DAVProperty("{DAV:}lockdiscovery", IDAVLockdiscovery)
+lockdiscovery.custom_widget = LockdiscoveryDAVWidget
 
 supportedlock = DAVProperty("{DAV:}supportedlock", IDAVSupportedlock)
 

Modified: zope.webdav/trunk/src/zope/webdav/locking.txt
===================================================================
--- zope.webdav/trunk/src/zope/webdav/locking.txt	2007-04-05 20:14:40 UTC (rev 74031)
+++ zope.webdav/trunk/src/zope/webdav/locking.txt	2007-04-06 11:29:31 UTC (rev 74032)
@@ -351,7 +351,10 @@
   ...        return self.data['depth']
   ...    @property
   ...    def owner(self):
-  ...        return self.data['owner'].replace('\n', '')
+  ...        value = self.data['owner']
+  ...        if value:
+  ...            return value.replace('\n', '')
+  ...        return None
   ...    @property
   ...    def timeout(self):
   ...        return "Second-%d" % self.data['duration'].seconds
@@ -410,11 +413,46 @@
 of application/xml and the body should be a rendering of the
 `{DAV:}lockdiscovery` property.
 
+First we note that the owner element is optional.
+
   >>> request = TestWebDAVRequest(
   ...    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:lockinfo>""")
+  >>> respbody = LOCK(resource, request).LOCK()
+  Locked the resource.
+  >>> print respbody #doctest:+XMLDATA
+  <prop xmlns="DAV:">
+    <lockdiscovery>
+      <activelock>
+        <lockscope><exclusive /></lockscope>
+        <locktype><write /></locktype>
+        <depth>infinity</depth>
+        <timeout>Second-720</timeout>
+        <locktoken><href>urn:resourcelocktoken</href></locktoken>
+        <lockroot>http://localhost/resource</lockroot>
+      </activelock>
+    </lockdiscovery>
+  </prop>
+  >>> request.response.getStatus()
+  200
+  >>> request.response.getHeader("Content-type")
+  'application/xml'
+  >>> request.response.getHeader("Lock-token")
+  '<urn:resourcelocktoken>'
+
+Unlock the resource and try again with the owner element included in the
+request.
+
+  >>> resource._lockinfo = None
+
+  >>> request = TestWebDAVRequest(
+  ...    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>
@@ -512,6 +550,10 @@
   'write'
   >>> resource._lockinfo['depth']
   'infinity'
+  >>> print resource._lockinfo['owner'] #doctest:+XMLDATA
+  <owner xmlns="DAV:">
+    <href>http://example.org/~ejw/contact.html</href>
+  </owner>
   >>> print respbody #doctest:+XMLDATA
   <prop xmlns="DAV:">
     <lockdiscovery>

Modified: zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py	2007-04-05 20:14:40 UTC (rev 74031)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_doctests.py	2007-04-06 11:29:31 UTC (rev 74032)
@@ -195,7 +195,10 @@
                              checker = zope.etree.testing.xmlOutputChecker,
                              setUp = zope.etree.testing.etreeSetup,
                              tearDown = zope.etree.testing.etreeTearDown),
-        doctest.DocTestSuite("zope.webdav.coreproperties"),
+        doctest.DocTestSuite("zope.webdav.coreproperties",
+                             checker = zope.etree.testing.xmlOutputChecker,
+                             setUp = zope.etree.testing.etreeSetup,
+                             tearDown = zope.etree.testing.etreeTearDown),
         doctest.DocFileSuite("datamodel.txt", package = "zope.webdav",
                              checker = zope.etree.testing.xmlOutputChecker,
                              setUp = zope.etree.testing.etreeSetup,

Modified: zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py	2007-04-05 20:14:40 UTC (rev 74031)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_widgets.py	2007-04-06 11:29:31 UTC (rev 74032)
@@ -22,7 +22,7 @@
 
 from zope import schema
 from zope import component
-from zope.schema.interfaces import ITextLine
+import zope.schema.interfaces
 from zope.interface import Interface, implements
 from zope.interface.verify import verifyObject
 from zope.datetime import tzinfo
@@ -97,8 +97,8 @@
         self.setUpWidget(element)
 
     def setUpWidget(self, element = None):
-        request = TestWebDAVRequest(element)
-        self.widget = self._WidgetFactory(self.field, request)
+        self.request = TestWebDAVRequest(element)
+        self.widget = self._WidgetFactory(self.field, self.request)
         self.widget.namespace = self.namespace
 
 
@@ -268,7 +268,8 @@
         self.etree = etreeSetup()
         component.getGlobalSiteManager().registerAdapter(
             widgets.TextDAVWidget,
-            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+            (zope.schema.interfaces.ITextLine,
+             zope.webdav.interfaces.IWebDAVRequest))
 
         foofield = schema.List(__name__ = self.name,
                                title = u"Foo Title",
@@ -293,27 +294,40 @@
     def tearDown(self):
         component.getGlobalSiteManager().unregisterAdapter(
             widgets.TextDAVWidget,
-            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+            (zope.schema.interfaces.ITextLine,
+             zope.webdav.interfaces.IWebDAVRequest))
         super(ListTextWebDAVWidgetTest, self).tearDown()
 
 
+class ISimpleInterface(Interface):
+    name = schema.TextLine(
+        title = u"Name subproperty",
+        description = u"",
+        required = False)
+
+    age = schema.Int(
+        title = u"Age subproject",
+        description = u"",
+        required = True)
+
+
+class SimpleObject(object):
+    implements(ISimpleInterface)
+
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+
 class ObjectDAVWidgetTest(WebDAVWidgetTest):
 
     _WidgetFactory = widgets.ObjectDAVWidget
 
-    rendered_content = '<ns0:name>Michael Kerrin</ns0:name>'
+    rendered_content = """<ns0:name>Michael Kerrin</ns0:name>
+                          <ns0:age>26</ns0:age>"""
 
     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",
@@ -324,7 +338,8 @@
         class DemoContent(object):
             implements(IDemoContent)
 
-        self.field_content = SimpleObject()
+        self.field_content = SimpleObject(name = u"Michael Kerrin",
+                                          age = 26)
         self.content = DemoContent()
         field = IDemoContent['foo']
         self.field = field.bind(self.content)
@@ -332,16 +347,111 @@
 
         component.getGlobalSiteManager().registerAdapter(
             widgets.TextDAVWidget,
-            (ITextLine,
+            (zope.schema.interfaces.ITextLine,
              zope.webdav.interfaces.IWebDAVRequest))
+        component.getGlobalSiteManager().registerAdapter(
+            widgets.IntDAVWidget,
+            (zope.schema.interfaces.IInt,
+             zope.webdav.interfaces.IWebDAVRequest))
 
     def tearDown(self):
         component.getGlobalSiteManager().unregisterAdapter(
             widgets.TextDAVWidget,
-            (ITextLine, zope.webdav.interfaces.IWebDAVRequest))
+            (zope.schema.interfaces.ITextLine,
+             zope.webdav.interfaces.IWebDAVRequest))
+        component.getGlobalSiteManager().unregisterAdapter(
+            widgets.IntDAVWidget,
+            (zope.schema.interfaces.IInt,
+             zope.webdav.interfaces.IWebDAVRequest))
         super(ObjectDAVWidgetTest, self).tearDown()
 
+    def test_default_render_missing_values(self):
+        widget = self._WidgetFactory(self.field, self.request)
+        self.assertEqual(widget.render_missing_values, True)
 
+    def test_renderMissingAttribute(self):
+        content = SimpleObject(name = u"Michael Kerrin")
+
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.namespace = self.namespace
+        widget.setRenderedValue(content)
+
+        self.assertRaises(AttributeError, widget.render)
+
+    def test_renderMissingFieldValue(self):
+        ## In this case the name attribute (which is not required) is equal
+        ## to the missing_value. The ObjectDAVWidget view still renders this
+        ## elements because the render_missing_values is set to True.
+        content = SimpleObject(name = None, age = 26)
+
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.namespace = self.namespace
+        widget.setRenderedValue(content)
+
+        self.assertEqual(widget.render_missing_values, True)
+
+        element = widget.render()
+        assertXMLEqual(element, """<ns0:foo xmlns:ns0="testns:">
+          <ns0:name />
+          <ns0:age>26</ns0:age>
+        </ns0:foo>""")
+
+    def test_dontRenderMissingFieldValue(self):
+        ## In this case the name attribute (which is not required) is equal
+        ## to the missing_value and because the render_missing_values
+        ## attribute is False we don't render this XML element.
+        content = SimpleObject(name = None, age = 26)
+
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.render_missing_values = False
+        widget.namespace = self.namespace
+        widget.setRenderedValue(content)
+
+        self.assertEqual(widget.render_missing_values, False)
+
+        element = widget.render()
+        assertXMLEqual(element, """<ns0:foo xmlns:ns0="testns:">
+          <ns0:age>26</ns0:age>
+        </ns0:foo>""")
+
+    def test_renderMissingRequiredFieldValue(self):
+        ## In this case the age attribute (which is required) is equal
+        ## to the missing value so the ObjectDAVWidget should try and
+        ## render it.
+        content = SimpleObject(name = u"Michael Kerrin", age = None)
+
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.namespace = self.namespace
+        widget.setRenderedValue(content)
+
+        self.assertEqual(widget.render_missing_values, True)
+
+        element = widget.render()
+        assertXMLEqual(element, """<ns0:foo xmlns:ns0="testns:">
+          <ns0:name>Michael Kerrin</ns0:name>
+          <ns0:age />
+        </ns0:foo>""")
+
+    def test_dontRenderMissingRequiredFieldValue(self):
+        ## In this case the age attribute (which is required) is equal
+        ## to the missing value so the ObjectDAVWidget should try and
+        ## render it, even since the render_missing_values is False.
+        content = SimpleObject(name = u"Michael Kerrin", age = None)
+
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.render_missing_values = False
+        widget.namespace = self.namespace
+        widget.setRenderedValue(content)
+
+        self.assertEqual(widget.render_missing_values, False)
+
+        element = widget.render()
+        assertXMLEqual(element, """<ns0:foo xmlns:ns0="testns:">
+          <ns0:name>Michael Kerrin</ns0:name>
+          <ns0:age />
+        </ns0:foo>""")
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(WebDAVBaseWidgetTest),

Modified: zope.webdav/trunk/src/zope/webdav/widgets.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/widgets.py	2007-04-05 20:14:40 UTC (rev 74031)
+++ zope.webdav/trunk/src/zope/webdav/widgets.py	2007-04-06 11:29:31 UTC (rev 74032)
@@ -124,8 +124,17 @@
 
 
 class ObjectDAVWidget(DAVWidget):
+    """
+    ObjectDAVWidget that will display all properties whether or not the
+    value of specific property in question is the missing value or not.
+
+    `render_missing_values` attribute is a marker to tell webdav to render
+    all fields which instance value is equal to the fields missing_value.
+    """
     interface.classProvides(interfaces.IIDAVWidget)
 
+    render_missing_values = True
+
     def render(self):
         etree = component.getUtility(IEtree)
         el = etree.Element(etree.QName(self.namespace, self.name))
@@ -136,7 +145,15 @@
         interface = self.context.schema
         for name, field in getFieldsInOrder(interface):
             field = field.bind(self._value)
+            field_value = field.get(self._value)
 
+            # Careful this could result in elements not been displayed that
+            # should be. This is tested in test_widgets but it mightened be
+            # what some people think.
+            if field_value == field.missing_value and \
+                   not self.render_missing_values and not field.required:
+                continue
+
             widget = component.getMultiAdapter((field, self.request),
                                                interfaces.IDAVWidget)
             widget.namespace = self.namespace



More information about the Checkins mailing list