1.1 --- a/imipweb/event.py Mon Apr 06 16:39:52 2015 +0200
1.2 +++ b/imipweb/event.py Mon Apr 06 22:57:23 2015 +0200
1.3 @@ -19,7 +19,7 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 -from datetime import datetime, timedelta
1.8 +from datetime import date, datetime, timedelta
1.9 from imiptools.client import update_attendees, update_participation
1.10 from imiptools.data import get_uri, uri_dict, uri_values
1.11 from imiptools.dates import format_datetime, to_date, get_datetime, \
1.12 @@ -27,8 +27,10 @@
1.13 to_timezone
1.14 from imiptools.mail import Messenger
1.15 from imiptools.period import have_conflict
1.16 -from imipweb.data import EventPeriod, handle_date_control_values, \
1.17 - handle_period_controls
1.18 +from imipweb.data import EventPeriod, \
1.19 + end_date_from_calendar, end_date_to_calendar, \
1.20 + event_period_from_recurrence_period, \
1.21 + FormDate, FormPeriod, PeriodError
1.22 from imipweb.handler import ManagerHandler
1.23 from imipweb.resource import Resource
1.24 import pytz
1.25 @@ -109,13 +111,15 @@
1.26
1.27 # Update time periods (main and recurring).
1.28
1.29 - period, errors = self.handle_main_period()
1.30 - if errors:
1.31 - return errors
1.32 + try:
1.33 + period = self.handle_main_period()
1.34 + except PeriodError, exc:
1.35 + return exc.args
1.36
1.37 - periods, errors = self.handle_recurrence_periods()
1.38 - if errors:
1.39 - return errors
1.40 + try:
1.41 + periods = self.handle_recurrence_periods()
1.42 + except PeriodError, exc:
1.43 + return exc.args
1.44
1.45 self.set_period_in_object(obj, period)
1.46 self.set_periods_in_object(obj, periods)
1.47 @@ -180,49 +184,62 @@
1.48
1.49 return None
1.50
1.51 + def set_period_in_object(self, obj, period):
1.52 +
1.53 + "Set in the given 'obj' the given 'period' as the main start and end."
1.54 +
1.55 + p = period.as_event_period()
1.56 + result = self.set_datetime_in_object(p.start, p.start_attr and p.start_attr.get("TZID"), "DTSTART", obj)
1.57 + result = self.set_datetime_in_object(end_date_to_calendar(p.end), p.end_attr and p.end_attr.get("TZID"), "DTEND", obj) or result
1.58 + return result
1.59 +
1.60 + def set_periods_in_object(self, obj, periods):
1.61 +
1.62 + "Set in the given 'obj' the given 'periods'."
1.63 +
1.64 + update = False
1.65 +
1.66 + old_values = obj.get_values("RDATE")
1.67 + new_rdates = []
1.68 +
1.69 + if obj.has_key("RDATE"):
1.70 + del obj["RDATE"]
1.71 +
1.72 + for period in periods:
1.73 + p = period.as_event_period()
1.74 + tzid = p.start_attr and p.start_attr.get("TZID") or p.end_attr and p.end_attr.get("TZID")
1.75 + new_rdates.append(get_period_item(p.start, end_date_to_calendar(p.end), tzid))
1.76 +
1.77 + obj["RDATE"] = new_rdates
1.78 +
1.79 + # NOTE: To do: calculate the update status.
1.80 + return update
1.81 +
1.82 + def set_datetime_in_object(self, dt, tzid, property, obj):
1.83 +
1.84 + """
1.85 + Set 'dt' and 'tzid' for the given 'property' in 'obj', returning whether
1.86 + an update has occurred.
1.87 + """
1.88 +
1.89 + if dt:
1.90 + old_value = obj.get_value(property)
1.91 + obj[property] = [get_datetime_item(dt, tzid)]
1.92 + return format_datetime(dt) != old_value
1.93 +
1.94 + return False
1.95 +
1.96 def handle_main_period(self):
1.97
1.98 "Return period details for the main start/end period in an event."
1.99
1.100 - args = self.env.get_args()
1.101 -
1.102 - dtend_enabled = args.get("dtend-control", [None])[0]
1.103 - dttimes_enabled = args.get("dttimes-control", [None])[0]
1.104 - start_values = self.get_date_control_values("dtstart")
1.105 - end_values = self.get_date_control_values("dtend")
1.106 -
1.107 - period, errors = handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled)
1.108 -
1.109 - if errors:
1.110 - return None, errors
1.111 - else:
1.112 - return period, errors
1.113 + return self.get_main_period().as_event_period()
1.114
1.115 def handle_recurrence_periods(self):
1.116
1.117 "Return period details for the recurrences specified for an event."
1.118
1.119 - args = self.env.get_args()
1.120 -
1.121 - all_dtend_enabled = args.get("dtend-control-recur", [])
1.122 - all_dttimes_enabled = args.get("dttimes-control-recur", [])
1.123 - all_start_values = self.get_date_control_values("dtstart-recur", multiple=True)
1.124 - all_end_values = self.get_date_control_values("dtend-recur", multiple=True, tzid_name="dtstart-recur")
1.125 -
1.126 - periods = []
1.127 - errors = []
1.128 -
1.129 - for index, (start_values, end_values, dtend_enabled, dttimes_enabled) in \
1.130 - enumerate(map(None, all_start_values, all_end_values, all_dtend_enabled, all_dttimes_enabled)):
1.131 -
1.132 - dtend_enabled = str(index) in all_dtend_enabled
1.133 - dttimes_enabled = str(index) in all_dttimes_enabled
1.134 - period, _errors = handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled, index)
1.135 -
1.136 - periods.append(period)
1.137 - errors += _errors
1.138 -
1.139 - return periods, errors
1.140 + return [p.as_event_period(i) for i, p in enumerate(self.get_recurrences())]
1.141
1.142 def get_date_control_values(self, name, multiple=False, tzid_name=None):
1.143
1.144 @@ -245,90 +262,40 @@
1.145 # Handle absent values by employing None values.
1.146
1.147 field_values = map(None, dates, hours, minutes, seconds, tzids)
1.148 - if not field_values and not multiple:
1.149 - field_values = [(None, None, None, None, None)]
1.150 -
1.151 - all_values = []
1.152 -
1.153 - for date, hour, minute, second, tzid in field_values:
1.154 -
1.155 - # Construct a usable dictionary of values.
1.156
1.157 - if hour or minute or second:
1.158 - hour = (hour or "").rjust(2, "0")[:2]
1.159 - minute = (minute or "").rjust(2, "0")[:2]
1.160 - second = (second or "").rjust(2, "0")[:2]
1.161 - time = "T%s%s%s" % (hour, minute, second)
1.162 - else:
1.163 - hour = minute = second = time = ""
1.164 + if not field_values and not multiple:
1.165 + all_values = FormDate()
1.166 + else:
1.167 + all_values = []
1.168 + for date, hour, minute, second, tzid in field_values:
1.169 + value = FormDate(date, hour, minute, second, tzid or self.get_tzid())
1.170
1.171 - value = {
1.172 - "date" : date, "time" : time,
1.173 - "tzid" : tzid or self.get_tzid(),
1.174 - "hour" : hour, "minute" : minute, "second" : second
1.175 - }
1.176 + # Return a single value or append to a collection of all values.
1.177
1.178 - # Return a single value or append to a collection of all values.
1.179 -
1.180 - if not multiple:
1.181 - return value
1.182 - else:
1.183 - all_values.append(value)
1.184 + if not multiple:
1.185 + return value
1.186 + else:
1.187 + all_values.append(value)
1.188
1.189 return all_values
1.190
1.191 - def set_period_in_object(self, obj, period):
1.192 -
1.193 - "Set in the given 'obj' the given 'period' as the main start and end."
1.194 -
1.195 - p = period
1.196 - result = self.set_datetime_in_object(p.start, p.start_attr and p.start_attr.get("TZID"), "DTSTART", obj)
1.197 - result = self.set_datetime_in_object(p.end, p.end_attr and p.end_attr.get("TZID"), "DTEND", obj) or result
1.198 - return result
1.199 -
1.200 - def set_periods_in_object(self, obj, periods):
1.201 -
1.202 - "Set in the given 'obj' the given 'periods'."
1.203 -
1.204 - update = False
1.205 + def get_current_main_period(self, obj):
1.206 + args = self.env.get_args()
1.207 + initial_load = not args.has_key("editing")
1.208
1.209 - old_values = obj.get_values("RDATE")
1.210 - new_rdates = []
1.211 -
1.212 - if obj.has_key("RDATE"):
1.213 - del obj["RDATE"]
1.214 + if initial_load or not self.is_organiser(obj):
1.215 + return self.get_existing_main_period(obj)
1.216 + else:
1.217 + return self.get_main_period()
1.218
1.219 - for p in periods:
1.220 - tzid = p.start_attr and p.start_attr.get("TZID") or p.end_attr and p.end_attr.get("TZID")
1.221 - new_rdates.append(get_period_item(p.start, p.end, tzid))
1.222 -
1.223 - obj["RDATE"] = new_rdates
1.224 -
1.225 - # NOTE: To do: calculate the update status.
1.226 - return update
1.227 -
1.228 - def set_datetime_in_object(self, dt, tzid, property, obj):
1.229 + def get_existing_main_period(self, obj):
1.230
1.231 """
1.232 - Set 'dt' and 'tzid' for the given 'property' in 'obj', returning whether
1.233 - an update has occurred.
1.234 - """
1.235 -
1.236 - if dt:
1.237 - old_value = obj.get_value(property)
1.238 - obj[property] = [get_datetime_item(dt, tzid)]
1.239 - return format_datetime(dt) != old_value
1.240 -
1.241 - return False
1.242 -
1.243 - def get_event_period(self, obj):
1.244 -
1.245 - """
1.246 - Return (dtstart, dtstart attributes), (dtend, dtend attributes) for
1.247 - 'obj'.
1.248 + Return the main event period for the given 'obj'.
1.249 """
1.250
1.251 dtstart, dtstart_attr = obj.get_datetime_item("DTSTART")
1.252 +
1.253 if obj.has_key("DTEND"):
1.254 dtend, dtend_attr = obj.get_datetime_item("DTEND")
1.255 elif obj.has_key("DURATION"):
1.256 @@ -337,7 +304,60 @@
1.257 dtend_attr = dtstart_attr
1.258 else:
1.259 dtend, dtend_attr = dtstart, dtstart_attr
1.260 - return (dtstart, dtstart_attr), (dtend, dtend_attr)
1.261 +
1.262 + return EventPeriod(dtstart, end_date_from_calendar(dtend), dtstart_attr, dtend_attr)
1.263 +
1.264 + def get_main_period(self):
1.265 +
1.266 + "Return the main period defined in the event form."
1.267 +
1.268 + args = self.env.get_args()
1.269 +
1.270 + dtend_enabled = args.get("dtend-control", [None])[0]
1.271 + dttimes_enabled = args.get("dttimes-control", [None])[0]
1.272 + start = self.get_date_control_values("dtstart")
1.273 + end = self.get_date_control_values("dtend")
1.274 +
1.275 + return FormPeriod(start, end, dtend_enabled, dttimes_enabled)
1.276 +
1.277 + def get_current_recurrences(self, obj):
1.278 + args = self.env.get_args()
1.279 + initial_load = not args.has_key("editing")
1.280 +
1.281 + if initial_load or not self.is_organiser(obj):
1.282 + return self.get_existing_recurrences(obj)
1.283 + else:
1.284 + return self.get_recurrences()
1.285 +
1.286 + def get_existing_recurrences(self, obj):
1.287 + recurrences = []
1.288 + for period in obj.get_periods(self.get_tzid(), self.get_window_end()):
1.289 + if period.origin == "RDATE":
1.290 + recurrences.append(event_period_from_recurrence_period(period))
1.291 + return recurrences
1.292 +
1.293 + def get_recurrences(self):
1.294 +
1.295 + "Return the recurrences defined in the event form."
1.296 +
1.297 + args = self.env.get_args()
1.298 +
1.299 + all_dtend_enabled = args.get("dtend-control-recur", [])
1.300 + all_dttimes_enabled = args.get("dttimes-control-recur", [])
1.301 + all_starts = self.get_date_control_values("dtstart-recur", multiple=True)
1.302 + all_ends = self.get_date_control_values("dtend-recur", multiple=True, tzid_name="dtstart-recur")
1.303 +
1.304 + periods = []
1.305 +
1.306 + for index, (start, end, dtend_enabled, dttimes_enabled) in \
1.307 + enumerate(map(None, all_starts, all_ends, all_dtend_enabled, all_dttimes_enabled)):
1.308 +
1.309 + dtend_enabled = str(index) in all_dtend_enabled
1.310 + dttimes_enabled = str(index) in all_dttimes_enabled
1.311 + period = FormPeriod(start, end, dtend_enabled, dttimes_enabled)
1.312 + periods.append(period)
1.313 +
1.314 + return periods
1.315
1.316 def get_current_attendees(self, obj):
1.317
1.318 @@ -486,14 +506,14 @@
1.319 else:
1.320 attendees = self.update_attendees(obj)
1.321
1.322 - (dtstart, dtstart_attr), (dtend, dtend_attr) = self.get_event_period(obj)
1.323 - self.show_object_datetime_controls(dtstart, dtend)
1.324 + p = self.get_current_main_period(obj)
1.325 + self.show_object_datetime_controls(p)
1.326
1.327 # Obtain any separate recurrences for this event.
1.328
1.329 recurrenceid = format_datetime(obj.get_utc_datetime("RECURRENCE-ID"))
1.330 recurrenceids = self._get_recurrences(uid)
1.331 - start_utc = format_datetime(to_timezone(dtstart, "UTC"))
1.332 + start_utc = format_datetime(to_timezone(p.get_start(), "UTC"))
1.333 replaced = not recurrenceid and recurrenceids and start_utc in recurrenceids
1.334
1.335 # Provide a summary of the object.
1.336 @@ -527,17 +547,13 @@
1.337
1.338 # Obtain the datetime.
1.339
1.340 - if name == "DTSTART":
1.341 - dt, attr = dtstart, dtstart_attr
1.342 + is_start = name == "DTSTART"
1.343
1.344 # Where no end datetime exists, use the start datetime as the
1.345 # basis of any potential datetime specified if dt-control is
1.346 # set.
1.347
1.348 - else:
1.349 - dt, attr = dtend or dtstart, dtend_attr or dtstart_attr
1.350 -
1.351 - self.show_datetime_controls(obj, dt, attr, name == "DTSTART")
1.352 + self.show_datetime_controls(obj, is_start and p.get_form_start() or p.get_form_end(), is_start)
1.353
1.354 elif name == "DTSTART":
1.355 page.td(class_="objectvalue %s replaced" % field, rowspan=2)
1.356 @@ -694,33 +710,27 @@
1.357
1.358 # Obtain the periods associated with the event in the user's time zone.
1.359
1.360 - periods = obj.get_periods(self.get_tzid(), self.get_window_end())
1.361 - recurrenceids = self._get_recurrences(uid)
1.362 + periods = map(event_period_from_recurrence_period, obj.get_periods(self.get_tzid(), self.get_window_end()))
1.363 + recurrences = self.get_current_recurrences(obj)
1.364
1.365 - if len(periods) == 1:
1.366 + if len(periods) <= 1:
1.367 return
1.368
1.369 - if self.is_organiser(obj):
1.370 - page.p("This event recurs on the following occasions within the next %d days:" % self.get_window_size())
1.371 - else:
1.372 - page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
1.373 -
1.374 - # Determine whether any periods are explicitly created or are part of a
1.375 - # rule.
1.376 -
1.377 - explicit_periods = filter(lambda p: p.origin != "RRULE", periods)
1.378 + recurrenceids = self._get_recurrences(uid)
1.379
1.380 # Show each recurrence in a separate table if editable.
1.381
1.382 - if self.is_organiser(obj) and explicit_periods:
1.383 + if self.is_organiser(obj) and recurrences:
1.384
1.385 - for index, p in enumerate(periods[1:]):
1.386 + page.p("The following occurrences are editable:")
1.387 +
1.388 + for index, p in enumerate(recurrences):
1.389
1.390 # Isolate the controls from neighbouring tables.
1.391
1.392 page.div()
1.393
1.394 - self.show_object_datetime_controls(p.start, p.end, index)
1.395 + self.show_object_datetime_controls(p, index)
1.396
1.397 page.table(cellspacing=5, cellpadding=5, class_="recurrence")
1.398 page.caption("Occurrence")
1.399 @@ -742,28 +752,26 @@
1.400
1.401 # Otherwise, use a compact single table.
1.402
1.403 - else:
1.404 - page.table(cellspacing=5, cellpadding=5, class_="recurrence")
1.405 - page.caption("Occurrences")
1.406 - page.thead()
1.407 - page.tr()
1.408 - page.th("Start", class_="objectheading start")
1.409 - page.th("End", class_="objectheading end")
1.410 - page.tr.close()
1.411 - page.thead.close()
1.412 - page.tbody()
1.413 + page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
1.414
1.415 - # Show only subsequent periods if organiser, since the principal
1.416 - # period will be the start and end datetimes.
1.417 + page.table(cellspacing=5, cellpadding=5, class_="recurrence")
1.418 + page.caption("Occurrences")
1.419 + page.thead()
1.420 + page.tr()
1.421 + page.th("Start", class_="objectheading start")
1.422 + page.th("End", class_="objectheading end")
1.423 + page.tr.close()
1.424 + page.thead.close()
1.425 + page.tbody()
1.426
1.427 - for index, p in enumerate(self.is_organiser(obj) and periods[1:] or periods):
1.428 - page.tr()
1.429 - self.show_recurrence_controls(obj, index, p, recurrenceid, recurrenceids, True)
1.430 - self.show_recurrence_controls(obj, index, p, recurrenceid, recurrenceids, False)
1.431 - page.tr.close()
1.432 + for index, p in enumerate(periods):
1.433 + page.tr()
1.434 + self.show_recurrence_label(p, recurrenceid, recurrenceids, True)
1.435 + self.show_recurrence_label(p, recurrenceid, recurrenceids, False)
1.436 + page.tr.close()
1.437
1.438 - page.tbody.close()
1.439 - page.table.close()
1.440 + page.tbody.close()
1.441 + page.table.close()
1.442
1.443 def show_conflicting_events(self, uid, obj):
1.444
1.445 @@ -849,14 +857,16 @@
1.446
1.447 # Generation of controls within page fragments.
1.448
1.449 - def show_object_datetime_controls(self, start, end, index=None):
1.450 + def show_object_datetime_controls(self, period, index=None):
1.451
1.452 """
1.453 Show datetime-related controls if already active or if an object needs
1.454 - them for the given 'start' to 'end' period. The given 'index' is used to
1.455 - parameterise individual controls for dynamic manipulation.
1.456 + them for the given 'period'. The given 'index' is used to parameterise
1.457 + individual controls for dynamic manipulation.
1.458 """
1.459
1.460 + p = period.as_form_period()
1.461 +
1.462 page = self.page
1.463 args = self.env.get_args()
1.464 sn = self._suffixed_name
1.465 @@ -884,46 +894,28 @@
1.466
1.467 page.style.close()
1.468
1.469 - dtend_control = args.get(ssn("dtend-control", "recur", index), [])
1.470 - dttimes_control = args.get(ssn("dttimes-control", "recur", index), [])
1.471 -
1.472 - dtend_enabled = index is not None and str(index) in dtend_control or index is None and dtend_control
1.473 - dttimes_enabled = index is not None and str(index) in dttimes_control or index is None and dttimes_control
1.474 -
1.475 - initial_load = not args.has_key("editing")
1.476 -
1.477 - dtend_enabled = dtend_enabled or initial_load and (isinstance(end, datetime) or start != end - timedelta(1))
1.478 - dttimes_enabled = dttimes_enabled or initial_load and (isinstance(start, datetime) or isinstance(end, datetime))
1.479 -
1.480 self._control(
1.481 ssn("dtend-control", "recur", index), "checkbox",
1.482 - index is not None and str(index) or "enable", dtend_enabled,
1.483 + index is not None and str(index) or "enable", p.end_enabled,
1.484 id=sn("dtend-enable", index)
1.485 )
1.486
1.487 self._control(
1.488 ssn("dttimes-control", "recur", index), "checkbox",
1.489 - index is not None and str(index) or "enable", dttimes_enabled,
1.490 + index is not None and str(index) or "enable", p.times_enabled,
1.491 id=sn("dttimes-enable", index)
1.492 )
1.493
1.494 - def show_datetime_controls(self, obj, dt, attr, show_start):
1.495 + def show_datetime_controls(self, obj, formdate, show_start):
1.496
1.497 """
1.498 - Show datetime details from the given 'obj' for the datetime 'dt' and
1.499 - attributes 'attr', showing start details if 'show_start' is set
1.500 - to a true value. Details will appear as controls for organisers and
1.501 - labels for attendees.
1.502 + Show datetime details from the given 'obj' for the 'formdate', showing
1.503 + start details if 'show_start' is set to a true value. Details will
1.504 + appear as controls for organisers and labels for attendees.
1.505 """
1.506
1.507 page = self.page
1.508
1.509 - # Change end dates to refer to the actual dates, not the iCalendar
1.510 - # "next day" dates.
1.511 -
1.512 - if not show_start and not isinstance(dt, datetime):
1.513 - dt -= timedelta(1)
1.514 -
1.515 # Show controls for editing as organiser.
1.516
1.517 if self.is_organiser(obj):
1.518 @@ -931,7 +923,7 @@
1.519
1.520 if show_start:
1.521 page.div(class_="dt enabled")
1.522 - self._show_date_controls("dtstart", dt, attr.get("TZID"))
1.523 + self._show_date_controls("dtstart", formdate)
1.524 page.br()
1.525 page.label("Specify times", for_="dttimes-enable", class_="time disabled enable")
1.526 page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable")
1.527 @@ -942,7 +934,7 @@
1.528 page.label("Specify end date", for_="dtend-enable", class_="enable")
1.529 page.div.close()
1.530 page.div(class_="dt enabled")
1.531 - self._show_date_controls("dtend", dt, attr.get("TZID"))
1.532 + self._show_date_controls("dtend", formdate)
1.533 page.br()
1.534 page.label("End on same day", for_="dtend-enable", class_="disable")
1.535 page.div.close()
1.536 @@ -952,7 +944,12 @@
1.537 # Show a label as attendee.
1.538
1.539 else:
1.540 - page.td(self.format_datetime(dt, "full"))
1.541 + t = formdate.as_datetime_item()
1.542 + if t:
1.543 + dt, attr = t
1.544 + page.td(self.format_datetime(dt, "full"))
1.545 + else:
1.546 + page.td("(Unrecognised date)")
1.547
1.548 def show_recurrence_controls(self, obj, index, period, recurrenceid, recurrenceids, show_start):
1.549
1.550 @@ -972,13 +969,7 @@
1.551 ssn = self._simple_suffixed_name
1.552 p = period
1.553
1.554 - # Change end dates to refer to the actual dates, not the iCalendar
1.555 - # "next day" dates.
1.556 -
1.557 - if not isinstance(p.end, datetime):
1.558 - p.end -= timedelta(1)
1.559 -
1.560 - start_utc = format_datetime(to_timezone(p.start, "UTC"))
1.561 + start_utc = format_datetime(to_timezone(p.get_start(), "UTC"))
1.562 replaced = recurrenceids and start_utc in recurrenceids and "replaced" or ""
1.563 css = " ".join([
1.564 replaced,
1.565 @@ -987,12 +978,12 @@
1.566
1.567 # Show controls for editing as organiser.
1.568
1.569 - if self.is_organiser(obj) and not replaced and p.origin != "RRULE":
1.570 + if self.is_organiser(obj) and not replaced:
1.571 page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
1.572
1.573 if show_start:
1.574 page.div(class_="dt enabled")
1.575 - self._show_date_controls(ssn("dtstart", "recur", index), p.start, p.start_attr.get("TZID"), index=index)
1.576 + self._show_date_controls(ssn("dtstart", "recur", index), p.get_form_start(), index=index)
1.577 page.br()
1.578 page.label("Specify times", for_=sn("dttimes-enable", index), class_="time disabled enable")
1.579 page.label("Specify dates only", for_=sn("dttimes-enable", index), class_="time enabled disable")
1.580 @@ -1003,7 +994,7 @@
1.581 page.label("Specify end date", for_=sn("dtend-enable", index), class_="enable")
1.582 page.div.close()
1.583 page.div(class_="dt enabled")
1.584 - self._show_date_controls(ssn("dtend", "recur", index), p.end, index=index, show_tzid=False)
1.585 + self._show_date_controls(ssn("dtend", "recur", index), p.get_form_end(), index=index, show_tzid=False)
1.586 page.br()
1.587 page.label("End on same day", for_=sn("dtend-enable", index), class_="disable")
1.588 page.div.close()
1.589 @@ -1013,7 +1004,35 @@
1.590 # Show label as attendee.
1.591
1.592 else:
1.593 - page.td(self.format_datetime(show_start and p.start or p.end, "long"), class_=css)
1.594 + self.show_recurrence_label(p, recurrenceid, recurrenceids, show_start)
1.595 +
1.596 + def show_recurrence_label(self, p, recurrenceid, recurrenceids, show_start):
1.597 +
1.598 + """
1.599 + Show datetime details for the given period 'p', employing any
1.600 + 'recurrenceid' and 'recurrenceids' for the object to configure the
1.601 + displayed information.
1.602 +
1.603 + If 'show_start' is set to a true value, the start details will be shown;
1.604 + otherwise, the end details will be shown.
1.605 + """
1.606 +
1.607 + page = self.page
1.608 +
1.609 + start_utc = format_datetime(to_timezone(p.get_start(), "UTC"))
1.610 + replaced = recurrenceids and start_utc in recurrenceids and "replaced" or ""
1.611 + css = " ".join([
1.612 + replaced,
1.613 + recurrenceid and start_utc == recurrenceid and "affected" or ""
1.614 + ])
1.615 +
1.616 + formdate = show_start and p.get_form_start() or p.get_form_end()
1.617 + t = formdate.as_datetime_item()
1.618 + if t:
1.619 + dt, attr = t
1.620 + page.td(self.format_datetime(dt, "long"), class_=css)
1.621 + else:
1.622 + page.td("(Unrecognised date)")
1.623
1.624 # Full page output methods.
1.625
1.626 @@ -1077,25 +1096,32 @@
1.627 page.option(label, value=v)
1.628 page.select.close()
1.629
1.630 - def _show_date_controls(self, name, default, tzid=None, index=None, show_tzid=True):
1.631 + def _show_date_controls(self, name, default, index=None, show_tzid=True):
1.632
1.633 """
1.634 - Show date controls for a field with the given 'name' and 'default' value
1.635 - and 'tzid'. If 'index' is specified, default field values will be
1.636 - overridden by the element from a collection of existing form values with
1.637 - the specified index; otherwise, field values will be overridden by a
1.638 - single form value.
1.639 + Show date controls for a field with the given 'name' and 'default' form
1.640 + date value.
1.641 +
1.642 + If 'index' is specified, default field values will be overridden by the
1.643 + element from a collection of existing form values with the specified
1.644 + index; otherwise, field values will be overridden by a single form
1.645 + value.
1.646
1.647 If 'show_tzid' is set to a false value, the time zone menu will not be
1.648 provided.
1.649 """
1.650
1.651 page = self.page
1.652 - args = self.env.get_args()
1.653
1.654 # Show dates for up to one week around the current date.
1.655
1.656 - base = to_date(default)
1.657 + t = default.as_datetime_item()
1.658 + if t:
1.659 + dt, attr = t
1.660 + else:
1.661 + dt = date.today()
1.662 +
1.663 + base = to_date(dt)
1.664 items = []
1.665 for i in range(-7, 8):
1.666 d = base + timedelta(i)
1.667 @@ -1105,25 +1131,18 @@
1.668
1.669 # Show time details.
1.670
1.671 - default_time = isinstance(default, datetime) and default or None
1.672 -
1.673 - hour = args.get("%s-hour" % name, [])[index or 0:]
1.674 - hour = hour and hour[0] or "%02d" % (default_time and default_time.hour or 0)
1.675 - minute = args.get("%s-minute" % name, [])[index or 0:]
1.676 - minute = minute and minute[0] or "%02d" % (default_time and default_time.minute or 0)
1.677 - second = args.get("%s-second" % name, [])[index or 0:]
1.678 - second = second and second[0] or "%02d" % (default_time and default_time.second or 0)
1.679 + page.span(class_="time enabled")
1.680 + page.input(name="%s-hour" % name, type="text", value=default.get_hour(), maxlength=2, size=2)
1.681 + page.add(":")
1.682 + page.input(name="%s-minute" % name, type="text", value=default.get_minute(), maxlength=2, size=2)
1.683 + page.add(":")
1.684 + page.input(name="%s-second" % name, type="text", value=default.get_second(), maxlength=2, size=2)
1.685
1.686 - page.span(class_="time enabled")
1.687 - page.input(name="%s-hour" % name, type="text", value=hour, maxlength=2, size=2)
1.688 - page.add(":")
1.689 - page.input(name="%s-minute" % name, type="text", value=minute, maxlength=2, size=2)
1.690 - page.add(":")
1.691 - page.input(name="%s-second" % name, type="text", value=second, maxlength=2, size=2)
1.692 if show_tzid:
1.693 - tzid = tzid or self.get_tzid()
1.694 page.add(" ")
1.695 + tzid = default.get_tzid() or self.get_tzid()
1.696 self._show_timezone_menu("%s-tzid" % name, tzid, index)
1.697 +
1.698 page.span.close()
1.699
1.700 def _show_timezone_menu(self, name, default, index=None):