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