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

285 statements  

1import warnings 

2 

3from markupsafe import escape 

4from markupsafe import Markup 

5 

6from wtforms._compat import get_signature 

7 

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) 

37 

38 

39def clean_key(key): 

40 key = key.rstrip("_") 

41 if key.startswith("data_") or key.startswith("aria_"): 

42 key = key.replace("_", "-") 

43 return key 

44 

45 

46def html_params(**kwargs): 

47 """ 

48 Generate HTML attribute syntax from inputted keyword arguments. 

49 

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. 

54 

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. 

58 

59 >>> html_params(data_attr='user.name', aria_labeledby='name') 

60 'data-attr="user.name" aria-labeledby="name"' 

61 

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. 

66 

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"' 

71 

72 .. versionchanged:: 3.0 

73 ``aria_`` args convert underscores to hyphens like ``data_`` 

74 args. 

75 

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) 

91 

92 

93class ListWidget: 

94 """ 

95 Render a list of fields as a :mdn-tag:`ul` or :mdn-tag:`ol`. 

96 

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. 

100 

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 """ 

105 

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 

110 

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)) 

121 

122 

123class TableWidget: 

124 """ 

125 Render a list of fields as a :mdn-tag:`table`. 

126 

127 If `with_table_tag` is True, then an enclosing :mdn-tag:`table` is placed 

128 around the rows. 

129 

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 """ 

134 

135 def __init__(self, with_table_tag=True): 

136 self.with_table_tag = with_table_tag 

137 

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)) 

158 

159 

160class DataListWidget: 

161 """ 

162 Render a :class:`~wtforms.DataList` as a :mdn-tag:`datalist` element. 

163 

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 """ 

171 

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>") 

185 

186 

187class Input: 

188 """ 

189 Render a basic :mdn-tag:`input` field. 

190 

191 This is used as the basis for most of the other input fields. 

192 

193 By default, the `_value()` method will be called upon the associated field 

194 to provide the ``value=`` HTML attribute. 

195 """ 

196 

197 html_params = staticmethod(html_params) 

198 

199 def __init__(self, input_type=None): 

200 if input_type is not None: 

201 self.input_type = input_type 

202 

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}>") 

218 

219 

220class Button: 

221 """ 

222 Render a ``<button>`` element. 

223 

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 """ 

228 

229 html_params = staticmethod(html_params) 

230 input_type = "submit" 

231 validation_attrs = ["disabled"] 

232 

233 def __init__(self, input_type=None): 

234 if input_type is not None: 

235 self.input_type = input_type 

236 

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>") 

248 

249 

250class TextInput(Input): 

251 """ 

252 Render a single-line :mdn-input:`text`. 

253 """ 

254 

255 input_type = "text" 

256 validation_attrs = [ 

257 "required", 

258 "disabled", 

259 "readonly", 

260 "maxlength", 

261 "minlength", 

262 "pattern", 

263 ] 

264 

265 

266class PasswordInput(Input): 

267 """ 

268 Render an :mdn-input:`password`. 

269 

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 """ 

274 

275 input_type = "password" 

276 validation_attrs = [ 

277 "required", 

278 "disabled", 

279 "readonly", 

280 "maxlength", 

281 "minlength", 

282 "pattern", 

283 ] 

284 

285 def __init__(self, hide_value=True): 

286 self.hide_value = hide_value 

287 

288 def __call__(self, field, **kwargs): 

289 if self.hide_value: 

290 kwargs["value"] = "" 

291 return super().__call__(field, **kwargs) 

292 

293 

294class HiddenInput(Input): 

295 """ 

296 Render an :mdn-input:`hidden`. 

297 """ 

298 

299 input_type = "hidden" 

300 validation_attrs = ["disabled"] 

301 

302 def __init__(self, *args, **kwargs): 

303 super().__init__(*args, **kwargs) 

304 self.field_flags = {"hidden": True} 

305 

306 

307class CheckboxInput(Input): 

308 """ 

309 Render an :mdn-input:`checkbox`. 

310 

311 The ``checked`` HTML attribute is set if the field's data is a non-false value. 

312 """ 

313 

314 input_type = "checkbox" 

315 validation_attrs = ["required", "disabled"] 

316 

317 def __call__(self, field, **kwargs): 

318 if getattr(field, "checked", field.data): 

319 kwargs.setdefault("checked", True) 

320 return super().__call__(field, **kwargs) 

321 

322 

323class RadioInput(Input): 

324 """ 

325 Render a single :mdn-input:`radio` button. 

326 

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 """ 

330 

331 input_type = "radio" 

332 validation_attrs = ["required", "disabled"] 

333 

334 def __call__(self, field, **kwargs): 

335 if field.checked: 

336 kwargs.setdefault("checked", True) 

337 return super().__call__(field, **kwargs) 

338 

339 

340class FileInput(Input): 

341 """Render an :mdn-input:`file`. 

342 

343 :param multiple: allow choosing multiple files 

344 """ 

345 

346 input_type = "file" 

347 validation_attrs = ["required", "disabled", "accept"] 

348 

349 def __init__(self, multiple=False): 

350 super().__init__() 

351 self.multiple = multiple 

352 

353 def __call__(self, field, **kwargs): 

354 # browser ignores value of file input for security 

355 kwargs["value"] = False 

356 

357 if self.multiple: 

358 kwargs["multiple"] = True 

359 

360 return super().__call__(field, **kwargs) 

361 

362 

363class SubmitInput(Input): 

364 """ 

365 Renders an :mdn-input:`submit`. 

366 

367 The field's label is used as the text of the submit button instead of the 

368 data on the field. 

369 """ 

370 

371 input_type = "submit" 

372 validation_attrs = ["required", "disabled"] 

373 

374 def __call__(self, field, **kwargs): 

375 kwargs.setdefault("value", field.label.text) 

376 return super().__call__(field, **kwargs) 

377 

378 

379class TextArea: 

380 """ 

381 Renders a multi-line :mdn-tag:`textarea`. 

382 

383 `rows` and `cols` ought to be passed as keyword args when rendering. 

384 """ 

385 

386 validation_attrs = ["required", "disabled", "readonly", "maxlength", "minlength"] 

387 

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 ) 

399 

400 

401class Select: 

402 """ 

403 Renders a :mdn-tag:`select` field. 

404 

405 If `multiple` is True, then the `size` property should be specified on 

406 rendering to make the field useful. 

407 

408 The field must provide an `iter_choices()` method which the widget will 

409 call on rendering; this method must yield :class:`Choice`. 

410 """ 

411 

412 validation_attrs = ["required", "disabled"] 

413 

414 def __init__(self, multiple=False): 

415 self.multiple = multiple 

416 

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)) 

442 

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>") 

453 

454 @classmethod 

455 def _dispatch_render_option(cls): 

456 """Return a callable ``(choice) -> str`` that invokes :meth:`render_option`. 

457 

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()) 

483 

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) 

489 

490 return adapter 

491 

492 

493class Option: 

494 """ 

495 Render an individual :mdn-tag:`option` from a select field. 

496 

497 This is just a convenience for various custom rendering situations, and an 

498 option by itself does not constitute an entire field. 

499 """ 

500 

501 def __call__(self, field, **kwargs): 

502 return Select.render_option(field.choice, **kwargs) 

503 

504 

505class SearchInput(Input): 

506 """ 

507 Render an :mdn-input:`search`. 

508 """ 

509 

510 input_type = "search" 

511 validation_attrs = [ 

512 "required", 

513 "disabled", 

514 "readonly", 

515 "maxlength", 

516 "minlength", 

517 "pattern", 

518 ] 

519 

520 

521class TelInput(Input): 

522 """ 

523 Render an :mdn-input:`tel`. 

524 """ 

525 

526 input_type = "tel" 

527 validation_attrs = [ 

528 "required", 

529 "disabled", 

530 "readonly", 

531 "maxlength", 

532 "minlength", 

533 "pattern", 

534 ] 

535 

536 

537class URLInput(Input): 

538 """ 

539 Render an :mdn-input:`url`. 

540 """ 

541 

542 input_type = "url" 

543 validation_attrs = [ 

544 "required", 

545 "disabled", 

546 "readonly", 

547 "maxlength", 

548 "minlength", 

549 "pattern", 

550 ] 

551 

552 

553class EmailInput(Input): 

554 """ 

555 Render an :mdn-input:`email`. 

556 """ 

557 

558 input_type = "email" 

559 validation_attrs = [ 

560 "required", 

561 "disabled", 

562 "readonly", 

563 "maxlength", 

564 "minlength", 

565 "pattern", 

566 ] 

567 

568 

569class _DateTimeBaseInput(Input): 

570 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"] 

571 

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) 

584 

585 

586class DateTimeInput(_DateTimeBaseInput): 

587 """ 

588 Render an ``<input type="datetime">`` control. 

589 

590 This is a legacy HTML input type. For modern browser support, prefer 

591 :class:`DateTimeLocalInput`. 

592 """ 

593 

594 input_type = "datetime" 

595 

596 

597class DateInput(_DateTimeBaseInput): 

598 """ 

599 Render a :mdn-input:`date`. 

600 """ 

601 

602 input_type = "date" 

603 

604 

605class MonthInput(_DateTimeBaseInput): 

606 """ 

607 Render an :mdn-input:`month`. 

608 """ 

609 

610 input_type = "month" 

611 

612 

613class WeekInput(_DateTimeBaseInput): 

614 """ 

615 Render an :mdn-input:`week`. 

616 """ 

617 

618 input_type = "week" 

619 

620 

621class TimeInput(_DateTimeBaseInput): 

622 """ 

623 Render a :mdn-input:`time`. 

624 """ 

625 

626 input_type = "time" 

627 

628 

629class DateTimeLocalInput(_DateTimeBaseInput): 

630 """ 

631 Render an :mdn-input:`datetime-local`. 

632 """ 

633 

634 input_type = "datetime-local" 

635 

636 

637class NumberInput(Input): 

638 """ 

639 Render an :mdn-input:`number`. 

640 """ 

641 

642 input_type = "number" 

643 validation_attrs = ["required", "disabled", "readonly", "max", "min", "step"] 

644 

645 def __init__(self, step=None, min=None, max=None): 

646 self.step = step 

647 self.min = min 

648 self.max = max 

649 

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) 

658 

659 

660class RangeInput(NumberInput): 

661 """ 

662 Render an :mdn-input:`range`. 

663 """ 

664 

665 input_type = "range" 

666 validation_attrs = ["disabled", "max", "min", "step"] 

667 

668 

669class ColorInput(Input): 

670 """ 

671 Render an :mdn-input:`color`. 

672 """ 

673 

674 input_type = "color" 

675 validation_attrs = ["disabled"]