Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/deform/widget.py : 32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Widget."""
2# Standard Library
3import csv
4import json
5import random
7# Pyramid
8from colander import Invalid
9from colander import Mapping
10from colander import SchemaNode
11from colander import SchemaType
12from colander import Sequence
13from colander import String
14from colander import null
15from iso8601.iso8601 import ISO8601_REGEX
16from translationstring import TranslationString
18from .compat import StringIO
19from .compat import sequence_types
20from .compat import string
21from .compat import string_types
22from .compat import text_
23from .compat import text_type
24from .compat import uppercase
25from .compat import url_quote
26from .i18n import _
29_BLANK = text_("")
32def _normalize_choices(values):
33 result = []
34 for item in values:
35 if isinstance(item, OptGroup):
36 normalized_options = _normalize_choices(item.options)
37 result.append(OptGroup(item.label, *normalized_options))
38 else:
39 value, description = item
40 if not isinstance(value, string_types):
41 value = str(value)
42 result.append((value, description))
43 return result
46class _PossiblyEmptyString(String):
47 def deserialize(self, node, cstruct):
48 if cstruct == "":
49 return _BLANK # String.deserialize returns null
50 return super(_PossiblyEmptyString, self).deserialize(node, cstruct)
53class _StrippedString(_PossiblyEmptyString):
54 def deserialize(self, node, cstruct):
55 appstruct = super(_StrippedString, self).deserialize(node, cstruct)
56 if isinstance(appstruct, string_types):
57 appstruct = appstruct.strip()
58 return appstruct
61class _FieldStorage(SchemaType):
62 def deserialize(self, node, cstruct):
63 if cstruct in (null, None, b""):
64 return null
65 # weak attempt at duck-typing
66 if not hasattr(cstruct, "file"):
67 raise Invalid(node, "%s is not a FieldStorage instance" % cstruct)
68 return cstruct
71_sequence_of_strings = SchemaNode(
72 Sequence(), SchemaNode(_PossiblyEmptyString())
73)
76class Widget(object):
77 """
78 A widget is the building block for rendering logic. The
79 :class:`deform.widget.Widget` class is never instantiated
80 directly: it is the abstract class from which all other widget
81 types within :mod:`deform.widget` derive. It should likely also
82 be subclassed by application-developer-defined widgets.
84 A widget instance is attached to a field during normal operation.
85 A widget is not meant to carry any state. Instead, widget
86 implementations should use the ``field`` object passed to them
87 during :meth:`deform.widget.Widget.serialize` and
88 :meth:`deform.widget.Widget.deserialize` as a scratchpad for state
89 information.
91 All widgets have the following attributes:
93 hidden
94 An attribute indicating the hidden state of this widget. The
95 default is ``False``. If this attribute is not ``False``, the
96 field associated with this widget will not be rendered in the
97 form (although, if the widget is a structural widget, its
98 children will be; ``hidden`` is not a recursive flag). No
99 label, no error message, nor any furniture such as a close
100 button when the widget is one of a sequence will exist for the
101 field in the rendered form.
103 readonly
104 If this attribute is true, the readonly rendering of the widget
105 should be output during HTML serialization.
107 category
108 A string value indicating the *category* of this widget. This
109 attribute exists to inform structural widget rendering
110 behavior. For example, when a text widget or another simple
111 'leaf-level' widget is rendered as a child of a mapping widget
112 using the default template mapping template, the field title
113 associated with the child widget will be rendered above the
114 field as a label by default. This is because simple child
115 widgets are in the ``default`` category and no special action
116 is taken when a structural widget renders child widgets that
117 are in the ``default`` category. However, if the default
118 mapping widget encounters a child widget with the category of
119 ``structural`` during rendering (the default mapping and
120 sequence widgets are in this category), it omits the title.
121 Default: ``default``
123 error_class
124 The name of the CSS class attached to various tags in the form
125 renderering indicating an error condition for the field
126 associated with this widget. Default: ``error``.
128 css_class
129 The name of the CSS class attached to various tags in
130 the form renderering specifying a new class for the field
131 associated with this widget. Default: ``None`` (no class).
133 item_css_class
134 The name of the CSS class attached to the li which surrounds the field
135 when it is rendered inside the mapping_item or sequence_item template.
137 style
138 A string that will be placed literally in a ``style`` attribute on
139 the primary input tag(s) related to the widget. For example,
140 'width:150px;'. Default: ``None``, meaning no style attribute will
141 be added to the input tag.
143 requirements
145 The requirements are specified as a sequence of either of the
146 following.
148 1. Two-tuples in the form ``(requirement_name, version_id)``.
149 The **logical** requirement name identifiers are resolved to
150 concrete files using the ``resource_registry``.
151 2. Dicts in the form ``{requirement_type:
152 requirement_location(s)}``. The ``resource_registry`` is
153 bypassed. This is useful for creating custom widgets with
154 their own resources.
156 Requirements specified as a sequence of two-tuples should be in the
157 form ``( (requirement_name, version_id), ...)`` indicating the logical
158 external requirements needed to make this widget render properly within
159 a form. The ``requirement_name`` is a string that *logically*
160 (not concretely, it is not a filename) identifies *one or
161 more* Javascript or CSS resources that must be included in the
162 page by the application performing the form rendering. The
163 requirement name string value should be interpreted as a
164 logical requirement name (e.g. ``jquery`` for JQuery,
165 'tinymce' for Tiny MCE). The ``version_id`` is a string
166 indicating the version number (or ``None`` if no particular
167 version is required). For example, a rich text widget might
168 declare ``requirements = (('tinymce', '3.3.8'),)``.
170 Requirements specified as a sequence of dicts should be in the form
171 ``({requirement_type: requirement_location(s)}, ...)``. The
172 ``requirement_type`` key must be either ``js`` or ``css``. The
173 ``requirement_location(s)`` value must be either a string or a list of
174 strings. Each string must resolve to a concrete resource. For example,
175 a widget might declare:
177 .. code-block:: python
179 requirements = (
180 {"js": "deform:static/tinymce/tinymce.min.js"},
181 {"css": "deform:static/tinymce/tinymce.min.css"},
182 )
184 See also:
185 :ref:`specifying_widget_requirements` and
186 :ref:`widget_requirements`.
188 Default: ``()`` (the empty tuple, meaning no special
189 requirements).
191 These attributes are also accepted as keyword arguments to all
192 widget constructors; if they are passed, they will override the
193 defaults.
195 Particular widget types also accept other keyword arguments that
196 get attached to the widget as attributes. These are documented as
197 'Attributes/Arguments' within the documentation of each concrete
198 widget implementation subclass.
199 """
201 hidden = False
202 readonly = False
203 category = "default"
204 error_class = "error"
205 css_class = None
206 item_css_class = None
207 style = None
208 requirements = ()
210 def __init__(self, **kw):
211 self.__dict__.update(kw)
213 def serialize(self, field, cstruct, **kw):
214 """
215 The ``serialize`` method of a widget must serialize a :term:`cstruct`
216 value to an HTML rendering. A :term:`cstruct` value is the value
217 which results from a :term:`Colander` schema serialization for the
218 schema node associated with this widget. ``serialize`` should return
219 the HTML rendering: the result of this method should always be a
220 string containing HTML. The ``field`` argument is the :term:`field`
221 object to which this widget is attached. The ``**kw`` argument
222 allows a caller to pass named arguments that might be used to
223 influence individual widget renderings.
224 """
225 raise NotImplementedError
227 def deserialize(self, field, pstruct):
228 """
229 The ``deserialize`` method of a widget must deserialize a
230 :term:`pstruct` value to a :term:`cstruct` value and return the
231 :term:`cstruct` value. The ``pstruct`` argument is a value resulting
232 from the ``parse`` method of the :term:`Peppercorn` package. The
233 ``field`` argument is the field object to which this widget is
234 attached.
235 """
236 raise NotImplementedError
238 def handle_error(self, field, error):
239 """
240 The ``handle_error`` method of a widget must:
242 - Set the ``error`` attribute of the ``field`` object it is
243 passed, if the ``error`` attribute has not already been set.
245 - Call the ``handle_error`` method of each subfield which also
246 has an error (as per the ``error`` argument's ``children``
247 attribute).
248 """
249 if field.error is None:
250 field.error = error
251 for e in error.children:
252 for num, subfield in enumerate(field.children):
253 if e.pos == num:
254 subfield.widget.handle_error(subfield, e)
256 def get_template_values(self, field, cstruct, kw):
257 values = {"cstruct": cstruct, "field": field}
258 values.update(kw)
259 values.pop("template", None)
260 return values
263class TextInputWidget(Widget):
264 """
265 Renders an ``<input type="text"/>`` widget.
267 **Attributes/Arguments**
269 template
270 The template name used to render the widget. Default:
271 ``textinput``.
273 readonly_template
274 The template name used to render the widget in read-only mode.
275 Default: ``readonly/textinput``.
277 strip
278 If true, during deserialization, strip the value of leading
279 and trailing whitespace (default ``True``).
281 mask
282 A :term:`jquery.maskedinput` input mask, as a string.
284 a - Represents an alpha character (A-Z,a-z)
285 9 - Represents a numeric character (0-9)
286 * - Represents an alphanumeric character (A-Z,a-z,0-9)
288 All other characters in the mask will be considered mask
289 literals.
291 Example masks:
293 Date: 99/99/9999
295 US Phone: (999) 999-9999
297 US SSN: 999-99-9999
299 When this option is used, the :term:`jquery.maskedinput`
300 library must be loaded into the page serving the form for the
301 mask argument to have any effect. See :ref:`masked_input`.
303 mask_placeholder
304 The placeholder for required nonliteral elements when a mask
305 is used. Default: ``_`` (underscore).
307 """
309 template = "textinput"
310 readonly_template = "readonly/textinput"
311 strip = True
312 mask = None
313 mask_placeholder = "_"
314 requirements = ()
316 def __init__(self, **kw):
317 super(TextInputWidget, self).__init__(**kw)
318 if getattr(self, "mask", False):
319 self.requirements = tuple(
320 list(self.requirements) + [("jquery.maskedinput", None)]
321 )
323 def serialize(self, field, cstruct, **kw):
324 if cstruct in (null, None):
325 cstruct = ""
326 readonly = kw.get("readonly", self.readonly)
327 template = readonly and self.readonly_template or self.template
328 values = self.get_template_values(field, cstruct, kw)
329 return field.renderer(template, **values)
331 def deserialize(self, field, pstruct):
332 if pstruct is null:
333 return null
334 elif not isinstance(pstruct, string_types):
335 raise Invalid(field.schema, "Pstruct is not a string")
336 if self.strip:
337 pstruct = pstruct.strip()
338 if not pstruct:
339 return null
340 return pstruct
343class MoneyInputWidget(Widget):
344 """
345 Renders an ``<input type="text"/>`` widget with Javascript which enforces
346 a valid currency input. It should be used along with the
347 ``colander.Decimal`` schema type (at least if you care about your money).
348 This widget depends on the ``jquery-maskMoney`` JQuery plugin.
350 **Attributes/Arguments**
352 template
353 The template name used to render the widget. Default:
354 ``moneyinput``.
356 readonly_template
357 The template name used to render the widget in read-only mode.
358 Default: ``readonly/textinput``.
360 options
361 A dictionary or sequence of two-tuples containing ``jquery-maskMoney``
362 options. The valid options are:
364 symbol
365 the symbol to be used before of the user values. default: ``$``
367 showSymbol
368 set if the symbol must be displayed or not. default: ``False``
370 symbolStay
371 set if the symbol will stay in the field after the user exists the
372 field. default: ``False``
374 thousands
375 the thousands separator. default: ``,``
377 decimal
378 the decimal separator. default: ``.``
380 precision
381 how many decimal places are allowed. default: 2
383 defaultZero
384 when the user enters the field, it sets a default mask using zero.
385 default: ``True``
387 allowZero
388 use this setting to prevent users from inputing zero. default:
389 ``False``
391 allowNegative
392 use this setting to prevent users from inputing negative values.
393 default: ``False``
394 """
396 template = "moneyinput"
397 readonly_template = "readonly/textinput"
398 requirements = (("jquery.maskMoney", None),)
399 options = None
401 def serialize(self, field, cstruct, **kw):
402 if cstruct in (null, None):
403 cstruct = ""
404 readonly = kw.get("readonly", self.readonly)
405 options = kw.get("options", self.options)
406 if options is None:
407 options = {}
408 options = json.dumps(dict(options))
409 kw["mask_options"] = options
410 values = self.get_template_values(field, cstruct, kw)
411 template = readonly and self.readonly_template or self.template
412 return field.renderer(template, **values)
414 def deserialize(self, field, pstruct):
415 if pstruct is null:
416 return null
417 elif not isinstance(pstruct, string_types):
418 raise Invalid(field.schema, "Pstruct is not a string")
419 pstruct = pstruct.strip()
420 thousands = ","
421 # Oh jquery-maskMoney, you submit the thousands separator in the
422 # control value. I'm no genius, but IMO that's not very smart. But
423 # then again you also don't inject the thousands separator into the
424 # value attached to the control when editing an existing value.
425 # Because it's obvious we should have *both* the server and the
426 # client doing this bullshit on both serialization and
427 # deserialization. I understand completely, you're just a client
428 # library, IT'S NO PROBLEM. LET ME HELP YOU.
429 if self.options:
430 thousands = dict(self.options).get("thousands", ",")
431 pstruct = pstruct.replace(thousands, "")
432 if not pstruct:
433 return null
434 return pstruct
437class AutocompleteInputWidget(Widget):
438 """
439 Renders an ``<input type="text"/>`` widget which provides
440 autocompletion via a list of values using bootstrap's typeahead plugin
441 https://github.com/twitter/typeahead.js/
443 **Attributes/Arguments**
445 template
446 The template name used to render the widget. Default:
447 ``typeahead_textinput``.
449 readonly_template
450 The template name used to render the widget in read-only mode.
451 Default: ``readonly/typeahead_textinput``.
453 strip
454 If true, during deserialization, strip the value of leading
455 and trailing whitespace (default ``True``).
457 values
458 A list of strings or string.
459 Defaults to ``[]``.
461 If ``values`` is a string it will be treated as a
462 URL. If values is an iterable which can be serialized to a
463 :term:`JSON` array, it will be treated as local data.
465 If a string is provided to a URL, an :term:`XHR` request will
466 be sent to the URL. The response should be a JSON
467 serialization of a list of values. For example:
469 ['foo', 'bar', 'baz']
471 min_length
472 ``min_length`` is an optional argument to
473 :term:`jquery.ui.autocomplete`. The number of characters to
474 wait for before activating the autocomplete call. Defaults to
475 ``1``.
477 items
478 The max number of items to display in the dropdown. Defaults to
479 ``8``.
481 """
483 min_length = 1
484 readonly_template = "readonly/textinput"
485 strip = True
486 items = 8
487 template = "autocomplete_input"
488 values = None
489 requirements = (("typeahead", None), ("deform", None))
491 def serialize(self, field, cstruct, **kw):
492 if "delay" in kw or getattr(self, "delay", None):
493 raise ValueError(
494 "AutocompleteWidget does not support *delay* parameter "
495 "any longer."
496 )
497 if cstruct in (null, None):
498 cstruct = ""
499 self.values = self.values or []
500 readonly = kw.get("readonly", self.readonly)
502 options = {}
503 if isinstance(self.values, string_types):
504 options["remote"] = "%s?term=%%QUERY" % self.values
505 else:
506 options["local"] = self.values
508 options["minLength"] = kw.pop("min_length", self.min_length)
509 options["limit"] = kw.pop("items", self.items)
510 kw["options"] = json.dumps(options)
511 tmpl_values = self.get_template_values(field, cstruct, kw)
512 template = readonly and self.readonly_template or self.template
513 return field.renderer(template, **tmpl_values)
515 def deserialize(self, field, pstruct):
516 if pstruct is null:
517 return null
518 elif not isinstance(pstruct, string_types):
519 raise Invalid(field.schema, "Pstruct is not a string")
520 if self.strip:
521 pstruct = pstruct.strip()
522 if not pstruct:
523 return null
524 return pstruct
527class TimeInputWidget(Widget):
528 """
529 Renders a time picker widget.
531 The default rendering is as a native HTML5 time input widget,
532 falling back to pickadate (https://github.com/amsul/pickadate.js.)
534 Most useful when the schema node is a ``colander.Time`` object.
536 **Attributes/Arguments**
538 style
539 A string that will be placed literally in a ``style`` attribute on
540 the text input tag. For example, 'width:150px;'. Default: ``None``,
541 meaning no style attribute will be added to the input tag.
543 options
544 Options for configuring the widget (eg: date format)
546 template
547 The template name used to render the widget. Default:
548 ``timeinput``.
550 readonly_template
551 The template name used to render the widget in read-only mode.
552 Default: ``readonly/timeinput``.
553 """
555 template = "timeinput"
556 readonly_template = "readonly/textinput"
557 type_name = "time"
558 size = None
559 style = None
560 requirements = (("modernizr", None), ("pickadate", None))
561 default_options = (("format", "HH:i"),)
563 _pstruct_schema = SchemaNode(
564 Mapping(),
565 SchemaNode(_StrippedString(), name="time"),
566 SchemaNode(_StrippedString(), name="time_submit", missing=""),
567 )
569 def __init__(self, *args, **kwargs):
570 self.options = dict(self.default_options)
571 self.options["formatSubmit"] = "HH:i"
572 Widget.__init__(self, *args, **kwargs)
574 def serialize(self, field, cstruct, **kw):
575 if cstruct in (null, None):
576 cstruct = ""
577 readonly = kw.get("readonly", self.readonly)
578 template = readonly and self.readonly_template or self.template
579 options = dict(
580 kw.get("options") or self.options or self.default_options
581 )
582 options["formatSubmit"] = "HH:i"
583 kw.setdefault("options_json", json.dumps(options))
584 values = self.get_template_values(field, cstruct, kw)
585 return field.renderer(template, **values)
587 def deserialize(self, field, pstruct):
588 if pstruct in ("", null):
589 return null
590 try:
591 validated = self._pstruct_schema.deserialize(pstruct)
592 except Invalid as exc:
593 raise Invalid(field.schema, text_("Invalid pstruct: %s" % exc))
594 return validated["time_submit"] or validated["time"]
597class DateInputWidget(Widget):
598 """
599 Renders a date picker widget.
601 The default rendering is as a native HTML5 date input widget,
602 falling back to pickadate (https://github.com/amsul/pickadate.js.)
604 Most useful when the schema node is a ``colander.Date`` object.
606 **Attributes/Arguments**
608 options
609 Dictionary of options for configuring the widget (eg: date format)
611 template
612 The template name used to render the widget. Default:
613 ``dateinput``.
615 readonly_template
616 The template name used to render the widget in read-only mode.
617 Default: ``readonly/textinput``.
618 """
620 template = "dateinput"
621 readonly_template = "readonly/textinput"
622 type_name = "date"
623 requirements = (("modernizr", None), ("pickadate", None))
624 default_options = (
625 ("format", "yyyy-mm-dd"),
626 ("selectMonths", True),
627 ("selectYears", True),
628 )
629 options = None
631 _pstruct_schema = SchemaNode(
632 Mapping(),
633 SchemaNode(_StrippedString(), name="date"),
634 SchemaNode(_StrippedString(), name="date_submit", missing=""),
635 )
637 def serialize(self, field, cstruct, **kw):
638 if cstruct in (null, None):
639 cstruct = ""
640 readonly = kw.get("readonly", self.readonly)
641 template = readonly and self.readonly_template or self.template
642 options = dict(
643 kw.get("options") or self.options or self.default_options
644 )
645 options["formatSubmit"] = "yyyy-mm-dd"
646 kw.setdefault("options_json", json.dumps(options))
647 values = self.get_template_values(field, cstruct, kw)
648 return field.renderer(template, **values)
650 def deserialize(self, field, pstruct):
651 if pstruct in ("", null):
652 return null
653 try:
654 validated = self._pstruct_schema.deserialize(pstruct)
655 except Invalid as exc:
656 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
657 return validated["date_submit"] or validated["date"]
660class DateTimeInputWidget(Widget):
661 """
662 Renders a datetime picker widget.
664 The default rendering is as a pair of inputs (a date and a time) using
665 pickadate.js (https://github.com/amsul/pickadate.js).
667 Used for ``colander.DateTime`` schema nodes.
669 **Attributes/Arguments**
671 date_options
672 A dictionary of date options passed to pickadate.
674 time_options
675 A dictionary of time options passed to pickadate.
677 template
678 The template name used to render the widget. Default:
679 ``dateinput``.
681 readonly_template
682 The template name used to render the widget in read-only mode.
683 Default: ``readonly/textinput``.
684 """
686 template = "datetimeinput"
687 readonly_template = "readonly/datetimeinput"
688 type_name = "datetime"
689 requirements = (("modernizr", None), ("pickadate", None))
690 default_date_options = (
691 ("format", "yyyy-mm-dd"),
692 ("selectMonths", True),
693 ("selectYears", True),
694 )
695 date_options = None
696 default_time_options = (("format", "h:i A"), ("interval", 30))
697 time_options = None
699 _pstruct_schema = SchemaNode(
700 Mapping(),
701 SchemaNode(_StrippedString(), name="date"),
702 SchemaNode(_StrippedString(), name="time"),
703 SchemaNode(_StrippedString(), name="date_submit", missing=""),
704 SchemaNode(_StrippedString(), name="time_submit", missing=""),
705 )
707 def serialize(self, field, cstruct, **kw):
708 if cstruct in (null, None):
709 cstruct = ""
710 readonly = kw.get("readonly", self.readonly)
711 if cstruct:
712 parsed = ISO8601_REGEX.match(cstruct)
713 if parsed: # strip timezone if it's there
714 timezone = parsed.groupdict()["timezone"]
715 if timezone and cstruct.endswith(timezone):
716 cstruct = cstruct[: -len(timezone)]
718 try:
719 date, time = cstruct.split("T", 1)
720 try:
721 # get rid of milliseconds
722 time, _ = time.split(".", 1)
723 except ValueError:
724 pass
725 kw["date"], kw["time"] = date, time
726 except ValueError: # need more than one item to unpack
727 kw["date"] = kw["time"] = ""
729 date_options = dict(
730 kw.get("date_options")
731 or self.date_options
732 or self.default_date_options
733 )
734 date_options["formatSubmit"] = "yyyy-mm-dd"
735 kw["date_options_json"] = json.dumps(date_options)
737 time_options = dict(
738 kw.get("time_options")
739 or self.time_options
740 or self.default_time_options
741 )
742 time_options["formatSubmit"] = "HH:i"
743 kw["time_options_json"] = json.dumps(time_options)
745 values = self.get_template_values(field, cstruct, kw)
746 template = readonly and self.readonly_template or self.template
747 return field.renderer(template, **values)
749 def deserialize(self, field, pstruct):
750 if pstruct is null:
751 return null
752 else:
753 try:
754 validated = self._pstruct_schema.deserialize(pstruct)
755 except Invalid as exc:
756 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
757 # seriously pickadate? oh. right. i forgot. you're javascript.
758 date = validated["date_submit"] or validated["date"]
759 time = validated["time_submit"] or validated["time"]
761 if not time and not date:
762 return null
764 result = "T".join([date, time])
766 if not date:
767 raise Invalid(field.schema, _("Incomplete date"), result)
769 if not time:
770 raise Invalid(field.schema, _("Incomplete time"), result)
772 return result
775class TextAreaWidget(TextInputWidget):
776 """
777 Renders a ``<textarea>`` widget.
779 **Attributes/Arguments**
781 cols
782 The size, in columns, of the text input field. Defaults to
783 ``None``, meaning that the ``cols`` is not included in the
784 widget output (uses browser default cols).
786 rows
787 The size, in rows, of the text input field. Defaults to
788 ``None``, meaning that the ``rows`` is not included in the
789 widget output (uses browser default cols).
791 template
792 The template name used to render the widget. Default:
793 ``textarea``.
795 readonly_template
796 The template name used to render the widget in read-only mode.
797 Default: ``readonly/textinput``.
800 strip
801 If true, during deserialization, strip the value of leading
802 and trailing whitespace (default ``True``).
803 """
805 template = "textarea"
806 readonly_template = "readonly/textinput"
807 cols = None
808 rows = None
809 strip = True
812class RichTextWidget(TextInputWidget):
813 """
814 Renders a ``<textarea>`` widget with the
815 :term:`TinyMCE Editor`.
817 To use this widget the :term:`TinyMCE Editor` library must be
818 provided in the page where the widget is rendered. A version of
819 :term:`TinyMCE Editor` is included in Deform's ``static`` directory.
822 **Attributes/Arguments**
824 readonly_template
825 The template name used to render the widget in read-only mode.
826 Default: ``readonly/richtext``.
828 delayed_load
829 If you have many richtext fields, you can set this option to
830 ``True``, and the richtext editor will only be loaded upon
831 the user clicking the field. Default: ``False``.
833 **Security Note**: Enabling ``delayed_load`` can create an
834 HTML injection vulnerability. When enabled, any existing value
835 for the field will be rendered without HTML escaping. Also,
836 on form re-display, any user-submitted value which passes
837 validation will be rendered unescaped. (If the field has a
838 validation error, ``delayed_load`` will be disabled during
839 re-display.) You should not enable ``delayed_load`` unless you
840 trust both existing and valid user-submitted values for the field
841 to be 'safe HTML'.
843 strip
844 If true, during deserialization, strip the value of leading
845 and trailing whitespace. Default: ``True``.
847 template
848 The template name used to render the widget. Default:
849 ``richtext``.
851 options
852 A dictionary or sequence of two-tuples containing additional
853 options to pass to the TinyMCE ``init`` function call. All types
854 within such structure should be Python native as the structure
855 will be converted to JSON on serialization. This widget provides
856 some sensible defaults, as described below in
857 :attr:`default_options`.
859 You should refer to the `TinyMCE Configuration
860 <https://www.tiny.cloud/docs/configure/>`_ documentation
861 for details regarding all available configuration options.
863 The ``language`` option is passed to TinyMCE within the default
864 template, using i18n machinery to determine the language to use.
865 This option can be overridden if it is specified here in ``options``.
867 *Note*: the ``elements`` option for TinyMCE is set automatically
868 according to the given field's ``oid``.
870 Default: ``None`` (no additional options)
872 Note that the RichTextWidget template does not honor the ``css_class``
873 or ``style`` attributes of the widget.
875 """
877 readonly_template = "readonly/richtext"
878 delayed_load = False
879 strip = True
880 template = "richtext"
881 requirements = ({"js": "deform:static/tinymce/tinymce.min.js"},)
883 #: Default options passed to TinyMCE. Customise by using :attr:`options`.
884 default_options = (
885 ("height", 240),
886 ("width", 0),
887 ("skin", "lightgray"),
888 ("theme", "modern"),
889 ("mode", "exact"),
890 ("strict_loading_mode", True),
891 ("theme_advanced_resizing", True),
892 ("theme_advanced_toolbar_align", "left"),
893 ("theme_advanced_toolbar_location", "top"),
894 )
895 #: Options to pass to TinyMCE that will override :attr:`default_options`.
896 options = None
898 def serialize(self, field, cstruct, **kw):
899 if cstruct in (null, None):
900 cstruct = ""
901 readonly = kw.get("readonly", self.readonly)
903 options = dict(self.default_options)
904 # Accept overrides from keywords or as an attribute
905 options_overrides = dict(kw.get("options", self.options or {}))
906 options.update(options_overrides)
907 # Dump to JSON and strip curly braces at start and end
908 kw["tinymce_options"] = json.dumps(options)[1:-1]
910 values = self.get_template_values(field, cstruct, kw)
911 template = readonly and self.readonly_template or self.template
912 return field.renderer(template, **values)
915class PasswordWidget(TextInputWidget):
916 """
917 Renders a single <input type="password"/> input field.
919 **Attributes/Arguments**
921 template
922 The template name used to render the widget. Default:
923 ``password``.
925 readonly_template
926 The template name used to render the widget in read-only mode.
927 Default: ``readonly/password``.
929 strip
930 If true, during deserialization, strip the value of leading
931 and trailing whitespace. Default: ``True``.
933 redisplay
934 If true, on validation failure, retain and redisplay the password
935 input. If false, on validation failure, this field will be
936 rendered empty. Default: ``False``.
938 """
940 template = "password"
941 readonly_template = "readonly/password"
942 redisplay = False
945class HiddenWidget(Widget):
946 """
947 Renders an ``<input type="hidden"/>`` widget.
949 **Attributes/Arguments**
951 template
952 The template name used to render the widget. Default:
953 ``hidden``.
954 """
956 template = "hidden"
957 hidden = True
959 def serialize(self, field, cstruct, **kw):
960 if cstruct in (null, None):
961 cstruct = ""
962 values = self.get_template_values(field, cstruct, kw)
963 return field.renderer(self.template, **values)
965 def deserialize(self, field, pstruct):
966 if not pstruct:
967 return null
968 elif not isinstance(pstruct, string_types):
969 raise Invalid(field.schema, "Pstruct is not a string")
970 return pstruct
973class CheckboxWidget(Widget):
974 """
975 Renders an ``<input type="checkbox"/>`` widget.
977 **Attributes/Arguments**
979 true_val
980 The value which should be returned during deserialization if
981 the box is checked. Default: ``true``.
983 false_val
984 The value which should be returned during deserialization if
985 the box was left unchecked. Default: ``false``.
987 template
988 The template name used to render the widget. Default:
989 ``checkbox``.
991 readonly_template
992 The template name used to render the widget in read-only mode.
993 Default: ``readonly/checkbox``.
995 """
997 true_val = "true"
998 false_val = "false"
1000 template = "checkbox"
1001 readonly_template = "readonly/checkbox"
1003 def serialize(self, field, cstruct, **kw):
1004 readonly = kw.get("readonly", self.readonly)
1005 template = readonly and self.readonly_template or self.template
1006 values = self.get_template_values(field, cstruct, kw)
1007 return field.renderer(template, **values)
1009 def deserialize(self, field, pstruct):
1010 if pstruct is null:
1011 return self.false_val
1012 elif not isinstance(pstruct, string_types):
1013 raise Invalid(field.schema, "Pstruct is not a string")
1014 return (pstruct == self.true_val) and self.true_val or self.false_val
1017class OptGroup(object):
1018 """
1019 Used in the ``values`` argument passed to an instance of
1020 ``SelectWidget`` to render an ``<optgroup>`` HTML tag.
1022 **Attributes/Arguments**
1024 label
1025 The label of the ``<optgroup>`` HTML tag.
1027 options
1028 A sequence that describes the ``<options>`` HTML tag(s). It
1029 must have the same structure as the ``values``
1030 argument/parameter in the ``SelectWidget`` class, but should
1031 not contain ``OptGroup`` instances since ``<optgroup>`` HTML
1032 tags cannot be nested.
1033 """
1035 def __init__(self, label, *options):
1036 self.label = label
1037 self.options = options
1040class SelectWidget(Widget):
1041 """
1042 Renders ``<select>`` field based on a predefined set of values.
1044 **Attributes/Arguments**
1046 values
1047 A sequence type (list, tuple, or range) of items where each item must
1048 be either:
1050 - a two-tuple (the first value must be of type string, unicode
1051 or integer, the second value must be string or unicode)
1052 indicating allowable, displayed values, e.g. ``('jsmith',
1053 'John Smith')``. The first element in the tuple is the value
1054 that should be returned when the form is posted. The second
1055 is the display value;
1057 - or an instance of ``optgroup_class`` (which is
1058 ``deform.widget.OptGroup`` by default).
1060 null_value
1061 The value which represents the null value. When the null
1062 value is encountered during serialization, the
1063 :attr:`colander.null` sentinel is returned to the caller.
1064 Default: ``''`` (the empty string).
1066 template
1067 The template name used to render the widget. Default:
1068 ``select``.
1070 readonly_template
1071 The template name used to render the widget in read-only mode.
1072 Default: ``readonly/select``.
1074 multiple
1075 Enable multiple on the select widget ( default: ``False`` )
1077 optgroup_class
1078 The class used to represent ``<optgroup>`` HTML tags. Default:
1079 ``deform.widget.OptGroup``.
1081 long_label_generator
1082 A function that returns the "long label" used as the
1083 description for very old browsers that do not support the
1084 ``<optgroup>`` HTML tag. If a function is provided, the
1085 ``label`` attribute will receive the (short) description,
1086 while the content of the ``<option>`` tag will receive the
1087 "long label". The function is called with two parameters: the
1088 group label and the option (short) description.
1090 For example, with the following widget:
1092 .. code-block:: python
1094 long_label_gener = lambda group, label: ' - '.join((group, label))
1095 SelectWidget(
1096 values=(
1097 ('', 'Select your favorite musician'),
1098 OptGroup('Guitarists',
1099 ('page', 'Jimmy Page'),
1100 ('hendrix', 'Jimi Hendrix')),
1101 OptGroup('Drummers',
1102 ('cobham', 'Billy Cobham'),
1103 ('bonham', 'John Bonham'))),
1104 long_label_generator=long_label_gener)
1106 ... the rendered options would look like:
1108 .. code-block:: html
1110 <option value="">Select your favorite musician</option>
1111 <optgroup label="Guitarists">
1112 <option value="page"
1113 label="Jimmy Page">Guitarists - Jimmy Page</option>
1114 <option value="hendrix"
1115 label="Jimi Hendrix">Guitarists - Jimi Hendrix</option>
1116 </optgroup>
1117 <optgroup label="Drummers">
1118 <option value="cobham"
1119 label="Billy Cobham">Drummers - Billy Cobham</option>
1120 <option value="bonham"
1121 label="John Bonham">Drummers - John Bonham</option>
1122 </optgroup>
1124 Default: ``None`` (which means that the ``label`` attribute is
1125 not rendered).
1127 size
1128 The size, in rows, of the select list. Defaults to
1129 ``None``, meaning that the ``size`` is not included in the
1130 widget output (uses browser default size).
1131 """
1133 template = "select"
1134 readonly_template = "readonly/select"
1135 null_value = ""
1136 values = ()
1137 size = None
1138 multiple = False
1139 optgroup_class = OptGroup
1140 long_label_generator = None
1141 selectize_options = None
1142 default_selectize_options = (("allowEmptyOption", True),)
1144 def get_select_value(self, cstruct, value):
1145 """Choose whether <opt> is selected or not.
1147 Incoming value is always string, as it has been passed through HTML.
1148 However, our values might be given as integer, UUID.
1149 """
1151 if self.multiple:
1152 if value in map(text_type, cstruct):
1153 return "selected"
1154 else:
1155 if value == text_type(cstruct):
1156 return "selected"
1158 return None
1160 def serialize(self, field, cstruct, **kw):
1161 if cstruct in (null, None):
1162 cstruct = self.null_value
1163 readonly = kw.get("readonly", self.readonly)
1164 values = kw.get("values", self.values)
1165 if not isinstance(values, sequence_types):
1166 e = "Values must be a sequence type (list, tuple, or range)."
1167 raise TypeError(e)
1168 template = readonly and self.readonly_template or self.template
1169 kw["values"] = _normalize_choices(values)
1171 selectize_options = dict(
1172 kw.get("selectize_options")
1173 or self.selectize_options
1174 or self.default_selectize_options
1175 )
1176 kw["selectize_options_json"] = json.dumps(selectize_options)
1177 tmpl_values = self.get_template_values(field, cstruct, kw)
1178 return field.renderer(template, **tmpl_values)
1180 def deserialize(self, field, pstruct):
1181 if pstruct in (null, self.null_value):
1182 return null
1183 if self.multiple:
1184 try:
1185 return _sequence_of_strings.deserialize(pstruct)
1186 except Invalid as exc:
1187 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
1188 else:
1189 if not isinstance(pstruct, string_types):
1190 raise Invalid(field.schema, "Pstruct is not a string")
1191 return pstruct
1194class Select2Widget(SelectWidget):
1195 """
1196 Renders ``<select>`` field based on a predefined set of values using
1197 `select2 <https://select2.org/>`_ library.
1199 **Attributes/Arguments**
1201 Same as :func:`~deform.widget.SelectWidget`, with some extra options
1202 listed here.
1204 tags: *bool*
1205 Allow dynamic option creation ( default: ``False`` ).
1206 See `select2 docs on tagging <https://select2.org/tagging>`_ for
1207 more details.
1208 """
1210 template = "select2"
1211 requirements = (
1212 ("deform", None),
1213 {
1214 "js": "deform:static/select2/select2.js",
1215 "css": "deform:static/select2/select2.css",
1216 },
1217 )
1220class SelectizeWidget(SelectWidget):
1221 """
1222 Renders ``<select>`` field based on a predefined set of values using
1223 `selectize.js <https://github.com/selectize/selectize.js>`_ library.
1225 **Attributes/Arguments**
1227 Same as :func:`~deform.widget.SelectWidget`, with some extra options
1228 listed here.
1230 selectize_options: *dict* or *two-tuples*
1231 Allows configuration of arbitrary options for Selectize. See
1232 `Selectize docs under Usage for configuration options
1233 <https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration>`
1234 for details.
1235 """
1237 template = "selectize"
1238 requirements = (
1239 ("deform", None),
1240 {
1241 "js": "deform:static/selectize/selectize.js",
1242 "css": "deform:static/selectize/selectize.bootstrap3.css",
1243 },
1244 )
1247class RadioChoiceWidget(SelectWidget):
1248 """
1249 Renders a sequence of ``<input type="radio"/>`` buttons based on a
1250 predefined set of values.
1252 **Attributes/Arguments**
1254 values
1255 A sequence type (list, tuple, or range) of two-tuples (the first value
1256 must be of type string, unicode, or integer, the second value must be
1257 string or unicode) indicating allowable, displayed values, e.g. ``(
1258 ('true', 'True'), ('false', 'False') )``. The first element
1259 in the tuple is the value that should be returned when the
1260 form is posted. The second is the display value.
1262 template
1263 The template name used to render the widget. Default:
1264 ``radio_choice``.
1266 readonly_template
1267 The template name used to render the widget in read-only mode.
1268 Default: ``readonly/radio_choice``.
1270 null_value
1271 The value used to replace the ``colander.null`` value when it
1272 is passed to the ``serialize`` or ``deserialize`` method.
1273 Default: the empty string.
1275 inline
1276 If true, choices will be rendered on a single line.
1277 Otherwise choices will be rendered one per line.
1278 Default: false.
1279 """
1281 template = "radio_choice"
1282 readonly_template = "readonly/radio_choice"
1285class CheckboxChoiceWidget(Widget):
1286 """
1287 Renders a sequence of ``<input type="check"/>`` buttons based on a
1288 predefined set of values.
1290 **Attributes/Arguments**
1292 values
1293 A sequence type (list, tuple, or range) of two-tuples (the first value
1294 must be of type string, unicode or integer, the second value must be
1295 string or unicode) indicating allowable, displayed values, e.g. ``(
1296 ('true', 'True'), ('false', 'False') )``. The first element
1297 in the tuple is the value that should be returned when the
1298 form is posted. The second is the display value.
1300 template
1301 The template name used to render the widget. Default:
1302 ``checkbox_choice``.
1304 readonly_template
1305 The template name used to render the widget in read-only mode.
1306 Default: ``readonly/checkbox_choice``.
1308 null_value
1309 The value used to replace the ``colander.null`` value when it
1310 is passed to the ``serialize`` or ``deserialize`` method.
1311 Default: the empty string.
1313 inline
1314 If true, choices will be rendered on a single line.
1315 Otherwise choices will be rendered one per line.
1316 Default: false.
1317 """
1319 template = "checkbox_choice"
1320 readonly_template = "readonly/checkbox_choice"
1321 values = ()
1323 def serialize(self, field, cstruct, **kw):
1324 if cstruct in (null, None):
1325 cstruct = ()
1326 readonly = kw.get("readonly", self.readonly)
1327 values = kw.get("values", self.values)
1328 if not isinstance(values, sequence_types):
1329 e = "Values must be a sequence type (list, tuple, or range)."
1330 raise TypeError(e)
1331 kw["values"] = _normalize_choices(values)
1332 template = readonly and self.readonly_template or self.template
1333 tmpl_values = self.get_template_values(field, cstruct, kw)
1334 return field.renderer(template, **tmpl_values)
1336 def deserialize(self, field, pstruct):
1337 if pstruct is null:
1338 return null
1339 if isinstance(pstruct, string_types):
1340 return (pstruct,)
1341 try:
1342 validated = _sequence_of_strings.deserialize(pstruct)
1343 except Invalid as exc:
1344 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
1345 return tuple(validated)
1348class CheckedInputWidget(Widget):
1349 """
1350 Renders two text input fields: 'value' and 'confirm'.
1351 Validates that the 'value' value matches the 'confirm' value.
1353 **Attributes/Arguments**
1355 template
1356 The template name used to render the widget. Default:
1357 ``checked_input``.
1359 readonly_template
1360 The template name used to render the widget in read-only mode.
1361 Default: ``readonly/textinput``.
1363 mismatch_message
1364 The message to be displayed when the value in the primary
1365 field does not match the value in the confirm field.
1367 mask
1368 A :term:`jquery.maskedinput` input mask, as a string. Both
1369 input fields will use this mask.
1371 a - Represents an alpha character (A-Z,a-z)
1372 9 - Represents a numeric character (0-9)
1373 * - Represents an alphanumeric character (A-Z,a-z,0-9)
1375 All other characters in the mask will be considered mask
1376 literals.
1378 Example masks:
1380 Date: 99/99/9999
1382 US Phone: (999) 999-9999
1384 US SSN: 999-99-9999
1386 When this option is used, the :term:`jquery.maskedinput`
1387 library must be loaded into the page serving the form for the
1388 mask argument to have any effect. See :ref:`masked_input`.
1390 mask_placeholder
1391 The placeholder for required nonliteral elements when a mask
1392 is used. Default: ``_`` (underscore).
1393 """
1395 template = "checked_input"
1396 readonly_template = "readonly/textinput"
1397 mismatch_message = _("Fields did not match")
1398 subject = _("Value")
1399 confirm_subject = _("Confirm Value")
1400 mask = None
1401 mask_placeholder = "_"
1402 requirements = ()
1404 def __init__(self, **kw):
1405 super(CheckedInputWidget, self).__init__(**kw)
1406 if getattr(self, "mask", False):
1407 self.requirements = tuple(
1408 list(self.requirements) + [("jquery.maskedinput", None)]
1409 )
1411 def serialize(self, field, cstruct, **kw):
1412 if cstruct in (null, None):
1413 cstruct = ""
1414 readonly = kw.get("readonly", self.readonly)
1415 kw.setdefault("subject", self.subject)
1416 kw.setdefault("confirm_subject", self.confirm_subject)
1417 confirm = getattr(field, "%s-confirm" % (field.name,), cstruct)
1418 kw["confirm"] = confirm
1419 template = readonly and self.readonly_template or self.template
1420 values = self.get_template_values(field, cstruct, kw)
1421 return field.renderer(template, **values)
1423 def deserialize(self, field, pstruct):
1424 if pstruct is null:
1425 return null
1426 confirm_name = "%s-confirm" % field.name
1427 schema = SchemaNode(
1428 Mapping(),
1429 SchemaNode(_PossiblyEmptyString(), name=field.name),
1430 SchemaNode(_PossiblyEmptyString(), name=confirm_name),
1431 )
1432 try:
1433 validated = schema.deserialize(pstruct)
1434 except Invalid as exc:
1435 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
1436 value = validated[field.name]
1437 confirm = validated[confirm_name]
1438 setattr(field, confirm_name, confirm)
1439 if (value or confirm) and (value != confirm):
1440 raise Invalid(field.schema, self.mismatch_message, value)
1441 if not value:
1442 return null
1443 return value
1446class CheckedPasswordWidget(CheckedInputWidget):
1447 """
1448 Renders two password input fields: 'password' and 'confirm'.
1449 Validates that the 'password' value matches the 'confirm' value.
1451 **Attributes/Arguments**
1453 template
1454 The template name used to render the widget. Default:
1455 ``checked_password``.
1457 readonly_template
1458 The template name used to render the widget in read-only mode.
1459 Default: ``readonly/checked_password``.
1461 mismatch_message
1462 The string shown in the error message when a validation failure is
1463 caused by the confirm field value does not match the password
1464 field value. Default: ``Password did not match confirm``.
1466 redisplay
1467 If true, on validation failure involving a field with this widget,
1468 retain and redisplay the provided values in the password inputs. If
1469 false, on validation failure, the fields will be rendered empty.
1470 Default:: ``False``.
1471 """
1473 template = "checked_password"
1474 readonly_template = "readonly/checked_password"
1475 mismatch_message = _("Password did not match confirm")
1476 redisplay = False
1479class MappingWidget(Widget):
1480 """
1481 Renders a mapping into a set of fields.
1483 **Attributes/Arguments**
1485 template
1486 The template name used to render the widget. Default:
1487 ``mapping``. See also ``mapping_accordion`` template for hideable
1488 user experience.
1490 readonly_template
1491 The template name used to render the widget in read-only mode.
1492 Default: ``readonly/mapping``.
1494 item_template
1495 The template name used to render each item in the mapping.
1496 Default: ``mapping_item``.
1498 readonly_item_template
1499 The template name used to render each item in the form.
1500 Default: ``readonly/mapping_item``.
1502 open: bool
1503 Used with ``mapping_accordion`` template to define if the
1504 mapping subform accordion is open or closed by default.
1506 Note that the MappingWidget template does not honor the ``css_class``
1507 or ``style`` attributes of the widget.
1508 """
1510 template = "mapping"
1511 readonly_template = "readonly/mapping"
1512 item_template = "mapping_item"
1513 readonly_item_template = "readonly/mapping_item"
1514 error_class = None
1515 category = "structural"
1516 requirements = (("deform", None),)
1518 def serialize(self, field, cstruct, **kw):
1519 if cstruct in (null, None):
1520 cstruct = {}
1521 readonly = kw.get("readonly", self.readonly)
1522 kw.setdefault("null", null)
1523 template = readonly and self.readonly_template or self.template
1524 values = self.get_template_values(field, cstruct, kw)
1525 return field.renderer(template, **values)
1527 def deserialize(self, field, pstruct):
1528 error = None
1530 result = {}
1532 if pstruct is null:
1533 pstruct = {}
1534 elif not isinstance(pstruct, dict):
1535 raise Invalid(field.schema, "Pstruct is not a dict")
1537 for num, subfield in enumerate(field.children):
1538 name = subfield.name
1539 subval = pstruct.get(name, null)
1541 try:
1542 result[name] = subfield.deserialize(subval)
1543 except Invalid as e:
1544 result[name] = e.value
1545 if error is None:
1546 error = Invalid(field.schema, value=result)
1547 error.add(e, num)
1549 if error is not None:
1550 raise error
1552 return result
1555class FormWidget(MappingWidget):
1556 """
1557 The top-level widget; represents an entire form.
1559 **Attributes/Arguments**
1561 template
1562 The template name used to render the widget. Default:
1563 ``form``.
1565 readonly_template
1566 The template name used to render the widget in read-only mode.
1567 Default: ``readonly/form``.
1569 item_template
1570 The template name used to render each item in the form.
1571 Default: ``mapping_item``.
1573 readonly_item_template
1574 The template name used to render each item in the form.
1575 Default: ``readonly/mapping_item``.
1577 """
1579 template = "form"
1580 readonly_template = "readonly/form"
1583class SequenceWidget(Widget):
1584 """Renders a sequence (0 .. N widgets, each the same as the other)
1585 into a set of fields.
1587 **Attributes/Arguments**
1589 template
1590 The template name used to render the widget. Default:
1591 ``sequence``.
1593 readonly_template
1594 The template name used to render the widget in read-only mode.
1595 Default: ``readonly/sequence``.
1597 item_template
1598 The template name used to render each value in the sequence.
1599 Default: ``sequence_item``.
1601 add_subitem_text_template
1602 The string used as the add link text for the widget.
1603 Interpolation markers in the template will be replaced in this
1604 string during serialization with a value as follows:
1606 ``${subitem_title}``
1607 The title of the subitem field
1609 ``${subitem_description}``
1610 The description of the subitem field
1612 ``${subitem_name}``
1613 The name of the subitem field
1615 Default: ``Add ${subitem_title}``.
1617 min_len
1618 Integer indicating minimum number of acceptable subitems. Default:
1619 ``None`` (meaning no minimum). On the first rendering of a form
1620 including this sequence widget, at least this many subwidgets will be
1621 rendered. The JavaScript sequence management will not allow fewer
1622 than this many subwidgets to be present in the sequence.
1624 max_len
1625 Integer indicating maximum number of acceptable subwidgets. Default:
1626 ``None`` (meaning no maximum). The JavaScript sequence management
1627 will not allow more than this many subwidgets to be added to the
1628 sequence.
1630 orderable
1631 Boolean indicating whether the Javascript sequence management will
1632 allow the user to explicitly re-order the subwidgets.
1633 Default: ``False``.
1635 Note that the SequenceWidget template does not honor the ``css_class``
1636 or ``style`` attributes of the widget.
1638 """
1640 template = "sequence"
1641 readonly_template = "readonly/sequence"
1642 item_template = "sequence_item"
1643 readonly_item_template = "readonly/sequence_item"
1644 error_class = None
1645 add_subitem_text_template = _("Add ${subitem_title}")
1646 min_len = None
1647 max_len = None
1648 orderable = False
1649 requirements = (
1650 ("deform", None),
1651 {"js": "deform:static/scripts/jquery-sortable.js"},
1652 )
1654 def prototype(self, field):
1655 # we clone the item field to bump the oid (for easier
1656 # automated testing; finding last node)
1657 item_field = field.children[0].clone()
1658 if not item_field.name:
1659 info = "Prototype for %r has no name" % field
1660 raise ValueError(info)
1661 # NB: item_field default should already be set up
1662 proto = item_field.render_template(self.item_template, parent=field)
1663 if isinstance(proto, string_types):
1664 proto = proto.encode("utf-8")
1665 proto = url_quote(proto)
1666 return proto
1668 def serialize(self, field, cstruct, **kw):
1669 # XXX make it possible to override min_len in kw
1671 if cstruct in (null, None):
1672 if self.min_len is not None:
1673 cstruct = [null] * self.min_len
1674 else:
1675 cstruct = []
1677 cstructlen = len(cstruct)
1679 if self.min_len is not None and (cstructlen < self.min_len):
1680 cstruct = list(cstruct) + ([null] * (self.min_len - cstructlen))
1682 item_field = field.children[0]
1684 if getattr(field, "sequence_fields", None):
1685 # this serialization is being performed as a result of a
1686 # validation failure (``deserialize`` was previously run)
1687 assert len(cstruct) == len(field.sequence_fields)
1688 subfields = list(zip(cstruct, field.sequence_fields))
1689 else:
1690 # this serialization is being performed as a result of a
1691 # first-time rendering
1692 subfields = []
1693 for val in cstruct:
1694 cloned = item_field.clone()
1695 if val is not null:
1696 # item field has already been set up with a default by
1697 # virtue of its constructor and setting cstruct to null
1698 # here wil overwrite the real default
1699 cloned.cstruct = val
1700 subfields.append((cloned.cstruct, cloned))
1702 readonly = kw.get("readonly", self.readonly)
1703 template = readonly and self.readonly_template or self.template
1704 translate = field.translate
1705 subitem_title = kw.get("subitem_title", item_field.title)
1706 subitem_description = kw.get(
1707 "subitem_description", item_field.description
1708 )
1709 add_subitem_text_template = kw.get(
1710 "add_subitem_text_template", self.add_subitem_text_template
1711 )
1712 add_template_mapping = dict(
1713 subitem_title=translate(subitem_title),
1714 subitem_description=translate(subitem_description),
1715 subitem_name=item_field.name,
1716 )
1717 if isinstance(add_subitem_text_template, TranslationString):
1718 add_subitem_text = add_subitem_text_template % add_template_mapping
1719 else:
1720 add_subitem_text = _(
1721 add_subitem_text_template, mapping=add_template_mapping
1722 )
1724 kw.setdefault("subfields", subfields)
1725 kw.setdefault("add_subitem_text", add_subitem_text)
1726 kw.setdefault("item_field", item_field)
1728 values = self.get_template_values(field, cstruct, kw)
1730 return field.renderer(template, **values)
1732 def deserialize(self, field, pstruct):
1733 result = []
1734 error = None
1736 if pstruct is null:
1737 pstruct = []
1738 elif not isinstance(pstruct, list):
1739 raise Invalid(field.schema, "Pstruct is not a list")
1741 field.sequence_fields = []
1742 item_field = field.children[0]
1744 for num, substruct in enumerate(pstruct):
1745 subfield = item_field.clone()
1746 try:
1747 subval = subfield.deserialize(substruct)
1748 except Invalid as e:
1749 subval = e.value
1750 if error is None:
1751 error = Invalid(field.schema, value=result)
1752 error.add(e, num)
1754 subfield.cstruct = subval
1755 result.append(subval)
1756 field.sequence_fields.append(subfield)
1758 if error is not None:
1759 raise error
1761 return result
1763 def handle_error(self, field, error):
1764 if field.error is None:
1765 field.error = error
1766 # XXX exponential time
1767 sequence_fields = getattr(field, "sequence_fields", [])
1768 for e in error.children:
1769 for num, subfield in enumerate(sequence_fields):
1770 if e.pos == num:
1771 subfield.widget.handle_error(subfield, e)
1774class filedict(dict):
1775 """Use a dict subclass to make it easy to detect file upload
1776 dictionaries in application code before trying to write them to
1777 persistent objects."""
1780class FileUploadWidget(Widget):
1781 """
1782 Represent a file upload. Meant to work with a
1783 :class:`deform.FileData` schema node.
1785 This widget accepts a single required positional argument in its
1786 constructor: ``tmpstore``. This argument should be passed an
1787 instance of an object that implements the
1788 :class:`deform.interfaces.FileUploadTempStore` interface. Such an
1789 instance will hold on to file upload data during the validation
1790 process, so the user doesn't need to reupload files if other parts
1791 of the form rendering fail validation. See also
1792 :class:`deform.interfaces.FileUploadTempStore`.
1794 **Attributes/Arguments**
1796 template
1797 The template name used to render the widget. Default:
1798 ``file_upload``.
1800 readonly_template
1801 The template name used to render the widget in read-only mode.
1802 Default: ``readonly/file_upload``.
1804 accept
1805 The ``accept`` attribute of the input field (default ``None``).
1806 """
1808 template = "file_upload"
1809 readonly_template = "readonly/file_upload"
1810 accept = None
1812 requirements = ({"js": "deform:static/scripts/file_upload.js"},)
1814 _pstruct_schema = SchemaNode(
1815 Mapping(),
1816 SchemaNode(_FieldStorage(), name="upload", missing=None),
1817 SchemaNode(_PossiblyEmptyString(), name="uid", missing=None),
1818 )
1820 def __init__(self, tmpstore, **kw):
1821 Widget.__init__(self, **kw)
1822 self.tmpstore = tmpstore
1824 def random_id(self):
1825 return "".join(
1826 [random.choice(uppercase + string.digits) for i in range(10)]
1827 )
1829 def serialize(self, field, cstruct, **kw):
1830 if cstruct in (null, None):
1831 cstruct = {}
1832 if cstruct:
1833 uid = cstruct["uid"]
1834 if uid not in self.tmpstore:
1835 self.tmpstore[uid] = cstruct
1837 readonly = kw.get("readonly", self.readonly)
1838 template = readonly and self.readonly_template or self.template
1839 values = self.get_template_values(field, cstruct, kw)
1840 return field.renderer(template, **values)
1842 def deserialize(self, field, pstruct):
1843 if pstruct is null:
1844 return null
1845 try:
1846 validated = self._pstruct_schema.deserialize(pstruct)
1847 except Invalid as exc:
1848 raise Invalid(field.schema, "Invalid pstruct: %s" % exc)
1850 upload = validated["upload"]
1851 uid = validated["uid"]
1853 if hasattr(upload, "file"):
1854 # the upload control had a file selected
1855 data = filedict()
1856 data["fp"] = upload.file
1857 filename = upload.filename
1858 # sanitize IE whole-path filenames
1859 filename = filename[filename.rfind("\\") + 1 :].strip()
1860 data["filename"] = filename
1861 data["mimetype"] = upload.type
1862 data["size"] = upload.length
1863 if uid is None:
1864 # no previous file exists
1865 while 1:
1866 uid = self.random_id()
1867 if self.tmpstore.get(uid) is None:
1868 data["uid"] = uid
1869 self.tmpstore[uid] = data
1870 preview_url = self.tmpstore.preview_url(uid)
1871 self.tmpstore[uid]["preview_url"] = preview_url
1872 break
1873 else:
1874 # a previous file exists
1875 data["uid"] = uid
1876 self.tmpstore[uid] = data
1877 preview_url = self.tmpstore.preview_url(uid)
1878 self.tmpstore[uid]["preview_url"] = preview_url
1879 else:
1880 # the upload control had no file selected
1881 if uid is None:
1882 # no previous file exists
1883 return null
1884 else:
1885 # a previous file should exist
1886 data = self.tmpstore.get(uid)
1887 # but if it doesn't, don't blow up
1888 if data is None:
1889 return null
1891 return data
1894class DatePartsWidget(Widget):
1895 """
1896 Renders a set of ``<input type='text'/>`` controls based on the
1897 year, month, and day parts of the serialization of a
1898 :class:`colander.Date` object or a string in the format
1899 ``YYYY-MM-DD``. This widget is usually meant to be used as widget
1900 which renders a :class:`colander.Date` type; validation
1901 likely won't work as you expect if you use it against a
1902 :class:`colander.String` object, but it is possible to use it
1903 with one if you use a proper validator.
1905 **Attributes/Arguments**
1907 template
1908 The template name used to render the input widget. Default:
1909 ``dateparts``.
1911 readonly_template
1912 The template name used to render the widget in read-only mode.
1913 Default: ``readonly/dateparts``.
1915 assume_y2k
1916 If a year is provided in 2-digit form, assume it means
1917 2000+year. Default: ``True``.
1919 """
1921 template = "dateparts"
1922 readonly_template = "readonly/dateparts"
1923 assume_y2k = True
1925 _pstruct_schema = SchemaNode(
1926 Mapping(),
1927 SchemaNode(_StrippedString(), name="year"),
1928 SchemaNode(_StrippedString(), name="month"),
1929 SchemaNode(_StrippedString(), name="day"),
1930 )
1932 def serialize(self, field, cstruct, **kw):
1933 if cstruct is null:
1934 year = ""
1935 month = ""
1936 day = ""
1937 else:
1938 year, month, day = cstruct.split("-", 2)
1940 kw.setdefault("year", year)
1941 kw.setdefault("day", day)
1942 kw.setdefault("month", month)
1944 readonly = kw.get("readonly", self.readonly)
1945 template = readonly and self.readonly_template or self.template
1946 values = self.get_template_values(field, cstruct, kw)
1947 return field.renderer(template, **values)
1949 def deserialize(self, field, pstruct):
1950 if pstruct is null:
1951 return null
1952 else:
1953 try:
1954 validated = self._pstruct_schema.deserialize(pstruct)
1955 except Invalid as exc:
1956 raise Invalid(field.schema, text_("Invalid pstruct: %s" % exc))
1957 year = validated["year"]
1958 month = validated["month"]
1959 day = validated["day"]
1961 if not year and not month and not day:
1962 return null
1964 if self.assume_y2k and len(year) == 2:
1965 year = "20" + year
1966 result = "-".join([year, month, day])
1968 if not year or not month or not day:
1969 raise Invalid(field.schema, _("Incomplete date"), result)
1971 return result
1974class TextAreaCSVWidget(Widget):
1975 """
1976 Widget used for a sequence of tuples of scalars; allows for
1977 editing CSV within a text area. Used with a schema node which is
1978 a sequence of tuples.
1980 **Attributes/Arguments**
1982 cols
1983 The size, in columns, of the text input field. Defaults to
1984 ``None``, meaning that the ``cols`` is not included in the
1985 widget output (uses browser default cols).
1987 rows
1988 The size, in rows, of the text input field. Defaults to
1989 ``None``, meaning that the ``rows`` is not included in the
1990 widget output (uses browser default cols).
1992 template
1993 The template name used to render the widget. Default:
1994 ``textarea``.
1996 readonly_template
1997 The template name used to render the widget in read-only mode.
1998 Default: ``readonly/textarea``.
2000 delimiter
2001 The csv module delimiter character.
2002 Default: ``,``.
2004 quotechar
2005 The csv module quoting character.
2006 Default: ``"``.
2008 quoting
2009 The csv module quoting dialect.
2010 Default: ``csv.QUOTE_MINIMAL``.
2011 """
2013 template = "textarea"
2014 readonly_template = "readonly/textarea"
2015 cols = None
2016 rows = None
2017 delimiter = ","
2018 quotechar = '"'
2019 quoting = csv.QUOTE_MINIMAL
2021 def serialize(self, field, cstruct, **kw):
2022 # XXX make cols and rows overrideable
2023 if cstruct is null:
2024 cstruct = []
2025 textrows = getattr(field, "unparseable", None)
2026 if textrows is None:
2027 outfile = StringIO()
2028 writer = csv.writer(
2029 outfile,
2030 delimiter=self.delimiter,
2031 quotechar=self.quotechar,
2032 quoting=self.quoting,
2033 )
2034 writer.writerows(cstruct)
2035 textrows = outfile.getvalue()
2036 readonly = kw.get("readonly", self.readonly)
2037 if readonly:
2038 template = self.readonly_template
2039 else:
2040 template = self.template
2041 values = self.get_template_values(field, textrows, kw)
2042 return field.renderer(template, **values)
2044 def deserialize(self, field, pstruct):
2045 if pstruct is null:
2046 return null
2047 elif not isinstance(pstruct, string_types):
2048 raise Invalid(field.schema, "Pstruct is not a string")
2049 if not pstruct.strip():
2050 return null
2051 try:
2052 infile = StringIO(pstruct)
2053 reader = csv.reader(
2054 infile,
2055 delimiter=self.delimiter,
2056 quotechar=self.quotechar,
2057 quoting=self.quoting,
2058 )
2059 rows = list(reader)
2060 except Exception as e:
2061 field.unparseable = pstruct
2062 raise Invalid(field.schema, str(e))
2063 return rows
2065 def handle_error(self, field, error):
2066 msgs = []
2067 if error.msg:
2068 field.error = error
2069 else:
2070 for e in error.children:
2071 msgs.append("line %s: %s" % (e.pos + 1, e))
2072 field.error = Invalid(field.schema, "\n".join(msgs))
2075class TextInputCSVWidget(Widget):
2076 """
2077 Widget used for a tuple of scalars; allows for editing a single
2078 CSV line within a text input. Used with a schema node which is a
2079 tuple composed entirely of scalar values (integers, strings, etc).
2081 **Attributes/Arguments**
2083 template
2084 The template name used to render the widget. Default:
2085 ``textinput``.
2087 readonly_template
2088 The template name used to render the widget in read-only mode.
2089 Default: ``readonly/textinput``.
2091 """
2093 template = "textinput"
2094 readonly_template = "readonly/textinput"
2095 mask = None
2096 mask_placeholder = "_"
2098 def serialize(self, field, cstruct, **kw):
2099 # XXX make size and mask overrideable
2100 if cstruct is null:
2101 cstruct = ""
2102 textrow = getattr(field, "unparseable", None)
2103 if textrow is None:
2104 outfile = StringIO()
2105 writer = csv.writer(outfile)
2106 writer.writerow(cstruct)
2107 textrow = outfile.getvalue().strip()
2108 readonly = kw.get("readonly", self.readonly)
2109 if readonly:
2110 template = self.readonly_template
2111 else:
2112 template = self.template
2113 values = self.get_template_values(field, textrow, kw)
2114 return field.renderer(template, **values)
2116 def deserialize(self, field, pstruct):
2117 if pstruct is null:
2118 return null
2119 elif not isinstance(pstruct, string_types):
2120 raise Invalid(field.schema, "Pstruct is not a string")
2121 if not pstruct.strip():
2122 return null
2123 try:
2124 infile = StringIO(pstruct)
2125 reader = csv.reader(infile)
2126 # row = reader.next()
2127 row = next(reader)
2128 except Exception as e:
2129 field.unparseable = pstruct
2130 raise Invalid(field.schema, str(e))
2131 return row
2133 def handle_error(self, field, error):
2134 msgs = []
2135 if error.msg:
2136 field.error = error
2137 else:
2138 for e in error.children:
2139 msgs.append("%s" % e)
2140 field.error = Invalid(field.schema, "\n".join(msgs))
2143class ResourceRegistry(object):
2144 """A resource registry maps :term:`widget requirement` name/version
2145 pairs to one or more relative resources. A resource registry can
2146 be passed to a :class:`deform.Form` constructor; if a resource
2147 registry is *not* passed to the form constructor, a default
2148 resource registry is used by that form. The default resource
2149 registry contains only mappings from requirement names to
2150 resources required by the built-in Deform widgets (not by any
2151 add-on widgets).
2153 If the ``use_defaults`` flag is True, the default set of Deform
2154 requirement-to-resource mappings is loaded into the registry.
2155 Otherwise, the registry is initialized without any mappings.
2156 """
2158 def __init__(self, use_defaults=True):
2159 if use_defaults is True:
2160 self.registry = default_resources.copy()
2161 else:
2162 self.registry = {}
2164 def set_js_resources(self, requirement, version, *resources):
2165 """Set the Javascript resources for the requirement/version
2166 pair, using ``resources`` as the set of relative resource paths."""
2167 reqt = self.registry.setdefault(requirement, {})
2168 ver = reqt.setdefault(version, {})
2169 ver["js"] = resources
2171 def set_css_resources(self, requirement, version, *resources):
2172 """Set the CSS resources for the requirement/version
2173 pair, using ``resources`` as the set of relative resource paths."""
2174 reqt = self.registry.setdefault(requirement, {})
2175 ver = reqt.setdefault(version, {})
2176 ver["css"] = resources
2178 def __call__(self, requirements):
2179 """Return a dictionary representing the resources required for a
2180 particular set of requirements (as returned by
2181 :meth:`deform.Field.get_widget_requirements`). The dictionary will be
2182 a mapping from resource type (``js`` and ``css`` are both keys in the
2183 dictionary) to a list of asset specifications paths. Each asset
2184 specification is a full path to a static resource in the form
2185 ``package:path``. You can use the paths for each resource type to
2186 inject CSS and Javascript on-demand into the head of dynamic pages that
2187 render Deform forms."""
2188 result = {"js": [], "css": []}
2189 for requirement, version in requirements:
2190 tmp = self.registry.get(requirement)
2191 if tmp is None:
2192 raise ValueError(
2193 "Cannot resolve widget requirement %r" % requirement
2194 )
2195 versioned = tmp.get(version)
2196 if versioned is None:
2197 raise ValueError(
2198 "Cannot resolve widget requirement %r (version %r)"
2199 % ((requirement, version))
2200 )
2201 for thing in ("js", "css"):
2202 sources = versioned.get(thing)
2203 if sources is None:
2204 continue
2205 if isinstance(sources, string_types):
2206 sources = (sources,)
2207 for source in sources:
2208 if source not in result[thing]:
2209 result[thing].append(source)
2211 return result
2214default_resources = {
2215 "jquery.form": {None: {"js": "deform:static/scripts/jquery.form-3.09.js"}},
2216 "jquery.maskedinput": {
2217 None: {"js": "deform:static/scripts/jquery.maskedinput-1.3.1.min.js"}
2218 },
2219 "jquery.maskMoney": {
2220 None: {"js": "deform:static/scripts/jquery.maskMoney-3.1.1.min.js"}
2221 },
2222 "deform": {
2223 None: {
2224 "js": (
2225 "deform:static/scripts/jquery.form-3.09.js",
2226 "deform:static/scripts/deform.js",
2227 )
2228 }
2229 },
2230 "typeahead": {
2231 None: {
2232 "js": "deform:static/scripts/typeahead.min.js",
2233 "css": "deform:static/css/typeahead.css",
2234 }
2235 },
2236 "modernizr": {
2237 None: {
2238 "js": "deform:static/scripts/modernizr.custom.input-types-and-atts.js" # noQA
2239 }
2240 },
2241 "pickadate": {
2242 None: {
2243 "js": (
2244 "deform:static/pickadate/picker.js",
2245 "deform:static/pickadate/picker.date.js",
2246 "deform:static/pickadate/picker.time.js",
2247 "deform:static/pickadate/legacy.js",
2248 ),
2249 "css": (
2250 "deform:static/pickadate/themes/default.css",
2251 "deform:static/pickadate/themes/default.date.css",
2252 "deform:static/pickadate/themes/default.time.css",
2253 ),
2254 }
2255 },
2256}
2258default_resource_registry = ResourceRegistry()