Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from . import core 

2import re 

3from . import util 

4import string 

5import datetime 

6import copy 

7import functools 

8import webob 

9import warnings 

10import uuid 

11 

12from .i18n import _ 

13import six 

14 

15# Compat 

16if six.PY3: 

17 capitalize = str.capitalize 

18else: 

19 capitalize = string.capitalize 

20 

21# This hack helps work with different versions of WebOb 

22if not hasattr(webob, 'MultiDict'): 

23 # Check for webob versions with UnicodeMultiDict 

24 if hasattr(webob.multidict, 'UnicodeMultiDict'): 

25 webob.MultiDict = webob.multidict.UnicodeMultiDict 

26 else: 

27 webob.MultiDict = webob.multidict.MultiDict 

28 

29try: 

30 import formencode 

31except ImportError: 

32 formencode = None 

33 

34 

35class Invalid(object): 

36 pass 

37 

38 

39class EmptyField(object): 

40 pass 

41 

42 

43if formencode: 

44 class BaseValidationError(core.WidgetError, formencode.Invalid): 

45 def __init__(self, msg): 

46 formencode.Invalid.__init__(self, msg, None, None) 

47else: 

48 class BaseValidationError(core.WidgetError): 

49 def __init__(self, msg): 

50 core.WidgetError.__init__(self, msg) 

51 self.msg = msg 

52 

53 

54class ValidationError(BaseValidationError): 

55 """Invalid data was encountered during validation. 

56 

57 The constructor can be passed a short message name, which is looked up in 

58 a validator's :attr:`msgs` dictionary. Any values in this, like 

59 ``$val``` are substituted with that attribute from the validator. An 

60 explicit validator instance can be passed to the constructor, or this 

61 defaults to :class:`Validator` otherwise. 

62 """ 

63 def __init__(self, msg, validator=None, widget=None): 

64 self.widget = widget 

65 validator = validator or Validator 

66 mw = core.request_local().get('middleware') 

67 if isinstance(validator, Validator): 

68 msg = validator.msg_rewrites.get(msg, msg) 

69 

70 if mw and msg in mw.config.validator_msgs: 

71 msg = mw.config.validator_msgs[msg] 

72 elif hasattr(validator, 'msgs') and msg in validator.msgs: 

73 msg = validator.msgs.get(msg, msg) 

74 

75 # In the event that the user specified a form-wide validator but 

76 # they did not specify a childerror message, show no error. 

77 if msg == 'childerror': 

78 msg = '' 

79 

80 msg = re.sub('\$(\w+)', 

81 lambda m: str(getattr(validator, m.group(1))), six.text_type(msg)) 

82 super(ValidationError, self).__init__(msg) 

83 

84 @property 

85 def message(self): 

86 """Added for backwards compatibility. Synonymous with `msg`.""" 

87 return self.msg 

88 

89 

90catch = ValidationError 

91if formencode: 

92 catch = (catch, formencode.Invalid) 

93 

94 

95def safe_validate(validator, value, state=None): 

96 try: 

97 return validator.to_python(value, state=state) 

98 except catch: 

99 return Invalid 

100 

101 

102def catch_errors(fn): 

103 @functools.wraps(fn) 

104 def wrapper(self, *args, **kw): 

105 try: 

106 d = fn(self, *args, **kw) 

107 return d 

108 except catch as e: 

109 e_msg = six.text_type(e) 

110 if self: 

111 self.error_msg = e_msg 

112 raise ValidationError(e_msg, widget=self) 

113 return wrapper 

114 

115 

116def unflatten_params(params): 

117 """This performs the first stage of validation. It takes a dictionary where 

118 some keys will be compound names, such as "form:subform:field" and converts 

119 this into a nested dict/list structure. It also performs unicode decoding, 

120 with the encoding specified in the middleware config. 

121 """ 

122 if isinstance(params, webob.MultiDict): 

123 params = params.mixed() 

124 

125 mw = core.request_local().get('middleware') 

126 enc = mw.config.encoding if mw else 'utf-8' 

127 

128 try: 

129 for p in params: 

130 if isinstance(params[p], six.binary_type): 

131 params[p] = params[p].decode(enc) 

132 except UnicodeDecodeError: 

133 raise ValidationError('decode', Validator(encoding=enc)) 

134 

135 out = {} 

136 for pname in params: 

137 dct = out 

138 elements = pname.split(':') 

139 for e in elements[:-1]: 

140 dct = dct.setdefault(e, {}) 

141 dct[elements[-1]] = params[pname] 

142 

143 numdict_to_list(out) 

144 return out 

145 

146number_re = re.compile('^\d+$') 

147 

148 

149def numdict_to_list(dct): 

150 for k, v in dct.items(): 

151 if isinstance(v, dict): 

152 numdict_to_list(v) 

153 if all(number_re.match(k) for k in v): 

154 dct[k] = [v[x] for x in sorted(v, key=int)] 

155 

156 

157class ValidatorMeta(type): 

158 """Metaclass for :class:`Validator`. 

159 

160 This makes the :attr:`msgs` dict copy from its base class. 

161 """ 

162 def __new__(meta, name, bases, dct): 

163 if 'msgs' in dct: 

164 msgs = {} 

165 rewrites = {} 

166 for b in bases: 

167 try: 

168 msgs.update(b.msgs) 

169 rewrites.update(b.msgs_rewrites) 

170 except AttributeError: 

171 pass 

172 msgs.update(dct['msgs']) 

173 add_to_msgs = {} 

174 del_from_msgs = [] 

175 for m, d in msgs.items(): 

176 if isinstance(d, tuple): 

177 add_to_msgs[d[0]] = d[1] 

178 rewrites[m] = d[0] 

179 del_from_msgs.append(m) 

180 msgs.update(add_to_msgs) 

181 for m in del_from_msgs: 

182 del msgs[m] 

183 dct['msgs'] = msgs 

184 dct['msg_rewrites'] = rewrites 

185 if 'validate_python' in dct and '_validate_python' not in dct: 

186 dct['_validate_python'] = dct.pop('validate_python') 

187 warnings.warn('validate_python() is deprecated;' 

188 ' use _validate_python() instead', 

189 DeprecationWarning, stacklevel=2) 

190 return type.__new__(meta, name, bases, dct) 

191 

192 

193class Validator(six.with_metaclass(ValidatorMeta, object)): 

194 """Base class for validators 

195 

196 `required` 

197 Whether empty values are forbidden in this field. (default: False) 

198 

199 `strip` 

200 Whether to strip leading and trailing space from the input, before 

201 any other validation. (default: True) 

202 

203 To convert and validate a value to Python, use the :meth:`to_python` 

204 method, to convert back from Python, use :meth:`from_python`. 

205 

206 To create your own validators, sublass this class, and override any of 

207 :meth:`_validate_python`, :meth:`_convert_to_python`, 

208 or :meth:`_convert_from_python`. Note that these methods are not 

209 meant to be used externally. All of them may raise ValidationErrors. 

210 

211 """ 

212 

213 msgs = { 

214 'required': _('Enter a value'), 

215 'decode': _('Received in wrong character set; should be $encoding'), 

216 'corrupt': _('Form submission received corrupted; please try again'), 

217 'childerror': '', # Children of this widget have errors 

218 } 

219 required = False 

220 strip = True 

221 if_empty = None 

222 

223 def __init__(self, **kw): 

224 for k in kw: 

225 setattr(self, k, kw[k]) 

226 

227 def to_python(self, value, state=None): 

228 """Convert an external value to Python and validate it.""" 

229 if self._is_empty(value): 

230 if self.required: 

231 raise ValidationError('required', self) 

232 return self.if_empty 

233 if self.strip and isinstance(value, six.string_types): 

234 value = value.strip() 

235 value = self._convert_to_python(value, state) 

236 self._validate_python(value, state) 

237 return value 

238 

239 def from_python(self, value, state=None): 

240 """Convert from a Python object to an external value.""" 

241 if self._is_empty(value): 

242 return '' 

243 if isinstance(value, six.string_types) and self.strip: 

244 value = value.strip() 

245 value = self._convert_from_python(value, state) 

246 return value 

247 

248 def __repr__(self): 

249 _bool = ['False', 'True'] 

250 return ("Validator(required=%s, strip=%s)" % 

251 (_bool[int(self.required)], _bool[int(self.strip)])) 

252 

253 def _validate_python(self, value, state=None): 

254 """"Overridable internal method for validation of Python values.""" 

255 pass 

256 

257 def _convert_to_python(self, value, state=None): 

258 """"Overridable internal method for conversion to Python values.""" 

259 return value 

260 

261 def _convert_from_python(self, value, state=None): 

262 """"Overridable internal method for conversion from Python values.""" 

263 return value 

264 

265 @staticmethod 

266 def _is_empty(value): 

267 """Check whether the given value should be considered "empty".""" 

268 return value is None or value == '' or ( 

269 isinstance(value, (list, tuple, dict)) and not value) 

270 

271 def validate_python(self, value, state=None): 

272 """"Deprecated, use :meth:`_validate_python` instead. 

273 

274 This method has been renamed in FormEncode 1.3 and ToscaWidgets 2.2 

275 in order to clarify that is an internal method that is meant to be 

276 overridden only; you must call meth:`to_python` to validate values. 

277 

278 """ 

279 warnings.warn('validate_python() is deprecated;' 

280 ' use _validate_python() instead', 

281 DeprecationWarning, stacklevel=2) 

282 return self._validate_python(value, state) 

283 

284 def clone(self, **kw): 

285 nself = copy.copy(self) 

286 for k in kw: 

287 setattr(nself, k, kw[k]) 

288 return nself 

289 

290if formencode: 

291 validator_classes = (Validator, formencode.Validator) 

292else: 

293 validator_classes = (Validator, ) 

294 

295 

296class BlankValidator(Validator): 

297 """ 

298 Always returns EmptyField. This is the default for hidden fields, 

299 so their values are not included in validated data. 

300 """ 

301 def to_python(self, value, state=None): 

302 return EmptyField 

303 

304 

305class LengthValidator(Validator): 

306 """ 

307 Confirm a value is of a suitable length. Usually you'll use 

308 :class:`StringLengthValidator` or :class:`ListLengthValidator` instead. 

309 

310 `min` 

311 Minimum length (default: None) 

312 

313 `max` 

314 Maximum length (default: None) 

315 """ 

316 msgs = { 

317 'tooshort': _('Value is too short'), 

318 'toolong': _('Value is too long'), 

319 } 

320 min = None 

321 max = None 

322 

323 def __init__(self, **kw): 

324 super(LengthValidator, self).__init__(**kw) 

325 if self.min: 

326 self.required = True 

327 

328 def _validate_python(self, value, state=None): 

329 if self.min and len(value) < self.min: 

330 raise ValidationError('tooshort', self) 

331 if self.max and len(value) > self.max: 

332 raise ValidationError('toolong', self) 

333 

334 

335class StringLengthValidator(LengthValidator): 

336 """ 

337 Check a string is a suitable length. The only difference to LengthValidator 

338 is that the messages are worded differently. 

339 """ 

340 

341 msgs = { 

342 'tooshort': ( 

343 'string_tooshort', _('Must be at least $min characters')), 

344 'toolong': ( 

345 'string_toolong', _('Cannot be longer than $max characters')), 

346 } 

347 

348 

349class ListLengthValidator(LengthValidator): 

350 """ 

351 Check a list is a suitable length. The only difference to LengthValidator 

352 is that the messages are worded differently. 

353 """ 

354 

355 msgs = { 

356 'tooshort': ('list_tooshort', _('Select at least $min')), 

357 'toolong': ('list_toolong', _('Select no more than $max')), 

358 } 

359 

360 

361class RangeValidator(Validator): 

362 """ 

363 Confirm a value is within an appropriate range. This is not usually used 

364 directly, but other validators are derived from this. 

365 

366 `min` 

367 Minimum value (default: None) 

368 

369 `max` 

370 Maximum value (default: None) 

371 """ 

372 msgs = { 

373 'toosmall': _('Must be at least $min'), 

374 'toobig': _('Cannot be more than $max'), 

375 } 

376 min = None 

377 max = None 

378 

379 def _validate_python(self, value, state=None): 

380 if self.min is not None and value < self.min: 

381 raise ValidationError('toosmall', self) 

382 if self.max is not None and value > self.max: 

383 raise ValidationError('toobig', self) 

384 

385 

386class IntValidator(RangeValidator): 

387 """ 

388 Confirm the value is an integer. This is derived from 

389 :class:`RangeValidator` so `min` and `max` can be specified. 

390 """ 

391 msgs = { 

392 'notint': _('Must be an integer'), 

393 } 

394 

395 def _convert_to_python(self, value, state=None): 

396 try: 

397 return int(value) 

398 except ValueError: 

399 raise ValidationError('notint', self) 

400 

401 def _convert_from_python(self, value, state=None): 

402 return str(value) 

403 

404 

405class BoolValidator(RangeValidator): 

406 """ 

407 Convert a value to a boolean. This is particularly intended to handle 

408 check boxes. 

409 """ 

410 msgs = { 

411 'required': ('bool_required', _('You must select this')) 

412 } 

413 if_empty = False 

414 

415 def _convert_to_python(self, value, state=None): 

416 return str(value).lower() in ('on', 'yes', 'true', '1', 'y', 't') 

417 

418 def _convert_from_python(self, value, state=None): 

419 return value and 'true' or 'false' 

420 

421 

422class OneOfValidator(Validator): 

423 """ 

424 Confirm the value is one of a list of acceptable values. This is useful for 

425 confirming that select fields have not been tampered with by a user. 

426 

427 `values` 

428 Acceptable values 

429 """ 

430 msgs = { 

431 'notinlist': _('Invalid value'), 

432 } 

433 values = [] 

434 

435 def _validate_python(self, value, state=None): 

436 if value not in self.values: 

437 raise ValidationError('notinlist', self) 

438 

439 

440class DateTimeValidator(RangeValidator): 

441 """ 

442 Confirm the value is a valid date and time. This is derived from 

443 :class:`RangeValidator` so `min` and `max` can be specified. 

444 

445 `format` 

446 The expected date/time format. The format must be specified using 

447 the same syntax as the Python strftime function. 

448 """ 

449 msgs = { 

450 'baddatetime': _('Must follow date/time format $format_str'), 

451 'toosmall': ('date_toosmall', _('Cannot be earlier than $min_str')), 

452 'toobig': ('date_toobig', _('Cannot be later than $max_str')), 

453 } 

454 format = '%Y-%m-%d %H:%M' 

455 

456 format_tbl = { 

457 'd': 'day', 

458 'H': 'hour', 

459 'I': 'hour', 

460 'm': 'month', 

461 'M': 'minute', 

462 'S': 'second', 

463 'y': 'year', 

464 'Y': 'year', 

465 } 

466 

467 @property 

468 def format_str(self): 

469 f = lambda m: self.format_tbl.get(m.group(1), '') 

470 return re.sub('%(.)', f, self.format) 

471 

472 @property 

473 def min_str(self): 

474 return self.min.strftime(self.format) 

475 

476 @property 

477 def max_str(self): 

478 return self.max.strftime(self.format) 

479 

480 def _convert_to_python(self, value, state=None): 

481 if isinstance(value, datetime.datetime): 

482 return value 

483 if isinstance(value, datetime.date): 

484 return datetime.datetime(value.year, value.month, value.day) 

485 try: 

486 return datetime.datetime.strptime(value, self.format) 

487 except ValueError: 

488 raise ValidationError('baddatetime', self) 

489 

490 def _validate_python(self, value, state=None): 

491 super(DateTimeValidator, self)._validate_python(value, state) 

492 

493 def _convert_from_python(self, value, state=None): 

494 return value.strftime(self.format) 

495 

496 

497class DateValidator(DateTimeValidator): 

498 """ 

499 Confirm the value is a valid date. 

500 

501 Just like :class:`DateTimeValidator`, but without the time component. 

502 """ 

503 msgs = { 

504 'baddatetime': ( 

505 'baddate', _('Must follow date format $format_str')), 

506 } 

507 format = '%Y-%m-%d' 

508 

509 def _convert_to_python(self, value, state=None): 

510 value = super(DateValidator, self)._convert_to_python(value) 

511 return value.date() 

512 

513 

514class RegexValidator(Validator): 

515 """ 

516 Confirm the value matches a regular expression. 

517 

518 `regex` 

519 A Python regular expression object, generated like 

520 ``re.compile('^\w+$')`` 

521 """ 

522 msgs = { 

523 'badregex': _('Invalid value'), 

524 } 

525 regex = None 

526 

527 def _validate_python(self, value, state=None): 

528 if not self.regex.search(value): 

529 raise ValidationError('badregex', self) 

530 

531 

532class EmailValidator(RegexValidator): 

533 """ 

534 Confirm the value is a valid email address. 

535 """ 

536 msgs = { 

537 'badregex': ('bademail', _('Must be a valid email address')), 

538 } 

539 regex = re.compile('^[\w\-.]+@[\w\-.]+$') 

540 

541 

542class UrlValidator(RegexValidator): 

543 """ 

544 Confirm the value is a valid URL. 

545 """ 

546 msgs = { 

547 'regex': ('badurl', _('Must be a valid URL')), 

548 } 

549 regex = re.compile('^https?://', re.IGNORECASE) 

550 

551 

552class IpAddressValidator(Validator): 

553 """ 

554 Confirm the value is a valid IP4 address, or network block. 

555 

556 `allow_netblock` 

557 Allow the IP address to include a network block (default: False) 

558 

559 `require_netblock` 

560 Require the IP address to include a network block (default: False) 

561 """ 

562 allow_netblock = False 

563 require_netblock = False 

564 

565 msgs = { 

566 'badipaddress': _('Must be a valid IP address'), 

567 'badnetblock': _('Must be a valid IP network block'), 

568 } 

569 regex = re.compile('^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$') 

570 

571 def _validate_python(self, value, state=None): 

572 m = self.regex.search(value) 

573 if not m or any(not(0 <= int(g) <= 255) for g in m.groups()[:4]): 

574 raise ValidationError('badipaddress', self) 

575 if m.group(6): 

576 if not self.allow_netblock: 

577 raise ValidationError('badipaddress', self) 

578 if not (0 <= int(m.group(6)) <= 32): 

579 raise ValidationError('badnetblock', self) 

580 elif self.require_netblock: 

581 raise ValidationError('badnetblock', self) 

582 

583 

584class UUIDValidator(Validator): 

585 """ 

586 Confirm the value is a valid uuid and convert to uuid.UUID. 

587 """ 

588 msgs = { 

589 'badregex': ('baduuid', _('Value not recognised as a UUID')), 

590 } 

591 

592 regex = re.compile(\ 

593 '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') 

594 

595 def _validate_python(self, value, state=None): 

596 if not self.regex.search(str(value)): 

597 raise ValidationError('badregex', self) 

598 

599 def _convert_to_python(self, value, state=None): 

600 try: 

601 return uuid.UUID( "{%s}" % value ) 

602 

603 except ValueError: 

604 raise ValidationError('baduuid', self) 

605 

606 def _convert_from_python(self, value, state=None): 

607 return str(value) 

608 

609 

610class MatchValidator(Validator): 

611 """Confirm a field matches another field 

612 

613 `other_field` 

614 Name of the sibling field this must match 

615 `pass_on_invalid` 

616 Pass validation if sibling field is Invalid 

617 """ 

618 msgs = { 

619 'mismatch': _("Must match $other_field_str"), 

620 'notfound': _("$other_field_str field is not found"), 

621 'invalid': _("$other_field_str field is invalid"), 

622 } 

623 

624 def __init__(self, other_field, pass_on_invalid=False, **kw): 

625 super(MatchValidator, self).__init__(**kw) 

626 self.other_field = other_field 

627 self.pass_on_invalid = pass_on_invalid 

628 

629 @property 

630 def other_field_str(self): 

631 return capitalize(util.name2label(self.other_field).lower()) 

632 

633 def _validate_python(self, value, state): 

634 if isinstance(state, dict): 

635 # Backward compatibility 

636 values = state 

637 else: 

638 values = state.full_dict 

639 

640 if self.other_field not in values: 

641 raise ValidationError('notfound', self) 

642 

643 other_value = values[self.other_field] 

644 

645 if other_value is Invalid: 

646 if not self.pass_on_invalid: 

647 raise ValidationError('invalid', self) 

648 elif value != other_value: 

649 raise ValidationError('mismatch', self) 

650 

651 def _is_empty(self, value): 

652 return self.required and super(MatchValidator, self)._is_empty(value) 

653 

654 

655class CompoundValidator(Validator): 

656 """ Base class for compound validators. 

657 

658 Child classes :class:`Any` and :class:`All` take validators as arguments 

659 and use them to validate "value". In case the validation fails, they 

660 raise a ValidationError with a compound message. 

661 

662 >>> v = All(StringLengthValidator(max=50), EmailValidator, required=True) 

663 """ 

664 

665 def __init__(self, *args, **kw): 

666 super(CompoundValidator, self).__init__(**kw) 

667 

668 self.validators = [] 

669 for arg in args: 

670 if isinstance(arg, validator_classes): 

671 self.validators.append(arg) 

672 elif issubclass(arg, validator_classes): 

673 self.validators.append(arg()) 

674 if getattr(arg, 'required', False): 

675 self.required = True 

676 

677 

678class All(CompoundValidator): 

679 """ 

680 Confirm all validators passed as arguments are valid. 

681 """ 

682 

683 def _validate_python(self, value, state=None): 

684 msg = [] 

685 for validator in self.validators: 

686 try: 

687 validator._validate_python(value, state) 

688 except ValidationError as e: 

689 msg.append(six.text_type(e)) 

690 if msg: 

691 msgset = set() 

692 msg = ', '.join(m for m in msg 

693 if m not in msgset and not msgset.add(m)) 

694 raise ValidationError(msg, self) 

695 

696 

697class Any(CompoundValidator): 

698 """ 

699 Confirm at least one of the validators passed as arguments is valid. 

700 """ 

701 

702 def _validate_python(self, value, state=None): 

703 msg = [] 

704 for validator in self.validators: 

705 try: 

706 validator._validate_python(value, state) 

707 except ValidationError as e: 

708 msg.append(six.text_type(e)) 

709 if len(msg) == len(self.validators): 

710 msgset = set() 

711 msg = ', '.join(m for m in msg 

712 if m not in msgset and not msgset.add(m)) 

713 raise ValidationError(msg, self)