pykolab/xml tests/functional tests/unit wallace/module_invitationpolicy.py

Thomas Brüderli bruederli at kolabsys.com
Mon Mar 2 16:01:33 CET 2015


 pykolab/xml/event.py                                       |   44 +++++++++----
 tests/functional/test_wallace/test_007_invitationpolicy.py |   42 +++++++++++-
 tests/unit/test-003-event.py                               |   38 +++++++++++
 wallace/module_invitationpolicy.py                         |   22 ++++--
 4 files changed, 124 insertions(+), 22 deletions(-)

New commits:
commit 3cc3391170e54fb3e4bf477562bc2ef4bf8885c3
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Mar 2 10:01:21 2015 -0500

    Store invitations to single occurrences with the same UID in one object (#4726)

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()))




More information about the commits mailing list