1.1 --- a/imiptools/client.py Sat Jul 25 19:29:44 2015 +0200
1.2 +++ b/imiptools/client.py Sat Jul 25 23:18:58 2015 +0200
1.3 @@ -19,9 +19,12 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 +from datetime import datetime
1.8 from imiptools.data import get_address, get_uri, get_window_end, uri_dict, uri_items, uri_values
1.9 -from imiptools.dates import get_default_timezone
1.10 +from imiptools.period import update_freebusy
1.11 from imiptools.profile import Preferences
1.12 +from imiptools.dates import format_datetime, get_default_timezone, \
1.13 + to_timezone
1.14
1.15 def update_attendees(obj, attendees, removed):
1.16
1.17 @@ -72,8 +75,9 @@
1.18
1.19 default_window_size = 100
1.20
1.21 - def __init__(self, user):
1.22 + def __init__(self, user, messenger=None):
1.23 self.user = user
1.24 + self.messenger = messenger
1.25 self.preferences = None
1.26
1.27 def get_preferences(self):
1.28 @@ -132,4 +136,79 @@
1.29 if self.messenger and self.messenger.sender != get_address(self.user):
1.30 attr["SENT-BY"] = get_uri(self.messenger.sender)
1.31
1.32 +class ClientForObject(Client):
1.33 +
1.34 + "A client maintaining a specific object."
1.35 +
1.36 + def __init__(self, obj, user, messenger=None):
1.37 + Client.__init__(self, user, messenger)
1.38 + self.set_object(obj)
1.39 +
1.40 + def set_object(self, obj):
1.41 + self.obj = obj
1.42 + self.uid = obj and self.obj.get_uid()
1.43 + self.recurrenceid = obj and self.obj.get_recurrenceid()
1.44 + self.sequence = obj and self.obj.get_value("SEQUENCE")
1.45 + self.dtstamp = obj and self.obj.get_value("DTSTAMP")
1.46 +
1.47 + def _update_freebusy(self, freebusy, periods, recurrenceid, transp=None):
1.48 +
1.49 + """
1.50 + Update the 'freebusy' collection with the given 'periods', indicating an
1.51 + explicit 'recurrenceid' to affect either a recurrence or the parent
1.52 + event.
1.53 + """
1.54 +
1.55 + update_freebusy(freebusy, periods,
1.56 + transp or self.obj.get_value("TRANSP") or "OPAQUE",
1.57 + self.uid, recurrenceid,
1.58 + self.obj.get_value("SUMMARY"),
1.59 + self.obj.get_value("ORGANIZER"))
1.60 +
1.61 + def update_freebusy(self, freebusy, periods, transp=None):
1.62 +
1.63 + """
1.64 + Update the 'freebusy' collection for this event with the given
1.65 + 'periods'.
1.66 + """
1.67 +
1.68 + self._update_freebusy(freebusy, periods, self.recurrenceid, transp)
1.69 +
1.70 + def update_freebusy_for_participant(self, freebusy, periods, attr, for_organiser=False):
1.71 +
1.72 + """
1.73 + Update the 'freebusy' collection using the given 'periods', subject to
1.74 + the 'attr' provided for the participant, indicating whether this is
1.75 + being generated 'for_organiser' or not.
1.76 + """
1.77 +
1.78 + # Organisers employ a special transparency if not attending.
1.79 +
1.80 + if self.is_participating(attr, for_organiser):
1.81 + self.update_freebusy(freebusy, periods,
1.82 + transp=self.get_overriding_transparency(attr, for_organiser))
1.83 + else:
1.84 + self.remove_from_freebusy(freebusy)
1.85 +
1.86 + def is_participating(self, attr, as_organiser=False):
1.87 + return as_organiser or not attr or attr.get("PARTSTAT") != "DECLINED"
1.88 +
1.89 + def get_overriding_transparency(self, attr, as_organiser=False):
1.90 + return as_organiser and not (attr and attr.get("PARTSTAT")) and "ORG" or None
1.91 +
1.92 + def update_dtstamp(self):
1.93 +
1.94 + "Update the DTSTAMP in the current object."
1.95 +
1.96 + dtstamp = self.obj.get_utc_datetime("DTSTAMP")
1.97 + utcnow = to_timezone(datetime.utcnow(), "UTC")
1.98 + self.obj["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
1.99 +
1.100 + def set_sequence(self, increment=False):
1.101 +
1.102 + "Update the SEQUENCE in the current object."
1.103 +
1.104 + sequence = self.obj.get_value("SEQUENCE") or "0"
1.105 + self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
1.106 +
1.107 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imiptools/handlers/__init__.py Sat Jul 25 19:29:44 2015 +0200
2.2 +++ b/imiptools/handlers/__init__.py Sat Jul 25 23:18:58 2015 +0200
2.3 @@ -19,18 +19,15 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 -from datetime import datetime
2.8 from email.mime.text import MIMEText
2.9 -from imiptools.client import Client
2.10 +from imiptools.client import ClientForObject
2.11 from imiptools.config import MANAGER_PATH, MANAGER_URL
2.12 -from imiptools.data import Object, \
2.13 - get_address, get_uri, get_value, \
2.14 +from imiptools.data import Object, get_address, get_uri, \
2.15 is_new_object, uri_dict, uri_item, uri_values
2.16 from imiptools.dates import format_datetime, get_recurrence_start_point, \
2.17 to_timezone
2.18 from imiptools.period import can_schedule, remove_period, \
2.19 - remove_additional_periods, remove_affected_period, \
2.20 - update_freebusy
2.21 + remove_additional_periods, remove_affected_period
2.22 from imiptools.profile import Preferences
2.23 from socket import gethostname
2.24 import imip_store
2.25 @@ -47,7 +44,7 @@
2.26 recurrenceid and "/%s" % recurrenceid or ""
2.27 )
2.28
2.29 -class Handler(Client):
2.30 +class Handler(ClientForObject):
2.31
2.32 "General handler support."
2.33
2.34 @@ -55,28 +52,24 @@
2.35 publisher=None):
2.36
2.37 """
2.38 - Initialise the handler with the calendar 'obj' and the 'senders' and
2.39 - 'recipient' of the object (if specifically indicated).
2.40 + Initialise the handler with any specifically indicated 'senders' and
2.41 + 'recipient' of a calendar object. The object is initially undefined.
2.42 +
2.43 + The optional 'messenger' provides a means of interacting with the mail
2.44 + system.
2.45
2.46 The optional 'store' and 'publisher' can be specified to override the
2.47 default store and publisher objects.
2.48 """
2.49
2.50 - Client.__init__(self, recipient and get_uri(recipient))
2.51 + ClientForObject.__init__(self, None, recipient and get_uri(recipient), messenger)
2.52
2.53 self.senders = senders and set(map(get_address, senders))
2.54 self.recipient = recipient and get_address(recipient)
2.55 - self.messenger = messenger
2.56
2.57 self.results = []
2.58 self.outgoing_methods = set()
2.59
2.60 - self.obj = None
2.61 - self.uid = None
2.62 - self.recurrenceid = None
2.63 - self.sequence = None
2.64 - self.dtstamp = None
2.65 -
2.66 self.store = store or imip_store.FileStore()
2.67
2.68 try:
2.69 @@ -84,13 +77,6 @@
2.70 except OSError:
2.71 self.publisher = None
2.72
2.73 - def set_object(self, obj):
2.74 - self.obj = obj
2.75 - self.uid = self.obj.get_uid()
2.76 - self.recurrenceid = self.obj.get_recurrenceid()
2.77 - self.sequence = self.obj.get_value("SEQUENCE")
2.78 - self.dtstamp = self.obj.get_value("DTSTAMP")
2.79 -
2.80 def wrap(self, text, link=True):
2.81
2.82 "Wrap any valid message for passing to the recipient."
2.83 @@ -164,51 +150,6 @@
2.84 recurrenceid = self.get_recurrence_start_point(recurrenceid)
2.85 remove_affected_period(freebusy, self.uid, recurrenceid)
2.86
2.87 - def _update_freebusy(self, freebusy, periods, recurrenceid, transp=None):
2.88 -
2.89 - """
2.90 - Update the 'freebusy' collection with the given 'periods', indicating an
2.91 - explicit 'recurrenceid' to affect either a recurrence or the parent
2.92 - event.
2.93 - """
2.94 -
2.95 - update_freebusy(freebusy, periods,
2.96 - transp or self.obj.get_value("TRANSP") or "OPAQUE",
2.97 - self.uid, recurrenceid,
2.98 - self.obj.get_value("SUMMARY"),
2.99 - self.obj.get_value("ORGANIZER"))
2.100 -
2.101 - def update_freebusy(self, freebusy, periods, transp=None):
2.102 -
2.103 - """
2.104 - Update the 'freebusy' collection for this event with the given
2.105 - 'periods'.
2.106 - """
2.107 -
2.108 - self._update_freebusy(freebusy, periods, self.recurrenceid, transp)
2.109 -
2.110 - def update_freebusy_for_participant(self, freebusy, periods, attr, for_organiser=False):
2.111 -
2.112 - """
2.113 - Update the 'freebusy' collection using the given 'periods', subject to
2.114 - the 'attr' provided for the participant, indicating whether this is
2.115 - being generated 'for_organiser' or not.
2.116 - """
2.117 -
2.118 - # Organisers employ a special transparency if not attending.
2.119 -
2.120 - if self.is_participating(attr, for_organiser):
2.121 - self.update_freebusy(freebusy, periods,
2.122 - transp=self.get_overriding_transparency(attr, for_organiser))
2.123 - else:
2.124 - self.remove_from_freebusy(freebusy)
2.125 -
2.126 - def is_participating(self, attr, as_organiser=False):
2.127 - return as_organiser or not attr or attr.get("PARTSTAT") != "DECLINED"
2.128 -
2.129 - def get_overriding_transparency(self, attr, as_organiser=False):
2.130 - return as_organiser and not (attr and attr.get("PARTSTAT")) and "ORG" or None
2.131 -
2.132 # Convenience methods for updating stored free/busy information.
2.133
2.134 def update_freebusy_from_participant(self, participant_item, for_organiser):
2.135 @@ -523,19 +464,4 @@
2.136
2.137 return True
2.138
2.139 - def update_dtstamp(self):
2.140 -
2.141 - "Update the DTSTAMP in the current object."
2.142 -
2.143 - dtstamp = self.obj.get_utc_datetime("DTSTAMP")
2.144 - utcnow = to_timezone(datetime.utcnow(), "UTC")
2.145 - self.obj["DTSTAMP"] = [(format_datetime(dtstamp > utcnow and dtstamp or utcnow), {})]
2.146 -
2.147 - def set_sequence(self, increment=False):
2.148 -
2.149 - "Update the SEQUENCE in the current object."
2.150 -
2.151 - sequence = self.obj.get_value("SEQUENCE") or "0"
2.152 - self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
2.153 -
2.154 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/imipweb/client.py Sat Jul 25 23:18:58 2015 +0200
3.3 @@ -0,0 +1,164 @@
3.4 +#!/usr/bin/env python
3.5 +
3.6 +"""
3.7 +Interaction with the mail system for the manager interface.
3.8 +
3.9 +Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
3.10 +
3.11 +This program is free software; you can redistribute it and/or modify it under
3.12 +the terms of the GNU General Public License as published by the Free Software
3.13 +Foundation; either version 3 of the License, or (at your option) any later
3.14 +version.
3.15 +
3.16 +This program is distributed in the hope that it will be useful, but WITHOUT
3.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
3.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
3.19 +details.
3.20 +
3.21 +You should have received a copy of the GNU General Public License along with
3.22 +this program. If not, see <http://www.gnu.org/licenses/>.
3.23 +"""
3.24 +
3.25 +from imiptools.client import ClientForObject
3.26 +from imiptools.data import get_address, get_uri, make_freebusy, \
3.27 + to_part, uri_item, uri_values
3.28 +from imiptools.dates import format_datetime, get_timestamp
3.29 +
3.30 +class ManagerClient(ClientForObject):
3.31 +
3.32 + """
3.33 + A content client for use by the manager, as opposed to operating within the
3.34 + mail processing pipeline.
3.35 + """
3.36 +
3.37 + # Communication methods.
3.38 +
3.39 + def send_message(self, method, sender, from_organiser, parts=None):
3.40 +
3.41 + """
3.42 + Create a full calendar object employing the given 'method', and send it
3.43 + to the appropriate recipients, also sending a copy to the 'sender'. The
3.44 + 'from_organiser' value indicates whether the organiser is sending this
3.45 + message.
3.46 + """
3.47 +
3.48 + parts = parts or [self.obj.to_part(method)]
3.49 +
3.50 + # As organiser, send an invitation to attendees, excluding oneself if
3.51 + # also attending. The updated event will be saved by the outgoing
3.52 + # handler.
3.53 +
3.54 + organiser = get_uri(self.obj.get_value("ORGANIZER"))
3.55 + attendees = uri_values(self.obj.get_values("ATTENDEE"))
3.56 +
3.57 + if from_organiser:
3.58 + recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
3.59 + else:
3.60 + recipients = [get_address(organiser)]
3.61 +
3.62 + # Bundle free/busy information if appropriate.
3.63 +
3.64 + if self.is_sharing() and self.is_bundling():
3.65 +
3.66 + # Invent a unique identifier.
3.67 +
3.68 + utcnow = get_timestamp()
3.69 + uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))
3.70 +
3.71 + freebusy = self.store.get_freebusy(self.user)
3.72 +
3.73 + # Since the outgoing handler updates this user's free/busy details,
3.74 + # the stored details will probably not have the updated details at
3.75 + # this point, so we update our copy for serialisation as the bundled
3.76 + # free/busy object.
3.77 +
3.78 + self.update_freebusy(freebusy,
3.79 + self.obj.get_periods(self.get_tzid(), self.get_window_end()))
3.80 +
3.81 + user_attr = {}
3.82 + self.update_sender(user_attr)
3.83 +
3.84 + parts.append(to_part("PUBLISH", [
3.85 + make_freebusy(freebusy, uid, self.user, user_attr)
3.86 + ]))
3.87 +
3.88 + # Explicitly specify the outgoing BCC recipient since we are sending as
3.89 + # the generic calendar user.
3.90 +
3.91 + message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
3.92 + self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
3.93 +
3.94 + # Action methods.
3.95 +
3.96 + def process_received_request(self):
3.97 +
3.98 + """
3.99 + Process the current request for the current user. Return whether any
3.100 + action was taken.
3.101 + """
3.102 +
3.103 + # Reply only on behalf of this user.
3.104 +
3.105 + attendee_attr = self.update_participation(self.obj)
3.106 +
3.107 + if not attendee_attr:
3.108 + return False
3.109 +
3.110 + self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
3.111 + self.update_dtstamp()
3.112 + self.set_sequence(False)
3.113 + self.send_message("REPLY", get_address(self.user), from_organiser=False)
3.114 + return True
3.115 +
3.116 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
3.117 +
3.118 + """
3.119 + Process the current request, sending a created request of the given
3.120 + 'method' to attendees. Return whether any action was taken.
3.121 +
3.122 + If 'to_cancel' is specified, a list of participants to be sent cancel
3.123 + messages is provided.
3.124 + """
3.125 +
3.126 + # Here, the organiser should be the current user.
3.127 +
3.128 + organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
3.129 +
3.130 + self.update_sender(organiser_attr)
3.131 + self.update_dtstamp()
3.132 + self.set_sequence(True)
3.133 +
3.134 + parts = [self.obj.to_part(method)]
3.135 +
3.136 + # Add message parts with cancelled occurrence information.
3.137 + # NOTE: This could probably be merged with the updated event message.
3.138 +
3.139 + if to_unschedule:
3.140 + obj = self.obj.copy()
3.141 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
3.142 +
3.143 + for p in to_unschedule:
3.144 + if not p.origin:
3.145 + continue
3.146 + obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), {})]
3.147 + parts.append(obj.to_part("CANCEL"))
3.148 +
3.149 + # Send the updated event, along with a cancellation for each of the
3.150 + # unscheduled occurrences.
3.151 +
3.152 + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
3.153 +
3.154 + # When cancelling, replace the attendees with those for whom the event
3.155 + # is now cancelled.
3.156 +
3.157 + if to_cancel:
3.158 + obj = self.obj.copy()
3.159 + obj["ATTENDEE"] = to_cancel
3.160 +
3.161 + # Send a cancellation to all uninvited attendees.
3.162 +
3.163 + self.send_message("CANCEL", get_address(organiser), from_organiser=True)
3.164 +
3.165 + return True
3.166 +
3.167 +# vim: tabstop=4 expandtab shiftwidth=4
4.1 --- a/imipweb/event.py Sat Jul 25 19:29:44 2015 +0200
4.2 +++ b/imipweb/event.py Sat Jul 25 23:18:58 2015 +0200
4.3 @@ -29,7 +29,7 @@
4.4 from imipweb.data import EventPeriod, \
4.5 event_period_from_period, form_period_from_period, \
4.6 FormDate, FormPeriod, PeriodError
4.7 -from imipweb.handler import ManagerHandler
4.8 +from imipweb.client import ManagerClient
4.9 from imipweb.resource import Resource
4.10 import pytz
4.11
4.12 @@ -157,7 +157,7 @@
4.13
4.14 if reply or invite or cancel:
4.15
4.16 - handler = ManagerHandler(obj, self.user, self.messenger)
4.17 + handler = ManagerClient(obj, self.user, self.messenger)
4.18
4.19 # Process the object and remove it from the list of requests.
4.20
5.1 --- a/imipweb/handler.py Sat Jul 25 19:29:44 2015 +0200
5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
5.3 @@ -1,171 +0,0 @@
5.4 -#!/usr/bin/env python
5.5 -
5.6 -"""
5.7 -Interaction with the mail system for the manager interface.
5.8 -
5.9 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
5.10 -
5.11 -This program is free software; you can redistribute it and/or modify it under
5.12 -the terms of the GNU General Public License as published by the Free Software
5.13 -Foundation; either version 3 of the License, or (at your option) any later
5.14 -version.
5.15 -
5.16 -This program is distributed in the hope that it will be useful, but WITHOUT
5.17 -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
5.18 -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
5.19 -details.
5.20 -
5.21 -You should have received a copy of the GNU General Public License along with
5.22 -this program. If not, see <http://www.gnu.org/licenses/>.
5.23 -"""
5.24 -
5.25 -from imiptools.client import Client
5.26 -from imiptools.data import get_address, get_uri, make_freebusy, \
5.27 - to_part, uri_item, uri_values
5.28 -from imiptools.dates import format_datetime, get_timestamp
5.29 -from imiptools.handlers import Handler
5.30 -
5.31 -class ManagerHandler(Handler):
5.32 -
5.33 - """
5.34 - A content handler for use by the manager, as opposed to operating within the
5.35 - mail processing pipeline.
5.36 - """
5.37 -
5.38 - def __init__(self, obj, user, messenger):
5.39 - Handler.__init__(self, messenger=messenger)
5.40 - Client.__init__(self, user) # this redefines the Handler initialisation
5.41 -
5.42 - self.set_object(obj)
5.43 -
5.44 - # Communication methods.
5.45 -
5.46 - def send_message(self, method, sender, from_organiser, parts=None):
5.47 -
5.48 - """
5.49 - Create a full calendar object employing the given 'method', and send it
5.50 - to the appropriate recipients, also sending a copy to the 'sender'. The
5.51 - 'from_organiser' value indicates whether the organiser is sending this
5.52 - message.
5.53 - """
5.54 -
5.55 - parts = parts or [self.obj.to_part(method)]
5.56 -
5.57 - # As organiser, send an invitation to attendees, excluding oneself if
5.58 - # also attending. The updated event will be saved by the outgoing
5.59 - # handler.
5.60 -
5.61 - organiser = get_uri(self.obj.get_value("ORGANIZER"))
5.62 - attendees = uri_values(self.obj.get_values("ATTENDEE"))
5.63 -
5.64 - if from_organiser:
5.65 - recipients = [get_address(attendee) for attendee in attendees if attendee != self.user]
5.66 - else:
5.67 - recipients = [get_address(organiser)]
5.68 -
5.69 - # Bundle free/busy information if appropriate.
5.70 -
5.71 - if self.is_sharing() and self.is_bundling():
5.72 -
5.73 - # Invent a unique identifier.
5.74 -
5.75 - utcnow = get_timestamp()
5.76 - uid = "imip-agent-%s-%s" % (utcnow, get_address(self.user))
5.77 -
5.78 - freebusy = self.store.get_freebusy(self.user)
5.79 -
5.80 - # Since the outgoing handler updates this user's free/busy details,
5.81 - # the stored details will probably not have the updated details at
5.82 - # this point, so we update our copy for serialisation as the bundled
5.83 - # free/busy object.
5.84 -
5.85 - self.update_freebusy(freebusy,
5.86 - self.obj.get_periods(self.get_tzid(), self.get_window_end()))
5.87 -
5.88 - user_attr = {}
5.89 - self.update_sender(user_attr)
5.90 -
5.91 - parts.append(to_part("PUBLISH", [
5.92 - make_freebusy(freebusy, uid, self.user, user_attr)
5.93 - ]))
5.94 -
5.95 - # Explicitly specify the outgoing BCC recipient since we are sending as
5.96 - # the generic calendar user.
5.97 -
5.98 - message = self.messenger.make_outgoing_message(parts, recipients, outgoing_bcc=sender)
5.99 - self.messenger.sendmail(recipients, message.as_string(), outgoing_bcc=sender)
5.100 -
5.101 - # Action methods.
5.102 -
5.103 - def process_received_request(self):
5.104 -
5.105 - """
5.106 - Process the current request for the current user. Return whether any
5.107 - action was taken.
5.108 - """
5.109 -
5.110 - # Reply only on behalf of this user.
5.111 -
5.112 - attendee_attr = self.update_participation(self.obj)
5.113 -
5.114 - if not attendee_attr:
5.115 - return False
5.116 -
5.117 - self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
5.118 - self.update_dtstamp()
5.119 - self.set_sequence(False)
5.120 - self.send_message("REPLY", get_address(self.user), from_organiser=False)
5.121 - return True
5.122 -
5.123 - def process_created_request(self, method, to_cancel=None, to_unschedule=None):
5.124 -
5.125 - """
5.126 - Process the current request, sending a created request of the given
5.127 - 'method' to attendees. Return whether any action was taken.
5.128 -
5.129 - If 'to_cancel' is specified, a list of participants to be sent cancel
5.130 - messages is provided.
5.131 - """
5.132 -
5.133 - # Here, the organiser should be the current user.
5.134 -
5.135 - organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER"))
5.136 -
5.137 - self.update_sender(organiser_attr)
5.138 - self.update_dtstamp()
5.139 - self.set_sequence(True)
5.140 -
5.141 - parts = [self.obj.to_part(method)]
5.142 -
5.143 - # Add message parts with cancelled occurrence information.
5.144 - # NOTE: This could probably be merged with the updated event message.
5.145 -
5.146 - if to_unschedule:
5.147 - obj = self.obj.copy()
5.148 - obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
5.149 -
5.150 - for p in to_unschedule:
5.151 - if not p.origin:
5.152 - continue
5.153 - obj["RECURRENCE-ID"] = [(format_datetime(p.get_start()), {})]
5.154 - parts.append(obj.to_part("CANCEL"))
5.155 -
5.156 - # Send the updated event, along with a cancellation for each of the
5.157 - # unscheduled occurrences.
5.158 -
5.159 - self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
5.160 -
5.161 - # When cancelling, replace the attendees with those for whom the event
5.162 - # is now cancelled.
5.163 -
5.164 - if to_cancel:
5.165 - obj = self.obj.copy()
5.166 - obj["ATTENDEE"] = to_cancel
5.167 -
5.168 - # Send a cancellation to all uninvited attendees.
5.169 -
5.170 - self.send_message("CANCEL", get_address(organiser), from_organiser=True)
5.171 -
5.172 - return True
5.173 -
5.174 -# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/tests/test_handle.py Sat Jul 25 19:29:44 2015 +0200
6.2 +++ b/tests/test_handle.py Sat Jul 25 23:18:58 2015 +0200
6.3 @@ -19,26 +19,19 @@
6.4 this program. If not, see <http://www.gnu.org/licenses/>.
6.5 """
6.6
6.7 -from imiptools.client import Client
6.8 +from imiptools.client import ClientForObject
6.9 from imiptools.data import Object, get_address
6.10 -from imiptools.handlers import Handler
6.11 from imiptools.mail import Messenger
6.12 import imip_store
6.13 import sys
6.14
6.15 -class TestHandler(Handler):
6.16 +class TestClient(ClientForObject):
6.17
6.18 """
6.19 A content handler for use in testing, as opposed to operating within the
6.20 mail processing pipeline.
6.21 """
6.22
6.23 - def __init__(self, obj, user, messenger):
6.24 - Handler.__init__(self, messenger=messenger)
6.25 - Client.__init__(self, user) # this redefines the Handler initialisation
6.26 -
6.27 - self.set_object(obj)
6.28 -
6.29 # Action methods.
6.30
6.31 def handle_request(self, accept):
6.32 @@ -55,7 +48,7 @@
6.33 if not attendee_attr:
6.34 return None
6.35
6.36 - # NOTE: This is a simpler form of the code in imipweb.handler.
6.37 + # NOTE: This is a simpler form of the code in imipweb.client.
6.38
6.39 organiser = get_address(self.obj.get_value("ORGANIZER"))
6.40
6.41 @@ -91,7 +84,7 @@
6.42 sys.exit(1)
6.43
6.44 obj = Object(fragment)
6.45 - handler = TestHandler(obj, user, Messenger())
6.46 + handler = TestClient(obj, user, Messenger())
6.47 response = handler.handle_request(accept == "accept")
6.48
6.49 if response: