codekingpro/portable-devtools
114k
1import warnings2 3from markupsafe import escape4from markupsafe import Markup5 6__all__ = (7 "CheckboxInput",8 "ColorInput",9 "DateInput",10 "DateTimeInput",11 "DateTimeLocalInput",12 "EmailInput",13 "FileInput",14 "HiddenInput",15 "ListWidget",16 "MonthInput",17 "NumberInput",18 "Option",19 "PasswordInput",20 "RadioInput",21 "RangeInput",22 "SearchInput",23 "Select",24 "SubmitInput",25 "TableWidget",26 "TextArea",27 "TextInput",28 "TelInput",29 "TimeInput",30 "URLInput",31 "WeekInput",32)33 34 35def clean_key(key):36 key = key.rstrip("_")37 if key.startswith("data_") or key.startswith("aria_"):38 key = key.replace("_", "-")39 return key40 41 42def html_params(**kwargs):43 """44 Generate HTML attribute syntax from inputted keyword arguments.45 46 The output value is sorted by the passed keys, to provide consistent output47 each time this function is called with the same parameters. Because of the48 frequent use of the normally reserved keywords `class` and `for`, suffixing49 these with an underscore will allow them to be used.50 51 In order to facilitate the use of ``data-`` and ``aria-`` attributes, if the52 name of the attribute begins with ``data_`` or ``aria_``, then every53 underscore will be replaced with a hyphen in the generated attribute.54 55 >>> html_params(data_attr='user.name', aria_labeledby='name')56 'data-attr="user.name" aria-labeledby="name"'57 58 In addition, the values ``True`` and ``False`` are special:59 * ``attr=True`` generates the HTML compact output of a boolean attribute,60 e.g. ``checked=True`` will generate simply ``checked``61 * ``attr=False`` will be ignored and generate no output.62 63 >>> html_params(name='text1', id='f', class_='text')64 'class="text" id="f" name="text1"'65 >>> html_params(checked=True, readonly=False, name="text1", abc="hello")66 'abc="hello" checked name="text1"'67 68 .. versionchanged:: 3.069 ``aria_`` args convert underscores to hyphens like ``data_``70 args.71 72 .. versionchanged:: 2.273 ``data_`` args convert all underscores to hyphens, instead of74 only the first one.75 """76 params = []77 for k, v in sorted(kwargs.items()):78 k = clean_key(k)79 if v is True:80 params.append(k)81 elif v is False:82 pass83 else:84 params.append(f'{str(k)}="{escape(v)}"') # noqa: B90785 return " ".join(params)86 87 88class ListWidget:89 """90 Renders a list of fields as a `ul` or `ol` list.91 92 This is used for fields which encapsulate many inner fields as subfields.93 The widget will try to iterate the field to get access to the subfields and94 call them to render them.95 96 If `prefix_label` is set, the subfield's label is printed before the field,97 otherwise afterwards. The latter is useful for iterating radios or98 checkboxes.99 """100 101 def __init__(self, html_tag="ul", prefix_label=True):102 assert html_tag in ("ol", "ul")103 self.html_tag = html_tag104 self.prefix_label = prefix_label105 106 def __call__(self, field, **kwargs):107 kwargs.setdefault("id", field.id)108 html = [f"<{self.html_tag} {html_params(**kwargs)}>"]109 for subfield in field:110 if self.prefix_label:111 html.append(f"<li>{subfield.label} {subfield()}</li>")112 else:113 html.append(f"<li>{subfield()} {subfield.label}</li>")114 html.append("</%s>" % self.html_tag)115 return Markup("".join(html))116 117 118class TableWidget:119 """120 Renders a list of fields as a set of table rows with th/td pairs.121 122 If `with_table_tag` is True, then an enclosing <table> is placed around the123 rows.124 125 Hidden fields will not be displayed with a row, instead the field will be126 pushed into a subsequent table row to ensure XHTML validity. Hidden fields127 at the end of the field list will appear outside the table.128 """129 130 def __init__(self, with_table_tag=True):131 self.with_table_tag = with_table_tag132 133 def __call__(self, field, **kwargs):134 html = []135 if self.with_table_tag:136 kwargs.setdefault("id", field.id)137 html.append("<table %s>" % html_params(**kwargs))138 hidden = ""139 for subfield in field:140 if subfield.type in ("HiddenField", "CSRFTokenField"):141 hidden += str(subfield)142 else:143 html.append(144 "<tr><th>%s</th><td>%s%s</td></tr>"145 % (str(subfield.label), hidden, str(subfield))146 )147 hidden = ""148 if self.with_table_tag:149 html.append("</table>")150 if hidden:151 html.append(hidden)152 return Markup("".join(html))153 154 155class Input:156 """157 Render a basic ``<input>`` field.158 159 This is used as the basis for most of the other input fields.160 161 By default, the `_value()` method will be called upon the associated field162 to provide the ``value=`` HTML attribute.163 """164 165 html_params = staticmethod(html_params)166 validation_attrs = ["required", "disabled"]167 168 def __init__(self, input_type=None):169 if input_type is not None:170 self.input_type = input_type171 172 def __call__(self, field, **kwargs):173 kwargs.setdefault("id", field.id)174 kwargs.setdefault("type", self.input_type)175 if "value" not in kwargs:176 kwargs["value"] = field._value()177 flags = getattr(field, "flags", {})178 for k in dir(flags):179 if k in self.validation_attrs and k not in kwargs:180 kwargs[k] = getattr(flags, k)181 return Markup("<input %s>" % self.html_params(name=field.name, **kwargs))182 183 184class TextInput(Input):185 """186 Render a single-line text input.187 """188 189 input_type = "text"190 validation_attrs = [191 "required",192 "disabled",193 "readonly",194 "maxlength",195 "minlength",196 "pattern",197 ]198 199 200class PasswordInput(Input):201 """202 Render a password input.203 204 For security purposes, this field will not reproduce the value on a form205 submit by default. To have the value filled in, set `hide_value` to206 `False`.207 """208 209 input_type = "password"210 validation_attrs = [211 "required",212 "disabled",213 "readonly",214 "maxlength",215 "minlength",216 "pattern",217 ]218 219 def __init__(self, hide_value=True):220 self.hide_value = hide_value221 222 def __call__(self, field, **kwargs):223 if self.hide_value:224 kwargs["value"] = ""225 return super().__call__(field, **kwargs)226 227 228class HiddenInput(Input):229 """230 Render a hidden input.231 """232 233 input_type = "hidden"234 235 def __init__(self, *args, **kwargs):236 super().__init__(*args, **kwargs)237 self.field_flags = {"hidden": True}238 239 240class CheckboxInput(Input):241 """242 Render a checkbox.243 244 The ``checked`` HTML attribute is set if the field's data is a non-false value.245 """246 247 input_type = "checkbox"248 249 def __call__(self, field, **kwargs):250 if getattr(field, "checked", field.data):251 kwargs["checked"] = True252 return super().__call__(field, **kwargs)253 254 255class RadioInput(Input):256 """257 Render a single radio button.258 259 This widget is most commonly used in conjunction with ListWidget or some260 other listing, as singular radio buttons are not very useful.261 """262 263 input_type = "radio"264 265 def __call__(self, field, **kwargs):266 if field.checked:267 kwargs["checked"] = True268 return super().__call__(field, **kwargs)269 270 271class FileInput(Input):272 """Render a file chooser input.273 274 :param multiple: allow choosing multiple files275 """276 277 input_type = "file"278 validation_attrs = ["required", "disabled", "accept"]279 280 def __init__(self, multiple=False):281 super().__init__()282 self.multiple = multiple283 284 def __call__(self, field, **kwargs):285 # browser ignores value of file input for security286 kwargs["value"] = False287 288 if self.multiple:289 kwargs["multiple"] = True290 291 return super().__call__(field, **kwargs)292 293 294class SubmitInput(Input):295 """296 Renders a submit button.297 298 The field's label is used as the text of the submit button instead of the299 data on the field.300 """301 302 input_type = "submit"303 304 def __call__(self, field, **kwargs):305 kwargs.setdefault("value", field.label.text)306 return super().__call__(field, **kwargs)307 308 309class TextArea:310 """311 Renders a multi-line text area.312 313 `rows` and `cols` ought to be passed as keyword args when rendering.314 """315 316 validation_attrs = ["required", "disabled", "readonly", "maxlength", "minlength"]317 318 def __call__(self, field, **kwargs):319 kwargs.setdefault("id", field.id)320 flags = getattr(field, "flags", {})321 for k in dir(flags):322 if k in self.validation_attrs and k not in kwargs:323 kwargs[k] = getattr(flags, k)324 return Markup(325 "<textarea %s>\r\n%s</textarea>"326 % (html_params(name=field.name, **kwargs), escape(field._value()))327 )328 329 330class Select:331 """332 Renders a select field.333 334 If `multiple` is True, then the `size` property should be specified on335 rendering to make the field useful.336 337 The field must provide an `iter_choices()` method which the widget will338 call on rendering; this method must yield tuples of339 `(value, label, selected)` or `(value, label, selected, render_kw)`.340 It also must provide a `has_groups()` method which tells whether choices341 are divided into groups, and if they do, the field must have an342 `iter_groups()` method that yields tuples of `(label, choices)`, where343 `choices` is a iterable of `(value, label, selected)` tuples.344 """345 346 validation_attrs = ["required", "disabled"]347 348 def __init__(self, multiple=False):349 self.multiple = multiple350 351 def __call__(self, field, **kwargs):352 kwargs.setdefault("id", field.id)353 if self.multiple:354 kwargs["multiple"] = True355 flags = getattr(field, "flags", {})356 for k in dir(flags):357 if k in self.validation_attrs and k not in kwargs:358 kwargs[k] = getattr(flags, k)359 html = ["<select %s>" % html_params(name=field.name, **kwargs)]360 if field.has_groups():361 for group, choices in field.iter_groups():362 html.append("<optgroup %s>" % html_params(label=group))363 for choice in choices:364 if len(choice) == 4:365 val, label, selected, render_kw = choice366 else:367 warnings.warn(368 "'iter_groups' is expected to return 4 items tuple since "369 "wtforms 3.1, this will be mandatory in wtforms 3.2",370 DeprecationWarning,371 stacklevel=2,372 )373 val, label, selected = choice374 render_kw = {}375 html.append(self.render_option(val, label, selected, **render_kw))376 html.append("</optgroup>")377 else:378 for choice in field.iter_choices():379 if len(choice) == 4:380 val, label, selected, render_kw = choice381 else:382 warnings.warn(383 "'iter_groups' is expected to return 4 items tuple since "384 "wtforms 3.1, this will be mandatory in wtforms 3.2",385 DeprecationWarning,386 stacklevel=2,387 )388 val, label, selected = choice389 render_kw = {}390 html.append(self.render_option(val, label, selected, **render_kw))391 html.append("</select>")392 return Markup("".join(html))393 394 @classmethod395 def render_option(cls, value, label, selected, **kwargs):396 if value is True:397 # Handle the special case of a 'True' value.398 value = str(value)399 400 options = dict(kwargs, value=value)401 if selected:402 options["selected"] = True403 return Markup(f"<option {html_params(**options)}>{escape(label)}</option>")404 405 406class Option:407 """408 Renders the individual option from a select field.409 410 This is just a convenience for various custom rendering situations, and an411 option by itself does not constitute an entire field.412 """413 414 def __call__(self, field, **kwargs):415 return Select.render_option(416 field._value(), field.label.text, field.checked, **kwargs417 )418 419 420class SearchInput(Input):421 """422 Renders an input with type "search".423 """424 425 input_type = "search"426 validation_attrs = [427 "required",428 "disabled",429 "readonly",430 "maxlength",431 "minlength",432 "pattern",433 ]434 435 436class TelInput(Input):437 """438 Renders an input with type "tel".439 """440 441 input_type = "tel"442 validation_attrs = [443 "required",444 "disabled",445 "readonly",446 "maxlength",447 "minlength",448 "pattern",449 ]450 451 452class URLInput(Input):453 """454 Renders an input with type "url".455 """456 457 input_type = "url"458 validation_attrs = [459 "required",460 "disabled",461 "readonly",462 "maxlength",463 "minlength",464 "pattern",465 ]466 467 468class EmailInput(Input):469 """470 Renders an input with type "email".471 """472 473 input_type = "email"474 validation_attrs = [475 "required",476 "disabled",477 "readonly",478 "maxlength",479 "minlength",480 "pattern",481 ]482 483 484class DateTimeInput(Input):485 """486 Renders an input with type "datetime".487 """488 489 input_type = "datetime"490 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]491 492 493class DateInput(Input):494 """495 Renders an input with type "date".496 """497 498 input_type = "date"499 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]500 501 502class MonthInput(Input):503 """504 Renders an input with type "month".505 """506 507 input_type = "month"508 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]509 510 511class WeekInput(Input):512 """513 Renders an input with type "week".514 """515 516 input_type = "week"517 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]518 519 520class TimeInput(Input):521 """522 Renders an input with type "time".523 """524 525 input_type = "time"526 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]527 528 529class DateTimeLocalInput(Input):530 """531 Renders an input with type "datetime-local".532 """533 534 input_type = "datetime-local"535 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]536 537 538class NumberInput(Input):539 """540 Renders an input with type "number".541 """542 543 input_type = "number"544 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"]545 546 def __init__(self, step=None, min=None, max=None):547 self.step = step548 self.min = min549 self.max = max550 551 def __call__(self, field, **kwargs):552 if self.step is not None:553 kwargs.setdefault("step", self.step)554 if self.min is not None:555 kwargs.setdefault("min", self.min)556 if self.max is not None:557 kwargs.setdefault("max", self.max)558 return super().__call__(field, **kwargs)559 560 561class RangeInput(Input):562 """563 Renders an input with type "range".564 """565 566 input_type = "range"567 validation_attrs = ["required", "disabled", "max", "min", "step"]568 569 def __init__(self, step=None):570 self.step = step571 572 def __call__(self, field, **kwargs):573 if self.step is not None:574 kwargs.setdefault("step", self.step)575 return super().__call__(field, **kwargs)576 577 578class ColorInput(Input):579 """580 Renders an input with type "color".581 """582 583 input_type = "color"584 