3 commits - pykolab/xml tests/functional wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Tue Mar 25 16:44:21 CET 2014


 pykolab/xml/attendee.py                                       |    2 
 pykolab/xml/event.py                                          |    4 
 tests/functional/test_wallace/test_005_resource_invitation.py |   44 +-
 wallace/module_resources.py                                   |  217 ++++++----
 4 files changed, 190 insertions(+), 77 deletions(-)

New commits:
commit 8e87571d9e0c42c504164f6fadfdc2a6eca47d24
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Mar 25 11:44:14 2014 -0400

    Delegate to another resource collection member if the booked resource is unavailable for the re-scheduled date

diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index aad5fd5..6d7748c 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -76,9 +76,9 @@ SEQUENCE:4
 ATTENDEE;CN=Company Cars;PARTSTAT=DELEGATED;ROLE=NON-PARTICIPANT;CUTYPE=IND
  IVIDUAL;RSVP=TRUE;DELEGATED-TO=resource-car-audia4 at example.org:mailto:reso
  urce-collection-companycars at example.org
-ATTENDEE;CN=Audi A4;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUA
- L;RSVP=TRUE;DELEGATED-FROM=resource-collection-companycars at example.org:mai
- lto:resource-car-audia4 at example.org
+ATTENDEE;CN=The Delegate;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;CUTYPE=INDI
+ VIDUAL;RSVP=TRUE;DELEGATED-FROM=resource-collection-companycars at example.or
+ g:mailto:resource-car-audia4 at example.org
 ORGANIZER;CN=:mailto:john.doe at example.org
 DESCRIPTION:Sent to %s
 END:VEVENT
@@ -440,6 +440,44 @@ class TestResourceInvitation(unittest.TestCase):
         self.assertEqual(event.get_sequence(), 2)
 
 
+    def test_005_rescheduling_collection(self):
+        self.purge_mailbox(self.john['mailbox'])
+
+        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")
+        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")
+
+        # re-schedule first booking to a conflicting date
+        self.purge_mailbox(self.john['mailbox'])
+        update_template = itip_delegated.replace("resource-car-audia4 at example.org", delegatee['mail'])
+        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")
+        self.assertIsInstance(new_accept, email.message.Message)
+
+        new_delegatee = self.find_resource_by_email(new_accept['from'])
+        self.assertNotEqual(delegatee['mail'], new_delegatee['mail'])
+
+        # event now booked into new delegate's calendar
+        event = self.check_resource_calendar_event(new_delegatee['kolabtargetfolder'], uid)
+        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'])
+        self.assertIsInstance(response, email.message.Message)
+
+        # old reservation was removed from old delegate's calendar
+        self.assertEqual(self.check_resource_calendar_event(delegatee['kolabtargetfolder'], uid), None)
+
+
     def test_006_cancelling_revervation(self):
         self.purge_mailbox(self.john['mailbox'])
 
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index ede976e..ed78024 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -214,33 +214,8 @@ def execute(*args, **kw):
 
 
     # Get the resource details, which includes details on the IMAP folder
-    resources = {}
-    for resource_dn in list(set(resource_dns)):
-        # Get the attributes for the record
-        # See if it is a resource collection
-        #   If it is, expand to individual resources
-        #   If it is not, ...
-        resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*'])
-        resource_attrs['dn'] = resource_dn
-        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(
-                            None,
-                            uniquemember,
-                            ['*']
-                        )
-
-                    if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
-                        resource_attrs['dn'] = uniquemember
-                        resources[uniquemember] = resource_attrs
-                        resources[uniquemember]['memberof'] = resource_dn
-                        if not resource_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
-                            resources[uniquemember]['owner'] = resources[resource_dn]['owner']
-                        resource_dns.append(uniquemember)
-        else:
-            resources[resource_dn] = resource_attrs
+    # This may append resource collection members to recource_dns
+    resources = get_resource_records(resource_dns)
 
     log.debug(_("Resources: %r; %r") % (resource_dns, resources), level=8)
 
@@ -276,12 +251,68 @@ def execute(*args, **kw):
         return
 
 
-    # For each resource, determine if any of the events in question is in
-    # conflict.
-    #
+    # do the magic for the receiving attendee
+    (available_resource, itip_event) = check_availability(itip_events, resource_dns, resources, receiving_attendee)
+
+    # accept reservation
+    if available_resource is not None:
+        if available_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+            # replace existing copy of this event
+            if len(available_resource['existing_events']) > 0:
+                for uid in available_resource['existing_events']:
+                    delete_resource_event(uid, available_resource)
+
+            log.debug(_("Accept invitation for individual resource %r / %r") % (available_resource['dn'], available_resource['mail']), level=8)
+
+            # check if reservation was delegated
+            original_resource = None
+            if available_resource['mail'] != receiving_resource['mail'] and receiving_attendee.get_participant_status() == kolabformat.PartDelegated:
+                original_resource = receiving_resource
+
+            accept_reservation_request(itip_event, available_resource, original_resource)
+
+        else:
+            # This must have been a resource collection originally.
+            # We have inserted the reference to the original resource
+            # record in 'memberof'.
+            if available_resource.has_key('memberof'):
+                original_resource = resources[available_resource['memberof']]
+
+                if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+                    #
+                    # Delegate:
+                    # - delegator: the original resource collection
+                    # - delegatee: the target resource
+                    #
+                    itip_event['xml'].delegate(original_resource['mail'], available_resource['mail'], available_resource['cn'])
+
+                    # set delegator to NON-PARTICIPANT and RSVP=FALSE
+                    delegator = itip_event['xml'].get_attendee_by_email(original_resource['mail'])
+                    delegator.set_role(kolabformat.NonParticipant)
+                    delegator.set_rsvp(False)
+
+                    log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], available_resource['mail']), level=8)
+                    accept_reservation_request(itip_event, available_resource, original_resource)
+
+    # decline reservation
+    else:
+        resource = resources[resource_dns[0]]  # this is the receiving resource record
+        decline_reservation_request(itip_event, resource)
+
+    cleanup()
+
+    os.unlink(filepath)
+
+
+def check_availability(itip_events, resource_dns, resources, receiving_attendee=None):
+    """
+        For each resource, determine if any of the events in question are in conflict.
+    """
+
     # Store the (first) conflicting event(s) alongside the resource information.
     start = time.time()
     num_messages = 0
+    available_resource = None
 
     for resource in resources.keys():
         # skip this for resource collections
@@ -338,31 +369,44 @@ def execute(*args, **kw):
             for itip_event in itip_events:
                 # Now we have the event that was conflicting
                 if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
-                    decline_reservation_request(itip_event, resources[resource])
-                    done = True
+                    # this resource initially was delegated from a collection ?
+                    if receiving_attendee and receiving_attendee.get_email() == resources[resource]['mail'] \
+                            and len(receiving_attendee.get_delegated_from()) > 0:
+                        for delegator in receiving_attendee.get_delegated_from():
+                            collection_data = get_resource_collection(delegator.email())
+                            if collection_data is not None:
+                                # check if another collection member is available
+                                (available_resource, dummy) = check_availability(itip_events, collection_data[0], collection_data[1])
+                                break
+
+                        if available_resource is not None:
+                            log.debug(_("Delegate to another resource collection member: %r to %r") % \
+                                (resources[resource]['mail'], available_resource['mail']), level=8)
+
+                            # set this new resource as delegate for the receiving_attendee
+                            itip_event['xml'].delegate(resources[resource]['mail'], available_resource['mail'], available_resource['cn'])
+
+                            # set delegator to NON-PARTICIPANT and RSVP=FALSE
+                            receiving_attendee.set_role(kolabformat.NonParticipant)
+                            receiving_attendee.set_rsvp(False)
+                            receiving_attendee.setDelegatedFrom([])
+
+                            # remove existing_events as we now delegated back to the collection
+                            if len(resources[resource]['existing_events']) > 0:
+                                for uid in resources[resource]['existing_events']:
+                                    delete_resource_event(uid, resources[resource])
 
-                else:
-                    # This must have been a resource collection originally.
-                    # We have inserted the reference to the original resource
-                    # record in 'memberof'.
-                    if resources[resource].has_key('memberof'):
-                        original_resource = resources[resources[resource]['memberof']]
+                    done = True
 
-                    if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
-                        decline_reservation_request(itip_event, original_resource)
-                        done = True
+                if done:
+                    break
 
         else:
             # No conflicts, go accept
             for itip_event in itip_events:
+                # directly invited resource
                 if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
-                    # replace existing copy of this event
-                    if len(resources[resource]['existing_events']) > 0:
-                        for uid in resources[resource]['existing_events']:
-                            delete_resource_event(uid, resources[resource])
-
-                    log.debug(_("Accept invitation for individual resource %r / %r") % (resource, resources[resource]['mail']), level=8)
-                    accept_reservation_request(itip_event, resources[resource])
+                    available_resource = resources[resource]
                     done = True
 
                 else:
@@ -372,26 +416,8 @@ def execute(*args, **kw):
                     if resources[resource].has_key('memberof'):
                         original_resource = resources[resources[resource]['memberof']]
 
-                        # Randomly selects a target resource from the resource collection.
-                        _target_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
-
-                        log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], _target_resource['mail']), level=8)
-
-                    if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
-                        #
-                        # Delegate:
-                        #
-                        # - delegator: the original resource collection
-                        # - delegatee: the target resource
-                        #
-                        itip_event['xml'].delegate(original_resource['mail'], _target_resource['mail'], _target_resource['cn'])
-
-                        # set delegator to NON-PARTICIPANT and RSVP=FALSE
-                        delegator = itip_event['xml'].get_attendee_by_email(original_resource['mail'])
-                        delegator.set_role(kolabformat.NonParticipant)
-                        delegator.set_rsvp(False)
-
-                        accept_reservation_request(itip_event, _target_resource, original_resource)
+                        # Randomly select a target resource from the resource collection.
+                        available_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
                         done = True
 
         if done:
@@ -399,9 +425,7 @@ def execute(*args, **kw):
 
     # end for resource in resource_dns:
 
-    cleanup()
-
-    os.unlink(filepath)
+    return (available_resource, itip_event)
 
 
 def read_resource_calendar(resource_rec, itip_events):
@@ -864,6 +888,57 @@ def resource_records_from_itip_events(itip_events, recipient_email=None):
     return resource_records
 
 
+def get_resource_records(resource_dns):
+    """
+        Get the resource details, which includes details on the IMAP folder
+    """
+    global auth
+
+    resources = {}
+    for resource_dn in list(set(resource_dns)):
+        # Get the attributes for the record
+        # See if it is a resource collection
+        #   If it is, expand to individual resources
+        #   If it is not, ...
+        resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*'])
+        resource_attrs['dn'] = resource_dn
+        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(
+                            None,
+                            uniquemember,
+                            ['*']
+                        )
+
+                    if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
+                        resource_attrs['dn'] = uniquemember
+                        resources[uniquemember] = resource_attrs
+                        resources[uniquemember]['memberof'] = resource_dn
+                        if not resource_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
+                            resources[uniquemember]['owner'] = resources[resource_dn]['owner']
+                        resource_dns.append(uniquemember)
+        else:
+            resources[resource_dn] = resource_attrs
+
+    return resources
+
+
+def get_resource_collection(email_address):
+    """
+        
+    """
+    resource_dns = resource_record_from_email_address(email_address)
+    if len(resource_dns) == 1:
+        resource_attrs = auth.get_entry_attributes(None, resource_dns[0], ['objectclass'])
+        if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
+            resources = get_resource_records(resource_dns)
+            return (resource_dns, resources)
+
+    return None
+
+
 def get_resource_owner(resource):
     """
         Get this resource's owner record


commit b142fff6696b137e8c7a0465281d962594766941
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Mar 19 22:34:54 2014 -0400

    Pass additional attributes to delegated-from contact reference

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index c62bbb1..877f810 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -67,7 +67,7 @@ class Attendee(kolabformat.Attendee):
             self.set_cutype(cutype)
 
         if ical_params and ical_params.has_key('DELEGATED-FROM'):
-            self.delegate_from(Attendee(str(ical_params['DELEGATED-FROM'])))
+            self.delegate_from(Attendee(str(ical_params['DELEGATED-FROM']), role=self.get_role(), cutype=self.get_cutype()))
 
         if ical_params and ical_params.has_key('DELEGATED-TO'):
             self.delegate_to(Attendee(str(ical_params['DELEGATED-TO'])))


commit cb6f96492e6abbb265e691a58b794257c75e4b7f
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Mar 19 22:34:13 2014 -0400

    Fix accessor methods for organizer attributes

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 2218400..280d2fb 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -761,8 +761,8 @@ class Event(object):
 
         elif method == "REQUEST":
             organizer = self.get_organizer()
-            email = organizer.get_email()
-            name = organizer.get_name()
+            email = organizer.email()
+            name = organizer.name()
             if not name:
                 msg_from = email
             else:




More information about the commits mailing list