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