Set hreferences for calendar items, fixing the PUT and DELETE requests.
This commit is contained in:
parent
9a9342a1bd
commit
a45ca25df9
@ -153,8 +153,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
@check_rights
|
@check_rights
|
||||||
def do_DELETE(self):
|
def do_DELETE(self):
|
||||||
"""Manage DELETE request."""
|
"""Manage DELETE request."""
|
||||||
obj = self.headers.get("If-Match", None)
|
# TODO: Check etag before deleting
|
||||||
answer = xmlutils.delete(obj, self._calendar, self.path)
|
etag = self.headers.get("If-Match", None)
|
||||||
|
answer = xmlutils.delete(self.path, self._calendar)
|
||||||
|
|
||||||
self.send_response(client.NO_CONTENT)
|
self.send_response(client.NO_CONTENT)
|
||||||
self.send_header("Content-Length", len(answer))
|
self.send_header("Content-Length", len(answer))
|
||||||
@ -171,7 +172,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
def do_PROPFIND(self):
|
def do_PROPFIND(self):
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
||||||
answer = xmlutils.propfind(xml_request, self._calendar, self.path)
|
answer = xmlutils.propfind(self.path, xml_request, self._calendar)
|
||||||
|
|
||||||
self.send_response(client.MULTI_STATUS)
|
self.send_response(client.MULTI_STATUS)
|
||||||
self.send_header("DAV", "1, calendar-access")
|
self.send_header("DAV", "1, calendar-access")
|
||||||
@ -182,10 +183,11 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
@check_rights
|
@check_rights
|
||||||
def do_PUT(self):
|
def do_PUT(self):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
|
# TODO: Check etag before putting
|
||||||
|
etag = self.headers.get("If-Match", None)
|
||||||
ical_request = self._decode(
|
ical_request = self._decode(
|
||||||
self.rfile.read(int(self.headers["Content-Length"])))
|
self.rfile.read(int(self.headers["Content-Length"])))
|
||||||
obj = self.headers.get("If-Match", None)
|
xmlutils.put(self.path, ical_request, self._calendar)
|
||||||
xmlutils.put(ical_request, self._calendar, self.path, obj)
|
|
||||||
|
|
||||||
self.send_response(client.CREATED)
|
self.send_response(client.CREATED)
|
||||||
|
|
||||||
@ -193,7 +195,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
|
|||||||
def do_REPORT(self):
|
def do_REPORT(self):
|
||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
|
||||||
answer = xmlutils.report(xml_request, self._calendar, self.path)
|
answer = xmlutils.report(self.path, xml_request, self._calendar)
|
||||||
|
|
||||||
self.send_response(client.MULTI_STATUS)
|
self.send_response(client.MULTI_STATUS)
|
||||||
self.send_header("Content-Length", len(answer))
|
self.send_header("Content-Length", len(answer))
|
||||||
|
194
radicale/ical.py
194
radicale/ical.py
@ -42,67 +42,91 @@ def open(path, mode="r"):
|
|||||||
# pylint: enable-msg=W0622
|
# pylint: enable-msg=W0622
|
||||||
|
|
||||||
|
|
||||||
def serialize(headers=(), timezones=(), events=(), todos=()):
|
def serialize(headers=(), items=()):
|
||||||
items = ["BEGIN:VCALENDAR"]
|
"""Return an iCal text corresponding to given ``headers`` and ``items``."""
|
||||||
for part in (headers, timezones, todos, events):
|
lines = ["BEGIN:VCALENDAR"]
|
||||||
|
for part in (headers, items):
|
||||||
if part:
|
if part:
|
||||||
items.append("\n".join(item.text for item in part))
|
lines.append("\n".join(item.text for item in part))
|
||||||
items.append("END:VCALENDAR")
|
lines.append("END:VCALENDAR")
|
||||||
return "\n".join(items)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
class Header(object):
|
class Item(object):
|
||||||
"""Internal header class."""
|
"""Internal iCal item."""
|
||||||
def __init__(self, text):
|
def __init__(self, text, name=None):
|
||||||
"""Initialize header from ``text``."""
|
"""Initialize object from ``text`` and different ``kwargs``."""
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
# We must synchronize the name in the text and in the object.
|
||||||
|
# An item must have a name, determined in order by:
|
||||||
|
#
|
||||||
|
# - the ``name`` parameter
|
||||||
|
# - the ``X-RADICALE-NAME`` iCal property (for Events and Todos)
|
||||||
|
# - the ``UID`` iCal property (for Events and Todos)
|
||||||
|
# - the ``TZID`` iCal property (for Timezones)
|
||||||
|
if not self._name:
|
||||||
|
for line in self.text.splitlines():
|
||||||
|
if line.startswith("X-RADICALE-NAME:"):
|
||||||
|
self._name = line.replace("X-RADICALE-NAME:", "").strip()
|
||||||
|
break
|
||||||
|
elif line.startswith("TZID:"):
|
||||||
|
self._name = line.replace("TZID:", "").strip()
|
||||||
|
break
|
||||||
|
elif line.startswith("UID:"):
|
||||||
|
self._name = line.replace("UID:", "").strip()
|
||||||
|
# Do not break, a ``X-RADICALE-NAME`` can appear next
|
||||||
|
|
||||||
class Event(object):
|
if "\nX-RADICALE-NAME:" in text:
|
||||||
"""Internal event class."""
|
for line in self.text.splitlines():
|
||||||
tag = "VEVENT"
|
if line.startswith("X-RADICALE-NAME:"):
|
||||||
|
self.text = self.text.replace(
|
||||||
def __init__(self, text):
|
line, "X-RADICALE-NAME:%s" % self._name)
|
||||||
"""Initialize event from ``text``."""
|
else:
|
||||||
self.text = text
|
self.text = self.text.replace(
|
||||||
|
"\nUID:", "\nX-RADICALE-NAME:%s\nUID:" % self._name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def etag(self):
|
def etag(self):
|
||||||
"""Etag from event."""
|
"""Item etag.
|
||||||
|
|
||||||
|
Etag is mainly used to know if an item has changed.
|
||||||
|
|
||||||
|
"""
|
||||||
return '"%s"' % hash(self.text)
|
return '"%s"' % hash(self.text)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Item name.
|
||||||
|
|
||||||
class Todo(object):
|
Name is mainly used to give an URL to the item.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
|
class Header(Item):
|
||||||
|
"""Internal header class."""
|
||||||
|
|
||||||
|
|
||||||
|
class Event(Item):
|
||||||
|
"""Internal event class."""
|
||||||
|
tag = "VEVENT"
|
||||||
|
|
||||||
|
|
||||||
|
class Todo(Item):
|
||||||
"""Internal todo class."""
|
"""Internal todo class."""
|
||||||
# This is not a TODO!
|
# This is not a TODO!
|
||||||
# pylint: disable-msg=W0511
|
# pylint: disable-msg=W0511
|
||||||
tag = "VTODO"
|
tag = "VTODO"
|
||||||
# pylint: enable-msg=W0511
|
# pylint: enable-msg=W0511
|
||||||
|
|
||||||
def __init__(self, text):
|
|
||||||
"""Initialize todo from ``text``."""
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
@property
|
class Timezone(Item):
|
||||||
def etag(self):
|
|
||||||
"""Etag from todo."""
|
|
||||||
return '"%s"' % hash(self.text)
|
|
||||||
|
|
||||||
|
|
||||||
class Timezone(object):
|
|
||||||
"""Internal timezone class."""
|
"""Internal timezone class."""
|
||||||
tag = "VTIMEZONE"
|
tag = "VTIMEZONE"
|
||||||
|
|
||||||
def __init__(self, text):
|
|
||||||
"""Initialize timezone from ``text``."""
|
|
||||||
lines = text.splitlines()
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("TZID:"):
|
|
||||||
self.name = line.replace("TZID:", "")
|
|
||||||
break
|
|
||||||
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
|
|
||||||
class Calendar(object):
|
class Calendar(object):
|
||||||
"""Internal calendar class."""
|
"""Internal calendar class."""
|
||||||
@ -115,81 +139,84 @@ class Calendar(object):
|
|||||||
self.ctag = self.etag
|
self.ctag = self.etag
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(text, obj):
|
def _parse(text, item_types, name=None):
|
||||||
"""Find ``obj.tag`` items in ``text`` text.
|
"""Find items with type in ``item_types`` in ``text`` text.
|
||||||
|
|
||||||
Return a list of items of type ``obj``.
|
If ``name`` is given, give this name to new items in ``text``.
|
||||||
|
|
||||||
|
Return a list of items.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
item_tags = {}
|
||||||
|
for item_type in item_types:
|
||||||
|
item_tags[item_type.tag] = item_type
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
lines = text.splitlines()
|
lines = text.splitlines()
|
||||||
in_item = False
|
in_item = False
|
||||||
item_lines = []
|
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith("BEGIN:%s" % obj.tag):
|
if line.startswith("BEGIN:") and not in_item:
|
||||||
|
item_tag = line.replace("BEGIN:", "").strip()
|
||||||
|
if item_tag in item_tags:
|
||||||
in_item = True
|
in_item = True
|
||||||
item_lines = []
|
item_lines = []
|
||||||
|
|
||||||
if in_item:
|
if in_item:
|
||||||
item_lines.append(line)
|
item_lines.append(line)
|
||||||
if line.startswith("END:%s" % obj.tag):
|
if line.startswith("END:%s" % item_tag):
|
||||||
items.append(obj("\n".join(item_lines)))
|
in_item = False
|
||||||
|
item_type = item_tags[item_tag]
|
||||||
|
item_text = "\n".join(item_lines)
|
||||||
|
item_name = None if item_tag == "VTIMEZONE" else name
|
||||||
|
items.append(item_type(item_text, item_name))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def append(self, text):
|
def append(self, name, text):
|
||||||
"""Append ``text`` to calendar."""
|
"""Append items from ``text`` to calendar.
|
||||||
|
|
||||||
|
If ``name`` is given, give this name to new items in ``text``.
|
||||||
|
|
||||||
|
"""
|
||||||
self.ctag = self.etag
|
self.ctag = self.etag
|
||||||
|
|
||||||
timezones = self.timezones
|
items = self.items
|
||||||
events = self.events
|
|
||||||
todos = self.todos
|
|
||||||
|
|
||||||
for new_timezone in self._parse(text, Timezone):
|
for new_item in self._parse(text, (Timezone, Event, Todo), name):
|
||||||
if new_timezone.name not in [timezone.name
|
if new_item.name not in (item.name for item in items):
|
||||||
for timezone in timezones]:
|
items.append(new_item)
|
||||||
timezones.append(new_timezone)
|
|
||||||
|
|
||||||
for new_event in self._parse(text, Event):
|
self.write(items=items)
|
||||||
if new_event.etag not in [event.etag for event in events]:
|
|
||||||
events.append(new_event)
|
|
||||||
|
|
||||||
for new_todo in self._parse(text, Todo):
|
def remove(self, name):
|
||||||
if new_todo.etag not in [todo.etag for todo in todos]:
|
"""Remove object named ``name`` from calendar."""
|
||||||
todos.append(new_todo)
|
|
||||||
|
|
||||||
self.write(timezones=timezones, events=events, todos=todos)
|
|
||||||
|
|
||||||
def remove(self, etag):
|
|
||||||
"""Remove object named ``etag`` from the calendar."""
|
|
||||||
self.ctag = self.etag
|
self.ctag = self.etag
|
||||||
todos = [todo for todo in self.todos if todo.etag != etag]
|
todos = [todo for todo in self.todos if todo.name != name]
|
||||||
events = [event for event in self.events if event.etag != etag]
|
events = [event for event in self.events if event.name != name]
|
||||||
|
|
||||||
self.write(todos=todos, events=events)
|
items = self.timezones + todos + events
|
||||||
|
self.write(items=items)
|
||||||
|
|
||||||
def replace(self, etag, text):
|
def replace(self, name, text):
|
||||||
"""Replace objet named ``etag`` by ``text`` in the calendar."""
|
"""Replace content by ``text`` in objet named ``name`` in calendar."""
|
||||||
self.ctag = self.etag
|
self.ctag = self.etag
|
||||||
self.remove(etag)
|
self.remove(name)
|
||||||
self.append(text)
|
self.append(name, text)
|
||||||
|
|
||||||
def write(self, headers=None, timezones=None, events=None, todos=None):
|
def write(self, headers=None, items=None):
|
||||||
"""Write calendar with given parameters."""
|
"""Write calendar with given parameters."""
|
||||||
headers = headers or self.headers or (
|
headers = headers or self.headers or (
|
||||||
Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
|
Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
|
||||||
Header("VERSION:2.0"))
|
Header("VERSION:2.0"))
|
||||||
timezones = timezones or self.timezones
|
items = items or self.items
|
||||||
events = events or self.events
|
|
||||||
todos = todos or self.todos
|
|
||||||
|
|
||||||
# Create folder if absent
|
# Create folder if absent
|
||||||
if not os.path.exists(os.path.dirname(self.path)):
|
if not os.path.exists(os.path.dirname(self.path)):
|
||||||
os.makedirs(os.path.dirname(self.path))
|
os.makedirs(os.path.dirname(self.path))
|
||||||
|
|
||||||
text = serialize(headers, timezones, events, todos)
|
text = serialize(headers, items)
|
||||||
return open(self.path, "w").write(text)
|
return open(self.path, "w").write(text)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -220,17 +247,22 @@ class Calendar(object):
|
|||||||
|
|
||||||
return header_lines
|
return header_lines
|
||||||
|
|
||||||
|
@property
|
||||||
|
def items(self):
|
||||||
|
"""Get list of all items in calendar."""
|
||||||
|
return self._parse(self.text, (Event, Todo, Timezone))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def events(self):
|
def events(self):
|
||||||
"""Get list of ``Event`` items in calendar."""
|
"""Get list of ``Event`` items in calendar."""
|
||||||
return self._parse(self.text, Event)
|
return self._parse(self.text, (Event,))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def todos(self):
|
def todos(self):
|
||||||
"""Get list of ``Todo`` items in calendar."""
|
"""Get list of ``Todo`` items in calendar."""
|
||||||
return self._parse(self.text, Todo)
|
return self._parse(self.text, (Todo,))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timezones(self):
|
def timezones(self):
|
||||||
"""Get list of ``Timezome`` items in calendar."""
|
"""Get list of ``Timezome`` items in calendar."""
|
||||||
return self._parse(self.text, Timezone)
|
return self._parse(self.text, (Timezone,))
|
||||||
|
@ -50,14 +50,19 @@ def _response(code):
|
|||||||
return "HTTP/1.1 %i %s" % (code, client.responses[code])
|
return "HTTP/1.1 %i %s" % (code, client.responses[code])
|
||||||
|
|
||||||
|
|
||||||
def delete(obj, calendar, url):
|
def _name_from_path(path):
|
||||||
|
"""Return Radicale item name from ``path``."""
|
||||||
|
return path.split("/")[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def delete(path, calendar):
|
||||||
"""Read and answer DELETE requests.
|
"""Read and answer DELETE requests.
|
||||||
|
|
||||||
Read rfc4918-9.6 for info.
|
Read rfc4918-9.6 for info.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Reading request
|
# Reading request
|
||||||
calendar.remove(obj)
|
calendar.remove(_name_from_path(path))
|
||||||
|
|
||||||
# Writing answer
|
# Writing answer
|
||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
@ -65,7 +70,7 @@ def delete(obj, calendar, url):
|
|||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
href = ET.Element(_tag("D", "href"))
|
href = ET.Element(_tag("D", "href"))
|
||||||
href.text = url
|
href.text = path
|
||||||
response.append(href)
|
response.append(href)
|
||||||
|
|
||||||
status = ET.Element(_tag("D", "status"))
|
status = ET.Element(_tag("D", "status"))
|
||||||
@ -74,7 +79,8 @@ def delete(obj, calendar, url):
|
|||||||
|
|
||||||
return ET.tostring(multistatus, config.get("encoding", "request"))
|
return ET.tostring(multistatus, config.get("encoding", "request"))
|
||||||
|
|
||||||
def propfind(xml_request, calendar, url):
|
|
||||||
|
def propfind(path, xml_request, calendar):
|
||||||
"""Read and answer PROPFIND requests.
|
"""Read and answer PROPFIND requests.
|
||||||
|
|
||||||
Read rfc4918-9.1 for info.
|
Read rfc4918-9.1 for info.
|
||||||
@ -93,7 +99,7 @@ def propfind(xml_request, calendar, url):
|
|||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
href = ET.Element(_tag("D", "href"))
|
href = ET.Element(_tag("D", "href"))
|
||||||
href.text = url
|
href.text = path
|
||||||
response.append(href)
|
response.append(href)
|
||||||
|
|
||||||
propstat = ET.Element(_tag("D", "propstat"))
|
propstat = ET.Element(_tag("D", "propstat"))
|
||||||
@ -133,17 +139,19 @@ def propfind(xml_request, calendar, url):
|
|||||||
|
|
||||||
return ET.tostring(multistatus, config.get("encoding", "request"))
|
return ET.tostring(multistatus, config.get("encoding", "request"))
|
||||||
|
|
||||||
def put(ical_request, calendar, url, obj):
|
|
||||||
"""Read PUT requests."""
|
|
||||||
# TODO: use url to set hreference
|
|
||||||
if obj:
|
|
||||||
# PUT is modifying obj
|
|
||||||
calendar.replace(obj, ical_request)
|
|
||||||
else:
|
|
||||||
# PUT is adding a new object
|
|
||||||
calendar.append(ical_request)
|
|
||||||
|
|
||||||
def report(xml_request, calendar, url):
|
def put(path, ical_request, calendar):
|
||||||
|
"""Read PUT requests."""
|
||||||
|
name = _name_from_path(path)
|
||||||
|
if name in (item.name for item in calendar.items):
|
||||||
|
# PUT is modifying an existing item
|
||||||
|
calendar.replace(name, ical_request)
|
||||||
|
else:
|
||||||
|
# PUT is adding a new item
|
||||||
|
calendar.append(name, ical_request)
|
||||||
|
|
||||||
|
|
||||||
|
def report(path, xml_request, calendar):
|
||||||
"""Read and answer REPORT requests.
|
"""Read and answer REPORT requests.
|
||||||
|
|
||||||
Read rfc3253-3.6 for info.
|
Read rfc3253-3.6 for info.
|
||||||
@ -158,41 +166,45 @@ def report(xml_request, calendar, url):
|
|||||||
|
|
||||||
if root.tag == _tag("C", "calendar-multiget"):
|
if root.tag == _tag("C", "calendar-multiget"):
|
||||||
# Read rfc4791-7.9 for info
|
# Read rfc4791-7.9 for info
|
||||||
hreferences = set([href_element.text for href_element
|
hreferences = set((href_element.text for href_element
|
||||||
in root.findall(_tag("D", "href"))])
|
in root.findall(_tag("D", "href"))))
|
||||||
else:
|
else:
|
||||||
hreferences = [url]
|
hreferences = (path,)
|
||||||
|
|
||||||
# Writing answer
|
# Writing answer
|
||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
|
|
||||||
# TODO: WTF, sunbird needs one response by object,
|
|
||||||
# is that really what is needed?
|
|
||||||
# Read rfc4791-9.[6|10] for info
|
|
||||||
for hreference in hreferences:
|
for hreference in hreferences:
|
||||||
objects = calendar.events + calendar.todos
|
# Check if the reference is an item or a calendar
|
||||||
|
name = hreference.split("/")[-1]
|
||||||
|
if name:
|
||||||
|
# Reference is an item
|
||||||
|
path = "/".join(hreference.split("/")[:-1]) + "/"
|
||||||
|
items = (item for item in calendar.items if item.name == name)
|
||||||
|
else:
|
||||||
|
# Reference is a calendar
|
||||||
|
path = hreference
|
||||||
|
items = calendar.events + calendar.todos
|
||||||
|
|
||||||
if not objects:
|
if not items:
|
||||||
# TODO: Read rfc4791-9.[6|10] to find a right answer
|
# TODO: Read rfc4791-9.[6|10] to find a right answer
|
||||||
response = ET.Element(_tag("D", "response"))
|
response = ET.Element(_tag("D", "response"))
|
||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
href = ET.Element(_tag("D", "href"))
|
href = ET.Element(_tag("D", "href"))
|
||||||
href.text = url
|
href.text = path
|
||||||
response.append(href)
|
response.append(href)
|
||||||
|
|
||||||
status = ET.Element(_tag("D", "status"))
|
status = ET.Element(_tag("D", "status"))
|
||||||
status.text = _response(204)
|
status.text = _response(204)
|
||||||
response.append(status)
|
response.append(status)
|
||||||
|
|
||||||
for obj in objects:
|
for item in items:
|
||||||
# TODO: Use the hreference to read data and create href.text
|
|
||||||
# We assume here that hreference is url
|
|
||||||
response = ET.Element(_tag("D", "response"))
|
response = ET.Element(_tag("D", "response"))
|
||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
href = ET.Element(_tag("D", "href"))
|
href = ET.Element(_tag("D", "href"))
|
||||||
href.text = url
|
href.text = path + item.name
|
||||||
response.append(href)
|
response.append(href)
|
||||||
|
|
||||||
propstat = ET.Element(_tag("D", "propstat"))
|
propstat = ET.Element(_tag("D", "propstat"))
|
||||||
@ -203,17 +215,17 @@ def report(xml_request, calendar, url):
|
|||||||
|
|
||||||
if _tag("D", "getetag") in props:
|
if _tag("D", "getetag") in props:
|
||||||
element = ET.Element(_tag("D", "getetag"))
|
element = ET.Element(_tag("D", "getetag"))
|
||||||
element.text = obj.etag
|
element.text = item.etag
|
||||||
prop.append(element)
|
prop.append(element)
|
||||||
|
|
||||||
if _tag("C", "calendar-data") in props:
|
if _tag("C", "calendar-data") in props:
|
||||||
element = ET.Element(_tag("C", "calendar-data"))
|
element = ET.Element(_tag("C", "calendar-data"))
|
||||||
if isinstance(obj, ical.Event):
|
if isinstance(item, ical.Event):
|
||||||
element.text = ical.serialize(
|
element.text = ical.serialize(
|
||||||
calendar.headers, calendar.timezones, events=[obj])
|
calendar.headers, calendar.timezones + [item])
|
||||||
elif isinstance(obj, ical.Todo):
|
elif isinstance(item, ical.Todo):
|
||||||
element.text = ical.serialize(
|
element.text = ical.serialize(
|
||||||
calendar.headers, calendar.timezones, todos=[obj])
|
calendar.headers, calendar.timezones + [item])
|
||||||
prop.append(element)
|
prop.append(element)
|
||||||
|
|
||||||
status = ET.Element(_tag("D", "status"))
|
status = ET.Element(_tag("D", "status"))
|
||||||
|
Loading…
Reference in New Issue
Block a user