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