2 commits - pykolab/itip pykolab/xml tests/unit

Thomas Brüderli bruederli at kolabsys.com
Wed Feb 11 13:51:01 CET 2015


 pykolab/itip/__init__.py     |    4 +-
 pykolab/xml/event.py         |   29 +++++++++++++++-----
 pykolab/xml/todo.py          |   60 +++++++++++++++++++++++++++++++++++--------
 tests/unit/test-003-event.py |    6 ++--
 tests/unit/test-016-todo.py  |   47 +++++++++++++++++++++++++++++++++
 5 files changed, 123 insertions(+), 23 deletions(-)

New commits:
commit a214f29290f4d5e5a2d62a8a549679c8a4c0fe51
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Feb 10 09:44:05 2015 +0100

    Fix iCal event parsing + unit tests after commit 21c116a

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index b179b8f..cb2b876 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -260,9 +260,9 @@ class Event(object):
         self.event.setAttendees(self._attendees)
 
     def from_ical(self, ical, raw=None):
-        if isinstance(ical, icalendar.Todo):
-            ical_todo = ical
-        if hasattr(icalendar.Event, 'from_ical'):
+        if isinstance(ical, icalendar.Event) or isinstance(ical_event, icalendar.Calendar):
+            ical_event = ical
+        elif hasattr(icalendar.Event, 'from_ical'):
             ical_event = icalendar.Event.from_ical(ical)
         elif hasattr(icalendar.Event, 'from_string'):
             ical_event = icalendar.Event.from_string(ical)
@@ -300,9 +300,11 @@ class Event(object):
                 self.set_from_ical(attr.lower(), ical_event[attr])
 
     def _xml_from_ical(self, ical):
+        if not "BEGIN:VCALENDAR" in ical:
+            ical = "BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR"
         from kolab.calendaring import EventCal
         self.event = EventCal()
-        self.event.fromICal("BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR")
+        self.event.fromICal(ical)
 
     def get_attendee_participant_status(self, attendee):
         return attendee.get_participant_status()
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 02bc8df..67481e3 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -369,7 +369,7 @@ METHOD:REQUEST
         """ + ical_event + "END:VCALENDAR"
 
         ical = icalendar.Calendar.from_ical(ical_str)
-        event = event_from_ical(ical.walk('VEVENT')[0].to_ical())
+        event = event_from_ical(ical.walk('VEVENT')[0], ical_str)
 
         self.assertEqual(event.get_location(), "Location")
         self.assertEqual(str(event.get_lastmodified()), "2014-04-07 12:23:11+00:00")
@@ -384,7 +384,7 @@ METHOD:REQUEST
         self.assertTrue(event.is_recurring())
         self.assertIsInstance(event.get_duration(), datetime.timedelta)
         self.assertIsInstance(event.get_end(), datetime.datetime)
-        self.assertEqual(str(event.get_end()), "2014-05-23 12:30:00+01:00")
+        self.assertEqual(str(event.get_end()), "2014-05-23 12:30:00+02:00")
         self.assertEqual(len(event.get_exception_dates()), 2)
         self.assertIsInstance(event.get_exception_dates()[0], datetime.datetime)
         self.assertEqual(len(event.get_alarms()), 1)
@@ -632,7 +632,7 @@ END:VEVENT
         self.assertEqual(data['attach'][0]['fmttype'], 'text/html')
 
         self.assertIsInstance(data['rrule'], dict)
-        self.assertEqual(data['rrule']['frequency'], 'DAILY')
+        self.assertEqual(data['rrule']['freq'], 'DAILY')
         self.assertEqual(data['rrule']['interval'], 1)
         self.assertEqual(data['rrule']['wkst'], 'MO')
         self.assertIsInstance(data['rrule']['until'], datetime.date)


commit 21c116a670c3a34a1e084e1af7c75541a1783f27
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Feb 10 09:26:56 2015 +0100

    Fix importing ical VTODO objects with attachments (#4532):
    
    - Avoid serializing and re-parsing: accept icalendar.* instances in pykolab.xml.* wrapper classes
    - Don't attempt to load iCal data with libkolabxml as there's no support for it in the python bindings
    - Import ATTACH properties from iCal into the XML Todo object

diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py
index 93dc8bf..cae8075 100644
--- a/pykolab/itip/__init__.py
+++ b/pykolab/itip/__init__.py
@@ -115,9 +115,9 @@ def objects_from_message(message, objnames, methods=None):
                     try:
                         # distinguish event and todo here
                         if itip['type'] == 'task':
-                            itip['xml'] = todo_from_ical(c.to_ical())
+                            itip['xml'] = todo_from_ical(c, itip_payload)
                         else:
-                            itip['xml'] = event_from_ical(c.to_ical())
+                            itip['xml'] = event_from_ical(c, itip_payload)
                     except Exception, e:
                         log.error("event|todo_from_ical() exception: %r; iCal: %s" % (e, itip_payload))
                         continue
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 625f555..b179b8f 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -35,8 +35,8 @@ def ustr(s):
 
     return s
 
-def event_from_ical(string):
-    return Event(from_ical=string)
+def event_from_ical(ical, string=None):
+    return Event(from_ical=ical, from_string=string)
 
 def event_from_string(string):
     return Event(from_string=string)
@@ -121,14 +121,14 @@ class Event(object):
         self._categories = []
         self._attachment_parts = []
 
-        if from_ical == "":
+        if isinstance(from_ical, str) and from_ical == "":
             if from_string == "":
                 self.event = kolabformat.Event()
             else:
                 self.event = kolabformat.readEvent(from_string, False)
                 self._load_attendees()
         else:
-            self.from_ical(from_ical)
+            self.from_ical(from_ical, from_string)
 
         self.uid = self.get_uid()
 
@@ -259,16 +259,27 @@ class Event(object):
 
         self.event.setAttendees(self._attendees)
 
-    def from_ical(self, ical):
+    def from_ical(self, ical, raw=None):
+        if isinstance(ical, icalendar.Todo):
+            ical_todo = ical
         if hasattr(icalendar.Event, 'from_ical'):
             ical_event = icalendar.Event.from_ical(ical)
         elif hasattr(icalendar.Event, 'from_string'):
             ical_event = icalendar.Event.from_string(ical)
 
+        # VCALENDAR block was given, find the first VEVENT item
+        if isinstance(ical_event, icalendar.Calendar):
+            for c in ical_event.walk():
+                if c.name == 'VEVENT':
+                    ical_event = c
+                    break
+
         # use the libkolab calendaring bindings to load the full iCal data
         if ical_event.has_key('RRULE') or ical_event.has_key('ATTACH') \
              or [part for part in ical_event.walk() if part.name == 'VALARM']:
-            self._xml_from_ical(ical)
+            if raw is None or raw == "":
+                raw = ical if isinstance(ical, str) else ical.to_ical()
+            self._xml_from_ical(raw)
         else:
             self.event = kolabformat.Event()
 
diff --git a/pykolab/xml/todo.py b/pykolab/xml/todo.py
index 0d34c63..5859cb2 100644
--- a/pykolab/xml/todo.py
+++ b/pykolab/xml/todo.py
@@ -2,6 +2,7 @@ import datetime
 import kolabformat
 import icalendar
 import pytz
+import base64
 
 import pykolab
 from pykolab import constants
@@ -12,8 +13,8 @@ from pykolab.translate import _
 
 log = pykolab.getLogger('pykolab.xml_todo')
 
-def todo_from_ical(string):
-    return Todo(from_ical=string)
+def todo_from_ical(ical, string=None):
+    return Todo(from_ical=ical, from_string=string)
 
 def todo_from_string(string):
     return Todo(from_string=string)
@@ -48,26 +49,40 @@ class Todo(Event):
             "end": "void"
         })
 
-        if from_ical == "":
+        if isinstance(from_ical, str) and from_ical == "":
             if from_string == "":
                 self.event = kolabformat.Todo()
             else:
                 self.event = kolabformat.readTodo(from_string, False)
                 self._load_attendees()
         else:
-            self.from_ical(from_ical)
+            self.from_ical(from_ical, from_string)
 
         self.uid = self.get_uid()
 
-    def from_ical(self, ical):
-        if hasattr(icalendar.Todo, 'from_ical'):
+    def from_ical(self, ical, raw):
+        if isinstance(ical, icalendar.Todo):
+            ical_todo = ical
+        elif hasattr(icalendar.Todo, 'from_ical'):
             ical_todo = icalendar.Todo.from_ical(ical)
         elif hasattr(icalendar.Todo, 'from_string'):
             ical_todo = icalendar.Todo.from_string(ical)
 
-        # use the libkolab calendaring bindings to load the full iCal data
-        if ical_todo.has_key('ATTACH') or [part for part in ical_todo.walk() if part.name == 'VALARM']:
-            self._xml_from_ical(ical)
+        # VCALENDAR block was given, find the first VTODO item
+        if isinstance(ical_todo, icalendar.Calendar):
+            for c in ical_todo.walk():
+                if c.name == 'VTODO':
+                    ical_todo = c
+                    break
+
+        log.debug("Todo.from_ical(); %r, %r, %r" % (type(ical_todo), ical_todo.has_key('ATTACH'), ical_todo.has_key('ATTENDEE')), level=8)
+
+        # DISABLED: use the libkolab calendaring bindings to load the full iCal data
+        # TODO: this requires support for iCal parsing in the kolab.calendaring bindings
+        if False and ical_todo.has_key('ATTACH') or [part for part in ical_todo.walk() if part.name == 'VALARM']:
+            if raw is None or raw == "":
+                raw = ical if isinstance(ical, str) else ical.to_ical()
+            self._xml_from_ical(raw)
         else:
             self.event = kolabformat.Todo()
 
@@ -88,8 +103,33 @@ class Todo(Event):
             self.set_from_ical('percentcomplete', ical_todo['PERCENT-COMPLETE'])
 
     def _xml_from_ical(self, ical):
+        # FIXME: kolabformat or kolab.calendaring modules do not provide bindings to import Todo from iCal
         self.event = Todo()
-        self.event.fromICal("BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR")
+
+    def set_ical_attach(self, attachment):
+        if hasattr(attachment, 'params'):
+            params = attachment.params
+        else:
+            params = {}
+
+        _attachment = kolabformat.Attachment()
+        if params.has_key('FMTTYPE'):
+            mimetype = str(params['FMTTYPE'])
+        else:
+            mimetype = 'application/octet-stream'
+
+        if params.has_key('X-LABEL'):
+            _attachment.setLabel(str(params['X-LABEL']))
+
+        decode = False
+        if params.has_key('ENCODING'):
+            if params['ENCODING'] == "BASE64" or params['ENCODING'] == "B":
+                decode = True
+
+        _attachment.setData(base64.b64decode(str(attachment)) if decode else str(attachment), mimetype)
+        vattach = self.event.attachments()
+        vattach.append(_attachment)
+        self.event.setAttachments(vattach)
 
     def set_ical_due(self, due):
         self.set_due(due)
diff --git a/tests/unit/test-016-todo.py b/tests/unit/test-016-todo.py
index 2e55c63..dcb89c5 100644
--- a/tests/unit/test-016-todo.py
+++ b/tests/unit/test-016-todo.py
@@ -41,6 +41,43 @@ END:VTODO
 END:VCALENDAR
 """
 
+ical_todo_attachment = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube//Roundcube libcalendaring 1.1-git//Sabre//Sabre VObject
+ 2.1.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTODO
+UID:18C2EBBD8B31D99F7AA578EDFDFB1AC0-FCBB6C4091F28CA0
+DTSTAMP;VALUE=DATE-TIME:20140820T101333Z
+CREATED;VALUE=DATE-TIME:20140731T100704Z
+LAST-MODIFIED;VALUE=DATE-TIME:20140820T101333Z
+DUE;VALUE=DATE-TIME;TZID=Europe/London:20150228T133000
+SUMMARY:Task with attachment
+SEQUENCE:3
+PRIORITY:1
+STATUS:IN-PROCESS
+ORGANIZER;CN=Thomas:mailto:thomas.bruederli at example.org
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=silhouette.pn
+ g:iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAIAAADY27xgAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
+ ZSBJbWFnZVJlYWR5ccllPAAAAsRJREFUeNrsmeluKjEMhTswrAWB4P3fECGx79CjsTDmOKRkpF
+ xxpfoHSmchX7ybFrfb7eszpPH1MfKH8ofyH6KUtd/c7/en0wmfWBdF0Wq1Op1Ou91uNGoer6iX
+ V1ar1Xa7xUJeB4qsr9frdyVlWWZH2VZyPp+xPXHIAoK70+m02+1m9JXj8bhcLi+Xi3J4xUCazS
+ bUltdtd7ud7ldUIhC3u+iTwF0sFhlR4Kds4LtRZK1w4te5UM6V6JaqhqC3CQ28OAsKggJfbZ3U
+ eozCqZ4koHIZCGmD9ivuos9YONFirmxrI0UNZG1kbZeUXdJQNJNa91RlqMn0ekYUMZDup6dXVV
+ m+1OSZhqLx6bVCELJGSsyFQtFrF15JGYMZgoxubWGDSDVhvTipDKWhoBOIpFobxtlbJ0Gh0/tg
+ lgXal4woUHi/36fQoBQncDAlupa8DeVwOPRe4lUyGAwQ+dl7W+xBXkJBhEUqR32UoJfYIKrR4d
+ ZBgcdIRqfEqn+mekl9FNRbSTA249la3ev1/kXHD47ZbEYR5L9kMplkd9vNZqMFyIYxxfN8Pk8q
+ QGlagT5QDtfrNYUMlWW9LiGNPPSmC/+OgpK2r4RO6dOatZd+4gAAemdIi6Fg9EKLD4vASWkzv3
+ ew06NSCiA40CumAIoaIrhrcAwjF7aDo58gUchgNV+0n1BAcDgcoAZrXV9mI4qkhtK6FJFhi9Fo
+ ZKPsgQI1ACJieH/Kd570t+xFoIzHYzl5Q40CFGrSqGuks3qmYIKJfIl0nPKLxAMFw7Dv1+2QYf
+ vFSOBQubbOFDSc7ZcfWvHv6DzhOzT6IeOVPuz8Roex0f6EgsE/2IL4qdg7hIXz7/pBie7q1uWr
+ tp66xrif0l1KwUE4P7Y9Gci/ZgtNRFX+Rw06Q2RigsjuDc3urwKHxuNITaaxyD9mT2WvSDAXn/
+ Pvhh8BBgBjyfPSGbSYcwAAAABJRU5ErkJggg==
+END:VTODO
+END:VCALENDAR
+"""
+
 xml_todo = """
 <icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
   <vcalendar>
@@ -223,6 +260,16 @@ METHOD:REQUEST
         self.assertIsInstance(vtodo['dtstamp'].dt, datetime.datetime)
 
 
+    def test_022_ical_with_attachment(self):
+        todo = todo_from_ical(ical_todo_attachment)
+
+        vattachment = todo.get_attachments()
+        self.assertEqual(len(vattachment), 1)
+
+        attachment = vattachment[0]
+        self.assertEqual(attachment.mimetype(), 'image/png')
+        self.assertEqual(attachment.label(), 'silhouette.png')
+
     def test_030_to_dict(self):
         data = todo_from_string(xml_todo).to_dict()
 




More information about the commits mailing list