# HG changeset patch # User Paul Boddie # Date 1427844699 -7200 # Node ID 4837e47eb5d6729efb9cd50b89e14c0cf2b0931f # Parent 712edee5d697a4c291e7adf4d9421e8c0fcc5946 Simplified attendee definition, employing a single attendees collection when handling an event form, updating attendees upon saving or sending events, and separating this operation from the sending operation. Changed the mechanism of adding attendees to only show an editable field after adding an attendee, and making the participation status area act as a form refresh button to be able to provide a menu for the current user as attendee. diff -r 712edee5d697 -r 4837e47eb5d6 htdocs/styles.css --- a/htdocs/styles.css Wed Apr 01 00:00:16 2015 +0200 +++ b/htdocs/styles.css Wed Apr 01 01:31:39 2015 +0200 @@ -184,6 +184,10 @@ input.remove:checked ~ label.remove, input.remove:not(:checked) ~ label.removed, +/* Hide the participation refresh control, selected using a label. */ + +input.refresh, + /* Hide the reset control, selected using a label. */ input#reset { diff -r 712edee5d697 -r 4837e47eb5d6 imiptools/client.py --- a/imiptools/client.py Wed Apr 01 00:00:16 2015 +0200 +++ b/imiptools/client.py Wed Apr 01 01:31:39 2015 +0200 @@ -19,20 +19,23 @@ this program. If not, see . """ -from imiptools.data import get_window_end, uri_items +from imiptools.data import get_uri, get_window_end, uri_dict, uri_items, uri_values from imiptools.dates import get_default_timezone from imiptools.profile import Preferences -def update_attendees(obj, added, removed): +def update_attendees(obj, attendees, removed): """ - Update the attendees in 'obj' with the given 'added' and 'removed' + Update the attendees in 'obj' with the given 'attendees' and 'removed' attendee lists. A list is returned containing the attendees whose attendance should be cancelled. """ to_cancel = [] + existing_attendees = uri_values(obj.get_values("ATTENDEE") or []) + added = set(attendees).difference(existing_attendees) + if added or removed: attendees = uri_items(obj.get_items("ATTENDEE") or []) sequence = obj.get_value("SEQUENCE") @@ -55,12 +58,26 @@ if added: for attendee in added: - attendees.append((attendee, {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"})) + attendee = attendee.strip() + if attendee: + attendees.append((get_uri(attendee), {"PARTSTAT" : "NEEDS-ACTION", "RSVP" : "TRUE"})) obj["ATTENDEE"] = attendees return to_cancel +def update_participation(obj, user, partstat): + + "Update the participation in 'obj' of 'user' with the given 'partstat'." + + existing_attendees = uri_dict(obj.get_value_map("ATTENDEE")) + + if partstat: + if existing_attendees.has_key(user): + existing_attendees[user]["PARTSTAT"] = partstat + if existing_attendees[user].has_key("RSVP"): + del existing_attendees[user]["RSVP"] + class Client: "Common handler and manager methods." diff -r 712edee5d697 -r 4837e47eb5d6 imipweb/event.py --- a/imipweb/event.py Wed Apr 01 00:00:16 2015 +0200 +++ b/imipweb/event.py Wed Apr 01 01:31:39 2015 +0200 @@ -20,7 +20,7 @@ """ from datetime import datetime, timedelta -from imiptools.client import update_attendees +from imiptools.client import update_attendees, update_participation from imiptools.data import get_uri, uri_dict, uri_values from imiptools.dates import format_datetime, to_date, get_datetime, \ get_datetime_item, get_period_item, \ @@ -89,31 +89,31 @@ if reply or invite or cancel or save: - # Update time periods (main and recurring). + # Update principal event details if organiser. + + if is_organiser: + + # Update time periods (main and recurring). + + if periods: + self.set_period_in_object(obj, periods[0]) + self.set_periods_in_object(obj, periods[1:]) - if periods: - self.set_period_in_object(obj, periods[0]) - self.set_periods_in_object(obj, periods[1:]) + # Update summary. + + if args.has_key("summary"): + obj["SUMMARY"] = [(args["summary"][0], {})] - # Update summary. + # Obtain any participants and those to be removed. - if args.has_key("summary"): - obj["SUMMARY"] = [(args["summary"][0], {})] + attendees = args.get("attendee") + removed = args.get("remove") + to_cancel = update_attendees(obj, attendees, removed) # Update attendee participation. - attendees = uri_dict(obj.get_value_map("ATTENDEE")) - if args.has_key("partstat"): - if attendees.has_key(self.user): - attendees[self.user]["PARTSTAT"] = args["partstat"][0] - if attendees[self.user].has_key("RSVP"): - del attendees[self.user]["RSVP"] - - # Obtain any participants to be added or removed. - - removed = args.get("remove") - added = args.get("added") + update_participation(obj, self.user, args["partstat"][0]) # Process any action. @@ -125,16 +125,19 @@ # Process the object and remove it from the list of requests. - if reply and handler.process_received_request(update) or \ - is_organiser and (invite or cancel) and \ - handler.process_created_request(invite and "REQUEST" or "CANCEL", update, removed, added): + if reply and handler.process_received_request(update): + self.remove_request(uid, recurrenceid) + + elif is_organiser and (invite or cancel): - self.remove_request(uid, recurrenceid) + if handler.process_created_request( + invite and "REQUEST" or "CANCEL", update, to_cancel): + + self.remove_request(uid, recurrenceid) # Save single user events. elif save: - to_cancel = update_attendees(obj, added, removed) self.store.set_event(self.user, uid, recurrenceid, node=obj.to_node()) self.update_freebusy(uid, recurrenceid, obj) self.remove_request(uid, recurrenceid) @@ -367,29 +370,23 @@ return False - def handle_new_attendees(self, obj): + def handle_attendees(self, obj): - "Add or remove new attendees. This does not affect the stored object." + "Add or remove attendees. This does not affect the stored object." args = self.env.get_args() - existing_attendees = uri_values(obj.get_values("ATTENDEE") or []) - new_attendees = args.get("added", []) - new_attendee = args.get("attendee", [""])[0] + attendees = args.get("attendee", []) if args.has_key("add"): - if new_attendee.strip(): - new_attendee = get_uri(new_attendee.strip()) - if new_attendee not in new_attendees and new_attendee not in existing_attendees: - new_attendees.append(new_attendee) - new_attendee = "" + attendees.append("") - if args.has_key("removenew"): - removed_attendee = args["removenew"][0] - if removed_attendee in new_attendees: - new_attendees.remove(removed_attendee) + if args.has_key("remove"): + removed_attendee = args["remove"][0] + if removed_attendee in attendees: + attendees.remove(removed_attendee) - return new_attendees, new_attendee + return attendees def get_event_period(self, obj): @@ -501,12 +498,10 @@ # Obtain basic event information, showing any necessary editing controls. is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user + initial_load = not args.has_key("editing") - if is_organiser: - new_attendees, new_attendee = self.handle_new_attendees(obj) - else: - new_attendees = [] - new_attendee = "" + attendees = is_organiser and self.handle_attendees(obj) or \ + (initial_load or not is_organiser) and uri_values(obj.get_values("ATTENDEE")) or [] (dtstart, dtstart_attr), (dtend, dtend_attr) = self.get_event_period(obj) self.show_object_datetime_controls(dtstart, dtend) @@ -528,7 +523,7 @@ rowspan = len(items) if name == "ATTENDEE": - rowspan += len(new_attendees) + 1 + rowspan = len(attendees) + 1 # for the add button elif not items: continue @@ -568,7 +563,83 @@ page.td.close() page.tr.close() - # Handle potentially many values. + # Handle attendees specially. + + elif name == "ATTENDEE": + attendee_map = dict(items) + first = True + + for i, value in enumerate(attendees): + if not first: + page.tr() + else: + first = False + + page.td(class_="objectvalue") + + # Obtain details of existing attendees. + + attr = attendee_map.get(value) + partstat = attr and attr.get("PARTSTAT") + + # Show a form control as organiser for new attendees. + + if is_organiser and not partstat: + page.input(name="attendee", type="value", value=value, size="40") + else: + page.input(name="attendee", type="hidden", value=value) + page.add(value) + page.add(" ") + + # Show participation status, editable for the current user. + + if value == self.user: + self._show_menu("partstat", partstat, self.partstat_items, "partstat") + + # Allow the participation indicator to act as a submit + # button in order to refresh the page and show a control for + # the current user, if indicated. + + elif is_organiser: + page.input(name="partstat-refresh", type="submit", value="refresh", id="partstat-%d" % i, class_="refresh") + page.label(dict(self.partstat_items).get(partstat, ""), for_="partstat-%s" % i, class_="partstat") + else: + page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat") + + # Permit organisers to remove attendees. + + if is_organiser: + + # Permit the removal of newly-added attendees. + + remove_type = partstat and "checkbox" or "submit" + + if value in args.get("remove", []): + page.input(name="remove", type=remove_type, value=value, id="remove-%d" % i, class_="remove", checked="checked") + else: + page.input(name="remove", type=remove_type, value=value, id="remove-%d" % i, class_="remove") + + page.label("Remove", for_="remove-%d" % i, class_="remove") + page.label("Uninvited", for_="remove-%d" % i, class_="removed") + + page.td.close() + page.tr.close() + + # Allow more attendees to be specified. + + if is_organiser: + i = len(attendees) + + if not first: + page.tr() + + page.td() + page.input(name="add", type="submit", value="add", id="add-%d" % i, class_="add") + page.label("Add attendee", for_="add-%d" % i, class_="add") + page.td.close() + page.tr.close() + + # Handle potentially many values of other kinds. else: first = True @@ -579,57 +650,8 @@ else: first = False - if name == "ATTENDEE": - value = get_uri(value) - - page.td(class_="objectvalue") - page.add(value) - page.add(" ") - - partstat = attr.get("PARTSTAT") - if value == self.user: - self._show_menu("partstat", partstat, self.partstat_items, "partstat") - else: - page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat") - - if is_organiser: - if value in args.get("remove", []): - page.input(name="remove", type="checkbox", value=value, id="remove-%d" % i, class_="remove", checked="checked") - else: - page.input(name="remove", type="checkbox", value=value, id="remove-%d" % i, class_="remove") - page.label("Remove", for_="remove-%d" % i, class_="remove") - page.label("Uninvited", for_="remove-%d" % i, class_="removed") - - else: - page.td(class_="objectvalue") - page.add(value) - - page.td.close() - page.tr.close() - - # Allow more attendees to be specified. - - if is_organiser and name == "ATTENDEE": - for i, attendee in enumerate(new_attendees): - if not first: - page.tr() - else: - first = False - - page.td() - page.input(name="added", type="value", value=attendee, size="40") - page.input(name="removenew", type="submit", value=attendee, id="removenew-%d" % i, class_="remove") - page.label("Remove", for_="removenew-%d" % i, class_="remove") - page.td.close() - page.tr.close() - - if not first: - page.tr() - - page.td() - page.input(name="attendee", type="value", value=new_attendee, size="40") - page.input(name="add", type="submit", value="add", id="add-%d" % i, class_="add") - page.label("Add", for_="add-%d" % i, class_="add") + page.td(class_="objectvalue") + page.add(value) page.td.close() page.tr.close() diff -r 712edee5d697 -r 4837e47eb5d6 imipweb/handler.py --- a/imipweb/handler.py Wed Apr 01 00:00:16 2015 +0200 +++ b/imipweb/handler.py Wed Apr 01 01:31:39 2015 +0200 @@ -19,8 +19,8 @@ this program. If not, see . """ -from imiptools.client import Client, update_attendees -from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \ +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.handlers import Handler @@ -131,20 +131,17 @@ return False - def process_created_request(self, method, update=False, removed=None, added=None): + def process_created_request(self, method, update=False, to_cancel=None): """ - Process the current request for the given 'user', sending a created - request of the given 'method' to attendees. Return whether any action - was taken. + Process the current request, sending a created request of the given + 'method' to attendees. Return whether any action was taken. If 'update' is given, the sequence number will be incremented in order to override any previous message. - If 'removed' is specified, a list of participants to be removed is - provided. - - If 'added' is specified, a list of participants to be added is provided. + If 'to_cancel' is specified, a list of participants to be sent cancel + messages is provided. """ organiser, organiser_attr = uri_item(self.obj.get_item("ORGANIZER")) @@ -152,10 +149,6 @@ if self.messenger and self.messenger.sender != get_address(organiser): organiser_attr["SENT-BY"] = get_uri(self.messenger.sender) - # Update the attendees in the event. - - to_cancel = update_attendees(self.obj, added, removed) - self.update_dtstamp() self.set_sequence(update)