# HG changeset patch # User Paul Boddie # Date 1505163042 -7200 # Node ID 95818819dcc5f59a12285c38c57654efb117cd54 # Parent ea2b740d77457ed980488af1bd180e7704326381 Introduced request-level state to retain computed information instead of serialising it to the request arguments and then re-computing it for display. Attempted to channel all accesses to event state via "current" data methods, moving mutation operations and eliminating some duplication. diff -r ea2b740d7745 -r 95818819dcc5 imipweb/env.py --- a/imipweb/env.py Mon Sep 11 22:31:35 2017 +0200 +++ b/imipweb/env.py Mon Sep 11 22:50:42 2017 +0200 @@ -3,7 +3,7 @@ """ Web interface utilities. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -37,6 +37,10 @@ self.user = None self.query_string = None + # Retain computed state. + + self.state = {} + def get_args(self): if self.args is None: if self.get_method() != "POST": @@ -91,4 +95,7 @@ path = self.get_path() return "%s/%s" % (path.rstrip("/"), path_info.lstrip("/")) + def get_state(self): + return self.state + # vim: tabstop=4 expandtab shiftwidth=4 diff -r ea2b740d7745 -r 95818819dcc5 imipweb/event.py --- a/imipweb/event.py Mon Sep 11 22:31:35 2017 +0200 +++ b/imipweb/event.py Mon Sep 11 22:50:42 2017 +0200 @@ -24,7 +24,7 @@ from imiptools.dates import format_datetime, to_timezone from imiptools.mail import Messenger from imipweb.data import EventPeriod, event_period_from_period, \ - get_period_control_values, set_period_control_values, \ + get_period_control_values, \ PeriodError from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject @@ -146,7 +146,6 @@ _ = self.get_translator() page = self.page - args = self.env.get_args() attendees = uri_values(self.get_current_attendees()) is_attendee = self.user in attendees @@ -367,7 +366,6 @@ _ = self.get_translator() page = self.page - args = self.env.get_args() attendee_uri = get_uri(attendee) partstat = attendee_attr and attendee_attr.get("PARTSTAT") @@ -413,7 +411,9 @@ # Permit the removal of newly-added attendees. remove_type = self.can_remove_attendee(attendee_uri) and "submit" or "checkbox" - self.control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove") + self.control("remove", remove_type, str(i), + str(i) in self.get_state("remove", list), + id="remove-%d" % i, class_="remove") page.label(_("Remove"), for_="remove-%d" % i, class_="remove") page.label(for_="remove-%d" % i, class_="removed") @@ -474,7 +474,6 @@ _ = self.get_translator() page = self.page - args = self.env.get_args() # Isolate the controls from neighbouring tables. @@ -511,7 +510,7 @@ remove_type = self.can_remove_recurrence(period) and "submit" or "checkbox" self.control("recur-remove", remove_type, str(index), - str(index) in args.get("recur-remove", []), + str(index) in self.get_state("recur-remove", list), id="recur-remove-%d" % index, class_="remove") page.label(_("Remove"), for_="recur-remove-%d" % index, class_="remove") @@ -797,6 +796,11 @@ args = self.env.get_args() + # Update mutable state. + + self.update_current_attendees() + self.update_current_recurrences() + # Get the possible actions. reply = args.has_key("reply") @@ -874,8 +878,9 @@ # Obtain any new participants and those to be removed. - attendees = self.get_attendees_from_page() - removed = [attendees[int(i)] for i in args.get("remove", [])] + attendees = self.get_current_attendees() + removed = self.get_removed_attendees(attendees) + added, to_cancel = self.update_attendees(attendees, removed) single_user = not attendees or uri_values(attendees) == [self.user] changed = added or changed @@ -987,13 +992,16 @@ "Return period details for the main start/end period in an event." - return self.get_main_period_from_page().as_event_period() + return self.get_current_main_period().as_event_period() def handle_recurrence_periods(self): "Return period details for the recurrences specified for an event." - return [p.as_event_period(i) for i, p in enumerate(self.get_recurrences_from_page())] + periods = [] + for i, p in enumerate(self.get_current_recurrences()): + periods.append(p.as_event_period(i)) + return periods # Access to form-originating object information. @@ -1024,40 +1032,6 @@ origin_name="recur-origin", replaced_name="recur-replaced", tzid=self.get_tzid()) - def set_recurrences_in_page(self, recurrences): - - "Set the recurrences defined in the event form." - - set_period_control_values(recurrences, self.env.get_args(), - "dtstart-recur", "dtend-recur", - "dtend-control-recur", "dttimes-control-recur", - "recur-origin", "recur-replaced") - - def get_removed_periods(self, periods): - - """ - Return those from the recurrence 'periods' to remove upon updating an - event along with those to exclude in a tuple of the form (unscheduled, - excluded). - """ - - args = self.env.get_args() - to_unschedule = [] - to_exclude = [] - - for i in args.get("recur-remove", []): - try: - period = periods[int(i)] - except (IndexError, ValueError): - continue - - if not self.can_edit_recurrence(period) and self.is_organiser(): - to_unschedule.append(period) - else: - to_exclude.append(period) - - return to_unschedule, to_exclude - def get_attendees_from_page(self): """ @@ -1121,9 +1095,8 @@ else: still_to_remove.append(str(i)) - args["remove"] = still_to_remove + self.set_state("remove", still_to_remove) - args["attendee"] = attendees return attendees def update_recurrences_from_page(self): @@ -1159,13 +1132,34 @@ else: still_to_remove.append(str(i)) - args["recur-remove"] = still_to_remove + self.set_state("recur-remove", still_to_remove) - self.set_recurrences_in_page(recurrences) return recurrences # Access to current object information. + def get_state(self, key, fn, overwrite=False): + + """ + Return state for the given 'key', using 'fn' if no state exists to + compute and set the state. If 'overwrite' is set to a true value, + compute and return the state using 'fn' regardless of existing state. + """ + + state = self.env.get_state() + if overwrite or not state.has_key(key): + state[key] = fn() + return state[key] + + def set_state(self, key, value): + + """ + Set state for the given 'key', establishing new state or replacing any + existing state with the given 'value'. + """ + + self.env.get_state()[key] = value + def get_current_main_period(self): """ @@ -1173,10 +1167,8 @@ on whether editing has begun or whether the object has just been loaded. """ - if self.is_initial_load(): - return self.get_stored_main_period() - else: - return self.get_main_period_from_page() + return self.get_state("main", self.is_initial_load() and + self.get_stored_main_period or self.get_main_period_from_page) def get_current_recurrences(self): @@ -1185,19 +1177,16 @@ details where no editing is in progress, using form data otherwise. """ - if self.is_initial_load(): - return self.get_stored_recurrences() - else: - return self.get_recurrences_from_page() + return self.get_state("recurrences", self.is_initial_load() and + self.get_stored_recurrences or self.get_recurrences_from_page) def update_current_recurrences(self): "Return an updated collection of recurrences for the current object." - if self.is_initial_load(): - return self.get_stored_recurrences() - else: - return self.update_recurrences_from_page() + return self.get_state("recurrences", self.is_initial_load() and + self.get_stored_recurrences or self.update_recurrences_from_page, + overwrite=True) def get_current_attendees(self): @@ -1207,19 +1196,52 @@ form. """ - if self.is_initial_load(): - return self.get_stored_attendees() - else: - return self.get_attendees_from_page() + return self.get_state("attendees", self.is_initial_load() and + self.get_stored_attendees or self.get_attendees_from_page) def update_current_attendees(self): "Return an updated collection of attendees for the current object." - if self.is_initial_load(): - return self.get_stored_attendees() - else: - return self.update_attendees_from_page() + return self.get_state("attendees", self.is_initial_load() and + self.get_stored_attendees or self.update_attendees_from_page, + overwrite=True) + + def get_removed_attendees(self, attendees): + + """ + Return details of 'attendees' to be removed according to previously + determined removal information. + """ + + removed = [] + for i in self.get_state("remove", list): + removed.append(attendees[int(i)]) + return removed + + def get_removed_periods(self, periods): + + """ + Return those from the recurrence 'periods' to remove upon updating an + event along with those to exclude in a tuple of the form (unscheduled, + excluded). + """ + + to_unschedule = [] + to_exclude = [] + + for i in self.get_state("recur-remove", list): + try: + period = periods[int(i)] + except (IndexError, ValueError): + continue + + if not self.can_edit_recurrence(period) and self.is_organiser(): + to_unschedule.append(period) + else: + to_exclude.append(period) + + return to_unschedule, to_exclude # Full page output methods. @@ -1239,9 +1261,6 @@ if not errors: return True - self.update_current_attendees() - self.update_current_recurrences() - _ = self.get_translator() self.new_page(title=_("Event"))