1.1 --- a/imiptools/client.py Fri Oct 23 00:08:38 2015 +0200
1.2 +++ b/imiptools/client.py Fri Oct 23 00:26:46 2015 +0200
1.3 @@ -564,6 +564,176 @@
1.4 self.update_sender(attendee_attr)
1.5 return attendee_attr
1.6
1.7 + # Communication methods.
1.8 +
1.9 + def send_message(self, parts, sender, obj, from_organiser, bcc_sender):
1.10 +
1.11 + """
1.12 + Send the given 'parts' to the appropriate recipients, also sending a
1.13 + copy to the 'sender'. The 'obj' together with the 'from_organiser' value
1.14 + (which indicates whether the organiser is sending this message) are used
1.15 + to determine the recipients of the message.
1.16 + """
1.17 +
1.18 + # As organiser, send an invitation to attendees, excluding oneself if
1.19 + # also attending. The updated event will be saved by the outgoing
1.20 + # handler.
1.21 +
1.22 + organiser = get_uri(obj.get_value("ORGANIZER"))
1.23 + attendees = uri_values(obj.get_values("ATTENDEE"))
1.24 +
1.25 + if from_organiser:
1.26 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.27 + else:
1.28 + recipients = [get_address(organiser)]
1.29 +
1.30 + # Since the outgoing handler updates this user's free/busy details,
1.31 + # the stored details will probably not have the updated details at
1.32 + # this point, so we update our copy for serialisation as the bundled
1.33 + # free/busy object.
1.34 +
1.35 + freebusy = self.store.get_freebusy(self.user)
1.36 + self.update_freebusy(freebusy, self.user, from_organiser)
1.37 +
1.38 + # Bundle free/busy information if appropriate.
1.39 +
1.40 + part = self.get_freebusy_part(freebusy)
1.41 + if part:
1.42 + parts.append(part)
1.43 +
1.44 + if recipients or bcc_sender:
1.45 + self._send_message(sender, recipients, parts, bcc_sender)
1.46 +
1.47 + def _send_message(self, sender, recipients, parts, bcc_sender):
1.48 +
1.49 + """
1.50 + Send a message, explicitly specifying the 'sender' as an outgoing BCC
1.51 + recipient since the generic calendar user will be the actual sender.
1.52 + """
1.53 +
1.54 + if not bcc_sender:
1.55 + message = self.messenger.make_outgoing_message(parts, recipients)
1.56 + self.messenger.sendmail(recipients, message.as_string())
1.57 + else:
1.58 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.59 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.60 +
1.61 + def send_message_to_self(self, parts):
1.62 +
1.63 + "Send a message composed of the given 'parts' to the given user."
1.64 +
1.65 + sender = get_address(self.user)
1.66 + message = self.messenger.make_outgoing_message(parts, [sender])
1.67 + self.messenger.sendmail([sender], message.as_string())
1.68 +
1.69 + # Action methods.
1.70 +
1.71 + def process_declined_counter(self, attendee):
1.72 +
1.73 + "Process a declined counter-proposal."
1.74 +
1.75 + # Obtain the counter-proposal for the attendee.
1.76 +
1.77 + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee)
1.78 + if not obj:
1.79 + return False
1.80 +
1.81 + method = "DECLINECOUNTER"
1.82 + self.update_senders(obj=obj)
1.83 + obj.update_dtstamp()
1.84 + obj.update_sequence(False)
1.85 + self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True)
1.86 + return True
1.87 +
1.88 + def process_received_request(self, changed=False):
1.89 +
1.90 + """
1.91 + Process the current request for the current user. Return whether any
1.92 + action was taken. If 'changed' is set to a true value, or if 'attendees'
1.93 + is specified and differs from the stored attendees, a counter-proposal
1.94 + will be sent instead of a reply.
1.95 + """
1.96 +
1.97 + # Reply only on behalf of this user.
1.98 +
1.99 + attendee_attr = self.update_participation()
1.100 +
1.101 + if not attendee_attr:
1.102 + return False
1.103 +
1.104 + if not changed:
1.105 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
1.106 + else:
1.107 + self.update_senders()
1.108 +
1.109 + self.update_dtstamp()
1.110 + self.update_sequence(False)
1.111 + self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True)
1.112 + return True
1.113 +
1.114 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
1.115 +
1.116 + """
1.117 + Process the current request, sending a created request of the given
1.118 + 'method' to attendees. Return whether any action was taken.
1.119 +
1.120 + If 'to_cancel' is specified, a list of participants to be sent cancel
1.121 + messages is provided.
1.122 +
1.123 + If 'to_unschedule' is specified, a list of periods to be unscheduled is
1.124 + provided.
1.125 + """
1.126 +
1.127 + # Here, the organiser should be the current user.
1.128 +
1.129 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.130 +
1.131 + self.update_sender(organiser_attr)
1.132 + self.update_senders()
1.133 + self.update_dtstamp()
1.134 + self.update_sequence(True)
1.135 +
1.136 + if method == "REQUEST":
1.137 + methods, parts = self.get_message_parts(self.obj, "REQUEST")
1.138 +
1.139 + # Add message parts with cancelled occurrence information.
1.140 +
1.141 + unscheduled_parts = self.get_unscheduled_parts(to_unschedule)
1.142 +
1.143 + # Send the updated event, along with a cancellation for each of the
1.144 + # unscheduled occurrences.
1.145 +
1.146 + self.send_message(parts + unscheduled_parts, get_address(organiser), self.obj, True, False)
1.147 +
1.148 + # Since the organiser can update the SEQUENCE but this can leave any
1.149 + # mail/calendar client lagging, issue a PUBLISH message to the
1.150 + # user's address.
1.151 +
1.152 + methods, parts = self.get_message_parts(self.obj, "PUBLISH")
1.153 + self.send_message_to_self(parts + unscheduled_parts)
1.154 +
1.155 + # When cancelling, replace the attendees with those for whom the event
1.156 + # is now cancelled.
1.157 +
1.158 + if method == "CANCEL" or to_cancel:
1.159 + if to_cancel:
1.160 + obj = self.obj.copy()
1.161 + obj["ATTENDEE"] = to_cancel
1.162 + else:
1.163 + obj = self.obj
1.164 +
1.165 + # Send a cancellation to all uninvited attendees.
1.166 +
1.167 + parts = [obj.to_part("CANCEL")]
1.168 + self.send_message(parts, get_address(organiser), obj, True, False)
1.169 +
1.170 + # Issue a CANCEL message to the user's address.
1.171 +
1.172 + if method == "CANCEL":
1.173 + self.send_message_to_self(parts)
1.174 +
1.175 + return True
1.176 +
1.177 # Object-related tests.
1.178
1.179 def is_recognised_organiser(self, organiser):
2.1 --- a/imipweb/resource.py Fri Oct 23 00:08:38 2015 +0200
2.2 +++ b/imipweb/resource.py Fri Oct 23 00:26:46 2015 +0200
2.3 @@ -21,8 +21,8 @@
2.4
2.5 from datetime import datetime, timedelta
2.6 from imiptools.client import Client, ClientForObject
2.7 -from imiptools.data import get_address, get_uri, uri_item, uri_values
2.8 -from imiptools.dates import format_datetime, get_recurrence_start_point, to_date
2.9 +from imiptools.data import get_uri
2.10 +from imiptools.dates import format_datetime, to_date
2.11 from imiptools.period import remove_period, remove_affected_period
2.12 from imipweb.data import event_period_from_period, form_period_from_period, \
2.13 FormDate, PeriodError
2.14 @@ -221,176 +221,6 @@
2.15 user = self.env.get_user()
2.16 ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger)
2.17
2.18 - # Communication methods.
2.19 -
2.20 - def send_message(self, parts, sender, obj, from_organiser, bcc_sender):
2.21 -
2.22 - """
2.23 - Send the given 'parts' to the appropriate recipients, also sending a
2.24 - copy to the 'sender'. The 'obj' together with the 'from_organiser' value
2.25 - (which indicates whether the organiser is sending this message) are used
2.26 - to determine the recipients of the message.
2.27 - """
2.28 -
2.29 - # As organiser, send an invitation to attendees, excluding oneself if
2.30 - # also attending. The updated event will be saved by the outgoing
2.31 - # handler.
2.32 -
2.33 - organiser = get_uri(obj.get_value("ORGANIZER"))
2.34 - attendees = uri_values(obj.get_values("ATTENDEE"))
2.35 -
2.36 - if from_organiser:
2.37 - recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
2.38 - else:
2.39 - recipients = [get_address(organiser)]
2.40 -
2.41 - # Since the outgoing handler updates this user's free/busy details,
2.42 - # the stored details will probably not have the updated details at
2.43 - # this point, so we update our copy for serialisation as the bundled
2.44 - # free/busy object.
2.45 -
2.46 - freebusy = self.store.get_freebusy(self.user)
2.47 - self.update_freebusy(freebusy, self.user, from_organiser)
2.48 -
2.49 - # Bundle free/busy information if appropriate.
2.50 -
2.51 - part = self.get_freebusy_part(freebusy)
2.52 - if part:
2.53 - parts.append(part)
2.54 -
2.55 - if recipients or bcc_sender:
2.56 - self._send_message(sender, recipients, parts, bcc_sender)
2.57 -
2.58 - def _send_message(self, sender, recipients, parts, bcc_sender):
2.59 -
2.60 - """
2.61 - Send a message, explicitly specifying the 'sender' as an outgoing BCC
2.62 - recipient since the generic calendar user will be the actual sender.
2.63 - """
2.64 -
2.65 - if not bcc_sender:
2.66 - message = self.messenger.make_outgoing_message(parts, recipients)
2.67 - self.messenger.sendmail(recipients, message.as_string())
2.68 - else:
2.69 - message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
2.70 - self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
2.71 -
2.72 - def send_message_to_self(self, parts):
2.73 -
2.74 - "Send a message composed of the given 'parts' to the given user."
2.75 -
2.76 - sender = get_address(self.user)
2.77 - message = self.messenger.make_outgoing_message(parts, [sender])
2.78 - self.messenger.sendmail([sender], message.as_string())
2.79 -
2.80 - # Action methods.
2.81 -
2.82 - def process_declined_counter(self, attendee):
2.83 -
2.84 - "Process a declined counter-proposal."
2.85 -
2.86 - # Obtain the counter-proposal for the attendee.
2.87 -
2.88 - obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee)
2.89 - if not obj:
2.90 - return False
2.91 -
2.92 - method = "DECLINECOUNTER"
2.93 - self.update_senders(obj=obj)
2.94 - obj.update_dtstamp()
2.95 - obj.update_sequence(False)
2.96 - self._send_message(get_address(self.user), [get_address(attendee)], [obj.to_part(method)], True)
2.97 - return True
2.98 -
2.99 - def process_received_request(self, changed=False):
2.100 -
2.101 - """
2.102 - Process the current request for the current user. Return whether any
2.103 - action was taken. If 'changed' is set to a true value, or if 'attendees'
2.104 - is specified and differs from the stored attendees, a counter-proposal
2.105 - will be sent instead of a reply.
2.106 - """
2.107 -
2.108 - # Reply only on behalf of this user.
2.109 -
2.110 - attendee_attr = self.update_participation()
2.111 -
2.112 - if not attendee_attr:
2.113 - return False
2.114 -
2.115 - if not changed:
2.116 - self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
2.117 - else:
2.118 - self.update_senders()
2.119 -
2.120 - self.update_dtstamp()
2.121 - self.update_sequence(False)
2.122 - self.send_message([self.obj.to_part(changed and "COUNTER" or "REPLY")], get_address(self.user), self.obj, False, True)
2.123 - return True
2.124 -
2.125 - def process_created_request(self, method, to_cancel=None, to_unschedule=None):
2.126 -
2.127 - """
2.128 - Process the current request, sending a created request of the given
2.129 - 'method' to attendees. Return whether any action was taken.
2.130 -
2.131 - If 'to_cancel' is specified, a list of participants to be sent cancel
2.132 - messages is provided.
2.133 -
2.134 - If 'to_unschedule' is specified, a list of periods to be unscheduled is
2.135 - provided.
2.136 - """
2.137 -
2.138 - # Here, the organiser should be the current user.
2.139 -
2.140 - organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
2.141 -
2.142 - self.update_sender(organiser_attr)
2.143 - self.update_senders()
2.144 - self.update_dtstamp()
2.145 - self.update_sequence(True)
2.146 -
2.147 - if method == "REQUEST":
2.148 - methods, parts = self.get_message_parts(self.obj, "REQUEST")
2.149 -
2.150 - # Add message parts with cancelled occurrence information.
2.151 -
2.152 - unscheduled_parts = self.get_unscheduled_parts(to_unschedule)
2.153 -
2.154 - # Send the updated event, along with a cancellation for each of the
2.155 - # unscheduled occurrences.
2.156 -
2.157 - self.send_message(parts + unscheduled_parts, get_address(organiser), self.obj, True, False)
2.158 -
2.159 - # Since the organiser can update the SEQUENCE but this can leave any
2.160 - # mail/calendar client lagging, issue a PUBLISH message to the
2.161 - # user's address.
2.162 -
2.163 - methods, parts = self.get_message_parts(self.obj, "PUBLISH")
2.164 - self.send_message_to_self(parts + unscheduled_parts)
2.165 -
2.166 - # When cancelling, replace the attendees with those for whom the event
2.167 - # is now cancelled.
2.168 -
2.169 - if method == "CANCEL" or to_cancel:
2.170 - if to_cancel:
2.171 - obj = self.obj.copy()
2.172 - obj["ATTENDEE"] = to_cancel
2.173 - else:
2.174 - obj = self.obj
2.175 -
2.176 - # Send a cancellation to all uninvited attendees.
2.177 -
2.178 - parts = [obj.to_part("CANCEL")]
2.179 - self.send_message(parts, get_address(organiser), obj, True, False)
2.180 -
2.181 - # Issue a CANCEL message to the user's address.
2.182 -
2.183 - if method == "CANCEL":
2.184 - self.send_message_to_self(parts)
2.185 -
2.186 - return True
2.187 -
2.188 class FormUtilities:
2.189
2.190 "Utility methods resource mix-in."