Source code for uw.web.html.form

"""Form generation routines.

Simple routines for generating HTML form elements and parsing the corresponding
form submission results.
"""

from datetime import date, time, datetime, timedelta

from ll.xist.ns import html

from uw.web.html.format import format_date, format_time

[docs]def render_select (name, values, value=None, blank=False, class_=None): """Make a select element with a given name and given option values. :param name: the form control name to use. :param values: the values to associate with the form control, expressed as a sequence of tuples (code, label), where the code is the value used to report the form value while the label is what will be displayed to the user. :param value: the code of the alternative that should be selected by default, or None to indicate that an extra blank option should be selected instead. :param blank: whether the extra blank option should always be included. :param class_: a string to be applied as a css class to the select element. """ options = [] if value is None or blank: options.append (html.option (selected=value is None or None)) for code, label in values: options.append ( html.option (label, value=code, selected=(code == value) or None ) ) return html.select (options, name=name, class_=class_)
[docs]def render_multi_select (name, form_values, values={}, blank=False, class_=None): """Make a multi select element with a given name and given option values. :param name: the form control name to use. :param values: the values to associate with the form control, expressed as a sequence of tuples (code, label), where the code is the value used to report the form value while the label is what will be displayed to the user. :param values: the code(s) of the alternative that should be selected by as a list, or None to indicate that an extra blank option should be selected instead. :param blank: whether the extra blank option should always be included. :param multiple: whether the select should have multiple selectable options :param class_: a string to be applied as a css class to the select element. """ options = [] if values is None or blank: options.append (html.option (selected=values is None or None)) for code, label in form_values: options.append ( html.option (label, value=code, selected = code in values or None ) ) return html.select (options, name=name, class_=class_, multiple=True)
[docs]def render_checkbox (name, checked=False, value="", fixed=False, class_=None): """Return an HTML <input> which is a checkbox. :param name: the form control name to use. :param checked: whether the checkbox should be checked. :param value: the value to associate with the form control. :param fixed: whether the control should be fixed and uneditable. :param class_: a css class to be applied to the checkbox If fixed is specified, the result is a disabled checkbox <input> together with a hidden <input> representing the name/value pair. If fixed is not specified, the result is a regular checkbox <input> with the name/value pair set directly. """ return render_check_radio (name, checked=checked, value=value, fixed=fixed, type="checkbox", class_=class_)
[docs]def render_radio (name, checked=False, value="", fixed=False): """Return an HTML <input> which is a radio button. :param name: the form control name to use. :param checked: whether the radio button should be checked. :param value: the value to associate with the form control. :param fixed: whether the control should be fixed and uneditable. If fixed is specified, the result is a disabled radio button <input> together with a hidden <input> representing the name/value pair. If fixed is not specified, the result is a regular radio button <input> with the name/value pair set directly. """ return render_check_radio (name, checked=checked, value=value, fixed=fixed, type="radio")
[docs]def render_check_radio (name, checked=False, value="", fixed=False, type=None, class_=None): """Return an HTML <input> which is a checkbox or radio button. :param name: the form control name to use. :param checked: whether the radio button should be checked. :param value: the value to associate with the form control. :param fixed: whether the control should be fixed and uneditable. :param type: either "radio" or "checkbox" as appropriate. :param class_: CSS class to be applied to input If fixed is specified, the result is a disabled form control <input> together with a hidden <input> representing the name/value pair. If fixed is not specified, the result is a regular form control <input> with the name/value pair set directly. HTML implementation note: we tried to use Unicode characters, as follows: if type == "checkbox": fixed_text = [u'◻', u'◼'] elif type == "radio": fixed_text = [u'⚪', u'⚫'] Unfortunately, apparently not supported by all browsers. Disabled controls cannot be successful, so still need to render chosen button or checkbox as hidden. """ return html.span ( html.input (type=type, name=name, value=value, checked=checked or None, disabled=fixed or None, class_=class_ or None), render_hidden (name, value) if fixed and checked else None, class_="uw-web-html-form-input" )
[docs]def render_hidden (name, value): """Return an HTML <input> which is a hidden control. :param name: the form control name to use. :param value: the value to associate with the form control. """ return html.input (type="hidden", name=name, value=value)
[docs]def render_form_value_hidden (form, name): """Return hidden controls equivalent to a form result. :param uw.web.wsgi.form.CGIFormResults form: form results. :param name: the form result name. Pulls all the values from the specified form results associated with the given form control name, and produces a list of HTML form hidden controls corresponding to the values. """ return [render_hidden (name, value) for value in form.multiple_field_value (name)]
[docs]def advance_month (day): """Add one month to the given date. :param datetime.date day: a date. Returns the date one month later. """ if day.month == 12: return day.replace (year=day.year+1, month=1) else: return day.replace (month=day.month+1)
[docs]def monthrange (start, months): """Iterate through months, represented as date objects. :param start: the starting date. :param months: the number of months to span. The first result is start with the day forced to 1. Subsequent results correspond to the 1st of each succeeding month, until months months later. Note that the actual number of months returned will be one greater if the start day is not 1. The idea is to yield all months that include days within the specified number of months of the start date. """ if (start.day != 1): start = start.replace (day=1) months += 1 for i in range (months): yield start start = advance_month (start)
[docs]def render_date_selector_2 (name, current=None, start=None, months=12, class_=None): """Return form controls for choosing a date with two pull-down menus. :param name: the form control name to use. :param current: the initial value of the form control. :param start: the earliest selectable date. :param months: the number of months to include. Returns two HTML pulldown menu controls, one for year and month, the other for the day within the month. This is intended for selecting a date within a range of a few months, where it would not make sense to have a separate year selector that only allowed one or two choices. """ if start is None: start = date.today () yearselect = render_select ('%sym' % name, [('%04i%02i' % (ym.year, ym.month), '%04i-%02i' % (ym.year, ym.month)) for ym in monthrange (start, months)], None if current is None else '%04i%02i' % (current.year, current.month), class_=class_) dayselect = render_select ('%sd' % name, [('%02i' % i, '%02i' %i) for i in range (1, 32)], None if current is None else '%02i' % current.day, class_=class_) return [yearselect, '-', dayselect]
[docs]def parse_date_2 (form, name): """Parse form results from a two-menu date selector. :param uw.web.wsgi.form.CGIFormResults form: form results. :param name: the form result name prefix. Returns the date specified by the form results. The form result names used begin with the specified name, with "ym" and "d" appended. This is compatible with render_date_selector_2 and is meant to be used with that function. """ monthval = form.optional_field_value ('%sym' % name) dayval = form.optional_field_value ('%sd' % name) if monthval is None or dayval is None: return None try: return date (int (monthval[0:4]), int (monthval[4:6]), int (dayval)) except ValueError: return None
[docs]def render_date_hidden_1 (name, current=None): """Render a date as an HTML hidden control. :param name: the form control name to use. :param current: the date to use. Returns an HTML hidden form control representing the specified date. """ if current is None: return None return html.input (type="hidden", name=name, value=current.strftime ('%Y%m%d'))
[docs]def render_date_selector_1 (name, current=None, start=None, days=None, dates=None, class_=None): """Return form controls for choosing a date with a single pull-down menu. :param str name: The form control name to use. :param datetime.date current: The initial value of the control. :param datetime.date start: The earliest selectable date, or None for today (if dates is None). :param int days: The number of days to include (if dates is None). :param list dates: The dates to include (or None to use start and days). :param str class_: CSS class to apply to result. """ if current is not None: current = current.strftime ('%Y%m%d') if start is None: start = date.today () if dates is None: dates = (start + timedelta (days=i) for i in range (days)) dates = [(d.strftime ('%Y%m%d'), format_date (d)) for d in dates] return render_select (name, dates, current, class_=class_)
[docs]def parse_date_1 (form, name): """Parse form results from a one-menu date selector. :param uw.web.wsgi.form.CGIFormResults form: form results. :param str name: the form result name to use. Returns the date specified by the form results. This is meant to be used with :func:`render_date_hidden_1` or :func:`render_date_selector_1`. """ try: field_value = form.optional_field_value (name) if field_value is None: return None return datetime.strptime (field_value, '%Y%m%d').date () except ValueError: return None
[docs]def render_time_selector_1 (name, current=None, times=None, class_=None): """Return form controls for choosing a time with a single pull-down menu. :param str name: The form control name to use. :param datetime.time current: The initial value of the control. :param list times: List of times to allow choosing. :param str class_: CSS class to apply to result. """ if current is not None: current = current.strftime ('%H%M') times = [(d.strftime ('%H%M'), format_time (d)) for d in times] return render_select (name, times, current, class_=class_)
[docs]def parse_time_1 (form, name): """Parse form results from a one-menu time selector. :param uw.web.wsgi.form.CGIFormResults form: form results. :param str name: the form result name to use. Returns the time specified by the form results. This is meant to be used with :func:`render_time_selector_1`. """ try: field_value = form.optional_field_value (name) if field_value is None: return None return datetime.strptime (field_value, '%H%M').time () except ValueError: return None
[docs]def render_time_selector_2 (name, start, hours, current=None, class_=None): """Return form controls for choosing a time with two pull-down menus. :param name: the form control name to use. :param start: the earliest selectable time. :param hours: the number of hours to include. :param current: the initial value of the form controls. Returns two HTML pulldown menu controls, one for hour, the other for the minute within the hour. This is intended for selecting a time within a range of a few hours. """ options = [] for x in ('%02d' % x for x in range (start, start + hours)): if int(x) <= 12: options.append ((x,x)) else: options.append ((x, x + ' (' + str(int(x) - 12) + ' PM)')) hourselect = render_select ('%sh' % name, options, None if current is None else '%02d' % current.hour, class_=class_) minuteselect = render_select ('%sm' % name, [(x, x) for x in ('%02d' % x for x in range (0, 60, 5))], None if current is None else '%02d' % current.minute, class_=class_) return [hourselect, ':', minuteselect]
[docs]def parse_time_2 (form, name): """Parse form results from a two-menu time selector. :param uw.web.wsgi.form.CGIFormResults form: form results. :param name: the form result name prefix. Returns the time specified by the form results. The form result names used begin with the specified name, with "h" and "m" appended. This is compatible with render_time_selector_2 and is meant to be used with that function. """ hourval = form.optional_field_value ('%sh' % name) minuteval = form.optional_field_value ('%sm' % name) if hourval is None or minuteval is None: return None try: return time (int (hourval), int (minuteval)) except ValueError: return None
[docs]def parse_datetime (form, name, parse_date, parse_time): """Parse form results for a combined date and time. :param uw.web.wsgi.form.CGIFormResults form: form results. :param name: the form result name prefix. :param parse_date: the parser to use for the date. :param parse_time: the parser to use for the time. Uses the specified parse_date and parse_time to parse the date and time, respectively, from the form results, using the specified form result name with "d" and "t" appended. The result is a datetime.datetime. """ dateval = parse_date (form, '%sd' % name) timeval = parse_time (form, '%st' % name) if dateval is None or timeval is None: return None else: return datetime.combine (dateval, timeval)