Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/widgets/core.py: 42%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import warnings
3from markupsafe import escape
4from markupsafe import Markup
6from wtforms._compat import get_signature
8__all__ = (
9 "Button",
10 "CheckboxInput",
11 "ColorInput",
12 "DataListWidget",
13 "DateInput",
14 "DateTimeInput",
15 "DateTimeLocalInput",
16 "EmailInput",
17 "FileInput",
18 "HiddenInput",
19 "ListWidget",
20 "MonthInput",
21 "NumberInput",
22 "Option",
23 "PasswordInput",
24 "RadioInput",
25 "RangeInput",
26 "SearchInput",
27 "Select",
28 "SubmitInput",
29 "TableWidget",
30 "TextArea",
31 "TextInput",
32 "TelInput",
33 "TimeInput",
34 "URLInput",
35 "WeekInput",
36)
39def clean_key(key):
40 key = key.rstrip("_")
41 if key.startswith("data_") or key.startswith("aria_"):
42 key = key.replace("_", "-")
43 return key
46def html_params(**kwargs):
47 """
48 Generate HTML attribute syntax from inputted keyword arguments.
50 The output value is sorted by the passed keys, to provide consistent output
51 each time this function is called with the same parameters. Because of the
52 frequent use of the normally reserved keywords `class` and `for`, suffixing
53 these with an underscore will allow them to be used.
55 In order to facilitate the use of ``data-`` and ``aria-`` attributes, if the
56 name of the attribute begins with ``data_`` or ``aria_``, then every
57 underscore will be replaced with a hyphen in the generated attribute.
59 >>> html_params(data_attr='user.name', aria_labeledby='name')
60 'data-attr="user.name" aria-labeledby="name"'
62 In addition, the values ``True`` and ``False`` are special:
63 * ``attr=True`` generates the HTML compact output of a boolean attribute,
64 e.g. ``checked=True`` will generate simply ``checked``
65 * ``attr=False`` will be ignored and generate no output.
67 >>> html_params(name='text1', id='f', class_='text')
68 'class="text" id="f" name="text1"'
69 >>> html_params(checked=True, readonly=False, name="text1", abc="hello")
70 'abc="hello" checked name="text1"'
72 .. versionchanged:: 3.0
73 ``aria_`` args convert underscores to hyphens like ``data_``
74 args.
76 .. versionchanged:: 2.2
77 ``data_`` args convert all underscores to hyphens, instead of
78 only the first one.
79 """
80 params = []
81 for k, v in sorted(kwargs.items()):
82 k = clean_key(k)
83 if v is True:
84 params.append(k)
85 elif v is False:
86 pass
87 else:
88 v = escape(v).replace(Markup('"'), Markup("""))
89 params.append(Markup('{k}="{v}"').format(k=k, v=v))
90 return Markup(" ").join(params)
93class ListWidget:
94 """
95 Render a list of fields as a :mdn-tag:`ul` or :mdn-tag:`ol`.
97 This is used for fields which encapsulate many inner fields as subfields.
98 The widget will try to iterate the field to get access to the subfields and
99 call them to render them.
101 If `prefix_label` is set, the subfield's label is printed before the field,
102 otherwise afterwards. The latter is useful for iterating radios or
103 checkboxes.
104 """
106 def __init__(self, html_tag="ul", prefix_label=True):
107 assert html_tag in ("ol", "ul")
108 self.html_tag = html_tag
109 self.prefix_label = prefix_label
111 def __call__(self, field, **kwargs):
112 kwargs.setdefault("id", field.id)
113 html = [f"<{self.html_tag} {html_params(**kwargs)}>"]
114 for subfield in field:
115 if self.prefix_label:
116 html.append(f"<li>{subfield.label} {subfield()}</li>")
117 else:
118 html.append(f"<li>{subfield()} {subfield.label}</li>")
119 html.append(f"</{self.html_tag}>")
120 return Markup("".join(html))
123class TableWidget:
124 """
125 Render a list of fields as a :mdn-tag:`table`.
127 If `with_table_tag` is True, then an enclosing :mdn-tag:`table` is placed
128 around the rows.
130 Hidden fields will not be displayed with a row, instead the field will be
131 pushed into a subsequent table row to ensure XHTML validity. Hidden fields
132 at the end of the field list will appear outside the table.
133 """
135 def __init__(self, with_table_tag=True):
136 self.with_table_tag = with_table_tag
138 def __call__(self, field, **kwargs):
139 html = []
140 if self.with_table_tag:
141 kwargs.setdefault("id", field.id)
142 table_params = html_params(**kwargs)
143 html.append(f"<table {table_params}>")
144 hidden = ""
145 for subfield in field:
146 if subfield.type in ("HiddenField", "CSRFTokenField"):
147 hidden += str(subfield)
148 else:
149 html.append(
150 f"<tr><th>{subfield.label}</th><td>{hidden}{subfield}</td></tr>"
151 )
152 hidden = ""
153 if self.with_table_tag:
154 html.append("</table>")
155 if hidden:
156 html.append(hidden)
157 return Markup("".join(html))
160class DataListWidget:
161 """
162 Render a :class:`~wtforms.DataList` as a :mdn-tag:`datalist` element.
164 Used as the default widget for :class:`~wtforms.DataList`. Receives
165 the bound :class:`~wtforms.DataList`, the current ``field`` (when
166 rendered from a field), and any HTML attributes to apply to the
167 ``<datalist>`` element. The DataList's ``render_kw`` is merged with
168 the caller's keyword arguments — caller kwargs win, and the
169 DataList's ``id`` always wins over both.
170 """
172 def __call__(self, datalist, field=None, **kwargs):
173 render_kw = {clean_key(k): v for k, v in datalist.render_kw.items()}
174 kwargs = {clean_key(k): v for k, v in kwargs.items()}
175 attrs = {**render_kw, "id": datalist.id, **kwargs}
176 options = []
177 for choice in datalist.iter_choices(field):
178 option_attrs = {"value": choice.value}
179 if choice.label is not None and choice.label != choice.value:
180 option_attrs["label"] = choice.label
181 if choice.render_kw:
182 option_attrs = {**choice.render_kw, **option_attrs}
183 options.append(f"<option {html_params(**option_attrs)}>")
184 return Markup(f"<datalist {html_params(**attrs)}>{''.join(options)}</datalist>")
187class Input:
188 """
189 Render a basic :mdn-tag:`input` field.
191 This is used as the basis for most of the other input fields.
193 By default, the `_value()` method will be called upon the associated field
194 to provide the ``value=`` HTML attribute.
195 """
197 html_params = staticmethod(html_params)
199 def __init__(self, input_type=None):
200 if input_type is not None:
201 self.input_type = input_type
203 def __call__(self, field, **kwargs):
204 kwargs.setdefault("id", field.id)
205 kwargs.setdefault("type", self.input_type)
206 if "value" not in kwargs:
207 kwargs["value"] = field._value()
208 datalist = getattr(field, "_datalist", None)
209 if datalist is not None and "list" not in kwargs:
210 kwargs["list"] = datalist if isinstance(datalist, str) else datalist.id
211 flags = getattr(field, "flags", {})
212 for k in dir(flags):
213 if k in self.validation_attrs and k not in kwargs:
214 value = getattr(flags, k)
215 kwargs[k] = value() if callable(value) else value
216 input_params = self.html_params(name=field.name, **kwargs)
217 return Markup(f"<input {input_params}>")
220class Button:
221 """
222 Render a ``<button>`` element.
224 Pass ``label=`` when rendering to override the visible button text. The
225 label is HTML-escaped; pass a :class:`markupsafe.Markup` instance to embed
226 HTML (icons, formatted text) in the button content.
227 """
229 html_params = staticmethod(html_params)
230 input_type = "submit"
231 validation_attrs = ["disabled"]
233 def __init__(self, input_type=None):
234 if input_type is not None:
235 self.input_type = input_type
237 def __call__(self, field, **kwargs):
238 label = kwargs.pop("label", field.label.text)
239 kwargs.setdefault("id", field.id)
240 kwargs.setdefault("type", self.input_type)
241 kwargs.setdefault("value", field._value())
242 flags = getattr(field, "flags", {})
243 for k in dir(flags):
244 if k in self.validation_attrs and k not in kwargs:
245 kwargs[k] = getattr(flags, k)
246 button_params = self.html_params(name=field.name, **kwargs)
247 return Markup(f"<button {button_params}>{escape(label)}</button>")
250class TextInput(Input):
251 """
252 Render a single-line :mdn-input:`text`.
253 """
255 input_type = "text"
256 validation_attrs = [
257 "required",
258 "disabled",
259 "readonly",
260 "maxlength",
261 "minlength",
262 "pattern",
263 ]
266class PasswordInput(Input):
267 """
268 Render an :mdn-input:`password`.
270 For security purposes, this field will not reproduce the value on a form
271 submit by default. To have the value filled in, set `hide_value` to
272 `False`.
273 """
275 input_type = "password"
276 validation_attrs = [
277 "required",
278 "disabled",
279 "readonly",
280 "maxlength",
281 "minlength",
282 "pattern",
283 ]
285 def __init__(self, hide_value=True):
286 self.hide_value = hide_value
288 def __call__(self, field, **kwargs):
289 if self.hide_value:
290 kwargs["value"] = ""
291 return super().__call__(field, **kwargs)
294class HiddenInput(Input):
295 """
296 Render an :mdn-input:`hidden`.
297 """
299 input_type = "hidden"
300 validation_attrs = ["disabled"]
302 def __init__(self, *args, **kwargs):
303 super().__init__(*args, **kwargs)
304 self.field_flags = {"hidden": True}
307class CheckboxInput(Input):
308 """
309 Render an :mdn-input:`checkbox`.
311 The ``checked`` HTML attribute is set if the field's data is a non-false value.
312 """
314 input_type = "checkbox"
315 validation_attrs = ["required", "disabled"]
317 def __call__(self, field, **kwargs):
318 if getattr(field, "checked", field.data):
319 kwargs.setdefault("checked", True)
320 return super().__call__(field, **kwargs)
323class RadioInput(Input):
324 """
325 Render a single :mdn-input:`radio` button.
327 This widget is most commonly used in conjunction with ListWidget or some
328 other listing, as singular radio buttons are not very useful.
329 """
331 input_type = "radio"
332 validation_attrs = ["required", "disabled"]
334 def __call__(self, field, **kwargs):
335 if field.checked:
336 kwargs.setdefault("checked", True)
337 return super().__call__(field, **kwargs)
340class FileInput(Input):
341 """Render an :mdn-input:`file`.
343 :param multiple: allow choosing multiple files
344 """
346 input_type = "file"
347 validation_attrs = ["required", "disabled", "accept"]
349 def __init__(self, multiple=False):
350 super().__init__()
351 self.multiple = multiple
353 def __call__(self, field, **kwargs):
354 # browser ignores value of file input for security
355 kwargs["value"] = False
357 if self.multiple:
358 kwargs["multiple"] = True
360 return super().__call__(field, **kwargs)
363class SubmitInput(Input):
364 """
365 Renders an :mdn-input:`submit`.
367 The field's label is used as the text of the submit button instead of the
368 data on the field.
369 """
371 input_type = "submit"
372 validation_attrs = ["required", "disabled"]
374 def __call__(self, field, **kwargs):
375 kwargs.setdefault("value", field.label.text)
376 return super().__call__(field, **kwargs)
379class TextArea:
380 """
381 Renders a multi-line :mdn-tag:`textarea`.
383 `rows` and `cols` ought to be passed as keyword args when rendering.
384 """
386 validation_attrs = ["required", "disabled", "readonly", "maxlength", "minlength"]
388 def __call__(self, field, **kwargs):
389 kwargs.setdefault("id", field.id)
390 flags = getattr(field, "flags", {})
391 for k in dir(flags):
392 if k in self.validation_attrs and k not in kwargs:
393 kwargs[k] = getattr(flags, k)
394 textarea_params = html_params(name=field.name, **kwargs)
395 textarea_innerhtml = escape(field._value())
396 return Markup(
397 f"<textarea {textarea_params}>\r\n{textarea_innerhtml}</textarea>"
398 )
401class Select:
402 """
403 Renders a :mdn-tag:`select` field.
405 If `multiple` is True, then the `size` property should be specified on
406 rendering to make the field useful.
408 The field must provide an `iter_choices()` method which the widget will
409 call on rendering; this method must yield :class:`Choice`.
410 """
412 validation_attrs = ["required", "disabled"]
414 def __init__(self, multiple=False):
415 self.multiple = multiple
417 def __call__(self, field, **kwargs):
418 kwargs.setdefault("id", field.id)
419 if self.multiple:
420 kwargs["multiple"] = True
421 flags = getattr(field, "flags", {})
422 for k in dir(flags):
423 if k in self.validation_attrs and k not in kwargs:
424 kwargs[k] = getattr(flags, k)
425 select_params = html_params(name=field.name, **kwargs)
426 html = [f"<select {select_params}>"]
427 render = type(self)._dispatch_render_option()
428 if field.has_groups():
429 for optgroup, choices in field._iter_groups_normalized():
430 if optgroup is not None:
431 optgroup_params = html_params(label=optgroup)
432 html.append(f"<optgroup {optgroup_params}>")
433 for choice in choices:
434 html.append(render(choice))
435 if optgroup is not None:
436 html.append("</optgroup>")
437 else:
438 for choice in field._iter_choices_normalized():
439 html.append(render(choice))
440 html.append("</select>")
441 return Markup("".join(html))
443 @classmethod
444 def render_option(cls, choice, **kwargs):
445 value = choice.value
446 if isinstance(value, bool):
447 value = str(value)
448 options = {"value": value, **(choice.render_kw or {}), **kwargs}
449 if choice.selected:
450 options["selected"] = True
451 label = escape(choice.label or choice.value)
452 return Markup(f"<option {html_params(**options)}>{label}</option>")
454 @classmethod
455 def _dispatch_render_option(cls):
456 """Return a callable ``(choice) -> str`` that invokes :meth:`render_option`.
458 Picks the right signature for the override.
459 """
460 ro = cls.render_option
461 try:
462 sig = get_signature(ro)
463 except (ValueError, TypeError):
464 return ro
465 positional_required = [
466 p
467 for p in sig.parameters.values()
468 if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
469 and p.default is p.empty
470 ]
471 if len(positional_required) <= 1:
472 return ro
473 warnings.warn(
474 f"{cls.__module__}.{cls.__qualname__}.render_option uses the "
475 "pre-3.3 signature (value, label, selected, **kwargs). Override "
476 "render_option(cls, choice, **kwargs) instead — choice is a "
477 "SelectChoice. The legacy signature will be removed in WTForms "
478 "4.0.",
479 DeprecationWarning,
480 stacklevel=3,
481 )
482 accepts_kwargs = any(p.kind == p.VAR_KEYWORD for p in sig.parameters.values())
484 def adapter(choice):
485 args = (choice.value, choice.label or choice.value, choice.selected)
486 if accepts_kwargs:
487 return ro(*args, **(choice.render_kw or {}))
488 return ro(*args)
490 return adapter
493class Option:
494 """
495 Render an individual :mdn-tag:`option` from a select field.
497 This is just a convenience for various custom rendering situations, and an
498 option by itself does not constitute an entire field.
499 """
501 def __call__(self, field, **kwargs):
502 return Select.render_option(field.choice, **kwargs)
505class SearchInput(Input):
506 """
507 Render an :mdn-input:`search`.
508 """
510 input_type = "search"
511 validation_attrs = [
512 "required",
513 "disabled",
514 "readonly",
515 "maxlength",
516 "minlength",
517 "pattern",
518 ]
521class TelInput(Input):
522 """
523 Render an :mdn-input:`tel`.
524 """
526 input_type = "tel"
527 validation_attrs = [
528 "required",
529 "disabled",
530 "readonly",
531 "maxlength",
532 "minlength",
533 "pattern",
534 ]
537class URLInput(Input):
538 """
539 Render an :mdn-input:`url`.
540 """
542 input_type = "url"
543 validation_attrs = [
544 "required",
545 "disabled",
546 "readonly",
547 "maxlength",
548 "minlength",
549 "pattern",
550 ]
553class EmailInput(Input):
554 """
555 Render an :mdn-input:`email`.
556 """
558 input_type = "email"
559 validation_attrs = [
560 "required",
561 "disabled",
562 "readonly",
563 "maxlength",
564 "minlength",
565 "pattern",
566 ]
569class _DateTimeBaseInput(Input):
570 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]
572 def __call__(self, field, **kwargs):
573 format = getattr(field, "format", None)
574 if format is not None:
575 format = format[0] if isinstance(format, list) else format
576 flags = getattr(field, "flags", {})
577 for attr in ("min", "max"):
578 value = kwargs.get(attr, getattr(flags, attr, None))
579 if callable(value):
580 value = value()
581 if hasattr(value, "strftime"):
582 kwargs[attr] = value.strftime(format)
583 return super().__call__(field, **kwargs)
586class DateTimeInput(_DateTimeBaseInput):
587 """
588 Render an ``<input type="datetime">`` control.
590 This is a legacy HTML input type. For modern browser support, prefer
591 :class:`DateTimeLocalInput`.
592 """
594 input_type = "datetime"
597class DateInput(_DateTimeBaseInput):
598 """
599 Render a :mdn-input:`date`.
600 """
602 input_type = "date"
605class MonthInput(_DateTimeBaseInput):
606 """
607 Render an :mdn-input:`month`.
608 """
610 input_type = "month"
613class WeekInput(_DateTimeBaseInput):
614 """
615 Render an :mdn-input:`week`.
616 """
618 input_type = "week"
621class TimeInput(_DateTimeBaseInput):
622 """
623 Render a :mdn-input:`time`.
624 """
626 input_type = "time"
629class DateTimeLocalInput(_DateTimeBaseInput):
630 """
631 Render an :mdn-input:`datetime-local`.
632 """
634 input_type = "datetime-local"
637class NumberInput(Input):
638 """
639 Render an :mdn-input:`number`.
640 """
642 input_type = "number"
643 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]
645 def __init__(self, step=None, min=None, max=None):
646 self.step = step
647 self.min = min
648 self.max = max
650 def __call__(self, field, **kwargs):
651 if self.step is not None:
652 kwargs.setdefault("step", self.step)
653 if self.min is not None:
654 kwargs.setdefault("min", self.min)
655 if self.max is not None:
656 kwargs.setdefault("max", self.max)
657 return super().__call__(field, **kwargs)
660class RangeInput(NumberInput):
661 """
662 Render an :mdn-input:`range`.
663 """
665 input_type = "range"
666 validation_attrs = ["disabled", "max", "min", "step"]
669class ColorInput(Input):
670 """
671 Render an :mdn-input:`color`.
672 """
674 input_type = "color"
675 validation_attrs = ["disabled"]