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