1.1 --- a/imipweb/resource.py Mon Oct 05 19:47:10 2015 +0200
1.2 +++ b/imipweb/resource.py Mon Oct 05 20:10:38 2015 +0200
1.3 @@ -21,7 +21,7 @@
1.4
1.5 from datetime import datetime, timedelta
1.6 from imiptools.client import Client, ClientForObject
1.7 -from imiptools.data import get_uri, uri_values
1.8 +from imiptools.data import get_address, get_uri, uri_item, uri_values
1.9 from imiptools.dates import format_datetime, get_recurrence_start_point, to_date
1.10 from imiptools.period import remove_period, remove_affected_period
1.11 from imipweb.data import event_period_from_period, form_period_from_period, FormDate
1.12 @@ -216,10 +216,131 @@
1.13
1.14 "A Web application resource and calendar client for a specific object."
1.15
1.16 - def __init__(self, resource=None):
1.17 + def __init__(self, resource=None, messenger=None):
1.18 Resource.__init__(self, resource)
1.19 user = self.env.get_user()
1.20 - ClientForObject.__init__(self, None, user and get_uri(user) or None)
1.21 + ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger)
1.22 +
1.23 + # Communication methods.
1.24 +
1.25 + def send_message(self, method, sender, from_organiser, parts=None):
1.26 +
1.27 + """
1.28 + Create a full calendar object employing the given 'method', and send it
1.29 + to the appropriate recipients, also sending a copy to the 'sender'. The
1.30 + 'from_organiser' value indicates whether the organiser is sending this
1.31 + message (and is thus equivalent to "as organiser").
1.32 + """
1.33 +
1.34 + parts = parts or [self.obj.to_part(method)]
1.35 +
1.36 + # As organiser, send an invitation to attendees, excluding oneself if
1.37 + # also attending. The updated event will be saved by the outgoing
1.38 + # handler.
1.39 +
1.40 + organiser = get_uri(self.obj.get_value("ORGANIZER"))
1.41 + attendees = uri_values(self.obj.get_values("ATTENDEE"))
1.42 +
1.43 + if from_organiser:
1.44 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.45 + else:
1.46 + recipients = [get_address(organiser)]
1.47 +
1.48 + # Since the outgoing handler updates this user's free/busy details,
1.49 + # the stored details will probably not have the updated details at
1.50 + # this point, so we update our copy for serialisation as the bundled
1.51 + # free/busy object.
1.52 +
1.53 + freebusy = self.store.get_freebusy(self.user)
1.54 + self.update_freebusy(freebusy, self.user, from_organiser)
1.55 +
1.56 + # Bundle free/busy information if appropriate.
1.57 +
1.58 + part = self.get_freebusy_part(freebusy)
1.59 + if part:
1.60 + parts.append(part)
1.61 +
1.62 + # Explicitly specify the outgoing BCC recipient since we are sending as
1.63 + # the generic calendar user.
1.64 +
1.65 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.66 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.67 +
1.68 + # Action methods.
1.69 +
1.70 + def process_received_request(self):
1.71 +
1.72 + """
1.73 + Process the current request for the current user. Return whether any
1.74 + action was taken.
1.75 + """
1.76 +
1.77 + # Reply only on behalf of this user.
1.78 +
1.79 + attendee_attr = self.update_participation(self.obj)
1.80 +
1.81 + if not attendee_attr:
1.82 + return False
1.83 +
1.84 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
1.85 + self.update_dtstamp()
1.86 + self.set_sequence(False)
1.87 + self.send_message("REPLY", get_address(self.user), from_organiser=False)
1.88 + return True
1.89 +
1.90 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
1.91 +
1.92 + """
1.93 + Process the current request, sending a created request of the given
1.94 + 'method' to attendees. Return whether any action was taken.
1.95 +
1.96 + If 'to_cancel' is specified, a list of participants to be sent cancel
1.97 + messages is provided.
1.98 +
1.99 + If 'to_unschedule' is specified, a list of periods to be unscheduled is
1.100 + provided.
1.101 + """
1.102 +
1.103 + # Here, the organiser should be the current user.
1.104 +
1.105 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.106 +
1.107 + self.update_sender(organiser_attr)
1.108 + self.update_dtstamp()
1.109 + self.set_sequence(True)
1.110 +
1.111 + parts = [self.obj.to_part(method)]
1.112 +
1.113 + # Add message parts with cancelled occurrence information.
1.114 + # NOTE: This could probably be merged with the updated event message.
1.115 +
1.116 + if to_unschedule:
1.117 + obj = self.obj.copy()
1.118 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
1.119 +
1.120 + for p in to_unschedule:
1.121 + if not p.origin:
1.122 + continue
1.123 + obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), p.get_start_attr())]
1.124 + parts.append(obj.to_part("CANCEL"))
1.125 +
1.126 + # Send the updated event, along with a cancellation for each of the
1.127 + # unscheduled occurrences.
1.128 +
1.129 + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
1.130 +
1.131 + # When cancelling, replace the attendees with those for whom the event
1.132 + # is now cancelled.
1.133 +
1.134 + if to_cancel:
1.135 + obj = self.obj.copy()
1.136 + obj["ATTENDEE"] = to_cancel
1.137 +
1.138 + # Send a cancellation to all uninvited attendees.
1.139 +
1.140 + self.send_message("CANCEL", get_address(organiser), from_organiser=True)
1.141 +
1.142 + return True
1.143
1.144 class FormUtilities:
1.145