1.1 --- a/imip_manager.py Tue Mar 24 01:10:28 2015 +0100
1.2 +++ b/imip_manager.py Tue Mar 24 01:11:02 2015 +0100
1.3 @@ -34,10 +34,11 @@
1.4 from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \
1.5 Object, to_part, \
1.6 uri_dict, uri_item, uri_items, uri_values
1.7 -from imiptools.dates import format_datetime, format_time, get_date, get_datetime, \
1.8 +from imiptools.dates import format_datetime, format_time, to_date, get_datetime, \
1.9 get_datetime_item, get_default_timezone, \
1.10 - get_end_of_day, get_start_of_day, get_start_of_next_day, \
1.11 - get_timestamp, ends_on_same_day, to_timezone
1.12 + get_end_of_day, get_period_item, get_start_of_day, \
1.13 + get_start_of_next_day, get_timestamp, ends_on_same_day, \
1.14 + to_timezone
1.15 from imiptools.handlers import Handler
1.16 from imiptools.mail import Messenger
1.17 from imiptools.period import add_day_start_points, add_empty_days, add_slots, \
1.18 @@ -347,6 +348,12 @@
1.19 except OSError:
1.20 self.publisher = None
1.21
1.22 + def _suffixed_name(self, name, index=None):
1.23 + return index is not None and "%s-%d" % (name, index) or name
1.24 +
1.25 + def _simple_suffixed_name(self, name, suffix, index=None):
1.26 + return index is not None and "%s-%s" % (name, suffix) or name
1.27 +
1.28 def _get_identifiers(self, path_info):
1.29 parts = path_info.lstrip("/").split("/")
1.30 if len(parts) == 1:
1.31 @@ -668,42 +675,12 @@
1.32 update = False
1.33
1.34 if is_organiser:
1.35 - dtend_enabled = args.get("dtend-control", [None])[0] == "enable"
1.36 - dttimes_enabled = args.get("dttimes-control", [None])[0] == "enable"
1.37 -
1.38 - t = self.handle_date_controls("dtstart", dttimes_enabled)
1.39 - if t:
1.40 - dtstart, attr = t
1.41 - update = self.set_datetime_in_object(dtstart, attr.get("TZID"), "DTSTART", obj) or update
1.42 - else:
1.43 - return ["dtstart"]
1.44 -
1.45 - # Handle specified end datetimes.
1.46 -
1.47 - if dtend_enabled:
1.48 - t = self.handle_date_controls("dtend", dttimes_enabled)
1.49 - if t:
1.50 - dtend, attr = t
1.51 -
1.52 - # Convert end dates to iCalendar "next day" dates.
1.53 -
1.54 - if not isinstance(dtend, datetime):
1.55 - dtend += timedelta(1)
1.56 - update = self.set_datetime_in_object(dtend, attr.get("TZID"), "DTEND", obj) or update
1.57 - else:
1.58 - return ["dtend"]
1.59 -
1.60 - # Otherwise, treat the end date as the start date. Datetimes are
1.61 - # handled by making the event occupy the rest of the day.
1.62 -
1.63 - else:
1.64 - dtend = dtstart + timedelta(1)
1.65 - if isinstance(dtstart, datetime):
1.66 - dtend = get_start_of_day(dtend, attr["TZID"])
1.67 - update = self.set_datetime_in_object(dtend, attr.get("TZID"), "DTEND", obj) or update
1.68 -
1.69 - if dtstart >= dtend:
1.70 - return ["dtstart", "dtend"]
1.71 + periods, errors = self.handle_all_period_controls()
1.72 + if errors:
1.73 + return errors
1.74 + elif periods:
1.75 + self.set_period_in_object(obj, periods[0])
1.76 + self.set_periods_in_object(obj, periods[1:])
1.77
1.78 # Obtain any participants to be added or removed.
1.79
1.80 @@ -751,38 +728,196 @@
1.81
1.82 return None
1.83
1.84 - def handle_date_controls(self, name, with_time=True):
1.85 + def handle_all_period_controls(self):
1.86
1.87 """
1.88 - Handle date control information for fields starting with 'name',
1.89 - returning a (datetime, attr) tuple or None if the fields cannot be used
1.90 - to construct a datetime object.
1.91 + Handle datetime controls for a particular period, where 'index' may be
1.92 + used to indicate a recurring period, or the main start and end datetimes
1.93 + are handled.
1.94 """
1.95
1.96 args = self.env.get_args()
1.97
1.98 - if args.has_key("%s-date" % name):
1.99 - date = args["%s-date" % name][0]
1.100 -
1.101 - if with_time:
1.102 - hour = args.get("%s-hour" % name, [None])[0]
1.103 - minute = args.get("%s-minute" % name, [None])[0]
1.104 - second = args.get("%s-second" % name, [None])[0]
1.105 - tzid = args.get("%s-tzid" % name, [self.get_tzid()])[0]
1.106 -
1.107 - time = (hour or minute or second) and "T%s%s%s" % (hour, minute, second) or ""
1.108 - value = "%s%s" % (date, time)
1.109 - attr = {"TZID" : tzid, "VALUE" : "DATE-TIME"}
1.110 - dt = get_datetime(value, attr)
1.111 + periods = []
1.112 +
1.113 + # Get the main period details.
1.114 +
1.115 + dtend_enabled = args.get("dtend-control", [None])[0] == "enable"
1.116 + dttimes_enabled = args.get("dttimes-control", [None])[0] == "enable"
1.117 + start_values = self.get_date_control_values("dtstart")
1.118 + end_values = self.get_date_control_values("dtend")
1.119 +
1.120 + period, errors = self.handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled)
1.121 +
1.122 + if errors:
1.123 + return None, errors
1.124 +
1.125 + periods.append(period)
1.126 +
1.127 + # Get the recurring period details.
1.128 +
1.129 + all_dtend_enabled = map(lambda x: x == "enable", args.get("dtend-control-recur", []))
1.130 + all_dttimes_enabled = map(lambda x: x == "enable", args.get("dttimes-control-recur", []))
1.131 + all_start_values = self.get_date_control_values("dtstart-recur", multiple=True)
1.132 + all_end_values = self.get_date_control_values("dtend-recur", multiple=True)
1.133 +
1.134 + for start_values, end_values, dtend_enabled, dttimes_enabled in \
1.135 + map(None, all_start_values, all_end_values, all_dtend_enabled, all_dttimes_enabled):
1.136 +
1.137 + period, errors = self.handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled)
1.138 +
1.139 + if errors:
1.140 + return None, errors
1.141 +
1.142 + periods.append(period)
1.143 +
1.144 + return periods, None
1.145 +
1.146 + def handle_period_controls(self, start_values, end_values, dtend_enabled, dttimes_enabled):
1.147 +
1.148 + """
1.149 + Handle datetime controls for a particular period, described by the given
1.150 + 'start_values' and 'end_values', with 'dtend_enabled' and
1.151 + 'dttimes_enabled' affecting the usage of the provided values.
1.152 + """
1.153 +
1.154 + t = self.handle_date_control_values(start_values, dttimes_enabled)
1.155 + if t:
1.156 + dtstart, dtstart_attr = t
1.157 + else:
1.158 + return None, ["dtstart"]
1.159 +
1.160 + # Handle specified end datetimes.
1.161 +
1.162 + if dtend_enabled:
1.163 + t = self.handle_date_control_values(end_values, dttimes_enabled)
1.164 + if t:
1.165 + dtend, dtend_attr = t
1.166 +
1.167 + # Convert end dates to iCalendar "next day" dates.
1.168 +
1.169 + if not isinstance(dtend, datetime):
1.170 + dtend += timedelta(1)
1.171 else:
1.172 - attr = {"VALUE" : "DATE"}
1.173 - dt = get_datetime(date)
1.174 -
1.175 - if dt:
1.176 - return dt, attr
1.177 + return None, ["dtend"]
1.178 +
1.179 + # Otherwise, treat the end date as the start date. Datetimes are
1.180 + # handled by making the event occupy the rest of the day.
1.181 +
1.182 + else:
1.183 + dtend = dtstart + timedelta(1)
1.184 + dtend_attr = dtstart_attr
1.185 +
1.186 + if isinstance(dtstart, datetime):
1.187 + dtend = get_start_of_day(dtend, attr["TZID"])
1.188 +
1.189 + if dtstart >= dtend:
1.190 + return None, ["dtstart", "dtend"]
1.191 +
1.192 + return ((dtstart, dtstart_attr), (dtend, dtend_attr)), None
1.193 +
1.194 + def handle_date_control_values(self, values, with_time=True):
1.195 +
1.196 + """
1.197 + Handle date control information for the given 'values', returning a
1.198 + (datetime, attr) tuple, or None if the fields cannot be used to
1.199 + construct a datetime object.
1.200 + """
1.201 +
1.202 + if not values or not values["date"]:
1.203 + return None
1.204 + elif with_time:
1.205 + value = "%s%s" % (values["date"], values["time"])
1.206 + attr = {"TZID" : values["tzid"], "VALUE" : "DATE-TIME"}
1.207 + dt = get_datetime(value, attr)
1.208 + else:
1.209 + attr = {"VALUE" : "DATE"}
1.210 + dt = get_datetime(values["date"])
1.211 +
1.212 + if dt:
1.213 + return dt, attr
1.214
1.215 return None
1.216
1.217 + def get_date_control_values(self, name, multiple=False):
1.218 +
1.219 + """
1.220 + Return a dictionary containing date, time and tzid entries for fields
1.221 + starting with 'name'.
1.222 + """
1.223 +
1.224 + args = self.env.get_args()
1.225 +
1.226 + dates = args.get("%s-date" % name, [])
1.227 + hours = args.get("%s-hour" % name, [])
1.228 + minutes = args.get("%s-minute" % name, [])
1.229 + seconds = args.get("%s-second" % name, [])
1.230 + tzids = args.get("%s-tzid" % name, [])
1.231 +
1.232 + # Handle absent values by employing None values.
1.233 +
1.234 + field_values = map(None, dates, hours, minutes, seconds, tzids)
1.235 + if not field_values and not multiple:
1.236 + field_values = [(None, None, None, None, None)]
1.237 +
1.238 + all_values = []
1.239 +
1.240 + for date, hour, minute, second, tzid in field_values:
1.241 +
1.242 + # Construct a usable dictionary of values.
1.243 +
1.244 + time = (hour or minute or second) and \
1.245 + "T%s%s%s" % (
1.246 + (hour or "").rjust(2, "0")[:2],
1.247 + (minute or "").rjust(2, "0")[:2],
1.248 + (second or "").rjust(2, "0")[:2]
1.249 + ) or ""
1.250 +
1.251 + value = {
1.252 + "date" : date,
1.253 + "time" : time,
1.254 + "tzid" : tzid or self.get_tzid()
1.255 + }
1.256 +
1.257 + # Return a single value or append to a collection of all values.
1.258 +
1.259 + if not multiple:
1.260 + return value
1.261 + else:
1.262 + all_values.append(value)
1.263 +
1.264 + return all_values
1.265 +
1.266 + def set_period_in_object(self, obj, period):
1.267 +
1.268 + "Set in the given 'obj' the given 'period' as the main start and end."
1.269 +
1.270 + (dtstart, dtstart_attr), (dtend, dtend_attr) = period
1.271 +
1.272 + return self.set_datetime_in_object(dtstart, dtstart_attr.get("TZID"), "DTSTART", obj) or \
1.273 + self.set_datetime_in_object(dtend, dtend_attr.get("TZID"), "DTEND", obj)
1.274 +
1.275 + def set_periods_in_object(self, obj, periods):
1.276 +
1.277 + "Set in the given 'obj' the given 'periods'."
1.278 +
1.279 + update = False
1.280 +
1.281 + old_values = obj.get_values("RDATE")
1.282 + new_rdates = []
1.283 +
1.284 + del obj["RDATE"]
1.285 +
1.286 + for period in periods:
1.287 + (dtstart, dtstart_attr), (dtend, dtend_attr) = period
1.288 + tzid = dtstart_attr.get("TZID") or dtend_attr.get("TZID")
1.289 + new_rdates.append(get_period_item(dtstart, dtend, tzid))
1.290 +
1.291 + obj["RDATE"] = new_rdates
1.292 +
1.293 + # NOTE: To do: calculate the update status.
1.294 + return update
1.295 +
1.296 def set_datetime_in_object(self, dt, tzid, property, obj):
1.297
1.298 """
1.299 @@ -860,12 +995,14 @@
1.300
1.301 (dtstart, dtstart_attr), (dtend, dtend_attr) = self.get_event_period(obj)
1.302
1.303 - t = self.handle_date_controls("dtstart", with_time)
1.304 + d = self.get_date_control_values("dtstart")
1.305 + t = self.handle_date_control_values(d, with_time)
1.306 if t:
1.307 dtstart, dtstart_attr = t
1.308
1.309 if dtend_control == "enable":
1.310 - t = self.handle_date_controls("dtend", with_time)
1.311 + d = self.get_date_control_values("dtend")
1.312 + t = self.handle_date_control_values(d, with_time)
1.313 if t:
1.314 dtend, dtend_attr = t
1.315 else:
1.316 @@ -1113,11 +1250,62 @@
1.317
1.318 page.form.close()
1.319
1.320 - def show_datetime_controls(self, obj, dt, attr, is_start_datetime):
1.321 + def show_object_datetime_controls(self, start, end, index=None):
1.322 +
1.323 + """
1.324 + Show datetime-related controls if already active or if an object needs
1.325 + them for the given 'start' to 'end' period. The given 'index' is used to
1.326 + parameterise individual controls for dynamic manipulation.
1.327 + """
1.328 +
1.329 + page = self.page
1.330 + args = self.env.get_args()
1.331 + sn = self._suffixed_name
1.332 + ssn = self._simple_suffixed_name
1.333 +
1.334 + # Add a dynamic stylesheet to permit the controls to modify the display.
1.335 + # NOTE: The style details need to be coordinated with the static
1.336 + # NOTE: stylesheet.
1.337 +
1.338 + if index is not None:
1.339 + page.style(type="text/css")
1.340 +
1.341 + # Unlike the rules for object properties, these affect recurrence
1.342 + # properties.
1.343 +
1.344 + page.add("""\
1.345 +input#dttimes-enable-%(index)d,
1.346 +input#dtend-enable-%(index)d,
1.347 +input#dttimes-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue .time.enabled,
1.348 +input#dttimes-enable-%(index)d:checked ~ .recurrence td.objectvalue .time.disabled,
1.349 +input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .dt.enabled,
1.350 +input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .dt.disabled {
1.351 + display: none;
1.352 +}""" % {"index" : index})
1.353 +
1.354 + page.style.close()
1.355 +
1.356 + dtend_control = args.get(ssn("dtend-control", "recur", index), [None])[0]
1.357 + dttimes_control = args.get(ssn("dttimes-control", "recur", index), [None])[0]
1.358 +
1.359 + dtend_enabled = dtend_control == "enable" or isinstance(end, datetime) or start != end
1.360 + dttimes_enabled = dttimes_control == "enable" or isinstance(start, datetime) or isinstance(end, datetime)
1.361 +
1.362 + if dtend_enabled:
1.363 + page.input(name=ssn("dtend-control", "recur", index), type="checkbox", value="enable", id=sn("dtend-enable", index), checked="checked")
1.364 + else:
1.365 + page.input(name=ssn("dtend-control", "recur", index), type="checkbox", value="enable", id=sn("dtend-enable", index))
1.366 +
1.367 + if dttimes_enabled:
1.368 + page.input(name=ssn("dttimes-control", "recur", index), type="checkbox", value="enable", id=sn("dttimes-enable", index), checked="checked")
1.369 + else:
1.370 + page.input(name=ssn("dttimes-control", "recur", index), type="checkbox", value="enable", id=sn("dttimes-enable", index))
1.371 +
1.372 + def show_datetime_controls(self, obj, dt, attr, show_start):
1.373
1.374 """
1.375 Show datetime details from the given 'obj' for the datetime 'dt' and
1.376 - attributes 'attr', showing start details if 'is_start_datetime' is set
1.377 + attributes 'attr', showing start details if 'show_start' is set
1.378 to a true value. Details will appear as controls for organisers and
1.379 labels for attendees.
1.380 """
1.381 @@ -1128,14 +1316,14 @@
1.382 # Show controls for editing as organiser.
1.383
1.384 if is_organiser:
1.385 - page.td(class_="objectvalue dt%s" % (is_start_datetime and "start" or "end"))
1.386 -
1.387 - if is_start_datetime:
1.388 + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
1.389 +
1.390 + if show_start:
1.391 page.div(class_="dt enabled")
1.392 self._show_date_controls("dtstart", dt, attr.get("TZID"))
1.393 page.br()
1.394 page.label("Specify times", for_="dttimes-enable", class_="time disabled enable")
1.395 - page.label("Specify dates only", for_="dttimes-disable", class_="time enabled disable")
1.396 + page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable")
1.397 page.div.close()
1.398
1.399 else:
1.400 @@ -1145,7 +1333,7 @@
1.401 page.div(class_="dt enabled")
1.402 self._show_date_controls("dtend", dt, attr.get("TZID"))
1.403 page.br()
1.404 - page.label("End on same day", for_="dtend-disable", class_="disable")
1.405 + page.label("End on same day", for_="dtend-enable", class_="disable")
1.406 page.div.close()
1.407
1.408 page.td.close()
1.409 @@ -1155,41 +1343,68 @@
1.410 else:
1.411 page.td(self.format_datetime(dt, "full"))
1.412
1.413 - def show_object_datetime_controls(self, start, end):
1.414 + def show_recurrence_controls(self, obj, index, start, end, origin, recurrenceid, recurrenceids, show_start):
1.415
1.416 """
1.417 - Show datetime-related controls if already active or if an object needs
1.418 - them for the given 'start' to 'end' period.
1.419 + Show datetime details from the given 'obj' for the recurrence having the
1.420 + given 'index', with the recurrence period described by the datetimes
1.421 + 'start' and 'end', indicating the 'origin' of the period from the event
1.422 + details, employing any 'recurrenceid' and 'recurrenceids' for the object
1.423 + to configure the displayed information.
1.424 +
1.425 + If 'show_start' is set to a true value, the start details will be shown;
1.426 + otherwise, the end details will be shown.
1.427 """
1.428
1.429 page = self.page
1.430 - args = self.env.get_args()
1.431 -
1.432 - dtend_control = args.get("dtend-control", [None])[0]
1.433 - dttimes_control = args.get("dttimes-control", [None])[0]
1.434 -
1.435 - dtend_enabled = dtend_control == "enable" or isinstance(end, datetime) or start != end
1.436 - dttimes_enabled = dttimes_control == "enable" or isinstance(start, datetime) or isinstance(end, datetime)
1.437 -
1.438 - if dtend_enabled:
1.439 - page.input(name="dtend-control", type="radio", value="enable", id="dtend-enable", checked="checked")
1.440 - page.input(name="dtend-control", type="radio", value="disable", id="dtend-disable")
1.441 + sn = self._suffixed_name
1.442 + ssn = self._simple_suffixed_name
1.443 +
1.444 + is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user
1.445 +
1.446 + start_utc = format_datetime(to_timezone(start, "UTC"))
1.447 + replaced = recurrenceids and start_utc in recurrenceids and "replaced" or ""
1.448 + css = " ".join([
1.449 + replaced,
1.450 + recurrenceid and start_utc == recurrenceid and "affected" or ""
1.451 + ])
1.452 +
1.453 + # Show controls for editing as organiser.
1.454 +
1.455 + if is_organiser and not replaced and origin != "RRULE":
1.456 + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
1.457 +
1.458 + if show_start:
1.459 + page.div(class_="dt enabled")
1.460 + self._show_date_controls(ssn("dtstart", "recur", index), start, None)
1.461 + page.br()
1.462 + page.label("Specify times", for_=sn("dttimes-enable", index), class_="time disabled enable")
1.463 + page.label("Specify dates only", for_=sn("dttimes-enable", index), class_="time enabled disable")
1.464 + page.div.close()
1.465 +
1.466 + else:
1.467 + page.div(class_="dt disabled")
1.468 + page.label("Specify end date", for_=sn("dtend-enable", index), class_="enable")
1.469 + page.div.close()
1.470 + page.div(class_="dt enabled")
1.471 + self._show_date_controls(ssn("dtend", "recur", index), end, None)
1.472 + page.br()
1.473 + page.label("End on same day", for_=sn("dtend-enable", index), class_="disable")
1.474 + page.div.close()
1.475 +
1.476 + page.td.close()
1.477 +
1.478 + # Show label as attendee.
1.479 +
1.480 else:
1.481 - page.input(name="dtend-control", type="radio", value="enable", id="dtend-enable")
1.482 - page.input(name="dtend-control", type="radio", value="disable", id="dtend-disable", checked="checked")
1.483 -
1.484 - if dttimes_enabled:
1.485 - page.input(name="dttimes-control", type="radio", value="enable", id="dttimes-enable", checked="checked")
1.486 - page.input(name="dttimes-control", type="radio", value="disable", id="dttimes-disable")
1.487 - else:
1.488 - page.input(name="dttimes-control", type="radio", value="enable", id="dttimes-enable")
1.489 - page.input(name="dttimes-control", type="radio", value="disable", id="dttimes-disable", checked="checked")
1.490 + page.td(self.format_datetime(show_start and start or end, "long"), class_=css)
1.491
1.492 def show_recurrences(self, obj):
1.493
1.494 "Show recurrences for the object having the given representation 'obj'."
1.495
1.496 page = self.page
1.497 + is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user
1.498
1.499 # Obtain any parent object if this object is a specific recurrence.
1.500
1.501 @@ -1205,37 +1420,71 @@
1.502
1.503 # Obtain the periods associated with the event in the user's time zone.
1.504
1.505 - periods = obj.get_periods(self.get_tzid(), self.get_window_end())
1.506 + periods = obj.get_periods(self.get_tzid(), self.get_window_end(), origin=True)
1.507 recurrenceids = self._get_recurrences(uid)
1.508
1.509 if len(periods) == 1:
1.510 return
1.511
1.512 - page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
1.513 -
1.514 - page.table(cellspacing=5, cellpadding=5, class_="recurrences")
1.515 - page.thead()
1.516 - page.tr()
1.517 - page.th("Start")
1.518 - page.th("End")
1.519 - page.tr.close()
1.520 - page.thead.close()
1.521 - page.tbody()
1.522 -
1.523 - for start, end in periods:
1.524 - start_utc = format_datetime(to_timezone(start, "UTC"))
1.525 - css = " ".join([
1.526 - recurrenceids and start_utc in recurrenceids and "replaced" or "",
1.527 - recurrenceid and start_utc == recurrenceid and "affected" or ""
1.528 - ])
1.529 -
1.530 + if is_organiser:
1.531 + page.p("This event recurs on the following occasions within the next %d days:" % self.get_window_size())
1.532 + else:
1.533 + page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
1.534 +
1.535 + # Determine whether any periods are explicitly created or are part of a
1.536 + # rule.
1.537 +
1.538 + explicit_periods = filter(lambda t: t[2] != "RRULE", periods)
1.539 +
1.540 + # Show each recurrence in a separate table if editable.
1.541 +
1.542 + if is_organiser and explicit_periods:
1.543 + for index, (start, end, origin) in enumerate(periods[1:]):
1.544 +
1.545 + # Isolate the controls from neighbouring tables.
1.546 +
1.547 + page.div()
1.548 +
1.549 + self.show_object_datetime_controls(start, end, index)
1.550 +
1.551 + # NOTE: Need to customise the TH classes according to errors and
1.552 + # NOTE: index information.
1.553 +
1.554 + page.table(cellspacing=5, cellpadding=5, class_="recurrence")
1.555 + page.caption("Occurrence")
1.556 + page.tbody()
1.557 + page.tr()
1.558 + page.th("Start", class_="objectheading start")
1.559 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, True)
1.560 + page.tr.close()
1.561 + page.tr()
1.562 + page.th("End", class_="objectheading end")
1.563 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, False)
1.564 + page.tr.close()
1.565 + page.tbody.close()
1.566 + page.table.close()
1.567 +
1.568 + page.div.close()
1.569 +
1.570 + # Otherwise, use a compact single table.
1.571 +
1.572 + else:
1.573 + page.table(cellspacing=5, cellpadding=5, class_="recurrence")
1.574 + page.caption("Occurrences")
1.575 + page.thead()
1.576 page.tr()
1.577 - page.td(self.format_datetime(start, "long"), class_=css)
1.578 - page.td(self.format_datetime(end, "long"), class_=css)
1.579 + page.th("Start", class_="objectheading start")
1.580 + page.th("End", class_="objectheading end")
1.581 page.tr.close()
1.582 -
1.583 - page.tbody.close()
1.584 - page.table.close()
1.585 + page.thead.close()
1.586 + page.tbody()
1.587 + for index, (start, end, origin) in enumerate(periods):
1.588 + page.tr()
1.589 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, True)
1.590 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, False)
1.591 + page.tr.close()
1.592 + page.tbody.close()
1.593 + page.table.close()
1.594
1.595 def show_conflicting_events(self, uid, obj):
1.596
1.597 @@ -1774,8 +2023,8 @@
1.598 ])
1.599
1.600 # Only anchor the first cell of events.
1.601 - # NOTE: Need to only anchor the first period for a
1.602 - # NOTE: recurring event.
1.603 + # Need to only anchor the first period for a recurring
1.604 + # event.
1.605
1.606 html_id = "%s-%s-%s" % (group_type, uid, recurrenceid or "")
1.607
1.608 @@ -1907,7 +2156,7 @@
1.609
1.610 """
1.611 Show date controls for a field with the given 'name' and 'default' value
1.612 - and 'attr'.
1.613 + and 'tzid'.
1.614 """
1.615
1.616 page = self.page
1.617 @@ -1917,7 +2166,7 @@
1.618
1.619 # Show dates for up to one week around the current date.
1.620
1.621 - base = get_date(default)
1.622 + base = to_date(default)
1.623 items = []
1.624 for i in range(-7, 8):
1.625 d = base + timedelta(i)