# HG changeset patch # User Paul Boddie # Date 1431713846 -7200 # Node ID 4a32bd4cb423d196223381517fb31b87848f1834 # Parent b447338c07b83724d007ac132ecb3941ffe0e49c Added initial support for cancelling specific event occurrences. diff -r b447338c07b8 -r 4a32bd4cb423 imipweb/event.py --- a/imipweb/event.py Fri May 15 20:16:29 2015 +0200 +++ b/imipweb/event.py Fri May 15 20:17:26 2015 +0200 @@ -120,6 +120,13 @@ except PeriodError, exc: return exc.args + # Set the periods in the object, first obtaining removed and + # modified period information. + + to_unschedule = [] + for i in args.get("recur-remove", []): + to_unschedule.append(periods[int(i)]) + self.set_period_in_object(obj, period) self.set_periods_in_object(obj, periods) @@ -158,8 +165,10 @@ elif self.is_organiser(obj) and (invite or cancel): + # Invitation, uninvitation and unscheduling... + if handler.process_created_request( - invite and "REQUEST" or "CANCEL", to_cancel): + invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule): self.remove_request(uid, recurrenceid) @@ -208,8 +217,7 @@ if obj.has_key("RDATE"): del obj["RDATE"] - for period in periods: - p = event_period_from_period(period) + for p in periods: if p.origin != "RRULE": tzid = p.start_attr and p.start_attr.get("TZID") or p.end_attr and p.end_attr.get("TZID") new_rdates.append(get_period_item(p.start, p.end, tzid)) @@ -681,7 +689,6 @@ # Permit the removal of newly-added attendees. remove_type = (not existing or sequence is None or attendee == self.user) and "submit" or "checkbox" - self._control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove") page.label("Remove", for_="remove-%d" % i, class_="remove") @@ -767,6 +774,9 @@ """ page = self.page + args = self.env.get_args() + + sequence = obj.get_value("SEQUENCE") # Isolate the controls from neighbouring tables. @@ -789,6 +799,21 @@ self.show_recurrence_controls(obj, index, period, recurrenceid, recurrenceids, False) page.tr.close() + # Permit the removal of recurrences. + + page.tr() + page.th("") + page.td() + + remove_type = sequence is None or not period.origin and "submit" or "checkbox" + self._control("recur-remove", remove_type, str(index), str(index) in args.get("recur-remove", []), id="recur-remove-%d" % index, class_="remove") + + page.label("Remove", for_="recur-remove-%d" % index, class_="remove") + page.label("Removed", for_="recur-remove-%d" % index, class_="removed") + + page.td.close() + page.tr.close() + page.tbody.close() page.table.close() diff -r b447338c07b8 -r 4a32bd4cb423 imipweb/handler.py --- a/imipweb/handler.py Fri May 15 20:16:29 2015 +0200 +++ b/imipweb/handler.py Fri May 15 20:17:26 2015 +0200 @@ -22,9 +22,10 @@ from imiptools.client import Client from imiptools.data import get_address, get_uri, make_freebusy, \ to_part, uri_item, uri_items, uri_values -from imiptools.dates import get_timestamp +from imiptools.dates import format_datetime, get_timestamp, to_utc_datetime from imiptools.handlers import Handler from imiptools.period import update_freebusy +from imipweb.data import event_period_from_period class ManagerHandler(Handler): @@ -41,7 +42,7 @@ # Communication methods. - def send_message(self, method, sender, from_organiser): + def send_message(self, method, sender, from_organiser, parts=None): """ Create a full calendar object employing the given 'method', and send it @@ -50,7 +51,7 @@ message. """ - parts = [self.obj.to_part(method)] + 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 @@ -128,7 +129,7 @@ return False - def process_created_request(self, method, to_cancel=None): + def process_created_request(self, method, to_cancel=None, to_unschedule=None): """ Process the current request, sending a created request of the given @@ -146,20 +147,37 @@ self.update_dtstamp() self.set_sequence(True) - self.send_message(method, get_address(organiser), from_organiser=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 + date_tzid = self.get_tzid() + obj["RECURRENCE-ID"] = [(format_datetime(to_utc_datetime(p.start, date_tzid)), {})] + 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: - remaining = self.obj["ATTENDEE"] - self.obj["ATTENDEE"] = to_cancel - self.send_message("CANCEL", get_address(organiser), from_organiser=True) + obj = self.obj.copy() + obj["ATTENDEE"] = to_cancel - # Just in case more work is done with this event, the attendees are - # now restored. + # Send a cancellation to all uninvited attendees. - self.obj["ATTENDEE"] = remaining + self.send_message("CANCEL", get_address(organiser), from_organiser=True) return True