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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
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: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true
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: 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")
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)
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: 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())
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
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
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, **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)
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))
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
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
341class FloatField(IntegerField):
342 default_error_messages = {
343 "invalid": _("Enter a number."),
344 }
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
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")
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
376class DecimalField(IntegerField):
377 default_error_messages = {
378 "invalid": _("Enter a number."),
379 }
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))
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
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 )
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
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
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")
451 def strptime(self, value, format):
452 raise NotImplementedError("Subclasses must define this method.")
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 }
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)
475 def strptime(self, value, format):
476 return datetime.datetime.strptime(value, format).date()
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.")}
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)
495 def strptime(self, value, format):
496 return datetime.datetime.strptime(value, format).time()
499class DateTimeFormatsIterator:
500 def __iter__(self):
501 yield from formats.get_format("DATETIME_INPUT_FORMATS")
502 yield from formats.get_format("DATE_INPUT_FORMATS")
505class DateTimeField(BaseTemporalField):
506 widget = DateTimeInput
507 input_formats = DateTimeFormatsIterator()
508 default_error_messages = {
509 "invalid": _("Enter a valid date/time."),
510 }
512 def prepare_value(self, value):
513 if isinstance(value, datetime.datetime):
514 value = to_current_timezone(value)
515 return value
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)
537 def strptime(self, value, format):
538 return datetime.datetime.strptime(value, format)
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 }
547 def prepare_value(self, value):
548 if isinstance(value, datetime.timedelta):
549 return duration_string(value)
550 return value
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
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)
581 def _get_regex(self):
582 return self._regex
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)
596 regex = property(_get_regex, _set_regex)
599class EmailField(CharField):
600 widget = EmailInput
601 default_validators = [validators.validate_email]
603 def __init__(self, **kwargs):
604 super().__init__(strip=True, **kwargs)
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 }
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)
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
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")
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")
649 return data
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)
672 def bound_data(self, data, initial):
673 if data in (None, FILE_INPUT_CONTRADICTION):
674 return initial
675 return data
677 def has_changed(self, initial, data):
678 return not self.disabled and data is not None
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 }
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
699 from PIL import Image
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"])
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()
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
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
740class URLField(CharField):
741 widget = URLInput
742 default_error_messages = {
743 "invalid": _("Enter a valid URL."),
744 }
745 default_validators = [validators.URLValidator()]
747 def __init__(self, **kwargs):
748 super().__init__(strip=True, **kwargs)
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")
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
781class BooleanField(Field):
782 widget = CheckboxInput
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)
796 def validate(self, value):
797 if not value and self.required:
798 raise ValidationError(self.error_messages["required"], code="required")
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)
808class NullBooleanField(BooleanField):
809 """
810 A field whose valid values are None, True, and False. Clean invalid values
811 to None.
812 """
814 widget = NullBooleanSelect
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
832 def validate(self, value):
833 pass
836class CallableChoiceIterator:
837 def __init__(self, choices_func):
838 self.choices_func = choices_func
840 def __iter__(self):
841 yield from self.choices_func()
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 }
852 def __init__(self, *, choices=(), **kwargs):
853 super().__init__(**kwargs)
854 self.choices = choices
856 def __deepcopy__(self, memo):
857 result = super().__deepcopy__(memo)
858 result._choices = copy.deepcopy(self._choices, memo)
859 return result
861 def _get_choices(self):
862 return self._choices
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)
873 self._choices = self.widget.choices = value
875 choices = property(_get_choices, _set_choices)
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)
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 )
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
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)
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
930 def clean(self, value):
931 value = super().clean(value)
932 return self._coerce(value)
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 }
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]
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 )
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
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)
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
1006 def clean(self, value):
1007 value = super().clean(value)
1008 return self._coerce(value)
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")
1017class ComboField(Field):
1018 """
1019 A Field whose clean() method calls multiple Field clean() methods.
1020 """
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
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
1042class MultiValueField(Field):
1043 """
1044 Aggregate the logic of multiple Fields.
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.
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.
1057 You'll probably want to use this with MultiWidget.
1058 """
1060 default_error_messages = {
1061 "invalid": _("Enter a list of values."),
1062 "incomplete": _("Enter a complete value."),
1063 }
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
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
1084 def validate(self, value):
1085 pass
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.
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)
1140 out = self.compress(clean_data)
1141 self.validate(out)
1142 self.run_validators(out)
1143 return out
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.
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.")
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
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)
1189 if self.required:
1190 self.choices = []
1191 else:
1192 self.choices = [("", "---------")]
1194 if self.match is not None:
1195 self.match_re = re.compile(self.match)
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)
1225 self.widget.choices = self.choices
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 }
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)
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
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)
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
1289class SlugField(CharField):
1290 default_validators = [validators.validate_slug]
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)
1299class UUIDField(CharField):
1300 default_error_messages = {
1301 "invalid": _("Enter a valid UUID."),
1302 }
1304 def prepare_value(self, value):
1305 if isinstance(value, uuid.UUID):
1306 return str(value)
1307 return value
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
1321class InvalidJSONInput(str):
1322 pass
1325class JSONString(str):
1326 pass
1329class JSONField(CharField):
1330 default_error_messages = {
1331 "invalid": _("Enter a valid JSON."),
1332 }
1333 widget = Textarea
1335 def __init__(self, encoder=None, decoder=None, **kwargs):
1336 self.encoder = encoder
1337 self.decoder = decoder
1338 super().__init__(**kwargs)
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
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)
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)
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 )