5 commits - pykolab/xml tests/functional tests/unit wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Mon Mar 3 17:02:49 CET 2014


 pykolab/xml/attendee.py                                       |   17 
 pykolab/xml/event.py                                          |    6 
 pykolab/xml/utils.py                                          |    2 
 tests/functional/test_wallace/test_005_resource_add.py        |   58 ++
 tests/functional/test_wallace/test_005_resource_invitation.py |  231 ++++++----
 tests/unit/test-002-attendee.py                               |    4 
 wallace/module_resources.py                                   |   50 +-
 7 files changed, 270 insertions(+), 98 deletions(-)

New commits:
commit f6bd847a037038ff85f72da1ffca251b82400320
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 20 22:05:56 2014 -0500

    Ignore event updates for delegated resource collections; clean-up debug logging

diff --git a/tests/functional/test_wallace/test_005_resource_add.py b/tests/functional/test_wallace/test_005_resource_add.py
new file mode 100644
index 0000000..2de60fb
--- /dev/null
+++ b/tests/functional/test_wallace/test_005_resource_add.py
@@ -0,0 +1,58 @@
+import time
+import pykolab
+
+from pykolab import wap_client
+from pykolab.auth import Auth
+from pykolab.imap import IMAP
+from wallace import module_resources
+from twisted.trial import unittest
+
+import tests.functional.resource_func as funcs
+
+conf = pykolab.getConf()
+
+class TestResourceAdd(unittest.TestCase):
+
+    @classmethod
+    def setUp(self):
+        from tests.functional.purge_users import purge_users
+        #purge_users()
+
+        self.john = {
+            'local': 'john.doe',
+            'domain': 'example.org'
+        }
+
+        from tests.functional.user_add import user_add
+        #user_add("John", "Doe")
+
+        funcs.purge_resources()
+        self.audi = funcs.resource_add("car", "Audi A4")
+        self.passat = funcs.resource_add("car", "VW Passat")
+        self.boxter = funcs.resource_add("car", "Porsche Boxter S")
+        self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
+
+        from tests.functional.synchronize import synchronize_once
+        synchronize_once()
+
+    @classmethod
+    def tearDown(self):
+        from tests.functional.purge_users import purge_users
+        #funcs.purge_resources()
+        #purge_users()
+
+    def test_001_resource_created(self):
+        resource = module_resources.resource_record_from_email_address(self.audi['mail'])
+        self.assertEqual(len(resource), 1)
+        self.assertEqual(resource[0], self.audi['dn'])
+
+        collection = module_resources.resource_record_from_email_address(self.cars['mail'])
+        self.assertEqual(len(collection), 1)
+        self.assertEqual(collection[0], self.cars['dn'])
+
+    def test_002_resource_collection(self):
+        auth = Auth()
+        auth.connect()
+        attrs = auth.get_entry_attributes(None, self.cars['dn'], ['*'])
+        self.assertIn('groupofuniquenames', attrs['objectclass'])
+        self.assertEqual(len(attrs['uniquemember']), 3)
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 8d6803d..5afde3c 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -18,27 +18,7 @@ import tests.functional.resource_func as funcs
 
 conf = pykolab.getConf()
 
-itip_invitation = """MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="=_c8894dbdb8baeedacae836230e3436fd"
-From: "Doe, John" <john.doe at example.org>
-Date: Tue, 25 Feb 2014 13:54:14 +0100
-Message-ID: <240fe7ae7e139129e9eb95213c1016d7 at example.org>
-User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
-To: %s
-Subject: "test" has been created
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Type: text/plain; charset=UTF-8; format=flowed
-Content-Transfer-Encoding: quoted-printable
-
-*test*
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Type: text/calendar; charset=UTF-8; method=REQUEST; name=event.ics
-Content-Disposition: attachment; filename=event.ics
-Content-Transfer-Encoding: 8bit
-
+itip_invitation = """
 BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
@@ -56,30 +36,9 @@ ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:ma
 TRANSP:OPAQUE
 END:VEVENT
 END:VCALENDAR
---=_c8894dbdb8baeedacae836230e3436fd--
 """
 
-itip_update = """MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="=_c8894dbdb8baeedacae836230e3436fd"
-From: "Doe, John" <john.doe at example.org>
-Date: Tue, 25 Feb 2014 13:54:14 +0100
-Message-ID: <240fe7ae7e139129e9eb95213c1016d7 at example.org>
-User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
-To: %s
-Subject: "test" has been updated
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Type: text/plain; charset=UTF-8; format=flowed
-Content-Transfer-Encoding: quoted-printable
-
-*test* updated
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Type: text/calendar; charset=UTF-8; method=REQUEST; name=event.ics
-Content-Disposition: attachment; filename=event.ics
-Content-Transfer-Encoding: 8bit
-
+itip_update = """
 BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
@@ -98,18 +57,35 @@ ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:ma
 TRANSP:OPAQUE
 END:VEVENT
 END:VCALENDAR
---=_c8894dbdb8baeedacae836230e3436fd--
 """
 
-itip_cancellation = """Return-Path: <john.doe at example.org>
-Content-Type: text/calendar; method=CANCEL; charset=UTF-8
-Content-Transfer-Encoding: quoted-printable
-To: %s
-From: john.doe at example.org
-Date: Mon, 24 Feb 2014 11:27:28 +0100
-Message-ID: <1a3aa8995e83dd24cf9247e538ac91ff at example.org>
-Subject: "test" cancelled
+itip_delegated = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube//Roundcube libcalendaring 1.0-git//Sabre//Sabre VObject
+  2.1.3//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:%s
+DTSTAMP;VALUE=DATE-TIME:20140227T141939Z
+DTSTART;VALUE=DATE-TIME;TZID=Europe/London:%s
+DTEND;VALUE=DATE-TIME;TZID=Europe/London:%s
+SUMMARY:test
+SEQUENCE:4
+ATTENDEE;CN=Company Cars;PARTSTAT=DELEGATED;ROLE=NON-PARTICIPANT;CUTYPE=IND
+ IVIDUAL;RSVP=TRUE;DELEGATED-TO=resource-car-audia4 at example.org:mailto:reso
+ urce-collection-companycars at example.org
+ATTENDEE;CN=Audi A4;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUA
+ L;RSVP=TRUE;DELEGATED-FROM=resource-collection-companycars at example.org:mai
+ lto:resource-car-audia4 at example.org
+ORGANIZER;CN=:mailto:john.doe at example.org
+DESCRIPTION:Sent to %s
+END:VEVENT
+END:VCALENDAR
+"""
 
+itip_cancellation = """
 BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
@@ -118,19 +94,65 @@ METHOD:CANCEL
 BEGIN:VEVENT
 UID:%s
 DTSTAMP:20140218T1254140
-DTSTART;TZID=3DEurope/London:20120713T100000
-DTEND;TZID=3DEurope/London:20120713T110000
+DTSTART;TZID=Europe/London:20120713T100000
+DTEND;TZID=Europe/London:20120713T110000
 SUMMARY:test
 DESCRIPTION:test
-ORGANIZER;CN=3D"Doe, John":mailto:john.doe at example.org
-ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DACCEPTED;RSVP=3DTRUE:mailt=
-o:%s
+ORGANIZER;CN="Doe, John":mailto:john.doe at example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailt=
+ o:%s
 TRANSP:OPAQUE
 SEQUENCE:3
 END:VEVENT
 END:VCALENDAR
 """
 
+itip_allday = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:%s
+DTSTAMP:20140213T1254140
+DTSTART;VALUE=DATE:%s
+DTEND;VALUE=DATE:%s
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe at example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:%s
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+"""
+
+
+mime_message = """MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="=_c8894dbdb8baeedacae836230e3436fd"
+From: "Doe, John" <john.doe at example.org>
+Date: Tue, 25 Feb 2014 13:54:14 +0100
+Message-ID: <240fe7ae7e139129e9eb95213c1016d7 at example.org>
+User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
+To: %s
+Subject: "test"
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+*test*
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/calendar; charset=UTF-8; method=REQUEST; name=event.ics
+Content-Disposition: attachment; filename=event.ics
+Content-Transfer-Encoding: 8bit
+
+%s
+--=_c8894dbdb8baeedacae836230e3436fd--
+"""
+
 class TestResourceInvitation(unittest.TestCase):
 
     john = None
@@ -167,37 +189,44 @@ class TestResourceInvitation(unittest.TestCase):
         from tests.functional.synchronize import synchronize_once
         synchronize_once()
 
-    def send_message(self, msg_source, to_addr, from_addr=None):
+    def send_message(self, itip_payload, to_addr, from_addr=None):
         if from_addr is None:
             from_addr = self.john['mail']
 
         smtp = smtplib.SMTP('localhost', 10026)
-        smtp.sendmail(from_addr, to_addr, msg_source)
+        smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, itip_payload))
 
-    def send_itip_invitation(self, resource_email, start=None):
+    def send_itip_invitation(self, resource_email, start=None, allday=False):
         if start is None:
             start = datetime.datetime.now()
 
         uid = str(uuid.uuid4())
-        end = start + datetime.timedelta(hours=4)
-        self.send_message(itip_invitation % (
-                resource_email,
+
+        if allday:
+            template = itip_allday
+            end = start + datetime.timedelta(days=1)
+            date_format = '%Y%m%d'
+        else:
+            end = start + datetime.timedelta(hours=4)
+            template = itip_invitation
+            date_format = '%Y%m%dT%H%M%S'
+
+        self.send_message(template % (
                 uid,
-                start.strftime('%Y%m%dT%H%M%S'),
-                end.strftime('%Y%m%dT%H%M%S'),
+                start.strftime(date_format),
+                end.strftime(date_format),
                 resource_email
             ),
             resource_email)
 
         return uid
 
-    def send_itip_update(self, resource_email, uid, start=None):
+    def send_itip_update(self, resource_email, uid, start=None, template=None):
         if start is None:
             start = datetime.datetime.now()
 
         end = start + datetime.timedelta(hours=4)
-        self.send_message(itip_update % (
-                resource_email,
+        self.send_message((template if template is not None else itip_update) % (
                 uid,
                 start.strftime('%Y%m%dT%H%M%S'),
                 end.strftime('%Y%m%dT%H%M%S'),
@@ -209,7 +238,6 @@ class TestResourceInvitation(unittest.TestCase):
 
     def send_itip_cancel(self, resource_email, uid):
         self.send_message(itip_cancellation % (
-                resource_email,
                 uid,
                 resource_email
             ),
@@ -290,6 +318,18 @@ class TestResourceInvitation(unittest.TestCase):
 
         imap.imap.m.expunge()
         imap.disconnect()
+        time.sleep(1)
+
+
+    def find_resource_by_email(self, email):
+        resource = None
+        if (email.find(self.audi['mail']) >= 0):
+            resource = self.audi
+        if (email.find(self.passat['mail']) >= 0):
+            resource = self.passat
+        if (email.find(self.boxter['mail']) >= 0):
+            resource = self.boxter
+        return resource
 
 
     def test_001_resource_from_email_address(self):
@@ -331,7 +371,7 @@ class TestResourceInvitation(unittest.TestCase):
         accept = self.check_message_received("Reservation Request for test was ACCEPTED")
         self.assertIsInstance(accept, email.message.Message)
 
-        delegatee = self.passat if accept['from'].find(self.passat['mail']) >= 0 else self.boxter
+        delegatee = self.find_resource_by_email(accept['from'])
         self.assertIn(delegatee['mail'], accept['from'])
 
         # check booking in the delegatee's resource calendar
@@ -343,13 +383,15 @@ class TestResourceInvitation(unittest.TestCase):
 
 
     def test_005_rescheduling_reservation(self):
-        uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,5,1, 10,0,0))
+        self.purge_mailbox(self.john['mailbox'])
+
+        uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,4,1, 10,0,0))
 
         response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         self.purge_mailbox(self.john['mailbox'])
-        self.send_itip_update(self.audi['mail'], uid, datetime.datetime(2014,5,1, 12,0,0)) # conflict with myself
+        self.send_itip_update(self.audi['mail'], uid, datetime.datetime(2014,4,1, 12,0,0)) # conflict with myself
 
         response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
@@ -361,6 +403,8 @@ class TestResourceInvitation(unittest.TestCase):
 
 
     def test_006_cancelling_revervation(self):
+        self.purge_mailbox(self.john['mailbox'])
+
         uid = self.send_itip_invitation(self.boxter['mail'], datetime.datetime(2014,5,1, 10,0,0))
         self.assertIsInstance(self.check_resource_calendar_event(self.boxter['kolabtargetfolder'], uid), pykolab.xml.Event)
 
@@ -374,3 +418,46 @@ class TestResourceInvitation(unittest.TestCase):
 
         response = self.check_message_received("Reservation Request for test was ACCEPTED", self.boxter['mail'])
         self.assertIsInstance(response, email.message.Message)
+
+
+    def test_007_update_delegated(self):
+        self.purge_mailbox(self.john['mailbox'])
+
+        dt = datetime.datetime(2014,8,1, 12,0,0)
+        uid = self.send_itip_invitation(self.cars['mail'], dt)
+
+        # wait for accept notification
+        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        self.assertIsInstance(accept, email.message.Message)
+        delegatee = self.find_resource_by_email(accept['from'])
+
+        # send update message to all attendees (collection and delegatee)
+        self.purge_mailbox(self.john['mailbox'])
+        update_template = itip_delegated.replace("resource-car-audia4 at example.org", delegatee['mail'])
+        self.send_itip_update(self.cars['mail'], uid, dt, template=update_template)
+        self.send_itip_update(delegatee['mail'], uid, dt, template=update_template)
+
+        # get response from delegatee
+        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        self.assertIsInstance(accept, email.message.Message)
+        self.assertIn(delegatee['mail'], accept['from'])
+
+        # no delegation response on updates
+        self.assertEqual(self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail']), None)
+
+
+    def test_008_allday_reservation(self):
+        self.purge_mailbox(self.john['mailbox'])
+
+        uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2), True)
+
+        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        self.assertIsInstance(accept, email.message.Message)
+
+        event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
+        self.assertIsInstance(event, pykolab.xml.Event)
+        self.assertIsInstance(event.get_start(), datetime.date)
+
+        uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2, 16,0,0))
+        response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+        self.assertIsInstance(response, email.message.Message)
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index b718ac4..6aaf64a 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -35,6 +35,7 @@ from email.utils import getaddresses
 import modules
 
 import pykolab
+import kolabformat
 
 from pykolab.auth import Auth
 from pykolab.conf import Conf
@@ -229,19 +230,31 @@ def execute(*args, **kw):
         else:
             resources[resource_dn] = resource_attrs
 
-    log.debug(_("Resources: %r, %r") % (resource_dns, resources), level=8)
+    log.debug(_("Resources: %r; %r") % (resource_dns, resources), level=8)
 
 
-    # process CANCEL messages
     done = False
+    receiving_resource = resources[resource_dns[0]]
+
     for itip_event in itip_events:
-        if itip_event['method'] == "CANCEL":
+        try:
+            receiving_attendee = itip_event['xml'].get_attendee_by_email(receiving_resource['mail'])
+            log.debug(_("Receiving Resource: %r; %r") % (receiving_resource, receiving_attendee), level=9)
+        except Exception, e:
+            log.error("Could not find envelope attendee: %r" % (e))
+            continue
+
+        # ignore updates and cancellations to resource collections who already delegated the event
+        if receiving_attendee.get_delegated_to().size() > 0 or receiving_attendee.get_role() == kolabformat.NonParticipant:
+            done = True
+            log.debug(_("Recipient %r is non-participant, ignoring message") % (receiving_resource['mail']), level=8)
+
+        # process CANCEL messages
+        if not done and itip_event['method'] == "CANCEL":
             for resource in resource_dns:
                 if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
                     delete_resource_event(itip_event['uid'], resources[resource])
 
-                # TODO: handle cancellations sent to resource collections. Really?
-
             done = True
 
     if done:
@@ -263,14 +276,14 @@ def execute(*args, **kw):
 
         # sets the 'conflicting' flag and adds a list of conflicting events found
         try:
-            read_resource_calendar(resources[resource], itip_events)
+            num_messages = read_resource_calendar(resources[resource], itip_events)
         except Exception, e:
             log.error(_("Failed to read resource calendar for %r: %r") % (resource, e))
             continue
 
     end = time.time()
 
-    log.debug(_("start: %r, end: %r, total: %r") % (start, end, (end-start)), level=1)
+    log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=9)
 
 
     # For each resource (collections are first!)
@@ -335,7 +348,7 @@ def execute(*args, **kw):
                         for uid in resources[resource]['existing_events']:
                             delete_resource_event(uid, resources[resource])
 
-                    log.debug(_("Accept invitation for individual resource %r / %r") % (resource, resources[resource]['mail']), level=9)
+                    log.debug(_("Accept invitation for individual resource %r / %r") % (resource, resources[resource]['mail']), level=8)
                     accept_reservation_request(itip_event, resources[resource])
                     done = True
 
@@ -349,7 +362,7 @@ def execute(*args, **kw):
                         # Randomly selects a target resource from the resource collection.
                         _target_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
 
-                        log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], _target_resource['mail']), level=9)
+                        log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], _target_resource['mail']), level=8)
 
                     if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
                         #
@@ -395,6 +408,8 @@ def read_resource_calendar(resource_rec, itip_events):
     imap.imap.m.select(mailbox)
     typ, data = imap.imap.m.search(None, 'ALL')
 
+    num_messages = len(data[0].split())
+
     for num in data[0].split():
         # For efficiency, makes the routine non-deterministic
         if resource_rec['conflict']:
@@ -456,7 +471,7 @@ def read_resource_calendar(resource_rec, itip_events):
                             resource_rec['conflicting_events'].append(event.get_uid())
                             resource_rec['conflict'] = True
 
-    return resource_rec['conflict']
+    return num_messages
 
 
 def accept_reservation_request(itip_event, resource, delegator=None):
@@ -570,7 +585,7 @@ def itip_events_from_message(message):
             # Get the itip_payload
             itip_payload = part.get_payload(decode=True)
 
-            log.debug(_("Raw iTip payload: %s") % (itip_payload))
+            log.debug(_("Raw iTip payload: %s") % (itip_payload), level=9)
 
             # Python iCalendar prior to 3.0 uses "from_string".
             if hasattr(icalendar.Calendar, 'from_ical'):
@@ -588,13 +603,14 @@ def itip_events_from_message(message):
                     itip = {}
 
                     if c['uid'] in seen_uids:
-                        log.debug(_("Duplicate iTip event: %s") % (c['uid']))
+                        log.debug(_("Duplicate iTip event: %s") % (c['uid']), level=9)
                         continue
 
                     # From the event, take the following properties:
                     #
                     # - method
                     # - uid
+                    # - sequence
                     # - start
                     # - end (if any)
                     # - duration (if any)
@@ -607,6 +623,7 @@ def itip_events_from_message(message):
 
                     itip['uid'] = str(c['uid'])
                     itip['method'] = str(cal['method']).upper()
+                    itip['sequence'] = int(c['sequence']) if c.has_key('sequence') else 0
 
                     if c.has_key('dtstart'):
                         itip['start'] = c['dtstart']
@@ -629,7 +646,12 @@ def itip_events_from_message(message):
                         itip['resources'] = c['resources']
 
                     itip['raw'] = itip_payload
-                    itip['xml'] = event_from_ical(c.to_ical())
+
+                    try:
+                        itip['xml'] = event_from_ical(c.to_ical())
+                    except Exception, e:
+                        log.error("event_from_ical() exception: %r" % (e))
+                        continue
 
                     itip_events.append(itip)
 
@@ -798,8 +820,6 @@ def resource_records_from_itip_events(itip_events, recipient_email=None):
     log.debug(_("The following resources are being referred to in the " + \
                 "iTip: %r") % (resource_records), level=8)
 
-    auth.disconnect()
-
     return resource_records
 
 


commit 6dd440aa974ad159d2d5666ff8370de6cafe5bdf
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 20 22:01:11 2014 -0500

    Fix type check of date/time objects

diff --git a/pykolab/xml/utils.py b/pykolab/xml/utils.py
index 0959c80..1771fa6 100644
--- a/pykolab/xml/utils.py
+++ b/pykolab/xml/utils.py
@@ -6,7 +6,7 @@ def to_dt(dt):
         Convert a naive date or datetime to a tz-aware datetime.
     """
 
-    if type(dt) == 'datetime.date' or not hasattr(dt, 'hour'):
+    if isinstance(dt, datetime.date) or not hasattr(dt, 'hour'):
         dt = datetime.datetime(dt.year, dt.month, dt.day, 0, 0, 0, 0)
 
     else:


commit 26d6038d6a8226ad94580ee5cc58fd0800251cfa
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 20 22:00:27 2014 -0500

    Read delegated-from/-to from iCal into libkolabxml event objects

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 4eef860..ffe80f7 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -41,7 +41,8 @@ class Attendee(kolabformat.Attendee):
             rsvp=False,
             role=None,
             participant_status=None,
-            cutype=None
+            cutype=None,
+            ical_params=None
         ):
 
         self.email = email
@@ -62,12 +63,18 @@ class Attendee(kolabformat.Attendee):
         if not role == None:
             self.set_role(role)
 
-        if not participant_status == None:
-            self.set_participant_status(participant_status)
-
         if not cutype == None:
             self.set_cutype(cutype)
 
+        if ical_params and ical_params.has_key('DELEGATED-FROM'):
+            self.delegate_from(Attendee(str(ical_params['DELEGATED-FROM'])))
+
+        if ical_params and ical_params.has_key('DELEGATED-TO'):
+            self.delegate_to(Attendee(str(ical_params['DELEGATED-TO'])))
+
+        if not participant_status == None:
+            self.set_participant_status(participant_status)
+
     def delegate_from(self, delegators):
         crefs = []
 
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 2dea819..7e109e1 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -44,8 +44,8 @@ class Event(object):
 
         self.uid = self.get_uid()
 
-    def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None, cutype="INDIVIDUAL"):
-        attendee = Attendee(email, name, rsvp, role, participant_status, cutype)
+    def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None, cutype="INDIVIDUAL", params=None):
+        attendee = Attendee(email, name, rsvp, role, participant_status, cutype, params)
         self._attendees.append(attendee)
         self.event.setAttendees(self._attendees)
 
@@ -701,7 +701,7 @@ class Event(object):
                 else:
                     cutype = kolabformat.CutypeIndividual
 
-                self.add_attendee(address, name=name, rsvp=rsvp, role=role, participant_status=partstat, cutype=cutype)
+                att = self.add_attendee(address, name=name, rsvp=rsvp, role=role, participant_status=partstat, cutype=cutype, params=params)
 
     def set_ical_dtend(self, dtend):
         self.set_end(dtend)


commit 3d4e25c18dab7563b6093428682639b0cbd99a12
Merge: adf8978 6ec6336
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 20 21:59:02 2014 -0500

    Merge branch 'master' of ssh://git.kolab.org/git/pykolab



commit adf8978f63aa4bd4fa57bd1bf097f22c6ad3b609
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 20 19:05:40 2014 -0500

    Fix invalid attendee role value NONPARTICIPANT => NON-PARTICIPANT

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 15f1c1a..4eef860 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -26,7 +26,7 @@ class Attendee(kolabformat.Attendee):
             "REQ-PARTICIPANT": kolabformat.Required,
             "CHAIR": kolabformat.Chair,
             "OPTIONAL": kolabformat.Optional,
-            "NONPARTICIPANT": kolabformat.NonParticipant,
+            "NON-PARTICIPANT": kolabformat.NonParticipant,
         }
 
     rsvp_map = {
diff --git a/tests/unit/test-002-attendee.py b/tests/unit/test-002-attendee.py
index 4298761..cbfa9cc 100644
--- a/tests/unit/test-002-attendee.py
+++ b/tests/unit/test-002-attendee.py
@@ -80,13 +80,13 @@ class TestEventXML(unittest.TestCase):
         self.assertEqual(self.attendee.role_map["REQ-PARTICIPANT"], 0)
         self.assertEqual(self.attendee.role_map["CHAIR"], 1)
         self.assertEqual(self.attendee.role_map["OPTIONAL"], 2)
-        self.assertEqual(self.attendee.role_map["NONPARTICIPANT"], 3)
+        self.assertEqual(self.attendee.role_map["NON-PARTICIPANT"], 3)
 
     def test_017_role_map_reverse_lookup(self):
         self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 0][0], "REQ-PARTICIPANT")
         self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 1][0], "CHAIR")
         self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 2][0], "OPTIONAL")
-        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 3][0], "NONPARTICIPANT")
+        self.assertEqual([k for k,v in self.attendee.role_map.iteritems() if v == 3][0], "NON-PARTICIPANT")
 
     def test_015_cutype_map_length(self):
         self.assertEqual(len(self.attendee.cutype_map.keys()), 3)




More information about the commits mailing list