summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2015-03-02 17:57:32 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2015-03-02 17:57:32 (GMT)
commite0cdbb50abceae359934f6421445491315069ab7 (patch)
treee415d3bc985e34bf9c30d42afbabbfbc7102c016
parent403257f86465a53237bb88fd6c7f2d292ab9f041 (diff)
downloadpykolab-e0cdbb50abceae359934f6421445491315069ab7.tar.gz
Store reservations for single occurrences with the same UID in one object (#4766)
-rw-r--r--pykolab/itip/__init__.py16
-rw-r--r--tests/functional/test_wallace/test_005_resource_invitation.py42
-rw-r--r--tests/unit/test-011-itip.py3
-rw-r--r--wallace/module_invitationpolicy.py1
-rw-r--r--wallace/module_resources.py20
5 files changed, 61 insertions, 21 deletions
diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py
index a81015b..83c9fe1 100644
--- a/pykolab/itip/__init__.py
+++ b/pykolab/itip/__init__.py
@@ -184,10 +184,10 @@ def check_event_conflict(kolab_event, itip_event):
_iv = _ix
# iterate through all exceptions (non-recurring)
- elif _is is None and not itip_event['xml'].is_recurring() and itip_event['xml'].has_exceptions() and len(itip_event['xml'].get_exceptions()) > _ii:
- _ix = itip_event['xml'].get_exceptions()[_ii]
- _is = to_dt(_ix.get_start())
- _ie = to_dt(_ix.get_end())
+ elif _is is None and not itip_event['xml'].is_recurring() and len(itip_event['xml'].get_exceptions()) > _ii:
+ _iv = itip_event['xml'].get_exceptions()[_ii]
+ _is = to_dt(_iv.get_start())
+ _ie = to_dt(_iv.get_end())
_ii += 1
_es = to_dt(kolab_event.get_next_occurence(_es)) if kolab_event.is_recurring() else None
@@ -202,10 +202,10 @@ def check_event_conflict(kolab_event, itip_event):
_ev = _ex
# iterate through all exceptions (non-recurring)
- elif _es is None and not kolab_event.is_recurring() and kolab_event.has_exceptions() and len(kolab_event.get_exceptions()) > _ei:
- _ex = kolab_event.get_exceptions()[_ei]
- _es = to_dt(_ex.get_start())
- _ee = to_dt(_ex.get_end())
+ elif _es is None and not kolab_event.is_recurring() and len(kolab_event.get_exceptions()) > _ei:
+ _ev = kolab_event.get_exceptions()[_ei]
+ _es = to_dt(_ev.get_start())
+ _ee = to_dt(_ev.get_end())
_ei += 1
return conflict
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index e7ac0f3..af84fa6 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -360,7 +360,7 @@ class TestResourceInvitation(unittest.TestCase):
return found
- def check_resource_calendar_event(self, mailbox, uid=None, instance=None):
+ def check_resource_calendar_event(self, mailbox, uid=None):
imap = IMAP()
imap.connect()
@@ -383,7 +383,7 @@ class TestResourceInvitation(unittest.TestCase):
continue
found = event_from_message(event_message)
- if found and (instance is None or found.is_recurring() or xmlutils.dates_equal(instance, found.get_recurrence_id())):
+ if found:
break
time.sleep(1)
@@ -904,16 +904,34 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(accept, email.message.Message)
self.assertIn("RECURRENCE-ID;TZID=Europe/London:" + start.strftime('%Y%m%dT%H%M%S'), str(accept))
- # the resource calendar now has two reservations stored
- one = self.check_resource_calendar_event(self.boxter['kolabtargetfolder'], uid, start)
+ # the resource calendar now has two reservations stored in one object
+ one = self.check_resource_calendar_event(self.boxter['kolabtargetfolder'], uid)
self.assertIsInstance(one, pykolab.xml.Event)
self.assertIsInstance(one.get_recurrence_id(), datetime.datetime)
self.assertEqual(one.get_start().hour, exstart.hour)
- two = self.check_resource_calendar_event(self.boxter['kolabtargetfolder'], uid, nextstart)
+ two = one.get_instance(nextstart)
self.assertIsInstance(two, pykolab.xml.Event)
self.assertIsInstance(two.get_recurrence_id(), datetime.datetime)
+ self.purge_mailbox(self.john['mailbox'])
+
+ # send rescheduling request to the 2nd instance
+ self.send_itip_update(self.boxter['mail'], uid, nextstart + datetime.timedelta(hours=2), sequence=2, instance=nextstart)
+
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
+ self.assertIsInstance(accept, email.message.Message)
+ self.assertIn("RECURRENCE-ID;TZID=Europe/London:" + nextstart.strftime('%Y%m%dT%H%M%S'), str(accept))
+
+ event = self.check_resource_calendar_event(self.boxter['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(len(event.get_exceptions()), 1)
+
+ two = event.get_instance(nextstart)
+ self.assertIsInstance(two, pykolab.xml.Event)
+ self.assertEqual(two.get_sequence(), 2)
+ self.assertEqual(two.get_start().hour, 20)
+
def test_019_cancel_single_occurrence(self):
self.purge_mailbox(self.john['mailbox'])
@@ -937,6 +955,20 @@ class TestResourceInvitation(unittest.TestCase):
self.assertEqual(exception.get_status(True), 'CANCELLED')
self.assertTrue(exception.get_transparency())
+ self.purge_mailbox(self.john['mailbox'])
+
+ # store a single occurrence with recurrence-id
+ start = datetime.datetime(2015,3,2, 18,30,0)
+ uid = self.send_itip_invitation(self.passat['mail'], start, instance=start)
+
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
+ self.assertIsInstance(accept, email.message.Message)
+
+ self.send_itip_cancel(self.passat['mail'], uid, instance=start)
+
+ time.sleep(5) # wait for IMAP to update
+ self.assertEqual(self.check_resource_calendar_event(self.passat['kolabtargetfolder'], uid), None)
+
def test_020_owner_confirmation_single_occurrence(self):
self.purge_mailbox(self.john['mailbox'])
diff --git a/tests/unit/test-011-itip.py b/tests/unit/test-011-itip.py
index 8179aa9..e47d314 100644
--- a/tests/unit/test-011-itip.py
+++ b/tests/unit/test-011-itip.py
@@ -501,6 +501,7 @@ class TestITip(unittest.TestCase):
second.set_start(dtstart + datetime.timedelta(hours=1))
second.set_end(dtstart + datetime.timedelta(hours=2))
second.set_recurrence_id(dtstart)
+ second.set_transparency(True)
itip_event['xml'].add_exception(second)
self.assertEqual(len(itip_event['xml'].get_exceptions()), 1)
@@ -514,7 +515,7 @@ class TestITip(unittest.TestCase):
event.set_start(datetime.datetime(2012,7,15, 11,0,0, tzinfo=itip_event['start'].tzinfo))
event.set_end(datetime.datetime(2012,7,15, 11,30,0, tzinfo=itip_event['start'].tzinfo))
- self.assertTrue(itip.check_event_conflict(event, itip_event), "Conflicting dates (exception)")
+ self.assertFalse(itip.check_event_conflict(event, itip_event), "Conflicting dates (exception)")
def test_003_send_reply(self):
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
index 70bf627..d6503a0 100644
--- a/wallace/module_invitationpolicy.py
+++ b/wallace/module_invitationpolicy.py
@@ -826,7 +826,6 @@ def find_existing_object(uid, type, recurrence_id, user_rec, lock=False):
# return master, even if instance is not found
if not event and master.uid == uid:
- log.debug("Instance not found, returning master" % (), level=8)
return (event, master)
if event is not None:
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index c1a684c..6742658 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -336,7 +336,7 @@ def execute(*args, **kw):
log.debug(_("Cancellation for entire event %r: deleting") % (itip_event['uid']), level=8)
delete_resource_event(itip_event['uid'], resources[resource], event._msguid)
# just cancel one single occurrence: add exception with status=cancelled
- elif master and master.is_recurring():
+ elif master is not None:
log.debug(_("Cancellation for a single occurrence %r of %r: updating...") % (itip_event['recurrence-id'], itip_event['uid']), level=8)
event.set_status('CANCELLED')
event.set_transparency(True)
@@ -687,9 +687,9 @@ def read_resource_calendar(resource_rec, itip_events):
for itip in itip_events:
conflict = check_event_conflict(event, itip)
- if event.get_uid() == itip['uid'] and (event.is_recurring() or itip['recurrence-id'] == event.get_recurrence_id()):
+ if event.get_uid() == itip['uid']:
setattr(event, '_msguid', msguid)
- if event.is_recurring():
+ if event.is_recurring() or itip['recurrence-id']:
resource_rec['existing_master'] = event
else:
resource_rec['existing_events'].append(event)
@@ -740,21 +740,29 @@ def find_existing_event(uid, recurrence_id, resource_rec):
event = event_from_message(message_from_string(data[0][1]))
# find instance in a recurring series
- if recurrence_id and event.is_recurring():
+ if recurrence_id and (event.is_recurring() or event.has_exceptions()):
master = event
event = master.get_instance(recurrence_id)
setattr(master, '_msguid', msguid)
+ # return master, even if instance is not found
+ if not event and master.uid == uid:
+ return (event, master)
+
# compare recurrence-id and skip to next message if not matching
- elif recurrence_id and not event.is_recurring() and not xmlutils.dates_equal(recurrence_id, event.get_recurrence_id()):
+ elif recurrence_id and not xmlutils.dates_equal(recurrence_id, event.get_recurrence_id()):
log.debug(_("Recurrence-ID not matching on message %s, skipping: %r != %r") % (
msguid, recurrence_id, event.get_recurrence_id()
), level=8)
continue
- setattr(event, '_msguid', msguid)
+
+ if event is not None:
+ setattr(event, '_msguid', msguid)
except Exception, e:
log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e))
+ event = None
+ master = None
continue
if event and event.uid == uid: