pykolab/xml tests/functional wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Sun Jul 20 17:47:12 CEST 2014


 pykolab/xml/event.py                                          |   10 
 tests/functional/resource_func.py                             |    4 
 tests/functional/test_wallace/test_005_resource_add.py        |   17 +
 tests/functional/test_wallace/test_005_resource_invitation.py |   37 +--
 wallace/module_resources.py                                   |  122 +++++++++-
 5 files changed, 166 insertions(+), 24 deletions(-)

New commits:
commit 5038b40a73f111293abff38a82f6bc1764d164ba
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jul 9 03:35:53 2014 -0400

    Send owner notifications for resource bookings (#3167)

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 65eb818..de9e4d9 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -299,6 +299,16 @@ class Event(object):
                 dt = self.get_start() + duration
         return dt
 
+    def get_date_text(self, date_format='%Y-%m-%d', time_format='%H:%M %Z'):
+        start = self.get_start()
+        end = self.get_end()
+        if start.date() == end.date():
+            end_format = time_format
+        else:
+            end_format = date_format + " " + time_format
+
+        return "%s - %s" % (start.strftime(date_format + " " + time_format), end.strftime(end_format))
+
     def get_exception_dates(self):
         return map(lambda _: xmlutils.from_cdatetime(_, True), self.event.exceptionDates())
 
diff --git a/tests/functional/resource_func.py b/tests/functional/resource_func.py
index 43aca96..ac80360 100644
--- a/tests/functional/resource_func.py
+++ b/tests/functional/resource_func.py
@@ -4,7 +4,7 @@ from pykolab import wap_client
 
 conf = pykolab.getConf()
 
-def resource_add(type, cn, members=None, owner=None):
+def resource_add(type, cn, members=None, owner=None, **kw):
     if type == None or type == '':
         raise Exception
 
@@ -18,6 +18,8 @@ def resource_add(type, cn, members=None, owner=None):
         'owner': owner
     }
 
+    resource_details.update(kw)
+
     result = wap_client.authenticate(conf.get('ldap', 'bind_dn'), conf.get('ldap', 'bind_pw'), conf.get('kolab', 'primary_domain'))
 
     type_id = 0
diff --git a/tests/functional/test_wallace/test_005_resource_add.py b/tests/functional/test_wallace/test_005_resource_add.py
index 2de60fb..fc7f3ed 100644
--- a/tests/functional/test_wallace/test_005_resource_add.py
+++ b/tests/functional/test_wallace/test_005_resource_add.py
@@ -29,8 +29,8 @@ class TestResourceAdd(unittest.TestCase):
         funcs.purge_resources()
         self.audi = funcs.resource_add("car", "Audi A4")
         self.passat = funcs.resource_add("car", "VW Passat")
-        self.boxter = funcs.resource_add("car", "Porsche Boxter S")
-        self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
+        self.boxter = funcs.resource_add("car", "Porsche Boxter S", kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
+        self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ], kolabinvitationpolicy='ACT_ACCEPT')
 
         from tests.functional.synchronize import synchronize_once
         synchronize_once()
@@ -56,3 +56,16 @@ class TestResourceAdd(unittest.TestCase):
         attrs = auth.get_entry_attributes(None, self.cars['dn'], ['*'])
         self.assertIn('groupofuniquenames', attrs['objectclass'])
         self.assertEqual(len(attrs['uniquemember']), 3)
+        self.assertEqual(attrs['kolabinvitationpolicy'], 'ACT_ACCEPT')
+
+    def test_003_get_resource_records(self):
+        resource_dns = module_resources.resource_record_from_email_address(self.cars['mail'])
+        self.assertEqual(resource_dns[0], self.cars['dn'])
+
+        resources = module_resources.get_resource_records(resource_dns)
+        self.assertEqual(len(resources), 4)
+
+        # check for (inherited) kolabinvitationpolicy values (bitmasks)
+        self.assertEqual(resources[self.cars['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+        self.assertEqual(resources[self.audi['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+        self.assertEqual(resources[self.boxter['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT_AND_NOTIFY])
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index d9f2d41..79ceaf2 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -214,9 +214,9 @@ class TestResourceInvitation(unittest.TestCase):
         self.boxter = funcs.resource_add("car", "Porsche Boxter S")
         self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
 
-        self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'])
+        self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
         self.room2 = funcs.resource_add("confroom", "Conference Room B-222")
-        self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'])
+        self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
 
         time.sleep(1)
         from tests.functional.synchronize import synchronize_once
@@ -353,12 +353,10 @@ class TestResourceInvitation(unittest.TestCase):
 
     def find_resource_by_email(self, email):
         resource = None
-        if (email.find(self.audi['mail']) >= 0):
-            resource = self.audi
-        if (email.find(self.passat['mail']) >= 0):
-            resource = self.passat
-        if (email.find(self.boxter['mail']) >= 0):
-            resource = self.boxter
+        for r in [self.audi, self.passat, self.boxter, self.room1, self.room2]:
+            if (email.find(r['mail']) >= 0):
+                resource = r
+                break
         return resource
 
 
@@ -593,20 +591,29 @@ class TestResourceInvitation(unittest.TestCase):
         self.assertIn(self.jane['displayname'], respose_text)
 
 
-    def TODO_test_012_owner_notification(self):
+    def test_012_owner_notification(self):
         self.purge_mailbox(self.john['mailbox'])
         self.purge_mailbox(self.jane['mailbox'])
 
-        self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,5,4, 13,0,0))
+        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("Reservation Request for test was ACCEPTED", self.room1['mail'], self.jane['mailbox'])
+        notify = self.check_message_received("Booking for %s has been ACCEPTED" % (self.room1['cn']), self.room1['mail'], self.jane['mailbox'])
         self.assertIsInstance(notify, email.message.Message)
-        self.assertEqual(notify['From'], self.room1['mail'])
-        self.assertEqual(notify['Cc'], self.jane['mail'])
+
+        notification_text = str(notify.get_payload())
+        self.assertIn(self.john['mail'], notification_text)
+        self.assertIn("ACCEPTED", notification_text)
+
+        self.purge_mailbox(self.john['mailbox'])
 
         # check notification sent to collection owner (jane)
-        self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,5,4, 12,30,0))
+        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")
+        delegatee = self.find_resource_by_email(accepted['from'])
 
-        notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'], self.jane['mailbox'])
+        notify = self.check_message_received("Booking for %s has been ACCEPTED" % (delegatee['cn']), 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 3864f7c..5e07552 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -46,6 +46,18 @@ from pykolab.itip import events_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.translate import _
 
+# define some contstants used in the code below
+COND_NOTIFY = 256
+ACT_MANUAL  = 1
+ACT_ACCEPT  = 2
+ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY
+
+policy_name_map = {
+    'ACT_MANUAL':            ACT_MANUAL,
+    'ACT_ACCEPT':            ACT_ACCEPT,
+    'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY
+}
+
 log = pykolab.getLogger('pykolab.wallace')
 conf = pykolab.getConf()
 
@@ -513,7 +525,11 @@ def accept_reservation_request(itip_event, resource, delegator=None):
         level=8
     )
 
-    send_response(delegator['mail'] if delegator else resource['mail'], itip_event, get_resource_owner(resource))
+    owner = get_resource_owner(resource)
+    send_response(delegator['mail'] if delegator else resource['mail'], itip_event, owner)
+
+    if owner:
+        send_owner_notification(resource, owner, itip_event, saved)
 
 
 def decline_reservation_request(itip_event, resource):
@@ -527,8 +543,12 @@ def decline_reservation_request(itip_event, resource):
         "DECLINED"
     )
 
+    owner = get_resource_owner(resource)
     send_response(resource['mail'], itip_event, get_resource_owner(resource))
 
+    if owner:
+        send_owner_notification(resource, owner, itip_event, True)
+
 
 def save_resource_event(itip_event, resource):
     """
@@ -749,21 +769,25 @@ def get_resource_records(resource_dns):
         #   If it is not, ...
         resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*'])
         resource_attrs['dn'] = resource_dn
+        parse_kolabinvitationpolicy(resource_attrs)
+
         if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
             if resource_attrs.has_key('uniquemember'):
                 resources[resource_dn] = resource_attrs
                 for uniquemember in resource_attrs['uniquemember']:
-                    resource_attrs = auth.get_entry_attributes(
+                    member_attrs = auth.get_entry_attributes(
                             None,
                             uniquemember,
                             ['*']
                         )
 
-                    if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
-                        resource_attrs['dn'] = uniquemember
-                        resources[uniquemember] = resource_attrs
+                    if 'kolabsharedfolder' in [x.lower() for x in member_attrs['objectclass']]:
+                        member_attrs['dn'] = uniquemember
+                        parse_kolabinvitationpolicy(member_attrs, resource_attrs)
+
+                        resources[uniquemember] = member_attrs
                         resources[uniquemember]['memberof'] = resource_dn
-                        if not resource_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
+                        if not member_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
                             resources[uniquemember]['owner'] = resources[resource_dn]['owner']
                         resource_dns.append(uniquemember)
         else:
@@ -772,6 +796,16 @@ def get_resource_records(resource_dns):
     return resources
 
 
+def parse_kolabinvitationpolicy(attrs, parent=None):
+    if attrs.has_key('kolabinvitationpolicy'):
+        if not isinstance(attrs['kolabinvitationpolicy'], list):
+            attrs['kolabinvitationpolicy'] = [attrs['kolabinvitationpolicy']]
+        attrs['kolabinvitationpolicy'] = [policy_name_map[p] for p in attrs['kolabinvitationpolicy'] if policy_name_map.has_key(p)]
+
+    elif isinstance(parent, dict) and parent.has_key('kolabinvitationpolicy'):
+        attrs['kolabinvitationpolicy'] = parent['kolabinvitationpolicy']
+
+
 def get_resource_collection(email_address):
     """
         
@@ -878,3 +912,79 @@ def reservation_response_text(status, owner):
         """) % (owner['cn'], owner['mail'], owner['telephoneNumber'] if owner.has_key('telephoneNumber') else '')
     
     return message_text
+
+
+def send_owner_notification(resource, owner, itip_event, success=True):
+    """
+        Send a reservation notification to the resource owner
+    """
+    import smtplib
+    from pykolab import utils
+    from email.MIMEText import MIMEText
+    from email.Utils import formatdate
+
+    notify = False
+    status = itip_event['xml'].get_attendee_by_email(resource['mail']).get_participant_status(True)
+
+    if resource.has_key('kolabinvitationpolicy'):
+        for policy in resource['kolabinvitationpolicy']:
+            # TODO: distingish ACCEPTED / DECLINED status notifications?
+            if policy & COND_NOTIFY and owner['mail']:
+                notify = True
+                break
+
+    if notify or not success:
+        log.debug(
+            _("Sending booking notification for event %r to %r from %r") % (
+                itip_event['uid'], owner['mail'], resource['cn']
+            ),
+            level=8
+        )
+
+        message_text = owner_notification_text(resource, owner, itip_event['xml'], success)
+
+        msg = MIMEText(utils.stripped_message(message_text))
+
+        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'))
+
+        smtp = smtplib.SMTP("localhost", 10027)
+
+        if conf.debuglevel > 8:
+            smtp.set_debuglevel(True)
+
+        try:
+            smtp.sendmail(resource['mail'], owner['mail'], msg.as_string())
+        except Exception, e:
+            log.error(_("SMTP sendmail error: %r") % (e))
+
+        smtp.quit()
+
+def owner_notification_text(resource, owner, event, success):
+    organizer = event.get_organizer()
+    status = event.get_attendee_by_email(resource['mail']).get_participant_status(True)
+
+    if success:
+        message_text = _("""
+            The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s.
+
+            *** This is an automated message, sent to you as the resource owner. ***
+        """)
+    else:
+        message_text = _("""
+            A reservation request for %(resource)s could not be processed automatically.
+            Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s.
+
+            *** This is an automated message, sent to you as the resource owner. ***
+        """)
+
+    return message_text % {
+        'resource': resource['cn'],
+        'summary': event.get_summary(),
+        'date': event.get_date_text(),
+        'status': _(status),
+        'orgname': organizer.name(),
+        'orgemail': organizer.email()
+    }




More information about the commits mailing list