# HG changeset patch # User Paul Boddie # Date 1444068638 -7200 # Node ID a28e6b149b4487ad2f54ff31f9fe3dc2800fb58e # Parent 406ea519958617d8ed07cc7fbf506d05ca559fce Removed the separate Web client class, moving its methods into a base class. diff -r 406ea5199586 -r a28e6b149b44 imipweb/client.py --- a/imipweb/client.py Mon Oct 05 19:47:10 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -#!/usr/bin/env python - -""" -Interaction with the mail system for the manager interface. - -Copyright (C) 2014, 2015 Paul Boddie - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 3 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . -""" - -from imiptools.client import ClientForObject -from imiptools.data import get_address, get_uri, uri_item, uri_values -from imiptools.dates import format_datetime - -class ManagerClient(ClientForObject): - - """ - A content client for use by the manager, as opposed to operating within the - mail processing pipeline. - """ - - # Communication methods. - - def send_message(self, method, sender, from_organiser, parts=None): - - """ - Create a full calendar object employing the given 'method', and send it - to the appropriate recipients, also sending a copy to the 'sender'. The - 'from_organiser' value indicates whether the organiser is sending this - message (and is thus equivalent to "as organiser"). - """ - - parts = parts or [self.obj.to_part(method)] - - # As organiser, send an invitation to attendees, excluding oneself if - # also attending. The updated event will be saved by the outgoing - # handler. - - organiser = get_uri(self.obj.get_value("ORGANIZER")) - attendees = uri_values(self.obj.get_values("ATTENDEE")) - - if from_organiser: - recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] - else: - recipients = [get_address(organiser)] - - # Since the outgoing handler updates this user's free/busy details, - # the stored details will probably not have the updated details at - # this point, so we update our copy for serialisation as the bundled - # free/busy object. - - freebusy = self.store.get_freebusy(self.user) - self.update_freebusy(freebusy, self.user, from_organiser) - - # Bundle free/busy information if appropriate. - - part = self.get_freebusy_part(freebusy) - if part: - parts.append(part) - - # Explicitly specify the outgoing BCC recipient since we are sending as - # the generic calendar user. - - message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) - self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) - - # Action methods. - - def process_received_request(self): - - """ - Process the current request for the current user. Return whether any - action was taken. - """ - - # Reply only on behalf of this user. - - attendee_attr = self.update_participation(self.obj) - - if not attendee_attr: - return False - - self.obj["ATTENDEE"] = [(self.user, attendee_attr)] - self.update_dtstamp() - self.set_sequence(False) - self.send_message("REPLY", get_address(self.user), from_organiser=False) - return True - - def process_created_request(self, method, to_cancel=None, to_unschedule=None): - - """ - Process the current request, sending a created request of the given - 'method' to attendees. Return whether any action was taken. - - If 'to_cancel' is specified, a list of participants to be sent cancel - messages is provided. - - If 'to_unschedule' is specified, a list of periods to be unscheduled is - provided. - """ - - # Here, the organiser should be the current user. - - organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) - - self.update_sender(organiser_attr) - self.update_dtstamp() - self.set_sequence(True) - - parts = [self.obj.to_part(method)] - - # Add message parts with cancelled occurrence information. - # NOTE: This could probably be merged with the updated event message. - - if to_unschedule: - obj = self.obj.copy() - obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"]) - - for p in to_unschedule: - if not p.origin: - continue - obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), p.get_start_attr())] - parts.append(obj.to_part("CANCEL")) - - # Send the updated event, along with a cancellation for each of the - # unscheduled occurrences. - - self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts) - - # When cancelling, replace the attendees with those for whom the event - # is now cancelled. - - if to_cancel: - obj = self.obj.copy() - obj["ATTENDEE"] = to_cancel - - # Send a cancellation to all uninvited attendees. - - self.send_message("CANCEL", get_address(organiser), from_organiser=True) - - return True - -# vim: tabstop=4 expandtab shiftwidth=4 diff -r 406ea5199586 -r a28e6b149b44 imipweb/event.py --- a/imipweb/event.py Mon Oct 05 19:47:10 2015 +0200 +++ b/imipweb/event.py Mon Oct 05 20:10:38 2015 +0200 @@ -24,7 +24,6 @@ from imiptools.mail import Messenger from imiptools.period import have_conflict from imipweb.data import EventPeriod, event_period_from_period, FormPeriod, PeriodError -from imipweb.client import ManagerClient from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject class EventPageFragment(ResourceClientForObject, DateTimeFormUtilities, FormUtilities): @@ -677,8 +676,7 @@ "A request handler for the event page." def __init__(self, resource=None, messenger=None): - ResourceClientForObject.__init__(self, resource) - self.messenger = messenger or Messenger() + ResourceClientForObject.__init__(self, resource, messenger or Messenger()) # Request logic methods. @@ -776,18 +774,16 @@ if reply or invite or cancel: - client = ManagerClient(self.obj, self.user, self.messenger) - # Process the object and remove it from the list of requests. - if reply and client.process_received_request(): + if reply and self.process_received_request(): self.remove_request(self.uid, self.recurrenceid) elif self.is_organiser() and (invite or cancel): # Invitation, uninvitation and unscheduling... - if client.process_created_request( + if self.process_created_request( invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule): self.remove_request(self.uid, self.recurrenceid) diff -r 406ea5199586 -r a28e6b149b44 imipweb/resource.py --- a/imipweb/resource.py Mon Oct 05 19:47:10 2015 +0200 +++ b/imipweb/resource.py Mon Oct 05 20:10:38 2015 +0200 @@ -21,7 +21,7 @@ from datetime import datetime, timedelta from imiptools.client import Client, ClientForObject -from imiptools.data import get_uri, uri_values +from imiptools.data import get_address, get_uri, uri_item, uri_values from imiptools.dates import format_datetime, get_recurrence_start_point, to_date from imiptools.period import remove_period, remove_affected_period from imipweb.data import event_period_from_period, form_period_from_period, FormDate @@ -216,10 +216,131 @@ "A Web application resource and calendar client for a specific object." - def __init__(self, resource=None): + def __init__(self, resource=None, messenger=None): Resource.__init__(self, resource) user = self.env.get_user() - ClientForObject.__init__(self, None, user and get_uri(user) or None) + ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger) + + # Communication methods. + + def send_message(self, method, sender, from_organiser, parts=None): + + """ + Create a full calendar object employing the given 'method', and send it + to the appropriate recipients, also sending a copy to the 'sender'. The + 'from_organiser' value indicates whether the organiser is sending this + message (and is thus equivalent to "as organiser"). + """ + + parts = parts or [self.obj.to_part(method)] + + # As organiser, send an invitation to attendees, excluding oneself if + # also attending. The updated event will be saved by the outgoing + # handler. + + organiser = get_uri(self.obj.get_value("ORGANIZER")) + attendees = uri_values(self.obj.get_values("ATTENDEE")) + + if from_organiser: + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user] + else: + recipients = [get_address(organiser)] + + # Since the outgoing handler updates this user's free/busy details, + # the stored details will probably not have the updated details at + # this point, so we update our copy for serialisation as the bundled + # free/busy object. + + freebusy = self.store.get_freebusy(self.user) + self.update_freebusy(freebusy, self.user, from_organiser) + + # Bundle free/busy information if appropriate. + + part = self.get_freebusy_part(freebusy) + if part: + parts.append(part) + + # Explicitly specify the outgoing BCC recipient since we are sending as + # the generic calendar user. + + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender) + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender) + + # Action methods. + + def process_received_request(self): + + """ + Process the current request for the current user. Return whether any + action was taken. + """ + + # Reply only on behalf of this user. + + attendee_attr = self.update_participation(self.obj) + + if not attendee_attr: + return False + + self.obj["ATTENDEE"] = [(self.user, attendee_attr)] + self.update_dtstamp() + self.set_sequence(False) + self.send_message("REPLY", get_address(self.user), from_organiser=False) + return True + + def process_created_request(self, method, to_cancel=None, to_unschedule=None): + + """ + Process the current request, sending a created request of the given + 'method' to attendees. Return whether any action was taken. + + If 'to_cancel' is specified, a list of participants to be sent cancel + messages is provided. + + If 'to_unschedule' is specified, a list of periods to be unscheduled is + provided. + """ + + # Here, the organiser should be the current user. + + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) + + self.update_sender(organiser_attr) + self.update_dtstamp() + self.set_sequence(True) + + parts = [self.obj.to_part(method)] + + # Add message parts with cancelled occurrence information. + # NOTE: This could probably be merged with the updated event message. + + if to_unschedule: + obj = self.obj.copy() + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"]) + + for p in to_unschedule: + if not p.origin: + continue + obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), p.get_start_attr())] + parts.append(obj.to_part("CANCEL")) + + # Send the updated event, along with a cancellation for each of the + # unscheduled occurrences. + + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts) + + # When cancelling, replace the attendees with those for whom the event + # is now cancelled. + + if to_cancel: + obj = self.obj.copy() + obj["ATTENDEE"] = to_cancel + + # Send a cancellation to all uninvited attendees. + + self.send_message("CANCEL", get_address(organiser), from_organiser=True) + + return True class FormUtilities: