# HG changeset patch # User Paul Boddie # Date 1423341617 -3600 # Node ID 29a41b1da2a60d6ff9d5a8e07419ee00354d8646 # Parent 5291a33d8fc7e7afb561a44c928f5877e2072ce1 Added datetime modification support for events, replacing the update control with a dynamic test for changed datetime information in events, only updating the SEQUENCE if the datetimes have changed. Handled PARTSTAT updates where the user is both organiser and attendee, also removing RSVP when setting PARTSTAT. Improved the event presentation, removing the dedicated attendance column. diff -r 5291a33d8fc7 -r 29a41b1da2a6 imip_manager.py --- a/imip_manager.py Sat Feb 07 21:33:20 2015 +0100 +++ b/imip_manager.py Sat Feb 07 21:40:17 2015 +0100 @@ -33,7 +33,8 @@ from imiptools.content import Handler from imiptools.data import get_address, get_uri, make_freebusy, parse_object, \ Object, to_part -from imiptools.dates import format_datetime, get_datetime, get_datetime_item, \ +from imiptools.dates import format_datetime, format_time, get_date, get_datetime, \ + get_datetime_item, \ get_end_of_day, get_start_of_day, get_start_of_next_day, \ get_timestamp, ends_on_same_day, to_timezone from imiptools.mail import Messenger @@ -456,12 +457,9 @@ self.redirect(self.env.new_url("%s-0" % uid)) - def handle_request(self, uid, obj, queued): + def handle_request(self, uid, obj): - """ - Handle actions involving the given 'uid' and 'obj' object, where - 'queued' indicates that the object has not yet been handled. - """ + "Handle actions involving the given 'uid' and 'obj' object." # Handle a submitted form. @@ -476,9 +474,39 @@ if args.has_key("partstat"): organisers = obj.get_value_map("ORGANIZER") attendees = obj.get_value_map("ATTENDEE") - d = attendees.has_key(self.user) and attendees or organisers.has_key(self.user) and organisers or None - if d: - d[self.user]["PARTSTAT"] = args["partstat"][0] + for d in attendees, organisers: + if d.has_key(self.user): + d[self.user]["PARTSTAT"] = args["partstat"][0] + if d[self.user].has_key("RSVP"): + del d[self.user]["RSVP"] + + is_organiser = obj.get_value("ORGANIZER") == self.user + + # Obtain the user's timezone and process datetime values. + + update = False + error = False + + if is_organiser: + t = self.handle_date_controls("dtstart") + if t: + dtstart, tzid = t + update = update or self.set_datetime_in_object(dtstart, tzid, "DTSTART", obj) + else: + error = True + + t = self.handle_date_controls("dtend") + if t: + dtend, tzid = t + update = update or self.set_datetime_in_object(dtend, tzid, "DTEND", obj) + else: + error = True + + if not error: + error = dtstart > dtend + + if error: + return False # Process any action. @@ -487,7 +515,6 @@ invite = args.has_key("invite") cancel = args.has_key("cancel") save = args.has_key("save") - update = not queued and args.has_key("update") if reply or invite or cancel: @@ -523,16 +550,52 @@ return handled - # Page fragment methods. - - def show_request_controls(self, obj, needs_update): + def handle_date_controls(self, name): """ - Show form controls for a request concerning 'obj', indicating whether - an update will be performed if 'needs_update' is specified as a true - value. + Handle date control information for fields starting with 'name', + returning a (datetime, tzid) tuple or None if the fields cannot be used + to construct a datetime object. """ + args = self.env.get_args() + tzid = self.get_tzid() + + if args.has_key("%s-date" % name): + date = args["%s-date" % name][0] + hour = args.get("%s-hour" % name, [None])[0] + minute = args.get("%s-minute" % name, [None])[0] + second = args.get("%s-second" % name, [None])[0] + tzid = args.get("%s-tzid" % name, [tzid])[0] + + time = (hour or minute or second) and "T%s%s%s" % (hour, minute, second) or "" + value = "%s%s" % (date, time) + dt = get_datetime(value, {"TZID" : tzid}) + if dt: + return dt, tzid + + return None + + def set_datetime_in_object(self, dt, tzid, property, obj): + + """ + Set 'dt' and 'tzid' for the given 'property' in 'obj', returning whether + an update has occurred. + """ + + if dt: + old_value = obj.get_value(property) + obj[property] = [get_datetime_item(dt, tzid)] + return format_datetime(dt) != old_value + + return False + + # Page fragment methods. + + def show_request_controls(self, obj): + + "Show form controls for a request concerning 'obj'." + page = self.page is_organiser = obj.get_value("ORGANIZER") == self.user @@ -548,10 +611,7 @@ # Show appropriate options depending on the role of the user. if is_attendee and not is_organiser: - if needs_update: - page.p("This request can be updated as follows:") - else: - page.p("An action is required for this request:") + page.p("An action is required for this request:") page.p() page.input(name="reply", type="submit", value="Reply") @@ -561,10 +621,7 @@ if is_organiser: if have_other_attendees: - if needs_update: - page.p("As organiser, you can perform the following:") - else: - page.p("As organiser, you will need to perform an action:") + page.p("As organiser, you can perform the following:") page.p() page.input(name="invite", type="submit", value="Invite") @@ -581,11 +638,6 @@ page.input(name="discard", type="submit", value="Discard") page.p.close() - # Updated objects need to have details updated upon sending. - - if needs_update: - page.input(name="update", type="hidden", value="true") - object_labels = { "SUMMARY" : "Summary", "DTSTART" : "Start", @@ -602,7 +654,7 @@ ("DELEGATED", "Delegated"), ] - def show_object_on_page(self, uid, obj, needs_update): + def show_object_on_page(self, uid, obj): """ Show the calendar object with the given 'uid' and representation 'obj' @@ -621,7 +673,7 @@ page.table(class_="object", cellspacing=5, cellpadding=5) page.thead() page.tr() - page.th("Event", class_="mainheading", colspan=3) + page.th("Event", class_="mainheading", colspan=2) page.tr.close() page.thead.close() page.tbody() @@ -637,12 +689,19 @@ if name in ["DTSTART", "DTEND"]: value, attr = obj.get_item(name) - tzid = attr.get("TZID", tzid) - value = ( + event_tzid = attr.get("TZID", tzid) + strvalue = ( name == "DTSTART" and self.format_datetime or self.format_end_datetime - )(to_timezone(get_datetime(value), tzid), "full") + )(to_timezone(get_datetime(value), event_tzid), "full") page.th(label, class_="objectheading") - page.td(value, colspan=2) + + if is_organiser: + page.td() + self._show_date_controls(name.lower(), value, attr, tzid) + page.td.close() + else: + page.td(strvalue) + page.tr.close() # Handle the summary specially. @@ -650,7 +709,7 @@ elif name == "SUMMARY": value = obj.get_value(name) page.th(label, class_="objectheading") - page.td(colspan=2) + page.td() if is_organiser: page.input(name="summary", type="text", value=value, size=80) else: @@ -678,16 +737,15 @@ if name in ("ATTENDEE", "ORGANIZER"): page.td(class_="objectattribute") page.add(value) - page.td.close() - page.td(class_="partstat") + page.add(" ") partstat = attr.get("PARTSTAT") - if value == self.user: + if value == self.user and (not is_organiser or name == "ORGANIZER"): self._show_menu("partstat", partstat, self.partstat_items) else: - page.add(dict(self.partstat_items).get(partstat, "")) + page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat") else: - page.td(class_="objectattribute", colspan=2) + page.td(class_="objectattribute") page.add(value) page.td.close() @@ -728,7 +786,7 @@ if found_obj: page.a(found_obj.get_value("SUMMARY"), href=self.env.new_url(found_uid)) - self.show_request_controls(obj, needs_update) + self.show_request_controls(obj) page.form.close() def show_requests_on_page(self): @@ -816,14 +874,13 @@ if not obj: return False - is_request = uid in self._get_requests() - handled = self.handle_request(uid, obj, is_request) + handled = self.handle_request(uid, obj) if handled: return True self.new_page(title="Event") - self.show_object_on_page(uid, obj, not is_request) + self.show_object_on_page(uid, obj) return True @@ -1286,9 +1343,9 @@ identifier = "slot-%s" % value return value, identifier - def _show_menu(self, name, value, items): + def _show_menu(self, name, default, items): page = self.page - values = self.env.get_args().get(name, [value]) + values = self.env.get_args().get(name, [default]) page.select(name=name) for v, label in items: if v in values: @@ -1297,6 +1354,48 @@ page.option(label, value=v) page.select.close() + def _show_date_controls(self, name, default, attr, tzid): + + """ + Show date controls for a field with the given 'name' and 'default' value + and 'attr', with the given 'tzid' being used if no other time regime + information is provided. + """ + + page = self.page + args = self.env.get_args() + + event_tzid = attr.get("TZID", tzid) + dt = get_datetime(default, attr) + + # Show dates for up to one week around the current date. + + base = get_date(dt) + items = [] + for i in range(-7, 8): + d = base + timedelta(i) + items.append((format_datetime(d), self.format_date(d, "full"))) + + self._show_menu("%s-date" % name, format_datetime(base), items) + + # Show time details. + + if isinstance(dt, datetime): + hour = args.get("%s-hour" % name, "%02d" % dt.hour) + minute = args.get("%s-minute" % name, "%02d" % dt.minute) + second = args.get("%s-second" % name, "%02d" % dt.second) + page.add(" ") + page.input(name="%s-hour" % name, type="text", value=hour, maxlength=2, size=2) + page.add(":") + page.input(name="%s-minute" % name, type="text", value=minute, maxlength=2, size=2) + page.add(":") + page.input(name="%s-second" % name, type="text", value=second, maxlength=2, size=2) + page.add(" ") + self._show_menu("%s-tzid" % name, event_tzid, + [(event_tzid, event_tzid)] + ( + event_tzid != tzid and [(tzid, tzid)] or [] + )) + # Incoming HTTP request direction. def select_action(self):