Plain is headed towards 1.0! Subscribe for development updates →

Forms

Handle user input.

  • forms don't render themselves
  • registered scripts vs media?
  • move model save logic into form.save() - disconnect them
  • Form: validates input from request, saves it
  • Form field: validates field, knows how to parse from html data ?
  • starter includes form components - no markup in python

Re-using input styles

plain-elements, or includes

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