# HG changeset patch # User Paul Boddie # Date 1444082208 -7200 # Node ID e6600c59ed01b5d02e4cd79b7ff638aac1fd5e9a # Parent edb6009315183983ba26c33a7617677b4c32b2e1 Moved SEQUENCE and DTSTAMP update methods to Object, adjusting method names. Introduced support for declining counter-proposals in the Web interface. Changed the handlers to explicitly remove counter-proposals alongside dequeuing requests, making some minor fixes. Use the stored attributes to present attendee details when editing events. diff -r edb600931518 -r e6600c59ed01 imiptools/client.py --- a/imiptools/client.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imiptools/client.py Mon Oct 05 23:56:48 2015 +0200 @@ -25,7 +25,7 @@ is_new_object, make_freebusy, to_part, \ uri_dict, uri_items, uri_values from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \ - get_duration, get_time, get_timestamp + get_duration, get_timestamp from imiptools.period import can_schedule, remove_period, \ remove_additional_periods, remove_affected_period, \ update_freebusy @@ -370,21 +370,19 @@ self.obj["RECURRENCE-ID"] = [self.obj.get_item("DTSTART")] self.recurrenceid = self.obj.get_recurrenceid() - def update_dtstamp(self): + def update_dtstamp(self, obj=None): - "Update the DTSTAMP in the current object." + "Update the DTSTAMP in the current object or any given object 'obj'." + + obj = obj or self.obj + self.dtstamp = obj.update_dtstamp() - dtstamp = self.obj.get_utc_datetime("DTSTAMP") - utcnow = get_time() - self.dtstamp = format_datetime(dtstamp and dtstamp > utcnow and dtstamp or utcnow) - self.obj["DTSTAMP"] = [(self.dtstamp, {})] + def update_sequence(self, increment=False, obj=None): - def set_sequence(self, increment=False): + "Update the SEQUENCE in the current object or any given object 'obj'." - "Update the SEQUENCE in the current object." - - sequence = self.obj.get_value("SEQUENCE") or "0" - self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})] + obj = obj or self.obj + obj.update_sequence(increment) def merge_attendance(self, attendees): @@ -866,4 +864,17 @@ return True + # Convenience methods for removing counter-proposals and updating the + # request queue. + + def remove_counter(self, attendee): + self.remove_counters([attendee]) + + def remove_counters(self, attendees): + for attendee in attendees: + self.store.remove_counter(self.user, attendee, self.uid, self.recurrenceid) + + if not self.store.get_counters(self.user, self.uid, self.recurrenceid): + self.store.dequeue_request(self.user, self.uid, self.recurrenceid) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r edb600931518 -r e6600c59ed01 imiptools/data.py --- a/imiptools/data.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imiptools/data.py Mon Oct 05 23:56:48 2015 +0200 @@ -28,7 +28,8 @@ get_datetime_tzid, \ get_duration, get_period, get_period_item, \ get_recurrence_start_point, \ - get_tzid, to_datetime, to_timezone, to_utc_datetime + get_time, get_tzid, to_datetime, to_timezone, \ + to_utc_datetime from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod, period_overlaps from vCalendar import iterwrite, parse, ParseError, to_dict, to_node from vRecurrence import get_parameters, get_rule @@ -419,6 +420,24 @@ return old_values != set(self.get_date_values("RDATE") or []) + def update_dtstamp(self): + + "Update the DTSTAMP in the object." + + dtstamp = self.get_utc_datetime("DTSTAMP") + utcnow = get_time() + dtstamp = format_datetime(dtstamp and dtstamp > utcnow and dtstamp or utcnow) + self["DTSTAMP"] = [(dtstamp, {})] + return dtstamp + + def update_sequence(self, increment=False): + + "Set or update the SEQUENCE in the object." + + sequence = self.get_value("SEQUENCE") or "0" + self["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})] + return sequence + def update_exceptions(self, excluded): """ diff -r edb600931518 -r e6600c59ed01 imiptools/handlers/person.py --- a/imiptools/handlers/person.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imiptools/handlers/person.py Mon Oct 05 23:56:48 2015 +0200 @@ -164,11 +164,11 @@ if cancel: self.store.cancel_event(self.user, self.uid, self.recurrenceid) - self.store.dequeue_request(self.user, self.uid, self.recurrenceid) # Remove any associated request. self.store.dequeue_request(self.user, self.uid, self.recurrenceid) + self.store.remove_counters(self.user, self.uid, self.recurrenceid) # No return message will occur to update the free/busy # information, so this is done here using outgoing message diff -r edb600931518 -r e6600c59ed01 imiptools/handlers/person_outgoing.py --- a/imiptools/handlers/person_outgoing.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imiptools/handlers/person_outgoing.py Mon Oct 05 23:56:48 2015 +0200 @@ -119,6 +119,7 @@ # Remove any associated request. self.store.dequeue_request(self.user, self.uid, self.recurrenceid) + self.store.remove_counters(self.user, self.uid, self.recurrenceid) # Update free/busy information. @@ -197,11 +198,11 @@ if cancel_entire_event: self.store.cancel_event(self.user, self.uid, self.recurrenceid) - self.store.dequeue_request(self.user, self.uid, self.recurrenceid) # Remove any associated request. self.store.dequeue_request(self.user, self.uid, self.recurrenceid) + self.store.remove_counters(self.user, self.uid, self.recurrenceid) return True @@ -217,7 +218,7 @@ if not self.have_new_object(): return False - self.store.dequeue_request(self.user, self.uid, self.recurrenceid) + self.remove_counters(uri_values(self.obj.get_values("ATTENDEE"))) class Event(PersonHandler): diff -r edb600931518 -r e6600c59ed01 imipweb/event.py --- a/imipweb/event.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imipweb/event.py Mon Oct 05 23:56:48 2015 +0200 @@ -534,6 +534,8 @@ if not attendees: return + attendees = self.get_verbose_attendees(attendees) + page.p("The following counter-proposals have been received for this event:") page.table(cellspacing=5, cellpadding=5, class_="counters") @@ -549,8 +551,9 @@ page.thead.close() page.tbody() - for attendee in attendees: - obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee) + for i, attendee in enumerate(attendees): + attendee_uri = get_uri(attendee) + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee_uri) periods = self.get_periods(obj) first = True @@ -562,7 +565,6 @@ if first: page.td(attendee, rowspan=len(periods)) - first = False start = self.format_datetime(to_timezone(p.get_start(), tzid), "long") end = self.format_datetime(to_timezone(p.get_end(), tzid), "long") @@ -570,7 +572,14 @@ page.td(start) page.td(end) + if first: + page.td(rowspan=len(periods)) + self.control("decline-%d" % i, "submit", "Decline") + self.control("decline", "hidden", attendee) + page.td.close() + page.tr.close() + first = False page.tbody.close() page.table.close() @@ -705,6 +714,25 @@ cancel = args.has_key("cancel") ignore = args.has_key("ignore") save = args.has_key("save") + decline = filter(None, [(arg.startswith("decline-") and arg[len("decline-"):]) for arg in args.keys()]) + + # Decline a counter-proposal. + + if decline: + for s in decline: + try: + i = int(s) + except (IndexError, ValueError): + pass + else: + attendee_uri = get_uri(args.get("decline", [])[i]) + self.process_declined_counter(attendee_uri) + + # Update the counter-proposals synchronously instead of + # assuming that the outgoing handler will have done so + # before the form is refreshed. + + self.remove_counter(attendee_uri) have_action = reply or discard or create or cancel or ignore or save @@ -923,9 +951,22 @@ def get_attendees_from_page(self): - "Return attendees from the request." + """ + Return attendees from the request, using any stored attributes to obtain + verbose details. + """ + + return self.get_verbose_attendees(self.env.get_args().get("attendee", [])) - return self.env.get_args().get("attendee", []) + def get_verbose_attendees(self, attendees): + + """ + Use any stored attributes to obtain verbose details for the given + 'attendees'. + """ + + attendee_map = self.obj.get_value_map("ATTENDEE") + return [get_verbose_address(value, attendee_map.get(value)) for value in attendees] def update_attendees_from_page(self): diff -r edb600931518 -r e6600c59ed01 imipweb/resource.py --- a/imipweb/resource.py Mon Oct 05 23:53:38 2015 +0200 +++ b/imipweb/resource.py Mon Oct 05 23:56:48 2015 +0200 @@ -260,6 +260,10 @@ if part: parts.append(part) + self._send_message(sender, recipients, parts) + + def _send_message(self, sender, recipients, parts): + # Explicitly specify the outgoing BCC recipient since we are sending as # the generic calendar user. @@ -268,6 +272,22 @@ # Action methods. + def process_declined_counter(self, attendee): + + "Process a declined counter-proposal." + + # Obtain the counter-proposal for the attendee. + + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee) + if not obj: + return False + + method = "DECLINECOUNTER" + obj.update_dtstamp() + obj.update_sequence(False) + self._send_message(get_address(self.user), [get_address(attendee)], parts=[obj.to_part(method)]) + return True + def process_received_request(self): """ @@ -284,7 +304,7 @@ self.obj["ATTENDEE"] = [(self.user, attendee_attr)] self.update_dtstamp() - self.set_sequence(False) + self.update_sequence(False) self.send_message("REPLY", get_address(self.user), from_organiser=False) return True @@ -307,7 +327,7 @@ self.update_sender(organiser_attr) self.update_dtstamp() - self.set_sequence(True) + self.update_sequence(True) parts = [self.obj.to_part(method)] diff -r edb600931518 -r e6600c59ed01 tests/test_handle.py --- a/tests/test_handle.py Mon Oct 05 23:53:38 2015 +0200 +++ b/tests/test_handle.py Mon Oct 05 23:56:48 2015 +0200 @@ -79,7 +79,7 @@ self.update_sender(attendee_attr) self.obj["ATTENDEE"] = [(self.user, attendee_attr)] self.update_dtstamp() - self.set_sequence(False) + self.update_sequence(False) message = self.messenger.make_outgoing_message( [self.obj.to_part(method)],