Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/widgets/core.py: 51%

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

223 statements  

1from markupsafe import escape 

2from markupsafe import Markup 

3 

4__all__ = ( 

5 "CheckboxInput", 

6 "ColorInput", 

7 "DateInput", 

8 "DateTimeInput", 

9 "DateTimeLocalInput", 

10 "EmailInput", 

11 "FileInput", 

12 "HiddenInput", 

13 "ListWidget", 

14 "MonthInput", 

15 "NumberInput", 

16 "Option", 

17 "PasswordInput", 

18 "RadioInput", 

19 "RangeInput", 

20 "SearchInput", 

21 "Select", 

22 "SubmitInput", 

23 "TableWidget", 

24 "TextArea", 

25 "TextInput", 

26 "TelInput", 

27 "TimeInput", 

28 "URLInput", 

29 "WeekInput", 

30) 

31 

32 

33def clean_key(key): 

34 key = key.rstrip("_") 

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

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

37 return key 

38 

39 

40def html_params(**kwargs): 

41 """ 

42 Generate HTML attribute syntax from inputted keyword arguments. 

43 

44 The output value is sorted by the passed keys, to provide consistent output 

45 each time this function is called with the same parameters. Because of the 

46 frequent use of the normally reserved keywords `class` and `for`, suffixing 

47 these with an underscore will allow them to be used. 

48 

49 In order to facilitate the use of ``data-`` and ``aria-`` attributes, if the 

50 name of the attribute begins with ``data_`` or ``aria_``, then every 

51 underscore will be replaced with a hyphen in the generated attribute. 

52 

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

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

55 

56 In addition, the values ``True`` and ``False`` are special: 

57 * ``attr=True`` generates the HTML compact output of a boolean attribute, 

58 e.g. ``checked=True`` will generate simply ``checked`` 

59 * ``attr=False`` will be ignored and generate no output. 

60 

61 >>> html_params(name='text1', id='f', class_='text') 

62 'class="text" id="f" name="text1"' 

63 >>> html_params(checked=True, readonly=False, name="text1", abc="hello") 

64 'abc="hello" checked name="text1"' 

65 

66 .. versionchanged:: 3.0 

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

68 args. 

69 

70 .. versionchanged:: 2.2 

71 ``data_`` args convert all underscores to hyphens, instead of 

72 only the first one. 

73 """ 

74 params = [] 

75 for k, v in sorted(kwargs.items()): 

76 k = clean_key(k) 

77 if v is True: 

78 params.append(k) 

79 elif v is False: 

80 pass 

81 else: 

82 params.append(f'{str(k)}="{escape(v)}"') # noqa: B907 

83 return " ".join(params) 

84 

85 

86class ListWidget: 

87 """ 

88 Renders a list of fields as a `ul` or `ol` list. 

89 

90 This is used for fields which encapsulate many inner fields as subfields. 

91 The widget will try to iterate the field to get access to the subfields and 

92 call them to render them. 

93 

94 If `prefix_label` is set, the subfield's label is printed before the field, 

95 otherwise afterwards. The latter is useful for iterating radios or 

96 checkboxes. 

97 """ 

98 

99 def __init__(self, html_tag="ul", prefix_label=True): 

100 assert html_tag in ("ol", "ul") 

101 self.html_tag = html_tag 

102 self.prefix_label = prefix_label 

103 

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

105 kwargs.setdefault("id", field.id) 

106 html = [f"<{self.html_tag} {html_params(**kwargs)}>"] 

107 for subfield in field: 

108 if self.prefix_label: 

109 html.append(f"<li>{subfield.label} {subfield()}</li>") 

110 else: 

111 html.append(f"<li>{subfield()} {subfield.label}</li>") 

112 html.append(f"</{self.html_tag}>") 

113 return Markup("".join(html)) 

114 

115 

116class TableWidget: 

117 """ 

118 Renders a list of fields as a set of table rows with th/td pairs. 

119 

120 If `with_table_tag` is True, then an enclosing <table> is placed around the 

121 rows. 

122 

123 Hidden fields will not be displayed with a row, instead the field will be 

124 pushed into a subsequent table row to ensure XHTML validity. Hidden fields 

125 at the end of the field list will appear outside the table. 

126 """ 

127 

128 def __init__(self, with_table_tag=True): 

129 self.with_table_tag = with_table_tag 

130 

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

132 html = [] 

133 if self.with_table_tag: 

134 kwargs.setdefault("id", field.id) 

135 table_params = html_params(**kwargs) 

136 html.append(f"<table {table_params}>") 

137 hidden = "" 

138 for subfield in field: 

139 if subfield.type in ("HiddenField", "CSRFTokenField"): 

140 hidden += str(subfield) 

141 else: 

142 html.append( 

143 f"<tr><th>{subfield.label}</th><td>{hidden}{subfield}</td></tr>" 

144 ) 

145 hidden = "" 

146 if self.with_table_tag: 

147 html.append("</table>") 

148 if hidden: 

149 html.append(hidden) 

150 return Markup("".join(html)) 

151 

152 

153class Input: 

154 """ 

155 Render a basic ``<input>`` field. 

156 

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

158 

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

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

161 """ 

162 

163 html_params = staticmethod(html_params) 

164 

165 def __init__(self, input_type=None): 

166 if input_type is not None: 

167 self.input_type = input_type 

168 

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

170 kwargs.setdefault("id", field.id) 

171 kwargs.setdefault("type", self.input_type) 

172 if "value" not in kwargs: 

173 kwargs["value"] = field._value() 

174 flags = getattr(field, "flags", {}) 

175 for k in dir(flags): 

176 if k in self.validation_attrs and k not in kwargs: 

177 kwargs[k] = getattr(flags, k) 

178 input_params = self.html_params(name=field.name, **kwargs) 

179 return Markup(f"<input {input_params}>") 

180 

181 

182class TextInput(Input): 

183 """ 

184 Render a single-line text input. 

185 """ 

186 

187 input_type = "text" 

188 validation_attrs = [ 

189 "required", 

190 "disabled", 

191 "readonly", 

192 "maxlength", 

193 "minlength", 

194 "pattern", 

195 ] 

196 

197 

198class PasswordInput(Input): 

199 """ 

200 Render a password input. 

201 

202 For security purposes, this field will not reproduce the value on a form 

203 submit by default. To have the value filled in, set `hide_value` to 

204 `False`. 

205 """ 

206 

207 input_type = "password" 

208 validation_attrs = [ 

209 "required", 

210 "disabled", 

211 "readonly", 

212 "maxlength", 

213 "minlength", 

214 "pattern", 

215 ] 

216 

217 def __init__(self, hide_value=True): 

218 self.hide_value = hide_value 

219 

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

221 if self.hide_value: 

222 kwargs["value"] = "" 

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

224 

225 

226class HiddenInput(Input): 

227 """ 

228 Render a hidden input. 

229 """ 

230 

231 input_type = "hidden" 

232 validation_attrs = ["disabled"] 

233 

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

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

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

237 

238 

239class CheckboxInput(Input): 

240 """ 

241 Render a checkbox. 

242 

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

244 """ 

245 

246 input_type = "checkbox" 

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

248 

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

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

251 kwargs["checked"] = True 

252 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 some 

260 other listing, as singular radio buttons are not very useful. 

261 """ 

262 

263 input_type = "radio" 

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

265 

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

267 if field.checked: 

268 kwargs["checked"] = True 

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

270 

271 

272class FileInput(Input): 

273 """Render a file chooser input. 

274 

275 :param multiple: allow choosing multiple files 

276 """ 

277 

278 input_type = "file" 

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

280 

281 def __init__(self, multiple=False): 

282 super().__init__() 

283 self.multiple = multiple 

284 

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

286 # browser ignores value of file input for security 

287 kwargs["value"] = False 

288 

289 if self.multiple: 

290 kwargs["multiple"] = True 

291 

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

293 

294 

295class SubmitInput(Input): 

296 """ 

297 Renders a submit button. 

298 

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

300 data on the field. 

301 """ 

302 

303 input_type = "submit" 

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

305 

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

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

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

309 

310 

311class TextArea: 

312 """ 

313 Renders a multi-line text area. 

314 

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

316 """ 

317 

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

319 

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

321 kwargs.setdefault("id", field.id) 

322 flags = getattr(field, "flags", {}) 

323 for k in dir(flags): 

324 if k in self.validation_attrs and k not in kwargs: 

325 kwargs[k] = getattr(flags, k) 

326 textarea_params = html_params(name=field.name, **kwargs) 

327 textarea_innerhtml = escape(field._value()) 

328 return Markup( 

329 f"<textarea {textarea_params}>\r\n{textarea_innerhtml}</textarea>" 

330 ) 

331 

332 

333class Select: 

334 """ 

335 Renders a select field. 

336 

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

338 rendering to make the field useful. 

339 

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

341 call on rendering; this method must yield tuples of 

342 `(value, label, selected)` or `(value, label, selected, render_kw)`. 

343 It also must provide a `has_groups()` method which tells whether choices 

344 are divided into groups, and if they do, the field must have an 

345 `iter_groups()` method that yields tuples of `(label, choices)`, where 

346 `choices` is a iterable of `(value, label, selected)` tuples. 

347 """ 

348 

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

350 

351 def __init__(self, multiple=False): 

352 self.multiple = multiple 

353 

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

355 kwargs.setdefault("id", field.id) 

356 if self.multiple: 

357 kwargs["multiple"] = True 

358 flags = getattr(field, "flags", {}) 

359 for k in dir(flags): 

360 if k in self.validation_attrs and k not in kwargs: 

361 kwargs[k] = getattr(flags, k) 

362 select_params = html_params(name=field.name, **kwargs) 

363 html = [f"<select {select_params}>"] 

364 if field.has_groups(): 

365 for group, choices in field.iter_groups(): 

366 optgroup_params = html_params(label=group) 

367 html.append(f"<optgroup {optgroup_params}>") 

368 for choice in choices: 

369 val, label, selected, render_kw = choice 

370 html.append(self.render_option(val, label, selected, **render_kw)) 

371 html.append("</optgroup>") 

372 else: 

373 for choice in field.iter_choices(): 

374 val, label, selected, render_kw = choice 

375 html.append(self.render_option(val, label, selected, **render_kw)) 

376 html.append("</select>") 

377 return Markup("".join(html)) 

378 

379 @classmethod 

380 def render_option(cls, value, label, selected, **kwargs): 

381 if value is True: 

382 # Handle the special case of a 'True' value. 

383 value = str(value) 

384 

385 options = dict(kwargs, value=value) 

386 if selected: 

387 options["selected"] = True 

388 return Markup(f"<option {html_params(**options)}>{escape(label)}</option>") 

389 

390 

391class Option: 

392 """ 

393 Renders the individual option from a select field. 

394 

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

396 option by itself does not constitute an entire field. 

397 """ 

398 

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

400 return Select.render_option( 

401 field._value(), field.label.text, field.checked, **kwargs 

402 ) 

403 

404 

405class SearchInput(Input): 

406 """ 

407 Renders an input with type "search". 

408 """ 

409 

410 input_type = "search" 

411 validation_attrs = [ 

412 "required", 

413 "disabled", 

414 "readonly", 

415 "maxlength", 

416 "minlength", 

417 "pattern", 

418 ] 

419 

420 

421class TelInput(Input): 

422 """ 

423 Renders an input with type "tel". 

424 """ 

425 

426 input_type = "tel" 

427 validation_attrs = [ 

428 "required", 

429 "disabled", 

430 "readonly", 

431 "maxlength", 

432 "minlength", 

433 "pattern", 

434 ] 

435 

436 

437class URLInput(Input): 

438 """ 

439 Renders an input with type "url". 

440 """ 

441 

442 input_type = "url" 

443 validation_attrs = [ 

444 "required", 

445 "disabled", 

446 "readonly", 

447 "maxlength", 

448 "minlength", 

449 "pattern", 

450 ] 

451 

452 

453class EmailInput(Input): 

454 """ 

455 Renders an input with type "email". 

456 """ 

457 

458 input_type = "email" 

459 validation_attrs = [ 

460 "required", 

461 "disabled", 

462 "readonly", 

463 "maxlength", 

464 "minlength", 

465 "pattern", 

466 ] 

467 

468 

469class DateTimeInput(Input): 

470 """ 

471 Renders an input with type "datetime". 

472 """ 

473 

474 input_type = "datetime" 

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

476 

477 

478class DateInput(Input): 

479 """ 

480 Renders an input with type "date". 

481 """ 

482 

483 input_type = "date" 

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

485 

486 

487class MonthInput(Input): 

488 """ 

489 Renders an input with type "month". 

490 """ 

491 

492 input_type = "month" 

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

494 

495 

496class WeekInput(Input): 

497 """ 

498 Renders an input with type "week". 

499 """ 

500 

501 input_type = "week" 

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

503 

504 

505class TimeInput(Input): 

506 """ 

507 Renders an input with type "time". 

508 """ 

509 

510 input_type = "time" 

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

512 

513 

514class DateTimeLocalInput(Input): 

515 """ 

516 Renders an input with type "datetime-local". 

517 """ 

518 

519 input_type = "datetime-local" 

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

521 

522 

523class NumberInput(Input): 

524 """ 

525 Renders an input with type "number". 

526 """ 

527 

528 input_type = "number" 

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

530 

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

532 self.step = step 

533 self.min = min 

534 self.max = max 

535 

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

537 if self.step is not None: 

538 kwargs.setdefault("step", self.step) 

539 if self.min is not None: 

540 kwargs.setdefault("min", self.min) 

541 if self.max is not None: 

542 kwargs.setdefault("max", self.max) 

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

544 

545 

546class RangeInput(Input): 

547 """ 

548 Renders an input with type "range". 

549 """ 

550 

551 input_type = "range" 

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

553 

554 def __init__(self, step=None): 

555 self.step = step 

556 

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

558 if self.step is not None: 

559 kwargs.setdefault("step", self.step) 

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

561 

562 

563class ColorInput(Input): 

564 """ 

565 Renders an input with type "color". 

566 """ 

567 

568 input_type = "color" 

569 validation_attrs = ["disabled"]