proppatch actually writes properties.
This commit is contained in:
parent
85e283830a
commit
911cd48efe
@ -260,6 +260,10 @@ class Application(object):
|
|||||||
tz = props.get('C:calendar-timezone')
|
tz = props.get('C:calendar-timezone')
|
||||||
if tz:
|
if tz:
|
||||||
calendar.replace('', tz)
|
calendar.replace('', tz)
|
||||||
|
del props['C:calendar-timezone']
|
||||||
|
with calendar.props as calendar_props:
|
||||||
|
for key, value in props.items():
|
||||||
|
calendar_props[key] = value
|
||||||
calendar.write()
|
calendar.write()
|
||||||
return client.CREATED, {}, None
|
return client.CREATED, {}, None
|
||||||
|
|
||||||
@ -283,11 +287,11 @@ class Application(object):
|
|||||||
|
|
||||||
def proppatch(self, environ, calendar, content):
|
def proppatch(self, environ, calendar, content):
|
||||||
"""Manage PROPPATCH request."""
|
"""Manage PROPPATCH request."""
|
||||||
xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
|
answer = xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
|
||||||
headers = {
|
headers = {
|
||||||
"DAV": "1, calendar-access",
|
"DAV": "1, calendar-access",
|
||||||
"Content-Type": "text/xml"}
|
"Content-Type": "text/xml"}
|
||||||
return client.MULTI_STATUS, headers, None
|
return client.MULTI_STATUS, headers, answer
|
||||||
|
|
||||||
def put(self, environ, calendar, content):
|
def put(self, environ, calendar, content):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
|
@ -25,8 +25,10 @@ Define the main classes of a calendar as seen from the server.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import codecs
|
import codecs
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from radicale import config
|
from radicale import config
|
||||||
@ -254,7 +256,9 @@ class Calendar(object):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Calendar name."""
|
"""Calendar name."""
|
||||||
return self.path.split(os.path.sep)[-1]
|
with self.props as props:
|
||||||
|
return props.get('D:displayname',
|
||||||
|
self.path.split(os.path.sep)[-1])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
@ -322,3 +326,17 @@ class Calendar(object):
|
|||||||
|
|
||||||
modification_time = time.gmtime(os.path.getmtime(self.path))
|
modification_time = time.gmtime(os.path.getmtime(self.path))
|
||||||
return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time)
|
return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@contextmanager
|
||||||
|
def props(self):
|
||||||
|
props_path = self.path + '.props'
|
||||||
|
# on enter
|
||||||
|
properties = {}
|
||||||
|
if os.path.exists(props_path):
|
||||||
|
with open(props_path) as prop_file:
|
||||||
|
properties.update(json.load(prop_file))
|
||||||
|
yield properties
|
||||||
|
# on exit
|
||||||
|
with open(props_path, 'w') as prop_file:
|
||||||
|
json.dump(properties, prop_file)
|
||||||
|
@ -93,6 +93,23 @@ def _tag(short_name, local):
|
|||||||
return "{%s}%s" % (NAMESPACES[short_name], local)
|
return "{%s}%s" % (NAMESPACES[short_name], local)
|
||||||
|
|
||||||
|
|
||||||
|
def _tag_from_clark(name):
|
||||||
|
"""For a given name using the XML Clark notation returns a human-readable
|
||||||
|
variant of the tag name for known namespaces. Otherwise returns the name
|
||||||
|
as is.
|
||||||
|
|
||||||
|
"""
|
||||||
|
match = CLARK_TAG_REGEX.match(name)
|
||||||
|
if match and match.group('namespace') in NAMESPACES_REV:
|
||||||
|
args = {
|
||||||
|
'ns': NAMESPACES_REV[match.group('namespace')],
|
||||||
|
'tag': match.group('tag')}
|
||||||
|
tag_name = '%(ns)s:%(tag)s' % args
|
||||||
|
else:
|
||||||
|
tag_name = prop.tag
|
||||||
|
return tag_name
|
||||||
|
|
||||||
|
|
||||||
def _response(code):
|
def _response(code):
|
||||||
"""Return full W3C names from HTTP status codes."""
|
"""Return full W3C names from HTTP status codes."""
|
||||||
return "HTTP/1.1 %i %s" % (code, client.responses[code])
|
return "HTTP/1.1 %i %s" % (code, client.responses[code])
|
||||||
@ -105,28 +122,24 @@ def name_from_path(path, calendar):
|
|||||||
return path_parts[-1] if (len(path_parts) - len(calendar_parts)) else None
|
return path_parts[-1] if (len(path_parts) - len(calendar_parts)) else None
|
||||||
|
|
||||||
|
|
||||||
def props_from_request(xml_request):
|
def props_from_request(root, actions=("set", "remove")):
|
||||||
"""Returns a list of properties as a dictionary."""
|
"""Returns a list of properties as a dictionary."""
|
||||||
|
|
||||||
result = OrderedDict()
|
result = OrderedDict()
|
||||||
root = ET.fromstring(xml_request.encode("utf8"))
|
if not isinstance(root, ET.Element):
|
||||||
|
root = ET.fromstring(root.encode("utf8"))
|
||||||
|
|
||||||
set_element = root.find(_tag("D", "set"))
|
for action in actions:
|
||||||
if not set_element:
|
action_element = root.find(_tag("D", action))
|
||||||
set_element = root
|
if action_element is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
action_element = root
|
||||||
|
|
||||||
prop_element = set_element.find(_tag("D", "prop"))
|
prop_element = action_element.find(_tag("D", "prop"))
|
||||||
if prop_element:
|
if prop_element is not None:
|
||||||
for prop in prop_element:
|
for prop in prop_element:
|
||||||
match = CLARK_TAG_REGEX.match(prop.tag)
|
result[_tag_from_clark(prop.tag)] = prop.text
|
||||||
if match and match.group('namespace') in NAMESPACES_REV:
|
|
||||||
args = {
|
|
||||||
'ns': NAMESPACES_REV[match.group('namespace')],
|
|
||||||
'tag': match.group('tag')}
|
|
||||||
tag_name = '%(ns)s:%(tag)s' % args
|
|
||||||
else:
|
|
||||||
tag_name = prop.tag
|
|
||||||
result[tag_name] = prop.text
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@ -256,6 +269,27 @@ def propfind(path, xml_request, calendar, depth):
|
|||||||
return _pretty_xml(multistatus)
|
return _pretty_xml(multistatus)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_propstat_to(element, tag, status_number):
|
||||||
|
"""Adds a propstat structure to the given element for the
|
||||||
|
following `tag` with the given `status_number`."""
|
||||||
|
propstat = ET.Element(_tag("D", "propstat"))
|
||||||
|
element.append(propstat)
|
||||||
|
|
||||||
|
prop = ET.Element(_tag("D", "prop"))
|
||||||
|
propstat.append(prop)
|
||||||
|
|
||||||
|
if '{' in tag:
|
||||||
|
clark_tag = tag
|
||||||
|
else:
|
||||||
|
clark_tag = _tag(*tag.split(':', 1))
|
||||||
|
prop_tag = ET.Element(clark_tag)
|
||||||
|
prop.append(prop_tag)
|
||||||
|
|
||||||
|
status = ET.Element(_tag("D", "status"))
|
||||||
|
status.text = _response(status_number)
|
||||||
|
propstat.append(status)
|
||||||
|
|
||||||
|
|
||||||
def proppatch(path, xml_request, calendar):
|
def proppatch(path, xml_request, calendar):
|
||||||
"""Read and answer PROPPATCH requests.
|
"""Read and answer PROPPATCH requests.
|
||||||
|
|
||||||
@ -264,13 +298,8 @@ def proppatch(path, xml_request, calendar):
|
|||||||
"""
|
"""
|
||||||
# Reading request
|
# Reading request
|
||||||
root = ET.fromstring(xml_request.encode("utf8"))
|
root = ET.fromstring(xml_request.encode("utf8"))
|
||||||
props = []
|
props_to_set = props_from_request(root, actions=('set',))
|
||||||
|
props_to_remove = props_from_request(root, actions=('remove',))
|
||||||
for action in ("set", "remove"):
|
|
||||||
action_element = root.find(_tag("D", action))
|
|
||||||
if action_element is not None:
|
|
||||||
prop_element = action_element.find(_tag("D", "prop"))
|
|
||||||
props.extend(prop.tag for prop in prop_element)
|
|
||||||
|
|
||||||
# Writing answer
|
# Writing answer
|
||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
@ -282,19 +311,17 @@ def proppatch(path, xml_request, calendar):
|
|||||||
href.text = path
|
href.text = path
|
||||||
response.append(href)
|
response.append(href)
|
||||||
|
|
||||||
propstat = ET.Element(_tag("D", "propstat"))
|
with calendar.props as calendar_props:
|
||||||
response.append(propstat)
|
for short_name, value in props_to_set.items():
|
||||||
|
calendar_props[short_name] = value
|
||||||
prop = ET.Element(_tag("D", "prop"))
|
_add_propstat_to(response, short_name, 200)
|
||||||
propstat.append(prop)
|
for short_name in props_to_remove:
|
||||||
|
try:
|
||||||
for tag in props:
|
del calendar_props[short_name]
|
||||||
element = ET.Element(tag)
|
except KeyError:
|
||||||
prop.append(element)
|
_add_propstat_to(response, short_name, 412)
|
||||||
|
else:
|
||||||
status = ET.Element(_tag("D", "status"))
|
_add_propstat_to(response, short_name, 200)
|
||||||
status.text = _response(200)
|
|
||||||
propstat.append(status)
|
|
||||||
|
|
||||||
return _pretty_xml(multistatus)
|
return _pretty_xml(multistatus)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user