Plain is headed towards 1.0! Subscribe for development updates →

   1"""
   2Field classes.
   3"""
   4
   5import copy
   6import datetime
   7import json
   8import math
   9import re
  10import uuid
  11from decimal import Decimal, DecimalException
  12from io import BytesIO
  13from urllib.parse import urlsplit, urlunsplit
  14
  15from plain import validators
  16from plain.exceptions import ValidationError
  17from plain.utils import timezone
  18from plain.utils.dateparse import parse_datetime, parse_duration
  19from plain.utils.duration import duration_string
  20from plain.utils.regex_helper import _lazy_re_compile
  21from plain.utils.text import pluralize_lazy
  22
  23from .boundfield import BoundField
  24from .exceptions import FormFieldMissingError
  25
  26__all__ = (
  27    "Field",
  28    "CharField",
  29    "IntegerField",
  30    "DateField",
  31    "TimeField",
  32    "DateTimeField",
  33    "DurationField",
  34    "RegexField",
  35    "EmailField",
  36    "FileField",
  37    "ImageField",
  38    "URLField",
  39    "BooleanField",
  40    "NullBooleanField",
  41    "ChoiceField",
  42    "MultipleChoiceField",
  43    "FloatField",
  44    "DecimalField",
  45    "JSONField",
  46    "TypedChoiceField",
  47    "UUIDField",
  48)
  49
  50
  51FILE_INPUT_CONTRADICTION = object()
  52
  53
  54class Field:
  55    default_validators = []  # Default set of validators
  56    # Add an 'invalid' entry to default_error_message if you want a specific
  57    # field error message not raised by the field validators.
  58    default_error_messages = {
  59        "required": "This field is required.",
  60    }
  61    empty_values = list(validators.EMPTY_VALUES)
  62
  63    def __init__(
  64        self,
  65        *,
  66        required=True,
  67        initial=None,
  68        error_messages=None,
  69        validators=(),
  70    ):
  71        # required -- Boolean that specifies whether the field is required.
  72        #             True by default.
  73        # initial -- A value to use in this Field's initial display. This value
  74        #            is *not* used as a fallback if data isn't given.
  75        # error_messages -- An optional dictionary to override the default
  76        #                   messages that the field will raise.
  77        # validators -- List of additional validators to use
  78        self.required = required
  79        self.initial = initial
  80
  81        messages = {}
  82        for c in reversed(self.__class__.__mro__):
  83            messages.update(getattr(c, "default_error_messages", {}))
  84        messages.update(error_messages or {})
  85        self.error_messages = messages
  86
  87        self.validators = [*self.default_validators, *validators]
  88
  89    def prepare_value(self, value):
  90        return value
  91
  92    def to_python(self, value):
  93        return value
  94
  95    def validate(self, value):
  96        if value in self.empty_values and self.required:
  97            raise ValidationError(self.error_messages["required"], code="required")
  98
  99    def run_validators(self, value):
 100        if value in self.empty_values:
 101            return
 102        errors = []
 103        for v in self.validators:
 104            try:
 105                v(value)
 106            except ValidationError as e:
 107                if hasattr(e, "code") and e.code in self.error_messages:
 108                    e.message = self.error_messages[e.code]
 109                errors.extend(e.error_list)
 110        if errors:
 111            raise ValidationError(errors)
 112
 113    def clean(self, value):
 114        """
 115        Validate the given value and return its "cleaned" value as an
 116        appropriate Python object. Raise ValidationError for any errors.
 117        """
 118        value = self.to_python(value)
 119        self.validate(value)
 120        self.run_validators(value)
 121        return value
 122
 123    def bound_data(self, data, initial):
 124        """
 125        Return the value that should be shown for this field on render of a
 126        bound form, given the submitted POST data for the field and the initial
 127        data, if any.
 128
 129        For most fields, this will simply be data; FileFields need to handle it
 130        a bit differently.
 131        """
 132        return data
 133
 134    def has_changed(self, initial, data):
 135        """Return True if data differs from initial."""
 136        try:
 137            data = self.to_python(data)
 138            if hasattr(self, "_coerce"):
 139                return self._coerce(data) != self._coerce(initial)
 140        except ValidationError:
 141            return True
 142        # For purposes of seeing whether something has changed, None is
 143        # the same as an empty string, if the data or initial value we get
 144        # is None, replace it with ''.
 145        initial_value = initial if initial is not None else ""
 146        data_value = data if data is not None else ""
 147        return initial_value != data_value
 148
 149    def get_bound_field(self, form, field_name):
 150        """
 151        Return a BoundField instance that will be used when accessing the form
 152        field in a template.
 153        """
 154        return BoundField(form, self, field_name)
 155
 156    def __deepcopy__(self, memo):
 157        result = copy.copy(self)
 158        memo[id(self)] = result
 159        result.error_messages = self.error_messages.copy()
 160        result.validators = self.validators[:]
 161        return result
 162
 163    def value_from_form_data(self, data, files, html_name):
 164        # By default, all fields are expected to be present in HTML form data.
 165        try:
 166            return data[html_name]
 167        except KeyError as e:
 168            raise FormFieldMissingError(html_name) from e
 169
 170    def value_from_json_data(self, data, files, html_name):
 171        if self.required and html_name not in data:
 172            raise FormFieldMissingError(html_name)
 173
 174        return data.get(html_name, None)
 175
 176
 177class CharField(Field):
 178    def __init__(
 179        self, *, max_length=None, min_length=None, strip=True, empty_value="", **kwargs
 180    ):
 181        self.max_length = max_length
 182        self.min_length = min_length
 183        self.strip = strip
 184        self.empty_value = empty_value
 185        super().__init__(**kwargs)
 186        if min_length is not None:
 187            self.validators.append(validators.MinLengthValidator(int(min_length)))
 188        if max_length is not None:
 189            self.validators.append(validators.MaxLengthValidator(int(max_length)))
 190        self.validators.append(validators.ProhibitNullCharactersValidator())
 191
 192    def to_python(self, value):
 193        """Return a string."""
 194        if value not in self.empty_values:
 195            value = str(value)
 196            if self.strip:
 197                value = value.strip()
 198        if value in self.empty_values:
 199            return self.empty_value
 200        return value
 201
 202
 203class IntegerField(Field):
 204    default_error_messages = {
 205        "invalid": "Enter a whole number.",
 206    }
 207    re_decimal = _lazy_re_compile(r"\.0*\s*$")
 208
 209    def __init__(self, *, max_value=None, min_value=None, step_size=None, **kwargs):
 210        self.max_value, self.min_value, self.step_size = max_value, min_value, step_size
 211        super().__init__(**kwargs)
 212
 213        if max_value is not None:
 214            self.validators.append(validators.MaxValueValidator(max_value))
 215        if min_value is not None:
 216            self.validators.append(validators.MinValueValidator(min_value))
 217        if step_size is not None:
 218            self.validators.append(validators.StepValueValidator(step_size))
 219
 220    def to_python(self, value):
 221        """
 222        Validate that int() can be called on the input. Return the result
 223        of int() or None for empty values.
 224        """
 225        value = super().to_python(value)
 226        if value in self.empty_values:
 227            return None
 228        # Strip trailing decimal and zeros.
 229        try:
 230            value = int(self.re_decimal.sub("", str(value)))
 231        except (ValueError, TypeError):
 232            raise ValidationError(self.error_messages["invalid"], code="invalid")
 233        return value
 234
 235
 236class FloatField(IntegerField):
 237    default_error_messages = {
 238        "invalid": "Enter a number.",
 239    }
 240
 241    def to_python(self, value):
 242        """
 243        Validate that float() can be called on the input. Return the result
 244        of float() or None for empty values.
 245        """
 246        value = super(IntegerField, self).to_python(value)
 247        if value in self.empty_values:
 248            return None
 249        try:
 250            value = float(value)
 251        except (ValueError, TypeError):
 252            raise ValidationError(self.error_messages["invalid"], code="invalid")
 253        return value
 254
 255    def validate(self, value):
 256        super().validate(value)
 257        if value in self.empty_values:
 258            return
 259        if not math.isfinite(value):
 260            raise ValidationError(self.error_messages["invalid"], code="invalid")
 261
 262
 263class DecimalField(IntegerField):
 264    default_error_messages = {
 265        "invalid": "Enter a number.",
 266    }
 267
 268    def __init__(
 269        self,
 270        *,
 271        max_value=None,
 272        min_value=None,
 273        max_digits=None,
 274        decimal_places=None,
 275        **kwargs,
 276    ):
 277        self.max_digits, self.decimal_places = max_digits, decimal_places
 278        super().__init__(max_value=max_value, min_value=min_value, **kwargs)
 279        self.validators.append(validators.DecimalValidator(max_digits, decimal_places))
 280
 281    def to_python(self, value):
 282        """
 283        Validate that the input is a decimal number. Return a Decimal
 284        instance or None for empty values. Ensure that there are no more
 285        than max_digits in the number and no more than decimal_places digits
 286        after the decimal point.
 287        """
 288        if value in self.empty_values:
 289            return None
 290        try:
 291            value = Decimal(str(value))
 292        except DecimalException:
 293            raise ValidationError(self.error_messages["invalid"], code="invalid")
 294        return value
 295
 296    def validate(self, value):
 297        super().validate(value)
 298        if value in self.empty_values:
 299            return
 300        if not value.is_finite():
 301            raise ValidationError(
 302                self.error_messages["invalid"],
 303                code="invalid",
 304                params={"value": value},
 305            )
 306
 307
 308class BaseTemporalField(Field):
 309    # Default formats to be used when parsing dates from input boxes, in order
 310    # See all available format string here:
 311    # https://docs.python.org/library/datetime.html#strftime-behavior
 312    # * Note that these format strings are different from the ones to display dates
 313    DATE_INPUT_FORMATS = [
 314        "%Y-%m-%d",  # '2006-10-25'
 315        "%m/%d/%Y",  # '10/25/2006'
 316        "%m/%d/%y",  # '10/25/06'
 317        "%b %d %Y",  # 'Oct 25 2006'
 318        "%b %d, %Y",  # 'Oct 25, 2006'
 319        "%d %b %Y",  # '25 Oct 2006'
 320        "%d %b, %Y",  # '25 Oct, 2006'
 321        "%B %d %Y",  # 'October 25 2006'
 322        "%B %d, %Y",  # 'October 25, 2006'
 323        "%d %B %Y",  # '25 October 2006'
 324        "%d %B, %Y",  # '25 October, 2006'
 325    ]
 326
 327    # Default formats to be used when parsing times from input boxes, in order
 328    # See all available format string here:
 329    # https://docs.python.org/library/datetime.html#strftime-behavior
 330    # * Note that these format strings are different from the ones to display dates
 331    TIME_INPUT_FORMATS = [
 332        "%H:%M:%S",  # '14:30:59'
 333        "%H:%M:%S.%f",  # '14:30:59.000200'
 334        "%H:%M",  # '14:30'
 335    ]
 336
 337    # Default formats to be used when parsing dates and times from input boxes,
 338    # in order
 339    # See all available format string here:
 340    # https://docs.python.org/library/datetime.html#strftime-behavior
 341    # * Note that these format strings are different from the ones to display dates
 342    DATETIME_INPUT_FORMATS = [
 343        "%Y-%m-%d %H:%M:%S",  # '2006-10-25 14:30:59'
 344        "%Y-%m-%d %H:%M:%S.%f",  # '2006-10-25 14:30:59.000200'
 345        "%Y-%m-%d %H:%M",  # '2006-10-25 14:30'
 346        "%m/%d/%Y %H:%M:%S",  # '10/25/2006 14:30:59'
 347        "%m/%d/%Y %H:%M:%S.%f",  # '10/25/2006 14:30:59.000200'
 348        "%m/%d/%Y %H:%M",  # '10/25/2006 14:30'
 349        "%m/%d/%y %H:%M:%S",  # '10/25/06 14:30:59'
 350        "%m/%d/%y %H:%M:%S.%f",  # '10/25/06 14:30:59.000200'
 351        "%m/%d/%y %H:%M",  # '10/25/06 14:30'
 352    ]
 353
 354    def __init__(self, *, input_formats=None, **kwargs):
 355        super().__init__(**kwargs)
 356        if input_formats is not None:
 357            self.input_formats = input_formats
 358
 359    def to_python(self, value):
 360        value = value.strip()
 361        # Try to strptime against each input format.
 362        for format in self.input_formats:
 363            try:
 364                return self.strptime(value, format)
 365            except (ValueError, TypeError):
 366                continue
 367        raise ValidationError(self.error_messages["invalid"], code="invalid")
 368
 369    def strptime(self, value, format):
 370        raise NotImplementedError("Subclasses must define this method.")
 371
 372
 373class DateField(BaseTemporalField):
 374    input_formats = BaseTemporalField.DATE_INPUT_FORMATS
 375    default_error_messages = {
 376        "invalid": "Enter a valid date.",
 377    }
 378
 379    def to_python(self, value):
 380        """
 381        Validate that the input can be converted to a date. Return a Python
 382        datetime.date object.
 383        """
 384        if value in self.empty_values:
 385            return None
 386        if isinstance(value, datetime.datetime):
 387            return value.date()
 388        if isinstance(value, datetime.date):
 389            return value
 390        return super().to_python(value)
 391
 392    def strptime(self, value, format):
 393        return datetime.datetime.strptime(value, format).date()
 394
 395
 396class TimeField(BaseTemporalField):
 397    input_formats = BaseTemporalField.TIME_INPUT_FORMATS
 398    default_error_messages = {"invalid": "Enter a valid time."}
 399
 400    def to_python(self, value):
 401        """
 402        Validate that the input can be converted to a time. Return a Python
 403        datetime.time object.
 404        """
 405        if value in self.empty_values:
 406            return None
 407        if isinstance(value, datetime.time):
 408            return value
 409        return super().to_python(value)
 410
 411    def strptime(self, value, format):
 412        return datetime.datetime.strptime(value, format).time()
 413
 414
 415class DateTimeFormatsIterator:
 416    def __iter__(self):
 417        yield from BaseTemporalField.DATETIME_INPUT_FORMATS
 418        yield from BaseTemporalField.DATE_INPUT_FORMATS
 419
 420
 421class DateTimeField(BaseTemporalField):
 422    input_formats = DateTimeFormatsIterator()
 423    default_error_messages = {
 424        "invalid": "Enter a valid date/time.",
 425    }
 426
 427    def prepare_value(self, value):
 428        if isinstance(value, datetime.datetime):
 429            value = to_current_timezone(value)
 430        return value
 431
 432    def to_python(self, value):
 433        """
 434        Validate that the input can be converted to a datetime. Return a
 435        Python datetime.datetime object.
 436        """
 437        if value in self.empty_values:
 438            return None
 439        if isinstance(value, datetime.datetime):
 440            return from_current_timezone(value)
 441        if isinstance(value, datetime.date):
 442            result = datetime.datetime(value.year, value.month, value.day)
 443            return from_current_timezone(result)
 444        try:
 445            result = parse_datetime(value.strip())
 446        except ValueError:
 447            raise ValidationError(self.error_messages["invalid"], code="invalid")
 448        if not result:
 449            result = super().to_python(value)
 450        return from_current_timezone(result)
 451
 452    def strptime(self, value, format):
 453        return datetime.datetime.strptime(value, format)
 454
 455
 456class DurationField(Field):
 457    default_error_messages = {
 458        "invalid": "Enter a valid duration.",
 459        "overflow": "The number of days must be between {min_days} and {max_days}.",
 460    }
 461
 462    def prepare_value(self, value):
 463        if isinstance(value, datetime.timedelta):
 464            return duration_string(value)
 465        return value
 466
 467    def to_python(self, value):
 468        if value in self.empty_values:
 469            return None
 470        if isinstance(value, datetime.timedelta):
 471            return value
 472        try:
 473            value = parse_duration(str(value))
 474        except OverflowError:
 475            raise ValidationError(
 476                self.error_messages["overflow"].format(
 477                    min_days=datetime.timedelta.min.days,
 478                    max_days=datetime.timedelta.max.days,
 479                ),
 480                code="overflow",
 481            )
 482        if value is None:
 483            raise ValidationError(self.error_messages["invalid"], code="invalid")
 484        return value
 485
 486
 487class RegexField(CharField):
 488    def __init__(self, regex, **kwargs):
 489        """
 490        regex can be either a string or a compiled regular expression object.
 491        """
 492        kwargs.setdefault("strip", False)
 493        super().__init__(**kwargs)
 494        self._set_regex(regex)
 495
 496    def _get_regex(self):
 497        return self._regex
 498
 499    def _set_regex(self, regex):
 500        if isinstance(regex, str):
 501            regex = re.compile(regex)
 502        self._regex = regex
 503        if (
 504            hasattr(self, "_regex_validator")
 505            and self._regex_validator in self.validators
 506        ):
 507            self.validators.remove(self._regex_validator)
 508        self._regex_validator = validators.RegexValidator(regex=regex)
 509        self.validators.append(self._regex_validator)
 510
 511    regex = property(_get_regex, _set_regex)
 512
 513
 514class EmailField(CharField):
 515    default_validators = [validators.validate_email]
 516
 517    def __init__(self, **kwargs):
 518        super().__init__(strip=True, **kwargs)
 519
 520
 521class FileField(Field):
 522    default_error_messages = {
 523        "invalid": "No file was submitted. Check the encoding type on the form.",
 524        "missing": "No file was submitted.",
 525        "empty": "The submitted file is empty.",
 526        "text": pluralize_lazy(
 527            "Ensure this filename has at most %(max)d character (it has %(length)d).",
 528            "Ensure this filename has at most %(max)d characters (it has %(length)d).",
 529            "max",
 530        ),
 531        "contradiction": "Please either submit a file or check the clear checkbox, not both.",
 532    }
 533
 534    def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs):
 535        self.max_length = max_length
 536        self.allow_empty_file = allow_empty_file
 537        super().__init__(**kwargs)
 538
 539    def to_python(self, data):
 540        if data in self.empty_values:
 541            return None
 542
 543        # UploadedFile objects should have name and size attributes.
 544        try:
 545            file_name = data.name
 546            file_size = data.size
 547        except AttributeError:
 548            raise ValidationError(self.error_messages["invalid"], code="invalid")
 549
 550        if self.max_length is not None and len(file_name) > self.max_length:
 551            params = {"max": self.max_length, "length": len(file_name)}
 552            raise ValidationError(
 553                self.error_messages["max_length"], code="max_length", params=params
 554            )
 555        if not file_name:
 556            raise ValidationError(self.error_messages["invalid"], code="invalid")
 557        if not self.allow_empty_file and not file_size:
 558            raise ValidationError(self.error_messages["empty"], code="empty")
 559
 560        return data
 561
 562    def clean(self, data, initial=None):
 563        # If the widget got contradictory inputs, we raise a validation error
 564        if data is FILE_INPUT_CONTRADICTION:
 565            raise ValidationError(
 566                self.error_messages["contradiction"], code="contradiction"
 567            )
 568        # False means the field value should be cleared; further validation is
 569        # not needed.
 570        if data is False:
 571            if not self.required:
 572                return False
 573            # If the field is required, clearing is not possible (the widget
 574            # shouldn't return False data in that case anyway). False is not
 575            # in self.empty_value; if a False value makes it this far
 576            # it should be validated from here on out as None (so it will be
 577            # caught by the required check).
 578            data = None
 579        if not data and initial:
 580            return initial
 581        return super().clean(data)
 582
 583    def bound_data(self, _, initial):
 584        return initial
 585
 586    def has_changed(self, initial, data):
 587        return data is not None
 588
 589    def value_from_form_data(self, data, files, html_name):
 590        return files.get(html_name)
 591
 592    def value_from_json_data(self, data, files, html_name):
 593        return files.get(html_name)
 594
 595
 596class ImageField(FileField):
 597    default_validators = [validators.validate_image_file_extension]
 598    default_error_messages = {
 599        "invalid_image": "Upload a valid image. The file you uploaded was either not an image or a corrupted image.",
 600    }
 601
 602    def to_python(self, data):
 603        """
 604        Check that the file-upload field data contains a valid image (GIF, JPG,
 605        PNG, etc. -- whatever Pillow supports).
 606        """
 607        f = super().to_python(data)
 608        if f is None:
 609            return None
 610
 611        from PIL import Image
 612
 613        # We need to get a file object for Pillow. We might have a path or we might
 614        # have to read the data into memory.
 615        if hasattr(data, "temporary_file_path"):
 616            file = data.temporary_file_path()
 617        else:
 618            if hasattr(data, "read"):
 619                file = BytesIO(data.read())
 620            else:
 621                file = BytesIO(data["content"])
 622
 623        try:
 624            # load() could spot a truncated JPEG, but it loads the entire
 625            # image in memory, which is a DoS vector. See #3848 and #18520.
 626            image = Image.open(file)
 627            # verify() must be called immediately after the constructor.
 628            image.verify()
 629
 630            # Annotating so subclasses can reuse it for their own validation
 631            f.image = image
 632            # Pillow doesn't detect the MIME type of all formats. In those
 633            # cases, content_type will be None.
 634            f.content_type = Image.MIME.get(image.format)
 635        except Exception as exc:
 636            # Pillow doesn't recognize it as an image.
 637            raise ValidationError(
 638                self.error_messages["invalid_image"],
 639                code="invalid_image",
 640            ) from exc
 641        if hasattr(f, "seek") and callable(f.seek):
 642            f.seek(0)
 643        return f
 644
 645
 646class URLField(CharField):
 647    default_error_messages = {
 648        "invalid": "Enter a valid URL.",
 649    }
 650    default_validators = [validators.URLValidator()]
 651
 652    def __init__(self, **kwargs):
 653        super().__init__(strip=True, **kwargs)
 654
 655    def to_python(self, value):
 656        def split_url(url):
 657            """
 658            Return a list of url parts via urlparse.urlsplit(), or raise
 659            ValidationError for some malformed URLs.
 660            """
 661            try:
 662                return list(urlsplit(url))
 663            except ValueError:
 664                # urlparse.urlsplit can raise a ValueError with some
 665                # misformatted URLs.
 666                raise ValidationError(self.error_messages["invalid"], code="invalid")
 667
 668        value = super().to_python(value)
 669        if value:
 670            url_fields = split_url(value)
 671            if not url_fields[0]:
 672                # If no URL scheme given, assume http://
 673                url_fields[0] = "http"
 674            if not url_fields[1]:
 675                # Assume that if no domain is provided, that the path segment
 676                # contains the domain.
 677                url_fields[1] = url_fields[2]
 678                url_fields[2] = ""
 679                # Rebuild the url_fields list, since the domain segment may now
 680                # contain the path too.
 681                url_fields = split_url(urlunsplit(url_fields))
 682            value = urlunsplit(url_fields)
 683        return value
 684
 685
 686class BooleanField(Field):
 687    def to_python(self, value):
 688        """Return a Python boolean object."""
 689        # Explicitly check for the string 'False', which is what a hidden field
 690        # will submit for False. Also check for '0', since this is what
 691        # RadioSelect will provide. Because bool("True") == bool('1') == True,
 692        # we don't need to handle that explicitly.
 693        if isinstance(value, str) and value.lower() in ("false", "0"):
 694            value = False
 695        else:
 696            value = bool(value)
 697        return super().to_python(value)
 698
 699    def validate(self, value):
 700        if not value and self.required:
 701            raise ValidationError(self.error_messages["required"], code="required")
 702
 703    def has_changed(self, initial, data):
 704        # Sometimes data or initial may be a string equivalent of a boolean
 705        # so we should run it through to_python first to get a boolean value
 706        return self.to_python(initial) != self.to_python(data)
 707
 708    def value_from_form_data(self, data, files, html_name):
 709        if html_name not in data:
 710            # Unselected checkboxes aren't in HTML form data, so return False
 711            return False
 712
 713        value = data.get(html_name)
 714        # Translate true and false strings to boolean values.
 715        return {
 716            True: True,
 717            "True": True,
 718            "False": False,
 719            False: False,
 720            "true": True,
 721            "false": False,
 722            "on": True,
 723        }.get(value)
 724
 725    def value_from_json_data(self, data, files, html_name):
 726        # Boolean fields must be present in the JSON data
 727        try:
 728            return data[html_name]
 729        except KeyError as e:
 730            raise FormFieldMissingError(html_name) from e
 731
 732
 733class NullBooleanField(BooleanField):
 734    """
 735    A field whose valid values are None, True, and False. Clean invalid values
 736    to None.
 737    """
 738
 739    def to_python(self, value):
 740        """
 741        Explicitly check for the string 'True' and 'False', which is what a
 742        hidden field will submit for True and False, for 'true' and 'false',
 743        which are likely to be returned by JavaScript serializations of forms,
 744        and for '1' and '0', which is what a RadioField will submit. Unlike
 745        the Booleanfield, this field must check for True because it doesn't
 746        use the bool() function.
 747        """
 748        if value in (True, "True", "true", "1"):
 749            return True
 750        elif value in (False, "False", "false", "0"):
 751            return False
 752        else:
 753            return None
 754
 755    def validate(self, value):
 756        pass
 757
 758
 759class CallableChoiceIterator:
 760    def __init__(self, choices_func):
 761        self.choices_func = choices_func
 762
 763    def __iter__(self):
 764        yield from self.choices_func()
 765
 766
 767class ChoiceField(Field):
 768    default_error_messages = {
 769        "invalid_choice": "Select a valid choice. %(value)s is not one of the available choices.",
 770    }
 771
 772    def __init__(self, *, choices=(), **kwargs):
 773        super().__init__(**kwargs)
 774        if hasattr(choices, "choices"):
 775            choices = choices.choices
 776        self.choices = choices
 777
 778    def __deepcopy__(self, memo):
 779        result = super().__deepcopy__(memo)
 780        result._choices = copy.deepcopy(self._choices, memo)
 781        return result
 782
 783    def _get_choices(self):
 784        return self._choices
 785
 786    def _set_choices(self, value):
 787        # Setting choices also sets the choices on the widget.
 788        # choices can be any iterable, but we call list() on it because
 789        # it will be consumed more than once.
 790        if callable(value):
 791            value = CallableChoiceIterator(value)
 792        else:
 793            value = list(value)
 794
 795        self._choices = value
 796
 797    choices = property(_get_choices, _set_choices)
 798
 799    def to_python(self, value):
 800        """Return a string."""
 801        if value in self.empty_values:
 802            return ""
 803        return str(value)
 804
 805    def validate(self, value):
 806        """Validate that the input is in self.choices."""
 807        super().validate(value)
 808        if value and not self.valid_value(value):
 809            raise ValidationError(
 810                self.error_messages["invalid_choice"],
 811                code="invalid_choice",
 812                params={"value": value},
 813            )
 814
 815    def valid_value(self, value):
 816        """Check to see if the provided value is a valid choice."""
 817        text_value = str(value)
 818        for k, v in self.choices:
 819            if isinstance(v, list | tuple):
 820                # This is an optgroup, so look inside the group for options
 821                for k2, _ in v:
 822                    if value == k2 or text_value == str(k2):
 823                        return True
 824            else:
 825                if value == k or text_value == str(k):
 826                    return True
 827        return False
 828
 829
 830class TypedChoiceField(ChoiceField):
 831    def __init__(self, *, coerce=lambda val: val, empty_value="", **kwargs):
 832        self.coerce = coerce
 833        self.empty_value = empty_value
 834        super().__init__(**kwargs)
 835
 836    def _coerce(self, value):
 837        """
 838        Validate that the value can be coerced to the right type (if not empty).
 839        """
 840        if value == self.empty_value or value in self.empty_values:
 841            return self.empty_value
 842        try:
 843            value = self.coerce(value)
 844        except (ValueError, TypeError, ValidationError):
 845            raise ValidationError(
 846                self.error_messages["invalid_choice"],
 847                code="invalid_choice",
 848                params={"value": value},
 849            )
 850        return value
 851
 852    def clean(self, value):
 853        value = super().clean(value)
 854        return self._coerce(value)
 855
 856
 857class MultipleChoiceField(ChoiceField):
 858    default_error_messages = {
 859        "invalid_choice": "Select a valid choice. %(value)s is not one of the available choices.",
 860        "invalid_list": "Enter a list of values.",
 861    }
 862
 863    def to_python(self, value):
 864        if not value:
 865            return []
 866        elif not isinstance(value, list | tuple):
 867            raise ValidationError(
 868                self.error_messages["invalid_list"], code="invalid_list"
 869            )
 870        return [str(val) for val in value]
 871
 872    def validate(self, value):
 873        """Validate that the input is a list or tuple."""
 874        if self.required and not value:
 875            raise ValidationError(self.error_messages["required"], code="required")
 876        # Validate that each value in the value list is in self.choices.
 877        for val in value:
 878            if not self.valid_value(val):
 879                raise ValidationError(
 880                    self.error_messages["invalid_choice"],
 881                    code="invalid_choice",
 882                    params={"value": val},
 883                )
 884
 885    def has_changed(self, initial, data):
 886        if initial is None:
 887            initial = []
 888        if data is None:
 889            data = []
 890        if len(initial) != len(data):
 891            return True
 892        initial_set = {str(value) for value in initial}
 893        data_set = {str(value) for value in data}
 894        return data_set != initial_set
 895
 896    def value_from_form_data(self, data, files, html_name):
 897        return data.getlist(html_name)
 898
 899
 900class UUIDField(CharField):
 901    default_error_messages = {
 902        "invalid": "Enter a valid UUID.",
 903    }
 904
 905    def prepare_value(self, value):
 906        if isinstance(value, uuid.UUID):
 907            return str(value)
 908        return value
 909
 910    def to_python(self, value):
 911        value = super().to_python(value)
 912        if value in self.empty_values:
 913            return None
 914        if not isinstance(value, uuid.UUID):
 915            try:
 916                value = uuid.UUID(value)
 917            except ValueError:
 918                raise ValidationError(self.error_messages["invalid"], code="invalid")
 919        return value
 920
 921
 922class InvalidJSONInput(str):
 923    pass
 924
 925
 926class JSONString(str):
 927    pass
 928
 929
 930class JSONField(CharField):
 931    default_error_messages = {
 932        "invalid": "Enter a valid JSON.",
 933    }
 934
 935    def __init__(
 936        self, encoder=None, decoder=None, indent=None, sort_keys=False, **kwargs
 937    ):
 938        self.encoder = encoder
 939        self.decoder = decoder
 940        self.indent = indent
 941        self.sort_keys = sort_keys
 942        super().__init__(**kwargs)
 943
 944    def to_python(self, value):
 945        if value in self.empty_values:
 946            return None
 947        elif isinstance(value, list | dict | int | float | JSONString):
 948            return value
 949        try:
 950            converted = json.loads(value, cls=self.decoder)
 951        except json.JSONDecodeError:
 952            raise ValidationError(
 953                self.error_messages["invalid"],
 954                code="invalid",
 955                params={"value": value},
 956            )
 957        if isinstance(converted, str):
 958            return JSONString(converted)
 959        else:
 960            return converted
 961
 962    def bound_data(self, data, initial):
 963        if data is None:
 964            return None
 965        try:
 966            return json.loads(data, cls=self.decoder)
 967        except json.JSONDecodeError:
 968            return InvalidJSONInput(data)
 969
 970    def prepare_value(self, value):
 971        if isinstance(value, InvalidJSONInput):
 972            return value
 973        return json.dumps(
 974            value,
 975            indent=self.indent,
 976            sort_keys=self.sort_keys,
 977            ensure_ascii=False,
 978            cls=self.encoder,
 979        )
 980
 981    def has_changed(self, initial, data):
 982        if super().has_changed(initial, data):
 983            return True
 984        # For purposes of seeing whether something has changed, True isn't the
 985        # same as 1 and the order of keys doesn't matter.
 986        return json.dumps(initial, sort_keys=True, cls=self.encoder) != json.dumps(
 987            self.to_python(data), sort_keys=True, cls=self.encoder
 988        )
 989
 990
 991def from_current_timezone(value):
 992    """
 993    When time zone support is enabled, convert naive datetimes
 994    entered in the current time zone to aware datetimes.
 995    """
 996    if value is not None and timezone.is_naive(value):
 997        current_timezone = timezone.get_current_timezone()
 998        try:
 999            if timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
1000                raise ValueError("Ambiguous or non-existent time.")
1001            return timezone.make_aware(value, current_timezone)
1002        except Exception as exc:
1003            raise ValidationError(
1004                (
1005                    "%(datetime)s couldn’t be interpreted "
1006                    "in time zone %(current_timezone)s; it "
1007                    "may be ambiguous or it may not exist."
1008                ),
1009                code="ambiguous_timezone",
1010                params={"datetime": value, "current_timezone": current_timezone},
1011            ) from exc
1012    return value
1013
1014
1015def to_current_timezone(value):
1016    """
1017    When time zone support is enabled, convert aware datetimes
1018    to naive datetimes in the current time zone for display.
1019    """
1020    if value is not None and timezone.is_aware(value):
1021        return timezone.make_naive(value)
1022    return value