imip-agent

Annotated imipweb/resource.py

1069:37921ab84c01
2016-03-06 Paul Boddie Moved imip_store into a new imiptools.stores package as the file module.
paul@446 1
#!/usr/bin/env python
paul@446 2
paul@446 3
"""
paul@446 4
Common resource functionality for Web calendar clients.
paul@446 5
paul@1029 6
Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@446 7
paul@446 8
This program is free software; you can redistribute it and/or modify it under
paul@446 9
the terms of the GNU General Public License as published by the Free Software
paul@446 10
Foundation; either version 3 of the License, or (at your option) any later
paul@446 11
version.
paul@446 12
paul@446 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@446 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@446 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@446 16
details.
paul@446 17
paul@446 18
You should have received a copy of the GNU General Public License along with
paul@446 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@446 20
"""
paul@446 21
paul@764 22
from datetime import datetime, timedelta
paul@756 23
from imiptools.client import Client, ClientForObject
paul@909 24
from imiptools.data import get_uri
paul@909 25
from imiptools.dates import format_datetime, to_date
paul@845 26
from imipweb.data import event_period_from_period, form_period_from_period, \
paul@845 27
                         FormDate, PeriodError
paul@446 28
from imipweb.env import CGIEnvironment
paul@777 29
from urllib import urlencode
paul@446 30
import babel.dates
paul@446 31
import markup
paul@764 32
import pytz
paul@446 33
paul@756 34
class Resource:
paul@446 35
paul@756 36
    "A Web application resource."
paul@446 37
paul@446 38
    def __init__(self, resource=None):
paul@756 39
paul@756 40
        """
paul@756 41
        Initialise a resource, allowing it to share the environment of any given
paul@756 42
        existing 'resource'.
paul@756 43
        """
paul@756 44
paul@446 45
        self.encoding = "utf-8"
paul@446 46
        self.env = CGIEnvironment(self.encoding)
paul@446 47
paul@756 48
        self.objects = {}
paul@446 49
        self.locale = None
paul@446 50
        self.requests = None
paul@446 51
paul@446 52
        self.out = resource and resource.out or self.env.get_output()
paul@446 53
        self.page = resource and resource.page or markup.page()
paul@446 54
        self.html_ids = None
paul@446 55
paul@446 56
    # Presentation methods.
paul@446 57
paul@446 58
    def new_page(self, title):
paul@446 59
        self.page.init(title=title, charset=self.encoding, css=self.env.new_url("styles.css"))
paul@446 60
        self.html_ids = set()
paul@446 61
paul@446 62
    def status(self, code, message):
paul@446 63
        self.header("Status", "%s %s" % (code, message))
paul@446 64
paul@446 65
    def header(self, header, value):
paul@446 66
        print >>self.out, "%s: %s" % (header, value)
paul@446 67
paul@446 68
    def no_user(self):
paul@446 69
        self.status(403, "Forbidden")
paul@446 70
        self.new_page(title="Forbidden")
paul@446 71
        self.page.p("You are not logged in and thus cannot access scheduling requests.")
paul@446 72
paul@446 73
    def no_page(self):
paul@446 74
        self.status(404, "Not Found")
paul@446 75
        self.new_page(title="Not Found")
paul@446 76
        self.page.p("No page is provided at the given address.")
paul@446 77
paul@446 78
    def redirect(self, url):
paul@446 79
        self.status(302, "Redirect")
paul@446 80
        self.header("Location", url)
paul@446 81
        self.new_page(title="Redirect")
paul@446 82
        self.page.p("Redirecting to: %s" % url)
paul@446 83
paul@875 84
    def link_to(self, uid=None, recurrenceid=None, args=None):
paul@755 85
paul@755 86
        """
paul@875 87
        Return a link to a resource, being an object with any given 'uid' and
paul@875 88
        'recurrenceid', or the main resource otherwise.
paul@875 89
paul@763 90
        See get_identifiers for the decoding of such links.
paul@777 91
paul@777 92
        If 'args' is specified, the given dictionary is encoded and included.
paul@755 93
        """
paul@755 94
paul@875 95
        path = []
paul@875 96
        if uid:
paul@875 97
            path.append(uid)
paul@446 98
        if recurrenceid:
paul@755 99
            path.append(recurrenceid)
paul@777 100
        return "%s%s" % (self.env.new_url("/".join(path)), args and ("?%s" % urlencode(args)) or "")
paul@446 101
paul@623 102
    # Access to objects.
paul@623 103
paul@755 104
    def get_identifiers(self, path_info):
paul@755 105
paul@755 106
        """
paul@755 107
        Return identifiers provided by 'path_info', potentially encoded by
paul@755 108
        'link_to'.
paul@755 109
        """
paul@755 110
paul@446 111
        parts = path_info.lstrip("/").split("/")
paul@755 112
paul@755 113
        # UID only.
paul@755 114
paul@446 115
        if len(parts) == 1:
paul@763 116
            return parts[0], None
paul@755 117
paul@763 118
        # UID and RECURRENCE-ID.
paul@755 119
paul@446 120
        else:
paul@763 121
            return parts[:2]
paul@446 122
paul@769 123
    def _get_object(self, uid, recurrenceid=None, section=None, username=None):
paul@769 124
        if self.objects.has_key((uid, recurrenceid, section, username)):
paul@769 125
            return self.objects[(uid, recurrenceid, section, username)]
paul@446 126
paul@769 127
        obj = self.objects[(uid, recurrenceid, section, username)] = self.get_stored_object(uid, recurrenceid, section, username)
paul@446 128
        return obj
paul@446 129
paul@446 130
    def _get_recurrences(self, uid):
paul@446 131
        return self.store.get_recurrences(self.user, uid)
paul@446 132
paul@694 133
    def _get_active_recurrences(self, uid):
paul@694 134
        return self.store.get_active_recurrences(self.user, uid)
paul@694 135
paul@446 136
    def _get_requests(self):
paul@446 137
        if self.requests is None:
paul@699 138
            self.requests = self.store.get_requests(self.user)
paul@446 139
        return self.requests
paul@446 140
paul@755 141
    def _have_request(self, uid, recurrenceid=None, type=None, strict=False):
paul@755 142
        return self.store.have_request(self._get_requests(), uid, recurrenceid, type, strict)
paul@751 143
paul@818 144
    def _is_request(self):
paul@818 145
        return self._have_request(self.uid, self.recurrenceid)
paul@818 146
paul@766 147
    def _get_counters(self, uid, recurrenceid=None):
paul@766 148
        return self.store.get_counters(self.user, uid, recurrenceid)
paul@766 149
paul@446 150
    def _get_request_summary(self):
paul@630 151
paul@630 152
        "Return a list of periods comprising the request summary."
paul@630 153
paul@446 154
        summary = []
paul@630 155
paul@751 156
        for uid, recurrenceid, request_type in self._get_requests():
paul@769 157
paul@769 158
            # Obtain either normal objects or counter-proposals.
paul@769 159
paul@769 160
            if not request_type:
paul@769 161
                objs = [self._get_object(uid, recurrenceid)]
paul@769 162
            elif request_type == "COUNTER":
paul@769 163
                objs = []
paul@769 164
                for attendee in self.store.get_counters(self.user, uid, recurrenceid):
paul@769 165
                    objs.append(self._get_object(uid, recurrenceid, "counters", attendee))
paul@446 166
paul@769 167
            # For each object, obtain the periods involved.
paul@769 168
paul@769 169
            for obj in objs:
paul@769 170
                if obj:
paul@855 171
                    recurrenceids = self._get_recurrences(uid)
paul@446 172
paul@769 173
                    # Obtain only active periods, not those replaced by redefined
paul@769 174
                    # recurrences, converting to free/busy periods.
paul@769 175
paul@769 176
                    for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()):
paul@769 177
                        summary.append(obj.get_freebusy_period(p))
paul@446 178
paul@446 179
        return summary
paul@446 180
paul@446 181
    # Preference methods.
paul@446 182
paul@446 183
    def get_user_locale(self):
paul@446 184
        if not self.locale:
paul@1029 185
            self.locale = self.get_preferences().get("LANG", "en", True) or "en"
paul@446 186
        return self.locale
paul@446 187
paul@446 188
    # Prettyprinting of dates and times.
paul@446 189
paul@446 190
    def format_date(self, dt, format):
paul@446 191
        return self._format_datetime(babel.dates.format_date, dt, format)
paul@446 192
paul@446 193
    def format_time(self, dt, format):
paul@446 194
        return self._format_datetime(babel.dates.format_time, dt, format)
paul@446 195
paul@446 196
    def format_datetime(self, dt, format):
paul@446 197
        return self._format_datetime(
paul@446 198
            isinstance(dt, datetime) and babel.dates.format_datetime or babel.dates.format_date,
paul@446 199
            dt, format)
paul@446 200
paul@446 201
    def _format_datetime(self, fn, dt, format):
paul@446 202
        return fn(dt, format=format, locale=self.get_user_locale())
paul@446 203
paul@756 204
class ResourceClient(Resource, Client):
paul@446 205
paul@756 206
    "A Web application resource and calendar client."
paul@446 207
paul@756 208
    def __init__(self, resource=None):
paul@756 209
        Resource.__init__(self, resource)
paul@756 210
        user = self.env.get_user()
paul@756 211
        Client.__init__(self, user and get_uri(user) or None)
paul@446 212
paul@756 213
class ResourceClientForObject(Resource, ClientForObject):
paul@447 214
paul@756 215
    "A Web application resource and calendar client for a specific object."
paul@672 216
paul@807 217
    def __init__(self, resource=None, messenger=None):
paul@756 218
        Resource.__init__(self, resource)
paul@756 219
        user = self.env.get_user()
paul@807 220
        ClientForObject.__init__(self, None, user and get_uri(user) or None, messenger)
paul@807 221
paul@764 222
class FormUtilities:
paul@764 223
paul@765 224
    "Utility methods resource mix-in."
paul@764 225
paul@813 226
    def prefixed_args(self, prefix, convert=None):
paul@813 227
paul@813 228
        """
paul@813 229
        Return values for all arguments having the given 'prefix' in their
paul@813 230
        names, removing the prefix to obtain each value from the argument name
paul@813 231
        itself. The 'convert' callable can be specified to perform a conversion
paul@813 232
        (to int, for example).
paul@813 233
        """
paul@813 234
paul@813 235
        args = self.env.get_args()
paul@813 236
paul@813 237
        values = []
paul@813 238
        for name in args.keys():
paul@813 239
            if name.startswith(prefix):
paul@813 240
                value = name[len(prefix):]
paul@813 241
                if convert:
paul@813 242
                    try:
paul@813 243
                        value = convert(value)
paul@813 244
                    except ValueError:
paul@813 245
                        pass
paul@813 246
                values.append(value)
paul@813 247
        return values
paul@813 248
paul@764 249
    def control(self, name, type, value, selected=False, **kw):
paul@764 250
paul@764 251
        """
paul@764 252
        Show a control with the given 'name', 'type' and 'value', with
paul@764 253
        'selected' indicating whether it should be selected (checked or
paul@764 254
        equivalent), and with keyword arguments setting other properties.
paul@764 255
        """
paul@764 256
paul@764 257
        page = self.page
paul@779 258
        if type in ("checkbox", "radio") and selected:
paul@852 259
            page.input(name=name, type=type, value=value, checked="checked", **kw)
paul@764 260
        else:
paul@764 261
            page.input(name=name, type=type, value=value, **kw)
paul@764 262
paul@923 263
    def menu(self, name, default, items, values=None, class_="", index=None):
paul@764 264
paul@764 265
        """
paul@764 266
        Show a select menu having the given 'name', set to the given 'default',
paul@923 267
        providing the given (value, label) 'items', selecting the given 'values'
paul@923 268
        (or using the request parameters if not specified), and employing the
paul@923 269
        given CSS 'class_' if specified.
paul@764 270
        """
paul@764 271
paul@764 272
        page = self.page
paul@923 273
        values = values or self.env.get_args().get(name, [default])
paul@764 274
        if index is not None:
paul@764 275
            values = values[index:]
paul@764 276
            values = values and values[0:1] or [default]
paul@764 277
paul@764 278
        page.select(name=name, class_=class_)
paul@764 279
        for v, label in items:
paul@764 280
            if v is None:
paul@764 281
                continue
paul@764 282
            if v in values:
paul@764 283
                page.option(label, value=v, selected="selected")
paul@764 284
            else:
paul@764 285
                page.option(label, value=v)
paul@764 286
        page.select.close()
paul@764 287
paul@764 288
    def date_controls(self, name, default, index=None, show_tzid=True, read_only=False):
paul@764 289
paul@764 290
        """
paul@764 291
        Show date controls for a field with the given 'name' and 'default' form
paul@764 292
        date value.
paul@764 293
paul@764 294
        If 'index' is specified, default field values will be overridden by the
paul@764 295
        element from a collection of existing form values with the specified
paul@764 296
        index; otherwise, field values will be overridden by a single form
paul@764 297
        value.
paul@764 298
paul@764 299
        If 'show_tzid' is set to a false value, the time zone menu will not be
paul@764 300
        provided.
paul@764 301
paul@764 302
        If 'read_only' is set to a true value, the controls will be hidden and
paul@764 303
        labels will be employed instead.
paul@764 304
        """
paul@764 305
paul@764 306
        page = self.page
paul@764 307
paul@764 308
        # Show dates for up to one week around the current date.
paul@764 309
paul@886 310
        page.span(class_="date enabled")
paul@886 311
paul@764 312
        dt = default.as_datetime()
paul@764 313
        if not dt:
paul@764 314
            dt = date.today()
paul@764 315
paul@764 316
        base = to_date(dt)
paul@764 317
paul@764 318
        # Show a date label with a hidden field if read-only.
paul@764 319
paul@764 320
        if read_only:
paul@764 321
            self.control("%s-date" % name, "hidden", format_datetime(base))
paul@764 322
            page.span(self.format_date(base, "long"))
paul@764 323
paul@764 324
        # Show dates for up to one week around the current date.
paul@764 325
        # NOTE: Support paging to other dates.
paul@764 326
paul@764 327
        else:
paul@764 328
            items = []
paul@764 329
            for i in range(-7, 8):
paul@764 330
                d = base + timedelta(i)
paul@764 331
                items.append((format_datetime(d), self.format_date(d, "full")))
paul@764 332
            self.menu("%s-date" % name, format_datetime(base), items, index=index)
paul@764 333
paul@886 334
        page.span.close()
paul@886 335
paul@764 336
        # Show time details.
paul@764 337
paul@764 338
        page.span(class_="time enabled")
paul@764 339
paul@764 340
        if read_only:
paul@764 341
            page.span("%s:%s:%s" % (default.get_hour(), default.get_minute(), default.get_second()))
paul@764 342
            self.control("%s-hour" % name, "hidden", default.get_hour())
paul@764 343
            self.control("%s-minute" % name, "hidden", default.get_minute())
paul@764 344
            self.control("%s-second" % name, "hidden", default.get_second())
paul@764 345
        else:
paul@764 346
            self.control("%s-hour" % name, "text", default.get_hour(), maxlength=2, size=2)
paul@764 347
            page.add(":")
paul@764 348
            self.control("%s-minute" % name, "text", default.get_minute(), maxlength=2, size=2)
paul@764 349
            page.add(":")
paul@764 350
            self.control("%s-second" % name, "text", default.get_second(), maxlength=2, size=2)
paul@764 351
paul@764 352
        # Show time zone details.
paul@764 353
paul@764 354
        if show_tzid:
paul@764 355
            page.add(" ")
paul@764 356
            tzid = default.get_tzid() or self.get_tzid()
paul@764 357
paul@764 358
            # Show a label if read-only or a menu otherwise.
paul@764 359
paul@764 360
            if read_only:
paul@764 361
                self.control("%s-tzid" % name, "hidden", tzid)
paul@764 362
                page.span(tzid)
paul@764 363
            else:
paul@764 364
                self.timezone_menu("%s-tzid" % name, tzid, index)
paul@764 365
paul@764 366
        page.span.close()
paul@764 367
paul@764 368
    def timezone_menu(self, name, default, index=None):
paul@764 369
paul@764 370
        """
paul@764 371
        Show timezone controls using a menu with the given 'name', set to the
paul@764 372
        given 'default' unless a field of the given 'name' provides a value.
paul@764 373
        """
paul@764 374
paul@764 375
        entries = [(tzid, tzid) for tzid in pytz.all_timezones]
paul@764 376
        self.menu(name, default, entries, index=index)
paul@764 377
paul@765 378
class DateTimeFormUtilities:
paul@765 379
paul@765 380
    "Date/time control methods resource mix-in."
paul@765 381
paul@776 382
    # Control naming helpers.
paul@776 383
paul@776 384
    def element_identifier(self, name, index=None):
paul@776 385
        return index is not None and "%s-%d" % (name, index) or name
paul@776 386
paul@776 387
    def element_name(self, name, suffix, index=None):
paul@776 388
        return index is not None and "%s-%s" % (name, suffix) or name
paul@776 389
paul@776 390
    def element_enable(self, index=None):
paul@776 391
        return index is not None and str(index) or "enable"
paul@776 392
paul@765 393
    def show_object_datetime_controls(self, period, index=None):
paul@765 394
paul@765 395
        """
paul@765 396
        Show datetime-related controls if already active or if an object needs
paul@765 397
        them for the given 'period'. The given 'index' is used to parameterise
paul@765 398
        individual controls for dynamic manipulation.
paul@765 399
        """
paul@765 400
paul@765 401
        p = form_period_from_period(period)
paul@765 402
paul@765 403
        page = self.page
paul@765 404
        args = self.env.get_args()
paul@765 405
        _id = self.element_identifier
paul@765 406
        _name = self.element_name
paul@765 407
        _enable = self.element_enable
paul@765 408
paul@765 409
        # Add a dynamic stylesheet to permit the controls to modify the display.
paul@765 410
        # NOTE: The style details need to be coordinated with the static
paul@765 411
        # NOTE: stylesheet.
paul@765 412
paul@765 413
        if index is not None:
paul@765 414
            page.style(type="text/css")
paul@765 415
paul@765 416
            # Unlike the rules for object properties, these affect recurrence
paul@765 417
            # properties.
paul@765 418
paul@765 419
            page.add("""\
paul@765 420
input#dttimes-enable-%(index)d,
paul@765 421
input#dtend-enable-%(index)d,
paul@765 422
input#dttimes-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue .time.enabled,
paul@765 423
input#dttimes-enable-%(index)d:checked ~ .recurrence td.objectvalue .time.disabled,
paul@765 424
input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .dt.enabled,
paul@765 425
input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .dt.disabled {
paul@765 426
    display: none;
paul@886 427
}
paul@886 428
paul@886 429
input#dtend-enable-%(index)d:not(:checked) ~ .recurrence td.objectvalue.dtend .date.enabled,
paul@886 430
input#dtend-enable-%(index)d:checked ~ .recurrence td.objectvalue.dtend .date.disabled {
paul@886 431
    visibility: hidden;
paul@765 432
}""" % {"index" : index})
paul@765 433
paul@765 434
            page.style.close()
paul@765 435
paul@765 436
        self.control(
paul@765 437
            _name("dtend-control", "recur", index), "checkbox",
paul@765 438
            _enable(index), p.end_enabled,
paul@765 439
            id=_id("dtend-enable", index)
paul@765 440
            )
paul@765 441
paul@765 442
        self.control(
paul@765 443
            _name("dttimes-control", "recur", index), "checkbox",
paul@765 444
            _enable(index), p.times_enabled,
paul@765 445
            id=_id("dttimes-enable", index)
paul@765 446
            )
paul@765 447
paul@765 448
    def show_datetime_controls(self, formdate, show_start):
paul@765 449
paul@765 450
        """
paul@765 451
        Show datetime details from the current object for the 'formdate',
paul@765 452
        showing start details if 'show_start' is set to a true value. Details
paul@765 453
        will appear as controls for organisers and labels for attendees.
paul@765 454
        """
paul@765 455
paul@765 456
        page = self.page
paul@765 457
paul@867 458
        # Show controls for editing.
paul@765 459
paul@867 460
        page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
paul@765 461
paul@867 462
        if show_start:
paul@867 463
            page.div(class_="dt enabled")
paul@867 464
            self.date_controls("dtstart", formdate)
paul@867 465
            page.br()
paul@867 466
            page.label("Specify times", for_="dttimes-enable", class_="time disabled enable")
paul@867 467
            page.label("Specify dates only", for_="dttimes-enable", class_="time enabled disable")
paul@867 468
            page.div.close()
paul@765 469
paul@765 470
        else:
paul@886 471
            self.date_controls("dtend", formdate)
paul@867 472
            page.div(class_="dt disabled")
paul@867 473
            page.label("Specify end date", for_="dtend-enable", class_="enable")
paul@867 474
            page.div.close()
paul@867 475
            page.div(class_="dt enabled")
paul@867 476
            page.label("End on same day", for_="dtend-enable", class_="disable")
paul@867 477
            page.div.close()
paul@867 478
paul@867 479
        page.td.close()
paul@765 480
paul@868 481
    def show_recurrence_controls(self, index, period, recurrenceid, show_start):
paul@765 482
paul@765 483
        """
paul@765 484
        Show datetime details from the current object for the recurrence having
paul@765 485
        the given 'index', with the recurrence period described by 'period',
paul@765 486
        indicating a start, end and origin of the period from the event details,
paul@868 487
        employing any 'recurrenceid' for the object to configure the displayed
paul@868 488
        information.
paul@765 489
paul@765 490
        If 'show_start' is set to a true value, the start details will be shown;
paul@765 491
        otherwise, the end details will be shown.
paul@765 492
        """
paul@765 493
paul@765 494
        page = self.page
paul@765 495
        _id = self.element_identifier
paul@765 496
        _name = self.element_name
paul@765 497
paul@845 498
        period = form_period_from_period(period)
paul@765 499
paul@867 500
        # Show controls for editing.
paul@765 501
paul@868 502
        if not period.replaced:
paul@868 503
            page.td(class_="objectvalue dt%s" % (show_start and "start" or "end"))
paul@765 504
paul@765 505
            read_only = period.origin == "RRULE"
paul@765 506
paul@765 507
            if show_start:
paul@765 508
                page.div(class_="dt enabled")
paul@845 509
                self.date_controls(_name("dtstart", "recur", index), period.get_form_start(), index=index, read_only=read_only)
paul@765 510
                if not read_only:
paul@765 511
                    page.br()
paul@765 512
                    page.label("Specify times", for_=_id("dttimes-enable", index), class_="time disabled enable")
paul@765 513
                    page.label("Specify dates only", for_=_id("dttimes-enable", index), class_="time enabled disable")
paul@765 514
                page.div.close()
paul@765 515
paul@765 516
                # Put the origin somewhere.
paul@765 517
paul@845 518
                self.control("recur-origin", "hidden", period.origin or "")
paul@868 519
                self.control("recur-replaced", "hidden", period.replaced and str(index) or "")
paul@765 520
paul@765 521
            else:
paul@845 522
                self.date_controls(_name("dtend", "recur", index), period.get_form_end(), index=index, show_tzid=False, read_only=read_only)
paul@765 523
                if not read_only:
paul@886 524
                    page.div(class_="dt disabled")
paul@886 525
                    page.label("Specify end date", for_=_id("dtend-enable", index), class_="enable")
paul@886 526
                    page.div.close()
paul@886 527
                    page.div(class_="dt enabled")
paul@765 528
                    page.label("End on same day", for_=_id("dtend-enable", index), class_="disable")
paul@886 529
                    page.div.close()
paul@765 530
paul@765 531
            page.td.close()
paul@765 532
paul@765 533
        # Show label as attendee.
paul@765 534
paul@765 535
        else:
paul@868 536
            self.show_recurrence_label(index, period, recurrenceid, show_start)
paul@765 537
paul@868 538
    def show_recurrence_label(self, index, period, recurrenceid, show_start):
paul@765 539
paul@765 540
        """
paul@852 541
        Show datetime details from the current object for the recurrence having
paul@852 542
        the given 'index', for the given recurrence 'period', employing any
paul@868 543
        'recurrenceid' for the object to configure the displayed information.
paul@765 544
paul@765 545
        If 'show_start' is set to a true value, the start details will be shown;
paul@765 546
        otherwise, the end details will be shown.
paul@765 547
        """
paul@765 548
paul@765 549
        page = self.page
paul@852 550
        _name = self.element_name
paul@765 551
paul@845 552
        try:
paul@845 553
            p = event_period_from_period(period)
paul@845 554
        except PeriodError, exc:
paul@845 555
            affected = False
paul@845 556
        else:
paul@845 557
            affected = p.is_affected(recurrenceid)
paul@845 558
paul@845 559
        period = form_period_from_period(period)
paul@765 560
paul@765 561
        css = " ".join([
paul@868 562
            period.replaced and "replaced" or "",
paul@845 563
            affected and "affected" or ""
paul@765 564
            ])
paul@765 565
paul@845 566
        formdate = show_start and period.get_form_start() or period.get_form_end()
paul@765 567
        dt = formdate.as_datetime()
paul@765 568
        if dt:
paul@852 569
            page.td(class_=css)
paul@852 570
            if show_start:
paul@852 571
                self.date_controls(_name("dtstart", "recur", index), period.get_form_start(), index=index, read_only=True)
paul@852 572
                self.control("recur-origin", "hidden", period.origin or "")
paul@868 573
                self.control("recur-replaced", "hidden", period.replaced and str(index) or "")
paul@852 574
            else:
paul@852 575
                self.date_controls(_name("dtend", "recur", index), period.get_form_end(), index=index, show_tzid=False, read_only=True)
paul@852 576
            page.td.close()
paul@765 577
        else:
paul@765 578
            page.td("(Unrecognised date)")
paul@765 579
paul@765 580
    def get_date_control_values(self, name, multiple=False, tzid_name=None):
paul@765 581
paul@765 582
        """
paul@787 583
        Return a form date object representing fields starting with 'name'. If
paul@787 584
        'multiple' is set to a true value, many date objects will be returned
paul@787 585
        corresponding to a collection of datetimes.
paul@787 586
paul@787 587
        If 'tzid_name' is specified, the time zone information will be acquired
paul@787 588
        from fields starting with 'tzid_name' instead of 'name'.
paul@765 589
        """
paul@765 590
paul@765 591
        args = self.env.get_args()
paul@765 592
paul@765 593
        dates = args.get("%s-date" % name, [])
paul@765 594
        hours = args.get("%s-hour" % name, [])
paul@765 595
        minutes = args.get("%s-minute" % name, [])
paul@765 596
        seconds = args.get("%s-second" % name, [])
paul@765 597
        tzids = args.get("%s-tzid" % (tzid_name or name), [])
paul@765 598
paul@765 599
        # Handle absent values by employing None values.
paul@765 600
paul@765 601
        field_values = map(None, dates, hours, minutes, seconds, tzids)
paul@765 602
paul@765 603
        if not field_values and not multiple:
paul@765 604
            all_values = FormDate()
paul@765 605
        else:
paul@765 606
            all_values = []
paul@765 607
            for date, hour, minute, second, tzid in field_values:
paul@765 608
                value = FormDate(date, hour, minute, second, tzid or self.get_tzid())
paul@765 609
paul@765 610
                # Return a single value or append to a collection of all values.
paul@765 611
paul@765 612
                if not multiple:
paul@765 613
                    return value
paul@765 614
                else:
paul@765 615
                    all_values.append(value)
paul@765 616
paul@765 617
        return all_values
paul@765 618
paul@787 619
    def set_date_control_values(self, name, formdates, tzid_name=None):
paul@787 620
paul@787 621
        """
paul@787 622
        Replace form fields starting with 'name' using the values of the given
paul@787 623
        'formdates'.
paul@787 624
paul@787 625
        If 'tzid_name' is specified, the time zone information will be stored in
paul@787 626
        fields starting with 'tzid_name' instead of 'name'.
paul@787 627
        """
paul@787 628
paul@787 629
        args = self.env.get_args()
paul@787 630
paul@787 631
        args["%s-date" % name] = [d.date for d in formdates]
paul@787 632
        args["%s-hour" % name] = [d.hour for d in formdates]
paul@787 633
        args["%s-minute" % name] = [d.minute for d in formdates]
paul@787 634
        args["%s-second" % name] = [d.second for d in formdates]
paul@787 635
        args["%s-tzid" % (tzid_name or name)] = [d.tzid for d in formdates]
paul@787 636
paul@446 637
# vim: tabstop=4 expandtab shiftwidth=4