Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/forms/fields.py: 54%

767 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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: 

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: 

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: 

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: 

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: 

277 value = str(value) 

278 if self.strip: 

279 value = value.strip() 

280 if value in self.empty_values: 

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: 

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, step_size=None, **kwargs): 

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

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 if step_size is not None: 

314 self.validators.append(validators.StepValueValidator(step_size)) 

315 

316 def to_python(self, value): 

317 """ 

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

319 of int() or None for empty values. 

320 """ 

321 value = super().to_python(value) 

322 if value in self.empty_values: 

323 return None 

324 if self.localize: 

325 value = formats.sanitize_separators(value) 

326 # Strip trailing decimal and zeros. 

327 try: 

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

329 except (ValueError, TypeError): 

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

331 return value 

332 

333 def widget_attrs(self, widget): 

334 attrs = super().widget_attrs(widget) 

335 if isinstance(widget, NumberInput): 

336 if self.min_value is not None: 

337 attrs["min"] = self.min_value 

338 if self.max_value is not None: 

339 attrs["max"] = self.max_value 

340 if self.step_size is not None: 

341 attrs["step"] = self.step_size 

342 return attrs 

343 

344 

345class FloatField(IntegerField): 

346 default_error_messages = { 

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

348 } 

349 

350 def to_python(self, value): 

351 """ 

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

353 of float() or None for empty values. 

354 """ 

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

356 if value in self.empty_values: 

357 return None 

358 if self.localize: 

359 value = formats.sanitize_separators(value) 

360 try: 

361 value = float(value) 

362 except (ValueError, TypeError): 

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

364 return value 

365 

366 def validate(self, value): 

367 super().validate(value) 

368 if value in self.empty_values: 

369 return 

370 if not math.isfinite(value): 

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

372 

373 def widget_attrs(self, widget): 

374 attrs = super().widget_attrs(widget) 

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

376 if self.step_size is not None: 

377 step = str(self.step_size) 

378 else: 

379 step = "any" 

380 attrs.setdefault("step", step) 

381 return attrs 

382 

383 

384class DecimalField(IntegerField): 

385 default_error_messages = { 

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

387 } 

388 

389 def __init__( 

390 self, 

391 *, 

392 max_value=None, 

393 min_value=None, 

394 max_digits=None, 

395 decimal_places=None, 

396 **kwargs, 

397 ): 

398 self.max_digits, self.decimal_places = max_digits, decimal_places 

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

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

401 

402 def to_python(self, value): 

403 """ 

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

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

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

407 after the decimal point. 

408 """ 

409 if value in self.empty_values: 

410 return None 

411 if self.localize: 

412 value = formats.sanitize_separators(value) 

413 try: 

414 value = Decimal(str(value)) 

415 except DecimalException: 

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

417 return value 

418 

419 def validate(self, value): 

420 super().validate(value) 

421 if value in self.empty_values: 

422 return 

423 if not value.is_finite(): 

424 raise ValidationError( 

425 self.error_messages["invalid"], 

426 code="invalid", 

427 params={"value": value}, 

428 ) 

429 

430 def widget_attrs(self, widget): 

431 attrs = super().widget_attrs(widget) 

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

433 if self.decimal_places is not None: 

434 # Use exponential notation for small values since they might 

435 # be parsed as 0 otherwise. ref #20765 

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

437 else: 

438 step = "any" 

439 attrs.setdefault("step", step) 

440 return attrs 

441 

442 

443class BaseTemporalField(Field): 

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

445 super().__init__(**kwargs) 

446 if input_formats is not None: 

447 self.input_formats = input_formats 

448 

449 def to_python(self, value): 

450 value = value.strip() 

451 # Try to strptime against each input format. 

452 for format in self.input_formats: 

453 try: 

454 return self.strptime(value, format) 

455 except (ValueError, TypeError): 

456 continue 

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

458 

459 def strptime(self, value, format): 

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

461 

462 

463class DateField(BaseTemporalField): 

464 widget = DateInput 

465 input_formats = formats.get_format_lazy("DATE_INPUT_FORMATS") 

466 default_error_messages = { 

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

468 } 

469 

470 def to_python(self, value): 

471 """ 

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

473 datetime.date object. 

474 """ 

475 if value in self.empty_values: 

476 return None 

477 if isinstance(value, datetime.datetime): 

478 return value.date() 

479 if isinstance(value, datetime.date): 

480 return value 

481 return super().to_python(value) 

482 

483 def strptime(self, value, format): 

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

485 

486 

487class TimeField(BaseTemporalField): 

488 widget = TimeInput 

489 input_formats = formats.get_format_lazy("TIME_INPUT_FORMATS") 

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

491 

492 def to_python(self, value): 

493 """ 

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

495 datetime.time object. 

496 """ 

497 if value in self.empty_values: 

498 return None 

499 if isinstance(value, datetime.time): 

500 return value 

501 return super().to_python(value) 

502 

503 def strptime(self, value, format): 

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

505 

506 

507class DateTimeFormatsIterator: 

508 def __iter__(self): 

509 yield from formats.get_format("DATETIME_INPUT_FORMATS") 

510 yield from formats.get_format("DATE_INPUT_FORMATS") 

511 

512 

513class DateTimeField(BaseTemporalField): 

514 widget = DateTimeInput 

515 input_formats = DateTimeFormatsIterator() 

516 default_error_messages = { 

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

518 } 

519 

520 def prepare_value(self, value): 

521 if isinstance(value, datetime.datetime): 

522 value = to_current_timezone(value) 

523 return value 

524 

525 def to_python(self, value): 

526 """ 

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

528 Python datetime.datetime object. 

529 """ 

530 if value in self.empty_values: 

531 return None 

532 if isinstance(value, datetime.datetime): 

533 return from_current_timezone(value) 

534 if isinstance(value, datetime.date): 

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

536 return from_current_timezone(result) 

537 try: 

538 result = parse_datetime(value.strip()) 

539 except ValueError: 

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

541 if not result: 

542 result = super().to_python(value) 

543 return from_current_timezone(result) 

544 

545 def strptime(self, value, format): 

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

547 

548 

549class DurationField(Field): 

550 default_error_messages = { 

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

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

553 } 

554 

555 def prepare_value(self, value): 

556 if isinstance(value, datetime.timedelta): 

557 return duration_string(value) 

558 return value 

559 

560 def to_python(self, value): 

561 if value in self.empty_values: 

562 return None 

563 if isinstance(value, datetime.timedelta): 

564 return value 

565 try: 

566 value = parse_duration(str(value)) 

567 except OverflowError: 

568 raise ValidationError( 

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

570 min_days=datetime.timedelta.min.days, 

571 max_days=datetime.timedelta.max.days, 

572 ), 

573 code="overflow", 

574 ) 

575 if value is None: 

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

577 return value 

578 

579 

580class RegexField(CharField): 

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

582 """ 

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

584 """ 

585 kwargs.setdefault("strip", False) 

586 super().__init__(**kwargs) 

587 self._set_regex(regex) 

588 

589 def _get_regex(self): 

590 return self._regex 

591 

592 def _set_regex(self, regex): 

593 if isinstance(regex, str): 

594 regex = re.compile(regex) 

595 self._regex = regex 

596 if ( 

597 hasattr(self, "_regex_validator") 

598 and self._regex_validator in self.validators 

599 ): 

600 self.validators.remove(self._regex_validator) 

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

602 self.validators.append(self._regex_validator) 

603 

604 regex = property(_get_regex, _set_regex) 

605 

606 

607class EmailField(CharField): 

608 widget = EmailInput 

609 default_validators = [validators.validate_email] 

610 

611 def __init__(self, **kwargs): 

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

613 

614 

615class FileField(Field): 

616 widget = ClearableFileInput 

617 default_error_messages = { 

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

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

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

621 "max_length": ngettext_lazy( 

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

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

624 "max", 

625 ), 

626 "contradiction": _( 

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

628 ), 

629 } 

630 

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

632 self.max_length = max_length 

633 self.allow_empty_file = allow_empty_file 

634 super().__init__(**kwargs) 

635 

636 def to_python(self, data): 

637 if data in self.empty_values: 

638 return None 

639 

640 # UploadedFile objects should have name and size attributes. 

641 try: 

642 file_name = data.name 

643 file_size = data.size 

644 except AttributeError: 

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

646 

647 if self.max_length is not None and len(file_name) > self.max_length: 

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

649 raise ValidationError( 

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

651 ) 

652 if not file_name: 

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

654 if not self.allow_empty_file and not file_size: 

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

656 

657 return data 

658 

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

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

661 if data is FILE_INPUT_CONTRADICTION: 

662 raise ValidationError( 

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

664 ) 

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

666 # not needed. 

667 if data is False: 

668 if not self.required: 

669 return False 

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

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

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

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

674 # caught by the required check). 

675 data = None 

676 if not data and initial: 

677 return initial 

678 return super().clean(data) 

679 

680 def bound_data(self, _, initial): 

681 return initial 

682 

683 def has_changed(self, initial, data): 

684 return not self.disabled and data is not None 

685 

686 

687class ImageField(FileField): 

688 default_validators = [validators.validate_image_file_extension] 

689 default_error_messages = { 

690 "invalid_image": _( 

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

692 "image or a corrupted image." 

693 ), 

694 } 

695 

696 def to_python(self, data): 

697 """ 

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

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

700 """ 

701 f = super().to_python(data) 

702 if f is None: 

703 return None 

704 

705 from PIL import Image 

706 

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

708 # have to read the data into memory. 

709 if hasattr(data, "temporary_file_path"): 

710 file = data.temporary_file_path() 

711 else: 

712 if hasattr(data, "read"): 

713 file = BytesIO(data.read()) 

714 else: 

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

716 

717 try: 

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

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

720 image = Image.open(file) 

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

722 image.verify() 

723 

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

725 f.image = image 

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

727 # cases, content_type will be None. 

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

729 except Exception as exc: 

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

731 raise ValidationError( 

732 self.error_messages["invalid_image"], 

733 code="invalid_image", 

734 ) from exc 

735 if hasattr(f, "seek") and callable(f.seek): 

736 f.seek(0) 

737 return f 

738 

739 def widget_attrs(self, widget): 

740 attrs = super().widget_attrs(widget) 

741 if isinstance(widget, FileInput) and "accept" not in widget.attrs: 

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

743 return attrs 

744 

745 

746class URLField(CharField): 

747 widget = URLInput 

748 default_error_messages = { 

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

750 } 

751 default_validators = [validators.URLValidator()] 

752 

753 def __init__(self, **kwargs): 

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

755 

756 def to_python(self, value): 

757 def split_url(url): 

758 """ 

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

760 ValidationError for some malformed URLs. 

761 """ 

762 try: 

763 return list(urlsplit(url)) 

764 except ValueError: 

765 # urlparse.urlsplit can raise a ValueError with some 

766 # misformatted URLs. 

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

768 

769 value = super().to_python(value) 

770 if value: 

771 url_fields = split_url(value) 

772 if not url_fields[0]: 

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

774 url_fields[0] = "http" 

775 if not url_fields[1]: 

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

777 # contains the domain. 

778 url_fields[1] = url_fields[2] 

779 url_fields[2] = "" 

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

781 # contain the path too. 

782 url_fields = split_url(urlunsplit(url_fields)) 

783 value = urlunsplit(url_fields) 

784 return value 

785 

786 

787class BooleanField(Field): 

788 widget = CheckboxInput 

789 

790 def to_python(self, value): 

791 """Return a Python boolean object.""" 

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

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

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

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

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

797 value = False 

798 else: 

799 value = bool(value) 

800 return super().to_python(value) 

801 

802 def validate(self, value): 

803 if not value and self.required: 

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

805 

806 def has_changed(self, initial, data): 

807 if self.disabled: 

808 return False 

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

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

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

812 

813 

814class NullBooleanField(BooleanField): 

815 """ 

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

817 to None. 

818 """ 

819 

820 widget = NullBooleanSelect 

821 

822 def to_python(self, value): 

823 """ 

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

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

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

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

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

829 use the bool() function. 

830 """ 

831 if value in (True, "True", "true", "1"): 

832 return True 

833 elif value in (False, "False", "false", "0"): 

834 return False 

835 else: 

836 return None 

837 

838 def validate(self, value): 

839 pass 

840 

841 

842class CallableChoiceIterator: 

843 def __init__(self, choices_func): 

844 self.choices_func = choices_func 

845 

846 def __iter__(self): 

847 yield from self.choices_func() 

848 

849 

850class ChoiceField(Field): 

851 widget = Select 

852 default_error_messages = { 

853 "invalid_choice": _( 

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

855 ), 

856 } 

857 

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

859 super().__init__(**kwargs) 

860 self.choices = choices 

861 

862 def __deepcopy__(self, memo): 

863 result = super().__deepcopy__(memo) 

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

865 return result 

866 

867 def _get_choices(self): 

868 return self._choices 

869 

870 def _set_choices(self, value): 

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

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

873 # it will be consumed more than once. 

874 if callable(value): 

875 value = CallableChoiceIterator(value) 

876 else: 

877 value = list(value) 

878 

879 self._choices = self.widget.choices = value 

880 

881 choices = property(_get_choices, _set_choices) 

882 

883 def to_python(self, value): 

884 """Return a string.""" 

885 if value in self.empty_values: 

886 return "" 

887 return str(value) 

888 

889 def validate(self, value): 

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

891 super().validate(value) 

892 if value and not self.valid_value(value): 

893 raise ValidationError( 

894 self.error_messages["invalid_choice"], 

895 code="invalid_choice", 

896 params={"value": value}, 

897 ) 

898 

899 def valid_value(self, value): 

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

901 text_value = str(value) 

902 for k, v in self.choices: 

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

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

905 for k2, v2 in v: 

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

907 return True 

908 else: 

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

910 return True 

911 return False 

912 

913 

914class TypedChoiceField(ChoiceField): 

915 def __init__(self, *, coerce=lambda val: val, empty_value="", **kwargs): 

916 self.coerce = coerce 

917 self.empty_value = empty_value 

918 super().__init__(**kwargs) 

919 

920 def _coerce(self, value): 

921 """ 

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

923 """ 

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

925 return self.empty_value 

926 try: 

927 value = self.coerce(value) 

928 except (ValueError, TypeError, ValidationError): 

929 raise ValidationError( 

930 self.error_messages["invalid_choice"], 

931 code="invalid_choice", 

932 params={"value": value}, 

933 ) 

934 return value 

935 

936 def clean(self, value): 

937 value = super().clean(value) 

938 return self._coerce(value) 

939 

940 

941class MultipleChoiceField(ChoiceField): 

942 hidden_widget = MultipleHiddenInput 

943 widget = SelectMultiple 

944 default_error_messages = { 

945 "invalid_choice": _( 

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

947 ), 

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

949 } 

950 

951 def to_python(self, value): 

952 if not value: 

953 return [] 

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

955 raise ValidationError( 

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

957 ) 

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

959 

960 def validate(self, value): 

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

962 if self.required and not value: 

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

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

965 for val in value: 

966 if not self.valid_value(val): 

967 raise ValidationError( 

968 self.error_messages["invalid_choice"], 

969 code="invalid_choice", 

970 params={"value": val}, 

971 ) 

972 

973 def has_changed(self, initial, data): 

974 if self.disabled: 

975 return False 

976 if initial is None: 

977 initial = [] 

978 if data is None: 

979 data = [] 

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

981 return True 

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

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

984 return data_set != initial_set 

985 

986 

987class TypedMultipleChoiceField(MultipleChoiceField): 

988 def __init__(self, *, coerce=lambda val: val, **kwargs): 

989 self.coerce = coerce 

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

991 super().__init__(**kwargs) 

992 

993 def _coerce(self, value): 

994 """ 

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

996 right type. 

997 """ 

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

999 return self.empty_value 

1000 new_value = [] 

1001 for choice in value: 

1002 try: 

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

1004 except (ValueError, TypeError, ValidationError): 

1005 raise ValidationError( 

1006 self.error_messages["invalid_choice"], 

1007 code="invalid_choice", 

1008 params={"value": choice}, 

1009 ) 

1010 return new_value 

1011 

1012 def clean(self, value): 

1013 value = super().clean(value) 

1014 return self._coerce(value) 

1015 

1016 def validate(self, value): 

1017 if value != self.empty_value: 

1018 super().validate(value) 

1019 elif self.required: 

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

1021 

1022 

1023class ComboField(Field): 

1024 """ 

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

1026 """ 

1027 

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

1029 super().__init__(**kwargs) 

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

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

1032 # individual fields. 

1033 for f in fields: 

1034 f.required = False 

1035 self.fields = fields 

1036 

1037 def clean(self, value): 

1038 """ 

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

1040 list of Field instances. 

1041 """ 

1042 super().clean(value) 

1043 for field in self.fields: 

1044 value = field.clean(value) 

1045 return value 

1046 

1047 

1048class MultiValueField(Field): 

1049 """ 

1050 Aggregate the logic of multiple Fields. 

1051 

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

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

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

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

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

1057 "compressed" into a single value. 

1058 

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

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

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

1062 

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

1064 """ 

1065 

1066 default_error_messages = { 

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

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

1069 } 

1070 

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

1072 self.require_all_fields = require_all_fields 

1073 super().__init__(**kwargs) 

1074 for f in fields: 

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

1076 if self.disabled: 

1077 f.disabled = True 

1078 if self.require_all_fields: 

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

1080 # required validation will be handled by MultiValueField, not 

1081 # by those individual fields. 

1082 f.required = False 

1083 self.fields = fields 

1084 

1085 def __deepcopy__(self, memo): 

1086 result = super().__deepcopy__(memo) 

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

1088 return result 

1089 

1090 def validate(self, value): 

1091 pass 

1092 

1093 def clean(self, value): 

1094 """ 

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

1096 the corresponding Field in self.fields. 

1097 

1098 For example, if this MultiValueField was instantiated with 

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

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

1101 """ 

1102 clean_data = [] 

1103 errors = [] 

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

1105 value = self.widget.decompress(value) 

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

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

1108 if self.required: 

1109 raise ValidationError( 

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

1111 ) 

1112 else: 

1113 return self.compress([]) 

1114 else: 

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

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

1117 try: 

1118 field_value = value[i] 

1119 except IndexError: 

1120 field_value = None 

1121 if field_value in self.empty_values: 

1122 if self.require_all_fields: 

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

1124 # required and any field is empty. 

1125 if self.required: 

1126 raise ValidationError( 

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

1128 ) 

1129 elif field.required: 

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

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

1132 # field is empty. 

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

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

1135 continue 

1136 try: 

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

1138 except ValidationError as e: 

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

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

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

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

1143 if errors: 

1144 raise ValidationError(errors) 

1145 

1146 out = self.compress(clean_data) 

1147 self.validate(out) 

1148 self.run_validators(out) 

1149 return out 

1150 

1151 def compress(self, data_list): 

1152 """ 

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

1154 assumed to be valid. 

1155 

1156 For example, if this MultiValueField was instantiated with 

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

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

1159 """ 

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

1161 

1162 def has_changed(self, initial, data): 

1163 if self.disabled: 

1164 return False 

1165 if initial is None: 

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

1167 else: 

1168 if not isinstance(initial, list): 

1169 initial = self.widget.decompress(initial) 

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

1171 try: 

1172 initial = field.to_python(initial) 

1173 except ValidationError: 

1174 return True 

1175 if field.has_changed(initial, data): 

1176 return True 

1177 return False 

1178 

1179 

1180class FilePathField(ChoiceField): 

1181 def __init__( 

1182 self, 

1183 path, 

1184 *, 

1185 match=None, 

1186 recursive=False, 

1187 allow_files=True, 

1188 allow_folders=False, 

1189 **kwargs, 

1190 ): 

1191 self.path, self.match, self.recursive = path, match, recursive 

1192 self.allow_files, self.allow_folders = allow_files, allow_folders 

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

1194 

1195 if self.required: 

1196 self.choices = [] 

1197 else: 

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

1199 

1200 if self.match is not None: 

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

1202 

1203 if recursive: 

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

1205 if self.allow_files: 

1206 for f in sorted(files): 

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

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

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

1210 if self.allow_folders: 

1211 for f in sorted(dirs): 

1212 if f == "__pycache__": 

1213 continue 

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

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

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

1217 else: 

1218 choices = [] 

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

1220 for f in entries: 

1221 if f.name == "__pycache__": 

1222 continue 

1223 if ( 

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

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

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

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

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

1229 self.choices.extend(choices) 

1230 

1231 self.widget.choices = self.choices 

1232 

1233 

1234class SplitDateTimeField(MultiValueField): 

1235 widget = SplitDateTimeWidget 

1236 hidden_widget = SplitHiddenDateTimeWidget 

1237 default_error_messages = { 

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

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

1240 } 

1241 

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

1243 errors = self.default_error_messages.copy() 

1244 if "error_messages" in kwargs: 

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

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

1247 fields = ( 

1248 DateField( 

1249 input_formats=input_date_formats, 

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

1251 localize=localize, 

1252 ), 

1253 TimeField( 

1254 input_formats=input_time_formats, 

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

1256 localize=localize, 

1257 ), 

1258 ) 

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

1260 

1261 def compress(self, data_list): 

1262 if data_list: 

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

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

1265 if data_list[0] in self.empty_values: 

1266 raise ValidationError( 

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

1268 ) 

1269 if data_list[1] in self.empty_values: 

1270 raise ValidationError( 

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

1272 ) 

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

1274 return from_current_timezone(result) 

1275 return None 

1276 

1277 

1278class GenericIPAddressField(CharField): 

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

1280 self.unpack_ipv4 = unpack_ipv4 

1281 self.default_validators = validators.ip_address_validators( 

1282 protocol, unpack_ipv4 

1283 )[0] 

1284 super().__init__(**kwargs) 

1285 

1286 def to_python(self, value): 

1287 if value in self.empty_values: 

1288 return "" 

1289 value = value.strip() 

1290 if value and ":" in value: 

1291 return clean_ipv6_address(value, self.unpack_ipv4) 

1292 return value 

1293 

1294 

1295class SlugField(CharField): 

1296 default_validators = [validators.validate_slug] 

1297 

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

1299 self.allow_unicode = allow_unicode 

1300 if self.allow_unicode: 

1301 self.default_validators = [validators.validate_unicode_slug] 

1302 super().__init__(**kwargs) 

1303 

1304 

1305class UUIDField(CharField): 

1306 default_error_messages = { 

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

1308 } 

1309 

1310 def prepare_value(self, value): 

1311 if isinstance(value, uuid.UUID): 

1312 return str(value) 

1313 return value 

1314 

1315 def to_python(self, value): 

1316 value = super().to_python(value) 

1317 if value in self.empty_values: 

1318 return None 

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

1320 try: 

1321 value = uuid.UUID(value) 

1322 except ValueError: 

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

1324 return value 

1325 

1326 

1327class InvalidJSONInput(str): 

1328 pass 

1329 

1330 

1331class JSONString(str): 

1332 pass 

1333 

1334 

1335class JSONField(CharField): 

1336 default_error_messages = { 

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

1338 } 

1339 widget = Textarea 

1340 

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

1342 self.encoder = encoder 

1343 self.decoder = decoder 

1344 super().__init__(**kwargs) 

1345 

1346 def to_python(self, value): 

1347 if self.disabled: 

1348 return value 

1349 if value in self.empty_values: 

1350 return None 

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

1352 return value 

1353 try: 

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

1355 except json.JSONDecodeError: 

1356 raise ValidationError( 

1357 self.error_messages["invalid"], 

1358 code="invalid", 

1359 params={"value": value}, 

1360 ) 

1361 if isinstance(converted, str): 

1362 return JSONString(converted) 

1363 else: 

1364 return converted 

1365 

1366 def bound_data(self, data, initial): 

1367 if self.disabled: 

1368 return initial 

1369 if data is None: 

1370 return None 

1371 try: 

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

1373 except json.JSONDecodeError: 

1374 return InvalidJSONInput(data) 

1375 

1376 def prepare_value(self, value): 

1377 if isinstance(value, InvalidJSONInput): 

1378 return value 

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

1380 

1381 def has_changed(self, initial, data): 

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

1383 return True 

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

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

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

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

1388 )