1.1 --- a/imiptools/client.py Mon Oct 05 23:53:38 2015 +0200
1.2 +++ b/imiptools/client.py Mon Oct 05 23:56:48 2015 +0200
1.3 @@ -25,7 +25,7 @@
1.4 is_new_object, make_freebusy, to_part, \
1.5 uri_dict, uri_items, uri_values
1.6 from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \
1.7 - get_duration, get_time, get_timestamp
1.8 + get_duration, get_timestamp
1.9 from imiptools.period import can_schedule, remove_period, \
1.10 remove_additional_periods, remove_affected_period, \
1.11 update_freebusy
1.12 @@ -370,21 +370,19 @@
1.13 self.obj["RECURRENCE-ID"] = [self.obj.get_item("DTSTART")]
1.14 self.recurrenceid = self.obj.get_recurrenceid()
1.15
1.16 - def update_dtstamp(self):
1.17 + def update_dtstamp(self, obj=None):
1.18
1.19 - "Update the DTSTAMP in the current object."
1.20 + "Update the DTSTAMP in the current object or any given object 'obj'."
1.21 +
1.22 + obj = obj or self.obj
1.23 + self.dtstamp = obj.update_dtstamp()
1.24
1.25 - dtstamp = self.obj.get_utc_datetime("DTSTAMP")
1.26 - utcnow = get_time()
1.27 - self.dtstamp = format_datetime(dtstamp and dtstamp > utcnow and dtstamp or utcnow)
1.28 - self.obj["DTSTAMP"] = [(self.dtstamp, {})]
1.29 + def update_sequence(self, increment=False, obj=None):
1.30
1.31 - def set_sequence(self, increment=False):
1.32 + "Update the SEQUENCE in the current object or any given object 'obj'."
1.33
1.34 - "Update the SEQUENCE in the current object."
1.35 -
1.36 - sequence = self.obj.get_value("SEQUENCE") or "0"
1.37 - self.obj["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
1.38 + obj = obj or self.obj
1.39 + obj.update_sequence(increment)
1.40
1.41 def merge_attendance(self, attendees):
1.42
1.43 @@ -866,4 +864,17 @@
1.44
1.45 return True
1.46
1.47 + # Convenience methods for removing counter-proposals and updating the
1.48 + # request queue.
1.49 +
1.50 + def remove_counter(self, attendee):
1.51 + self.remove_counters([attendee])
1.52 +
1.53 + def remove_counters(self, attendees):
1.54 + for attendee in attendees:
1.55 + self.store.remove_counter(self.user, attendee, self.uid, self.recurrenceid)
1.56 +
1.57 + if not self.store.get_counters(self.user, self.uid, self.recurrenceid):
1.58 + self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
1.59 +
1.60 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imiptools/data.py Mon Oct 05 23:53:38 2015 +0200
2.2 +++ b/imiptools/data.py Mon Oct 05 23:56:48 2015 +0200
2.3 @@ -28,7 +28,8 @@
2.4 get_datetime_tzid, \
2.5 get_duration, get_period, get_period_item, \
2.6 get_recurrence_start_point, \
2.7 - get_tzid, to_datetime, to_timezone, to_utc_datetime
2.8 + get_time, get_tzid, to_datetime, to_timezone, \
2.9 + to_utc_datetime
2.10 from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod, period_overlaps
2.11 from vCalendar import iterwrite, parse, ParseError, to_dict, to_node
2.12 from vRecurrence import get_parameters, get_rule
2.13 @@ -419,6 +420,24 @@
2.14
2.15 return old_values != set(self.get_date_values("RDATE") or [])
2.16
2.17 + def update_dtstamp(self):
2.18 +
2.19 + "Update the DTSTAMP in the object."
2.20 +
2.21 + dtstamp = self.get_utc_datetime("DTSTAMP")
2.22 + utcnow = get_time()
2.23 + dtstamp = format_datetime(dtstamp and dtstamp > utcnow and dtstamp or utcnow)
2.24 + self["DTSTAMP"] = [(dtstamp, {})]
2.25 + return dtstamp
2.26 +
2.27 + def update_sequence(self, increment=False):
2.28 +
2.29 + "Set or update the SEQUENCE in the object."
2.30 +
2.31 + sequence = self.get_value("SEQUENCE") or "0"
2.32 + self["SEQUENCE"] = [(str(int(sequence) + (increment and 1 or 0)), {})]
2.33 + return sequence
2.34 +
2.35 def update_exceptions(self, excluded):
2.36
2.37 """
3.1 --- a/imiptools/handlers/person.py Mon Oct 05 23:53:38 2015 +0200
3.2 +++ b/imiptools/handlers/person.py Mon Oct 05 23:56:48 2015 +0200
3.3 @@ -164,11 +164,11 @@
3.4
3.5 if cancel:
3.6 self.store.cancel_event(self.user, self.uid, self.recurrenceid)
3.7 - self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
3.8
3.9 # Remove any associated request.
3.10
3.11 self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
3.12 + self.store.remove_counters(self.user, self.uid, self.recurrenceid)
3.13
3.14 # No return message will occur to update the free/busy
3.15 # information, so this is done here using outgoing message
4.1 --- a/imiptools/handlers/person_outgoing.py Mon Oct 05 23:53:38 2015 +0200
4.2 +++ b/imiptools/handlers/person_outgoing.py Mon Oct 05 23:56:48 2015 +0200
4.3 @@ -119,6 +119,7 @@
4.4 # Remove any associated request.
4.5
4.6 self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
4.7 + self.store.remove_counters(self.user, self.uid, self.recurrenceid)
4.8
4.9 # Update free/busy information.
4.10
4.11 @@ -197,11 +198,11 @@
4.12
4.13 if cancel_entire_event:
4.14 self.store.cancel_event(self.user, self.uid, self.recurrenceid)
4.15 - self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
4.16
4.17 # Remove any associated request.
4.18
4.19 self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
4.20 + self.store.remove_counters(self.user, self.uid, self.recurrenceid)
4.21
4.22 return True
4.23
4.24 @@ -217,7 +218,7 @@
4.25 if not self.have_new_object():
4.26 return False
4.27
4.28 - self.store.dequeue_request(self.user, self.uid, self.recurrenceid)
4.29 + self.remove_counters(uri_values(self.obj.get_values("ATTENDEE")))
4.30
4.31 class Event(PersonHandler):
4.32
5.1 --- a/imipweb/event.py Mon Oct 05 23:53:38 2015 +0200
5.2 +++ b/imipweb/event.py Mon Oct 05 23:56:48 2015 +0200
5.3 @@ -534,6 +534,8 @@
5.4 if not attendees:
5.5 return
5.6
5.7 + attendees = self.get_verbose_attendees(attendees)
5.8 +
5.9 page.p("The following counter-proposals have been received for this event:")
5.10
5.11 page.table(cellspacing=5, cellpadding=5, class_="counters")
5.12 @@ -549,8 +551,9 @@
5.13 page.thead.close()
5.14 page.tbody()
5.15
5.16 - for attendee in attendees:
5.17 - obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee)
5.18 + for i, attendee in enumerate(attendees):
5.19 + attendee_uri = get_uri(attendee)
5.20 + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee_uri)
5.21 periods = self.get_periods(obj)
5.22
5.23 first = True
5.24 @@ -562,7 +565,6 @@
5.25
5.26 if first:
5.27 page.td(attendee, rowspan=len(periods))
5.28 - first = False
5.29
5.30 start = self.format_datetime(to_timezone(p.get_start(), tzid), "long")
5.31 end = self.format_datetime(to_timezone(p.get_end(), tzid), "long")
5.32 @@ -570,7 +572,14 @@
5.33 page.td(start)
5.34 page.td(end)
5.35
5.36 + if first:
5.37 + page.td(rowspan=len(periods))
5.38 + self.control("decline-%d" % i, "submit", "Decline")
5.39 + self.control("decline", "hidden", attendee)
5.40 + page.td.close()
5.41 +
5.42 page.tr.close()
5.43 + first = False
5.44
5.45 page.tbody.close()
5.46 page.table.close()
5.47 @@ -705,6 +714,25 @@
5.48 cancel = args.has_key("cancel")
5.49 ignore = args.has_key("ignore")
5.50 save = args.has_key("save")
5.51 + decline = filter(None, [(arg.startswith("decline-") and arg[len("decline-"):]) for arg in args.keys()])
5.52 +
5.53 + # Decline a counter-proposal.
5.54 +
5.55 + if decline:
5.56 + for s in decline:
5.57 + try:
5.58 + i = int(s)
5.59 + except (IndexError, ValueError):
5.60 + pass
5.61 + else:
5.62 + attendee_uri = get_uri(args.get("decline", [])[i])
5.63 + self.process_declined_counter(attendee_uri)
5.64 +
5.65 + # Update the counter-proposals synchronously instead of
5.66 + # assuming that the outgoing handler will have done so
5.67 + # before the form is refreshed.
5.68 +
5.69 + self.remove_counter(attendee_uri)
5.70
5.71 have_action = reply or discard or create or cancel or ignore or save
5.72
5.73 @@ -923,9 +951,22 @@
5.74
5.75 def get_attendees_from_page(self):
5.76
5.77 - "Return attendees from the request."
5.78 + """
5.79 + Return attendees from the request, using any stored attributes to obtain
5.80 + verbose details.
5.81 + """
5.82 +
5.83 + return self.get_verbose_attendees(self.env.get_args().get("attendee", []))
5.84
5.85 - return self.env.get_args().get("attendee", [])
5.86 + def get_verbose_attendees(self, attendees):
5.87 +
5.88 + """
5.89 + Use any stored attributes to obtain verbose details for the given
5.90 + 'attendees'.
5.91 + """
5.92 +
5.93 + attendee_map = self.obj.get_value_map("ATTENDEE")
5.94 + return [get_verbose_address(value, attendee_map.get(value)) for value in attendees]
5.95
5.96 def update_attendees_from_page(self):
5.97
6.1 --- a/imipweb/resource.py Mon Oct 05 23:53:38 2015 +0200
6.2 +++ b/imipweb/resource.py Mon Oct 05 23:56:48 2015 +0200
6.3 @@ -260,6 +260,10 @@
6.4 if part:
6.5 parts.append(part)
6.6
6.7 + self._send_message(sender, recipients, parts)
6.8 +
6.9 + def _send_message(self, sender, recipients, parts):
6.10 +
6.11 # Explicitly specify the outgoing BCC recipient since we are sending as
6.12 # the generic calendar user.
6.13
6.14 @@ -268,6 +272,22 @@
6.15
6.16 # Action methods.
6.17
6.18 + def process_declined_counter(self, attendee):
6.19 +
6.20 + "Process a declined counter-proposal."
6.21 +
6.22 + # Obtain the counter-proposal for the attendee.
6.23 +
6.24 + obj = self.get_stored_object(self.uid, self.recurrenceid, "counters", attendee)
6.25 + if not obj:
6.26 + return False
6.27 +
6.28 + method = "DECLINECOUNTER"
6.29 + obj.update_dtstamp()
6.30 + obj.update_sequence(False)
6.31 + self._send_message(get_address(self.user), [get_address(attendee)], parts=[obj.to_part(method)])
6.32 + return True
6.33 +
6.34 def process_received_request(self):
6.35
6.36 """
6.37 @@ -284,7 +304,7 @@
6.38
6.39 self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
6.40 self.update_dtstamp()
6.41 - self.set_sequence(False)
6.42 + self.update_sequence(False)
6.43 self.send_message("REPLY", get_address(self.user), from_organiser=False)
6.44 return True
6.45
6.46 @@ -307,7 +327,7 @@
6.47
6.48 self.update_sender(organiser_attr)
6.49 self.update_dtstamp()
6.50 - self.set_sequence(True)
6.51 + self.update_sequence(True)
6.52
6.53 parts = [self.obj.to_part(method)]
6.54
7.1 --- a/tests/test_handle.py Mon Oct 05 23:53:38 2015 +0200
7.2 +++ b/tests/test_handle.py Mon Oct 05 23:56:48 2015 +0200
7.3 @@ -79,7 +79,7 @@
7.4 self.update_sender(attendee_attr)
7.5 self.obj["ATTENDEE"] = [(self.user, attendee_attr)]
7.6 self.update_dtstamp()
7.7 - self.set_sequence(False)
7.8 + self.update_sequence(False)
7.9
7.10 message = self.messenger.make_outgoing_message(
7.11 [self.obj.to_part(method)],