Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/forms/fields.py: 37%

762 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2Field classes. 

3""" 

4 

5import copy 

6import datetime 

7import json 

8import math 

9import operator 

10import os 

11import re 

12import uuid 

13from decimal import Decimal, DecimalException 

14from io import BytesIO 

15from urllib.parse import urlsplit, urlunsplit 

16 

17from django.core import validators 

18from django.core.exceptions import ValidationError 

19from django.forms.boundfield import BoundField 

20from django.forms.utils import from_current_timezone, to_current_timezone 

21from django.forms.widgets import ( 

22 FILE_INPUT_CONTRADICTION, 

23 CheckboxInput, 

24 ClearableFileInput, 

25 DateInput, 

26 DateTimeInput, 

27 EmailInput, 

28 FileInput, 

29 HiddenInput, 

30 MultipleHiddenInput, 

31 NullBooleanSelect, 

32 NumberInput, 

33 Select, 

34 SelectMultiple, 

35 SplitDateTimeWidget, 

36 SplitHiddenDateTimeWidget, 

37 Textarea, 

38 TextInput, 

39 TimeInput, 

40 URLInput, 

41) 

42from django.utils import formats 

43from django.utils.dateparse import parse_datetime, parse_duration 

44from django.utils.duration import duration_string 

45from django.utils.ipv6 import clean_ipv6_address 

46from django.utils.regex_helper import _lazy_re_compile 

47from django.utils.translation import gettext_lazy as _ 

48from django.utils.translation import ngettext_lazy 

49 

50__all__ = ( 

51 "Field", 

52 "CharField", 

53 "IntegerField", 

54 "DateField", 

55 "TimeField", 

56 "DateTimeField", 

57 "DurationField", 

58 "RegexField", 

59 "EmailField", 

60 "FileField", 

61 "ImageField", 

62 "URLField", 

63 "BooleanField", 

64 "NullBooleanField", 

65 "ChoiceField", 

66 "MultipleChoiceField", 

67 "ComboField", 

68 "MultiValueField", 

69 "FloatField", 

70 "DecimalField", 

71 "SplitDateTimeField", 

72 "GenericIPAddressField", 

73 "FilePathField", 

74 "JSONField", 

75 "SlugField", 

76 "TypedChoiceField", 

77 "TypedMultipleChoiceField", 

78 "UUIDField", 

79) 

80 

81 

82class Field: 

83 widget = TextInput # Default widget to use when rendering this type of Field. 

84 hidden_widget = ( 

85 HiddenInput # Default widget to use when rendering this as "hidden". 

86 ) 

87 default_validators = [] # Default set of validators 

88 # Add an 'invalid' entry to default_error_message if you want a specific 

89 # field error message not raised by the field validators. 

90 default_error_messages = { 

91 "required": _("This field is required."), 

92 } 

93 empty_values = list(validators.EMPTY_VALUES) 

94 

95 def __init__( 

96 self, 

97 *, 

98 required=True, 

99 widget=None, 

100 label=None, 

101 initial=None, 

102 help_text="", 

103 error_messages=None, 

104 show_hidden_initial=False, 

105 validators=(), 

106 localize=False, 

107 disabled=False, 

108 label_suffix=None, 

109 ): 

110 # required -- Boolean that specifies whether the field is required. 

111 # True by default. 

112 # widget -- A Widget class, or instance of a Widget class, that should 

113 # be used for this Field when displaying it. Each Field has a 

114 # default Widget that it'll use if you don't specify this. In 

115 # most cases, the default widget is TextInput. 

116 # label -- A verbose name for this field, for use in displaying this 

117 # field in a form. By default, Django will use a "pretty" 

118 # version of the form field name, if the Field is part of a 

119 # Form. 

120 # initial -- A value to use in this Field's initial display. This value 

121 # is *not* used as a fallback if data isn't given. 

122 # help_text -- An optional string to use as "help text" for this Field. 

123 # error_messages -- An optional dictionary to override the default 

124 # messages that the field will raise. 

125 # show_hidden_initial -- Boolean that specifies if it is needed to render a 

126 # hidden widget with initial value after widget. 

127 # validators -- List of additional validators to use 

128 # localize -- Boolean that specifies if the field should be localized. 

129 # disabled -- Boolean that specifies whether the field is disabled, that 

130 # is its widget is shown in the form but not editable. 

131 # label_suffix -- Suffix to be added to the label. Overrides 

132 # form's label_suffix. 

133 self.required, self.label, self.initial = required, label, initial 

134 self.show_hidden_initial = show_hidden_initial 

135 self.help_text = help_text 

136 self.disabled = disabled 

137 self.label_suffix = label_suffix 

138 widget = widget or self.widget 

139 if isinstance(widget, type): 

140 widget = widget() 

141 else: 

142 widget = copy.deepcopy(widget) 

143 

144 # Trigger the localization machinery if needed. 

145 self.localize = localize 

146 if self.localize: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 widget.is_localized = True 

148 

149 # Let the widget know whether it should display as required. 

150 widget.is_required = self.required 

151 

152 # Hook into self.widget_attrs() for any Field-specific HTML attributes. 

153 extra_attrs = self.widget_attrs(widget) 

154 if extra_attrs: 

155 widget.attrs.update(extra_attrs) 

156 

157 self.widget = widget 

158 

159 messages = {} 

160 for c in reversed(self.__class__.__mro__): 

161 messages.update(getattr(c, "default_error_messages", {})) 

162 messages.update(error_messages or {}) 

163 self.error_messages = messages 

164 

165 self.validators = [*self.default_validators, *validators] 

166 

167 super().__init__() 

168 

169 def prepare_value(self, value): 

170 return value 

171 

172 def to_python(self, value): 

173 return value 

174 

175 def validate(self, value): 

176 if value in self.empty_values and self.required: 176 ↛ 177line 176 didn't jump to line 177, because the condition on line 176 was never true

177 raise ValidationError(self.error_messages["required"], code="required") 

178 

179 def run_validators(self, value): 

180 if value in self.empty_values: 

181 return 

182 errors = [] 

183 for v in self.validators: 

184 try: 

185 v(value) 

186 except ValidationError as e: 

187 if hasattr(e, "code") and e.code in self.error_messages: 

188 e.message = self.error_messages[e.code] 

189 errors.extend(e.error_list) 

190 if errors: 190 ↛ 191line 190 didn't jump to line 191, because the condition on line 190 was never true

191 raise ValidationError(errors) 

192 

193 def clean(self, value): 

194 """ 

195 Validate the given value and return its "cleaned" value as an 

196 appropriate Python object. Raise ValidationError for any errors. 

197 """ 

198 value = self.to_python(value) 

199 self.validate(value) 

200 self.run_validators(value) 

201 return value 

202 

203 def bound_data(self, data, initial): 

204 """ 

205 Return the value that should be shown for this field on render of a 

206 bound form, given the submitted POST data for the field and the initial 

207 data, if any. 

208 

209 For most fields, this will simply be data; FileFields need to handle it 

210 a bit differently. 

211 """ 

212 if self.disabled: 

213 return initial 

214 return data 

215 

216 def widget_attrs(self, widget): 

217 """ 

218 Given a Widget instance (*not* a Widget class), return a dictionary of 

219 any HTML attributes that should be added to the Widget, based on this 

220 Field. 

221 """ 

222 return {} 

223 

224 def has_changed(self, initial, data): 

225 """Return True if data differs from initial.""" 

226 # Always return False if the field is disabled since self.bound_data 

227 # always uses the initial value in this case. 

228 if self.disabled: 

229 return False 

230 try: 

231 data = self.to_python(data) 

232 if hasattr(self, "_coerce"): 

233 return self._coerce(data) != self._coerce(initial) 

234 except ValidationError: 

235 return True 

236 # For purposes of seeing whether something has changed, None is 

237 # the same as an empty string, if the data or initial value we get 

238 # is None, replace it with ''. 

239 initial_value = initial if initial is not None else "" 

240 data_value = data if data is not None else "" 

241 return initial_value != data_value 

242 

243 def get_bound_field(self, form, field_name): 

244 """ 

245 Return a BoundField instance that will be used when accessing the form 

246 field in a template. 

247 """ 

248 return BoundField(form, self, field_name) 

249 

250 def __deepcopy__(self, memo): 

251 result = copy.copy(self) 

252 memo[id(self)] = result 

253 result.widget = copy.deepcopy(self.widget, memo) 

254 result.error_messages = self.error_messages.copy() 

255 result.validators = self.validators[:] 

256 return result 

257 

258 

259class CharField(Field): 

260 def __init__( 

261 self, *, max_length=None, min_length=None, strip=True, empty_value="", **kwargs 

262 ): 

263 self.max_length = max_length 

264 self.min_length = min_length 

265 self.strip = strip 

266 self.empty_value = empty_value 

267 super().__init__(**kwargs) 

268 if min_length is not None: 268 ↛ 269line 268 didn't jump to line 269, because the condition on line 268 was never true

269 self.validators.append(validators.MinLengthValidator(int(min_length))) 

270 if max_length is not None: 

271 self.validators.append(validators.MaxLengthValidator(int(max_length))) 

272 self.validators.append(validators.ProhibitNullCharactersValidator()) 

273 

274 def to_python(self, value): 

275 """Return a string.""" 

276 if value not in self.empty_values: 276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true

277 value = str(value) 

278 if self.strip: 

279 value = value.strip() 

280 if value in self.empty_values: 280 ↛ 282line 280 didn't jump to line 282, because the condition on line 280 was never false

281 return self.empty_value 

282 return value 

283 

284 def widget_attrs(self, widget): 

285 attrs = super().widget_attrs(widget) 

286 if self.max_length is not None and not widget.is_hidden: 

287 # The HTML attribute is maxlength, not max_length. 

288 attrs["maxlength"] = str(self.max_length) 

289 if self.min_length is not None and not widget.is_hidden: 289 ↛ 291line 289 didn't jump to line 291, because the condition on line 289 was never true

290 # The HTML attribute is minlength, not min_length. 

291 attrs["minlength"] = str(self.min_length) 

292 return attrs 

293 

294 

295class IntegerField(Field): 

296 widget = NumberInput 

297 default_error_messages = { 

298 "invalid": _("Enter a whole number."), 

299 } 

300 re_decimal = _lazy_re_compile(r"\.0*\s*$") 

301 

302 def __init__(self, *, max_value=None, min_value=None, **kwargs): 

303 self.max_value, self.min_value = max_value, min_value 

304 if kwargs.get("localize") and self.widget == NumberInput: 

305 # Localized number input is not well supported on most browsers 

306 kwargs.setdefault("widget", super().widget) 

307 super().__init__(**kwargs) 

308 

309 if max_value is not None: 

310 self.validators.append(validators.MaxValueValidator(max_value)) 

311 if min_value is not None: 

312 self.validators.append(validators.MinValueValidator(min_value)) 

313 

314 def to_python(self, value): 

315 """ 

316 Validate that int() can be called on the input. Return the result 

317 of int() or None for empty values. 

318 """ 

319 value = super().to_python(value) 

320 if value in self.empty_values: 

321 return None 

322 if self.localize: 

323 value = formats.sanitize_separators(value) 

324 # Strip trailing decimal and zeros. 

325 try: 

326 value = int(self.re_decimal.sub("", str(value))) 

327 except (ValueError, TypeError): 

328 raise ValidationError(self.error_messages["invalid"], code="invalid") 

329 return value 

330 

331 def widget_attrs(self, widget): 

332 attrs = super().widget_attrs(widget) 

333 if isinstance(widget, NumberInput): 

334 if self.min_value is not None: 

335 attrs["min"] = self.min_value 

336 if self.max_value is not None: 

337 attrs["max"] = self.max_value 

338 return attrs 

339 

340 

341class FloatField(IntegerField): 

342 default_error_messages = { 

343 "invalid": _("Enter a number."), 

344 } 

345 

346 def to_python(self, value): 

347 """ 

348 Validate that float() can be called on the input. Return the result 

349 of float() or None for empty values. 

350 """ 

351 value = super(IntegerField, self).to_python(value) 

352 if value in self.empty_values: 

353 return None 

354 if self.localize: 

355 value = formats.sanitize_separators(value) 

356 try: 

357 value = float(value) 

358 except (ValueError, TypeError): 

359 raise ValidationError(self.error_messages["invalid"], code="invalid") 

360 return value 

361 

362 def validate(self, value): 

363 super().validate(value) 

364 if value in self.empty_values: 

365 return 

366 if not math.isfinite(value): 

367 raise ValidationError(self.error_messages["invalid"], code="invalid") 

368 

369 def widget_attrs(self, widget): 

370 attrs = super().widget_attrs(widget) 

371 if isinstance(widget, NumberInput) and "step" not in widget.attrs: 

372 attrs.setdefault("step", "any") 

373 return attrs 

374 

375 

376class DecimalField(IntegerField): 

377 default_error_messages = { 

378 "invalid": _("Enter a number."), 

379 } 

380 

381 def __init__( 

382 self, 

383 *, 

384 max_value=None, 

385 min_value=None, 

386 max_digits=None, 

387 decimal_places=None, 

388 **kwargs, 

389 ): 

390 self.max_digits, self.decimal_places = max_digits, decimal_places 

391 super().__init__(max_value=max_value, min_value=min_value, **kwargs) 

392 self.validators.append(validators.DecimalValidator(max_digits, decimal_places)) 

393 

394 def to_python(self, value): 

395 """ 

396 Validate that the input is a decimal number. Return a Decimal 

397 instance or None for empty values. Ensure that there are no more 

398 than max_digits in the number and no more than decimal_places digits 

399 after the decimal point. 

400 """ 

401 if value in self.empty_values: 

402 return None 

403 if self.localize: 

404 value = formats.sanitize_separators(value) 

405 try: 

406 value = Decimal(str(value)) 

407 except DecimalException: 

408 raise ValidationError(self.error_messages["invalid"], code="invalid") 

409 return value 

410 

411 def validate(self, value): 

412 super().validate(value) 

413 if value in self.empty_values: 

414 return 

415 if not value.is_finite(): 

416 raise ValidationError( 

417 self.error_messages["invalid"], 

418 code="invalid", 

419 params={"value": value}, 

420 ) 

421 

422 def widget_attrs(self, widget): 

423 attrs = super().widget_attrs(widget) 

424 if isinstance(widget, NumberInput) and "step" not in widget.attrs: 

425 if self.decimal_places is not None: 

426 # Use exponential notation for small values since they might 

427 # be parsed as 0 otherwise. ref #20765 

428 step = str(Decimal(1).scaleb(-self.decimal_places)).lower() 

429 else: 

430 step = "any" 

431 attrs.setdefault("step", step) 

432 return attrs 

433 

434 

435class BaseTemporalField(Field): 

436 def __init__(self, *, input_formats=None, **kwargs): 

437 super().__init__(**kwargs) 

438 if input_formats is not None: 438 ↛ 439line 438 didn't jump to line 439, because the condition on line 438 was never true

439 self.input_formats = input_formats 

440 

441 def to_python(self, value): 

442 value = value.strip() 

443 # Try to strptime against each input format. 

444 for format in self.input_formats: 

445 try: 

446 return self.strptime(value, format) 

447 except (ValueError, TypeError): 

448 continue 

449 raise ValidationError(self.error_messages["invalid"], code="invalid") 

450 

451 def strptime(self, value, format): 

452 raise NotImplementedError("Subclasses must define this method.") 

453 

454 

455class DateField(BaseTemporalField): 

456 widget = DateInput 

457 input_formats = formats.get_format_lazy("DATE_INPUT_FORMATS") 

458 default_error_messages = { 

459 "invalid": _("Enter a valid date."), 

460 } 

461 

462 def to_python(self, value): 

463 """ 

464 Validate that the input can be converted to a date. Return a Python 

465 datetime.date object. 

466 """ 

467 if value in self.empty_values: 467 ↛ 469line 467 didn't jump to line 469, because the condition on line 467 was never false

468 return None 

469 if isinstance(value, datetime.datetime): 

470 return value.date() 

471 if isinstance(value, datetime.date): 

472 return value 

473 return super().to_python(value) 

474 

475 def strptime(self, value, format): 

476 return datetime.datetime.strptime(value, format).date() 

477 

478 

479class TimeField(BaseTemporalField): 

480 widget = TimeInput 

481 input_formats = formats.get_format_lazy("TIME_INPUT_FORMATS") 

482 default_error_messages = {"invalid": _("Enter a valid time.")} 

483 

484 def to_python(self, value): 

485 """ 

486 Validate that the input can be converted to a time. Return a Python 

487 datetime.time object. 

488 """ 

489 if value in self.empty_values: 489 ↛ 491line 489 didn't jump to line 491, because the condition on line 489 was never false

490 return None 

491 if isinstance(value, datetime.time): 

492 return value 

493 return super().to_python(value) 

494 

495 def strptime(self, value, format): 

496 return datetime.datetime.strptime(value, format).time() 

497 

498 

499class DateTimeFormatsIterator: 

500 def __iter__(self): 

501 yield from formats.get_format("DATETIME_INPUT_FORMATS") 

502 yield from formats.get_format("DATE_INPUT_FORMATS") 

503 

504 

505class DateTimeField(BaseTemporalField): 

506 widget = DateTimeInput 

507 input_formats = DateTimeFormatsIterator() 

508 default_error_messages = { 

509 "invalid": _("Enter a valid date/time."), 

510 } 

511 

512 def prepare_value(self, value): 

513 if isinstance(value, datetime.datetime): 

514 value = to_current_timezone(value) 

515 return value 

516 

517 def to_python(self, value): 

518 """ 

519 Validate that the input can be converted to a datetime. Return a 

520 Python datetime.datetime object. 

521 """ 

522 if value in self.empty_values: 

523 return None 

524 if isinstance(value, datetime.datetime): 

525 return from_current_timezone(value) 

526 if isinstance(value, datetime.date): 

527 result = datetime.datetime(value.year, value.month, value.day) 

528 return from_current_timezone(result) 

529 try: 

530 result = parse_datetime(value.strip()) 

531 except ValueError: 

532 raise ValidationError(self.error_messages["invalid"], code="invalid") 

533 if not result: 

534 result = super().to_python(value) 

535 return from_current_timezone(result) 

536 

537 def strptime(self, value, format): 

538 return datetime.datetime.strptime(value, format) 

539 

540 

541class DurationField(Field): 

542 default_error_messages = { 

543 "invalid": _("Enter a valid duration."), 

544 "overflow": _("The number of days must be between {min_days} and {max_days}."), 

545 } 

546 

547 def prepare_value(self, value): 

548 if isinstance(value, datetime.timedelta): 

549 return duration_string(value) 

550 return value 

551 

552 def to_python(self, value): 

553 if value in self.empty_values: 

554 return None 

555 if isinstance(value, datetime.timedelta): 

556 return value 

557 try: 

558 value = parse_duration(str(value)) 

559 except OverflowError: 

560 raise ValidationError( 

561 self.error_messages["overflow"].format( 

562 min_days=datetime.timedelta.min.days, 

563 max_days=datetime.timedelta.max.days, 

564 ), 

565 code="overflow", 

566 ) 

567 if value is None: 

568 raise ValidationError(self.error_messages["invalid"], code="invalid") 

569 return value 

570 

571 

572class RegexField(CharField): 

573 def __init__(self, regex, **kwargs): 

574 """ 

575 regex can be either a string or a compiled regular expression object. 

576 """ 

577 kwargs.setdefault("strip", False) 

578 super().__init__(**kwargs) 

579 self._set_regex(regex) 

580 

581 def _get_regex(self): 

582 return self._regex 

583 

584 def _set_regex(self, regex): 

585 if isinstance(regex, str): 

586 regex = re.compile(regex) 

587 self._regex = regex 

588 if ( 

589 hasattr(self, "_regex_validator") 

590 and self._regex_validator in self.validators 

591 ): 

592 self.validators.remove(self._regex_validator) 

593 self._regex_validator = validators.RegexValidator(regex=regex) 

594 self.validators.append(self._regex_validator) 

595 

596 regex = property(_get_regex, _set_regex) 

597 

598 

599class EmailField(CharField): 

600 widget = EmailInput 

601 default_validators = [validators.validate_email] 

602 

603 def __init__(self, **kwargs): 

604 super().__init__(strip=True, **kwargs) 

605 

606 

607class FileField(Field): 

608 widget = ClearableFileInput 

609 default_error_messages = { 

610 "invalid": _("No file was submitted. Check the encoding type on the form."), 

611 "missing": _("No file was submitted."), 

612 "empty": _("The submitted file is empty."), 

613 "max_length": ngettext_lazy( 

614 "Ensure this filename has at most %(max)d character (it has %(length)d).", 

615 "Ensure this filename has at most %(max)d characters (it has %(length)d).", 

616 "max", 

617 ), 

618 "contradiction": _( 

619 "Please either submit a file or check the clear checkbox, not both." 

620 ), 

621 } 

622 

623 def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs): 

624 self.max_length = max_length 

625 self.allow_empty_file = allow_empty_file 

626 super().__init__(**kwargs) 

627 

628 def to_python(self, data): 

629 if data in self.empty_values: 629 ↛ 630line 629 didn't jump to line 630, because the condition on line 629 was never true

630 return None 

631 

632 # UploadedFile objects should have name and size attributes. 

633 try: 

634 file_name = data.name 

635 file_size = data.size 

636 except AttributeError: 

637 raise ValidationError(self.error_messages["invalid"], code="invalid") 

638 

639 if self.max_length is not None and len(file_name) > self.max_length: 639 ↛ 640line 639 didn't jump to line 640, because the condition on line 639 was never true

640 params = {"max": self.max_length, "length": len(file_name)} 

641 raise ValidationError( 

642 self.error_messages["max_length"], code="max_length", params=params 

643 ) 

644 if not file_name: 644 ↛ 645line 644 didn't jump to line 645, because the condition on line 644 was never true

645 raise ValidationError(self.error_messages["invalid"], code="invalid") 

646 if not self.allow_empty_file and not file_size: 646 ↛ 647line 646 didn't jump to line 647, because the condition on line 646 was never true

647 raise ValidationError(self.error_messages["empty"], code="empty") 

648 

649 return data 

650 

651 def clean(self, data, initial=None): 

652 # If the widget got contradictory inputs, we raise a validation error 

653 if data is FILE_INPUT_CONTRADICTION: 653 ↛ 654line 653 didn't jump to line 654, because the condition on line 653 was never true

654 raise ValidationError( 

655 self.error_messages["contradiction"], code="contradiction" 

656 ) 

657 # False means the field value should be cleared; further validation is 

658 # not needed. 

659 if data is False: 659 ↛ 660line 659 didn't jump to line 660, because the condition on line 659 was never true

660 if not self.required: 

661 return False 

662 # If the field is required, clearing is not possible (the widget 

663 # shouldn't return False data in that case anyway). False is not 

664 # in self.empty_value; if a False value makes it this far 

665 # it should be validated from here on out as None (so it will be 

666 # caught by the required check). 

667 data = None 

668 if not data and initial: 668 ↛ 669line 668 didn't jump to line 669, because the condition on line 668 was never true

669 return initial 

670 return super().clean(data) 

671 

672 def bound_data(self, data, initial): 

673 if data in (None, FILE_INPUT_CONTRADICTION): 

674 return initial 

675 return data 

676 

677 def has_changed(self, initial, data): 

678 return not self.disabled and data is not None 

679 

680 

681class ImageField(FileField): 

682 default_validators = [validators.validate_image_file_extension] 

683 default_error_messages = { 

684 "invalid_image": _( 

685 "Upload a valid image. The file you uploaded was either not an " 

686 "image or a corrupted image." 

687 ), 

688 } 

689 

690 def to_python(self, data): 

691 """ 

692 Check that the file-upload field data contains a valid image (GIF, JPG, 

693 PNG, etc. -- whatever Pillow supports). 

694 """ 

695 f = super().to_python(data) 

696 if f is None: 696 ↛ 697line 696 didn't jump to line 697, because the condition on line 696 was never true

697 return None 

698 

699 from PIL import Image 

700 

701 # We need to get a file object for Pillow. We might have a path or we might 

702 # have to read the data into memory. 

703 if hasattr(data, "temporary_file_path"): 703 ↛ 704line 703 didn't jump to line 704, because the condition on line 703 was never true

704 file = data.temporary_file_path() 

705 else: 

706 if hasattr(data, "read"): 706 ↛ 709line 706 didn't jump to line 709, because the condition on line 706 was never false

707 file = BytesIO(data.read()) 

708 else: 

709 file = BytesIO(data["content"]) 

710 

711 try: 

712 # load() could spot a truncated JPEG, but it loads the entire 

713 # image in memory, which is a DoS vector. See #3848 and #18520. 

714 image = Image.open(file) 

715 # verify() must be called immediately after the constructor. 

716 image.verify() 

717 

718 # Annotating so subclasses can reuse it for their own validation 

719 f.image = image 

720 # Pillow doesn't detect the MIME type of all formats. In those 

721 # cases, content_type will be None. 

722 f.content_type = Image.MIME.get(image.format) 

723 except Exception as exc: 

724 # Pillow doesn't recognize it as an image. 

725 raise ValidationError( 

726 self.error_messages["invalid_image"], 

727 code="invalid_image", 

728 ) from exc 

729 if hasattr(f, "seek") and callable(f.seek): 729 ↛ 731line 729 didn't jump to line 731, because the condition on line 729 was never false

730 f.seek(0) 

731 return f 

732 

733 def widget_attrs(self, widget): 

734 attrs = super().widget_attrs(widget) 

735 if isinstance(widget, FileInput) and "accept" not in widget.attrs: 735 ↛ 737line 735 didn't jump to line 737, because the condition on line 735 was never false

736 attrs.setdefault("accept", "image/*") 

737 return attrs 

738 

739 

740class URLField(CharField): 

741 widget = URLInput 

742 default_error_messages = { 

743 "invalid": _("Enter a valid URL."), 

744 } 

745 default_validators = [validators.URLValidator()] 

746 

747 def __init__(self, **kwargs): 

748 super().__init__(strip=True, **kwargs) 

749 

750 def to_python(self, value): 

751 def split_url(url): 

752 """ 

753 Return a list of url parts via urlparse.urlsplit(), or raise 

754 ValidationError for some malformed URLs. 

755 """ 

756 try: 

757 return list(urlsplit(url)) 

758 except ValueError: 

759 # urlparse.urlsplit can raise a ValueError with some 

760 # misformatted URLs. 

761 raise ValidationError(self.error_messages["invalid"], code="invalid") 

762 

763 value = super().to_python(value) 

764 if value: 

765 url_fields = split_url(value) 

766 if not url_fields[0]: 

767 # If no URL scheme given, assume http:// 

768 url_fields[0] = "http" 

769 if not url_fields[1]: 

770 # Assume that if no domain is provided, that the path segment 

771 # contains the domain. 

772 url_fields[1] = url_fields[2] 

773 url_fields[2] = "" 

774 # Rebuild the url_fields list, since the domain segment may now 

775 # contain the path too. 

776 url_fields = split_url(urlunsplit(url_fields)) 

777 value = urlunsplit(url_fields) 

778 return value 

779 

780 

781class BooleanField(Field): 

782 widget = CheckboxInput 

783 

784 def to_python(self, value): 

785 """Return a Python boolean object.""" 

786 # Explicitly check for the string 'False', which is what a hidden field 

787 # will submit for False. Also check for '0', since this is what 

788 # RadioSelect will provide. Because bool("True") == bool('1') == True, 

789 # we don't need to handle that explicitly. 

790 if isinstance(value, str) and value.lower() in ("false", "0"): 

791 value = False 

792 else: 

793 value = bool(value) 

794 return super().to_python(value) 

795 

796 def validate(self, value): 

797 if not value and self.required: 

798 raise ValidationError(self.error_messages["required"], code="required") 

799 

800 def has_changed(self, initial, data): 

801 if self.disabled: 

802 return False 

803 # Sometimes data or initial may be a string equivalent of a boolean 

804 # so we should run it through to_python first to get a boolean value 

805 return self.to_python(initial) != self.to_python(data) 

806 

807 

808class NullBooleanField(BooleanField): 

809 """ 

810 A field whose valid values are None, True, and False. Clean invalid values 

811 to None. 

812 """ 

813 

814 widget = NullBooleanSelect 

815 

816 def to_python(self, value): 

817 """ 

818 Explicitly check for the string 'True' and 'False', which is what a 

819 hidden field will submit for True and False, for 'true' and 'false', 

820 which are likely to be returned by JavaScript serializations of forms, 

821 and for '1' and '0', which is what a RadioField will submit. Unlike 

822 the Booleanfield, this field must check for True because it doesn't 

823 use the bool() function. 

824 """ 

825 if value in (True, "True", "true", "1"): 825 ↛ 826line 825 didn't jump to line 826, because the condition on line 825 was never true

826 return True 

827 elif value in (False, "False", "false", "0"): 827 ↛ 828line 827 didn't jump to line 828, because the condition on line 827 was never true

828 return False 

829 else: 

830 return None 

831 

832 def validate(self, value): 

833 pass 

834 

835 

836class CallableChoiceIterator: 

837 def __init__(self, choices_func): 

838 self.choices_func = choices_func 

839 

840 def __iter__(self): 

841 yield from self.choices_func() 

842 

843 

844class ChoiceField(Field): 

845 widget = Select 

846 default_error_messages = { 

847 "invalid_choice": _( 

848 "Select a valid choice. %(value)s is not one of the available choices." 

849 ), 

850 } 

851 

852 def __init__(self, *, choices=(), **kwargs): 

853 super().__init__(**kwargs) 

854 self.choices = choices 

855 

856 def __deepcopy__(self, memo): 

857 result = super().__deepcopy__(memo) 

858 result._choices = copy.deepcopy(self._choices, memo) 

859 return result 

860 

861 def _get_choices(self): 

862 return self._choices 

863 

864 def _set_choices(self, value): 

865 # Setting choices also sets the choices on the widget. 

866 # choices can be any iterable, but we call list() on it because 

867 # it will be consumed more than once. 

868 if callable(value): 868 ↛ 869line 868 didn't jump to line 869, because the condition on line 868 was never true

869 value = CallableChoiceIterator(value) 

870 else: 

871 value = list(value) 

872 

873 self._choices = self.widget.choices = value 

874 

875 choices = property(_get_choices, _set_choices) 

876 

877 def to_python(self, value): 

878 """Return a string.""" 

879 if value in self.empty_values: 879 ↛ 881line 879 didn't jump to line 881, because the condition on line 879 was never false

880 return "" 

881 return str(value) 

882 

883 def validate(self, value): 

884 """Validate that the input is in self.choices.""" 

885 super().validate(value) 

886 if value and not self.valid_value(value): 886 ↛ 887line 886 didn't jump to line 887, because the condition on line 886 was never true

887 raise ValidationError( 

888 self.error_messages["invalid_choice"], 

889 code="invalid_choice", 

890 params={"value": value}, 

891 ) 

892 

893 def valid_value(self, value): 

894 """Check to see if the provided value is a valid choice.""" 

895 text_value = str(value) 

896 for k, v in self.choices: 

897 if isinstance(v, (list, tuple)): 

898 # This is an optgroup, so look inside the group for options 

899 for k2, v2 in v: 

900 if value == k2 or text_value == str(k2): 

901 return True 

902 else: 

903 if value == k or text_value == str(k): 

904 return True 

905 return False 

906 

907 

908class TypedChoiceField(ChoiceField): 

909 def __init__(self, *, coerce=lambda val: val, empty_value="", **kwargs): 909 ↛ exitline 909 didn't run the lambda on line 909

910 self.coerce = coerce 

911 self.empty_value = empty_value 

912 super().__init__(**kwargs) 

913 

914 def _coerce(self, value): 

915 """ 

916 Validate that the value can be coerced to the right type (if not empty). 

917 """ 

918 if value == self.empty_value or value in self.empty_values: 

919 return self.empty_value 

920 try: 

921 value = self.coerce(value) 

922 except (ValueError, TypeError, ValidationError): 

923 raise ValidationError( 

924 self.error_messages["invalid_choice"], 

925 code="invalid_choice", 

926 params={"value": value}, 

927 ) 

928 return value 

929 

930 def clean(self, value): 

931 value = super().clean(value) 

932 return self._coerce(value) 

933 

934 

935class MultipleChoiceField(ChoiceField): 

936 hidden_widget = MultipleHiddenInput 

937 widget = SelectMultiple 

938 default_error_messages = { 

939 "invalid_choice": _( 

940 "Select a valid choice. %(value)s is not one of the available choices." 

941 ), 

942 "invalid_list": _("Enter a list of values."), 

943 } 

944 

945 def to_python(self, value): 

946 if not value: 

947 return [] 

948 elif not isinstance(value, (list, tuple)): 

949 raise ValidationError( 

950 self.error_messages["invalid_list"], code="invalid_list" 

951 ) 

952 return [str(val) for val in value] 

953 

954 def validate(self, value): 

955 """Validate that the input is a list or tuple.""" 

956 if self.required and not value: 

957 raise ValidationError(self.error_messages["required"], code="required") 

958 # Validate that each value in the value list is in self.choices. 

959 for val in value: 

960 if not self.valid_value(val): 

961 raise ValidationError( 

962 self.error_messages["invalid_choice"], 

963 code="invalid_choice", 

964 params={"value": val}, 

965 ) 

966 

967 def has_changed(self, initial, data): 

968 if self.disabled: 

969 return False 

970 if initial is None: 

971 initial = [] 

972 if data is None: 

973 data = [] 

974 if len(initial) != len(data): 

975 return True 

976 initial_set = {str(value) for value in initial} 

977 data_set = {str(value) for value in data} 

978 return data_set != initial_set 

979 

980 

981class TypedMultipleChoiceField(MultipleChoiceField): 

982 def __init__(self, *, coerce=lambda val: val, **kwargs): 982 ↛ exitline 982 didn't run the lambda on line 982

983 self.coerce = coerce 

984 self.empty_value = kwargs.pop("empty_value", []) 

985 super().__init__(**kwargs) 

986 

987 def _coerce(self, value): 

988 """ 

989 Validate that the values are in self.choices and can be coerced to the 

990 right type. 

991 """ 

992 if value == self.empty_value or value in self.empty_values: 

993 return self.empty_value 

994 new_value = [] 

995 for choice in value: 

996 try: 

997 new_value.append(self.coerce(choice)) 

998 except (ValueError, TypeError, ValidationError): 

999 raise ValidationError( 

1000 self.error_messages["invalid_choice"], 

1001 code="invalid_choice", 

1002 params={"value": choice}, 

1003 ) 

1004 return new_value 

1005 

1006 def clean(self, value): 

1007 value = super().clean(value) 

1008 return self._coerce(value) 

1009 

1010 def validate(self, value): 

1011 if value != self.empty_value: 

1012 super().validate(value) 

1013 elif self.required: 

1014 raise ValidationError(self.error_messages["required"], code="required") 

1015 

1016 

1017class ComboField(Field): 

1018 """ 

1019 A Field whose clean() method calls multiple Field clean() methods. 

1020 """ 

1021 

1022 def __init__(self, fields, **kwargs): 

1023 super().__init__(**kwargs) 

1024 # Set 'required' to False on the individual fields, because the 

1025 # required validation will be handled by ComboField, not by those 

1026 # individual fields. 

1027 for f in fields: 

1028 f.required = False 

1029 self.fields = fields 

1030 

1031 def clean(self, value): 

1032 """ 

1033 Validate the given value against all of self.fields, which is a 

1034 list of Field instances. 

1035 """ 

1036 super().clean(value) 

1037 for field in self.fields: 

1038 value = field.clean(value) 

1039 return value 

1040 

1041 

1042class MultiValueField(Field): 

1043 """ 

1044 Aggregate the logic of multiple Fields. 

1045 

1046 Its clean() method takes a "decompressed" list of values, which are then 

1047 cleaned into a single value according to self.fields. Each value in 

1048 this list is cleaned by the corresponding field -- the first value is 

1049 cleaned by the first field, the second value is cleaned by the second 

1050 field, etc. Once all fields are cleaned, the list of clean values is 

1051 "compressed" into a single value. 

1052 

1053 Subclasses should not have to implement clean(). Instead, they must 

1054 implement compress(), which takes a list of valid values and returns a 

1055 "compressed" version of those values -- a single value. 

1056 

1057 You'll probably want to use this with MultiWidget. 

1058 """ 

1059 

1060 default_error_messages = { 

1061 "invalid": _("Enter a list of values."), 

1062 "incomplete": _("Enter a complete value."), 

1063 } 

1064 

1065 def __init__(self, fields, *, require_all_fields=True, **kwargs): 

1066 self.require_all_fields = require_all_fields 

1067 super().__init__(**kwargs) 

1068 for f in fields: 

1069 f.error_messages.setdefault("incomplete", self.error_messages["incomplete"]) 

1070 if self.disabled: 

1071 f.disabled = True 

1072 if self.require_all_fields: 

1073 # Set 'required' to False on the individual fields, because the 

1074 # required validation will be handled by MultiValueField, not 

1075 # by those individual fields. 

1076 f.required = False 

1077 self.fields = fields 

1078 

1079 def __deepcopy__(self, memo): 

1080 result = super().__deepcopy__(memo) 

1081 result.fields = tuple(x.__deepcopy__(memo) for x in self.fields) 

1082 return result 

1083 

1084 def validate(self, value): 

1085 pass 

1086 

1087 def clean(self, value): 

1088 """ 

1089 Validate every value in the given list. A value is validated against 

1090 the corresponding Field in self.fields. 

1091 

1092 For example, if this MultiValueField was instantiated with 

1093 fields=(DateField(), TimeField()), clean() would call 

1094 DateField.clean(value[0]) and TimeField.clean(value[1]). 

1095 """ 

1096 clean_data = [] 

1097 errors = [] 

1098 if self.disabled and not isinstance(value, list): 

1099 value = self.widget.decompress(value) 

1100 if not value or isinstance(value, (list, tuple)): 

1101 if not value or not [v for v in value if v not in self.empty_values]: 

1102 if self.required: 

1103 raise ValidationError( 

1104 self.error_messages["required"], code="required" 

1105 ) 

1106 else: 

1107 return self.compress([]) 

1108 else: 

1109 raise ValidationError(self.error_messages["invalid"], code="invalid") 

1110 for i, field in enumerate(self.fields): 

1111 try: 

1112 field_value = value[i] 

1113 except IndexError: 

1114 field_value = None 

1115 if field_value in self.empty_values: 

1116 if self.require_all_fields: 

1117 # Raise a 'required' error if the MultiValueField is 

1118 # required and any field is empty. 

1119 if self.required: 

1120 raise ValidationError( 

1121 self.error_messages["required"], code="required" 

1122 ) 

1123 elif field.required: 

1124 # Otherwise, add an 'incomplete' error to the list of 

1125 # collected errors and skip field cleaning, if a required 

1126 # field is empty. 

1127 if field.error_messages["incomplete"] not in errors: 

1128 errors.append(field.error_messages["incomplete"]) 

1129 continue 

1130 try: 

1131 clean_data.append(field.clean(field_value)) 

1132 except ValidationError as e: 

1133 # Collect all validation errors in a single list, which we'll 

1134 # raise at the end of clean(), rather than raising a single 

1135 # exception for the first error we encounter. Skip duplicates. 

1136 errors.extend(m for m in e.error_list if m not in errors) 

1137 if errors: 

1138 raise ValidationError(errors) 

1139 

1140 out = self.compress(clean_data) 

1141 self.validate(out) 

1142 self.run_validators(out) 

1143 return out 

1144 

1145 def compress(self, data_list): 

1146 """ 

1147 Return a single value for the given list of values. The values can be 

1148 assumed to be valid. 

1149 

1150 For example, if this MultiValueField was instantiated with 

1151 fields=(DateField(), TimeField()), this might return a datetime 

1152 object created by combining the date and time in data_list. 

1153 """ 

1154 raise NotImplementedError("Subclasses must implement this method.") 

1155 

1156 def has_changed(self, initial, data): 

1157 if self.disabled: 

1158 return False 

1159 if initial is None: 

1160 initial = ["" for x in range(0, len(data))] 

1161 else: 

1162 if not isinstance(initial, list): 

1163 initial = self.widget.decompress(initial) 

1164 for field, initial, data in zip(self.fields, initial, data): 

1165 try: 

1166 initial = field.to_python(initial) 

1167 except ValidationError: 

1168 return True 

1169 if field.has_changed(initial, data): 

1170 return True 

1171 return False 

1172 

1173 

1174class FilePathField(ChoiceField): 

1175 def __init__( 

1176 self, 

1177 path, 

1178 *, 

1179 match=None, 

1180 recursive=False, 

1181 allow_files=True, 

1182 allow_folders=False, 

1183 **kwargs, 

1184 ): 

1185 self.path, self.match, self.recursive = path, match, recursive 

1186 self.allow_files, self.allow_folders = allow_files, allow_folders 

1187 super().__init__(choices=(), **kwargs) 

1188 

1189 if self.required: 

1190 self.choices = [] 

1191 else: 

1192 self.choices = [("", "---------")] 

1193 

1194 if self.match is not None: 

1195 self.match_re = re.compile(self.match) 

1196 

1197 if recursive: 

1198 for root, dirs, files in sorted(os.walk(self.path)): 

1199 if self.allow_files: 

1200 for f in sorted(files): 

1201 if self.match is None or self.match_re.search(f): 

1202 f = os.path.join(root, f) 

1203 self.choices.append((f, f.replace(path, "", 1))) 

1204 if self.allow_folders: 

1205 for f in sorted(dirs): 

1206 if f == "__pycache__": 

1207 continue 

1208 if self.match is None or self.match_re.search(f): 

1209 f = os.path.join(root, f) 

1210 self.choices.append((f, f.replace(path, "", 1))) 

1211 else: 

1212 choices = [] 

1213 with os.scandir(self.path) as entries: 

1214 for f in entries: 

1215 if f.name == "__pycache__": 

1216 continue 

1217 if ( 

1218 (self.allow_files and f.is_file()) 

1219 or (self.allow_folders and f.is_dir()) 

1220 ) and (self.match is None or self.match_re.search(f.name)): 

1221 choices.append((f.path, f.name)) 

1222 choices.sort(key=operator.itemgetter(1)) 

1223 self.choices.extend(choices) 

1224 

1225 self.widget.choices = self.choices 

1226 

1227 

1228class SplitDateTimeField(MultiValueField): 

1229 widget = SplitDateTimeWidget 

1230 hidden_widget = SplitHiddenDateTimeWidget 

1231 default_error_messages = { 

1232 "invalid_date": _("Enter a valid date."), 

1233 "invalid_time": _("Enter a valid time."), 

1234 } 

1235 

1236 def __init__(self, *, input_date_formats=None, input_time_formats=None, **kwargs): 

1237 errors = self.default_error_messages.copy() 

1238 if "error_messages" in kwargs: 

1239 errors.update(kwargs["error_messages"]) 

1240 localize = kwargs.get("localize", False) 

1241 fields = ( 

1242 DateField( 

1243 input_formats=input_date_formats, 

1244 error_messages={"invalid": errors["invalid_date"]}, 

1245 localize=localize, 

1246 ), 

1247 TimeField( 

1248 input_formats=input_time_formats, 

1249 error_messages={"invalid": errors["invalid_time"]}, 

1250 localize=localize, 

1251 ), 

1252 ) 

1253 super().__init__(fields, **kwargs) 

1254 

1255 def compress(self, data_list): 

1256 if data_list: 

1257 # Raise a validation error if time or date is empty 

1258 # (possible if SplitDateTimeField has required=False). 

1259 if data_list[0] in self.empty_values: 

1260 raise ValidationError( 

1261 self.error_messages["invalid_date"], code="invalid_date" 

1262 ) 

1263 if data_list[1] in self.empty_values: 

1264 raise ValidationError( 

1265 self.error_messages["invalid_time"], code="invalid_time" 

1266 ) 

1267 result = datetime.datetime.combine(*data_list) 

1268 return from_current_timezone(result) 

1269 return None 

1270 

1271 

1272class GenericIPAddressField(CharField): 

1273 def __init__(self, *, protocol="both", unpack_ipv4=False, **kwargs): 

1274 self.unpack_ipv4 = unpack_ipv4 

1275 self.default_validators = validators.ip_address_validators( 

1276 protocol, unpack_ipv4 

1277 )[0] 

1278 super().__init__(**kwargs) 

1279 

1280 def to_python(self, value): 

1281 if value in self.empty_values: 

1282 return "" 

1283 value = value.strip() 

1284 if value and ":" in value: 

1285 return clean_ipv6_address(value, self.unpack_ipv4) 

1286 return value 

1287 

1288 

1289class SlugField(CharField): 

1290 default_validators = [validators.validate_slug] 

1291 

1292 def __init__(self, *, allow_unicode=False, **kwargs): 

1293 self.allow_unicode = allow_unicode 

1294 if self.allow_unicode: 

1295 self.default_validators = [validators.validate_unicode_slug] 

1296 super().__init__(**kwargs) 

1297 

1298 

1299class UUIDField(CharField): 

1300 default_error_messages = { 

1301 "invalid": _("Enter a valid UUID."), 

1302 } 

1303 

1304 def prepare_value(self, value): 

1305 if isinstance(value, uuid.UUID): 

1306 return str(value) 

1307 return value 

1308 

1309 def to_python(self, value): 

1310 value = super().to_python(value) 

1311 if value in self.empty_values: 

1312 return None 

1313 if not isinstance(value, uuid.UUID): 

1314 try: 

1315 value = uuid.UUID(value) 

1316 except ValueError: 

1317 raise ValidationError(self.error_messages["invalid"], code="invalid") 

1318 return value 

1319 

1320 

1321class InvalidJSONInput(str): 

1322 pass 

1323 

1324 

1325class JSONString(str): 

1326 pass 

1327 

1328 

1329class JSONField(CharField): 

1330 default_error_messages = { 

1331 "invalid": _("Enter a valid JSON."), 

1332 } 

1333 widget = Textarea 

1334 

1335 def __init__(self, encoder=None, decoder=None, **kwargs): 

1336 self.encoder = encoder 

1337 self.decoder = decoder 

1338 super().__init__(**kwargs) 

1339 

1340 def to_python(self, value): 

1341 if self.disabled: 

1342 return value 

1343 if value in self.empty_values: 

1344 return None 

1345 elif isinstance(value, (list, dict, int, float, JSONString)): 

1346 return value 

1347 try: 

1348 converted = json.loads(value, cls=self.decoder) 

1349 except json.JSONDecodeError: 

1350 raise ValidationError( 

1351 self.error_messages["invalid"], 

1352 code="invalid", 

1353 params={"value": value}, 

1354 ) 

1355 if isinstance(converted, str): 

1356 return JSONString(converted) 

1357 else: 

1358 return converted 

1359 

1360 def bound_data(self, data, initial): 

1361 if self.disabled: 

1362 return initial 

1363 if data is None: 

1364 return None 

1365 try: 

1366 return json.loads(data, cls=self.decoder) 

1367 except json.JSONDecodeError: 

1368 return InvalidJSONInput(data) 

1369 

1370 def prepare_value(self, value): 

1371 if isinstance(value, InvalidJSONInput): 

1372 return value 

1373 return json.dumps(value, ensure_ascii=False, cls=self.encoder) 

1374 

1375 def has_changed(self, initial, data): 

1376 if super().has_changed(initial, data): 

1377 return True 

1378 # For purposes of seeing whether something has changed, True isn't the 

1379 # same as 1 and the order of keys doesn't matter. 

1380 return json.dumps(initial, sort_keys=True, cls=self.encoder) != json.dumps( 

1381 self.to_python(data), sort_keys=True, cls=self.encoder 

1382 )