1.1 --- a/imipweb/event.py Thu Sep 24 19:40:03 2015 +0200
1.2 +++ b/imipweb/event.py Thu Sep 24 20:41:58 2015 +0200
1.3 @@ -19,20 +19,17 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 -from datetime import date, timedelta
1.8 from imiptools.data import get_uri, uri_dict, uri_items, uri_values
1.9 -from imiptools.dates import format_datetime, get_datetime_item, \
1.10 - to_date, to_timezone
1.11 +from imiptools.dates import to_timezone
1.12 from imiptools.mail import Messenger
1.13 from imiptools.period import have_conflict
1.14 from imipweb.data import EventPeriod, \
1.15 event_period_from_period, form_period_from_period, \
1.16 FormDate, FormPeriod, PeriodError
1.17 from imipweb.client import ManagerClient
1.18 -from imipweb.resource import ResourceClientForObject
1.19 -import pytz
1.20 +from imipweb.resource import FormUtilities, ResourceClientForObject
1.21
1.22 -class EventPage(ResourceClientForObject):
1.23 +class EventPage(ResourceClientForObject, FormUtilities):
1.24
1.25 "A request handler for the event page."
1.26
1.27 @@ -448,27 +445,27 @@
1.28 page.p("An action is required for this request:")
1.29
1.30 page.p()
1.31 - self._control("reply", "submit", "Send reply")
1.32 + self.control("reply", "submit", "Send reply")
1.33 page.add(" ")
1.34 - self._control("discard", "submit", "Discard event")
1.35 + self.control("discard", "submit", "Discard event")
1.36 page.add(" ")
1.37 - self._control("ignore", "submit", "Do nothing for now")
1.38 + self.control("ignore", "submit", "Do nothing for now")
1.39 page.p.close()
1.40
1.41 if self.is_organiser():
1.42 page.p("As organiser, you can perform the following:")
1.43
1.44 page.p()
1.45 - self._control("create", "submit", not self.obj.is_shared() and "Create event" or "Update event")
1.46 + self.control("create", "submit", not self.obj.is_shared() and "Create event" or "Update event")
1.47 page.add(" ")
1.48
1.49 if self.obj.is_shared() and not is_request:
1.50 - self._control("cancel", "submit", "Cancel event")
1.51 + self.control("cancel", "submit", "Cancel event")
1.52 else:
1.53 - self._control("discard", "submit", "Discard event")
1.54 + self.control("discard", "submit", "Discard event")
1.55
1.56 page.add(" ")
1.57 - self._control("save", "submit", "Save without sending")
1.58 + self.control("save", "submit", "Save without sending")
1.59 page.p.close()
1.60
1.61 def show_object_on_page(self, errors=None):
1.62 @@ -483,7 +480,7 @@
1.63
1.64 # Add a hidden control to help determine whether editing has already begun.
1.65
1.66 - self._control("editing", "hidden", "true")
1.67 + self.control("editing", "hidden", "true")
1.68
1.69 args = self.env.get_args()
1.70
1.71 @@ -555,7 +552,7 @@
1.72
1.73 page.td(class_="objectvalue summary")
1.74 if self.is_organiser():
1.75 - self._control("summary", "text", value, size=80)
1.76 + self.control("summary", "text", value, size=80)
1.77 else:
1.78 page.add(value)
1.79 page.td.close()
1.80 @@ -585,7 +582,7 @@
1.81 page.tr()
1.82
1.83 page.td()
1.84 - self._control("add", "submit", "add", id="add", class_="add")
1.85 + self.control("add", "submit", "add", id="add", class_="add")
1.86 page.label("Add attendee", for_="add", class_="add")
1.87 page.td.close()
1.88 page.tr.close()
1.89 @@ -632,23 +629,23 @@
1.90 # Show a form control as organiser for new attendees.
1.91
1.92 if self.is_organiser() and self.can_edit_attendee(attendee):
1.93 - self._control("attendee", "value", attendee, size="40")
1.94 + self.control("attendee", "value", attendee, size="40")
1.95 else:
1.96 - self._control("attendee", "hidden", attendee)
1.97 + self.control("attendee", "hidden", attendee)
1.98 page.add(attendee)
1.99 page.add(" ")
1.100
1.101 # Show participation status, editable for the current user.
1.102
1.103 if attendee == self.user:
1.104 - self._show_menu("partstat", partstat, self.partstat_items, "partstat")
1.105 + self.menu("partstat", partstat, self.partstat_items, "partstat")
1.106
1.107 # Allow the participation indicator to act as a submit
1.108 # button in order to refresh the page and show a control for
1.109 # the current user, if indicated.
1.110
1.111 elif self.is_organiser() and self.attendee_is_new(attendee):
1.112 - self._control("partstat-refresh", "submit", "refresh", id="partstat-%d" % i, class_="refresh")
1.113 + self.control("partstat-refresh", "submit", "refresh", id="partstat-%d" % i, class_="refresh")
1.114 page.label(dict(self.partstat_items).get(partstat, ""), for_="partstat-%s" % i, class_="partstat")
1.115
1.116 # Otherwise, just show a label with the participation status.
1.117 @@ -663,7 +660,7 @@
1.118 # Permit the removal of newly-added attendees.
1.119
1.120 remove_type = self.can_remove_attendee(attendee) and "submit" or "checkbox"
1.121 - self._control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove")
1.122 + self.control("remove", remove_type, str(i), str(i) in args.get("remove", []), id="remove-%d" % i, class_="remove")
1.123
1.124 page.label("Remove", for_="remove-%d" % i, class_="remove")
1.125 page.label(for_="remove-%d" % i, class_="removed")
1.126 @@ -782,7 +779,7 @@
1.127 page.td()
1.128
1.129 remove_type = not self.obj.is_shared() or not period.origin and "submit" or "checkbox"
1.130 - self._control("recur-remove", remove_type, str(index),
1.131 + self.control("recur-remove", remove_type, str(index),
1.132 str(index) in args.get("recur-remove", []),
1.133 id="recur-remove-%d" % index, class_="remove")
1.134
1.135 @@ -934,13 +931,13 @@
1.136
1.137 page.style.close()
1.138
1.139 - self._control(
1.140 + self.control(
1.141 _name("dtend-control", "recur", index), "checkbox",
1.142 _enable(index), p.end_enabled,
1.143 id=_id("dtend-enable", index)
1.144 )
1.145
1.146 - self._control(
1.147 + self.control(
1.148 _name("dttimes-control", "recur", index), "checkbox",
1.149 _enable(index), p.times_enabled,
1.150 id=_id("dttimes-enable", index)
1.151 @@ -963,7 +960,7 @@
1.152
1.153 if show_start:
1.154 page.div(class_="dt enabled")
1.155 - self._show_date_controls("dtstart", formdate)
1.156 + self.date_controls("dtstart", formdate)
1.157 page.br()
1.158 page.label("Specify times", for_="dttimes-enable", class_="time disabled enable")
1.159 page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable")
1.160 @@ -974,7 +971,7 @@
1.161 page.label("Specify end date", for_="dtend-enable", class_="enable")
1.162 page.div.close()
1.163 page.div(class_="dt enabled")
1.164 - self._show_date_controls("dtend", formdate)
1.165 + self.date_controls("dtend", formdate)
1.166 page.br()
1.167 page.label("End on same day", for_="dtend-enable", class_="disable")
1.168 page.div.close()
1.169 @@ -1019,7 +1016,7 @@
1.170
1.171 if show_start:
1.172 page.div(class_="dt enabled")
1.173 - self._show_date_controls(_name("dtstart", "recur", index), p.get_form_start(), index=index, read_only=read_only)
1.174 + self.date_controls(_name("dtstart", "recur", index), p.get_form_start(), index=index, read_only=read_only)
1.175 if not read_only:
1.176 page.br()
1.177 page.label("Specify times", for_=_id("dttimes-enable", index), class_="time disabled enable")
1.178 @@ -1028,7 +1025,7 @@
1.179
1.180 # Put the origin somewhere.
1.181
1.182 - self._control("recur-origin", "hidden", p.origin or "")
1.183 + self.control("recur-origin", "hidden", p.origin or "")
1.184
1.185 else:
1.186 page.div(class_="dt disabled")
1.187 @@ -1036,7 +1033,7 @@
1.188 page.label("Specify end date", for_=_id("dtend-enable", index), class_="enable")
1.189 page.div.close()
1.190 page.div(class_="dt enabled")
1.191 - self._show_date_controls(_name("dtend", "recur", index), p.get_form_end(), index=index, show_tzid=False, read_only=read_only)
1.192 + self.date_controls(_name("dtend", "recur", index), p.get_form_end(), index=index, show_tzid=False, read_only=read_only)
1.193 if not read_only:
1.194 page.br()
1.195 page.label("End on same day", for_=_id("dtend-enable", index), class_="disable")
1.196 @@ -1100,130 +1097,4 @@
1.197
1.198 return True
1.199
1.200 - # Utility methods.
1.201 -
1.202 - def _control(self, name, type, value, selected=False, **kw):
1.203 -
1.204 - """
1.205 - Show a control with the given 'name', 'type' and 'value', with
1.206 - 'selected' indicating whether it should be selected (checked or
1.207 - equivalent), and with keyword arguments setting other properties.
1.208 - """
1.209 -
1.210 - page = self.page
1.211 - if selected:
1.212 - page.input(name=name, type=type, value=value, checked=selected, **kw)
1.213 - else:
1.214 - page.input(name=name, type=type, value=value, **kw)
1.215 -
1.216 - def _show_menu(self, name, default, items, class_="", index=None):
1.217 -
1.218 - """
1.219 - Show a select menu having the given 'name', set to the given 'default',
1.220 - providing the given (value, label) 'items', and employing the given CSS
1.221 - 'class_' if specified.
1.222 - """
1.223 -
1.224 - page = self.page
1.225 - values = self.env.get_args().get(name, [default])
1.226 - if index is not None:
1.227 - values = values[index:]
1.228 - values = values and values[0:1] or [default]
1.229 -
1.230 - page.select(name=name, class_=class_)
1.231 - for v, label in items:
1.232 - if v is None:
1.233 - continue
1.234 - if v in values:
1.235 - page.option(label, value=v, selected="selected")
1.236 - else:
1.237 - page.option(label, value=v)
1.238 - page.select.close()
1.239 -
1.240 - def _show_date_controls(self, name, default, index=None, show_tzid=True, read_only=False):
1.241 -
1.242 - """
1.243 - Show date controls for a field with the given 'name' and 'default' form
1.244 - date value.
1.245 -
1.246 - If 'index' is specified, default field values will be overridden by the
1.247 - element from a collection of existing form values with the specified
1.248 - index; otherwise, field values will be overridden by a single form
1.249 - value.
1.250 -
1.251 - If 'show_tzid' is set to a false value, the time zone menu will not be
1.252 - provided.
1.253 -
1.254 - If 'read_only' is set to a true value, the controls will be hidden and
1.255 - labels will be employed instead.
1.256 - """
1.257 -
1.258 - page = self.page
1.259 -
1.260 - # Show dates for up to one week around the current date.
1.261 -
1.262 - dt = default.as_datetime()
1.263 - if not dt:
1.264 - dt = date.today()
1.265 -
1.266 - base = to_date(dt)
1.267 -
1.268 - # Show a date label with a hidden field if read-only.
1.269 -
1.270 - if read_only:
1.271 - self._control("%s-date" % name, "hidden", format_datetime(base))
1.272 - page.span(self.format_date(base, "long"))
1.273 -
1.274 - # Show dates for up to one week around the current date.
1.275 - # NOTE: Support paging to other dates.
1.276 -
1.277 - else:
1.278 - items = []
1.279 - for i in range(-7, 8):
1.280 - d = base + timedelta(i)
1.281 - items.append((format_datetime(d), self.format_date(d, "full")))
1.282 - self._show_menu("%s-date" % name, format_datetime(base), items, index=index)
1.283 -
1.284 - # Show time details.
1.285 -
1.286 - page.span(class_="time enabled")
1.287 -
1.288 - if read_only:
1.289 - page.span("%s:%s:%s" % (default.get_hour(), default.get_minute(), default.get_second()))
1.290 - self._control("%s-hour" % name, "hidden", default.get_hour())
1.291 - self._control("%s-minute" % name, "hidden", default.get_minute())
1.292 - self._control("%s-second" % name, "hidden", default.get_second())
1.293 - else:
1.294 - self._control("%s-hour" % name, "text", default.get_hour(), maxlength=2, size=2)
1.295 - page.add(":")
1.296 - self._control("%s-minute" % name, "text", default.get_minute(), maxlength=2, size=2)
1.297 - page.add(":")
1.298 - self._control("%s-second" % name, "text", default.get_second(), maxlength=2, size=2)
1.299 -
1.300 - # Show time zone details.
1.301 -
1.302 - if show_tzid:
1.303 - page.add(" ")
1.304 - tzid = default.get_tzid() or self.get_tzid()
1.305 -
1.306 - # Show a label if read-only or a menu otherwise.
1.307 -
1.308 - if read_only:
1.309 - self._control("%s-tzid" % name, "hidden", tzid)
1.310 - page.span(tzid)
1.311 - else:
1.312 - self._show_timezone_menu("%s-tzid" % name, tzid, index)
1.313 -
1.314 - page.span.close()
1.315 -
1.316 - def _show_timezone_menu(self, name, default, index=None):
1.317 -
1.318 - """
1.319 - Show timezone controls using a menu with the given 'name', set to the
1.320 - given 'default' unless a field of the given 'name' provides a value.
1.321 - """
1.322 -
1.323 - entries = [(tzid, tzid) for tzid in pytz.all_timezones]
1.324 - self._show_menu(name, default, entries, index=index)
1.325 -
1.326 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imipweb/resource.py Thu Sep 24 19:40:03 2015 +0200
2.2 +++ b/imipweb/resource.py Thu Sep 24 20:41:58 2015 +0200
2.3 @@ -19,15 +19,16 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 -from datetime import datetime
2.8 +from datetime import datetime, timedelta
2.9 from imiptools.client import Client, ClientForObject
2.10 from imiptools.data import get_uri, uri_values
2.11 -from imiptools.dates import get_recurrence_start_point
2.12 +from imiptools.dates import format_datetime, get_recurrence_start_point, to_date
2.13 from imiptools.period import remove_period, remove_affected_period
2.14 from imipweb.env import CGIEnvironment
2.15 import babel.dates
2.16 import imip_store
2.17 import markup
2.18 +import pytz
2.19
2.20 class Resource:
2.21
2.22 @@ -212,4 +213,132 @@
2.23 user = self.env.get_user()
2.24 ClientForObject.__init__(self, None, user and get_uri(user) or None)
2.25
2.26 +class FormUtilities:
2.27 +
2.28 + "Utility methods."
2.29 +
2.30 + def control(self, name, type, value, selected=False, **kw):
2.31 +
2.32 + """
2.33 + Show a control with the given 'name', 'type' and 'value', with
2.34 + 'selected' indicating whether it should be selected (checked or
2.35 + equivalent), and with keyword arguments setting other properties.
2.36 + """
2.37 +
2.38 + page = self.page
2.39 + if selected:
2.40 + page.input(name=name, type=type, value=value, checked=selected, **kw)
2.41 + else:
2.42 + page.input(name=name, type=type, value=value, **kw)
2.43 +
2.44 + def menu(self, name, default, items, class_="", index=None):
2.45 +
2.46 + """
2.47 + Show a select menu having the given 'name', set to the given 'default',
2.48 + providing the given (value, label) 'items', and employing the given CSS
2.49 + 'class_' if specified.
2.50 + """
2.51 +
2.52 + page = self.page
2.53 + values = self.env.get_args().get(name, [default])
2.54 + if index is not None:
2.55 + values = values[index:]
2.56 + values = values and values[0:1] or [default]
2.57 +
2.58 + page.select(name=name, class_=class_)
2.59 + for v, label in items:
2.60 + if v is None:
2.61 + continue
2.62 + if v in values:
2.63 + page.option(label, value=v, selected="selected")
2.64 + else:
2.65 + page.option(label, value=v)
2.66 + page.select.close()
2.67 +
2.68 + def date_controls(self, name, default, index=None, show_tzid=True, read_only=False):
2.69 +
2.70 + """
2.71 + Show date controls for a field with the given 'name' and 'default' form
2.72 + date value.
2.73 +
2.74 + If 'index' is specified, default field values will be overridden by the
2.75 + element from a collection of existing form values with the specified
2.76 + index; otherwise, field values will be overridden by a single form
2.77 + value.
2.78 +
2.79 + If 'show_tzid' is set to a false value, the time zone menu will not be
2.80 + provided.
2.81 +
2.82 + If 'read_only' is set to a true value, the controls will be hidden and
2.83 + labels will be employed instead.
2.84 + """
2.85 +
2.86 + page = self.page
2.87 +
2.88 + # Show dates for up to one week around the current date.
2.89 +
2.90 + dt = default.as_datetime()
2.91 + if not dt:
2.92 + dt = date.today()
2.93 +
2.94 + base = to_date(dt)
2.95 +
2.96 + # Show a date label with a hidden field if read-only.
2.97 +
2.98 + if read_only:
2.99 + self.control("%s-date" % name, "hidden", format_datetime(base))
2.100 + page.span(self.format_date(base, "long"))
2.101 +
2.102 + # Show dates for up to one week around the current date.
2.103 + # NOTE: Support paging to other dates.
2.104 +
2.105 + else:
2.106 + items = []
2.107 + for i in range(-7, 8):
2.108 + d = base + timedelta(i)
2.109 + items.append((format_datetime(d), self.format_date(d, "full")))
2.110 + self.menu("%s-date" % name, format_datetime(base), items, index=index)
2.111 +
2.112 + # Show time details.
2.113 +
2.114 + page.span(class_="time enabled")
2.115 +
2.116 + if read_only:
2.117 + page.span("%s:%s:%s" % (default.get_hour(), default.get_minute(), default.get_second()))
2.118 + self.control("%s-hour" % name, "hidden", default.get_hour())
2.119 + self.control("%s-minute" % name, "hidden", default.get_minute())
2.120 + self.control("%s-second" % name, "hidden", default.get_second())
2.121 + else:
2.122 + self.control("%s-hour" % name, "text", default.get_hour(), maxlength=2, size=2)
2.123 + page.add(":")
2.124 + self.control("%s-minute" % name, "text", default.get_minute(), maxlength=2, size=2)
2.125 + page.add(":")
2.126 + self.control("%s-second" % name, "text", default.get_second(), maxlength=2, size=2)
2.127 +
2.128 + # Show time zone details.
2.129 +
2.130 + if show_tzid:
2.131 + page.add(" ")
2.132 + tzid = default.get_tzid() or self.get_tzid()
2.133 +
2.134 + # Show a label if read-only or a menu otherwise.
2.135 +
2.136 + if read_only:
2.137 + self.control("%s-tzid" % name, "hidden", tzid)
2.138 + page.span(tzid)
2.139 + else:
2.140 + self.timezone_menu("%s-tzid" % name, tzid, index)
2.141 +
2.142 + page.span.close()
2.143 +
2.144 + def timezone_menu(self, name, default, index=None):
2.145 +
2.146 + """
2.147 + Show timezone controls using a menu with the given 'name', set to the
2.148 + given 'default' unless a field of the given 'name' provides a value.
2.149 + """
2.150 +
2.151 + entries = [(tzid, tzid) for tzid in pytz.all_timezones]
2.152 + self.menu(name, default, entries, index=index)
2.153 +
2.154 # vim: tabstop=4 expandtab shiftwidth=4