1from __future__ import annotations
2
3import collections.abc
4import copy
5import datetime
6import decimal
7import enum
8import operator
9import uuid
10import warnings
11from base64 import b64decode, b64encode
12from collections.abc import Callable, Sequence
13from functools import cached_property, total_ordering
14from typing import TYPE_CHECKING, Any, Generic, Protocol, Self, TypeVar, cast, overload
15
16from plain import exceptions, validators
17from plain.models.constants import LOOKUP_SEP
18from plain.models.db import db_connection
19from plain.models.enums import ChoicesMeta
20from plain.models.query_utils import RegisterLookupMixin
21from plain.preflight import PreflightResult
22from plain.utils import timezone
23from plain.utils.datastructures import DictWrapper
24from plain.utils.dateparse import (
25 parse_date,
26 parse_datetime,
27 parse_duration,
28 parse_time,
29)
30from plain.utils.duration import duration_microseconds, duration_string
31from plain.utils.functional import Promise
32from plain.utils.ipv6 import clean_ipv6_address
33from plain.utils.itercompat import is_iterable
34
35from ..registry import models_registry
36
37if TYPE_CHECKING:
38 from plain.models.backends.base.base import BaseDatabaseWrapper
39 from plain.models.base import Model
40 from plain.models.fields.reverse_related import ForeignObjectRel
41 from plain.models.sql.compiler import SQLCompiler
42
43__all__ = [
44 "BLANK_CHOICE_DASH",
45 "PrimaryKeyField",
46 "BigIntegerField",
47 "BinaryField",
48 "BooleanField",
49 "CharField",
50 "DateField",
51 "DateTimeField",
52 "DecimalField",
53 "DurationField",
54 "EmailField",
55 "Empty",
56 "Field",
57 "FloatField",
58 "GenericIPAddressField",
59 "IntegerField",
60 "NOT_PROVIDED",
61 "PositiveBigIntegerField",
62 "PositiveIntegerField",
63 "PositiveSmallIntegerField",
64 "SmallIntegerField",
65 "TextField",
66 "TimeField",
67 "URLField",
68 "UUIDField",
69]
70
71
72class Empty:
73 pass
74
75
76class NOT_PROVIDED:
77 pass
78
79
80# The values to use for "blank" in SelectFields. Will be appended to the start
81# of most "choices" lists.
82BLANK_CHOICE_DASH = [("", "---------")]
83
84
85def _load_field(
86 package_label: str, model_name: str, field_name: str
87) -> Field[Any] | ForeignObjectRel:
88 return models_registry.get_model(package_label, model_name)._model_meta.get_field(
89 field_name
90 )
91
92
93# A guide to Field parameters:
94#
95# * name: The name of the field specified in the model.
96# * attname: The attribute to use on the model object. This is the same as
97# "name", except in the case of ForeignKeys, where "_id" is
98# appended.
99# * db_column: The db_column specified in the model (or None).
100# * column: The database column for this field. This is the same as
101# "attname", except if db_column is specified.
102#
103# Code that introspects values, or does other dynamic things, should use
104# attname.
105
106
107def _empty(of_cls: type) -> Empty:
108 new = Empty()
109 new.__class__ = of_cls
110 return new
111
112
113def return_None() -> None:
114 return None
115
116
117# TypeVar for Field's generic type parameter
118T = TypeVar("T")
119
120
121@total_ordering
122class Field(RegisterLookupMixin, Generic[T]):
123 """Base class for all field types"""
124
125 # Instance attributes set during field lifecycle
126 # Set by __init__
127 name: str | None
128 max_length: int | None
129 db_column: str | None
130 # Set by set_attributes_from_name (called by contribute_to_class)
131 attname: str
132 column: str
133 concrete: bool
134 # Set by contribute_to_class
135 model: type[Model]
136
137 # Designates whether empty strings fundamentally are allowed at the
138 # database level.
139 empty_strings_allowed = True
140 empty_values = list(validators.EMPTY_VALUES)
141
142 # These track each time a Field instance is created. Used to retain order.
143 # The auto_creation_counter is used for fields that Plain implicitly
144 # creates, creation_counter is used for all user-specified fields.
145 creation_counter: int = 0
146 auto_creation_counter: int = -1
147 default_validators = [] # Default set of validators
148 default_error_messages = {
149 "invalid_choice": "Value %(value)r is not a valid choice.",
150 "allow_null": "This field cannot be null.",
151 "required": "This field is be required.",
152 "unique": "A %(model_name)s with this %(field_label)s already exists.",
153 }
154
155 # Attributes that don't affect a column definition.
156 # These attributes are ignored when altering the field.
157 non_db_attrs = (
158 "required",
159 "choices",
160 "db_column",
161 "error_messages",
162 "limit_choices_to",
163 # Database-level options are not supported, see #21961.
164 "on_delete",
165 "related_query_name",
166 "validators",
167 )
168
169 # Generic field type description, usually overridden by subclasses
170 def _description(self) -> str:
171 return f"Field of type: {self.__class__.__name__}"
172
173 description = property(_description)
174
175 def __init__(
176 self,
177 *,
178 max_length: int | None = None,
179 required: bool = True,
180 allow_null: bool = False,
181 default: Any = NOT_PROVIDED,
182 choices: Any = None,
183 db_column: str | None = None,
184 validators: Sequence[Callable[..., Any]] = (),
185 error_messages: dict[str, str] | None = None,
186 db_comment: str | None = None,
187 ):
188 self.name = None # Set by set_attributes_from_name
189 self.max_length = max_length
190 self.required, self.allow_null = required, allow_null
191 self.default = default
192 if isinstance(choices, ChoicesMeta):
193 choices = choices.choices
194 elif isinstance(choices, enum.EnumMeta):
195 choices = [(member.value, member.name) for member in choices]
196 if isinstance(choices, collections.abc.Iterator):
197 choices = list(choices)
198 self.choices = choices
199 self.db_column = db_column
200 self.db_comment = db_comment
201
202 self.primary_key = False
203 self.auto_created = False
204
205 # Adjust the appropriate creation counter, and save our local copy.
206 self.creation_counter = Field.creation_counter
207 Field.creation_counter += 1
208
209 self._validators = list(validators) # Store for deconstruction later
210
211 self._error_messages = error_messages # Store for deconstruction later
212
213 def __str__(self) -> str:
214 """
215 Return "package_label.model_label.field_name" for fields attached to
216 models.
217 """
218 if not hasattr(self, "model"):
219 return super().__str__()
220 model = self.model
221 return f"{model.model_options.label}.{self.name}"
222
223 def __repr__(self) -> str:
224 """Display the module, class, and name of the field."""
225 path = f"{self.__class__.__module__}.{self.__class__.__qualname__}"
226 name = getattr(self, "name", None)
227 if name is not None:
228 return f"<{path}: {name}>"
229 return f"<{path}>"
230
231 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
232 return [
233 *self._check_field_name(),
234 *self._check_choices(),
235 *self._check_db_comment(),
236 *self._check_null_allowed_for_primary_keys(),
237 *self._check_backend_specific_checks(),
238 *self._check_validators(),
239 ]
240
241 def _check_field_name(self) -> list[PreflightResult]:
242 """
243 Check if field name is valid, i.e. 1) does not end with an
244 underscore, 2) does not contain "__" and 3) is not "id".
245 """
246 assert self.name is not None, "Field name must be set before checking"
247 if self.name.endswith("_"):
248 return [
249 PreflightResult(
250 fix="Field names must not end with an underscore.",
251 obj=self,
252 id="fields.name_ends_with_underscore",
253 )
254 ]
255 elif LOOKUP_SEP in self.name:
256 return [
257 PreflightResult(
258 fix=f'Field names must not contain "{LOOKUP_SEP}".',
259 obj=self,
260 id="fields.name_contains_lookup_separator",
261 )
262 ]
263 elif self.name == "id":
264 return [
265 PreflightResult(
266 fix="'id' is a reserved word that cannot be used as a field name.",
267 obj=self,
268 id="fields.reserved_field_name_id",
269 )
270 ]
271 else:
272 return []
273
274 @classmethod
275 def _choices_is_value(cls, value: Any) -> bool:
276 return isinstance(value, str | Promise) or not is_iterable(value)
277
278 def _check_choices(self) -> list[PreflightResult]:
279 if not self.choices:
280 return []
281
282 if not is_iterable(self.choices) or isinstance(self.choices, str):
283 return [
284 PreflightResult(
285 fix="'choices' must be an iterable (e.g., a list or tuple).",
286 obj=self,
287 id="fields.choices_not_iterable",
288 )
289 ]
290
291 choice_max_length = 0
292 # Expect [group_name, [value, display]]
293 for choices_group in self.choices:
294 try:
295 group_name, group_choices = choices_group
296 except (TypeError, ValueError):
297 # Containing non-pairs
298 break
299 try:
300 if not all(
301 self._choices_is_value(value) and self._choices_is_value(human_name)
302 for value, human_name in group_choices
303 ):
304 break
305 if self.max_length is not None and group_choices:
306 choice_max_length = max(
307 [
308 choice_max_length,
309 *(
310 len(value)
311 for value, _ in group_choices
312 if isinstance(value, str)
313 ),
314 ]
315 )
316 except (TypeError, ValueError):
317 # No groups, choices in the form [value, display]
318 value, human_name = group_name, group_choices
319 if not self._choices_is_value(value) or not self._choices_is_value(
320 human_name
321 ):
322 break
323 if self.max_length is not None and isinstance(value, str):
324 choice_max_length = max(choice_max_length, len(value))
325
326 # Special case: choices=['ab']
327 if isinstance(choices_group, str):
328 break
329 else:
330 if self.max_length is not None and choice_max_length > self.max_length:
331 return [
332 PreflightResult(
333 fix="'max_length' is too small to fit the longest value " # noqa: UP031
334 "in 'choices' (%d characters)." % choice_max_length,
335 obj=self,
336 id="fields.max_length_too_small_for_choices",
337 ),
338 ]
339 return []
340
341 return [
342 PreflightResult(
343 fix="'choices' must be an iterable containing "
344 "(actual value, human readable name) tuples.",
345 obj=self,
346 id="fields.choices_invalid_format",
347 )
348 ]
349
350 def _check_db_comment(self) -> list[PreflightResult]:
351 if not self.db_comment:
352 return []
353 errors = []
354 if not (
355 db_connection.features.supports_comments
356 or "supports_comments" in self.model.model_options.required_db_features
357 ):
358 errors.append(
359 PreflightResult(
360 fix=f"{db_connection.display_name} does not support comments on "
361 f"columns (db_comment).",
362 obj=self,
363 id="fields.db_comment_unsupported",
364 warning=True,
365 )
366 )
367 return errors
368
369 def _check_null_allowed_for_primary_keys(self) -> list[PreflightResult]:
370 if self.primary_key and self.allow_null:
371 # We cannot reliably check this for backends like Oracle which
372 # consider NULL and '' to be equal (and thus set up
373 # character-based fields a little differently).
374 return [
375 PreflightResult(
376 fix="Primary keys must not have allow_null=True. "
377 "Set allow_null=False on the field, or "
378 "remove primary_key=True argument.",
379 obj=self,
380 id="fields.primary_key_allows_null",
381 )
382 ]
383 else:
384 return []
385
386 def _check_backend_specific_checks(self) -> list[PreflightResult]:
387 errors = []
388 errors.extend(db_connection.validation.check_field(self))
389 return errors
390
391 def _check_validators(self) -> list[PreflightResult]:
392 errors = []
393 for i, validator in enumerate(self.validators):
394 if not callable(validator):
395 errors.append(
396 PreflightResult(
397 fix=(
398 "All 'validators' must be callable. "
399 f"validators[{i}] ({repr(validator)}) isn't a function or "
400 "instance of a validator class."
401 ),
402 obj=self,
403 id="fields.invalid_validator",
404 )
405 )
406 return errors
407
408 def get_col(self, alias: str | None, output_field: Field | None = None) -> Any:
409 if alias == self.model.model_options.db_table and (
410 output_field is None or output_field == self
411 ):
412 return self.cached_col
413 from plain.models.expressions import Col
414
415 return Col(alias, self, output_field)
416
417 @cached_property
418 def cached_col(self) -> Any:
419 from plain.models.expressions import Col
420
421 return Col(self.model.model_options.db_table, self)
422
423 def select_format(
424 self, compiler: SQLCompiler, sql: str, params: Any
425 ) -> tuple[str, Any]:
426 """
427 Custom format for select clauses. For example, GIS columns need to be
428 selected as AsText(table.col) on MySQL as the table.col data can't be
429 used by Plain.
430 """
431 return sql, params
432
433 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
434 """
435 Return enough information to recreate the field as a 4-tuple:
436
437 * The name of the field on the model, if contribute_to_class() has
438 been run.
439 * The import path of the field, including the class, e.g.
440 plain.models.IntegerField. This should be the most portable
441 version, so less specific may be better.
442 * A list of positional arguments.
443 * A dict of keyword arguments.
444
445 Note that the positional or keyword arguments must contain values of
446 the following types (including inner values of collection types):
447
448 * None, bool, str, int, float, complex, set, frozenset, list, tuple,
449 dict
450 * UUID
451 * datetime.datetime (naive), datetime.date
452 * top-level classes, top-level functions - will be referenced by their
453 full import path
454 * Storage instances - these have their own deconstruct() method
455
456 This is because the values here must be serialized into a text format
457 (possibly new Python code, possibly JSON) and these are the only types
458 with encoding handlers defined.
459
460 There's no need to return the exact way the field was instantiated this
461 time, just ensure that the resulting field is the same - prefer keyword
462 arguments over positional ones, and omit parameters with their default
463 values.
464 """
465 # Short-form way of fetching all the default parameters
466 keywords = {}
467 possibles = {
468 "max_length": None,
469 "required": True,
470 "allow_null": False,
471 "default": NOT_PROVIDED,
472 "choices": None,
473 "db_column": None,
474 "db_comment": None,
475 "validators": [],
476 "error_messages": None,
477 }
478 attr_overrides = {
479 "error_messages": "_error_messages",
480 "validators": "_validators",
481 }
482 equals_comparison = {"choices", "validators"}
483 for name, default in possibles.items():
484 value = getattr(self, attr_overrides.get(name, name))
485 # Unroll anything iterable for choices into a concrete list
486 if name == "choices" and isinstance(value, collections.abc.Iterable):
487 value = list(value)
488 # Do correct kind of comparison
489 if name in equals_comparison:
490 if value != default:
491 keywords[name] = value
492 else:
493 if value is not default:
494 keywords[name] = value
495 # Work out path - we shorten it for known Plain core fields
496 path = f"{self.__class__.__module__}.{self.__class__.__qualname__}"
497 if path.startswith("plain.models.fields.related"):
498 path = path.replace("plain.models.fields.related", "plain.models")
499 elif path.startswith("plain.models.fields.json"):
500 path = path.replace("plain.models.fields.json", "plain.models")
501 elif path.startswith("plain.models.fields.proxy"):
502 path = path.replace("plain.models.fields.proxy", "plain.models")
503 elif path.startswith("plain.models.fields"):
504 path = path.replace("plain.models.fields", "plain.models")
505 # Return basic info - other fields should override this.
506 # Note: self.name can be None during migration state rendering when fields are cloned
507 return (self.name, path, [], keywords)
508
509 def clone(self) -> Self:
510 """
511 Uses deconstruct() to clone a new copy of this Field.
512 Will not preserve any class attachments/attribute names.
513 """
514 name, path, args, kwargs = self.deconstruct()
515 return cast(Self, self.__class__(*args, **kwargs))
516
517 def __eq__(self, other: object) -> bool:
518 # Needed for @total_ordering
519 if isinstance(other, Field):
520 return self.creation_counter == other.creation_counter and getattr(
521 self, "model", None
522 ) == getattr(other, "model", None)
523 return NotImplemented
524
525 def __lt__(self, other: object) -> bool:
526 # This is needed because bisect does not take a comparison function.
527 # Order by creation_counter first for backward compatibility.
528 if not isinstance(other, Field):
529 return NotImplemented
530
531 # Type narrowing: other is now known to be a Field
532 other_field: Field[Any] = other
533
534 if (
535 self.creation_counter != other_field.creation_counter
536 or not hasattr(self, "model")
537 and not hasattr(other_field, "model")
538 ):
539 return self.creation_counter < other_field.creation_counter
540 elif hasattr(self, "model") != hasattr(other_field, "model"):
541 return not hasattr(self, "model") # Order no-model fields first
542 else:
543 # creation_counter's are equal, compare only models.
544 # Use getattr with defaults to satisfy type checker
545 self_pkg = getattr(getattr(self, "model", None), "model_options", None)
546 other_pkg = getattr(
547 getattr(other_field, "model", None), "model_options", None
548 )
549 if self_pkg is not None and other_pkg is not None:
550 return (
551 self_pkg.package_label,
552 self_pkg.model_name,
553 ) < (
554 other_pkg.package_label,
555 other_pkg.model_name,
556 )
557 # Fallback if model_options not available
558 return self.creation_counter < other_field.creation_counter
559
560 def __hash__(self) -> int:
561 return hash(self.creation_counter)
562
563 def __deepcopy__(self, memodict: dict[int, Any]) -> Self:
564 # We don't have to deepcopy very much here, since most things are not
565 # intended to be altered after initial creation.
566 obj = copy.copy(self)
567 memodict[id(self)] = obj
568 return obj
569
570 def __copy__(self) -> Self:
571 # We need to avoid hitting __reduce__, so define this
572 # slightly weird copy construct.
573 obj = Empty()
574 obj.__class__ = self.__class__
575 obj.__dict__ = self.__dict__.copy()
576 return cast(Self, obj)
577
578 def __reduce__(
579 self,
580 ) -> (
581 tuple[Callable[..., Any], tuple[Any, ...], dict[str, Any]]
582 | tuple[Callable[..., Field[Any] | ForeignObjectRel], tuple[str, str, str]]
583 ):
584 """
585 Pickling should return the model._model_meta.fields instance of the field,
586 not a new copy of that field. So, use the app registry to load the
587 model and then the field back.
588 """
589 model = getattr(self, "model", None)
590 if model is None:
591 # Fields are sometimes used without attaching them to models (for
592 # example in aggregation). In this case give back a plain field
593 # instance. The code below will create a new empty instance of
594 # class self.__class__, then update its dict with self.__dict__
595 # values - so, this is very close to normal pickle.
596 state = self.__dict__.copy()
597 # The _get_default cached_property can't be pickled due to lambda
598 # usage.
599 state.pop("_get_default", None)
600 return _empty, (self.__class__,), state
601 assert self.name is not None
602 options = model.model_options
603 return _load_field, (
604 options.package_label,
605 options.object_name,
606 self.name,
607 )
608
609 def get_id_value_on_save(self, instance: Any) -> Any:
610 """
611 Hook to generate new primary key values on save. This method is called when
612 saving instances with no primary key value set. If this method returns
613 something else than None, then the returned value is used when saving
614 the new instance.
615 """
616 if self.default:
617 return self.get_default()
618 return None
619
620 def to_python(self, value: Any) -> Any:
621 """
622 Convert the input value into the expected Python data type, raising
623 plain.exceptions.ValidationError if the data can't be converted.
624 Return the converted value. Subclasses should override this.
625 """
626 return value
627
628 @cached_property
629 def error_messages(self) -> dict[str, str]:
630 messages = {}
631 for c in reversed(self.__class__.__mro__):
632 messages.update(getattr(c, "default_error_messages", {}))
633 messages.update(self._error_messages or {})
634 return messages
635
636 @cached_property
637 def validators(self) -> list[Callable[..., Any]]:
638 """
639 Some validators can't be created at field initialization time.
640 This method provides a way to delay their creation until required.
641 """
642 return [*self.default_validators, *self._validators]
643
644 def run_validators(self, value: Any) -> None:
645 if value in self.empty_values:
646 return
647
648 errors = []
649 for v in self.validators:
650 try:
651 v(value)
652 except exceptions.ValidationError as e:
653 if hasattr(e, "code") and e.code in self.error_messages:
654 e.message = self.error_messages[e.code]
655 errors.extend(e.error_list)
656
657 if errors:
658 raise exceptions.ValidationError(errors)
659
660 def validate(self, value: Any, model_instance: Any) -> None:
661 """
662 Validate value and raise ValidationError if necessary. Subclasses
663 should override this to provide validation logic.
664 """
665
666 if self.choices is not None and value not in self.empty_values:
667 for option_key, option_value in self.choices:
668 if isinstance(option_value, list | tuple):
669 # This is an optgroup, so look inside the group for
670 # options.
671 for optgroup_key, optgroup_value in option_value:
672 if value == optgroup_key:
673 return
674 elif value == option_key:
675 return
676 raise exceptions.ValidationError(
677 self.error_messages["invalid_choice"],
678 code="invalid_choice",
679 params={"value": value},
680 )
681
682 if value is None and not self.allow_null:
683 raise exceptions.ValidationError(
684 self.error_messages["allow_null"], code="allow_null"
685 )
686
687 if self.required and value in self.empty_values:
688 raise exceptions.ValidationError(
689 self.error_messages["required"], code="required"
690 )
691
692 def clean(self, value: Any, model_instance: Any) -> Any:
693 """
694 Convert the value's type and run validation. Validation errors
695 from to_python() and validate() are propagated. Return the correct
696 value if no error is raised.
697 """
698 value = self.to_python(value)
699 self.validate(value, model_instance)
700 self.run_validators(value)
701 return value
702
703 def db_type_parameters(self, connection: BaseDatabaseWrapper) -> DictWrapper:
704 return DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
705
706 def db_check(self, connection: BaseDatabaseWrapper) -> str | None:
707 """
708 Return the database column check constraint for this field, for the
709 provided connection. Works the same way as db_type() for the case that
710 get_internal_type() does not map to a preexisting model field.
711 """
712 data = self.db_type_parameters(connection)
713 try:
714 return (
715 connection.data_type_check_constraints[self.get_internal_type()] % data
716 )
717 except KeyError:
718 return None
719
720 def db_type(self, connection: BaseDatabaseWrapper) -> str | None:
721 """
722 Return the database column data type for this field, for the provided
723 connection.
724 """
725 # The default implementation of this method looks at the
726 # backend-specific data_types dictionary, looking up the field by its
727 # "internal type".
728 #
729 # A Field class can implement the get_internal_type() method to specify
730 # which *preexisting* Plain Field class it's most similar to -- i.e.,
731 # a custom field might be represented by a TEXT column type, which is
732 # the same as the TextField Plain field type, which means the custom
733 # field's get_internal_type() returns 'TextField'.
734 #
735 # But the limitation of the get_internal_type() / data_types approach
736 # is that it cannot handle database column types that aren't already
737 # mapped to one of the built-in Plain field types. In this case, you
738 # can implement db_type() instead of get_internal_type() to specify
739 # exactly which wacky database column type you want to use.
740 data = self.db_type_parameters(connection)
741 try:
742 column_type = connection.data_types[self.get_internal_type()]
743 except KeyError:
744 return None
745 else:
746 # column_type is either a single-parameter function or a string.
747 if callable(column_type):
748 return column_type(data)
749 return column_type % data
750
751 def rel_db_type(self, connection: BaseDatabaseWrapper) -> str | None:
752 """
753 Return the data type that a related field pointing to this field should
754 use. For example, this method is called by ForeignKeyField to determine its data type.
755 """
756 return self.db_type(connection)
757
758 def cast_db_type(self, connection: BaseDatabaseWrapper) -> str | None:
759 """Return the data type to use in the Cast() function."""
760 db_type = connection.ops.cast_data_types.get(self.get_internal_type())
761 if db_type:
762 return db_type % self.db_type_parameters(connection)
763 return self.db_type(connection)
764
765 def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, Any]:
766 """
767 Extension of db_type(), providing a range of different return values
768 (type, checks). This will look at db_type(), allowing custom model
769 fields to override it.
770 """
771 type_string = self.db_type(connection)
772 check_string = self.db_check(connection)
773 return {
774 "type": type_string,
775 "check": check_string,
776 }
777
778 def db_type_suffix(self, connection: BaseDatabaseWrapper) -> str | None:
779 return connection.data_types_suffix.get(self.get_internal_type())
780
781 def get_db_converters(
782 self, connection: BaseDatabaseWrapper
783 ) -> list[Callable[..., Any]]:
784 if from_db_value := getattr(self, "from_db_value", None):
785 return [from_db_value]
786 return []
787
788 @property
789 def db_returning(self) -> bool:
790 """
791 Private API intended only to be used by Plain itself. Currently only
792 the PostgreSQL backend supports returning multiple fields on a model.
793 """
794 return False
795
796 def set_attributes_from_name(self, name: str) -> None:
797 self.name = self.name or name
798 self.attname, self.column = self.get_attname_column()
799 self.concrete = self.column is not None
800
801 def contribute_to_class(self, cls: type[Model], name: str) -> None:
802 """
803 Register the field with the model class it belongs to.
804
805 Field now acts as its own descriptor - it stays on the class and handles
806 __get__/__set__/__delete__ directly.
807 """
808 self.set_attributes_from_name(name)
809 self.model = cls
810 cls._model_meta.add_field(self)
811
812 # Field is now a descriptor itself - ensure it's set on the class
813 # This is important for inherited fields that get deepcopied in Meta.__get__
814 if self.column:
815 setattr(cls, self.attname, self)
816
817 # Descriptor protocol implementation
818 @overload
819 def __get__(self, instance: None, owner: type) -> Self: ...
820
821 @overload
822 def __get__(self, instance: Any, owner: type) -> T: ...
823
824 def __get__(self, instance: Any | None, owner: type) -> Self | T:
825 """
826 Descriptor __get__ for attribute access.
827
828 Class access (User.email) returns the Field descriptor itself.
829 Instance access (user.email) returns the field value from instance.__dict__,
830 with lazy loading support if the value is not yet loaded.
831 """
832 # Class access - return the Field descriptor
833 if instance is None:
834 return self
835
836 # If field hasn't been contributed to a class yet (e.g., used standalone
837 # as an output_field in aggregates), just return self
838 if not hasattr(self, "attname"):
839 return self
840
841 # Instance access - get value from instance dict
842 data = instance.__dict__
843 field_name = self.attname
844
845 # If value not in dict, lazy load from database
846 if field_name not in data:
847 # Deferred field - load it from the database
848 instance.refresh_from_db(fields=[field_name])
849
850 return data.get(field_name)
851
852 def __set__(self, instance: Any, value: Any) -> None:
853 """
854 Descriptor __set__ for attribute assignment.
855
856 Validates and converts the value using to_python(), then stores it
857 in instance.__dict__[attname].
858 """
859 # Safety check: ensure field has been properly initialized
860 if not hasattr(self, "attname"):
861 raise AttributeError(
862 f"Field {self.__class__.__name__} has not been initialized properly. "
863 f"The field's contribute_to_class() has not been called yet. "
864 f"This usually means the field is being used before it was added to a model class."
865 )
866
867 # Convert/validate the value
868 if value is not None:
869 value = self.to_python(value)
870
871 # Store in instance dict
872 instance.__dict__[self.attname] = value
873
874 def __delete__(self, instance: Any) -> None:
875 """
876 Descriptor __delete__ for attribute deletion.
877
878 Removes the value from instance.__dict__.
879 """
880 try:
881 del instance.__dict__[self.attname]
882 except KeyError:
883 raise AttributeError(
884 f"{instance.__class__.__name__!r} object has no attribute {self.attname!r}"
885 )
886
887 def get_attname(self) -> str:
888 assert self.name is not None # Field name must be set
889 return self.name
890
891 def get_attname_column(self) -> tuple[str, str]:
892 attname = self.get_attname()
893 column = self.db_column or attname
894 return attname, column
895
896 def get_internal_type(self) -> str:
897 return self.__class__.__name__
898
899 def pre_save(self, model_instance: Any, add: bool) -> Any:
900 """Return field's value just before saving."""
901 return getattr(model_instance, self.attname)
902
903 def get_prep_value(self, value: Any) -> Any:
904 """Perform preliminary non-db specific value checks and conversions."""
905 if isinstance(value, Promise):
906 value = value._proxy____cast()
907 return value
908
909 def get_db_prep_value(
910 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
911 ) -> Any:
912 """
913 Return field's value prepared for interacting with the database backend.
914
915 Used by the default implementations of get_db_prep_save().
916 """
917 if not prepared:
918 value = self.get_prep_value(value)
919 return value
920
921 def get_db_prep_save(self, value: Any, connection: BaseDatabaseWrapper) -> Any:
922 """Return field's value prepared for saving into a database."""
923 if hasattr(value, "as_sql"):
924 return value
925 return self.get_db_prep_value(value, connection=connection, prepared=False)
926
927 def has_default(self) -> bool:
928 """Return a boolean of whether this field has a default value."""
929 return self.default is not NOT_PROVIDED
930
931 def get_default(self) -> Any:
932 """Return the default value for this field."""
933 return self._get_default()
934
935 @cached_property
936 def _get_default(self) -> Callable[[], Any]:
937 if self.has_default():
938 if callable(self.default):
939 return self.default
940 return lambda: self.default
941
942 if not self.empty_strings_allowed or self.allow_null:
943 return return_None
944 return str # return empty string
945
946 def get_limit_choices_to(self) -> Any:
947 """
948 Return ``limit_choices_to`` for this model field.
949 Overridden by related fields (ForeignKey, etc.).
950 """
951 raise NotImplementedError(
952 "get_limit_choices_to() should only be called on related fields"
953 )
954
955 def get_choices(
956 self,
957 include_blank: bool = True,
958 blank_choice: list[tuple[str, str]] = BLANK_CHOICE_DASH,
959 limit_choices_to: Any = None,
960 ordering: tuple[str, ...] = (),
961 ) -> list[tuple[Any, str]]:
962 """
963 Return choices with a default blank choices included, for use
964 as <select> choices for this field.
965 """
966 if self.choices is not None:
967 choices = list(self.choices)
968 if include_blank:
969 blank_defined = any(
970 choice in ("", None) for choice, _ in self.flatchoices
971 )
972 if not blank_defined:
973 choices = blank_choice + choices
974 return choices
975 remote_field = getattr(self, "remote_field", None)
976 if remote_field is None or getattr(remote_field, "model", None) is None:
977 return blank_choice if include_blank else []
978 rel_model = remote_field.model
979 limit_choices_to = limit_choices_to or self.get_limit_choices_to()
980 related_field_name = (
981 remote_field.get_related_field().attname
982 if hasattr(remote_field, "get_related_field")
983 else "id"
984 )
985 choice_func = operator.attrgetter(related_field_name)
986 qs = rel_model.query.complex_filter(limit_choices_to)
987 if ordering:
988 qs = qs.order_by(*ordering)
989 return (blank_choice if include_blank else []) + [
990 (choice_func(x), str(x)) for x in qs
991 ]
992
993 def value_to_string(self, obj: Any) -> str:
994 """
995 Return a string value of this field from the passed obj.
996 This is used by the serialization framework.
997 """
998 return str(self.value_from_object(obj))
999
1000 def _get_flatchoices(self) -> list[tuple[Any, Any]]:
1001 """Flattened version of choices tuple."""
1002 if self.choices is None:
1003 return []
1004 flat = []
1005 for choice, value in self.choices:
1006 if isinstance(value, list | tuple):
1007 flat.extend(value)
1008 else:
1009 flat.append((choice, value))
1010 return flat
1011
1012 flatchoices = property(_get_flatchoices)
1013
1014 def save_form_data(self, instance: Any, data: Any) -> None:
1015 assert self.name is not None
1016 setattr(instance, self.name, data)
1017
1018 def value_from_object(self, obj: Any) -> Any:
1019 """Return the value of this field in the given model instance."""
1020 return getattr(obj, self.attname)
1021
1022
1023class BooleanField(Field[bool]):
1024 empty_strings_allowed = False
1025 default_error_messages = {
1026 "invalid": '"%(value)s" value must be either True or False.',
1027 "invalid_nullable": '"%(value)s" value must be either True, False, or None.',
1028 }
1029 description = "Boolean (Either True or False)"
1030
1031 def get_internal_type(self) -> str:
1032 return "BooleanField"
1033
1034 def to_python(self, value: Any) -> Any:
1035 if self.allow_null and value in self.empty_values:
1036 return None
1037 if value in (True, False):
1038 # 1/0 are equal to True/False. bool() converts former to latter.
1039 return bool(value)
1040 if value in ("t", "True", "1"):
1041 return True
1042 if value in ("f", "False", "0"):
1043 return False
1044 raise exceptions.ValidationError(
1045 self.error_messages["invalid_nullable" if self.allow_null else "invalid"],
1046 code="invalid",
1047 params={"value": value},
1048 )
1049
1050 def get_prep_value(self, value: Any) -> Any:
1051 value = super().get_prep_value(value)
1052 if value is None:
1053 return None
1054 return self.to_python(value)
1055
1056
1057class CharField(Field[str]):
1058 def __init__(self, *, db_collation: str | None = None, **kwargs: Any):
1059 super().__init__(**kwargs)
1060 self.db_collation = db_collation
1061 if self.max_length is not None:
1062 self.validators.append(validators.MaxLengthValidator(self.max_length))
1063
1064 @property
1065 def description(self) -> str:
1066 if self.max_length is not None:
1067 return "String (up to %(max_length)s)"
1068 else:
1069 return "String (unlimited)"
1070
1071 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1072 return [
1073 *super().preflight(**kwargs),
1074 *self._check_db_collation(),
1075 *self._check_max_length_attribute(),
1076 ]
1077
1078 def _check_max_length_attribute(self, **kwargs: Any) -> list[PreflightResult]:
1079 if self.max_length is None:
1080 if (
1081 db_connection.features.supports_unlimited_charfield
1082 or "supports_unlimited_charfield"
1083 in self.model.model_options.required_db_features
1084 ):
1085 return []
1086 return [
1087 PreflightResult(
1088 fix="CharFields must define a 'max_length' attribute.",
1089 obj=self,
1090 id="fields.charfield_missing_max_length",
1091 )
1092 ]
1093 elif (
1094 not isinstance(self.max_length, int)
1095 or isinstance(self.max_length, bool)
1096 or self.max_length <= 0
1097 ):
1098 return [
1099 PreflightResult(
1100 fix="'max_length' must be a positive integer.",
1101 obj=self,
1102 id="fields.charfield_invalid_max_length",
1103 )
1104 ]
1105 else:
1106 return []
1107
1108 def _check_db_collation(self) -> list[PreflightResult]:
1109 errors = []
1110 if not (
1111 self.db_collation is None
1112 or "supports_collation_on_charfield"
1113 in self.model.model_options.required_db_features
1114 or db_connection.features.supports_collation_on_charfield
1115 ):
1116 errors.append(
1117 PreflightResult(
1118 fix=f"{db_connection.display_name} does not support a database collation on "
1119 "CharFields.",
1120 obj=self,
1121 id="fields.db_collation_unsupported",
1122 ),
1123 )
1124 return errors
1125
1126 def cast_db_type(self, connection: BaseDatabaseWrapper) -> str | None:
1127 if self.max_length is None:
1128 return connection.ops.cast_char_field_without_max_length
1129 return super().cast_db_type(connection)
1130
1131 def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, Any]:
1132 db_params = super().db_parameters(connection)
1133 db_params["collation"] = self.db_collation
1134 return db_params
1135
1136 def get_internal_type(self) -> str:
1137 return "CharField"
1138
1139 def to_python(self, value: Any) -> Any:
1140 if isinstance(value, str) or value is None:
1141 return value
1142 return str(value)
1143
1144 def get_prep_value(self, value: Any) -> Any:
1145 value = super().get_prep_value(value)
1146 return self.to_python(value)
1147
1148 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
1149 name, path, args, kwargs = super().deconstruct()
1150 if self.db_collation:
1151 kwargs["db_collation"] = self.db_collation
1152 return name, path, args, kwargs
1153
1154
1155def _to_naive(value: datetime.datetime) -> datetime.datetime:
1156 if timezone.is_aware(value):
1157 value = timezone.make_naive(value, datetime.UTC)
1158 return value
1159
1160
1161def _get_naive_now() -> datetime.datetime:
1162 return _to_naive(timezone.now())
1163
1164
1165class DateTimeCheckMixin(Field):
1166 auto_now: bool
1167 auto_now_add: bool
1168
1169 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1170 return [
1171 *super().preflight(**kwargs),
1172 *self._check_mutually_exclusive_options(),
1173 *self._check_fix_default_value(),
1174 ]
1175
1176 def _check_mutually_exclusive_options(self) -> list[PreflightResult]:
1177 # auto_now, auto_now_add, and default are mutually exclusive
1178 # options. The use of more than one of these options together
1179 # will trigger an Error
1180 mutually_exclusive_options = [
1181 self.auto_now_add,
1182 self.auto_now,
1183 self.has_default(),
1184 ]
1185 enabled_options = [
1186 option not in (None, False) for option in mutually_exclusive_options
1187 ].count(True)
1188 if enabled_options > 1:
1189 return [
1190 PreflightResult(
1191 fix="The options auto_now, auto_now_add, and default "
1192 "are mutually exclusive. Only one of these options "
1193 "may be present.",
1194 obj=self,
1195 id="fields.datetime_auto_options_mutually_exclusive",
1196 )
1197 ]
1198 else:
1199 return []
1200
1201 def _check_fix_default_value(self) -> list[PreflightResult]:
1202 return []
1203
1204 # Concrete subclasses use this in their implementations of
1205 # _check_fix_default_value().
1206 def _check_if_value_fixed(
1207 self,
1208 value: datetime.date | datetime.datetime,
1209 now: datetime.datetime | None = None,
1210 ) -> list[PreflightResult]:
1211 """
1212 Check if the given value appears to have been provided as a "fixed"
1213 time value, and include a warning in the returned list if it does. The
1214 value argument must be a date object or aware/naive datetime object. If
1215 now is provided, it must be a naive datetime object.
1216 """
1217 if now is None:
1218 now = _get_naive_now()
1219 offset = datetime.timedelta(seconds=10)
1220 lower = now - offset
1221 upper = now + offset
1222 if isinstance(value, datetime.datetime):
1223 value = _to_naive(value)
1224 else:
1225 assert isinstance(value, datetime.date)
1226 lower = lower.date()
1227 upper = upper.date()
1228 if lower <= value <= upper:
1229 return [
1230 PreflightResult(
1231 fix="Fixed default value provided. "
1232 "It seems you set a fixed date / time / datetime "
1233 "value as default for this field. This may not be "
1234 "what you want. If you want to have the current date "
1235 "as default, use `plain.utils.timezone.now`",
1236 obj=self,
1237 id="fields.datetime_naive_default_value",
1238 warning=True,
1239 )
1240 ]
1241 return []
1242
1243
1244class DateField(DateTimeCheckMixin, Field[datetime.date]):
1245 empty_strings_allowed = False
1246 default_error_messages = {
1247 "invalid": '"%(value)s" value has an invalid date format. It must be in YYYY-MM-DD format.',
1248 "invalid_date": '"%(value)s" value has the correct format (YYYY-MM-DD) but it is an invalid date.',
1249 }
1250 description = "Date (without time)"
1251
1252 def __init__(
1253 self, *, auto_now: bool = False, auto_now_add: bool = False, **kwargs: Any
1254 ):
1255 self.auto_now, self.auto_now_add = auto_now, auto_now_add
1256 if auto_now or auto_now_add:
1257 kwargs["required"] = False
1258 super().__init__(**kwargs)
1259
1260 def _check_fix_default_value(self) -> list[PreflightResult]:
1261 """
1262 Warn that using an actual date or datetime value is probably wrong;
1263 it's only evaluated on server startup.
1264 """
1265 if not self.has_default():
1266 return []
1267
1268 value = self.default
1269 if isinstance(value, datetime.datetime):
1270 value = _to_naive(value).date()
1271 elif isinstance(value, datetime.date):
1272 pass
1273 else:
1274 # No explicit date / datetime value -- no checks necessary
1275 return []
1276 # At this point, value is a date object.
1277 return self._check_if_value_fixed(value)
1278
1279 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
1280 name, path, args, kwargs = super().deconstruct()
1281 if self.auto_now:
1282 kwargs["auto_now"] = True
1283 if self.auto_now_add:
1284 kwargs["auto_now_add"] = True
1285 if self.auto_now or self.auto_now_add:
1286 del kwargs["required"]
1287 return name, path, args, kwargs
1288
1289 def get_internal_type(self) -> str:
1290 return "DateField"
1291
1292 def to_python(self, value: Any) -> Any:
1293 if value is None:
1294 return value
1295 if isinstance(value, datetime.datetime):
1296 if timezone.is_aware(value):
1297 # Convert aware datetimes to the default time zone
1298 # before casting them to dates (#17742).
1299 default_timezone = timezone.get_default_timezone()
1300 value = timezone.make_naive(value, default_timezone)
1301 return value.date()
1302 if isinstance(value, datetime.date):
1303 return value
1304
1305 try:
1306 parsed = parse_date(value)
1307 if parsed is not None:
1308 return parsed
1309 except ValueError:
1310 raise exceptions.ValidationError(
1311 self.error_messages["invalid_date"],
1312 code="invalid_date",
1313 params={"value": value},
1314 )
1315
1316 raise exceptions.ValidationError(
1317 self.error_messages["invalid"],
1318 code="invalid",
1319 params={"value": value},
1320 )
1321
1322 def pre_save(self, model_instance: Any, add: bool) -> Any:
1323 if self.auto_now or (self.auto_now_add and add):
1324 value = datetime.date.today()
1325 setattr(model_instance, self.attname, value)
1326 return value
1327 else:
1328 return super().pre_save(model_instance, add)
1329
1330 def get_prep_value(self, value: Any) -> Any:
1331 value = super().get_prep_value(value)
1332 return self.to_python(value)
1333
1334 def get_db_prep_value(
1335 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1336 ) -> Any:
1337 # Casts dates into the format expected by the backend
1338 if not prepared:
1339 value = self.get_prep_value(value)
1340 return connection.ops.adapt_datefield_value(value)
1341
1342 def value_to_string(self, obj: Any) -> str:
1343 val = self.value_from_object(obj)
1344 return "" if val is None else val.isoformat()
1345
1346
1347class DateTimeField(DateField):
1348 empty_strings_allowed = False
1349 default_error_messages = {
1350 "invalid": '"%(value)s" value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.',
1351 "invalid_date": '"%(value)s" value has the correct format (YYYY-MM-DD) but it is an invalid date.',
1352 "invalid_datetime": '"%(value)s" value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but it is an invalid date/time.',
1353 }
1354 description = "Date (with time)"
1355
1356 # __init__ is inherited from DateField
1357
1358 def _check_fix_default_value(self) -> list[PreflightResult]:
1359 """
1360 Warn that using an actual date or datetime value is probably wrong;
1361 it's only evaluated on server startup.
1362 """
1363 if not self.has_default():
1364 return []
1365
1366 value = self.default
1367 if isinstance(value, datetime.datetime | datetime.date):
1368 return self._check_if_value_fixed(value)
1369 # No explicit date / datetime value -- no checks necessary.
1370 return []
1371
1372 def get_internal_type(self) -> str:
1373 return "DateTimeField"
1374
1375 def to_python(self, value: Any) -> Any:
1376 if value is None:
1377 return value
1378 if isinstance(value, datetime.datetime):
1379 return value
1380 if isinstance(value, datetime.date):
1381 value = datetime.datetime(value.year, value.month, value.day)
1382
1383 # For backwards compatibility, interpret naive datetimes in
1384 # local time. This won't work during DST change, but we can't
1385 # do much about it, so we let the exceptions percolate up the
1386 # call stack.
1387 warnings.warn(
1388 f"DateTimeField {self.model.__name__}.{self.name} received a naive datetime "
1389 f"({value}) while time zone support is active.",
1390 RuntimeWarning,
1391 )
1392 default_timezone = timezone.get_default_timezone()
1393 value = timezone.make_aware(value, default_timezone)
1394
1395 return value
1396
1397 try:
1398 parsed = parse_datetime(value)
1399 if parsed is not None:
1400 return parsed
1401 except ValueError:
1402 raise exceptions.ValidationError(
1403 self.error_messages["invalid_datetime"],
1404 code="invalid_datetime",
1405 params={"value": value},
1406 )
1407
1408 try:
1409 parsed = parse_date(value)
1410 if parsed is not None:
1411 return datetime.datetime(parsed.year, parsed.month, parsed.day)
1412 except ValueError:
1413 raise exceptions.ValidationError(
1414 self.error_messages["invalid_date"],
1415 code="invalid_date",
1416 params={"value": value},
1417 )
1418
1419 raise exceptions.ValidationError(
1420 self.error_messages["invalid"],
1421 code="invalid",
1422 params={"value": value},
1423 )
1424
1425 def pre_save(self, model_instance: Any, add: bool) -> Any:
1426 if self.auto_now or (self.auto_now_add and add):
1427 value = timezone.now()
1428 setattr(model_instance, self.attname, value)
1429 return value
1430 else:
1431 return super().pre_save(model_instance, add)
1432
1433 def get_prep_value(self, value: Any) -> Any:
1434 value = super().get_prep_value(value)
1435 value = self.to_python(value)
1436 if value is not None and timezone.is_naive(value):
1437 # For backwards compatibility, interpret naive datetimes in local
1438 # time. This won't work during DST change, but we can't do much
1439 # about it, so we let the exceptions percolate up the call stack.
1440 try:
1441 name = f"{self.model.__name__}.{self.name}"
1442 except AttributeError:
1443 name = "(unbound)"
1444 warnings.warn(
1445 f"DateTimeField {name} received a naive datetime ({value})"
1446 " while time zone support is active.",
1447 RuntimeWarning,
1448 )
1449 default_timezone = timezone.get_default_timezone()
1450 value = timezone.make_aware(value, default_timezone)
1451 return value
1452
1453 def get_db_prep_value(
1454 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1455 ) -> Any:
1456 # Casts datetimes into the format expected by the backend
1457 if not prepared:
1458 value = self.get_prep_value(value)
1459 return connection.ops.adapt_datetimefield_value(value)
1460
1461 def value_to_string(self, obj: Any) -> str:
1462 val = self.value_from_object(obj)
1463 return "" if val is None else val.isoformat()
1464
1465
1466class DecimalField(Field[decimal.Decimal]):
1467 empty_strings_allowed = False
1468 default_error_messages = {
1469 "invalid": '"%(value)s" value must be a decimal number.',
1470 }
1471 description = "Decimal number"
1472
1473 def __init__(
1474 self,
1475 *,
1476 max_digits: int | None = None,
1477 decimal_places: int | None = None,
1478 **kwargs: Any,
1479 ):
1480 self.max_digits, self.decimal_places = max_digits, decimal_places
1481 super().__init__(**kwargs)
1482
1483 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1484 errors = super().preflight(**kwargs)
1485
1486 digits_errors = [
1487 *self._check_decimal_places(),
1488 *self._check_max_digits(),
1489 ]
1490 if not digits_errors:
1491 errors.extend(self._check_decimal_places_and_max_digits())
1492 else:
1493 errors.extend(digits_errors)
1494 return errors
1495
1496 def _check_decimal_places(self) -> list[PreflightResult]:
1497 try:
1498 decimal_places = int(self.decimal_places)
1499 if decimal_places < 0:
1500 raise ValueError()
1501 except TypeError:
1502 return [
1503 PreflightResult(
1504 fix="DecimalFields must define a 'decimal_places' attribute.",
1505 obj=self,
1506 id="fields.decimalfield_missing_decimal_places",
1507 )
1508 ]
1509 except ValueError:
1510 return [
1511 PreflightResult(
1512 fix="'decimal_places' must be a non-negative integer.",
1513 obj=self,
1514 id="fields.decimalfield_invalid_decimal_places",
1515 )
1516 ]
1517 else:
1518 return []
1519
1520 def _check_max_digits(self) -> list[PreflightResult]:
1521 try:
1522 max_digits = int(self.max_digits)
1523 if max_digits <= 0:
1524 raise ValueError()
1525 except TypeError:
1526 return [
1527 PreflightResult(
1528 fix="DecimalFields must define a 'max_digits' attribute.",
1529 obj=self,
1530 id="fields.decimalfield_missing_max_digits",
1531 )
1532 ]
1533 except ValueError:
1534 return [
1535 PreflightResult(
1536 fix="'max_digits' must be a positive integer.",
1537 obj=self,
1538 id="fields.decimalfield_invalid_max_digits",
1539 )
1540 ]
1541 else:
1542 return []
1543
1544 def _check_decimal_places_and_max_digits(self) -> list[PreflightResult]:
1545 if int(self.decimal_places) > int(self.max_digits):
1546 return [
1547 PreflightResult(
1548 fix="'max_digits' must be greater or equal to 'decimal_places'.",
1549 obj=self,
1550 id="fields.decimalfield_decimal_places_exceeds_max_digits",
1551 )
1552 ]
1553 return []
1554
1555 @cached_property
1556 def validators(self) -> list[Callable[..., Any]]:
1557 return super().validators + [
1558 validators.DecimalValidator(self.max_digits, self.decimal_places)
1559 ]
1560
1561 @cached_property
1562 def context(self) -> decimal.Context:
1563 return decimal.Context(prec=self.max_digits)
1564
1565 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
1566 name, path, args, kwargs = super().deconstruct()
1567 if self.max_digits is not None:
1568 kwargs["max_digits"] = self.max_digits
1569 if self.decimal_places is not None:
1570 kwargs["decimal_places"] = self.decimal_places
1571 return name, path, args, kwargs
1572
1573 def get_internal_type(self) -> str:
1574 return "DecimalField"
1575
1576 def to_python(self, value: Any) -> Any:
1577 if value is None:
1578 return value
1579 try:
1580 if isinstance(value, float):
1581 decimal_value = self.context.create_decimal_from_float(value)
1582 else:
1583 decimal_value = decimal.Decimal(value)
1584 except (decimal.InvalidOperation, TypeError, ValueError):
1585 raise exceptions.ValidationError(
1586 self.error_messages["invalid"],
1587 code="invalid",
1588 params={"value": value},
1589 )
1590 if not decimal_value.is_finite():
1591 raise exceptions.ValidationError(
1592 self.error_messages["invalid"],
1593 code="invalid",
1594 params={"value": value},
1595 )
1596 return decimal_value
1597
1598 def get_db_prep_value(
1599 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1600 ) -> Any:
1601 if not prepared:
1602 value = self.get_prep_value(value)
1603 if hasattr(value, "as_sql"):
1604 return value
1605 return connection.ops.adapt_decimalfield_value(
1606 value, self.max_digits, self.decimal_places
1607 )
1608
1609 def get_prep_value(self, value: Any) -> Any:
1610 value = super().get_prep_value(value)
1611 return self.to_python(value)
1612
1613
1614class DurationField(Field[datetime.timedelta]):
1615 """
1616 Store timedelta objects.
1617
1618 Use interval on PostgreSQL, INTERVAL DAY TO SECOND on Oracle, and bigint
1619 of microseconds on other databases.
1620 """
1621
1622 empty_strings_allowed = False
1623 default_error_messages = {
1624 "invalid": '"%(value)s" value has an invalid format. It must be in [DD] [[HH:]MM:]ss[.uuuuuu] format.',
1625 }
1626 description = "Duration"
1627
1628 def get_internal_type(self) -> str:
1629 return "DurationField"
1630
1631 def to_python(self, value: Any) -> Any:
1632 if value is None:
1633 return value
1634 if isinstance(value, datetime.timedelta):
1635 return value
1636 try:
1637 parsed = parse_duration(value)
1638 except ValueError:
1639 pass
1640 else:
1641 if parsed is not None:
1642 return parsed
1643
1644 raise exceptions.ValidationError(
1645 self.error_messages["invalid"],
1646 code="invalid",
1647 params={"value": value},
1648 )
1649
1650 def get_db_prep_value(
1651 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1652 ) -> Any:
1653 if connection.features.has_native_duration_field:
1654 return value
1655 if value is None:
1656 return None
1657 return duration_microseconds(value)
1658
1659 def get_db_converters(
1660 self, connection: BaseDatabaseWrapper
1661 ) -> list[Callable[..., Any]]:
1662 converters = []
1663 if not connection.features.has_native_duration_field:
1664 converters.append(connection.ops.convert_durationfield_value)
1665 return converters + super().get_db_converters(connection)
1666
1667 def value_to_string(self, obj: Any) -> str:
1668 val = self.value_from_object(obj)
1669 return "" if val is None else duration_string(val)
1670
1671
1672class EmailField(CharField):
1673 default_validators = [validators.validate_email]
1674 description = "Email address"
1675
1676 def __init__(self, **kwargs: Any):
1677 # max_length=254 to be compliant with RFCs 3696 and 5321
1678 kwargs.setdefault("max_length", 254)
1679 super().__init__(**kwargs)
1680
1681 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
1682 name, path, args, kwargs = super().deconstruct()
1683 # We do not exclude max_length if it matches default as we want to change
1684 # the default in future.
1685 return name, path, args, kwargs
1686
1687
1688class FloatField(Field[float]):
1689 empty_strings_allowed = False
1690 default_error_messages = {
1691 "invalid": '"%(value)s" value must be a float.',
1692 }
1693 description = "Floating point number"
1694
1695 def get_prep_value(self, value: Any) -> Any:
1696 value = super().get_prep_value(value)
1697 if value is None:
1698 return None
1699 try:
1700 return float(value)
1701 except (TypeError, ValueError) as e:
1702 raise e.__class__(
1703 f"Field '{self.name}' expected a number but got {value!r}.",
1704 ) from e
1705
1706 def get_internal_type(self) -> str:
1707 return "FloatField"
1708
1709 def to_python(self, value: Any) -> Any:
1710 if value is None:
1711 return value
1712 try:
1713 return float(value)
1714 except (TypeError, ValueError):
1715 raise exceptions.ValidationError(
1716 self.error_messages["invalid"],
1717 code="invalid",
1718 params={"value": value},
1719 )
1720
1721
1722class IntegerField(Field[int]):
1723 empty_strings_allowed = False
1724 default_error_messages = {
1725 "invalid": '"%(value)s" value must be an integer.',
1726 }
1727 description = "Integer"
1728
1729 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1730 return [
1731 *super().preflight(**kwargs),
1732 *self._check_max_length_warning(),
1733 ]
1734
1735 def _check_max_length_warning(self) -> list[PreflightResult]:
1736 if self.max_length is not None:
1737 return [
1738 PreflightResult(
1739 fix=f"'max_length' is ignored when used with {self.__class__.__name__}. Remove 'max_length' from field.",
1740 obj=self,
1741 id="fields.max_length_ignored",
1742 warning=True,
1743 )
1744 ]
1745 return []
1746
1747 @cached_property
1748 def validators(self) -> list[Callable[..., Any]]:
1749 # These validators can't be added at field initialization time since
1750 # they're based on values retrieved from the database connection.
1751 validators_ = super().validators
1752 internal_type = self.get_internal_type()
1753 min_value, max_value = db_connection.ops.integer_field_range(internal_type)
1754 if min_value is not None and not any(
1755 (
1756 isinstance(validator, validators.MinValueValidator)
1757 and (
1758 validator.limit_value()
1759 if callable(validator.limit_value)
1760 else validator.limit_value
1761 )
1762 >= min_value
1763 )
1764 for validator in validators_
1765 ):
1766 validators_.append(validators.MinValueValidator(min_value))
1767 if max_value is not None and not any(
1768 (
1769 isinstance(validator, validators.MaxValueValidator)
1770 and (
1771 validator.limit_value()
1772 if callable(validator.limit_value)
1773 else validator.limit_value
1774 )
1775 <= max_value
1776 )
1777 for validator in validators_
1778 ):
1779 validators_.append(validators.MaxValueValidator(max_value))
1780 return validators_
1781
1782 def get_prep_value(self, value: Any) -> Any:
1783 value = super().get_prep_value(value)
1784 if value is None:
1785 return None
1786 try:
1787 return int(value)
1788 except (TypeError, ValueError) as e:
1789 raise e.__class__(
1790 f"Field '{self.name}' expected a number but got {value!r}.",
1791 ) from e
1792
1793 def get_db_prep_value(
1794 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1795 ) -> Any:
1796 value = super().get_db_prep_value(value, connection, prepared)
1797 return connection.ops.adapt_integerfield_value(value, self.get_internal_type())
1798
1799 def get_internal_type(self) -> str:
1800 return "IntegerField"
1801
1802 def to_python(self, value: Any) -> Any:
1803 if value is None:
1804 return value
1805 try:
1806 return int(value)
1807 except (TypeError, ValueError):
1808 raise exceptions.ValidationError(
1809 self.error_messages["invalid"],
1810 code="invalid",
1811 params={"value": value},
1812 )
1813
1814
1815class BigIntegerField(IntegerField):
1816 description = "Big (8 byte) integer"
1817
1818 def get_internal_type(self) -> str:
1819 return "BigIntegerField"
1820
1821
1822class SmallIntegerField(IntegerField):
1823 description = "Small integer"
1824
1825 def get_internal_type(self) -> str:
1826 return "SmallIntegerField"
1827
1828
1829class GenericIPAddressField(Field[str]):
1830 empty_strings_allowed = False
1831 description = "IP address"
1832 default_error_messages = {}
1833
1834 def __init__(
1835 self,
1836 *,
1837 protocol: str = "both",
1838 unpack_ipv4: bool = False,
1839 **kwargs: Any,
1840 ):
1841 self.unpack_ipv4 = unpack_ipv4
1842 self.protocol = protocol
1843 (
1844 self.default_validators,
1845 invalid_error_message,
1846 ) = validators.ip_address_validators(protocol, unpack_ipv4)
1847 self.default_error_messages["invalid"] = invalid_error_message
1848 kwargs["max_length"] = 39
1849 super().__init__(**kwargs)
1850
1851 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1852 return [
1853 *super().preflight(**kwargs),
1854 *self._check_required_and_null_values(),
1855 ]
1856
1857 def _check_required_and_null_values(self) -> list[PreflightResult]:
1858 if not getattr(self, "allow_null", False) and not getattr(
1859 self, "required", True
1860 ):
1861 return [
1862 PreflightResult(
1863 fix="GenericIPAddressFields cannot have required=False if allow_null=False, "
1864 "as blank values are stored as nulls.",
1865 obj=self,
1866 id="fields.generic_ip_field_null_blank_config",
1867 )
1868 ]
1869 return []
1870
1871 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
1872 name, path, args, kwargs = super().deconstruct()
1873 if self.unpack_ipv4 is not False:
1874 kwargs["unpack_ipv4"] = self.unpack_ipv4
1875 if self.protocol != "both":
1876 kwargs["protocol"] = self.protocol
1877 if kwargs.get("max_length") == 39:
1878 del kwargs["max_length"]
1879 return name, path, args, kwargs
1880
1881 def get_internal_type(self) -> str:
1882 return "GenericIPAddressField"
1883
1884 def to_python(self, value: Any) -> Any:
1885 if value is None:
1886 return None
1887 if not isinstance(value, str):
1888 value = str(value)
1889 value = value.strip()
1890 if ":" in value:
1891 return clean_ipv6_address(
1892 value, self.unpack_ipv4, self.error_messages["invalid"]
1893 )
1894 return value
1895
1896 def get_db_prep_value(
1897 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
1898 ) -> Any:
1899 if not prepared:
1900 value = self.get_prep_value(value)
1901 return connection.ops.adapt_ipaddressfield_value(value)
1902
1903 def get_prep_value(self, value: Any) -> Any:
1904 value = super().get_prep_value(value)
1905 if value is None:
1906 return None
1907 if value and ":" in value:
1908 try:
1909 return clean_ipv6_address(value, self.unpack_ipv4)
1910 except exceptions.ValidationError:
1911 pass
1912 return str(value)
1913
1914
1915class _HasDbType(Protocol):
1916 """Protocol for objects that have a db_type method and integer_field_class."""
1917
1918 integer_field_class: type[IntegerField]
1919
1920 def db_type(self, connection: BaseDatabaseWrapper) -> str | None: ...
1921
1922
1923class PositiveIntegerRelDbTypeMixin(IntegerField):
1924 integer_field_class: type[IntegerField]
1925
1926 def __init_subclass__(cls, **kwargs: Any) -> None:
1927 super().__init_subclass__(**kwargs)
1928 if not hasattr(cls, "integer_field_class"):
1929 cls.integer_field_class = next(
1930 (
1931 parent
1932 for parent in cls.__mro__[1:]
1933 if issubclass(parent, IntegerField)
1934 ),
1935 None,
1936 )
1937
1938 def rel_db_type(self: _HasDbType, connection: BaseDatabaseWrapper) -> str | None:
1939 """
1940 Return the data type that a related field pointing to this field should
1941 use. In most cases, a foreign key pointing to a positive integer
1942 primary key will have an integer column data type but some databases
1943 (e.g. MySQL) have an unsigned integer type. In that case
1944 (related_fields_match_type=True), the primary key should return its
1945 db_type.
1946 """
1947 if connection.features.related_fields_match_type:
1948 return self.db_type(connection)
1949 else:
1950 return self.integer_field_class().db_type(connection=connection)
1951
1952
1953class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField):
1954 description = "Positive big integer"
1955
1956 def get_internal_type(self) -> str:
1957 return "PositiveBigIntegerField"
1958
1959
1960class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
1961 description = "Positive integer"
1962
1963 def get_internal_type(self) -> str:
1964 return "PositiveIntegerField"
1965
1966
1967class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField):
1968 description = "Positive small integer"
1969
1970 def get_internal_type(self) -> str:
1971 return "PositiveSmallIntegerField"
1972
1973
1974class TextField(Field[str]):
1975 description = "Text"
1976
1977 def __init__(self, *, db_collation: str | None = None, **kwargs: Any):
1978 super().__init__(**kwargs)
1979 self.db_collation = db_collation
1980
1981 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
1982 return [
1983 *super().preflight(**kwargs),
1984 *self._check_db_collation(),
1985 ]
1986
1987 def _check_db_collation(self) -> list[PreflightResult]:
1988 errors = []
1989 if not (
1990 self.db_collation is None
1991 or "supports_collation_on_textfield"
1992 in self.model.model_options.required_db_features
1993 or db_connection.features.supports_collation_on_textfield
1994 ):
1995 errors.append(
1996 PreflightResult(
1997 fix=f"{db_connection.display_name} does not support a database collation on "
1998 "TextFields.",
1999 obj=self,
2000 id="fields.db_collation_unsupported",
2001 ),
2002 )
2003 return errors
2004
2005 def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, Any]:
2006 db_params = super().db_parameters(connection)
2007 db_params["collation"] = self.db_collation
2008 return db_params
2009
2010 def get_internal_type(self) -> str:
2011 return "TextField"
2012
2013 def to_python(self, value: Any) -> Any:
2014 if isinstance(value, str) or value is None:
2015 return value
2016 return str(value)
2017
2018 def get_prep_value(self, value: Any) -> Any:
2019 value = super().get_prep_value(value)
2020 return self.to_python(value)
2021
2022 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
2023 name, path, args, kwargs = super().deconstruct()
2024 if self.db_collation:
2025 kwargs["db_collation"] = self.db_collation
2026 return name, path, args, kwargs
2027
2028
2029class TimeField(DateTimeCheckMixin, Field[datetime.time]):
2030 empty_strings_allowed = False
2031 default_error_messages = {
2032 "invalid": '"%(value)s" value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format.',
2033 "invalid_time": '"%(value)s" value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid time.',
2034 }
2035 description = "Time"
2036
2037 def __init__(
2038 self, *, auto_now: bool = False, auto_now_add: bool = False, **kwargs: Any
2039 ):
2040 self.auto_now, self.auto_now_add = auto_now, auto_now_add
2041 if auto_now or auto_now_add:
2042 kwargs["required"] = False
2043 super().__init__(**kwargs)
2044
2045 def _check_fix_default_value(self) -> list[PreflightResult]:
2046 """
2047 Warn that using an actual date or datetime value is probably wrong;
2048 it's only evaluated on server startup.
2049 """
2050 if not self.has_default():
2051 return []
2052
2053 value = self.default
2054 if isinstance(value, datetime.datetime):
2055 now = None
2056 elif isinstance(value, datetime.time):
2057 now = _get_naive_now()
2058 # This will not use the right date in the race condition where now
2059 # is just before the date change and value is just past 0:00.
2060 value = datetime.datetime.combine(now.date(), value)
2061 else:
2062 # No explicit time / datetime value -- no checks necessary
2063 return []
2064 # At this point, value is a datetime object.
2065 return self._check_if_value_fixed(value, now=now)
2066
2067 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
2068 name, path, args, kwargs = super().deconstruct()
2069 if self.auto_now is not False:
2070 kwargs["auto_now"] = self.auto_now
2071 if self.auto_now_add is not False:
2072 kwargs["auto_now_add"] = self.auto_now_add
2073 if self.auto_now or self.auto_now_add:
2074 del kwargs["required"]
2075 return name, path, args, kwargs
2076
2077 def get_internal_type(self) -> str:
2078 return "TimeField"
2079
2080 def to_python(self, value: Any) -> Any:
2081 if value is None:
2082 return None
2083 if isinstance(value, datetime.time):
2084 return value
2085 if isinstance(value, datetime.datetime):
2086 # Not usually a good idea to pass in a datetime here (it loses
2087 # information), but this can be a side-effect of interacting with a
2088 # database backend (e.g. Oracle), so we'll be accommodating.
2089 return value.time()
2090
2091 try:
2092 parsed = parse_time(value)
2093 if parsed is not None:
2094 return parsed
2095 except ValueError:
2096 raise exceptions.ValidationError(
2097 self.error_messages["invalid_time"],
2098 code="invalid_time",
2099 params={"value": value},
2100 )
2101
2102 raise exceptions.ValidationError(
2103 self.error_messages["invalid"],
2104 code="invalid",
2105 params={"value": value},
2106 )
2107
2108 def pre_save(self, model_instance: Any, add: bool) -> Any:
2109 if self.auto_now or (self.auto_now_add and add):
2110 value = datetime.datetime.now().time()
2111 setattr(model_instance, self.attname, value)
2112 return value
2113 else:
2114 return super().pre_save(model_instance, add)
2115
2116 def get_prep_value(self, value: Any) -> Any:
2117 value = super().get_prep_value(value)
2118 return self.to_python(value)
2119
2120 def get_db_prep_value(
2121 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
2122 ) -> Any:
2123 # Casts times into the format expected by the backend
2124 if not prepared:
2125 value = self.get_prep_value(value)
2126 return connection.ops.adapt_timefield_value(value)
2127
2128 def value_to_string(self, obj: Any) -> str:
2129 val = self.value_from_object(obj)
2130 return "" if val is None else val.isoformat()
2131
2132
2133class URLField(CharField):
2134 default_validators = [validators.URLValidator()]
2135 description = "URL"
2136
2137 def __init__(self, **kwargs: Any):
2138 kwargs.setdefault("max_length", 200)
2139 super().__init__(**kwargs)
2140
2141 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
2142 name, path, args, kwargs = super().deconstruct()
2143 if kwargs.get("max_length") == 200:
2144 del kwargs["max_length"]
2145 return name, path, args, kwargs
2146
2147
2148class BinaryField(Field[bytes | memoryview]):
2149 description = "Raw binary data"
2150 empty_values = [None, b""]
2151
2152 def __init__(self, **kwargs: Any):
2153 super().__init__(**kwargs)
2154 if self.max_length is not None:
2155 self.validators.append(validators.MaxLengthValidator(self.max_length))
2156
2157 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
2158 return [*super().preflight(**kwargs), *self._check_str_default_value()]
2159
2160 def _check_str_default_value(self) -> list[PreflightResult]:
2161 if self.has_default() and isinstance(self.default, str):
2162 return [
2163 PreflightResult(
2164 fix="BinaryField's default cannot be a string. Use bytes "
2165 "content instead.",
2166 obj=self,
2167 id="fields.filefield_upload_to_not_callable",
2168 )
2169 ]
2170 return []
2171
2172 def get_internal_type(self) -> str:
2173 return "BinaryField"
2174
2175 def get_placeholder(
2176 self, value: Any, compiler: SQLCompiler, connection: BaseDatabaseWrapper
2177 ) -> Any:
2178 return connection.ops.binary_placeholder_sql(value)
2179
2180 def get_default(self) -> Any:
2181 if self.has_default() and not callable(self.default):
2182 return self.default
2183 default = super().get_default()
2184 if default == "":
2185 return b""
2186 return default
2187
2188 def get_db_prep_value(
2189 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
2190 ) -> Any:
2191 value = super().get_db_prep_value(value, connection, prepared)
2192 if value is not None:
2193 return connection.Database.Binary(value)
2194 return value
2195
2196 def value_to_string(self, obj: Any) -> str:
2197 """Binary data is serialized as base64"""
2198 return b64encode(self.value_from_object(obj)).decode("ascii")
2199
2200 def to_python(self, value: Any) -> Any:
2201 # If it's a string, it should be base64-encoded data
2202 if isinstance(value, str):
2203 return memoryview(b64decode(value.encode("ascii")))
2204 return value
2205
2206
2207class UUIDField(Field[uuid.UUID]):
2208 default_error_messages = {
2209 "invalid": '"%(value)s" is not a valid UUID.',
2210 }
2211 description = "Universally unique identifier"
2212 empty_strings_allowed = False
2213
2214 def __init__(self, **kwargs: Any):
2215 kwargs["max_length"] = 32
2216 super().__init__(**kwargs)
2217
2218 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
2219 name, path, args, kwargs = super().deconstruct()
2220 del kwargs["max_length"]
2221 return name, path, args, kwargs
2222
2223 def get_internal_type(self) -> str:
2224 return "UUIDField"
2225
2226 def get_prep_value(self, value: Any) -> Any:
2227 value = super().get_prep_value(value)
2228 return self.to_python(value)
2229
2230 def get_db_prep_value(
2231 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
2232 ) -> Any:
2233 if value is None:
2234 return None
2235 if not isinstance(value, uuid.UUID):
2236 value = self.to_python(value)
2237
2238 if connection.features.has_native_uuid_field:
2239 return value
2240 return value.hex
2241
2242 def to_python(self, value: Any) -> Any:
2243 if value is not None and not isinstance(value, uuid.UUID):
2244 input_form = "int" if isinstance(value, int) else "hex"
2245 try:
2246 return uuid.UUID(**{input_form: value})
2247 except (AttributeError, ValueError):
2248 raise exceptions.ValidationError(
2249 self.error_messages["invalid"],
2250 code="invalid",
2251 params={"value": value},
2252 )
2253 return value
2254
2255
2256class PrimaryKeyField(BigIntegerField):
2257 db_returning = True
2258
2259 def __init__(self):
2260 super().__init__(required=False)
2261 self.primary_key = True
2262 self.auto_created = True
2263 # Adjust creation counter for auto-created fields
2264 # We need to undo the counter increment from Field.__init__ and use the auto counter
2265 Field.creation_counter -= 1 # Undo the increment
2266 self.creation_counter = Field.auto_creation_counter
2267 Field.auto_creation_counter -= 1
2268
2269 def preflight(self, **kwargs: Any) -> list[PreflightResult]:
2270 errors = super().preflight(**kwargs)
2271 # Remove the reserved_field_name_id error for 'id' field name since PrimaryKeyField is allowed to use it
2272 errors = [e for e in errors if e.id != "fields.reserved_field_name_id"]
2273 return errors
2274
2275 def deconstruct(self) -> tuple[str | None, str, list[Any], dict[str, Any]]:
2276 # PrimaryKeyField takes no parameters, so we return an empty kwargs dict
2277 return (
2278 self.name,
2279 "plain.models.PrimaryKeyField",
2280 cast(list[Any], []),
2281 cast(dict[str, Any], {}),
2282 )
2283
2284 def validate(self, value: Any, model_instance: Any) -> None:
2285 pass
2286
2287 def get_db_prep_value(
2288 self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
2289 ) -> Any:
2290 if not prepared:
2291 value = self.get_prep_value(value)
2292 value = connection.ops.validate_autopk_value(value)
2293 return value
2294
2295 def get_internal_type(self) -> str:
2296 return "PrimaryKeyField"
2297
2298 def rel_db_type(self, connection: BaseDatabaseWrapper) -> str | None:
2299 return BigIntegerField().db_type(connection=connection)