From 21a743fcde3d0a743fd539f8f8c64f10492e06dc Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 10 Feb 2010 18:57:21 +0100 Subject: [PATCH] Code cleaned using Pylint, fixes various minor bugs too. --- radicale.py | 14 +++-- radicale/__init__.py | 25 ++++---- radicale/acl/__init__.py | 3 + radicale/acl/fake.py | 2 +- radicale/acl/htpasswd.py | 32 ++++++---- radicale/calendar.py | 13 +++- radicale/config.py | 39 ++++++------ radicale/ical.py | 72 +++++++++++++--------- radicale/support/__init__.py | 2 + radicale/support/plain.py | 85 ++++++++++++++------------ radicale/xmlutils.py | 113 +++++++++++++++++------------------ 11 files changed, 225 insertions(+), 175 deletions(-) diff --git a/radicale.py b/radicale.py index 6708064..e6a8df0 100755 --- a/radicale.py +++ b/radicale.py @@ -19,16 +19,21 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +# This file is just a script, allow [a-z0-9]* variable names +# pylint: disable-msg=C0103 + +# ``import radicale`` refers to the ``radicale`` module, not ``radicale.py`` +# pylint: disable-msg=W0406 + """ Radicale Server entry point. Launch the Radicale Serve according to configuration and command-line arguments. + """ -# TODO: Manage depth and calendars/collections (see xmlutils) # TODO: Manage smart and configurable logs -# TODO: Manage authentication import os import sys @@ -62,7 +67,7 @@ parser.add_option( "-c", "--certificate", default=radicale.config.get("server", "certificate"), help="certificate file ") -options, args = parser.parse_args() +options = parser.parse_args()[0] # Update Radicale configuration according to options for option in parser.option_list: @@ -79,5 +84,6 @@ if options.daemon: # Launch calendar server server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer -server = server_class((options.host, options.port), radicale.CalendarHTTPHandler) +server = server_class( + (options.host, options.port), radicale.CalendarHTTPHandler) server.serve_forever() diff --git a/radicale/__init__.py b/radicale/__init__.py index 1aa266c..7711fd8 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -33,8 +33,6 @@ should have been included in this package. """ -# TODO: Manage errors (see xmlutils) - import base64 import socket try: @@ -43,9 +41,10 @@ except ImportError: import httplib as client import BaseHTTPServer as server -from radicale import acl, config, support, xmlutils +from radicale import acl, calendar, config, support, xmlutils -def check(request, function): + +def _check(request, function): """Check if user has sufficient rights for performing ``request``.""" authorization = request.headers.get("Authorization", None) if authorization: @@ -64,8 +63,6 @@ def check(request, function): "Basic realm=\"Radicale Server - Password Required\"") request.end_headers() -# Decorator checking rights before performing request -check_rights = lambda function: lambda request: check(request, function) class HTTPServer(server.HTTPServer): """HTTP server.""" @@ -74,6 +71,7 @@ class HTTPServer(server.HTTPServer): server.HTTPServer.__init__(self, address, handler) self.acl = acl.load() + class HTTPSServer(HTTPServer): """HTTPS server.""" def __init__(self, address, handler): @@ -91,10 +89,14 @@ class HTTPSServer(HTTPServer): self.server_bind() self.server_activate() + class CalendarHTTPHandler(server.BaseHTTPRequestHandler): """HTTP requests handler for calendars.""" _encoding = config.get("encoding", "request") + # Decorator checking rights before performing request + check_rights = lambda function: lambda request: _check(request, function) + @property def calendar(self): """The ``calendar.Calendar`` object corresponding to the given path.""" @@ -109,9 +111,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler): charsets = [] # First append content charset given in the request - contentType = self.headers["Content-Type"] - if contentType and "charset=" in contentType: - charsets.append(contentType.split("charset=")[1].strip()) + content_type = self.headers["Content-Type"] + if content_type and "charset=" in content_type: + charsets.append(content_type.split("charset=")[1].strip()) # Then append default Radicale charset charsets.append(self._encoding) # Then append various fallbacks @@ -126,10 +128,13 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler): pass raise UnicodeDecodeError + # Naming methods ``do_*`` is OK here + # pylint: disable-msg=C0103 + @check_rights def do_GET(self): """Manage GET request.""" - answer = self.calendar.vcalendar.encode(_encoding) + answer = self.calendar.vcalendar.encode(self._encoding) self.send_response(client.OK) self.send_header("Content-Length", len(answer)) diff --git a/radicale/acl/__init__.py b/radicale/acl/__init__.py index 28c91d1..2767970 100644 --- a/radicale/acl/__init__.py +++ b/radicale/acl/__init__.py @@ -23,11 +23,14 @@ Users and rights management. This module loads a list of users with access rights, according to the acl configuration. + """ from radicale import config + def load(): + """Load list of available ACL managers.""" module = __import__("radicale.acl", globals(), locals(), [config.get("acl", "type")]) return getattr(module, config.get("acl", "type")) diff --git a/radicale/acl/fake.py b/radicale/acl/fake.py index 60fdc25..145f3d1 100644 --- a/radicale/acl/fake.py +++ b/radicale/acl/fake.py @@ -25,6 +25,6 @@ No rights management. """ -def has_right(user, password): +def has_right(*_): """Check if ``user``/``password`` couple is valid.""" return True diff --git a/radicale/acl/htpasswd.py b/radicale/acl/htpasswd.py index c5b6720..08e0f0b 100644 --- a/radicale/acl/htpasswd.py +++ b/radicale/acl/htpasswd.py @@ -33,27 +33,35 @@ import hashlib from radicale import config -def _plain(hash, password): - return hash == password -def _crypt(hash, password): - return crypt.crypt(password, hash) == hash +FILENAME = config.get("acl", "filename") +CHECK_PASSWORD = locals()["_%s" % config.get("acl", "encryption")] -def _sha1(hash, password): - hash = hash.replace("{SHA}", "").encode("ascii") + +def _plain(hash_value, password): + """Check if ``hash_value`` and ``password`` match using plain method.""" + return hash_value == password + + +def _crypt(hash_value, password): + """Check if ``hash_value`` and ``password`` match using crypt method.""" + return crypt.crypt(password, hash_value) == hash_value + + +def _sha1(hash_value, password): + """Check if ``hash_value`` and ``password`` match using sha1 method.""" + hash_value = hash_value.replace("{SHA}", "").encode("ascii") password = password.encode(config.get("encoding", "stock")) sha1 = hashlib.sha1() sha1.update(password) - return sha1.digest() == base64.b64decode(hash) + return sha1.digest() == base64.b64decode(hash_value) -_filename = config.get("acl", "filename") -_check_password = locals()["_%s" % config.get("acl", "encryption")] def has_right(user, password): """Check if ``user``/``password`` couple is valid.""" - for line in open(_filename).readlines(): + for line in open(FILENAME).readlines(): if line.strip(): - login, hash = line.strip().split(":") + login, hash_value = line.strip().split(":") if login == user: - return _check_password(hash, password) + return CHECK_PASSWORD(hash_value, password) return False diff --git a/radicale/calendar.py b/radicale/calendar.py index aeb5ab0..1db24e5 100644 --- a/radicale/calendar.py +++ b/radicale/calendar.py @@ -22,11 +22,16 @@ Radicale calendar classes. Define the main classes of a calendar as seen from the server. + """ from radicale import support -hash_tag = lambda vcalendar: str(hash(vcalendar)) + +def hash_tag(vcalendar): + """Hash an vcalendar string.""" + return str(hash(vcalendar)) + class Calendar(object): """Internal calendar class.""" @@ -67,6 +72,7 @@ class Calendar(object): """Etag from calendar.""" return '"%s"' % hash_tag(self.vcalendar) + class Event(object): """Internal event class.""" def __init__(self, vcalendar): @@ -78,12 +84,14 @@ class Event(object): """Etag from event.""" return '"%s"' % hash_tag(self.text) + class Header(object): """Internal header class.""" def __init__(self, vcalendar): """Initialize header from ``vcalendar``.""" self.text = vcalendar + class Timezone(object): """Internal timezone class.""" def __init__(self, vcalendar): @@ -91,11 +99,12 @@ class Timezone(object): lines = vcalendar.splitlines() for line in lines: if line.startswith("TZID:"): - self.tzid = line.lstrip("TZID:") + self.id = line.lstrip("TZID:") break self.text = vcalendar + class Todo(object): """Internal todo class.""" def __init__(self, vcalendar): diff --git a/radicale/config.py b/radicale/config.py index 6de04dc..e99b010 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -22,26 +22,21 @@ Radicale configuration module. Give a configparser-like interface to read and write configuration. + """ # TODO: Use abstract filenames for other platforms import os +import sys try: from configparser import RawConfigParser as ConfigParser except ImportError: from ConfigParser import RawConfigParser as ConfigParser -_config = ConfigParser() -get = _config.get -set = _config.set -getboolean = _config.getboolean -getint = _config.getint -getfloat = _config.getfloat -options = _config.options -items = _config.items -_initial = { +# Default configuration +INITIAL_CONFIG = { "server": { "host": "", "port": "5232", @@ -49,17 +44,11 @@ _initial = { "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt", "key": "/etc/apache2/ssl/server.key", - #"log": "/var/www/radicale/server.log", }, "encoding": { "request": "utf-8", "stock": "utf-8", }, - "namespace": { - "C": "urn:ietf:params:xml:ns:caldav", - "D": "DAV:", - "CS": "http://calendarserver.org/ns/", - }, "acl": { "type": "fake", "filename": "/etc/radicale/users", @@ -68,14 +57,20 @@ _initial = { "support": { "type": "plain", "folder": os.path.expanduser("~/.config/radicale"), - "calendar": "radicale/calendar", + "calendar": "radicale/cal", }, } -for section, values in _initial.items(): - _config.add_section(section) - for key, value in values.items(): - _config.set(section, key, value) +# Create a ConfigParser and configure it +_CONFIG = ConfigParser() -_config.read("/etc/radicale/config") -_config.read(os.path.expanduser("~/.config/radicale/config")) +for section, values in INITIAL_CONFIG.items(): + _CONFIG.add_section(section) + for key, value in values.items(): + _CONFIG.set(section, key, value) + +_CONFIG.read("/etc/radicale/config") +_CONFIG.read(os.path.expanduser("~/.config/radicale/config")) + +# Wrap config module into ConfigParser instance +sys.modules[__name__] = _CONFIG diff --git a/radicale/ical.py b/radicale/ical.py index ac02637..e10b46d 100644 --- a/radicale/ical.py +++ b/radicale/ical.py @@ -20,18 +20,19 @@ """ iCal parsing functions. + """ # TODO: Manage filters (see xmlutils) from radicale import calendar -def write_calendar(headers=[ + +def write_calendar(headers=( calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"), - calendar.Header("VERSION:2.0")], - timezones=[], todos=[], events=[]): - """Create calendar from ``headers``, ``timezones``, ``todos``, ``events``.""" - # TODO: Manage encoding and EOL + calendar.Header("VERSION:2.0")), + timezones=(), todos=(), events=()): + """Create calendar from given parameters.""" cal = "\n".join(( "BEGIN:VCALENDAR", "\n".join([header.text for header in headers]), @@ -41,44 +42,57 @@ def write_calendar(headers=[ "END:VCALENDAR")) return "\n".join([line for line in cal.splitlines() if line]) -def headers(vcalendar): - """Find Headers items in ``vcalendar``.""" - headers = [] - - lines = vcalendar.splitlines() - for line in lines: - if line.startswith("PRODID:"): - headers.append(calendar.Header(line)) - for line in lines: - if line.startswith("VERSION:"): - headers.append(calendar.Header(line)) - - return headers def _parse(vcalendar, tag, obj): """Find ``tag`` items in ``vcalendar``. Return a list of items of type ``obj``. + """ items = [] lines = vcalendar.splitlines() - inItem = False - itemLines = [] + in_item = False + item_lines = [] for line in lines: if line.startswith("BEGIN:%s" % tag): - inItem = True - itemLines = [] + in_item = True + item_lines = [] - if inItem: - # TODO: Manage encoding - itemLines.append(line) + if in_item: + item_lines.append(line) if line.startswith("END:%s" % tag): - items.append(obj("\n".join(itemLines))) + items.append(obj("\n".join(item_lines))) return items -events = lambda vcalendar: _parse(vcalendar, "VEVENT", calendar.Event) -todos = lambda vcalendar: _parse(vcalendar, "VTODO", calendar.Todo) -timezones = lambda vcalendar: _parse(vcalendar, "VTIMEZONE", calendar.Timezone) + +def headers(vcalendar): + """Find Headers items in ``vcalendar``.""" + header_lines = [] + + lines = vcalendar.splitlines() + for line in lines: + if line.startswith("PRODID:"): + header_lines.append(calendar.Header(line)) + for line in lines: + if line.startswith("VERSION:"): + header_lines.append(calendar.Header(line)) + + return header_lines + + +def events(vcalendar): + """Get list of ``Event`` from VEVENTS items in ``vcalendar``.""" + return _parse(vcalendar, "VEVENT", calendar.Event) + + +def todos(vcalendar): + """Get list of ``Todo`` from VTODO items in ``vcalendar``.""" + return _parse(vcalendar, "VTODO", calendar.Todo) + + +def timezones(vcalendar): + """Get list of ``Timezome`` from VTIMEZONE items in ``vcalendar``.""" + return _parse(vcalendar, "VTIMEZONE", calendar.Timezone) diff --git a/radicale/support/__init__.py b/radicale/support/__init__.py index 9bd3ea9..ace80ef 100644 --- a/radicale/support/__init__.py +++ b/radicale/support/__init__.py @@ -20,11 +20,13 @@ """ Calendar storage support configuration. + """ from radicale import config def load(): + """Load list of available storage support managers.""" module = __import__("radicale.support", globals(), locals(), [config.get("support", "type")]) return getattr(module, config.get("support", "type")) diff --git a/radicale/support/plain.py b/radicale/support/plain.py index 299a5a7..3dd58dd 100644 --- a/radicale/support/plain.py +++ b/radicale/support/plain.py @@ -20,6 +20,7 @@ """ Plain text storage. + """ import os @@ -28,39 +29,47 @@ import codecs from radicale import config, ical -_folder = os.path.expanduser(config.get("support", "folder")) +FOLDER = os.path.expanduser(config.get("support", "folder")) +DEFAULT_CALENDAR = config.get("support", "calendar") + def _open(path, mode="r"): + """Open file at ``path`` with ``mode``, automagically managing encoding.""" return codecs.open(path, mode, config.get("encoding", "stock")) + def calendars(): """List available calendars paths.""" - calendars = [] + available_calendars = [] - for folder in os.listdir(_folder): - for cal in os.listdir(os.path.join(_folder, folder)): - calendars.append(posixpath.join(folder, cal)) + for filename in os.listdir(FOLDER): + if os.path.isdir(os.path.join(FOLDER, filename)): + for cal in os.listdir(os.path.join(FOLDER, filename)): + available_calendars.append(posixpath.join(filename, cal)) + + return available_calendars - return calendars def mkcalendar(name): """Write a new calendar called ``name``.""" user, cal = name.split(posixpath.sep) - if not os.path.exists(os.path.join(_folder, user)): - os.makedirs(os.path.join(_folder, user)) - fd = _open(os.path.join(_folder, user, cal), "w") - fd.write(ical.write_calendar()) + if not os.path.exists(os.path.join(FOLDER, user)): + os.makedirs(os.path.join(FOLDER, user)) + descriptor = _open(os.path.join(FOLDER, user, cal), "w") + descriptor.write(ical.write_calendar()) + def read(cal): """Read calendar ``cal``.""" - path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep)) + path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep)) return _open(path).read() + def append(cal, vcalendar): """Append ``vcalendar`` to ``cal``.""" old_calendar = read(cal) - old_tzs = [tz.tzid for tz in ical.timezones(old_calendar)] - path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep)) + old_timezones = [timezone.id for timezone in ical.timezones(old_calendar)] + path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep)) old_objects = [] old_objects.extend([event.etag for event in ical.events(old_calendar)]) @@ -70,37 +79,36 @@ def append(cal, vcalendar): objects.extend(ical.events(vcalendar)) objects.extend(ical.todos(vcalendar)) - for tz in ical.timezones(vcalendar): - if tz.tzid not in old_tzs: - # TODO: Manage position and EOL - fd = _open(path) - lines = [line for line in fd.readlines() if line] - fd.close() + for timezone in ical.timezones(vcalendar): + if timezone.id not in old_timezones: + descriptor = _open(path) + lines = [line for line in descriptor.readlines() if line] + descriptor.close() - for i,line in enumerate(tz.text.splitlines()): + for i, line in enumerate(timezone.text.splitlines()): lines.insert(2 + i, line + "\n") - fd = _open(path, "w") - fd.writelines(lines) - fd.close() + descriptor = _open(path, "w") + descriptor.writelines(lines) + descriptor.close() for obj in objects: if obj.etag not in old_objects: - # TODO: Manage position and EOL - fd = _open(path) - lines = [line for line in fd.readlines() if line] - fd.close() + descriptor = _open(path) + lines = [line for line in descriptor.readlines() if line] + descriptor.close() for line in obj.text.splitlines(): lines.insert(-1, line + "\n") - fd = _open(path, "w") - fd.writelines(lines) - fd.close() + descriptor = _open(path, "w") + descriptor.writelines(lines) + descriptor.close() + def remove(cal, etag): """Remove object named ``etag`` from ``cal``.""" - path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep)) + path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep)) cal = read(cal) @@ -109,11 +117,12 @@ def remove(cal, etag): todos = [todo for todo in ical.todos(cal) if todo.etag != etag] events = [event for event in ical.events(cal) if event.etag != etag] - fd = _open(path, "w") - fd.write(ical.write_calendar(headers, timezones, todos, events)) - fd.close() + descriptor = _open(path, "w") + descriptor.write(ical.write_calendar(headers, timezones, todos, events)) + descriptor.close() -if config.get("support", "calendar"): - user, cal = config.get("support", "calendar").split(posixpath.sep) - if not os.path.exists(os.path.join(_folder, user, cal)): - mkcalendar(config.get("support", "calendar")) + +# Create default calendar if not present +if DEFAULT_CALENDAR: + if DEFAULT_CALENDAR not in calendars(): + mkcalendar(DEFAULT_CALENDAR) diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index f19b56b..d8866ca 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -24,33 +24,42 @@ XML and iCal requests manager. Note that all these functions need to receive unicode objects for full iCal requests (PUT) and string objects with charset correctly defined in them for XML requests (all but PUT). + """ -# TODO: Manage errors (see __init__) -# TODO: Manage depth and calendars/collections (see main) +# TODO: Manage depth and calendars/collections import xml.etree.ElementTree as ET from radicale import client, config, ical + # TODO: This is a well-known and accepted hack for ET to avoid ET from renaming # namespaces, which is accepted in XML norm but often not in XML # readers. Is there another clean solution to force namespaces? -for key, value in config.items("namespace"): +PROTECTED_NAMESPACES = { + "C": "urn:ietf:params:xml:ns:caldav", + "D": "DAV:", + "CS": "http://calendarserver.org/ns/"} +for key, value in PROTECTED_NAMESPACES.items(): ET._namespace_map[value] = key + def _tag(short_name, local): """Get XML Clark notation {uri(``short_name``)}``local``.""" - return "{%s}%s" % (config.get("namespace", short_name), local) + return "{%s}%s" % (PROTECTED_NAMESPACES[short_name], local) + def _response(code): """Return full W3C names from HTTP status codes.""" return "HTTP/1.1 %i %s" % (code, client.responses[code]) + def delete(obj, calendar, url): """Read and answer DELETE requests. Read rfc4918-9.6 for info. + """ # Reading request calendar.remove(obj) @@ -74,13 +83,14 @@ def propfind(xml_request, calendar, url): """Read and answer PROPFIND requests. Read rfc4918-9.1 for info. + """ # Reading request root = ET.fromstring(xml_request) - propElement = root.find(_tag("D", "prop")) - propList = propElement.getchildren() - properties = [property.tag for property in propList] + prop_element = root.find(_tag("D", "prop")) + prop_list = prop_element.getchildren() + props = [prop.tag for prop in prop_list] # Writing answer multistatus = ET.Element(_tag("D", "multistatus")) @@ -97,30 +107,30 @@ def propfind(xml_request, calendar, url): prop = ET.Element(_tag("D", "prop")) propstat.append(prop) - if _tag("D", "resourcetype") in properties: - resourcetype = ET.Element(_tag("D", "resourcetype")) - resourcetype.append(ET.Element(_tag("C", "calendar"))) - prop.append(resourcetype) + if _tag("D", "resourcetype") in props: + element = ET.Element(_tag("D", "resourcetype")) + element.append(ET.Element(_tag("C", "calendar"))) + prop.append(element) - if _tag("D", "owner") in properties: - owner = ET.Element(_tag("D", "owner")) - owner.text = calendar.owner - prop.append(owner) + if _tag("D", "owner") in props: + element = ET.Element(_tag("D", "owner")) + element.text = calendar.owner + prop.append(element) - if _tag("D", "getcontenttype") in properties: - getcontenttype = ET.Element(_tag("D", "getcontenttype")) - getcontenttype.text = "text/calendar" - prop.append(getcontenttype) + if _tag("D", "getcontenttype") in props: + element = ET.Element(_tag("D", "getcontenttype")) + element.text = "text/calendar" + prop.append(element) - if _tag("D", "getetag") in properties: - getetag = ET.Element(_tag("D", "getetag")) - getetag.text = calendar.etag - prop.append(getetag) + if _tag("D", "getetag") in props: + element = ET.Element(_tag("D", "getetag")) + element.text = calendar.etag + prop.append(element) - if _tag("CS", "getctag") in properties: - getctag = ET.Element(_tag("CS", "getctag")) - getctag.text = calendar.ctag - prop.append(getctag) + if _tag("CS", "getctag") in props: + element = ET.Element(_tag("CS", "getctag")) + element.text = calendar.ctag + prop.append(element) status = ET.Element(_tag("D", "status")) status.text = _response(200) @@ -128,40 +138,32 @@ def propfind(xml_request, calendar, url): return ET.tostring(multistatus, config.get("encoding", "request")) -def put(icalRequest, calendar, url, obj): +def put(ical_request, calendar, url, obj): """Read PUT requests.""" if obj: # PUT is modifying obj - calendar.replace(obj, icalRequest) + calendar.replace(obj, ical_request) else: # PUT is adding a new object - calendar.append(icalRequest) + calendar.append(ical_request) def report(xml_request, calendar, url): """Read and answer REPORT requests. Read rfc3253-3.6 for info. + """ # Reading request root = ET.fromstring(xml_request) - propElement = root.find(_tag("D", "prop")) - propList = propElement.getchildren() - properties = [property.tag for property in propList] - - filters = {} - filterElement = root.find(_tag("C", "filter")) - filterList = propElement.getchildren() - # TODO: This should be recursive - # TODO: Really manage filters (see ical) - for filter in filterList: - sub = filters[filter.get("name")] = {} - for subfilter in filter.getchildren(): - sub[subfilter.get("name")] = {} + prop_element = root.find(_tag("D", "prop")) + prop_list = prop_element.getchildren() + props = [prop.tag for prop in prop_list] if root.tag == _tag("C", "calendar-multiget"): # Read rfc4791-7.9 for info - hreferences = set([hrefElement.text for hrefElement in root.findall(_tag("D", "href"))]) + hreferences = set([href_element.text for href_element + in root.findall(_tag("D", "href"))]) else: hreferences = [url] @@ -173,12 +175,10 @@ def report(xml_request, calendar, url): # Read rfc4791-9.[6|10] for info for hreference in hreferences: headers = ical.headers(calendar.vcalendar) - # TODO: Define timezones by obj timezones = ical.timezones(calendar.vcalendar) - objects = [] - objects.extend(ical.events(calendar.vcalendar)) - objects.extend(ical.todos(calendar.vcalendar)) + objects = \ + ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar) if not objects: # TODO: Read rfc4791-9.[6|10] to find a right answer @@ -209,17 +209,16 @@ def report(xml_request, calendar, url): prop = ET.Element(_tag("D", "prop")) propstat.append(prop) - if _tag("D", "getetag") in properties: - # TODO: Can UID and ETAG be the same? - getetag = ET.Element(_tag("D", "getetag")) - getetag.text = obj.etag - prop.append(getetag) + if _tag("D", "getetag") in props: + element = ET.Element(_tag("D", "getetag")) + element.text = obj.etag + prop.append(element) - if _tag("C", "calendar-data") in properties: - cdata = ET.Element(_tag("C", "calendar-data")) + if _tag("C", "calendar-data") in props: + element = ET.Element(_tag("C", "calendar-data")) # TODO: Maybe assume that events and todos are not the same - cdata.text = ical.write_calendar(headers, timezones, [obj]) - prop.append(cdata) + element.text = ical.write_calendar(headers, timezones, [obj]) + prop.append(element) status = ET.Element(_tag("D", "status")) status.text = _response(200)