summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2015-03-02 15:01:21 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2015-03-02 15:01:21 (GMT)
commit3cc3391170e54fb3e4bf477562bc2ef4bf8885c3 (patch)
tree4a182e5b16dbab0c4b37202813fc718bf7fdcafd
parentdaaa39a33e05934462b42d650873a4453c6fd5ca (diff)
downloadpykolab-3cc3391170e54fb3e4bf477562bc2ef4bf8885c3.tar.gz
Store invitations to single occurrences with the same UID in one object (#4726)
-rw-r--r--pykolab/xml/event.py44
-rw-r--r--tests/functional/test_wallace/test_007_invitationpolicy.py42
-rw-r--r--tests/unit/test-003-event.py38
-rw-r--r--wallace/module_invitationpolicy.py22
4 files changed, 124 insertions, 22 deletions
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 4158d31..7a81ab8 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -181,10 +181,6 @@ class Event(object):
self.event.addExceptionDate(xmlutils.to_cdatetime(_datetime, True))
def add_exception(self, exception):
- # sanity checks
- if not self.is_recurring():
- raise EventIntegrityError, "Cannot add exceptions to a non-recurring event"
-
recurrence_id = exception.get_recurrence_id()
if recurrence_id is None:
raise EventIntegrityError, "Recurrence exceptions require a Recurrence-ID property"
@@ -199,6 +195,13 @@ class Event(object):
self._exceptions[i] = exception
append = False
+ # check if main event matches the given recurrence-id
+ if append and self.get_recurrence_id() == recurrence_id:
+ self.event = exception.event
+ self._load_attendees()
+ self._load_exceptions()
+ append = False
+
if append:
vexceptions.append(exception.event)
self._exceptions.append(exception)
@@ -476,6 +479,9 @@ class Event(object):
def get_exceptions(self):
return self._exceptions
+ def has_exceptions(self):
+ return len(self._exceptions) > 0
+
def get_attachments(self):
return self.event.attachments()
@@ -1384,15 +1390,27 @@ class Event(object):
if hasattr(_start, 'tzinfo'):
_datetime = _datetime.replace(tzinfo=_start.tzinfo)
- instance = self.get_next_instance(_datetime - datetime.timedelta(days=1))
- while instance:
- recurrence_id = instance.get_recurrence_id()
- if type(recurrence_id) == type(_datetime) and recurrence_id <= _datetime:
- if xmlutils.dates_equal(recurrence_id, _datetime):
- return instance
- instance = self.get_next_instance(instance.get_start())
- else:
- break
+ if self.is_recurring():
+ instance = self.get_next_instance(_datetime - datetime.timedelta(days=1))
+ while instance:
+ recurrence_id = instance.get_recurrence_id()
+ if type(recurrence_id) == type(_datetime) and recurrence_id <= _datetime:
+ if xmlutils.dates_equal(recurrence_id, _datetime):
+ return instance
+ instance = self.get_next_instance(instance.get_start())
+ else:
+ break
+
+ elif self.has_exceptions():
+ for exception in self._exceptions:
+ recurrence_id = exception.get_recurrence_id()
+ if type(recurrence_id) == type(_datetime) and xmlutils.dates_equal(recurrence_id, _datetime):
+ return exception
+
+ if self.get_recurrence_id():
+ recurrence_id = self.get_recurrence_id()
+ if type(recurrence_id) == type(_datetime) and xmlutils.dates_equal(recurrence_id, _datetime):
+ return self
return None
diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py
index cc99c73..a019949 100644
--- a/tests/functional/test_wallace/test_007_invitationpolicy.py
+++ b/tests/functional/test_wallace/test_007_invitationpolicy.py
@@ -351,11 +351,13 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
smtp = smtplib.SMTP('localhost', 10026)
smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, method, itip_payload))
- def send_itip_invitation(self, attendee_email, start=None, allday=False, template=None, summary="test", sequence=0, partstat='NEEDS-ACTION', from_addr=None, instance=None):
+ def send_itip_invitation(self, attendee_email, start=None, allday=False, template=None, summary="test", sequence=0, partstat='NEEDS-ACTION', from_addr=None, uid=None, instance=None):
if start is None:
start = datetime.datetime.now()
- uid = str(uuid.uuid4())
+ if uid is None:
+ uid = str(uuid.uuid4())
+
recurrence_id = ''
if allday:
@@ -1235,9 +1237,12 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
start = datetime.datetime(2015,5,4, 6,30,0)
uid = self.send_itip_invitation(self.mark['mail'], summary="recurring", start=start, template=itip_recurring)
- response = self.check_message_received(self.itip_reply_subject % { 'summary':'recurring', 'status':participant_status_label('ACCEPTED') }, self.mark['mail'])
+ pykolab.translate.setUserLanguage(self.mark['preferredlanguage'])
+ response = self.check_message_received(_('"%(summary)s" has been %(status)s') % { 'summary':'recurring', 'status':participant_status_label('ACCEPTED') }, self.mark['mail'])
self.assertIsInstance(response, email.message.Message)
+ pykolab.translate.setUserLanguage(conf.get('kolab','default_locale'))
+
event = self.check_user_calendar_event(self.mark['kolabcalendarfolder'], uid)
self.assertIsInstance(event, pykolab.xml.Event)
@@ -1267,6 +1272,37 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
self.assertIsInstance(event, pykolab.xml.Event)
self.assertIsInstance(event.get_recurrence_id(), datetime.datetime)
+ # send another invitation with the same UID for different RECURRENCE-ID
+ newstart = datetime.datetime(2015,2,6, 17,0,0, tzinfo=pytz.timezone("Europe/Zurich"))
+ self.send_itip_invitation(self.jane['mail'], summary="single #2", uid=uid, start=newstart, instance=newstart)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'single #2', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+ self.assertIn("RECURRENCE-ID", str(response))
+
+ event = self.check_user_calendar_event(self.jane['kolabcalendarfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(len(event.get_exceptions()), 1)
+
+ exception = event.get_instance(newstart)
+ self.assertEqual(exception.get_summary(), "single #2")
+ self.assertEqual(exception.get_recurrence_id(), newstart)
+
+ # send an update occurrence #1
+ self.send_itip_invitation(self.jane['mail'], summary="updated #1", uid=uid, start=start, instance=start)
+ time.sleep(5)
+
+ # send an update occurrence #2
+ self.send_itip_invitation(self.jane['mail'], summary="updated #2", uid=uid, start=newstart, instance=newstart)
+
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.jane['kolabcalendarfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "updated #1")
+
+ exception = event.get_instance(newstart)
+ self.assertEqual(exception.get_summary(), "updated #2")
+
def test_020_task_assignment_accept(self):
start = datetime.datetime(2014,9,10, 19,0,0)
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 1f9f36b..5798101 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -744,6 +744,44 @@ END:VEVENT
self.assertIsInstance(exception['recurrence-id'].dt, datetime.datetime)
self.assertEqual(exception['recurrence-id'].params.get('RANGE'), None)
+ def test_021_single_instances(self):
+ self.event = Event()
+ self.event.set_summary('singles')
+
+ _start = datetime.datetime(2015,3,1, 14,0,0, tzinfo=pytz.timezone("Europe/London"))
+ self.event.set_start(_start)
+ self.event.set_end(_start + datetime.timedelta(hours=1))
+ self.event.set_recurrence_id(_start)
+
+ _start2 = datetime.datetime(2015,3,5, 15,0,0, tzinfo=pytz.timezone("Europe/London"))
+ xmlexception = Event(from_string=str(self.event))
+ xmlexception.set_start(_start2)
+ xmlexception.set_end(_start2 + datetime.timedelta(hours=1))
+ xmlexception.set_summary('singles #2')
+ xmlexception.set_recurrence_id(_start2)
+ self.event.add_exception(xmlexception)
+
+ self.assertEqual(self.event.has_exceptions(), True)
+
+ first = self.event.get_instance(_start)
+ self.assertIsInstance(first, Event)
+ self.assertEqual(first.get_summary(), "singles")
+
+ second = self.event.get_instance(_start2)
+ self.assertIsInstance(second, Event)
+ self.assertEqual(second.get_summary(), "singles #2")
+
+ # update main instance
+ first.set_status('CANCELLED')
+ first.set_summary("singles #1")
+ self.event.add_exception(first)
+
+ event = event_from_string(str(self.event))
+ self.assertEqual(self.event.has_exceptions(), True)
+ self.assertEqual(event.get_status(True), 'CANCELLED')
+ self.assertEqual(event.get_summary(), "singles #1")
+
+
def test_022_load_from_xml(self):
event = event_from_string(xml_event)
self.assertEqual(event.uid, '75c740bb-b3c6-442c-8021-ecbaeb0a025e')
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
index 405f743..fca2128 100644
--- a/wallace/module_invitationpolicy.py
+++ b/wallace/module_invitationpolicy.py
@@ -440,6 +440,7 @@ def process_itip_request(itip_event, policy, recipient_email, sender_email, rece
log.debug(_("Auto-updating %s %r on iTip REQUEST (no re-scheduling)") % (existing.type, existing.uid), level=8)
save_object = True
+ rsvp = False
# retain task status and percent-complete properties from my old copy
if is_task:
@@ -473,10 +474,13 @@ def process_itip_request(itip_event, policy, recipient_email, sender_email, rece
if save_object:
targetfolder = None
+ # delete old version from IMAP
if existing:
- # delete old version from IMAP
targetfolder = existing._imap_folder
delete_object(existing)
+ elif master and hasattr(master, '_imap_folder'):
+ targetfolder = master._imap_folder
+ delete_object(master)
if not nonpart or existing:
# save new copy from iTip
@@ -814,12 +818,17 @@ def find_existing_object(uid, type, recurrence_id, user_rec, lock=False):
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() or event.get_recurrence_id()):
master = event
event = master.get_instance(recurrence_id)
setattr(master, '_imap_folder', folder)
setattr(master, '_msguid', msguid)
+ # 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)
+ """
# 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()):
log.debug(_("Recurrence-ID not matching on message %s, skipping: %r != %r") % (
@@ -828,10 +837,11 @@ def find_existing_object(uid, type, recurrence_id, user_rec, lock=False):
event = None
master = None
continue
-
- setattr(event, '_imap_folder', folder)
- setattr(event, '_lock_key', lock_key)
- setattr(event, '_msguid', msguid)
+ """
+ if event is not None:
+ setattr(event, '_imap_folder', folder)
+ setattr(event, '_lock_key', lock_key)
+ setattr(event, '_msguid', msguid)
except Exception, e:
log.error(_("Failed to parse %s from message %s/%s: %s") % (type, folder, num, traceback.format_exc()))