6 commits - po/en.po pykolab/itip pykolab/translate.py pykolab/xml tests/functional tests/unit wallace/module_invitationpolicy.py wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Tue Jul 22 13:58:47 CEST 2014


 po/en.po                                                      |   15 -
 pykolab/itip/__init__.py                                      |    7 
 pykolab/translate.py                                          |   14 -
 pykolab/xml/__init__.py                                       |    1 
 pykolab/xml/attendee.py                                       |   21 ++
 pykolab/xml/event.py                                          |    3 
 tests/functional/test_wallace/test_005_resource_invitation.py |   56 +++---
 tests/functional/test_wallace/test_007_invitationpolicy.py    |   48 ++++-
 tests/unit/test-002-attendee.py                               |    7 
 tests/unit/test-011-itip.py                                   |    6 
 tests/unit/test-012-wallace_invitationpolicy.py               |   16 +
 tests/unit/test-015-translate.py                              |   10 -
 wallace/module_invitationpolicy.py                            |   82 +++++++++-
 wallace/module_resources.py                                   |    7 
 14 files changed, 209 insertions(+), 84 deletions(-)

New commits:
commit 5d97d7f2da3d97930941c081c8bbba7de687863f
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 06:26:46 2014 -0400

    Implement gettext language switch; remove en.po as this is not used

diff --git a/po/en.po b/po/en.po
deleted file mode 100644
index da6a905..0000000
--- a/po/en.po
+++ /dev/null
@@ -1,15 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# 
-msgid ""
-msgstr ""
-"Project-Id-Version: Kolab Groupware Solution\n"
-"Report-Msgid-Bugs-To: https://isues.kolab.org/\n"
-"POT-Creation-Date: 2014-07-17 10:22+0100\n"
-"PO-Revision-Date: 2014-07-14 11:13+0000\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: en\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
diff --git a/pykolab/translate.py b/pykolab/translate.py
index 85f4516..080cbc2 100644
--- a/pykolab/translate.py
+++ b/pykolab/translate.py
@@ -26,9 +26,10 @@ import gettext
 import os
 
 N_ = lambda x: x
-_ = lambda x: gettext.ldgettext(domain, x)
+_ = lambda x: current.lgettext(x)
 
-#gettext.bindtextdomain(domain, '/usr/local/share/locale')
+localedir = '/usr/local/share/locale'
+current = gettext.translation(domain, localedir, fallback=True)
 
 def getDefaultLangs():
     languages = []
@@ -49,15 +50,14 @@ def getDefaultLangs():
     return nelangs
 
 def setUserLanguage(lang):
+    global current
+
     langs = []
     for l in gettext._expand_lang(lang):
         if l not in langs:
             langs.append(l)
 
     try:
-        translation = gettext.translation(domain, languages=langs)
-        translation.install()
+        current = gettext.translation(domain, localedir, languages=langs, fallback=True)
     except:
-        return False
-
-    return True
+        pass
diff --git a/tests/unit/test-015-translate.py b/tests/unit/test-015-translate.py
index 8ca9463..6819b80 100644
--- a/tests/unit/test-015-translate.py
+++ b/tests/unit/test-015-translate.py
@@ -4,9 +4,6 @@ from pykolab import translate
 
 class TestTranslate(unittest.TestCase):
 
-    def setUp(self):
-        translate.setUserLanguage('en')
-
     def test_001_default_langs(self):
         self.assertTrue(len(translate.getDefaultLangs()) > 0)
 
@@ -16,10 +13,11 @@ class TestTranslate(unittest.TestCase):
 
     def test_003_set_lang(self):
         from pykolab.translate import _
-        self.assertFalse(translate.setUserLanguage('foo_bar'))
         self.assertEqual(_("Folder name"), "Folder name")
-        self.assertTrue(translate.setUserLanguage('de_DE'))
-        self.assertEqual(_("Folder name"), "Ordnername")
+        translate.setUserLanguage('de_DE')
+        self.assertEqual(_("Folder name"), "Ordnername", "German Translation found")
+        translate.setUserLanguage('foo_bar')
+        self.assertEqual(_("Folder name"), "Folder name", "Unkonwn language falls back to NullTranslations")
 
 if __name__ == '__main__':
     unittest.main()


commit 6b3df45c2cc13f5fcc3320404881cbff0a8f012e
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 05:17:01 2014 -0400

    Use deferred translations

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 220ab8c..a01d955 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -1,27 +1,28 @@
 import kolabformat
 
 from pykolab.translate import _
+from pykolab.translate import N_
 
 from contact_reference import ContactReference
 
 participant_status_labels = {
-        "NEEDS-ACTION": _("Needs Action"),
-        "ACCEPTED": _("Accepted"),
-        "DECLINED": _("Declined"),
-        "TENTATIVE": _("Tentatively Accepted"),
-        "DELEGATED": _("Delegated"),
-        "COMPLETED": _("Completed"),
-        "IN-PROCESS": _("In Process"),
+        "NEEDS-ACTION": N_("Needs Action"),
+        "ACCEPTED": N_("Accepted"),
+        "DECLINED": N_("Declined"),
+        "TENTATIVE": N_("Tentatively Accepted"),
+        "DELEGATED": N_("Delegated"),
+        "COMPLETED": N_("Completed"),
+        "IN-PROCESS": N_("In Process"),
         # support integer values, too
-        kolabformat.PartNeedsAction: _("Needs Action"),
-        kolabformat.PartAccepted: _("Accepted"),
-        kolabformat.PartDeclined: _("Declined"),
-        kolabformat.PartTentative: _("Tentatively Accepted"),
-        kolabformat.PartDelegated: _("Delegated"),
+        kolabformat.PartNeedsAction: N_("Needs Action"),
+        kolabformat.PartAccepted: N_("Accepted"),
+        kolabformat.PartDeclined: N_("Declined"),
+        kolabformat.PartTentative: N_("Tentatively Accepted"),
+        kolabformat.PartDelegated: N_("Delegated"),
     }
 
 def participant_status_label(status):
-    return participant_status_labels[status] if participant_status_labels.has_key(status) else status
+    return _(participant_status_labels[status]) if participant_status_labels.has_key(status) else status
 
 
 class Attendee(kolabformat.Attendee):


commit c395789e553531a4a565bcc61a422255ef5385b4
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 05:09:11 2014 -0400

    Send consolidated update notifications to an event organizer. This means suppressing notifications triggered by wallace replies as long as more automated replies can be expected; Use localized participant status texts in iTip messages

diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py
index 8dc3ff7..dd419f0 100644
--- a/tests/functional/test_wallace/test_007_invitationpolicy.py
+++ b/tests/functional/test_wallace/test_007_invitationpolicy.py
@@ -12,6 +12,7 @@ from wallace import module_resources
 
 from pykolab.translate import _
 from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
 from email import message_from_string
 from twisted.trial import unittest
 
@@ -177,6 +178,16 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
             'kolabinvitationpolicy': ['ACT_TENTATIVE_IF_NO_CONFLICT','ACT_SAVE_TO_CALENDAR','ACT_UPDATE']
         }
 
+        self.bob = {
+            'displayname': 'Bob Auto',
+            'mail': 'bob.auto at example.org',
+            'dn': 'uid=auto,ou=People,dc=example,dc=org',
+            'preferredlanguage': 'en_US',
+            'mailbox': 'user/bob.auto at example.org',
+            'kolabtargetfolder': 'user/bob.auto/Calendar at example.org',
+            'kolabinvitationpolicy': ['ACT_ACCEPT','ACT_UPDATE']
+        }
+
         self.external = {
             'displayname': 'Bob External',
             'mail': 'bob.external at gmail.com'
@@ -186,6 +197,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         user_add("John", "Doe", kolabinvitationpolicy=self.john['kolabinvitationpolicy'], preferredlanguage=self.john['preferredlanguage'])
         user_add("Jane", "Manager", kolabinvitationpolicy=self.jane['kolabinvitationpolicy'], preferredlanguage=self.jane['preferredlanguage'])
         user_add("Jack", "Tentative", kolabinvitationpolicy=self.jack['kolabinvitationpolicy'], preferredlanguage=self.jack['preferredlanguage'])
+        user_add("Bob", "Auto", kolabinvitationpolicy=self.bob['kolabinvitationpolicy'], preferredlanguage=self.bob['preferredlanguage'])
 
         time.sleep(1)
         from tests.functional.synchronize import synchronize_once
@@ -432,7 +444,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         start = datetime.datetime(2014,8,13, 10,0,0)
         uid = self.send_itip_invitation(self.jane['mail'], start)
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('ACCEPTED') }, self.jane['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
@@ -453,7 +465,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
     def test_002_invite_conflict_reject(self):
         uid = self.send_itip_invitation(self.jane['mail'], datetime.datetime(2014,8,13, 11,0,0), summary="test2")
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':_('DECLINED') }, self.jane['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jane['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
@@ -466,7 +478,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
 
         uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,24, 8,0,0))
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('TENTATIVE') }, self.jack['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
         self.assertIsInstance(response, email.message.Message)
 
 
@@ -474,12 +486,12 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         self.purge_mailbox(self.john['mailbox'])
 
         self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 8,0,0))
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('TENTATIVE') }, self.jack['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         # send conflicting request to jack
         uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 10,0,0), summary="test2")
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':_('DECLINED') }, self.jack['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jack['mail'])
         self.assertEqual(response, None, "No reply expected")
 
         event = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid)
@@ -494,7 +506,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         start = datetime.datetime(2014,8,14, 9,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
         uid = self.send_itip_invitation(self.jane['mail'], start)
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('ACCEPTED') }, self.jane['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
@@ -507,7 +519,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         new_start = datetime.datetime(2014,8,15, 15,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
         self.send_itip_update(self.jane['mail'], uid, new_start, summary="test", sequence=1)
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('ACCEPTED') }, self.jane['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
@@ -523,7 +535,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         start = datetime.datetime(2014,8,9, 17,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
         uid = self.send_itip_invitation(self.jack['mail'], start)
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('TENTATIVE') }, self.jack['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         # send update with new but conflicting date and incremented sequence
@@ -531,7 +543,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         new_start = datetime.datetime(2014,8,10, 9,30,0, tzinfo=pytz.timezone("Europe/Berlin"))
         self.send_itip_update(self.jack['mail'], uid, new_start, summary="test (updated)", sequence=1)
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':_('DECLINED') }, self.jack['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.jack['mail'])
         self.assertEqual(response, None)
 
         # verify re-scheduled copy in jack's calendar with NEEDS-ACTION
@@ -577,7 +589,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
 
         uid = self.send_itip_invitation(self.jane['mail'], summary="cancelled")
 
-        response = self.check_message_received(self.itip_reply_subject % { 'summary':'cancelled', 'status':_('ACCEPTED') }, self.jane['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'cancelled', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         self.send_itip_cancel(self.jane['mail'], uid, summary="cancelled")
@@ -594,13 +606,19 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         self.purge_mailbox(self.john['mailbox'])
 
         start = datetime.datetime(2014,8,12, 16,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
-        uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.jack])
+        uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.bob, self.jack])
 
         # send a reply from jane to john
         self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start)
 
         # check for notification message
-        # TODO: this notification should be suppressed until jack has replied, too
+        # this notification should be suppressed until bob has replied, too
+        notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
+        self.assertEqual(notification, None)
+
+        # send a reply from bob to john
+        self.send_itip_reply(uid, self.bob['mail'], self.john['mail'], start=start, partstat='ACCEPTED')
+
         notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
         self.assertIsInstance(notification, email.message.Message)
 
@@ -610,14 +628,14 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
 
         self.purge_mailbox(self.john['mailbox'])
 
-        # send a reply from jack to john
-        self.send_itip_reply(uid, self.jack['mail'], self.john['mail'], start=start, partstat='TENTATIVE')
+        # send a reply from bob to john
+        self.send_itip_reply(uid, self.jack['mail'], self.john['mail'], start=start, partstat='ACCEPTED')
 
+        # this triggers an additional notification
         notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
         self.assertIsInstance(notification, email.message.Message)
 
         notification_text = str(notification.get_payload());
-        self.assertIn(self.jack['mail'], notification_text)
         self.assertNotIn(_("PENDING"), notification_text)
 
 
diff --git a/tests/unit/test-012-wallace_invitationpolicy.py b/tests/unit/test-012-wallace_invitationpolicy.py
index 0b64f6a..dbe0713 100644
--- a/tests/unit/test-012-wallace_invitationpolicy.py
+++ b/tests/unit/test-012-wallace_invitationpolicy.py
@@ -142,4 +142,20 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         MIP.remove_write_lock(lock_key)
         self.assertFalse(os.path.isfile(lock_file))
 
+    def test_005_is_auto_reply(self):
+        all_manual  = [ 'ACT_MANUAL' ]
+        accept_none = [ 'ACT_REJECT' ]
+        accept_all  = [ 'ACT_ACCEPT', 'ACT_UPDATE' ]
+        accept_cond = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_REJECT_IF_CONFLICT' ]
+        accept_some = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_SAVE_TO_CALENDAR:example.org', 'ACT_REJECT_IF_CONFLICT' ]
+        accept_avail = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_REJECT_IF_CONFLICT:example.org' ]
+
+        self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':all_manual },   'domain.org'))
+        self.assertTrue(  MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_none },  'domain.org'))
+        self.assertTrue(  MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_all },   'domain.com'))
+        self.assertTrue(  MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_cond },  'domain.com'))
+        self.assertTrue(  MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_some },  'domain.com'))
+        self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_some },  'example.org'))
+        self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_avail }, 'domain.com'))
+        self.assertTrue(  MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_avail }, 'example.org'))
         
\ No newline at end of file
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
index 1426e2b..03585ee 100644
--- a/wallace/module_invitationpolicy.py
+++ b/wallace/module_invitationpolicy.py
@@ -41,6 +41,7 @@ from pykolab.conf import Conf
 from pykolab.imap import IMAP
 from pykolab.xml import to_dt
 from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
 from pykolab.itip import events_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.itip import send_reply
@@ -500,12 +501,17 @@ def user_dn_from_email_address(email_address):
         auth = Auth()
         auth.connect()
 
+    # return cached value
+    if user_dn_from_email_address.cache.has_key(email_address):
+        return user_dn_from_email_address.cache[email_address]
+
     local_domains = auth.list_domains()
 
     if not local_domains == None:
         local_domains = list(set(local_domains.keys()))
 
     if not email_address.split('@')[1] in local_domains:
+        user_dn_from_email_address.cache[email_address] = None
         return None
 
     log.debug(_("Checking if email address %r belongs to a local user") % (email_address), level=8)
@@ -517,8 +523,13 @@ def user_dn_from_email_address(email_address):
     else:
         log.debug(_("No user record(s) found for %r") % (email_address), level=9)
 
+    # remember this lookup
+    user_dn_from_email_address.cache[email_address] = user_dn
+
     return user_dn
 
+user_dn_from_email_address.cache = {}
+
 
 def get_matching_invitation_policies(receiving_user, sender_domain):
     # get user's kolabInvitationPolicy settings
@@ -843,6 +854,8 @@ def send_reply_notification(event, receiving_user):
     """
         Send a (consolidated) notification about the current participant status to organizer
     """
+    global auth
+
     import smtplib
     from email.MIMEText import MIMEText
     from email.Utils import formatdate
@@ -851,6 +864,13 @@ def send_reply_notification(event, receiving_user):
         event.uid, receiving_user['mail']
     ), level=8)
 
+    organizer = event.get_organizer()
+    orgemail = organizer.email()
+    orgname = organizer.name()
+    sender_domain = orgemail.split('@')[-1]
+
+    auto_replies_expected = 0
+    auto_replies_received = 0
     partstats = { 'ACCEPTED':[], 'TENTATIVE':[], 'DECLINED':[], 'DELEGATED':[], 'PENDING':[] }
     for attendee in event.get_attendees():
         parstat = attendee.get_participant_status(True)
@@ -859,13 +879,34 @@ def send_reply_notification(event, receiving_user):
         else:
             partstats['PENDING'].append(attendee.get_displayname())
 
-    # TODO: for every attendee, look-up its kolabinvitationpolicy and skip notification
-    # until we got replies from all automatically responding attendees
+        # look-up kolabinvitationpolicy for this attendee
+        if attendee.get_cutype() == kolabformat.CutypeResource:
+            resource_dns = auth.find_resource(attendee.get_email())
+            if isinstance(resource_dns, list):
+                attendee_dn = resource_dns[0] if len(resource_dns) > 0 else None
+            else:
+                attendee_dn = resource_dns
+        else:
+            attendee_dn = user_dn_from_email_address(attendee.get_email())
+
+        if attendee_dn:
+            attendee_rec = auth.get_entry_attributes(None, attendee_dn, ['kolabinvitationpolicy'])
+            if is_auto_reply(attendee_rec, sender_domain):
+                auto_replies_expected += 1
+                if not parstat == 'NEEDS-ACTION':
+                    auto_replies_received += 1
+
+    # skip notification until we got replies from all automatically responding attendees
+    if auto_replies_received < auto_replies_expected:
+        log.debug(_("Waiting for more automated replies (got %d of %d); skipping notification") % (
+            auto_replies_received, auto_replies_expected
+        ), level=8)
+        return
 
     roundup = ''
     for status,attendees in partstats.iteritems():
         if len(attendees) > 0:
-            roundup += "\n" + _(status) + ":\n" + "\n".join(attendees) + "\n"
+            roundup += "\n" + participant_status_label(status) + ":\n" + "\n".join(attendees) + "\n"
 
     message_text = """
         The event '%(summary)s' at %(start)s has been updated in your calendar.
@@ -882,11 +923,6 @@ def send_reply_notification(event, receiving_user):
     msg['To'] = receiving_user['mail']
     msg['Date'] = formatdate(localtime=True)
     msg['Subject'] = _('"%s" has been updated') % (event.get_summary())
-
-    organizer = event.get_organizer()
-    orgemail = organizer.email()
-    orgname = organizer.name()
-
     msg['From'] = '"%s" <%s>' % (orgname, orgemail) if orgname else orgemail
 
     smtp = smtplib.SMTP("localhost", 10027)
@@ -902,6 +938,36 @@ def send_reply_notification(event, receiving_user):
     smtp.quit()
 
 
+def is_auto_reply(user, sender_domain):
+    accept_available = False
+    accept_conflicts = False
+    for policy in get_matching_invitation_policies(user, sender_domain):
+        if policy & (ACT_ACCEPT | ACT_REJECT | ACT_DELEGATE):
+            if check_policy_condition(policy, True):
+                accept_available = True
+            if check_policy_condition(policy, False):
+                accept_conflicts = True
+
+        # we have both cases covered by a policy
+        if accept_available and accept_conflicts:
+            return True
+
+        # manual action reached
+        if policy & (ACT_MANUAL | ACT_SAVE_TO_CALENDAR):
+            return False
+
+    return False
+
+
+def check_policy_condition(policy, available):
+    condition_fulfilled = True
+    if policy & (COND_IF_AVAILABLE | COND_IF_CONFLICT):
+        condition_fulfilled = available
+    if policy & COND_IF_CONFLICT:
+        condition_fulfilled = not condition_fulfilled
+    return condition_fulfilled
+
+
 def propagate_changes_to_attendees_calendars(event):
     """
         Find and update copies of this event in all attendee's calendars


commit d13dd3849d09d1eaa4fdd906686cb1217cc23ad4
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 05:06:42 2014 -0400

    Use localized participant status texts in resource replies

diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 79ceaf2..60b6587 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -8,7 +8,9 @@ import uuid
 from pykolab.imap import IMAP
 from wallace import module_resources
 
+from pykolab.translate import _
 from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
 from email import message_from_string
 from twisted.trial import unittest
 
@@ -185,6 +187,8 @@ class TestResourceInvitation(unittest.TestCase):
 
     @classmethod
     def setup_class(self, *args, **kw):
+        self.itip_reply_subject = _("Reservation Request for %(summary)s was %(status)s")
+
         from tests.functional.purge_users import purge_users
         purge_users()
 
@@ -373,7 +377,7 @@ class TestResourceInvitation(unittest.TestCase):
     def test_002_invite_resource(self):
         uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 10,0,0))
 
-        response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -384,7 +388,7 @@ class TestResourceInvitation(unittest.TestCase):
     def test_003_invite_resource_conflict(self):
         uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 12,0,0))
 
-        response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         self.assertEqual(self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid), None)
@@ -396,7 +400,7 @@ class TestResourceInvitation(unittest.TestCase):
         uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,7,13, 12,0,0))
 
         # one of the collection members accepted the reservation
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
 
         delegatee = self.find_resource_by_email(accept['from'])
@@ -406,7 +410,7 @@ class TestResourceInvitation(unittest.TestCase):
         self.assertIsInstance(self.check_resource_calendar_event(delegatee['kolabtargetfolder'], uid), pykolab.xml.Event)
 
         # resource collection responds with a DELEGATED message
-        response = self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail'])
         self.assertIsInstance(response, email.message.Message)
         self.assertIn("ROLE=NON-PARTICIPANT;RSVP=FALSE", str(response))
 
@@ -416,13 +420,13 @@ class TestResourceInvitation(unittest.TestCase):
 
         uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,4,1, 10,0,0))
 
-        response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         self.purge_mailbox(self.john['mailbox'])
         self.send_itip_update(self.audi['mail'], uid, datetime.datetime(2014,4,1, 12,0,0)) # conflict with myself
 
-        response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -437,13 +441,13 @@ class TestResourceInvitation(unittest.TestCase):
         uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,4,24, 12,0,0))
 
         # one of the collection members accepted the reservation
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
         delegatee = self.find_resource_by_email(accept['from'])
 
         # book that resource for the next day
         self.send_itip_invitation(delegatee['mail'], datetime.datetime(2014,4,25, 14,0,0))
-        accept2 = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept2 = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
 
         # re-schedule first booking to a conflicting date
         self.purge_mailbox(self.john['mailbox'])
@@ -451,7 +455,7 @@ class TestResourceInvitation(unittest.TestCase):
         self.send_itip_update(delegatee['mail'], uid, datetime.datetime(2014,4,25, 12,0,0), template=update_template)
 
         # expect response from another member of the initially delegated collection
-        new_accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        new_accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(new_accept, email.message.Message)
 
         new_delegatee = self.find_resource_by_email(new_accept['from'])
@@ -462,7 +466,7 @@ class TestResourceInvitation(unittest.TestCase):
         self.assertIsInstance(event, pykolab.xml.Event)
 
         # old resource responds with a DELEGATED message
-        response = self.check_message_received("Reservation Request for test was DELEGATED", delegatee['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, delegatee['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         # old reservation was removed from old delegate's calendar
@@ -483,7 +487,7 @@ class TestResourceInvitation(unittest.TestCase):
         # make new reservation to the now free'd slot
         self.send_itip_invitation(self.boxter['mail'], datetime.datetime(2014,5,1, 9,0,0))
 
-        response = self.check_message_received("Reservation Request for test was ACCEPTED", self.boxter['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.boxter['mail'])
         self.assertIsInstance(response, email.message.Message)
 
 
@@ -494,7 +498,7 @@ class TestResourceInvitation(unittest.TestCase):
         uid = self.send_itip_invitation(self.cars['mail'], dt)
 
         # wait for accept notification
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
         delegatee = self.find_resource_by_email(accept['from'])
 
@@ -505,12 +509,12 @@ class TestResourceInvitation(unittest.TestCase):
         self.send_itip_update(delegatee['mail'], uid, dt, template=update_template)
 
         # get response from delegatee
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
         self.assertIn(delegatee['mail'], accept['from'])
 
         # no delegation response on updates
-        self.assertEqual(self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail']), None)
+        self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail']), None)
 
 
     def test_008_allday_reservation(self):
@@ -518,7 +522,7 @@ class TestResourceInvitation(unittest.TestCase):
 
         uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2), True)
 
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
 
         event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -526,7 +530,7 @@ class TestResourceInvitation(unittest.TestCase):
         self.assertIsInstance(event.get_start(), datetime.date)
 
         uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2, 16,0,0))
-        response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
 
@@ -537,19 +541,19 @@ class TestResourceInvitation(unittest.TestCase):
         uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,20, 12,0,0),
             template=itip_recurring.replace(";COUNT=10", ""))
 
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
 
         # check non-recurring against recurring
         uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,13, 10,0,0))
-        response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+        response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
         self.purge_mailbox(self.john['mailbox'])
 
         # check recurring against recurring
         uid3 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,22, 8,0,0), template=itip_recurring)
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         self.assertIsInstance(accept, email.message.Message)
 
 
@@ -564,7 +568,7 @@ class TestResourceInvitation(unittest.TestCase):
         itip_invalid = itip_invitation.replace("DTSTART;", "X-DTSTART;")
         self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,24, 19,30,0), template=itip_invalid)
 
-        self.assertEqual(self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail']), None)
+        self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail']), None)
 
 
     def test_011_owner_info(self):
@@ -572,7 +576,7 @@ class TestResourceInvitation(unittest.TestCase):
 
         self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,6,19, 16,0,0))
 
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'])
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room1['mail'])
         self.assertIsInstance(accept, email.message.Message)
         respose_text = str(accept.get_payload(0))
         self.assertIn(self.jane['mail'], respose_text)
@@ -584,7 +588,7 @@ class TestResourceInvitation(unittest.TestCase):
 
         self.send_itip_invitation(self.room2['mail'], datetime.datetime(2014,6,19, 16,0,0))
 
-        accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'])
+        accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room2['mail'])
         self.assertIsInstance(accept, email.message.Message)
         respose_text = str(accept.get_payload(0))
         self.assertIn(self.jane['mail'], respose_text)
@@ -598,12 +602,12 @@ class TestResourceInvitation(unittest.TestCase):
         self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,8,4, 13,0,0))
 
         # check notification message sent to resource owner (jane)
-        notify = self.check_message_received("Booking for %s has been ACCEPTED" % (self.room1['cn']), self.room1['mail'], self.jane['mailbox'])
+        notify = self.check_message_received(_('Booking for %s has been %s') % (self.room1['cn'], participant_status_label('ACCEPTED')), self.room1['mail'], self.jane['mailbox'])
         self.assertIsInstance(notify, email.message.Message)
 
         notification_text = str(notify.get_payload())
         self.assertIn(self.john['mail'], notification_text)
-        self.assertIn("ACCEPTED", notification_text)
+        self.assertIn(participant_status_label('ACCEPTED'), notification_text)
 
         self.purge_mailbox(self.john['mailbox'])
 
@@ -611,9 +615,9 @@ class TestResourceInvitation(unittest.TestCase):
         self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,8,4, 12,30,0))
 
         # one of the collection members accepted the reservation
-        accepted = self.check_message_received("Reservation Request for test was ACCEPTED")
+        accepted = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
         delegatee = self.find_resource_by_email(accepted['from'])
 
-        notify = self.check_message_received("Booking for %s has been ACCEPTED" % (delegatee['cn']), delegatee['mail'], self.jane['mailbox'])
+        notify = self.check_message_received(_('Booking for %s has been %s') % (delegatee['cn'], participant_status_label('ACCEPTED')), delegatee['mail'], self.jane['mailbox'])
         self.assertIsInstance(notify, email.message.Message)
         self.assertIn(self.john['mail'], notification_text)
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index b99cf1a..dba2653 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -42,6 +42,7 @@ from pykolab.conf import Conf
 from pykolab.imap import IMAP
 from pykolab.xml import to_dt
 from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
 from pykolab.itip import events_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.translate import _
@@ -905,7 +906,7 @@ def reservation_response_text(status, owner):
         *** This is an automated response, please do not reply! ***
         
         We hereby inform you that your reservation was %s.
-    """) % (_(status))
+    """) % (participant_status_label(status))
 
     if owner:
         message_text += _("""
@@ -950,7 +951,7 @@ def send_owner_notification(resource, owner, itip_event, success=True):
         msg['To'] = owner['mail']
         msg['From'] = resource['mail']
         msg['Date'] = formatdate(localtime=True)
-        msg['Subject'] = _('Booking for %s has been %s') % (resource['cn'], _(status) if success else _('failed'))
+        msg['Subject'] = _('Booking for %s has been %s') % (resource['cn'], participant_status_label(status) if success else _('failed'))
 
         smtp = smtplib.SMTP("localhost", 10027)
 
@@ -986,7 +987,7 @@ def owner_notification_text(resource, owner, event, success):
         'resource': resource['cn'],
         'summary': event.get_summary(),
         'date': event.get_date_text(),
-        'status': _(status),
+        'status': participant_status_label(status),
         'orgname': organizer.name(),
         'orgemail': organizer.email()
     }


commit a44dcfb0690305fe42f3b374cef0fbced465d64d
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 05:00:39 2014 -0400

    Fix all-day event date checks

diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py
index 8cf6435..17da24e 100644
--- a/pykolab/itip/__init__.py
+++ b/pykolab/itip/__init__.py
@@ -3,6 +3,7 @@ import pykolab
 
 from pykolab.xml import to_dt
 from pykolab.xml import event_from_ical
+from pykolab.xml import participant_status_label
 from pykolab.translate import _
 
 log = pykolab.getLogger('pykolab.wallace')
@@ -144,7 +145,7 @@ def check_event_conflict(kolab_event, itip_event):
         return conflict
 
     _es = to_dt(kolab_event.get_start())
-    _ee = to_dt(kolab_event.get_end())
+    _ee = to_dt(kolab_event.get_ical_dtend())  # use iCal style end date: next day for all-day events
 
     # naive loops to check for collisions in (recurring) events
     # TODO: compare recurrence rules directly (e.g. matching time slot or weekday or monthday)
@@ -208,10 +209,10 @@ def send_reply(from_address, itip_events, response_text, subject=None):
         participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee)
 
         event_summary = itip_event['xml'].get_summary()
-        message_text = response_text % { 'summary':event_summary, 'status':_(participant_status), 'name':attendee.get_name() }
+        message_text = response_text % { 'summary':event_summary, 'status':participant_status_label(participant_status), 'name':attendee.get_name() }
 
         if subject is not None:
-            subject = subject % { 'summary':event_summary, 'status':_(participant_status), 'name':attendee.get_name() }
+            subject = subject % { 'summary':event_summary, 'status':participant_status_label(participant_status), 'name':attendee.get_name() }
 
         try:
             message = itip_event['xml'].to_message_itip(from_address,
diff --git a/tests/unit/test-011-itip.py b/tests/unit/test-011-itip.py
index abbaa92..a120fd2 100644
--- a/tests/unit/test-011-itip.py
+++ b/tests/unit/test-011-itip.py
@@ -357,6 +357,12 @@ class TestITip(unittest.TestCase):
         event.set_uid(itip_event['uid'])
         self.assertFalse(itip.check_event_conflict(event, itip_event), "No conflict for same UID")
 
+        allday = Event()
+        allday.set_start(datetime.date(2012,7,13))
+        allday.set_end(datetime.date(2012,7,13))
+
+        self.assertTrue(itip.check_event_conflict(allday, itip_event), "Conflicting allday event")
+
         event2 = Event()
         event2.set_start(datetime.datetime(2012,7,13, 10,0,0, tzinfo=pytz.timezone("US/Central")))
         event2.set_end(datetime.datetime(2012,7,13, 11,0,0, tzinfo=pytz.timezone("US/Central")))


commit 214bde4656f3e3a10096ba54dfa2704f4e8de719
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 10 04:35:36 2014 -0400

    New function to get a localized string for iCal participant status

diff --git a/pykolab/xml/__init__.py b/pykolab/xml/__init__.py
index 64b06ae..2bb42d4 100644
--- a/pykolab/xml/__init__.py
+++ b/pykolab/xml/__init__.py
@@ -1,5 +1,6 @@
 from attendee import Attendee
 from attendee import InvalidAttendeeParticipantStatusError
+from attendee import participant_status_label
 
 from contact import Contact
 from contact_reference import ContactReference
diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 579158e..220ab8c 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -4,6 +4,26 @@ from pykolab.translate import _
 
 from contact_reference import ContactReference
 
+participant_status_labels = {
+        "NEEDS-ACTION": _("Needs Action"),
+        "ACCEPTED": _("Accepted"),
+        "DECLINED": _("Declined"),
+        "TENTATIVE": _("Tentatively Accepted"),
+        "DELEGATED": _("Delegated"),
+        "COMPLETED": _("Completed"),
+        "IN-PROCESS": _("In Process"),
+        # support integer values, too
+        kolabformat.PartNeedsAction: _("Needs Action"),
+        kolabformat.PartAccepted: _("Accepted"),
+        kolabformat.PartDeclined: _("Declined"),
+        kolabformat.PartTentative: _("Tentatively Accepted"),
+        kolabformat.PartDelegated: _("Delegated"),
+    }
+
+def participant_status_label(status):
+    return participant_status_labels[status] if participant_status_labels.has_key(status) else status
+
+
 class Attendee(kolabformat.Attendee):
     cutype_map = {
             "INDIVIDUAL": kolabformat.CutypeIndividual,
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 98a91aa..4ac4997 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -12,6 +12,7 @@ import pykolab
 from pykolab import constants
 from pykolab import utils
 from pykolab.xml import utils as xmlutils
+from pykolab.xml import participant_status_label
 from pykolab.translate import _
 
 from os import path
@@ -928,7 +929,7 @@ class Event(object):
         msg['Date'] = formatdate(localtime=True)
 
         if subject is None:
-            subject = _("Invitation for %s was %s") % (self.get_summary(), _(participant_status))
+            subject = _("Invitation for %s was %s") % (self.get_summary(), participant_status_label(participant_status))
 
         msg["Subject"] = subject
 
diff --git a/tests/unit/test-002-attendee.py b/tests/unit/test-002-attendee.py
index 9da93c7..8bcee3c 100644
--- a/tests/unit/test-002-attendee.py
+++ b/tests/unit/test-002-attendee.py
@@ -1,7 +1,9 @@
 import datetime
 import unittest
+import kolabformat
 
 from pykolab.xml import Attendee
+from pykolab.xml import participant_status_label
 
 class TestEventXML(unittest.TestCase):
     attendee = Attendee("jane at doe.org")
@@ -101,5 +103,10 @@ class TestEventXML(unittest.TestCase):
         self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 2][0], "INDIVIDUAL")
         self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 3][0], "RESOURCE")
 
+    def test_018_partstat_label(self):
+        self.assertEqual(participant_status_label('NEEDS-ACTION'), "Needs Action")
+        self.assertEqual(participant_status_label(kolabformat.PartTentative), "Tentatively Accepted")
+        self.assertEqual(participant_status_label('UNKNOWN'), "UNKNOWN")
+
 if __name__ == '__main__':
     unittest.main()





More information about the commits mailing list