Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/validators.py: 47%

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

378 statements  

1import ipaddress 

2import math 

3import re 

4import uuid 

5from collections.abc import Callable 

6from datetime import date 

7from datetime import datetime 

8from datetime import time 

9from urllib.parse import urlparse 

10 

11__all__ = ( 

12 "DataRequired", 

13 "data_required", 

14 "Email", 

15 "email", 

16 "EqualTo", 

17 "equal_to", 

18 "IPAddress", 

19 "ip_address", 

20 "InputRequired", 

21 "input_required", 

22 "Length", 

23 "length", 

24 "NumberRange", 

25 "DateRange", 

26 "number_range", 

27 "date_range", 

28 "Optional", 

29 "optional", 

30 "Regexp", 

31 "regexp", 

32 "URL", 

33 "url", 

34 "AnyOf", 

35 "any_of", 

36 "NoneOf", 

37 "none_of", 

38 "MacAddress", 

39 "mac_address", 

40 "UUID", 

41 "ValidationError", 

42 "StopValidation", 

43 "readonly", 

44 "ReadOnly", 

45 "disabled", 

46 "Disabled", 

47) 

48 

49 

50class ValidationError(ValueError): 

51 """ 

52 Raised when a validator fails to validate its input. 

53 """ 

54 

55 def __init__(self, message="", *args, **kwargs): 

56 ValueError.__init__(self, message, *args, **kwargs) 

57 

58 

59class StopValidation(Exception): 

60 """ 

61 Causes the validation chain to stop. 

62 

63 If StopValidation is raised, no more validators in the validation chain are 

64 called. If raised with a message, the message will be added to the errors 

65 list. 

66 """ 

67 

68 def __init__(self, message="", *args, **kwargs): 

69 Exception.__init__(self, message, *args, **kwargs) 

70 

71 

72class EqualTo: 

73 """ 

74 Compares the values of two fields. 

75 

76 :param fieldname: 

77 The name of the other field to compare to. 

78 :param message: 

79 Error message to raise in case of a validation error. Can be 

80 interpolated with `%(other_label)s` and `%(other_name)s` to provide a 

81 more helpful error. 

82 """ 

83 

84 def __init__(self, fieldname, message=None): 

85 self.fieldname = fieldname 

86 self.message = message 

87 

88 def __call__(self, form, field): 

89 try: 

90 other = form[self.fieldname] 

91 except KeyError as exc: 

92 raise ValidationError( 

93 field.gettext("Invalid field name '%s'.") % self.fieldname 

94 ) from exc 

95 if field.data == other.data: 

96 return 

97 

98 d = { 

99 "other_label": hasattr(other, "label") 

100 and other.label.text 

101 or self.fieldname, 

102 "other_name": self.fieldname, 

103 } 

104 message = self.message 

105 if message is None: 

106 message = field.gettext("Field must be equal to %(other_name)s.") 

107 

108 raise ValidationError(message % d) 

109 

110 

111class Length: 

112 """ 

113 Validates the length of a string. 

114 

115 :param min: 

116 The minimum required length of the string. If not provided, minimum 

117 length will not be checked. 

118 :param max: 

119 The maximum length of the string. If not provided, maximum length 

120 will not be checked. 

121 :param message: 

122 Error message to raise in case of a validation error. Can be 

123 interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults 

124 are provided depending on the existence of min and max. 

125 

126 When supported, sets the `minlength` and `maxlength` attributes on widgets. 

127 """ 

128 

129 def __init__(self, min=-1, max=-1, message=None): 

130 assert min != -1 or max != -1, ( 

131 "At least one of `min` or `max` must be specified." 

132 ) 

133 assert max == -1 or min <= max, "`min` cannot be more than `max`." 

134 self.min = min 

135 self.max = max 

136 self.message = message 

137 self.field_flags = {} 

138 if self.min != -1: 

139 self.field_flags["minlength"] = self.min 

140 if self.max != -1: 

141 self.field_flags["maxlength"] = self.max 

142 

143 def __call__(self, form, field): 

144 length = field.data and len(field.data) or 0 

145 if length >= self.min and (self.max == -1 or length <= self.max): 

146 return 

147 

148 if self.message is not None: 

149 message = self.message 

150 

151 elif self.max == -1: 

152 message = field.ngettext( 

153 "Field must be at least %(min)d character long.", 

154 "Field must be at least %(min)d characters long.", 

155 self.min, 

156 ) 

157 elif self.min == -1: 

158 message = field.ngettext( 

159 "Field cannot be longer than %(max)d character.", 

160 "Field cannot be longer than %(max)d characters.", 

161 self.max, 

162 ) 

163 elif self.min == self.max: 

164 message = field.ngettext( 

165 "Field must be exactly %(max)d character long.", 

166 "Field must be exactly %(max)d characters long.", 

167 self.max, 

168 ) 

169 else: 

170 message = field.gettext( 

171 "Field must be between %(min)d and %(max)d characters long." 

172 ) 

173 

174 raise ValidationError(message % dict(min=self.min, max=self.max, length=length)) 

175 

176 

177class NumberRange: 

178 """ 

179 Validates that a number is of a minimum and/or maximum value, inclusive. 

180 This will work with any comparable number type, such as floats and 

181 decimals, not just integers. 

182 

183 ``min`` and ``max`` may be callables to compute dynamic bounds. 

184 

185 :param min: 

186 The minimum required value of the number. If not provided, minimum 

187 value will not be checked. Can also be a callable that returns the 

188 minimum value. 

189 :param max: 

190 The maximum value of the number. If not provided, maximum value 

191 will not be checked. Can also be a callable that returns the maximum 

192 value. 

193 :param message: 

194 Error message to raise in case of a validation error. Can be 

195 interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults 

196 are provided depending on the existence of min and max. 

197 

198 When supported, sets the `min` and `max` attributes on widgets. 

199 """ 

200 

201 def __init__(self, min=None, max=None, message=None): 

202 self.min = min 

203 self.max = max 

204 self.message = message 

205 self.field_flags = {} 

206 if self.min is not None: 

207 self.field_flags["min"] = self.min 

208 if self.max is not None: 

209 self.field_flags["max"] = self.max 

210 

211 @staticmethod 

212 def _resolve(value): 

213 return value() if callable(value) else value 

214 

215 def __call__(self, form, field): 

216 min_value = self._resolve(self.min) 

217 max_value = self._resolve(self.max) 

218 data = field.data 

219 if ( 

220 data is not None 

221 and not math.isnan(data) 

222 and (min_value is None or data >= min_value) 

223 and (max_value is None or data <= max_value) 

224 ): 

225 return 

226 

227 if self.message is not None: 

228 message = self.message 

229 

230 # we use %(min)s interpolation to support floats, None, and 

231 # Decimals without throwing a formatting exception. 

232 elif max_value is None: 

233 message = field.gettext("Number must be at least %(min)s.") 

234 

235 elif min_value is None: 

236 message = field.gettext("Number must be at most %(max)s.") 

237 

238 else: 

239 message = field.gettext("Number must be between %(min)s and %(max)s.") 

240 

241 raise ValidationError(message % dict(min=min_value, max=max_value)) 

242 

243 

244class DateRange: 

245 """ 

246 Validates that a date or datetime is of a minimum and/or maximum value, 

247 inclusive. This will work with dates and datetimes. 

248 

249 ``min`` and ``max`` may be callables to compute dynamic bounds. 

250 

251 For example:: 

252 

253 def in_n_days(days): 

254 return datetime.now() + timedelta(days=days) 

255 

256 

257 cb = partial(in_n_days, 5) 

258 

259 

260 class DateForm(Form): 

261 date = DateField("date", [DateRange(min=date(2023, 1, 1), max=cb)]) 

262 datetime = DateTimeLocalField( 

263 "datetime-local", 

264 [DateRange(min=datetime(2023, 1, 1, 15, 30), max=cb)], 

265 ) 

266 

267 :param min: 

268 The minimum required date or datetime. If not provided, minimum 

269 date or datetime will not be checked. Can also be a callable that 

270 returns a date or datetime. 

271 :param max: 

272 The maximum date or datetime. If not provided, maximum date or datetime 

273 will not be checked. Can also be a callable that returns a date or 

274 datetime. 

275 :param message: 

276 Error message to raise in case of a validation error. Can be 

277 interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults 

278 are provided depending on the existence of min and max. 

279 

280 When supported, sets the `min` and `max` attributes on widgets. 

281 """ 

282 

283 def __init__( 

284 self, 

285 min=None, 

286 max=None, 

287 message=None, 

288 ): 

289 self.min = min 

290 self.max = max 

291 self.message = message 

292 self.field_flags = {} 

293 

294 if self.min is not None: 

295 self.field_flags["min"] = self.min 

296 if self.max is not None: 

297 self.field_flags["max"] = self.max 

298 

299 @staticmethod 

300 def _to_datetime(value): 

301 if isinstance(value, datetime): 

302 return value 

303 if isinstance(value, date): 

304 return datetime.combine(value, time()) 

305 return value 

306 

307 @classmethod 

308 def _coerce_bound(cls, value): 

309 if callable(value): 

310 value = value() 

311 return cls._to_datetime(value) 

312 

313 def __call__(self, form, field): 

314 min_value = self._coerce_bound(self.min) 

315 max_value = self._coerce_bound(self.max) 

316 

317 data = self._to_datetime(field.data) 

318 if data is not None and ( 

319 (min_value is None or data >= min_value) 

320 and (max_value is None or data <= max_value) 

321 ): 

322 return 

323 

324 if self.message is not None: 

325 message = self.message 

326 

327 elif max_value is None: 

328 message = field.gettext("Date must be at least %(min)s.") 

329 

330 elif min_value is None: 

331 message = field.gettext("Date must be at most %(max)s.") 

332 

333 else: 

334 message = field.gettext("Date must be between %(min)s and %(max)s.") 

335 

336 raise ValidationError(message % dict(min=min_value, max=max_value)) 

337 

338 

339class Optional: 

340 """ 

341 Allows empty input and stops the validation chain from continuing. 

342 

343 If input is empty, also removes prior errors (such as processing errors) 

344 from the field. 

345 

346 :param strip_whitespace: 

347 If True (the default) also stop the validation chain on input which 

348 consists of only whitespace. 

349 

350 Sets the `optional` attribute on widgets. 

351 """ 

352 

353 def __init__(self, strip_whitespace=True): 

354 if strip_whitespace: 

355 self.string_check = lambda s: s.strip() 

356 else: 

357 self.string_check = lambda s: s 

358 

359 self.field_flags = {"optional": True} 

360 

361 def __call__(self, form, field): 

362 if ( 

363 not field.raw_data 

364 or isinstance(field.raw_data[0], str) 

365 and not self.string_check(field.raw_data[0]) 

366 ): 

367 field.errors[:] = [] 

368 raise StopValidation() 

369 

370 

371class DataRequired: 

372 """ 

373 Checks the field's data is 'truthy' otherwise stops the validation chain. 

374 

375 This validator checks that the ``data`` attribute on the field is a 'true' 

376 value (effectively, it does ``if field.data``.) Furthermore, if the data 

377 is a string type, a string containing only whitespace characters is 

378 considered false. 

379 

380 If the data is empty, also removes prior errors (such as processing errors) 

381 from the field. 

382 

383 **NOTE** this validator used to be called `Required` but the way it behaved 

384 (requiring coerced data, not input data) meant it functioned in a way 

385 which was not symmetric to the `Optional` validator and furthermore caused 

386 confusion with certain fields which coerced data to 'falsey' values like 

387 ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason 

388 exists, we recommend using the :class:`InputRequired` instead. 

389 

390 :param message: 

391 Error message to raise in case of a validation error. 

392 

393 Sets the `required` attribute on widgets. 

394 """ 

395 

396 def __init__(self, message=None): 

397 self.message = message 

398 self.field_flags = {"required": True} 

399 

400 def __call__(self, form, field): 

401 if field.data and (not isinstance(field.data, str) or field.data.strip()): 

402 return 

403 

404 if self.message is None: 

405 message = field.gettext("This field is required.") 

406 else: 

407 message = self.message 

408 

409 field.errors[:] = [] 

410 raise StopValidation(message) 

411 

412 

413class InputRequired: 

414 """ 

415 Validates that input was provided for this field. 

416 

417 Note there is a distinction between this and DataRequired in that 

418 InputRequired looks that form-input data was provided, and DataRequired 

419 looks at the post-coercion data. This means that this validator only checks 

420 whether non-empty data was sent, not whether non-empty data was coerced 

421 from that data. Initially populated data is not considered sent. 

422 

423 Sets the `required` attribute on widgets. 

424 """ 

425 

426 def __init__(self, message=None): 

427 self.message = message 

428 self.field_flags = {"required": True} 

429 

430 def __call__(self, form, field): 

431 if field.raw_data and field.raw_data[0]: 

432 return 

433 

434 if self.message is None: 

435 message = field.gettext("This field is required.") 

436 else: 

437 message = self.message 

438 

439 field.errors[:] = [] 

440 raise StopValidation(message) 

441 

442 

443class Regexp: 

444 """ 

445 Validates the field against a user provided regexp. 

446 

447 :param regex: 

448 The regular expression string to use. Can also be a compiled regular 

449 expression pattern. 

450 :param flags: 

451 The regexp flags to use, for example re.IGNORECASE. Ignored if 

452 `regex` is not a string. 

453 :param message: 

454 Error message to raise in case of a validation error. 

455 :param matcher: 

456 Callable invoked as ``matcher(pattern, value)`` to perform the match. 

457 Defaults to :func:`re.match`. Pass :func:`re.search` or 

458 :func:`re.fullmatch` to change the anchoring behaviour. 

459 :param html_pattern: 

460 Controls the HTML ``pattern`` attribute emitted on supporting widgets. 

461 Defaults to ``False`` (no attribute). Set to ``True`` to emit the 

462 Python pattern source as-is, to a string to emit a custom 

463 browser-specific pattern, or to a callable invoked as 

464 ``html_pattern(regex)`` returning ``bool`` or ``str`` interpreted by 

465 the same rules. Python and JavaScript regex syntaxes differ; emitting 

466 a Python regex unchanged may fail in browsers. Note that the HTML 

467 ``pattern`` attribute is implicitly anchored at both ends (equivalent 

468 to :func:`re.fullmatch`), so a pattern paired with ``matcher=re.match`` 

469 or ``matcher=re.search`` may be accepted server-side but rejected by 

470 the browser. 

471 """ 

472 

473 def __init__( 

474 self, 

475 regex, 

476 flags=0, 

477 message=None, 

478 matcher=re.match, 

479 html_pattern: bool | str | Callable[[re.Pattern], bool | str] = False, 

480 ): 

481 if isinstance(regex, str): 

482 regex = re.compile(regex, flags) 

483 self.regex = regex 

484 self.message = message 

485 self.matcher = matcher 

486 self.field_flags = self._resolve_field_flags(html_pattern) 

487 

488 def _resolve_field_flags(self, html_pattern): 

489 if callable(html_pattern): 

490 html_pattern = html_pattern(self.regex) 

491 if html_pattern is True: 

492 return {"pattern": self.regex.pattern} 

493 if isinstance(html_pattern, str): 

494 return {"pattern": html_pattern} 

495 return {} 

496 

497 def __call__(self, form, field, message=None): 

498 match = self.matcher(self.regex, field.data or "") 

499 if match: 

500 return match 

501 

502 if message is None: 

503 if self.message is None: 

504 message = field.gettext("Invalid input.") 

505 else: 

506 message = self.message 

507 

508 raise ValidationError(message) 

509 

510 

511class Email: 

512 """ 

513 Validates an email address. Requires email_validator package to be 

514 installed. For ex: pip install wtforms[email]. 

515 

516 Options that default to ``None`` are not forwarded to 

517 ``email_validator``, so its module-level defaults (e.g. 

518 ``email_validator.TEST_ENVIRONMENT``) take effect. Pass an explicit 

519 value to override per-instance. 

520 

521 :param message: 

522 Error message to raise in case of a validation error. 

523 :param granular_message: 

524 Use validation failed message from email_validator library 

525 (Default False). 

526 :param check_deliverability: 

527 Perform domain name resolution check (Default False, diverging 

528 from ``email_validator``'s default of True for safety on public 

529 forms). 

530 :param test_environment: 

531 Allow `test` and `*.test` domain names, and disable DNS-based 

532 deliverability checks (Default: defer to ``email_validator``). 

533 :param allow_smtputf8: 

534 Fail validation for addresses that would require SMTPUTF8 

535 (Default: defer to ``email_validator``). 

536 :param allow_empty_local: 

537 Allow an empty local part (i.e. @example.com), e.g. for validating 

538 Postfix aliases (Default: defer to ``email_validator``). 

539 """ 

540 

541 def __init__( 

542 self, 

543 message=None, 

544 granular_message=False, 

545 check_deliverability=False, 

546 test_environment=None, 

547 allow_smtputf8=None, 

548 allow_empty_local=None, 

549 ): 

550 self.message = message 

551 self.granular_message = granular_message 

552 self.check_deliverability = check_deliverability 

553 self.test_environment = test_environment 

554 self.allow_smtputf8 = allow_smtputf8 

555 self.allow_empty_local = allow_empty_local 

556 

557 def __call__(self, form, field): 

558 try: 

559 import email_validator 

560 except ImportError as exc: # pragma: no cover 

561 raise Exception( 

562 "Install 'email_validator' for email validation support." 

563 ) from exc 

564 

565 try: 

566 if field.data is None: 

567 raise email_validator.EmailNotValidError() 

568 email_validator.validate_email( 

569 field.data, 

570 check_deliverability=self.check_deliverability, 

571 test_environment=self.test_environment, 

572 allow_smtputf8=self.allow_smtputf8, 

573 allow_empty_local=self.allow_empty_local, 

574 ) 

575 except email_validator.EmailNotValidError as e: 

576 message = self.message 

577 if message is None: 

578 if self.granular_message: 

579 message = field.gettext(e) 

580 else: 

581 message = field.gettext("Invalid email address.") 

582 raise ValidationError(message) from e 

583 

584 

585class IPAddress: 

586 """ 

587 Validates an IP address. 

588 

589 :param ipv4: 

590 If True, accept IPv4 addresses as valid (default True) 

591 :param ipv6: 

592 If True, accept IPv6 addresses as valid (default False) 

593 :param message: 

594 Error message to raise in case of a validation error. 

595 """ 

596 

597 def __init__(self, ipv4=True, ipv6=False, message=None): 

598 if not ipv4 and not ipv6: 

599 raise ValueError( 

600 "IP Address Validator must have at least one of ipv4 or ipv6 enabled." 

601 ) 

602 self.ipv4 = ipv4 

603 self.ipv6 = ipv6 

604 self.message = message 

605 

606 def __call__(self, form, field): 

607 value = field.data 

608 valid = False 

609 if value: 

610 valid = (self.ipv4 and self.check_ipv4(value)) or ( 

611 self.ipv6 and self.check_ipv6(value) 

612 ) 

613 

614 if valid: 

615 return 

616 

617 message = self.message 

618 if message is None: 

619 message = field.gettext("Invalid IP address.") 

620 raise ValidationError(message) 

621 

622 @classmethod 

623 def check_ipv4(cls, value): 

624 try: 

625 address = ipaddress.ip_address(value) 

626 except ValueError: 

627 return False 

628 

629 if not isinstance(address, ipaddress.IPv4Address): 

630 return False 

631 

632 return True 

633 

634 @classmethod 

635 def check_ipv6(cls, value): 

636 try: 

637 address = ipaddress.ip_address(value) 

638 except ValueError: 

639 return False 

640 

641 if not isinstance(address, ipaddress.IPv6Address): 

642 return False 

643 

644 return True 

645 

646 

647class MacAddress(Regexp): 

648 """ 

649 Validates a MAC address. 

650 

651 :param message: 

652 Error message to raise in case of a validation error. 

653 """ 

654 

655 def __init__(self, message=None): 

656 pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" 

657 super().__init__(pattern, message=message) 

658 

659 def __call__(self, form, field): 

660 message = self.message 

661 if message is None: 

662 message = field.gettext("Invalid Mac address.") 

663 

664 super().__call__(form, field, message) 

665 

666 

667class URL: 

668 """ 

669 Simple url validation based on :func:`urllib.parse.urlparse`. Much like 

670 the email validator, you probably want to validate the url later by 

671 other means if the url must resolve. 

672 

673 :param require_tld: 

674 If true, then the domain-name portion of the URL must contain a .tld 

675 suffix. Set this to false if you want to allow domains like 

676 `localhost`. 

677 :param allow_ip: 

678 If false, then giving an ip as host will fail validation. 

679 :param allow_userinfo: 

680 If true, accept ``username[:password]@`` in the URL. Defaults to 

681 false: forms collecting URLs are often displayed back to users, and 

682 userinfo is a known phishing vector 

683 (e.g. ``https://accounts.example.com@evil.example/``). 

684 :param schemes: 

685 Iterable of allowed URL schemes. Defaults to ``("http", "https")``. 

686 Pass ``None`` to accept any scheme (use with caution: ``javascript:``, 

687 ``data:``, etc. would be accepted). 

688 :param message: 

689 Error message to raise in case of a validation error. 

690 """ 

691 

692 def __init__( 

693 self, 

694 require_tld=True, 

695 allow_ip=True, 

696 allow_userinfo=False, 

697 schemes=("http", "https"), 

698 message=None, 

699 ): 

700 self.allow_userinfo = allow_userinfo 

701 self.schemes = schemes 

702 self.message = message 

703 self.validate_hostname = HostnameValidation( 

704 require_tld=require_tld, allow_ip=allow_ip 

705 ) 

706 

707 def __call__(self, form, field): 

708 message = self.message 

709 if message is None: 

710 message = field.gettext("Invalid URL.") 

711 

712 try: 

713 r = urlparse(field.data) 

714 except ValueError as exc: 

715 raise ValidationError(message) from exc 

716 

717 if not r.scheme or not r.hostname: 

718 raise ValidationError(message) 

719 

720 if self.schemes is not None and r.scheme not in self.schemes: 

721 raise ValidationError(message) 

722 

723 if not self.allow_userinfo and (r.username or r.password): 

724 raise ValidationError(message) 

725 

726 try: 

727 _ = r.port 

728 except ValueError as exc: 

729 raise ValidationError(message) from exc 

730 

731 if not self.validate_hostname(r.hostname): 

732 raise ValidationError(message) 

733 

734 

735class UUID: 

736 """ 

737 Validates a UUID. 

738 

739 :param message: 

740 Error message to raise in case of a validation error. 

741 """ 

742 

743 def __init__(self, message=None): 

744 self.message = message 

745 

746 def __call__(self, form, field): 

747 message = self.message 

748 if message is None: 

749 message = field.gettext("Invalid UUID.") 

750 if isinstance(field.data, uuid.UUID): 

751 return 

752 if not isinstance(field.data, str): 

753 raise ValidationError(message) 

754 try: 

755 uuid.UUID(field.data) 

756 except ValueError as exc: 

757 raise ValidationError(message) from exc 

758 

759 

760class AnyOf: 

761 """ 

762 Compares the incoming data to a sequence of valid inputs. 

763 

764 :param values: 

765 A sequence of valid inputs. 

766 :param message: 

767 Error message to raise in case of a validation error. `%(values)s` 

768 contains the list of values. 

769 :param values_formatter: 

770 Function used to format the list of values in the error message. 

771 """ 

772 

773 def __init__(self, values, message=None, values_formatter=None): 

774 self.values = values 

775 self.message = message 

776 if values_formatter is None: 

777 values_formatter = self.default_values_formatter 

778 self.values_formatter = values_formatter 

779 

780 def __call__(self, form, field): 

781 data = field.data if isinstance(field.data, list) else [field.data] 

782 if any(d in self.values for d in data): 

783 return 

784 

785 message = self.message 

786 if message is None: 

787 message = field.gettext("Invalid value, must be one of: %(values)s.") 

788 

789 raise ValidationError(message % dict(values=self.values_formatter(self.values))) 

790 

791 @staticmethod 

792 def default_values_formatter(values): 

793 return ", ".join(str(x) for x in values) 

794 

795 

796class NoneOf: 

797 """ 

798 Compares the incoming data to a sequence of invalid inputs. 

799 

800 :param values: 

801 A sequence of invalid inputs. 

802 :param message: 

803 Error message to raise in case of a validation error. `%(values)s` 

804 contains the list of values. 

805 :param values_formatter: 

806 Function used to format the list of values in the error message. 

807 """ 

808 

809 def __init__(self, values, message=None, values_formatter=None): 

810 self.values = values 

811 self.message = message 

812 if values_formatter is None: 

813 values_formatter = self.default_values_formatter 

814 self.values_formatter = values_formatter 

815 

816 def __call__(self, form, field): 

817 data = field.data if isinstance(field.data, list) else [field.data] 

818 if not any(d in self.values for d in data): 

819 return 

820 

821 message = self.message 

822 if message is None: 

823 message = field.gettext("Invalid value, can't be any of: %(values)s.") 

824 

825 raise ValidationError(message % dict(values=self.values_formatter(self.values))) 

826 

827 @staticmethod 

828 def default_values_formatter(v): 

829 return ", ".join(str(x) for x in v) 

830 

831 

832class HostnameValidation: 

833 """ 

834 Helper class for checking hostnames for validation. 

835 

836 This is not a validator in and of itself, and as such is not exported. 

837 """ 

838 

839 hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE) 

840 tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE) 

841 

842 def __init__(self, require_tld=True, allow_ip=False): 

843 self.require_tld = require_tld 

844 self.allow_ip = allow_ip 

845 

846 def __call__(self, hostname): 

847 if self.allow_ip and ( 

848 IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname) 

849 ): 

850 return True 

851 

852 # Encode out IDNA hostnames. This makes further validation easier. 

853 try: 

854 hostname = hostname.encode("idna") 

855 except UnicodeError: 

856 pass 

857 

858 # Turn back into a string in Python 3x 

859 if not isinstance(hostname, str): 

860 hostname = hostname.decode("ascii") 

861 

862 if len(hostname) > 253: 

863 return False 

864 

865 # Check that all labels in the hostname are valid 

866 parts = hostname.split(".") 

867 for part in parts: 

868 if not part or len(part) > 63: 

869 return False 

870 if not self.hostname_part.match(part): 

871 return False 

872 

873 if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])): 

874 return False 

875 

876 return True 

877 

878 

879class ReadOnly: 

880 """ 

881 Set a field readonly. 

882 

883 Validation fails if the form data is different than the 

884 field object data, or if unset, from the field default data. 

885 """ 

886 

887 def __init__(self): 

888 self.field_flags = {"readonly": True} 

889 

890 def __call__(self, form, field): 

891 if field.data != field.object_data: 

892 raise ValidationError(field.gettext("This field cannot be edited.")) 

893 

894 

895class Disabled: 

896 """ 

897 Set a field disabled. 

898 

899 Validation fails if the form data has any value. 

900 """ 

901 

902 def __init__(self): 

903 self.field_flags = {"disabled": True} 

904 

905 def __call__(self, form, field): 

906 if field.raw_data: 

907 raise ValidationError( 

908 field.gettext("This field is disabled and cannot have a value.") 

909 ) 

910 

911 

912email = Email 

913equal_to = EqualTo 

914ip_address = IPAddress 

915mac_address = MacAddress 

916length = Length 

917number_range = NumberRange 

918date_range = DateRange 

919optional = Optional 

920input_required = InputRequired 

921data_required = DataRequired 

922regexp = Regexp 

923url = URL 

924any_of = AnyOf 

925none_of = NoneOf 

926readonly = ReadOnly 

927disabled = Disabled