1.1 --- a/imipweb/client.py Mon Oct 05 19:47:10 2015 +0200
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,154 +0,0 @@
1.4 -#!/usr/bin/env python
1.5 -
1.6 -"""
1.7 -Interaction with the mail system for the manager interface.
1.8 -
1.9 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
1.10 -
1.11 -This program is free software; you can redistribute it and/or modify it under
1.12 -the terms of the GNU General Public License as published by the Free Software
1.13 -Foundation; either version 3 of the License, or (at your option) any later
1.14 -version.
1.15 -
1.16 -This program is distributed in the hope that it will be useful, but WITHOUT
1.17 -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 -details.
1.20 -
1.21 -You should have received a copy of the GNU General Public License along with
1.22 -this program. If not, see <http://www.gnu.org/licenses/>.
1.23 -"""
1.24 -
1.25 -from imiptools.client import ClientForObject
1.26 -from imiptools.data import get_address, get_uri, uri_item, uri_values
1.27 -from imiptools.dates import format_datetime
1.28 -
1.29 -class ManagerClient(ClientForObject):
1.30 -
1.31 - """
1.32 - A content client for use by the manager, as opposed to operating within the
1.33 - mail processing pipeline.
1.34 - """
1.35 -
1.36 - # Communication methods.
1.37 -
1.38 - def send_message(self, method, sender, from_organiser, parts=None):
1.39 -
1.40 - """
1.41 - Create a full calendar object employing the given 'method', and send it
1.42 - to the appropriate recipients, also sending a copy to the 'sender'. The
1.43 - 'from_organiser' value indicates whether the organiser is sending this
1.44 - message (and is thus equivalent to "as organiser").
1.45 - """
1.46 -
1.47 - parts = parts or [self.obj.to_part(method)]
1.48 -
1.49 - # As organiser, send an invitation to attendees, excluding oneself if
1.50 - # also attending. The updated event will be saved by the outgoing
1.51 - # handler.
1.52 -
1.53 - organiser = get_uri(self.obj.get_value("ORGANIZER"))
1.54 - attendees = uri_values(self.obj.get_values("ATTENDEE"))
1.55 -
1.56 - if from_organiser:
1.57 - recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
1.58 - else:
1.59 - recipients = [get_address(organiser)]
1.60 -
1.61 - # Since the outgoing handler updates this user's free/busy details,
1.62 - # the stored details will probably not have the updated details at
1.63 - # this point, so we update our copy for serialisation as the bundled
1.64 - # free/busy object.
1.65 -
1.66 - freebusy = self.store.get_freebusy(self.user)
1.67 - self.update_freebusy(freebusy, self.user, from_organiser)
1.68 -
1.69 - # Bundle free/busy information if appropriate.
1.70 -
1.71 - part = self.get_freebusy_part(freebusy)
1.72 - if part:
1.73 - parts.append(part)
1.74 -
1.75 - # Explicitly specify the outgoing BCC recipient since we are sending as
1.76 - # the generic calendar user.
1.77 -
1.78 - message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
1.79 - self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
1.80 -
1.81 - # Action methods.
1.82 -
1.83 - def process_received_request(self):
1.84 -
1.85 - """
1.86 - Process the current request for the current user. Return whether any
1.87 - action was taken.
1.88 - """
1.89 -
1.90 - # Reply only on behalf of this user.
1.91 -
1.92 - attendee_attr = self.update_participation(self.obj)
1.93 -
1.94 - if not attendee_attr:
1.95 - return False
1.96 -
1.97 - self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
1.98 - self.update_dtstamp()
1.99 - self.set_sequence(False)
1.100 - self.send_message("REPLY", get_address(self.user), from_organiser=False)
1.101 - return True
1.102 -
1.103 - def process_created_request(self, method, to_cancel=None, to_unschedule=None):
1.104 -
1.105 - """
1.106 - Process the current request, sending a created request of the given
1.107 - 'method' to attendees. Return whether any action was taken.
1.108 -
1.109 - If 'to_cancel' is specified, a list of participants to be sent cancel
1.110 - messages is provided.
1.111 -
1.112 - If 'to_unschedule' is specified, a list of periods to be unscheduled is
1.113 - provided.
1.114 - """
1.115 -
1.116 - # Here, the organiser should be the current user.
1.117 -
1.118 - organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
1.119 -
1.120 - self.update_sender(organiser_attr)
1.121 - self.update_dtstamp()
1.122 - self.set_sequence(True)
1.123 -
1.124 - parts = [self.obj.to_part(method)]
1.125 -
1.126 - # Add message parts with cancelled occurrence information.
1.127 - # NOTE: This could probably be merged with the updated event message.
1.128 -
1.129 - if to_unschedule:
1.130 - obj = self.obj.copy()
1.131 - obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
1.132 -
1.133 - for p in to_unschedule:
1.134 - if not p.origin:
1.135 - continue
1.136 - obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), p.get_start_attr())]
1.137 - parts.append(obj.to_part("CANCEL"))
1.138 -
1.139 - # Send the updated event, along with a cancellation for each of the
1.140 - # unscheduled occurrences.
1.141 -
1.142 - self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
1.143 -
1.144 - # When cancelling, replace the attendees with those for whom the event
1.145 - # is now cancelled.
1.146 -
1.147 - if to_cancel:
1.148 - obj = self.obj.copy()
1.149 - obj["ATTENDEE"] = to_cancel
1.150 -
1.151 - # Send a cancellation to all uninvited attendees.
1.152 -
1.153 - self.send_message("CANCEL", get_address(organiser), from_organiser=True)
1.154 -
1.155 - return True
1.156 -
1.157 -# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imipweb/event.py Mon Oct 05 19:47:10 2015 +0200
2.2 +++ b/imipweb/event.py Mon Oct 05 20:10:38 2015 +0200
2.3 @@ -24,7 +24,6 @@
2.4 from imiptools.mail import Messenger
2.5 from imiptools.period import have_conflict
2.6 from imipweb.data import EventPeriod, event_period_from_period, FormPeriod, PeriodError
2.7 -from imipweb.client import ManagerClient
2.8 from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject
2.9
2.10 class EventPageFragment(ResourceClientForObject, DateTimeFormUtilities, FormUtilities):
2.11 @@ -677,8 +676,7 @@
2.12 "A request handler for the event page."
2.13
2.14 def __init__(self, resource=None, messenger=None):
2.15 - ResourceClientForObject.__init__(self, resource)
2.16 - self.messenger = messenger or Messenger()
2.17 + ResourceClientForObject.__init__(self, resource, messenger or Messenger())
2.18
2.19 # Request logic methods.
2.20
2.21 @@ -776,18 +774,16 @@
2.22
2.23 if reply or invite or cancel:
2.24
2.25 - client = ManagerClient(self.obj, self.user, self.messenger)
2.26 -
2.27 # Process the object and remove it from the list of requests.
2.28
2.29 - if reply and client.process_received_request():
2.30 + if reply and self.process_received_request():
2.31 self.remove_request(self.uid, self.recurrenceid)
2.32
2.33 elif self.is_organiser() and (invite or cancel):
2.34
2.35 # Invitation, uninvitation and unscheduling...
2.36
2.37 - if client.process_created_request(
2.38 + if self.process_created_request(
2.39 invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule):
2.40
2.41 self.remove_request(self.uid, self.recurrenceid)
3.1 --- a/imipweb/resource.py Mon Oct 05 19:47:10 2015 +0200
3.2 +++ b/imipweb/resource.py Mon Oct 05 20:10:38 2015 +0200
3.3 @@ -21,7 +21,7 @@
3.4
3.5 from datetime import datetime, timedelta
3.6 from imiptools.client import Client, ClientForObject
3.7 -from imiptools.data import get_uri, uri_values
3.8 +from imiptools.data import get_address, get_uri, uri_item, uri_values
3.9 from imiptools.dates import format_datetime, get_recurrence_start_point, to_date
3.10 from imiptools.period import remove_period, remove_affected_period
3.11 from imipweb.data import event_period_from_period, form_period_from_period, FormDate
3.12 @@ -216,10 +216,131 @@
3.13
3.14 "A Web application resource and calendar client for a specific object."
3.15
3.16 - def __init__(self, resource=None):
3.17 + def __init__(self, resource=None, messenger=None):
3.18 Resource.__init__(self, resource)
3.19 user = self.env.get_user()
3.20 - ClientForObject.__init__(self, None, user and get_uri(user) or None)
3.21 + ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger)
3.22 +
3.23 + # Communication methods.
3.24 +
3.25 + def send_message(self, method, sender, from_organiser, parts=None):
3.26 +
3.27 + """
3.28 + Create a full calendar object employing the given 'method', and send it
3.29 + to the appropriate recipients, also sending a copy to the 'sender'. The
3.30 + 'from_organiser' value indicates whether the organiser is sending this
3.31 + message (and is thus equivalent to "as organiser").
3.32 + """
3.33 +
3.34 + parts = parts or [self.obj.to_part(method)]
3.35 +
3.36 + # As organiser, send an invitation to attendees, excluding oneself if
3.37 + # also attending. The updated event will be saved by the outgoing
3.38 + # handler.
3.39 +
3.40 + organiser = get_uri(self.obj.get_value("ORGANIZER"))
3.41 + attendees = uri_values(self.obj.get_values("ATTENDEE"))
3.42 +
3.43 + if from_organiser:
3.44 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
3.45 + else:
3.46 + recipients = [get_address(organiser)]
3.47 +
3.48 + # Since the outgoing handler updates this user's free/busy details,
3.49 + # the stored details will probably not have the updated details at
3.50 + # this point, so we update our copy for serialisation as the bundled
3.51 + # free/busy object.
3.52 +
3.53 + freebusy = self.store.get_freebusy(self.user)
3.54 + self.update_freebusy(freebusy, self.user, from_organiser)
3.55 +
3.56 + # Bundle free/busy information if appropriate.
3.57 +
3.58 + part = self.get_freebusy_part(freebusy)
3.59 + if part:
3.60 + parts.append(part)
3.61 +
3.62 + # Explicitly specify the outgoing BCC recipient since we are sending as
3.63 + # the generic calendar user.
3.64 +
3.65 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
3.66 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
3.67 +
3.68 + # Action methods.
3.69 +
3.70 + def process_received_request(self):
3.71 +
3.72 + """
3.73 + Process the current request for the current user. Return whether any
3.74 + action was taken.
3.75 + """
3.76 +
3.77 + # Reply only on behalf of this user.
3.78 +
3.79 + attendee_attr = self.update_participation(self.obj)
3.80 +
3.81 + if not attendee_attr:
3.82 + return False
3.83 +
3.84 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
3.85 + self.update_dtstamp()
3.86 + self.set_sequence(False)
3.87 + self.send_message("REPLY", get_address(self.user), from_organiser=False)
3.88 + return True
3.89 +
3.90 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
3.91 +
3.92 + """
3.93 + Process the current request, sending a created request of the given
3.94 + 'method' to attendees. Return whether any action was taken.
3.95 +
3.96 + If 'to_cancel' is specified, a list of participants to be sent cancel
3.97 + messages is provided.
3.98 +
3.99 + If 'to_unschedule' is specified, a list of periods to be unscheduled is
3.100 + provided.
3.101 + """
3.102 +
3.103 + # Here, the organiser should be the current user.
3.104 +
3.105 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
3.106 +
3.107 + self.update_sender(organiser_attr)
3.108 + self.update_dtstamp()
3.109 + self.set_sequence(True)
3.110 +
3.111 + parts = [self.obj.to_part(method)]
3.112 +
3.113 + # Add message parts with cancelled occurrence information.
3.114 + # NOTE: This could probably be merged with the updated event message.
3.115 +
3.116 + if to_unschedule:
3.117 + obj = self.obj.copy()
3.118 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
3.119 +
3.120 + for p in to_unschedule:
3.121 + if not p.origin:
3.122 + continue
3.123 + obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), p.get_start_attr())]
3.124 + parts.append(obj.to_part("CANCEL"))
3.125 +
3.126 + # Send the updated event, along with a cancellation for each of the
3.127 + # unscheduled occurrences.
3.128 +
3.129 + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
3.130 +
3.131 + # When cancelling, replace the attendees with those for whom the event
3.132 + # is now cancelled.
3.133 +
3.134 + if to_cancel:
3.135 + obj = self.obj.copy()
3.136 + obj["ATTENDEE"] = to_cancel
3.137 +
3.138 + # Send a cancellation to all uninvited attendees.
3.139 +
3.140 + self.send_message("CANCEL", get_address(organiser), from_organiser=True)
3.141 +
3.142 + return True
3.143
3.144 class FormUtilities:
3.145