=================
WebDAV data model
=================

.. contents::

Introduction
============

The WebDAV data model is the set of properties defined on a resource.
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. A 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.

Live properties are declared in advance by declaring a utility which
implements *z3c.dav.interfaces.IDAVProperty*. *IAVProperty* is just a data
structure containing all the information necessary to process a request.
This information includes:

+ __name__ and namespace - specify the together specify the name of the
  property.

+ iface - a storage interface used to look up an adapter which knows how
  to get or set the value of this property.

+ field - used to validate the data sent through PROPPATCH request.

+ restricted - a boolean indicting if this property should be displayed in
  response to a `allprop` PROPFIND request.

+ custom_widget and custom_input_widget - specifies custom widgets needed
  to render and parse the property. If either value is None then we look up
  an *IDAVWidget* or *IDAVInputWidget* adapter of the field specified in this
  data structure and the request object.

This document will describe how Zope maintains and queries the WebDAV data
model for all content objects, 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 z3c.dav.properties
  >>> import z3c.dav.interfaces
  >>> import z3c.dav.publisher
  >>> import z3c.dav.widgets
  >>> from cStringIO import StringIO

  >>> component.getGlobalSiteManager().registerAdapter(
  ...    z3c.dav.widgets.IntDAVWidget,
  ...    (zope.schema.interfaces.IInt, z3c.dav.interfaces.IWebDAVRequest),
  ...    z3c.dav.interfaces.IDAVWidget)
  >>> component.getGlobalSiteManager().registerAdapter(
  ...    z3c.dav.widgets.TextDAVWidget,
  ...    (zope.schema.interfaces.ITextLine,
  ...     z3c.dav.interfaces.IWebDAVRequest),
  ...    z3c.dav.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 = z3c.dav.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 = z3c.dav.properties.DAVProperty("{examplens:}age",
  ...    IAgeStorage)
  >>> component.getGlobalSiteManager().registerUtility(
  ...    ageProp, z3c.dav.interfaces.IDAVProperty, "{examplens:}age")

It order for it to be defined there must exist a multi-adapter from the
resource, and request 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(z3c.dav.properties.getAllProperties(democontent, request))
  []
  >>> z3c.dav.properties.hasProperty(
  ...    democontent, request, "{examplens:}age")
  False
  >>> z3c.dav.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, z3c.dav.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, z3c.dav.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.

  >>> z3c.dav.properties.hasProperty(
  ...    democontent, request, "{examplens:}age")
  True

So now we can ask the *z3c.dav.interfaces.getProperty* method to return the
*IDAVProperty* utility representing this property and the storage adapter.

  >>> prop, adapter = z3c.dav.properties.getProperty(
  ...   democontent, request, "{examplens:}age")
  >>> print prop #doctest:+ELLIPSIS
  <z3c.dav.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
*z3c.dav.properties.getWidget* method to return a widget that knows how to
render this type of property.

  >>> davwidget = z3c.dav.properties.getWidget(prop, adapter, request)
  >>> print etree.tostring(davwidget.render()) #doctest:+XMLDATA
  <E:age xmlns:E="examplens:">10</E:age>

Finally the *z3c.dav.properties.getAllProperties* method contains one entry:

  >>> ['%s, %s' %(prop.namespace, prop.__name__) for prop, adapter in 
  ...    z3c.dav.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, z3c.dav.interfaces.IWebDAVRequest), IAgeStorage)
  True

  >>> prop, adapter = z3c.dav.properties.getProperty(democontent, request,
  ...    "{examplens:}age")
  Traceback (most recent call last):
  ...
  PropertyNotFound: {examplens:}age

Dead properties
===============

Dead properties are managede through a
*z3c.dav.interfaces.IOpaquePropertyStorage* adapter. This just stores the
data sent through a PROPPATCH request, performing no checks on the value of
the data sent.

Dead properties are handled just slightly differently. We first need to setup
a *z3c.dav.properties.IOpaquePropertyStorage* adapter for our demo content.

  >>> import weakref
  >>> deadprops = weakref.WeakKeyDictionary()
  >>> class DeadProperties(object):
  ...   interface.implements(z3c.dav.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.

  >>> z3c.dav.properties.hasProperty(democontent, request, dpname)
  False
  >>> [prop.__name__ for prop, adapter in
  ...  z3c.dav.properties.getAllProperties(democontent, request)]
  []
  >>> z3c.dav.properties.getProperty(democontent, request, dpname)
  Traceback (most recent call last):
  ...
  PropertyNotFound: {examplens:}deadprop

  >>> component.getGlobalSiteManager().registerAdapter(DeadProperties)

Since we have defined a *z3c.dav.properties.IOpaquePropertyStorage* adapter,
we can now set and get any dead properties.

  >>> prop, adapter = z3c.dav.properties.getProperty(
  ...    democontent, request, dpname)
  >>> prop #doctest:+ELLIPSIS
  <z3c.dav.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 argument `exists` to True.

  >>> z3c.dav.properties.getProperty(
  ...    democontent, request, dpname, exists = True)
  Traceback (most recent call last):
  ...
  PropertyNotFound: {examplens:}deadprop

  >>> list(z3c.dav.properties.getAllProperties(democontent, request))
  []
  >>> z3c.dav.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
  ...  z3c.dav.properties.getAllProperties(democontent, request)]
  ['examplens:, deadprop']
  >>> z3c.dav.properties.hasProperty(democontent, request, dpname)
  True
  >>> prop, adapter = z3c.dav.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 = z3c.dav.properties.DAVProperty(
  ...    "{examplens:}name", INameStorage)
  >>> titleProp = z3c.dav.properties.DAVProperty("{examplens:}title",
  ...    ITitleStorage)
  >>> component.getGlobalSiteManager().registerUtility(
  ...    nameProp, z3c.dav.interfaces.IDAVProperty, "{examplens:}name")
  >>> component.getGlobalSiteManager().registerUtility(
  ...    titleProp, z3c.dav.interfaces.IDAVProperty, "{examplens:}title")

Now write the storage adapter and register with the component architecture.

  >>> class Storage(object):
  ...    component.adapts(IDemoContent, z3c.dav.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 = z3c.dav.properties.getProperty(democontent, request,
  ...    "{examplens:}name")
  >>> titleprop, adapter = z3c.dav.properties.getProperty(
  ...    democontent, request, "{examplens:}title")

  >>> z3c.dav.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, z3c.dav.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)

  >>> z3c.dav.properties.hasProperty(
  ...    democontent, request, "{examplens:}name")
  True
  >>> prop, adapter = z3c.dav.properties.getProperty(democontent, request,
  ...    "{examplens:}name")
  >>> props = [prop for prop, adapter in
  ...          z3c.dav.properties.getAllProperties(democontent, request)]
  >>> props = [prop.__name__ for prop in props]
  >>> props.sort()
  >>> props
  ['deadprop', 'name', 'title']

Cleanup test
============

  >>> component.getGlobalSiteManager().unregisterAdapter(
  ...    z3c.dav.widgets.IntDAVWidget,
  ...    (zope.schema.interfaces.IInt, z3c.dav.interfaces.IWebDAVRequest),
  ...    z3c.dav.interfaces.IDAVWidget)
  True
  >>> component.getGlobalSiteManager().unregisterAdapter(
  ...    z3c.dav.widgets.TextDAVWidget,
  ...    (zope.schema.interfaces.ITextLine,
  ...     z3c.dav.interfaces.IWebDAVRequest),
  ...    z3c.dav.interfaces.IDAVWidget)
  True
  >>> component.getGlobalSiteManager().unregisterUtility(
  ...     ageProp, z3c.dav.interfaces.IDAVProperty, "{examplens:}age")
  True
  >>> component.getGlobalSiteManager().unregisterAdapter(DeadProperties)
  True
