2.1 --- a/imip_manager.py Tue Mar 24 01:10:28 2015 +0100
2.2 +++ b/imip_manager.py Tue Mar 24 01:11:02 2015 +0100
2.3 @@ -34,10 +34,11 @@
2.4 from imiptools.data import get_address, get_uri, get_window_end, make_freebusy, \
2.5 Object, to_part, \
2.6 uri_dict, uri_item, uri_items, uri_values
2.7 -from imiptools.dates import format_datetime, format_time, get_date, get_datetime, \
2.8 +from imiptools.dates import format_datetime, format_time, to_date, get_datetime, \
2.9 get_datetime_item, get_default_timezone, \
2.10 - get_end_of_day, get_start_of_day, get_start_of_next_day, \
2.11 - get_timestamp, ends_on_same_day, to_timezone
2.12 + get_end_of_day, get_period_item, get_start_of_day, \
2.13 + get_start_of_next_day, get_timestamp, ends_on_same_day, \
2.14 + to_timezone
2.15 from imiptools.handlers import Handler
2.16 from imiptools.mail import Messenger
2.17 from imiptools.period import add_day_start_points, add_empty_days, add_slots, \
2.18 @@ -347,6 +348,12 @@
2.19 except OSError:
2.20 self.publisher = None
2.21
2.22 + def _suffixed_name(self, name, index=None):
2.23 + return index is not None and "%s-%d" % (name, index) or name
2.24 +
2.25 + def _simple_suffixed_name(self, name, suffix, index=None):
2.26 + return index is not None and "%s-%s" % (name, suffix) or name
2.27 +
2.28 def _get_identifiers(self, path_info):
2.29 parts = path_info.lstrip("/").split("/")
2.30 if len(parts) == 1:
2.31 @@ -668,42 +675,12 @@
2.32 update = False
2.33
2.34 if is_organiser:
2.35 - dtend_enabled = args.get("dtend-control", [None])[0] == "enable"
2.36 - dttimes_enabled = args.get("dttimes-control", [None])[0] == "enable"
2.37 -
2.38 - t = self.handle_date_controls("dtstart", dttimes_enabled)
2.39 - if t:
2.40 - dtstart, attr = t
2.41 - update = self.set_datetime_in_object(dtstart, attr.get("TZID"), "DTSTART", obj) or update
2.42 - else:
2.43 - return ["dtstart"]
2.44 -
2.45 - # Handle specified end datetimes.
2.46 -
2.47 - if dtend_enabled:
2.48 - t = self.handle_date_controls("dtend", dttimes_enabled)
2.49 - if t:
2.50 - dtend, attr = t
2.51 -
2.52 - # Convert end dates to iCalendar "next day" dates.
2.53 -
2.54 - if not isinstance(dtend, datetime):
2.55 - dtend += timedelta(1)
2.56 - update = self.set_datetime_in_object(dtend, attr.get("TZID"), "DTEND", obj) or update
2.57 - else:
2.58 - return ["dtend"]
2.59 -
2.60 - # Otherwise, treat the end date as the start date. Datetimes are
2.61 - # handled by making the event occupy the rest of the day.
2.62 -
2.63 - else:
2.64 - dtend = dtstart + timedelta(1)
2.65 - if isinstance(dtstart, datetime):
2.66 - dtend = get_start_of_day(dtend, attr["TZID"])
2.67 - update = self.set_datetime_in_object(dtend, attr.get("TZID"), "DTEND", obj) or update
2.68 -
2.69 - if dtstart >= dtend:
2.70 - return ["dtstart", "dtend"]
2.71 + periods, errors = self.handle_all_period_controls()
2.72 + if errors:
2.73 + return errors
2.74 + elif periods:
2.75 + self.set_period_in_object(obj, periods[0])
2.76 + self.set_periods_in_object(obj, periods[1:])
2.77
2.78 # Obtain any participants to be added or removed.
2.79
2.80 @@ -751,38 +728,196 @@
2.81
2.82 return None
2.83
2.84 - def handle_date_controls(self, name, with_time=True):
2.85 + def handle_all_period_controls(self):
2.86
2.87 """
2.88 - Handle date control information for fields starting with 'name',
2.89 - returning a (datetime, attr) tuple or None if the fields cannot be used
2.90 - to construct a datetime object.
2.91 + Handle datetime controls for a particular period, where 'index' may be
2.92 + used to indicate a recurring period, or the main start and end datetimes
2.93 + are handled.
2.94 """
2.95
2.96 args = self.env.get_args()
2.97
2.98 - if args.has_key("%s-date" % name):
2.99 - date = args["%s-date" % name][0]
2.100 -
2.101 - if with_time:
2.102 - hour = args.get("%s-hour" % name, [None])[0]
2.103 - minute = args.get("%s-minute" % name, [None])[0]
2.104 - second = args.get("%s-second" % name, [None])[0]
2.105 - tzid = args.get("%s-tzid" % name, [self.get_tzid()])[0]
2.106 -
2.107 - time = (hour or minute or second) and "T%s%s%s" % (hour, minute, second) or ""
2.108 - value = "%s%s" % (date, time)
2.109 - attr = {"TZID" : tzid, "VALUE" : "DATE-TIME"}
2.110 - dt = get_datetime(value, attr)
2.111 + periods = []
2.112 +
2.113 + # Get the main period details.
2.114 +
2.115 + dtend_enabled = args.get("dtend-control", [None])[0] == "enable"
2.116 + dttimes_enabled = args.get("dttimes-control", [None])[0] == "enable"
2.117 + start_values = self.get_date_control_values("dtstart")
2.118 + end_values = self.get_date_control_values("dtend")
2.119 +
2.120 + period, errors = self.handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled)
2.121 +
2.122 + if errors:
2.123 + return None, errors
2.124 +
2.125 + periods.append(period)
2.126 +
2.127 + # Get the recurring period details.
2.128 +
2.129 + all_dtend_enabled = map(lambda x: x == "enable", args.get("dtend-control-recur", []))
2.130 + all_dttimes_enabled = map(lambda x: x == "enable", args.get("dttimes-control-recur", []))
2.131 + all_start_values = self.get_date_control_values("dtstart-recur", multiple=True)
2.132 + all_end_values = self.get_date_control_values("dtend-recur", multiple=True)
2.133 +
2.134 + for start_values, end_values, dtend_enabled, dttimes_enabled in \
2.135 + map(None, all_start_values, all_end_values, all_dtend_enabled, all_dttimes_enabled):
2.136 +
2.137 + period, errors = self.handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled)
2.138 +
2.139 + if errors:
2.140 + return None, errors
2.141 +
2.142 + periods.append(period)
2.143 +
2.144 + return periods, None
2.145 +
2.146 + def handle_period_controls(self, start_values, end_values, dtend_enabled, dttimes_enabled):
2.147 +
2.148 + """
2.149 + Handle datetime controls for a particular period, described by the given
2.150 + 'start_values' and 'end_values', with 'dtend_enabled' and
2.151 + 'dttimes_enabled' affecting the usage of the provided values.
2.152 + """
2.153 +
2.154 + t = self.handle_date_control_values(start_values, dttimes_enabled)
2.155 + if t:
2.156 + dtstart, dtstart_attr = t
2.157 + else:
2.158 + return None, ["dtstart"]
2.159 +
2.160 + # Handle specified end datetimes.
2.161 +
2.162 + if dtend_enabled:
2.163 + t = self.handle_date_control_values(end_values, dttimes_enabled)
2.164 + if t:
2.165 + dtend, dtend_attr = t
2.166 +
2.167 + # Convert end dates to iCalendar "next day" dates.
2.168 +
2.169 + if not isinstance(dtend, datetime):
2.170 + dtend += timedelta(1)
2.171 else:
2.172 - attr = {"VALUE" : "DATE"}
2.173 - dt = get_datetime(date)
2.174 -
2.175 - if dt:
2.176 - return dt, attr
2.177 + return None, ["dtend"]
2.178 +
2.179 + # Otherwise, treat the end date as the start date. Datetimes are
2.180 + # handled by making the event occupy the rest of the day.
2.181 +
2.182 + else:
2.183 + dtend = dtstart + timedelta(1)
2.184 + dtend_attr = dtstart_attr
2.185 +
2.186 + if isinstance(dtstart, datetime):
2.187 + dtend = get_start_of_day(dtend, attr["TZID"])
2.188 +
2.189 + if dtstart >= dtend:
2.190 + return None, ["dtstart", "dtend"]
2.191 +
2.192 + return ((dtstart, dtstart_attr), (dtend, dtend_attr)), None
2.193 +
2.194 + def handle_date_control_values(self, values, with_time=True):
2.195 +
2.196 + """
2.197 + Handle date control information for the given 'values', returning a
2.198 + (datetime, attr) tuple, or None if the fields cannot be used to
2.199 + construct a datetime object.
2.200 + """
2.201 +
2.202 + if not values or not values["date"]:
2.203 + return None
2.204 + elif with_time:
2.205 + value = "%s%s" % (values["date"], values["time"])
2.206 + attr = {"TZID" : values["tzid"], "VALUE" : "DATE-TIME"}
2.207 + dt = get_datetime(value, attr)
2.208 + else:
2.209 + attr = {"VALUE" : "DATE"}
2.210 + dt = get_datetime(values["date"])
2.211 +
2.212 + if dt:
2.213 + return dt, attr
2.214
2.215 return None
2.216
2.217 + def get_date_control_values(self, name, multiple=False):
2.218 +
2.219 + """
2.220 + Return a dictionary containing date, time and tzid entries for fields
2.221 + starting with 'name'.
2.222 + """
2.223 +
2.224 + args = self.env.get_args()
2.225 +
2.226 + dates = args.get("%s-date" % name, [])
2.227 + hours = args.get("%s-hour" % name, [])
2.228 + minutes = args.get("%s-minute" % name, [])
2.229 + seconds = args.get("%s-second" % name, [])
2.230 + tzids = args.get("%s-tzid" % name, [])
2.231 +
2.232 + # Handle absent values by employing None values.
2.233 +
2.234 + field_values = map(None, dates, hours, minutes, seconds, tzids)
2.235 + if not field_values and not multiple:
2.236 + field_values = [(None, None, None, None, None)]
2.237 +
2.238 + all_values = []
2.239 +
2.240 + for date, hour, minute, second, tzid in field_values:
2.241 +
2.242 + # Construct a usable dictionary of values.
2.243 +
2.244 + time = (hour or minute or second) and \
2.245 + "T%s%s%s" % (
2.246 + (hour or "").rjust(2, "0")[:2],
2.247 + (minute or "").rjust(2, "0")[:2],
2.248 + (second or "").rjust(2, "0")[:2]
2.249 + ) or ""
2.250 +
2.251 + value = {
2.252 + "date" : date,
2.253 + "time" : time,
2.254 + "tzid" : tzid or self.get_tzid()
2.255 + }
2.256 +
2.257 + # Return a single value or append to a collection of all values.
2.258 +
2.259 + if not multiple:
2.260 + return value
2.261 + else:
2.262 + all_values.append(value)
2.263 +
2.264 + return all_values
2.265 +
2.266 + def set_period_in_object(self, obj, period):
2.267 +
2.268 + "Set in the given 'obj' the given 'period' as the main start and end."
2.269 +
2.270 + (dtstart, dtstart_attr), (dtend, dtend_attr) = period
2.271 +
2.272 + return self.set_datetime_in_object(dtstart, dtstart_attr.get("TZID"), "DTSTART", obj) or \
2.273 + self.set_datetime_in_object(dtend, dtend_attr.get("TZID"), "DTEND", obj)
2.274 +
2.275 + def set_periods_in_object(self, obj, periods):
2.276 +
2.277 + "Set in the given 'obj' the given 'periods'."
2.278 +
2.279 + update = False
2.280 +
2.281 + old_values = obj.get_values("RDATE")
2.282 + new_rdates = []
2.283 +
2.284 + del obj["RDATE"]
2.285 +
2.286 + for period in periods:
2.287 + (dtstart, dtstart_attr), (dtend, dtend_attr) = period
2.288 + tzid = dtstart_attr.get("TZID") or dtend_attr.get("TZID")
2.289 + new_rdates.append(get_period_item(dtstart, dtend, tzid))
2.290 +
2.291 + obj["RDATE"] = new_rdates
2.292 +
2.293 + # NOTE: To do: calculate the update status.
2.294 + return update
2.295 +
2.296 def set_datetime_in_object(self, dt, tzid, property, obj):
2.297
2.298 """
2.299 @@ -860,12 +995,14 @@
2.300
2.301 (dtstart, dtstart_attr), (dtend, dtend_attr) = self.get_event_period(obj)
2.302
2.303 - t = self.handle_date_controls("dtstart", with_time)
2.304 + d = self.get_date_control_values("dtstart")
2.305 + t = self.handle_date_control_values(d, with_time)
2.306 if t:
2.307 dtstart, dtstart_attr = t
2.308
2.309 if dtend_control == "enable":
2.310 - t = self.handle_date_controls("dtend", with_time)
2.311 + d = self.get_date_control_values("dtend")
2.312 + t = self.handle_date_control_values(d, with_time)
2.313 if t:
2.314 dtend, dtend_attr = t
2.315 else:
2.316 @@ -1113,11 +1250,62 @@
2.317
2.318 page.form.close()
2.319
2.320 - def show_datetime_controls(self, obj, dt, attr, is_start_datetime):
2.321 + def show_object_datetime_controls(self, start, end, index=None):
2.322 +
2.323 + """
2.324 + Show datetime-related controls if already active or if an object needs
2.325 + them for the given 'start' to 'end' period. The given 'index' is used to
2.326 + parameterise individual controls for dynamic manipulation.
2.327 + """
2.328 +
2.329 + page = self.page
2.330 + args = self.env.get_args()
2.331 + sn = self._suffixed_name
2.332 + ssn = self._simple_suffixed_name
2.333 +
2.334 + # Add a dynamic stylesheet to permit the controls to modify the display.
2.335 + # NOTE: The style details need to be coordinated with the static
2.336 + # NOTE: stylesheet.
2.337 +
2.338 + if index is not None:
2.339 + page.style(type="text/css")
2.340 +
2.341 + # Unlike the rules for object properties, these affect recurrence
2.342 + # properties.
2.343 +
2.344 + page.add("""\
2.345 +input#dttimes-enable-%(index)d,
2.346 +input#dtend-enable-%(index)d,
2.347 +input#dttimes-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue .time.enabled,
2.348 +input#dttimes-enable-%(index)d:checked ~ .recurrence td.objectvalue .time.disabled,
2.349 +input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .dt.enabled,
2.350 +input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .dt.disabled {
2.351 + display: none;
2.352 +}""" % {"index" : index})
2.353 +
2.354 + page.style.close()
2.355 +
2.356 + dtend_control = args.get(ssn("dtend-control", "recur", index), [None])[0]
2.357 + dttimes_control = args.get(ssn("dttimes-control", "recur", index), [None])[0]
2.358 +
2.359 + dtend_enabled = dtend_control == "enable" or isinstance(end, datetime) or start != end
2.360 + dttimes_enabled = dttimes_control == "enable" or isinstance(start, datetime) or isinstance(end, datetime)
2.361 +
2.362 + if dtend_enabled:
2.363 + page.input(name=ssn("dtend-control", "recur", index), type="checkbox", value="enable", id=sn("dtend-enable", index), checked="checked")
2.364 + else:
2.365 + page.input(name=ssn("dtend-control", "recur", index), type="checkbox", value="enable", id=sn("dtend-enable", index))
2.366 +
2.367 + if dttimes_enabled:
2.368 + page.input(name=ssn("dttimes-control", "recur", index), type="checkbox", value="enable", id=sn("dttimes-enable", index), checked="checked")
2.369 + else:
2.370 + page.input(name=ssn("dttimes-control", "recur", index), type="checkbox", value="enable", id=sn("dttimes-enable", index))
2.371 +
2.372 + def show_datetime_controls(self, obj, dt, attr, show_start):
2.373
2.374 """
2.375 Show datetime details from the given 'obj' for the datetime 'dt' and
2.376 - attributes 'attr', showing start details if 'is_start_datetime' is set
2.377 + attributes 'attr', showing start details if 'show_start' is set
2.378 to a true value. Details will appear as controls for organisers and
2.379 labels for attendees.
2.380 """
2.381 @@ -1128,14 +1316,14 @@
2.382 # Show controls for editing as organiser.
2.383
2.384 if is_organiser:
2.385 - page.td(class_="objectvalue dt%s" % (is_start_datetime and "start" or "end"))
2.386 -
2.387 - if is_start_datetime:
2.388 + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
2.389 +
2.390 + if show_start:
2.391 page.div(class_="dt enabled")
2.392 self._show_date_controls("dtstart", dt, attr.get("TZID"))
2.393 page.br()
2.394 page.label("Specify times", for_="dttimes-enable", class_="time disabled enable")
2.395 - page.label("Specify dates only", for_="dttimes-disable", class_="time enabled disable")
2.396 + page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable")
2.397 page.div.close()
2.398
2.399 else:
2.400 @@ -1145,7 +1333,7 @@
2.401 page.div(class_="dt enabled")
2.402 self._show_date_controls("dtend", dt, attr.get("TZID"))
2.403 page.br()
2.404 - page.label("End on same day", for_="dtend-disable", class_="disable")
2.405 + page.label("End on same day", for_="dtend-enable", class_="disable")
2.406 page.div.close()
2.407
2.408 page.td.close()
2.409 @@ -1155,41 +1343,68 @@
2.410 else:
2.411 page.td(self.format_datetime(dt, "full"))
2.412
2.413 - def show_object_datetime_controls(self, start, end):
2.414 + def show_recurrence_controls(self, obj, index, start, end, origin, recurrenceid, recurrenceids, show_start):
2.415
2.416 """
2.417 - Show datetime-related controls if already active or if an object needs
2.418 - them for the given 'start' to 'end' period.
2.419 + Show datetime details from the given 'obj' for the recurrence having the
2.420 + given 'index', with the recurrence period described by the datetimes
2.421 + 'start' and 'end', indicating the 'origin' of the period from the event
2.422 + details, employing any 'recurrenceid' and 'recurrenceids' for the object
2.423 + to configure the displayed information.
2.424 +
2.425 + If 'show_start' is set to a true value, the start details will be shown;
2.426 + otherwise, the end details will be shown.
2.427 """
2.428
2.429 page = self.page
2.430 - args = self.env.get_args()
2.431 -
2.432 - dtend_control = args.get("dtend-control", [None])[0]
2.433 - dttimes_control = args.get("dttimes-control", [None])[0]
2.434 -
2.435 - dtend_enabled = dtend_control == "enable" or isinstance(end, datetime) or start != end
2.436 - dttimes_enabled = dttimes_control == "enable" or isinstance(start, datetime) or isinstance(end, datetime)
2.437 -
2.438 - if dtend_enabled:
2.439 - page.input(name="dtend-control", type="radio", value="enable", id="dtend-enable", checked="checked")
2.440 - page.input(name="dtend-control", type="radio", value="disable", id="dtend-disable")
2.441 + sn = self._suffixed_name
2.442 + ssn = self._simple_suffixed_name
2.443 +
2.444 + is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user
2.445 +
2.446 + start_utc = format_datetime(to_timezone(start, "UTC"))
2.447 + replaced = recurrenceids and start_utc in recurrenceids and "replaced" or ""
2.448 + css = " ".join([
2.449 + replaced,
2.450 + recurrenceid and start_utc == recurrenceid and "affected" or ""
2.451 + ])
2.452 +
2.453 + # Show controls for editing as organiser.
2.454 +
2.455 + if is_organiser and not replaced and origin != "RRULE":
2.456 + page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
2.457 +
2.458 + if show_start:
2.459 + page.div(class_="dt enabled")
2.460 + self._show_date_controls(ssn("dtstart", "recur", index), start, None)
2.461 + page.br()
2.462 + page.label("Specify times", for_=sn("dttimes-enable", index), class_="time disabled enable")
2.463 + page.label("Specify dates only", for_=sn("dttimes-enable", index), class_="time enabled disable")
2.464 + page.div.close()
2.465 +
2.466 + else:
2.467 + page.div(class_="dt disabled")
2.468 + page.label("Specify end date", for_=sn("dtend-enable", index), class_="enable")
2.469 + page.div.close()
2.470 + page.div(class_="dt enabled")
2.471 + self._show_date_controls(ssn("dtend", "recur", index), end, None)
2.472 + page.br()
2.473 + page.label("End on same day", for_=sn("dtend-enable", index), class_="disable")
2.474 + page.div.close()
2.475 +
2.476 + page.td.close()
2.477 +
2.478 + # Show label as attendee.
2.479 +
2.480 else:
2.481 - page.input(name="dtend-control", type="radio", value="enable", id="dtend-enable")
2.482 - page.input(name="dtend-control", type="radio", value="disable", id="dtend-disable", checked="checked")
2.483 -
2.484 - if dttimes_enabled:
2.485 - page.input(name="dttimes-control", type="radio", value="enable", id="dttimes-enable", checked="checked")
2.486 - page.input(name="dttimes-control", type="radio", value="disable", id="dttimes-disable")
2.487 - else:
2.488 - page.input(name="dttimes-control", type="radio", value="enable", id="dttimes-enable")
2.489 - page.input(name="dttimes-control", type="radio", value="disable", id="dttimes-disable", checked="checked")
2.490 + page.td(self.format_datetime(show_start and start or end, "long"), class_=css)
2.491
2.492 def show_recurrences(self, obj):
2.493
2.494 "Show recurrences for the object having the given representation 'obj'."
2.495
2.496 page = self.page
2.497 + is_organiser = get_uri(obj.get_value("ORGANIZER")) == self.user
2.498
2.499 # Obtain any parent object if this object is a specific recurrence.
2.500
2.501 @@ -1205,37 +1420,71 @@
2.502
2.503 # Obtain the periods associated with the event in the user's time zone.
2.504
2.505 - periods = obj.get_periods(self.get_tzid(), self.get_window_end())
2.506 + periods = obj.get_periods(self.get_tzid(), self.get_window_end(), origin=True)
2.507 recurrenceids = self._get_recurrences(uid)
2.508
2.509 if len(periods) == 1:
2.510 return
2.511
2.512 - page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
2.513 -
2.514 - page.table(cellspacing=5, cellpadding=5, class_="recurrences")
2.515 - page.thead()
2.516 - page.tr()
2.517 - page.th("Start")
2.518 - page.th("End")
2.519 - page.tr.close()
2.520 - page.thead.close()
2.521 - page.tbody()
2.522 -
2.523 - for start, end in periods:
2.524 - start_utc = format_datetime(to_timezone(start, "UTC"))
2.525 - css = " ".join([
2.526 - recurrenceids and start_utc in recurrenceids and "replaced" or "",
2.527 - recurrenceid and start_utc == recurrenceid and "affected" or ""
2.528 - ])
2.529 -
2.530 + if is_organiser:
2.531 + page.p("This event recurs on the following occasions within the next %d days:" % self.get_window_size())
2.532 + else:
2.533 + page.p("This event occurs on the following occasions within the next %d days:" % self.get_window_size())
2.534 +
2.535 + # Determine whether any periods are explicitly created or are part of a
2.536 + # rule.
2.537 +
2.538 + explicit_periods = filter(lambda t: t[2] != "RRULE", periods)
2.539 +
2.540 + # Show each recurrence in a separate table if editable.
2.541 +
2.542 + if is_organiser and explicit_periods:
2.543 + for index, (start, end, origin) in enumerate(periods[1:]):
2.544 +
2.545 + # Isolate the controls from neighbouring tables.
2.546 +
2.547 + page.div()
2.548 +
2.549 + self.show_object_datetime_controls(start, end, index)
2.550 +
2.551 + # NOTE: Need to customise the TH classes according to errors and
2.552 + # NOTE: index information.
2.553 +
2.554 + page.table(cellspacing=5, cellpadding=5, class_="recurrence")
2.555 + page.caption("Occurrence")
2.556 + page.tbody()
2.557 + page.tr()
2.558 + page.th("Start", class_="objectheading start")
2.559 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, True)
2.560 + page.tr.close()
2.561 + page.tr()
2.562 + page.th("End", class_="objectheading end")
2.563 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, False)
2.564 + page.tr.close()
2.565 + page.tbody.close()
2.566 + page.table.close()
2.567 +
2.568 + page.div.close()
2.569 +
2.570 + # Otherwise, use a compact single table.
2.571 +
2.572 + else:
2.573 + page.table(cellspacing=5, cellpadding=5, class_="recurrence")
2.574 + page.caption("Occurrences")
2.575 + page.thead()
2.576 page.tr()
2.577 - page.td(self.format_datetime(start, "long"), class_=css)
2.578 - page.td(self.format_datetime(end, "long"), class_=css)
2.579 + page.th("Start", class_="objectheading start")
2.580 + page.th("End", class_="objectheading end")
2.581 page.tr.close()
2.582 -
2.583 - page.tbody.close()
2.584 - page.table.close()
2.585 + page.thead.close()
2.586 + page.tbody()
2.587 + for index, (start, end, origin) in enumerate(periods):
2.588 + page.tr()
2.589 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, True)
2.590 + self.show_recurrence_controls(obj, index, start, end, origin, recurrenceid, recurrenceids, False)
2.591 + page.tr.close()
2.592 + page.tbody.close()
2.593 + page.table.close()
2.594
2.595 def show_conflicting_events(self, uid, obj):
2.596
2.597 @@ -1774,8 +2023,8 @@
2.598 ])
2.599
2.600 # Only anchor the first cell of events.
2.601 - # NOTE: Need to only anchor the first period for a
2.602 - # NOTE: recurring event.
2.603 + # Need to only anchor the first period for a recurring
2.604 + # event.
2.605
2.606 html_id = "%s-%s-%s" % (group_type, uid, recurrenceid or "")
2.607
2.608 @@ -1907,7 +2156,7 @@
2.609
2.610 """
2.611 Show date controls for a field with the given 'name' and 'default' value
2.612 - and 'attr'.
2.613 + and 'tzid'.
2.614 """
2.615
2.616 page = self.page
2.617 @@ -1917,7 +2166,7 @@
2.618
2.619 # Show dates for up to one week around the current date.
2.620
2.621 - base = get_date(default)
2.622 + base = to_date(default)
2.623 items = []
2.624 for i in range(-7, 8):
2.625 d = base + timedelta(i)