1.1 --- a/imip_manager.py Sat Feb 07 21:33:20 2015 +0100
1.2 +++ b/imip_manager.py Sat Feb 07 21:40:17 2015 +0100
1.3 @@ -33,7 +33,8 @@
1.4 from imiptools.content import Handler
1.5 from imiptools.data import get_address, get_uri, make_freebusy, parse_object, \
1.6 Object, to_part
1.7 -from imiptools.dates import format_datetime, get_datetime, get_datetime_item, \
1.8 +from imiptools.dates import format_datetime, format_time, get_date, get_datetime, \
1.9 + get_datetime_item, \
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 from imiptools.mail import Messenger
1.13 @@ -456,12 +457,9 @@
1.14
1.15 self.redirect(self.env.new_url("%s-0" % uid))
1.16
1.17 - def handle_request(self, uid, obj, queued):
1.18 + def handle_request(self, uid, obj):
1.19
1.20 - """
1.21 - Handle actions involving the given 'uid' and 'obj' object, where
1.22 - 'queued' indicates that the object has not yet been handled.
1.23 - """
1.24 + "Handle actions involving the given 'uid' and 'obj' object."
1.25
1.26 # Handle a submitted form.
1.27
1.28 @@ -476,9 +474,39 @@
1.29 if args.has_key("partstat"):
1.30 organisers = obj.get_value_map("ORGANIZER")
1.31 attendees = obj.get_value_map("ATTENDEE")
1.32 - d = attendees.has_key(self.user) and attendees or organisers.has_key(self.user) and organisers or None
1.33 - if d:
1.34 - d[self.user]["PARTSTAT"] = args["partstat"][0]
1.35 + for d in attendees, organisers:
1.36 + if d.has_key(self.user):
1.37 + d[self.user]["PARTSTAT"] = args["partstat"][0]
1.38 + if d[self.user].has_key("RSVP"):
1.39 + del d[self.user]["RSVP"]
1.40 +
1.41 + is_organiser = obj.get_value("ORGANIZER") == self.user
1.42 +
1.43 + # Obtain the user's timezone and process datetime values.
1.44 +
1.45 + update = False
1.46 + error = False
1.47 +
1.48 + if is_organiser:
1.49 + t = self.handle_date_controls("dtstart")
1.50 + if t:
1.51 + dtstart, tzid = t
1.52 + update = update or self.set_datetime_in_object(dtstart, tzid, "DTSTART", obj)
1.53 + else:
1.54 + error = True
1.55 +
1.56 + t = self.handle_date_controls("dtend")
1.57 + if t:
1.58 + dtend, tzid = t
1.59 + update = update or self.set_datetime_in_object(dtend, tzid, "DTEND", obj)
1.60 + else:
1.61 + error = True
1.62 +
1.63 + if not error:
1.64 + error = dtstart > dtend
1.65 +
1.66 + if error:
1.67 + return False
1.68
1.69 # Process any action.
1.70
1.71 @@ -487,7 +515,6 @@
1.72 invite = args.has_key("invite")
1.73 cancel = args.has_key("cancel")
1.74 save = args.has_key("save")
1.75 - update = not queued and args.has_key("update")
1.76
1.77 if reply or invite or cancel:
1.78
1.79 @@ -523,16 +550,52 @@
1.80
1.81 return handled
1.82
1.83 - # Page fragment methods.
1.84 -
1.85 - def show_request_controls(self, obj, needs_update):
1.86 + def handle_date_controls(self, name):
1.87
1.88 """
1.89 - Show form controls for a request concerning 'obj', indicating whether
1.90 - an update will be performed if 'needs_update' is specified as a true
1.91 - value.
1.92 + Handle date control information for fields starting with 'name',
1.93 + returning a (datetime, tzid) tuple or None if the fields cannot be used
1.94 + to construct a datetime object.
1.95 """
1.96
1.97 + args = self.env.get_args()
1.98 + tzid = self.get_tzid()
1.99 +
1.100 + if args.has_key("%s-date" % name):
1.101 + date = args["%s-date" % name][0]
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, [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 + dt = get_datetime(value, {"TZID" : tzid})
1.110 + if dt:
1.111 + return dt, tzid
1.112 +
1.113 + return None
1.114 +
1.115 + def set_datetime_in_object(self, dt, tzid, property, obj):
1.116 +
1.117 + """
1.118 + Set 'dt' and 'tzid' for the given 'property' in 'obj', returning whether
1.119 + an update has occurred.
1.120 + """
1.121 +
1.122 + if dt:
1.123 + old_value = obj.get_value(property)
1.124 + obj[property] = [get_datetime_item(dt, tzid)]
1.125 + return format_datetime(dt) != old_value
1.126 +
1.127 + return False
1.128 +
1.129 + # Page fragment methods.
1.130 +
1.131 + def show_request_controls(self, obj):
1.132 +
1.133 + "Show form controls for a request concerning 'obj'."
1.134 +
1.135 page = self.page
1.136
1.137 is_organiser = obj.get_value("ORGANIZER") == self.user
1.138 @@ -548,10 +611,7 @@
1.139 # Show appropriate options depending on the role of the user.
1.140
1.141 if is_attendee and not is_organiser:
1.142 - if needs_update:
1.143 - page.p("This request can be updated as follows:")
1.144 - else:
1.145 - page.p("An action is required for this request:")
1.146 + page.p("An action is required for this request:")
1.147
1.148 page.p()
1.149 page.input(name="reply", type="submit", value="Reply")
1.150 @@ -561,10 +621,7 @@
1.151
1.152 if is_organiser:
1.153 if have_other_attendees:
1.154 - if needs_update:
1.155 - page.p("As organiser, you can perform the following:")
1.156 - else:
1.157 - page.p("As organiser, you will need to perform an action:")
1.158 + page.p("As organiser, you can perform the following:")
1.159
1.160 page.p()
1.161 page.input(name="invite", type="submit", value="Invite")
1.162 @@ -581,11 +638,6 @@
1.163 page.input(name="discard", type="submit", value="Discard")
1.164 page.p.close()
1.165
1.166 - # Updated objects need to have details updated upon sending.
1.167 -
1.168 - if needs_update:
1.169 - page.input(name="update", type="hidden", value="true")
1.170 -
1.171 object_labels = {
1.172 "SUMMARY" : "Summary",
1.173 "DTSTART" : "Start",
1.174 @@ -602,7 +654,7 @@
1.175 ("DELEGATED", "Delegated"),
1.176 ]
1.177
1.178 - def show_object_on_page(self, uid, obj, needs_update):
1.179 + def show_object_on_page(self, uid, obj):
1.180
1.181 """
1.182 Show the calendar object with the given 'uid' and representation 'obj'
1.183 @@ -621,7 +673,7 @@
1.184 page.table(class_="object", cellspacing=5, cellpadding=5)
1.185 page.thead()
1.186 page.tr()
1.187 - page.th("Event", class_="mainheading", colspan=3)
1.188 + page.th("Event", class_="mainheading", colspan=2)
1.189 page.tr.close()
1.190 page.thead.close()
1.191 page.tbody()
1.192 @@ -637,12 +689,19 @@
1.193
1.194 if name in ["DTSTART", "DTEND"]:
1.195 value, attr = obj.get_item(name)
1.196 - tzid = attr.get("TZID", tzid)
1.197 - value = (
1.198 + event_tzid = attr.get("TZID", tzid)
1.199 + strvalue = (
1.200 name == "DTSTART" and self.format_datetime or self.format_end_datetime
1.201 - )(to_timezone(get_datetime(value), tzid), "full")
1.202 + )(to_timezone(get_datetime(value), event_tzid), "full")
1.203 page.th(label, class_="objectheading")
1.204 - page.td(value, colspan=2)
1.205 +
1.206 + if is_organiser:
1.207 + page.td()
1.208 + self._show_date_controls(name.lower(), value, attr, tzid)
1.209 + page.td.close()
1.210 + else:
1.211 + page.td(strvalue)
1.212 +
1.213 page.tr.close()
1.214
1.215 # Handle the summary specially.
1.216 @@ -650,7 +709,7 @@
1.217 elif name == "SUMMARY":
1.218 value = obj.get_value(name)
1.219 page.th(label, class_="objectheading")
1.220 - page.td(colspan=2)
1.221 + page.td()
1.222 if is_organiser:
1.223 page.input(name="summary", type="text", value=value, size=80)
1.224 else:
1.225 @@ -678,16 +737,15 @@
1.226 if name in ("ATTENDEE", "ORGANIZER"):
1.227 page.td(class_="objectattribute")
1.228 page.add(value)
1.229 - page.td.close()
1.230 - page.td(class_="partstat")
1.231 + page.add(" ")
1.232
1.233 partstat = attr.get("PARTSTAT")
1.234 - if value == self.user:
1.235 + if value == self.user and (not is_organiser or name == "ORGANIZER"):
1.236 self._show_menu("partstat", partstat, self.partstat_items)
1.237 else:
1.238 - page.add(dict(self.partstat_items).get(partstat, ""))
1.239 + page.span(dict(self.partstat_items).get(partstat, ""), class_="partstat")
1.240 else:
1.241 - page.td(class_="objectattribute", colspan=2)
1.242 + page.td(class_="objectattribute")
1.243 page.add(value)
1.244
1.245 page.td.close()
1.246 @@ -728,7 +786,7 @@
1.247 if found_obj:
1.248 page.a(found_obj.get_value("SUMMARY"), href=self.env.new_url(found_uid))
1.249
1.250 - self.show_request_controls(obj, needs_update)
1.251 + self.show_request_controls(obj)
1.252 page.form.close()
1.253
1.254 def show_requests_on_page(self):
1.255 @@ -816,14 +874,13 @@
1.256 if not obj:
1.257 return False
1.258
1.259 - is_request = uid in self._get_requests()
1.260 - handled = self.handle_request(uid, obj, is_request)
1.261 + handled = self.handle_request(uid, obj)
1.262
1.263 if handled:
1.264 return True
1.265
1.266 self.new_page(title="Event")
1.267 - self.show_object_on_page(uid, obj, not is_request)
1.268 + self.show_object_on_page(uid, obj)
1.269
1.270 return True
1.271
1.272 @@ -1286,9 +1343,9 @@
1.273 identifier = "slot-%s" % value
1.274 return value, identifier
1.275
1.276 - def _show_menu(self, name, value, items):
1.277 + def _show_menu(self, name, default, items):
1.278 page = self.page
1.279 - values = self.env.get_args().get(name, [value])
1.280 + values = self.env.get_args().get(name, [default])
1.281 page.select(name=name)
1.282 for v, label in items:
1.283 if v in values:
1.284 @@ -1297,6 +1354,48 @@
1.285 page.option(label, value=v)
1.286 page.select.close()
1.287
1.288 + def _show_date_controls(self, name, default, attr, tzid):
1.289 +
1.290 + """
1.291 + Show date controls for a field with the given 'name' and 'default' value
1.292 + and 'attr', with the given 'tzid' being used if no other time regime
1.293 + information is provided.
1.294 + """
1.295 +
1.296 + page = self.page
1.297 + args = self.env.get_args()
1.298 +
1.299 + event_tzid = attr.get("TZID", tzid)
1.300 + dt = get_datetime(default, attr)
1.301 +
1.302 + # Show dates for up to one week around the current date.
1.303 +
1.304 + base = get_date(dt)
1.305 + items = []
1.306 + for i in range(-7, 8):
1.307 + d = base + timedelta(i)
1.308 + items.append((format_datetime(d), self.format_date(d, "full")))
1.309 +
1.310 + self._show_menu("%s-date" % name, format_datetime(base), items)
1.311 +
1.312 + # Show time details.
1.313 +
1.314 + if isinstance(dt, datetime):
1.315 + hour = args.get("%s-hour" % name, "%02d" % dt.hour)
1.316 + minute = args.get("%s-minute" % name, "%02d" % dt.minute)
1.317 + second = args.get("%s-second" % name, "%02d" % dt.second)
1.318 + page.add(" ")
1.319 + page.input(name="%s-hour" % name, type="text", value=hour, maxlength=2, size=2)
1.320 + page.add(":")
1.321 + page.input(name="%s-minute" % name, type="text", value=minute, maxlength=2, size=2)
1.322 + page.add(":")
1.323 + page.input(name="%s-second" % name, type="text", value=second, maxlength=2, size=2)
1.324 + page.add(" ")
1.325 + self._show_menu("%s-tzid" % name, event_tzid,
1.326 + [(event_tzid, event_tzid)] + (
1.327 + event_tzid != tzid and [(tzid, tzid)] or []
1.328 + ))
1.329 +
1.330 # Incoming HTTP request direction.
1.331
1.332 def select_action(self):