1.1 --- a/imipweb/event.py Fri May 15 20:16:29 2015 +0200
1.2 +++ b/imipweb/event.py Fri May 15 20:17:26 2015 +0200
1.3 @@ -120,6 +120,13 @@
1.4 except PeriodError, exc:
1.5 return exc.args
1.6
1.7 + # Set the periods in the object, first obtaining removed and
1.8 + # modified period information.
1.9 +
1.10 + to_unschedule = []
1.11 + for i in args.get("recur-remove", []):
1.12 + to_unschedule.append(periods[int(i)])
1.13 +
1.14 self.set_period_in_object(obj, period)
1.15 self.set_periods_in_object(obj, periods)
1.16
1.17 @@ -158,8 +165,10 @@
1.18
1.19 elif self.is_organiser(obj) and (invite or cancel):
1.20
1.21 + # Invitation, uninvitation and unscheduling...
1.22 +
1.23 if handler.process_created_request(
1.24 - invite and "REQUEST" or "CANCEL", to_cancel):
1.25 + invite and "REQUEST" or "CANCEL", to_cancel, to_unschedule):
1.26
1.27 self.remove_request(uid, recurrenceid)
1.28
1.29 @@ -208,8 +217,7 @@
1.30 if obj.has_key("RDATE"):
1.31 del obj["RDATE"]
1.32
1.33 - for period in periods:
1.34 - p = event_period_from_period(period)
1.35 + for p in periods:
1.36 if p.origin != "RRULE":
1.37 tzid = p.start_attr and p.start_attr.get("TZID") or p.end_attr and p.end_attr.get("TZID")
1.38 new_rdates.append(get_period_item(p.start, p.end, tzid))
1.39 @@ -681,7 +689,6 @@
1.40 # Permit the removal of newly-added attendees.
1.41
1.42 remove_type = (not existing or sequence is None or attendee == self.user) and "submit" or "checkbox"
1.43 -
1.44 self._control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove")
1.45
1.46 page.label("Remove", for_="remove-%d" % i, class_="remove")
1.47 @@ -767,6 +774,9 @@
1.48 """
1.49
1.50 page = self.page
1.51 + args = self.env.get_args()
1.52 +
1.53 + sequence = obj.get_value("SEQUENCE")
1.54
1.55 # Isolate the controls from neighbouring tables.
1.56
1.57 @@ -789,6 +799,21 @@
1.58 self.show_recurrence_controls(obj, index, period, recurrenceid, recurrenceids, False)
1.59 page.tr.close()
1.60
1.61 + # Permit the removal of recurrences.
1.62 +
1.63 + page.tr()
1.64 + page.th("")
1.65 + page.td()
1.66 +
1.67 + remove_type = sequence is None or not period.origin and "submit" or "checkbox"
1.68 + self._control("recur-remove", remove_type, str(index), str(index) in args.get("recur-remove", []), id="recur-remove-%d" % index, class_="remove")
1.69 +
1.70 + page.label("Remove", for_="recur-remove-%d" % index, class_="remove")
1.71 + page.label("Removed", for_="recur-remove-%d" % index, class_="removed")
1.72 +
1.73 + page.td.close()
1.74 + page.tr.close()
1.75 +
1.76 page.tbody.close()
1.77 page.table.close()
1.78
2.1 --- a/imipweb/handler.py Fri May 15 20:16:29 2015 +0200
2.2 +++ b/imipweb/handler.py Fri May 15 20:17:26 2015 +0200
2.3 @@ -22,9 +22,10 @@
2.4 from imiptools.client import Client
2.5 from imiptools.data import get_address, get_uri, make_freebusy, \
2.6 to_part, uri_item, uri_items, uri_values
2.7 -from imiptools.dates import get_timestamp
2.8 +from imiptools.dates import format_datetime, get_timestamp, to_utc_datetime
2.9 from imiptools.handlers import Handler
2.10 from imiptools.period import update_freebusy
2.11 +from imipweb.data import event_period_from_period
2.12
2.13 class ManagerHandler(Handler):
2.14
2.15 @@ -41,7 +42,7 @@
2.16
2.17 # Communication methods.
2.18
2.19 - def send_message(self, method, sender, from_organiser):
2.20 + def send_message(self, method, sender, from_organiser, parts=None):
2.21
2.22 """
2.23 Create a full calendar object employing the given 'method', and send it
2.24 @@ -50,7 +51,7 @@
2.25 message.
2.26 """
2.27
2.28 - parts = [self.obj.to_part(method)]
2.29 + parts = parts or [self.obj.to_part(method)]
2.30
2.31 # As organiser, send an invitation to attendees, excluding oneself if
2.32 # also attending. The updated event will be saved by the outgoing
2.33 @@ -128,7 +129,7 @@
2.34
2.35 return False
2.36
2.37 - def process_created_request(self, method, to_cancel=None):
2.38 + def process_created_request(self, method, to_cancel=None, to_unschedule=None):
2.39
2.40 """
2.41 Process the current request, sending a created request of the given
2.42 @@ -146,20 +147,37 @@
2.43 self.update_dtstamp()
2.44 self.set_sequence(True)
2.45
2.46 - self.send_message(method, get_address(organiser), from_organiser=True)
2.47 + parts = [self.obj.to_part(method)]
2.48 +
2.49 + # Add message parts with cancelled occurrence information.
2.50 + # NOTE: This could probably be merged with the updated event message.
2.51 +
2.52 + if to_unschedule:
2.53 + obj = self.obj.copy()
2.54 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"])
2.55 +
2.56 + for p in to_unschedule:
2.57 + if not p.origin:
2.58 + continue
2.59 + date_tzid = self.get_tzid()
2.60 + obj["RECURRENCE-ID"] = [(format_datetime(to_utc_datetime(p.start, date_tzid)), {})]
2.61 + parts.append(obj.to_part("CANCEL"))
2.62 +
2.63 + # Send the updated event, along with a cancellation for each of the
2.64 + # unscheduled occurrences.
2.65 +
2.66 + self.send_message("CANCEL", get_address(organiser), from_organiser=True, parts=parts)
2.67
2.68 # When cancelling, replace the attendees with those for whom the event
2.69 # is now cancelled.
2.70
2.71 if to_cancel:
2.72 - remaining = self.obj["ATTENDEE"]
2.73 - self.obj["ATTENDEE"] = to_cancel
2.74 - self.send_message("CANCEL", get_address(organiser), from_organiser=True)
2.75 + obj = self.obj.copy()
2.76 + obj["ATTENDEE"] = to_cancel
2.77
2.78 - # Just in case more work is done with this event, the attendees are
2.79 - # now restored.
2.80 + # Send a cancellation to all uninvited attendees.
2.81
2.82 - self.obj["ATTENDEE"] = remaining
2.83 + self.send_message("CANCEL", get_address(organiser), from_organiser=True)
2.84
2.85 return True
2.86