# HG changeset patch # User Paul Boddie # Date 1507406893 -7200 # Node ID b139a6f7662b95a7388a7df5d07d53cf8085c961 # Parent d7a3053f4e7e5ee48c4cbbc4ce9cd675667427f7 Introduced a special state management abstraction for editing. diff -r d7a3053f4e7e -r b139a6f7662b imipweb/data.py --- a/imipweb/data.py Sat Oct 07 00:37:47 2017 +0200 +++ b/imipweb/data.py Sat Oct 07 22:08:13 2017 +0200 @@ -25,6 +25,57 @@ to_date 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 d7a3053f4e7e -r b139a6f7662b imipweb/event.py --- a/imipweb/event.py Sat Oct 07 00:37:47 2017 +0200 +++ b/imipweb/event.py Sat Oct 07 22:08:13 2017 +0200 @@ -28,7 +28,7 @@ classify_periods, filter_duplicates, \ remove_from_collection, \ get_period_control_values, \ - PeriodError + PeriodError, State from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject # Fake gettext method for strings to be translated later. @@ -39,8 +39,18 @@ "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_recurrences, + "recur-remove" : list, + "remove" : list, + }) # Various property values and labels. @@ -152,7 +162,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(): @@ -214,8 +224,8 @@ # 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) @@ -417,7 +427,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") @@ -452,7 +462,7 @@ # Obtain the periods associated with the event. - recurrences = self.get_current_recurrences() + recurrences = self.state.get("recurrences") if len(recurrences) < 1: return @@ -515,7 +525,7 @@ 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), + period in self.state.get("recur-remove"), id="recur-remove-%d" % index, class_="remove") page.label(_("Remove"), for_="recur-remove-%d" % index, class_="remove") @@ -549,7 +559,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 @@ -687,7 +697,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: @@ -771,9 +781,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 = {} @@ -886,8 +893,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) single_user = not attendees or uri_values(attendees) == [self.user] @@ -1000,14 +1007,14 @@ "Return period details for the main start/end period in an event." - return self.get_current_main_period().as_event_period() + return self.state.get("main").as_event_period() def handle_recurrence_periods(self): "Return period details for the recurrences specified for an event." periods = [] - for i, p in enumerate(self.get_current_recurrences()): + for i, p in enumerate(self.state.get("recurrences")): periods.append(p.as_event_period(i)) return periods @@ -1052,7 +1059,7 @@ # Get remaining periods and those whose removal is deferred. new, changed, unchanged, replaced, to_remove = classify_periods(periods, - self.get_state("recur-remove", list)) + self.state.get("recur-remove")) return new + unchanged, changed, to_remove @@ -1114,7 +1121,7 @@ if remove: still_to_remove = remove_from_collection(attendees, args["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) @@ -1132,7 +1139,7 @@ add = args.has_key("recur-add") if add: - period = self.get_current_main_period().as_form_period() + period = self.state.get("main").as_form_period() period.origin = "RDATE" recurrences.append(period) @@ -1144,33 +1151,12 @@ if remove: still_to_remove = remove_from_collection(recurrences, args["recur-remove"], self.can_remove_recurrence) - self.set_state("recur-remove", still_to_remove) + self.state.set("recur-remove", still_to_remove) 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. - """ - - 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): """ @@ -1178,8 +1164,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_recurrences(self): @@ -1188,16 +1176,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_recurrences or self.get_recurrences_from_page) + if self.is_initial_load(): + return self.get_stored_recurrences() + 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_recurrences or self.update_recurrences_from_page, - overwrite=True) + if self.is_initial_load(): + l = self.get_stored_recurrences() + else: + l = self.update_recurrences_from_page() + + self.state.set("recurrences", l) + return l def get_current_attendees(self): @@ -1207,25 +1201,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 d7a3053f4e7e -r b139a6f7662b imipweb/resource.py --- a/imipweb/resource.py Sat Oct 07 00:37:47 2017 +0200 +++ b/imipweb/resource.py Sat Oct 07 22:08:13 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):