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

Thomas Brüderli bruederli at kolabsys.com
Wed Aug 6 18:44:44 CEST 2014


 pykolab/xml/attendee.py                                       |   18 +++--
 pykolab/xml/event.py                                          |   15 +++-
 tests/functional/test_wallace/test_005_resource_invitation.py |   19 +++++
 tests/unit/test-003-event.py                                  |   36 ++++++++++
 wallace/module_resources.py                                   |    4 -
 5 files changed, 82 insertions(+), 10 deletions(-)

New commits:
commit 41a8d8edc3938880b363ccd0cc688f73f4e37867
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Aug 6 12:44:37 2014 -0400

    Add test for owner confirmation on (delegated) resource collection bookings

diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 61b9402..0f05993 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -779,3 +779,22 @@ class TestResourceInvitation(unittest.TestCase):
         event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
         self.assertIsInstance(event, pykolab.xml.Event)
         self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED')
+
+
+    def test_016_collection_owner_confirmation(self):
+        self.purge_mailbox(self.john['mailbox'])
+
+        uid = self.send_itip_invitation(self.viprooms['mail'], datetime.datetime(2014,8,15, 17,0,0))
+
+        # resource collection responds with a DELEGATED message
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.viprooms['mail'])
+        self.assertIsInstance(response, email.message.Message)
+
+        # the collection member tentatively accepted the reservation
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') })
+        self.assertIsInstance(accept, email.message.Message)
+        self.assertIn(self.room3['mail'], accept['from'])
+
+        # check confirmation message sent to resource owner (jane)
+        notify = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+        self.assertIsInstance(notify, email.message.Message)
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index c0983ca..ae6cd06 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -314,7 +314,7 @@ def execute(*args, **kw):
             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:
+        if len(receiving_attendee.get_delegated_to()) > 0 or receiving_attendee.get_role() == kolabformat.NonParticipant:
             done = True
             log.debug(_("Recipient %r is non-participant, ignoring message") % (receiving_resource['mail']), level=8)
 


commit 9febaa9596bff65c25f81688283fbf5940a4bdef
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Aug 6 12:39:22 2014 -0400

    Fix iTip REPLY when an attendee delegated to another: both attendees shall be listed (RFC 5546, Section 3.2.2.3.)

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 4a30622..c4ccb96 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -61,6 +61,8 @@ class Attendee(kolabformat.Attendee):
             'rsvp':  'rsvp',
             'partstat':  'get_participant_status',
             'cutype':   'get_cutype',
+            'delegated-to': 'get_delegated_to',
+            'delegated-from': 'get_delegated_from',
         }
 
     def __init__(
@@ -157,11 +159,17 @@ class Attendee(kolabformat.Attendee):
             return self._translate_value(cutype, self.cutype_map)
         return cutype
 
-    def get_delegated_from(self):
-        return self.delegatedFrom()
-
-    def get_delegated_to(self):
-        return self.delegatedTo()
+    def get_delegated_from(self, translated=False):
+        delegators = []
+        for cr in self.delegatedFrom():
+            delegators.append(cr.email() if translated else ContactReference(cr))
+        return delegators
+
+    def get_delegated_to(self, translated=False):
+        delegatees = []
+        for cr in self.delegatedTo():
+            delegatees.append(cr.email() if translated else ContactReference(cr))
+        return delegatees
 
     def get_email(self):
         return self.contactreference.get_email()
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index ca23694..eac764d 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -87,7 +87,7 @@ class Event(object):
         "summary": "summary",
         "description": "description",
         "priority": "priority",
-        "status": "get_status",
+        "status": "get_ical_status",
         "location": "location",
         "organizer": "organizer",
         "attendee": "get_attendees",
@@ -1012,16 +1012,16 @@ class Event(object):
             msg['To'] = self.get_organizer().email()
 
             attendees = self.get_attendees()
+            reply_attendees = []
 
-            # TODO: There's an exception here for delegation (partstat DELEGATED)
+            # There's an exception here for delegation (partstat DELEGATED)
             for attendee in attendees:
                 if attendee.get_email() == from_address:
                     # Only the attendee is supposed to be listed in a reply
                     attendee.set_participant_status(participant_status)
                     attendee.set_rsvp(False)
 
-                    self._attendees = [attendee]
-                    self.event.setAttendees(self._attendees)
+                    reply_attendees.append(attendee)
 
                     name = attendee.get_name()
                     email = attendee.get_email()
@@ -1031,6 +1031,13 @@ class Event(object):
                     else:
                         msg_from = '"%s" <%s>' % (name, email)
 
+                elif from_address in attendee.get_delegated_from(True):
+                    reply_attendees.append(attendee)
+
+            # keep only replying (and delegated) attendee(s)
+            self._attendees = reply_attendees
+            self.event.setAttendees(self._attendees)
+
             if msg_from == None:
                 organizer = self.get_organizer()
                 email = organizer.email()
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 1f54419..4736b19 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -428,6 +428,42 @@ END:VEVENT
         self.assertEqual(event['X-CUSTOM'], "check")
         self.assertIsInstance(event['dtstamp'].dt, datetime.datetime)
 
+    def test_019_to_message_itip(self):
+        self.event = Event()
+        self.event.set_summary("test")
+        self.event.set_start(datetime.datetime(2014, 05, 23, 11, 00, 00, tzinfo=pytz.timezone("Europe/London")))
+        self.event.set_end(datetime.datetime(2014, 05, 23, 12, 30, 00, tzinfo=pytz.timezone("Europe/London")))
+        self.event.set_organizer("me at kolab.org")
+        self.event.add_attendee("john at doe.org")
+        self.event.add_attendee("jane at doe.org")
+
+        message = self.event.to_message_itip("john at doe.org", method="REPLY", participant_status="ACCEPTED")
+        itip_event = None
+        for part in message.walk():
+            if part.get_content_type() == "text/calendar":
+                ical = icalendar.Calendar.from_ical(part.get_payload(decode=True))
+                itip_event = ical.walk('VEVENT')[0]
+                break
+
+        self.assertEqual(itip_event['uid'], self.event.get_uid())
+        self.assertEqual(itip_event['attendee'].lower(), 'mailto:john at doe.org')
+
+        # delegate jane => jack
+        self.event.delegate("jane at doe.org", "jack at ripper.com", "Jack")
+
+        message = self.event.to_message_itip("jane at doe.org", method="REPLY", participant_status="DELEGATED")
+        itip_event = None
+        for part in message.walk():
+            if part.get_content_type() == "text/calendar":
+                ical = icalendar.Calendar.from_ical(part.get_payload(decode=True))
+                itip_event = ical.walk('VEVENT')[0]
+                break
+
+        self.assertEqual(len(itip_event['attendee']), 2)
+        self.assertEqual(itip_event['attendee'][0].lower(), 'mailto:jane at doe.org')
+        self.assertEqual(itip_event['attendee'][1].lower(), 'mailto:jack at ripper.com')
+
+
     def test_020_calendaring_recurrence(self):
         rrule = kolabformat.RecurrenceRule()
         rrule.setFrequency(kolabformat.RecurrenceRule.Monthly)


commit 9151feafa799c0795845f2a020d38da4f45b4437
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Aug 6 11:41:28 2014 -0400

    Make sure 'uniquemember' is a list

diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index d65120c..c0983ca 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -903,6 +903,8 @@ def get_resource_records(resource_dns):
 
         if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
             if resource_attrs.has_key('uniquemember'):
+                if not isinstance(resource_attrs['uniquemember'], list):
+                    resource_attrs['uniquemember'] = [ resource_attrs['uniquemember'] ]
                 resources[resource_dn] = resource_attrs
                 for uniquemember in resource_attrs['uniquemember']:
                     member_attrs = auth.get_entry_attributes(




More information about the commits mailing list