2.1 --- a/imipweb/data.py Sat Oct 07 01:11:21 2017 +0200
2.2 +++ b/imipweb/data.py Sat Oct 07 22:38:01 2017 +0200
2.3 @@ -26,6 +26,57 @@
2.4 to_date, to_utc_datetime, to_timezone
2.5 from imiptools.period import RecurringPeriod
2.6
2.7 +# General editing abstractions.
2.8 +
2.9 +class State:
2.10 +
2.11 + "Manage computed state."
2.12 +
2.13 + def __init__(self, callables):
2.14 +
2.15 + """
2.16 + Define state variable initialisation using the given 'callables', which
2.17 + is a mapping that defines a callable for each variable name that is
2.18 + invoked when the variable is first requested.
2.19 + """
2.20 +
2.21 + self.state = {}
2.22 + self.callables = callables
2.23 +
2.24 + def get_callable(self, key):
2.25 + return self.callables.get(key, lambda: None)
2.26 +
2.27 + def get(self, key, reset=False):
2.28 +
2.29 + """
2.30 + Return state for the given 'key', using the configured callable to
2.31 + compute and set the state if no state is already defined.
2.32 +
2.33 + If 'reset' is set to a true value, compute and return the state using
2.34 + the configured callable regardless of any existing state.
2.35 + """
2.36 +
2.37 + if reset or not self.state.has_key(key):
2.38 + self.state[key] = self.get_callable(key)()
2.39 +
2.40 + return self.state[key]
2.41 +
2.42 + def set(self, key, value):
2.43 + self.state[key] = value
2.44 +
2.45 + def __getitem__(self, key):
2.46 + return self.get(key)
2.47 +
2.48 + def __setitem__(self, key, value):
2.49 + self.set(key, value)
2.50 +
2.51 + def has_changed(self, key):
2.52 + return self.get_callable(key)() != self.get(key)
2.53 +
2.54 +
2.55 +
2.56 +# Period-related abstractions.
2.57 +
2.58 class PeriodError(Exception):
2.59 pass
2.60
3.1 --- a/imipweb/event.py Sat Oct 07 01:11:21 2017 +0200
3.2 +++ b/imipweb/event.py Sat Oct 07 22:38:01 2017 +0200
3.3 @@ -30,7 +30,7 @@
3.4 form_period_from_period, form_periods_from_updated_periods, \
3.5 get_period_control_values, \
3.6 remove_from_collection, \
3.7 - PeriodError
3.8 + PeriodError, State
3.9 from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject
3.10
3.11 # Fake gettext method for strings to be translated later.
3.12 @@ -41,8 +41,17 @@
3.13
3.14 "A resource presenting the details of an event."
3.15
3.16 - def __init__(self, resource=None):
3.17 - ResourceClientForObject.__init__(self, resource)
3.18 + def __init__(self, resource=None, messenger=None):
3.19 + ResourceClientForObject.__init__(self, resource, messenger or Messenger())
3.20 +
3.21 + # Manage editing state.
3.22 +
3.23 + self.state = State({
3.24 + "attendees" : self.get_current_attendees,
3.25 + "main" : self.get_current_main_period,
3.26 + "recurrences" : self.get_current_recurrence_periods,
3.27 + "remove" : list,
3.28 + })
3.29
3.30 # Various property values and labels.
3.31
3.32 @@ -137,7 +146,7 @@
3.33
3.34 page = self.page
3.35
3.36 - attendees = uri_values(self.get_current_attendees())
3.37 + attendees = uri_values(self.state.get("attendees"))
3.38 is_attendee = self.user in attendees
3.39
3.40 if not self.obj.is_shared():
3.41 @@ -199,8 +208,9 @@
3.42
3.43 # Obtain basic event information, generating any necessary editing controls.
3.44
3.45 - attendees = self.get_current_attendees()
3.46 - period = self.get_current_main_period()
3.47 + attendees = self.state.get("attendees")
3.48 + period = self.state.get("main")
3.49 + stored_period = self.get_stored_main_period()
3.50 self.show_object_datetime_controls(period)
3.51
3.52 # Provide a summary of the object.
3.53 @@ -377,7 +387,7 @@
3.54
3.55 remove_type = self.can_remove_attendee(attendee_uri) and "submit" or "checkbox"
3.56 self.control("remove", remove_type, str(i),
3.57 - attendee in self.get_state("remove", list),
3.58 + attendee in self.state.get("remove"),
3.59 id="remove-%d" % i, class_="remove")
3.60
3.61 page.label(_("Remove"), for_="remove-%d" % i, class_="remove")
3.62 @@ -412,7 +422,7 @@
3.63
3.64 # Obtain the periods associated with the event.
3.65
3.66 - recurrences = self.get_current_recurrence_periods()
3.67 + recurrences = self.state.get("recurrences")
3.68
3.69 if len(recurrences) < 1:
3.70 return
3.71 @@ -471,7 +481,6 @@
3.72
3.73 if period.cancelled:
3.74 self.control("recur-restore", "checkbox", str(index),
3.75 - period in self.get_state("recur-restore", list),
3.76 id="recur-restore-%d" % index, class_="restore")
3.77
3.78 page.label(for_="recur-restore-%d" % index, class_="restore")
3.79 @@ -494,7 +503,6 @@
3.80 remove_type = self.can_remove_recurrence(period) and "submit" or "checkbox"
3.81
3.82 self.control("recur-remove", remove_type, str(index),
3.83 - period in self.get_state("recur-remove", list),
3.84 id="recur-remove-%d" % index, class_="remove")
3.85
3.86 page.label(_("Remove"), for_="recur-remove-%d" % index, class_="remove")
3.87 @@ -528,7 +536,7 @@
3.88 return
3.89
3.90 attendees = self.get_verbose_attendees(attendees)
3.91 - current_attendees = [uri for (name, uri) in uri_parts(self.get_current_attendees())]
3.92 + current_attendees = [uri for (name, uri) in uri_parts(self.state.get("attendees"))]
3.93 current_periods = set(self.get_periods(self.obj))
3.94
3.95 # Get suggestions. Attendees are aggregated and reference the existing
3.96 @@ -662,7 +670,7 @@
3.97 conflicts = set()
3.98 attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE"))
3.99
3.100 - for name, participant in uri_parts(self.get_current_attendees()):
3.101 + for name, participant in uri_parts(self.state.get("attendees")):
3.102 if participant == self.user:
3.103 freebusy = self.store.get_freebusy(participant)
3.104 elif participant:
3.105 @@ -746,9 +754,6 @@
3.106
3.107 "A request handler for the event page."
3.108
3.109 - def __init__(self, resource=None, messenger=None):
3.110 - ResourceClientForObject.__init__(self, resource, messenger or Messenger())
3.111 -
3.112 def link_to(self, uid=None, recurrenceid=None):
3.113 args = self.env.get_query()
3.114 d = {}
3.115 @@ -850,8 +855,8 @@
3.116
3.117 # Obtain any new participants and those to be removed.
3.118
3.119 - attendees = self.get_current_attendees()
3.120 - removed = self.get_removed_attendees()
3.121 + attendees = self.state.get("attendees")
3.122 + removed = self.state.get("remove")
3.123
3.124 added, to_cancel = self.update_attendees(attendees, removed)
3.125
3.126 @@ -978,9 +983,9 @@
3.127 "Return period details for the periods specified for an event."
3.128
3.129 periods = []
3.130 - periods.append(self.get_current_main_period().as_event_period())
3.131 + periods.append(self.state.get("main").as_event_period())
3.132
3.133 - for i, p in enumerate(self.get_current_recurrence_periods()):
3.134 + for i, p in enumerate(self.state.get("recurrences")):
3.135 periods.append(p.as_event_period(i))
3.136
3.137 return periods
3.138 @@ -1094,7 +1099,7 @@
3.139 if remove:
3.140 still_to_remove = remove_from_collection(attendees, remove,
3.141 self.can_remove_attendee)
3.142 - self.set_state("remove", still_to_remove)
3.143 + self.state.set("remove", still_to_remove)
3.144
3.145 if add or add_suggested or remove:
3.146 attendees = filter_duplicates(attendees)
3.147 @@ -1114,7 +1119,7 @@
3.148 add = args.has_key("recur-add")
3.149
3.150 if add:
3.151 - period = self.get_current_main_period()
3.152 + period = self.state.get("main").as_form_period()
3.153 period.origin = "RDATE"
3.154 period.replacement = False
3.155 period.cancelled = False
3.156 @@ -1144,27 +1149,6 @@
3.157
3.158 # Access to current object information.
3.159
3.160 - def get_state(self, key, fn, overwrite=False):
3.161 -
3.162 - """
3.163 - Return state for the given 'key', using 'fn' if no state exists to
3.164 - compute and set the state. If 'overwrite' is set to a true value,
3.165 - compute and return the state using 'fn' regardless of existing state.
3.166 - """
3.167 -
3.168 - if overwrite or not self.state.has_key(key):
3.169 - self.state[key] = fn()
3.170 - return self.state[key]
3.171 -
3.172 - def set_state(self, key, value):
3.173 -
3.174 - """
3.175 - Set state for the given 'key', establishing new state or replacing any
3.176 - existing state with the given 'value'.
3.177 - """
3.178 -
3.179 - self.state[key] = value
3.180 -
3.181 def get_current_main_period(self):
3.182
3.183 """
3.184 @@ -1172,8 +1156,10 @@
3.185 on whether editing has begun or whether the object has just been loaded.
3.186 """
3.187
3.188 - return self.get_state("main", self.is_initial_load() and
3.189 - self.get_stored_main_period or self.get_main_period_from_page)
3.190 + if self.is_initial_load():
3.191 + return self.get_stored_main_period()
3.192 + else:
3.193 + return self.get_main_period_from_page()
3.194
3.195 def get_current_recurrence_periods(self):
3.196
3.197 @@ -1182,16 +1168,22 @@
3.198 details where no editing is in progress, using form data otherwise.
3.199 """
3.200
3.201 - return self.get_state("recurrences", self.is_initial_load() and
3.202 - self.get_stored_recurrence_periods or self.get_recurrences_from_page)
3.203 + if self.is_initial_load():
3.204 + return self.get_stored_recurrence_periods()
3.205 + else:
3.206 + return self.get_recurrences_from_page()
3.207
3.208 def update_current_recurrences(self):
3.209
3.210 "Return an updated collection of recurrences for the current object."
3.211
3.212 - return self.get_state("recurrences", self.is_initial_load() and
3.213 - self.get_stored_recurrence_periods or self.update_recurrences_from_page,
3.214 - overwrite=True)
3.215 + if self.is_initial_load():
3.216 + l = self.get_stored_recurrence_periods()
3.217 + else:
3.218 + l = self.update_recurrences_from_page()
3.219 +
3.220 + self.state.set("recurrences", l)
3.221 + return l
3.222
3.223 def get_current_attendees(self):
3.224
3.225 @@ -1201,25 +1193,22 @@
3.226 form.
3.227 """
3.228
3.229 - return self.get_state("attendees", self.is_initial_load() and
3.230 - self.get_stored_attendees or self.get_attendees_from_page)
3.231 + if self.is_initial_load():
3.232 + return self.get_stored_attendees()
3.233 + else:
3.234 + return self.get_attendees_from_page()
3.235
3.236 def update_current_attendees(self):
3.237
3.238 "Return an updated collection of attendees for the current object."
3.239
3.240 - return self.get_state("attendees", self.is_initial_load() and
3.241 - self.get_stored_attendees or self.update_attendees_from_page,
3.242 - overwrite=True)
3.243 -
3.244 - def get_removed_attendees(self):
3.245 + if self.is_initial_load():
3.246 + l = self.get_stored_attendees()
3.247 + else:
3.248 + l = self.update_attendees_from_page()
3.249
3.250 - """
3.251 - Return details of attendees to be removed according to previously
3.252 - determined removal information.
3.253 - """
3.254 -
3.255 - return self.get_state("remove", list)
3.256 + self.state.set("attendees", l)
3.257 + return l
3.258
3.259 # Full page output methods.
3.260