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
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1"""
2Field classes.
3"""
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
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
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)
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)
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)
144 # Trigger the localization machinery if needed.
145 self.localize = localize
146 if self.localize:
147 widget.is_localized = True
149 # Let the widget know whether it should display as required.
150 widget.is_required = self.required
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)
157 self.widget = widget
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
165 self.validators = [*self.default_validators, *validators]
167 super().__init__()
169 def prepare_value(self, value):
170 return value
172 def to_python(self, value):
173 return value
175 def validate(self, value):
176 if value in self.empty_values and self.required:
177 raise ValidationError(self.error_messages["required"], code="required")
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)
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
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.
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
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 {}
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
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)
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
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())
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
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
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*$")
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)
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))
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
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
345class FloatField(IntegerField):
346 default_error_messages = {
347 "invalid": _("Enter a number."),
348 }
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
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")
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
384class DecimalField(IntegerField):
385 default_error_messages = {
386 "invalid": _("Enter a number."),
387 }
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))
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
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 )
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
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
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")
459 def strptime(self, value, format):
460 raise NotImplementedError("Subclasses must define this method.")
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 }
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)
483 def strptime(self, value, format):
484 return datetime.datetime.strptime(value, format).date()
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.")}
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)
503 def strptime(self, value, format):
504 return datetime.datetime.strptime(value, format).time()
507class DateTimeFormatsIterator:
508 def __iter__(self):
509 yield from formats.get_format("DATETIME_INPUT_FORMATS")
510 yield from formats.get_format("DATE_INPUT_FORMATS")
513class DateTimeField(BaseTemporalField):
514 widget = DateTimeInput
515 input_formats = DateTimeFormatsIterator()
516 default_error_messages = {
517 "invalid": _("Enter a valid date/time."),
518 }
520 def prepare_value(self, value):
521 if isinstance(value, datetime.datetime):
522 value = to_current_timezone(value)
523 return value
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)
545 def strptime(self, value, format):
546 return datetime.datetime.strptime(value, format)
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 }
555 def prepare_value(self, value):
556 if isinstance(value, datetime.timedelta):
557 return duration_string(value)
558 return value
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
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)
589 def _get_regex(self):
590 return self._regex
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)
604 regex = property(_get_regex, _set_regex)
607class EmailField(CharField):
608 widget = EmailInput
609 default_validators = [validators.validate_email]
611 def __init__(self, **kwargs):
612 super().__init__(strip=True, **kwargs)
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 }
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)
636 def to_python(self, data):
637 if data in self.empty_values:
638 return None
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")
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")
657 return data
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)
680 def bound_data(self, _, initial):
681 return initial
683 def has_changed(self, initial, data):
684 return not self.disabled and data is not None
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 }
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
705 from PIL import Image
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"])
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()
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
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
746class URLField(CharField):
747 widget = URLInput
748 default_error_messages = {
749 "invalid": _("Enter a valid URL."),
750 }
751 default_validators = [validators.URLValidator()]
753 def __init__(self, **kwargs):
754 super().__init__(strip=True, **kwargs)
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")
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
787class BooleanField(Field):
788 widget = CheckboxInput
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)
802 def validate(self, value):
803 if not value and self.required:
804 raise ValidationError(self.error_messages["required"], code="required")
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)
814class NullBooleanField(BooleanField):
815 """
816 A field whose valid values are None, True, and False. Clean invalid values
817 to None.
818 """
820 widget = NullBooleanSelect
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
838 def validate(self, value):
839 pass
842class CallableChoiceIterator:
843 def __init__(self, choices_func):
844 self.choices_func = choices_func
846 def __iter__(self):
847 yield from self.choices_func()
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 }
858 def __init__(self, *, choices=(), **kwargs):
859 super().__init__(**kwargs)
860 self.choices = choices
862 def __deepcopy__(self, memo):
863 result = super().__deepcopy__(memo)
864 result._choices = copy.deepcopy(self._choices, memo)
865 return result
867 def _get_choices(self):
868 return self._choices
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)
879 self._choices = self.widget.choices = value
881 choices = property(_get_choices, _set_choices)
883 def to_python(self, value):
884 """Return a string."""
885 if value in self.empty_values:
886 return ""
887 return str(value)
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 )
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
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)
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
936 def clean(self, value):
937 value = super().clean(value)
938 return self._coerce(value)
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 }
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]
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 )
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
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)
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
1012 def clean(self, value):
1013 value = super().clean(value)
1014 return self._coerce(value)
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")
1023class ComboField(Field):
1024 """
1025 A Field whose clean() method calls multiple Field clean() methods.
1026 """
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
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
1048class MultiValueField(Field):
1049 """
1050 Aggregate the logic of multiple Fields.
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.
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.
1063 You'll probably want to use this with MultiWidget.
1064 """
1066 default_error_messages = {
1067 "invalid": _("Enter a list of values."),
1068 "incomplete": _("Enter a complete value."),
1069 }
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
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
1090 def validate(self, value):
1091 pass
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.
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)
1146 out = self.compress(clean_data)
1147 self.validate(out)
1148 self.run_validators(out)
1149 return out
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.
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.")
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
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)
1195 if self.required:
1196 self.choices = []
1197 else:
1198 self.choices = [("", "---------")]
1200 if self.match is not None:
1201 self.match_re = re.compile(self.match)
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)
1231 self.widget.choices = self.choices
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 }
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)
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
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)
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
1295class SlugField(CharField):
1296 default_validators = [validators.validate_slug]
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)
1305class UUIDField(CharField):
1306 default_error_messages = {
1307 "invalid": _("Enter a valid UUID."),
1308 }
1310 def prepare_value(self, value):
1311 if isinstance(value, uuid.UUID):
1312 return str(value)
1313 return value
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
1327class InvalidJSONInput(str):
1328 pass
1331class JSONString(str):
1332 pass
1335class JSONField(CharField):
1336 default_error_messages = {
1337 "invalid": _("Enter a valid JSON."),
1338 }
1339 widget = Textarea
1341 def __init__(self, encoder=None, decoder=None, **kwargs):
1342 self.encoder = encoder
1343 self.decoder = decoder
1344 super().__init__(**kwargs)
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
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)
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)
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 )