# HG changeset patch # User Paul Boddie # Date 1507408681 -7200 # Node ID f9c72bcdde7a34ab3831d88a485d48da800d739a # Parent e981fec918769ccbb00d271ad0c02dee2af2f22d# Parent b139a6f7662b95a7388a7df5d07d53cf8085c961 Merged editing state management functionality from the default branch. Fixed erroneous operation ordering in the client messaging functionality. diff -r e981fec91876 -r f9c72bcdde7a imiptools/client.py --- a/imiptools/client.py Sat Oct 07 01:11:21 2017 +0200 +++ b/imiptools/client.py Sat Oct 07 22:38:01 2017 +0200 @@ -1075,8 +1075,8 @@ # Send a cancellation to all uninvited attendees. + obj = self.make_cancel_object(to_cancel) recipients = self.get_recipients(obj) - obj = self.make_cancel_object(to_cancel) message = self.make_cancel_message(recipients, obj) self.send_message(message, recipients) diff -r e981fec91876 -r f9c72bcdde7a imipweb/data.py --- a/imipweb/data.py Sat Oct 07 01:11:21 2017 +0200 +++ b/imipweb/data.py Sat Oct 07 22:38:01 2017 +0200 @@ -26,6 +26,57 @@ to_date, to_utc_datetime, to_timezone from imiptools.period import RecurringPeriod +# General editing abstractions. + +class State: + + "Manage computed state." + + def __init__(self, callables): + + """ + Define state variable initialisation using the given 'callables', which + is a mapping that defines a callable for each variable name that is + invoked when the variable is first requested. + """ + + self.state = {} + self.callables = callables + + def get_callable(self, key): + return self.callables.get(key, lambda: None) + + def get(self, key, reset=False): + + """ + Return state for the given 'key', using the configured callable to + compute and set the state if no state is already defined. + + If 'reset' is set to a true value, compute and return the state using + the configured callable regardless of any existing state. + """ + + if reset or not self.state.has_key(key): + self.state[key] = self.get_callable(key)() + + return self.state[key] + + def set(self, key, value): + self.state[key] = value + + def __getitem__(self, key): + return self.get(key) + + def __setitem__(self, key, value): + self.set(key, value) + + def has_changed(self, key): + return self.get_callable(key)() != self.get(key) + + + +# Period-related abstractions. + class PeriodError(Exception): pass diff -r e981fec91876 -r f9c72bcdde7a imipweb/event.py --- a/imipweb/event.py Sat Oct 07 01:11:21 2017 +0200 +++ b/imipweb/event.py Sat Oct 07 22:38:01 2017 +0200 @@ -30,7 +30,7 @@ form_period_from_period, form_periods_from_updated_periods, \ get_period_control_values, \ remove_from_collection, \ - PeriodError + PeriodError, State from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject # Fake gettext method for strings to be translated later. @@ -41,8 +41,17 @@ "A resource presenting the details of an event." - def __init__(self, resource=None): - ResourceClientForObject.__init__(self, resource) + def __init__(self, resource=None, messenger=None): + ResourceClientForObject.__init__(self, resource, messenger or Messenger()) + + # Manage editing state. + + self.state = State({ + "attendees" : self.get_current_attendees, + "main" : self.get_current_main_period, + "recurrences" : self.get_current_recurrence_periods, + "remove" : list, + }) # Various property values and labels. @@ -137,7 +146,7 @@ page = self.page - attendees = uri_values(self.get_current_attendees()) + attendees = uri_values(self.state.get("attendees")) is_attendee = self.user in attendees if not self.obj.is_shared(): @@ -199,8 +208,9 @@ # Obtain basic event information, generating any necessary editing controls. - attendees = self.get_current_attendees() - period = self.get_current_main_period() + attendees = self.state.get("attendees") + period = self.state.get("main") + stored_period = self.get_stored_main_period() self.show_object_datetime_controls(period) # Provide a summary of the object. @@ -377,7 +387,7 @@ remove_type = self.can_remove_attendee(attendee_uri) and "submit" or "checkbox" self.control("remove", remove_type, str(i), - attendee in self.get_state("remove", list), + attendee in self.state.get("remove"), id="remove-%d" % i, class_="remove") page.label(_("Remove"), for_="remove-%d" % i, class_="remove") @@ -412,7 +422,7 @@ # Obtain the periods associated with the event. - recurrences = self.get_current_recurrence_periods() + recurrences = self.state.get("recurrences") if len(recurrences) < 1: return @@ -471,7 +481,6 @@ if period.cancelled: self.control("recur-restore", "checkbox", str(index), - period in self.get_state("recur-restore", list), id="recur-restore-%d" % index, class_="restore") page.label(for_="recur-restore-%d" % index, class_="restore") @@ -494,7 +503,6 @@ remove_type = self.can_remove_recurrence(period) and "submit" or "checkbox" self.control("recur-remove", remove_type, str(index), - period in self.get_state("recur-remove", list), id="recur-remove-%d" % index, class_="remove") page.label(_("Remove"), for_="recur-remove-%d" % index, class_="remove") @@ -528,7 +536,7 @@ return attendees = self.get_verbose_attendees(attendees) - current_attendees = [uri for (name, uri) in uri_parts(self.get_current_attendees())] + current_attendees = [uri for (name, uri) in uri_parts(self.state.get("attendees"))] current_periods = set(self.get_periods(self.obj)) # Get suggestions. Attendees are aggregated and reference the existing @@ -662,7 +670,7 @@ conflicts = set() attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) - for name, participant in uri_parts(self.get_current_attendees()): + for name, participant in uri_parts(self.state.get("attendees")): if participant == self.user: freebusy = self.store.get_freebusy(participant) elif participant: @@ -746,9 +754,6 @@ "A request handler for the event page." - def __init__(self, resource=None, messenger=None): - ResourceClientForObject.__init__(self, resource, messenger or Messenger()) - def link_to(self, uid=None, recurrenceid=None): args = self.env.get_query() d = {} @@ -850,8 +855,8 @@ # Obtain any new participants and those to be removed. - attendees = self.get_current_attendees() - removed = self.get_removed_attendees() + attendees = self.state.get("attendees") + removed = self.state.get("remove") added, to_cancel = self.update_attendees(attendees, removed) @@ -978,9 +983,9 @@ "Return period details for the periods specified for an event." periods = [] - periods.append(self.get_current_main_period().as_event_period()) + periods.append(self.state.get("main").as_event_period()) - for i, p in enumerate(self.get_current_recurrence_periods()): + for i, p in enumerate(self.state.get("recurrences")): periods.append(p.as_event_period(i)) return periods @@ -1094,7 +1099,7 @@ if remove: still_to_remove = remove_from_collection(attendees, remove, self.can_remove_attendee) - self.set_state("remove", still_to_remove) + self.state.set("remove", still_to_remove) if add or add_suggested or remove: attendees = filter_duplicates(attendees) @@ -1114,7 +1119,7 @@ add = args.has_key("recur-add") if add: - period = self.get_current_main_period() + period = self.state.get("main").as_form_period() period.origin = "RDATE" period.replacement = False period.cancelled = False @@ -1144,27 +1149,6 @@ # 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. - """ - - if overwrite or not self.state.has_key(key): - self.state[key] = fn() - return self.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.state[key] = value - def get_current_main_period(self): """ @@ -1172,8 +1156,10 @@ on whether editing has begun or whether the object has just been loaded. """ - return self.get_state("main", self.is_initial_load() and - self.get_stored_main_period or self.get_main_period_from_page) + if self.is_initial_load(): + return self.get_stored_main_period() + else: + return self.get_main_period_from_page() def get_current_recurrence_periods(self): @@ -1182,16 +1168,22 @@ details where no editing is in progress, using form data otherwise. """ - return self.get_state("recurrences", self.is_initial_load() and - self.get_stored_recurrence_periods or self.get_recurrences_from_page) + if self.is_initial_load(): + return self.get_stored_recurrence_periods() + else: + return self.get_recurrences_from_page() def update_current_recurrences(self): "Return an updated collection of recurrences for the current object." - return self.get_state("recurrences", self.is_initial_load() and - self.get_stored_recurrence_periods or self.update_recurrences_from_page, - overwrite=True) + if self.is_initial_load(): + l = self.get_stored_recurrence_periods() + else: + l = self.update_recurrences_from_page() + + self.state.set("recurrences", l) + return l def get_current_attendees(self): @@ -1201,25 +1193,22 @@ form. """ - return self.get_state("attendees", self.is_initial_load() and - self.get_stored_attendees or self.get_attendees_from_page) + if self.is_initial_load(): + return self.get_stored_attendees() + else: + return self.get_attendees_from_page() def update_current_attendees(self): "Return an updated collection of attendees for the current object." - 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): + if self.is_initial_load(): + l = self.get_stored_attendees() + else: + l = self.update_attendees_from_page() - """ - Return details of attendees to be removed according to previously - determined removal information. - """ - - return self.get_state("remove", list) + self.state.set("attendees", l) + return l # Full page output methods. diff -r e981fec91876 -r f9c72bcdde7a imipweb/resource.py --- a/imipweb/resource.py Sat Oct 07 01:11:21 2017 +0200 +++ b/imipweb/resource.py Sat Oct 07 22:38:01 2017 +0200 @@ -56,10 +56,6 @@ self.page = resource and resource.page or markup.page() self.html_ids = None - # Computed state. - - self.state = {} - # Presentation methods. def new_page(self, title):