1.1 --- a/imipweb/data.py Sat Oct 07 00:37:47 2017 +0200
1.2 +++ b/imipweb/data.py Sat Oct 07 22:08:13 2017 +0200
1.3 @@ -25,6 +25,57 @@
1.4 to_date
1.5 from imiptools.period import RecurringPeriod
1.6
1.7 +# General editing abstractions.
1.8 +
1.9 +class State:
1.10 +
1.11 + "Manage computed state."
1.12 +
1.13 + def __init__(self, callables):
1.14 +
1.15 + """
1.16 + Define state variable initialisation using the given 'callables', which
1.17 + is a mapping that defines a callable for each variable name that is
1.18 + invoked when the variable is first requested.
1.19 + """
1.20 +
1.21 + self.state = {}
1.22 + self.callables = callables
1.23 +
1.24 + def get_callable(self, key):
1.25 + return self.callables.get(key, lambda: None)
1.26 +
1.27 + def get(self, key, reset=False):
1.28 +
1.29 + """
1.30 + Return state for the given 'key', using the configured callable to
1.31 + compute and set the state if no state is already defined.
1.32 +
1.33 + If 'reset' is set to a true value, compute and return the state using
1.34 + the configured callable regardless of any existing state.
1.35 + """
1.36 +
1.37 + if reset or not self.state.has_key(key):
1.38 + self.state[key] = self.get_callable(key)()
1.39 +
1.40 + return self.state[key]
1.41 +
1.42 + def set(self, key, value):
1.43 + self.state[key] = value
1.44 +
1.45 + def __getitem__(self, key):
1.46 + return self.get(key)
1.47 +
1.48 + def __setitem__(self, key, value):
1.49 + self.set(key, value)
1.50 +
1.51 + def has_changed(self, key):
1.52 + return self.get_callable(key)() != self.get(key)
1.53 +
1.54 +
1.55 +
1.56 +# Period-related abstractions.
1.57 +
1.58 class PeriodError(Exception):
1.59 pass
1.60
2.1 --- a/imipweb/event.py Sat Oct 07 00:37:47 2017 +0200
2.2 +++ b/imipweb/event.py Sat Oct 07 22:08:13 2017 +0200
2.3 @@ -28,7 +28,7 @@
2.4 classify_periods, filter_duplicates, \
2.5 remove_from_collection, \
2.6 get_period_control_values, \
2.7 - PeriodError
2.8 + PeriodError, State
2.9 from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject
2.10
2.11 # Fake gettext method for strings to be translated later.
2.12 @@ -39,8 +39,18 @@
2.13
2.14 "A resource presenting the details of an event."
2.15
2.16 - def __init__(self, resource=None):
2.17 - ResourceClientForObject.__init__(self, resource)
2.18 + def __init__(self, resource=None, messenger=None):
2.19 + ResourceClientForObject.__init__(self, resource, messenger or Messenger())
2.20 +
2.21 + # Manage editing state.
2.22 +
2.23 + self.state = State({
2.24 + "attendees" : self.get_current_attendees,
2.25 + "main" : self.get_current_main_period,
2.26 + "recurrences" : self.get_current_recurrences,
2.27 + "recur-remove" : list,
2.28 + "remove" : list,
2.29 + })
2.30
2.31 # Various property values and labels.
2.32
2.33 @@ -152,7 +162,7 @@
2.34
2.35 page = self.page
2.36
2.37 - attendees = uri_values(self.get_current_attendees())
2.38 + attendees = uri_values(self.state.get("attendees"))
2.39 is_attendee = self.user in attendees
2.40
2.41 if not self.obj.is_shared():
2.42 @@ -214,8 +224,8 @@
2.43
2.44 # Obtain basic event information, generating any necessary editing controls.
2.45
2.46 - attendees = self.get_current_attendees()
2.47 - period = self.get_current_main_period()
2.48 + attendees = self.state.get("attendees")
2.49 + period = self.state.get("main")
2.50 stored_period = self.get_stored_main_period()
2.51 self.show_object_datetime_controls(period)
2.52
2.53 @@ -417,7 +427,7 @@
2.54
2.55 remove_type = self.can_remove_attendee(attendee_uri) and "submit" or "checkbox"
2.56 self.control("remove", remove_type, str(i),
2.57 - attendee in self.get_state("remove", list),
2.58 + attendee in self.state.get("remove"),
2.59 id="remove-%d" % i, class_="remove")
2.60
2.61 page.label(_("Remove"), for_="remove-%d" % i, class_="remove")
2.62 @@ -452,7 +462,7 @@
2.63
2.64 # Obtain the periods associated with the event.
2.65
2.66 - recurrences = self.get_current_recurrences()
2.67 + recurrences = self.state.get("recurrences")
2.68
2.69 if len(recurrences) < 1:
2.70 return
2.71 @@ -515,7 +525,7 @@
2.72 remove_type = self.can_remove_recurrence(period) and "submit" or "checkbox"
2.73
2.74 self.control("recur-remove", remove_type, str(index),
2.75 - period in self.get_state("recur-remove", list),
2.76 + period in self.state.get("recur-remove"),
2.77 id="recur-remove-%d" % index, class_="remove")
2.78
2.79 page.label(_("Remove"), for_="recur-remove-%d" % index, class_="remove")
2.80 @@ -549,7 +559,7 @@
2.81 return
2.82
2.83 attendees = self.get_verbose_attendees(attendees)
2.84 - current_attendees = [uri for (name, uri) in uri_parts(self.get_current_attendees())]
2.85 + current_attendees = [uri for (name, uri) in uri_parts(self.state.get("attendees"))]
2.86 current_periods = set(self.get_periods(self.obj))
2.87
2.88 # Get suggestions. Attendees are aggregated and reference the existing
2.89 @@ -687,7 +697,7 @@
2.90 conflicts = set()
2.91 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
2.92
2.93 - for name, participant in uri_parts(self.get_current_attendees()):
2.94 + for name, participant in uri_parts(self.state.get("attendees")):
2.95 if participant == self.user:
2.96 freebusy = self.store.get_freebusy(participant)
2.97 elif participant:
2.98 @@ -771,9 +781,6 @@
2.99
2.100 "A request handler for the event page."
2.101
2.102 - def __init__(self, resource=None, messenger=None):
2.103 - ResourceClientForObject.__init__(self, resource, messenger or Messenger())
2.104 -
2.105 def link_to(self, uid=None, recurrenceid=None):
2.106 args = self.env.get_query()
2.107 d = {}
2.108 @@ -886,8 +893,8 @@
2.109
2.110 # Obtain any new participants and those to be removed.
2.111
2.112 - attendees = self.get_current_attendees()
2.113 - removed = self.get_removed_attendees()
2.114 + attendees = self.state.get("attendees")
2.115 + removed = self.state.get("remove")
2.116
2.117 added, to_cancel = self.update_attendees(attendees, removed)
2.118 single_user = not attendees or uri_values(attendees) == [self.user]
2.119 @@ -1000,14 +1007,14 @@
2.120
2.121 "Return period details for the main start/end period in an event."
2.122
2.123 - return self.get_current_main_period().as_event_period()
2.124 + return self.state.get("main").as_event_period()
2.125
2.126 def handle_recurrence_periods(self):
2.127
2.128 "Return period details for the recurrences specified for an event."
2.129
2.130 periods = []
2.131 - for i, p in enumerate(self.get_current_recurrences()):
2.132 + for i, p in enumerate(self.state.get("recurrences")):
2.133 periods.append(p.as_event_period(i))
2.134 return periods
2.135
2.136 @@ -1052,7 +1059,7 @@
2.137 # Get remaining periods and those whose removal is deferred.
2.138
2.139 new, changed, unchanged, replaced, to_remove = classify_periods(periods,
2.140 - self.get_state("recur-remove", list))
2.141 + self.state.get("recur-remove"))
2.142
2.143 return new + unchanged, changed, to_remove
2.144
2.145 @@ -1114,7 +1121,7 @@
2.146 if remove:
2.147 still_to_remove = remove_from_collection(attendees,
2.148 args["remove"], self.can_remove_attendee)
2.149 - self.set_state("remove", still_to_remove)
2.150 + self.state.set("remove", still_to_remove)
2.151
2.152 if add or add_suggested or remove:
2.153 attendees = filter_duplicates(attendees)
2.154 @@ -1132,7 +1139,7 @@
2.155 add = args.has_key("recur-add")
2.156
2.157 if add:
2.158 - period = self.get_current_main_period().as_form_period()
2.159 + period = self.state.get("main").as_form_period()
2.160 period.origin = "RDATE"
2.161 recurrences.append(period)
2.162
2.163 @@ -1144,33 +1151,12 @@
2.164 if remove:
2.165 still_to_remove = remove_from_collection(recurrences,
2.166 args["recur-remove"], self.can_remove_recurrence)
2.167 - self.set_state("recur-remove", still_to_remove)
2.168 + self.state.set("recur-remove", still_to_remove)
2.169
2.170 return recurrences
2.171
2.172 # Access to current object information.
2.173
2.174 - def get_state(self, key, fn, overwrite=False):
2.175 -
2.176 - """
2.177 - Return state for the given 'key', using 'fn' if no state exists to
2.178 - compute and set the state. If 'overwrite' is set to a true value,
2.179 - compute and return the state using 'fn' regardless of existing state.
2.180 - """
2.181 -
2.182 - if overwrite or not self.state.has_key(key):
2.183 - self.state[key] = fn()
2.184 - return self.state[key]
2.185 -
2.186 - def set_state(self, key, value):
2.187 -
2.188 - """
2.189 - Set state for the given 'key', establishing new state or replacing any
2.190 - existing state with the given 'value'.
2.191 - """
2.192 -
2.193 - self.state[key] = value
2.194 -
2.195 def get_current_main_period(self):
2.196
2.197 """
2.198 @@ -1178,8 +1164,10 @@
2.199 on whether editing has begun or whether the object has just been loaded.
2.200 """
2.201
2.202 - return self.get_state("main", self.is_initial_load() and
2.203 - self.get_stored_main_period or self.get_main_period_from_page)
2.204 + if self.is_initial_load():
2.205 + return self.get_stored_main_period()
2.206 + else:
2.207 + return self.get_main_period_from_page()
2.208
2.209 def get_current_recurrences(self):
2.210
2.211 @@ -1188,16 +1176,22 @@
2.212 details where no editing is in progress, using form data otherwise.
2.213 """
2.214
2.215 - return self.get_state("recurrences", self.is_initial_load() and
2.216 - self.get_stored_recurrences or self.get_recurrences_from_page)
2.217 + if self.is_initial_load():
2.218 + return self.get_stored_recurrences()
2.219 + else:
2.220 + return self.get_recurrences_from_page()
2.221
2.222 def update_current_recurrences(self):
2.223
2.224 "Return an updated collection of recurrences for the current object."
2.225
2.226 - return self.get_state("recurrences", self.is_initial_load() and
2.227 - self.get_stored_recurrences or self.update_recurrences_from_page,
2.228 - overwrite=True)
2.229 + if self.is_initial_load():
2.230 + l = self.get_stored_recurrences()
2.231 + else:
2.232 + l = self.update_recurrences_from_page()
2.233 +
2.234 + self.state.set("recurrences", l)
2.235 + return l
2.236
2.237 def get_current_attendees(self):
2.238
2.239 @@ -1207,25 +1201,22 @@
2.240 form.
2.241 """
2.242
2.243 - return self.get_state("attendees", self.is_initial_load() and
2.244 - self.get_stored_attendees or self.get_attendees_from_page)
2.245 + if self.is_initial_load():
2.246 + return self.get_stored_attendees()
2.247 + else:
2.248 + return self.get_attendees_from_page()
2.249
2.250 def update_current_attendees(self):
2.251
2.252 "Return an updated collection of attendees for the current object."
2.253
2.254 - return self.get_state("attendees", self.is_initial_load() and
2.255 - self.get_stored_attendees or self.update_attendees_from_page,
2.256 - overwrite=True)
2.257 -
2.258 - def get_removed_attendees(self):
2.259 + if self.is_initial_load():
2.260 + l = self.get_stored_attendees()
2.261 + else:
2.262 + l = self.update_attendees_from_page()
2.263
2.264 - """
2.265 - Return details of attendees to be removed according to previously
2.266 - determined removal information.
2.267 - """
2.268 -
2.269 - return self.get_state("remove", list)
2.270 + self.state.set("attendees", l)
2.271 + return l
2.272
2.273 # Full page output methods.
2.274