plain.models 0.50.0__py3-none-any.whl → 0.51.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. plain/models/CHANGELOG.md +24 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/backends/base/creation.py +2 -2
  5. plain/models/backends/base/introspection.py +8 -4
  6. plain/models/backends/base/schema.py +89 -71
  7. plain/models/backends/base/validation.py +1 -1
  8. plain/models/backends/mysql/compiler.py +1 -1
  9. plain/models/backends/mysql/operations.py +1 -1
  10. plain/models/backends/mysql/schema.py +4 -4
  11. plain/models/backends/postgresql/operations.py +1 -1
  12. plain/models/backends/postgresql/schema.py +3 -3
  13. plain/models/backends/sqlite3/operations.py +1 -1
  14. plain/models/backends/sqlite3/schema.py +61 -50
  15. plain/models/base.py +116 -163
  16. plain/models/cli.py +4 -4
  17. plain/models/constraints.py +14 -9
  18. plain/models/deletion.py +15 -14
  19. plain/models/expressions.py +21 -5
  20. plain/models/fields/__init__.py +20 -16
  21. plain/models/fields/json.py +3 -3
  22. plain/models/fields/related.py +73 -71
  23. plain/models/fields/related_descriptors.py +2 -2
  24. plain/models/fields/related_lookups.py +1 -1
  25. plain/models/fields/related_managers.py +21 -32
  26. plain/models/fields/reverse_related.py +8 -8
  27. plain/models/forms.py +12 -12
  28. plain/models/indexes.py +5 -4
  29. plain/models/meta.py +505 -0
  30. plain/models/migrations/operations/base.py +1 -1
  31. plain/models/migrations/operations/fields.py +6 -6
  32. plain/models/migrations/operations/models.py +18 -16
  33. plain/models/migrations/recorder.py +9 -5
  34. plain/models/migrations/state.py +35 -46
  35. plain/models/migrations/utils.py +1 -1
  36. plain/models/options.py +182 -518
  37. plain/models/preflight.py +7 -5
  38. plain/models/query.py +119 -65
  39. plain/models/query_utils.py +18 -13
  40. plain/models/registry.py +6 -5
  41. plain/models/sql/compiler.py +51 -37
  42. plain/models/sql/query.py +77 -68
  43. plain/models/sql/subqueries.py +4 -4
  44. plain/models/utils.py +4 -1
  45. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/METADATA +27 -43
  46. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/RECORD +49 -48
  47. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/WHEEL +0 -0
  48. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/entry_points.txt +0 -0
  49. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/licenses/LICENSE +0 -0
plain/models/base.py CHANGED
@@ -1,11 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
- import inspect
5
4
  import warnings
6
5
  from collections.abc import Iterable, Iterator, Sequence
7
6
  from itertools import chain
8
- from typing import Any
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ if TYPE_CHECKING:
10
+ from plain.models.meta import Meta
11
+ from plain.models.options import Options
9
12
 
10
13
  import plain.runtime
11
14
  from plain.exceptions import NON_FIELD_ERRORS, ValidationError
@@ -26,9 +29,9 @@ from plain.models.exceptions import (
26
29
  from plain.models.expressions import RawSQL, Value
27
30
  from plain.models.fields import NOT_PROVIDED, PrimaryKeyField
28
31
  from plain.models.fields.reverse_related import ForeignObjectRel
32
+ from plain.models.meta import Meta
29
33
  from plain.models.options import Options
30
34
  from plain.models.query import F, Q, QuerySet
31
- from plain.packages import packages_registry
32
35
  from plain.preflight import PreflightResult
33
36
  from plain.utils.encoding import force_str
34
37
  from plain.utils.hashable import make_hashable
@@ -61,71 +64,8 @@ class ModelBase(type):
61
64
  raise TypeError(
62
65
  f"A model can't extend another model: {name} extends {base}"
63
66
  )
64
- # Meta has to be defined on the model itself.
65
- if hasattr(base, "Meta"):
66
- raise TypeError(
67
- "Meta can only be defined on a model itself, not a parent class: "
68
- f"{name} extends {base}"
69
- )
70
-
71
- new_class = super().__new__(cls, name, bases, attrs, **kwargs)
72
-
73
- new_class._setup_meta()
74
-
75
- # Now go back over all the attrs on this class see if they have a contribute_to_class() method.
76
- # Attributes with contribute_to_class are fields and meta options.
77
- for attr_name, attr_value in inspect.getmembers(new_class):
78
- if attr_name.startswith("_"):
79
- continue
80
-
81
- if not inspect.isclass(attr_value) and hasattr(
82
- attr_value, "contribute_to_class"
83
- ):
84
- if attr_name not in attrs:
85
- # If the field came from an inherited class/mixin,
86
- # we need to make a copy of it to avoid altering the
87
- # original class and other classes that inherit from it.
88
- field = copy.deepcopy(attr_value)
89
- else:
90
- field = attr_value
91
- field.contribute_to_class(new_class, attr_name)
92
-
93
- # Set the name of _meta.indexes. This can't be done in
94
- # Options.contribute_to_class() because fields haven't been added to
95
- # the model at that point.
96
- for index in new_class._meta.indexes:
97
- if not index.name:
98
- index.set_name_with_model(new_class)
99
-
100
- return new_class
101
-
102
- def _setup_meta(cls) -> None:
103
- name = cls.__name__
104
- module = cls.__module__
105
-
106
- # The model's Meta class, if it has one.
107
- meta = getattr(cls, "Meta", None)
108
-
109
- # Look for an application configuration to attach the model to.
110
- package_config = packages_registry.get_containing_package_config(module)
111
-
112
- package_label = getattr(meta, "package_label", None)
113
- if package_label is None:
114
- if package_config is None:
115
- raise RuntimeError(
116
- f"Model class {module}.{name} doesn't declare an explicit "
117
- "package_label and isn't in an application in "
118
- "INSTALLED_PACKAGES."
119
- )
120
- else:
121
- package_label = package_config.package_label
122
67
 
123
- Options(meta, package_label).contribute_to_class(cls, "_meta")
124
-
125
- @property
126
- def query(cls) -> QuerySet:
127
- """Create a new QuerySet for this model."""
128
- return cls._meta.queryset
68
+ return super().__new__(cls, name, bases, attrs, **kwargs)
129
69
 
130
70
 
131
71
  class ModelStateFieldsCacheDescriptor:
@@ -150,19 +90,20 @@ class ModelState:
150
90
 
151
91
 
152
92
  class Model(metaclass=ModelBase):
153
- _meta: Options
93
+ # Every model gets an automatic id field
94
+ id = PrimaryKeyField()
154
95
 
155
- # Use descriptors for exception classes instead of metaclass generation
96
+ # Descriptors for other model behavior
97
+ query = QuerySet()
98
+ model_options = Options()
99
+ _model_meta = Meta()
156
100
  DoesNotExist = DoesNotExistDescriptor()
157
101
  MultipleObjectsReturned = MultipleObjectsReturnedDescriptor()
158
102
 
159
- # Every model gets an automatic id field
160
- id = PrimaryKeyField()
161
-
162
103
  def __init__(self, *args: Any, **kwargs: Any):
163
104
  # Alias some things as locals to avoid repeat global lookups
164
105
  cls = self.__class__
165
- opts = self._meta
106
+ meta = cls._model_meta
166
107
  _setattr = setattr
167
108
  _DEFERRED = DEFERRED
168
109
 
@@ -173,12 +114,12 @@ class Model(metaclass=ModelBase):
173
114
  # overrides it. It should be one or the other; don't duplicate the work
174
115
  # The reason for the kwargs check is that standard iterator passes in by
175
116
  # args, and instantiation for iteration is 33% faster.
176
- if len(args) > len(opts.concrete_fields):
117
+ if len(args) > len(meta.concrete_fields):
177
118
  # Daft, but matches old exception sans the err msg.
178
119
  raise IndexError("Number of args exceeds number of fields")
179
120
 
180
121
  if not kwargs:
181
- fields_iter = iter(opts.concrete_fields)
122
+ fields_iter = iter(meta.concrete_fields)
182
123
  # The ordering of the zip calls matter - zip throws StopIteration
183
124
  # when an iter throws it. So if the first iter throws it, the second
184
125
  # is *not* consumed. We rely on this, so don't change the order
@@ -189,7 +130,7 @@ class Model(metaclass=ModelBase):
189
130
  _setattr(self, field.attname, val)
190
131
  else:
191
132
  # Slower, kwargs-ready version.
192
- fields_iter = iter(opts.fields)
133
+ fields_iter = iter(meta.fields)
193
134
  for val, field in zip(args, fields_iter):
194
135
  if val is _DEFERRED:
195
136
  continue
@@ -244,7 +185,7 @@ class Model(metaclass=ModelBase):
244
185
  _setattr(self, field.attname, val)
245
186
 
246
187
  if kwargs:
247
- property_names = opts._property_names
188
+ property_names = meta._property_names
248
189
  unexpected = ()
249
190
  for prop, value in kwargs.items():
250
191
  # Any remaining kwargs must correspond to properties or virtual
@@ -254,7 +195,7 @@ class Model(metaclass=ModelBase):
254
195
  _setattr(self, prop, value)
255
196
  else:
256
197
  try:
257
- opts.get_field(prop)
198
+ meta.get_field(prop)
258
199
  except FieldDoesNotExist:
259
200
  unexpected += (prop,)
260
201
  else:
@@ -270,11 +211,11 @@ class Model(metaclass=ModelBase):
270
211
 
271
212
  @classmethod
272
213
  def from_db(cls, field_names: Iterable[str], values: Sequence[Any]) -> Model:
273
- if len(values) != len(cls._meta.concrete_fields):
214
+ if len(values) != len(cls._model_meta.concrete_fields):
274
215
  values_iter = iter(values)
275
216
  values = [
276
217
  next(values_iter) if f.attname in field_names else DEFERRED
277
- for f in cls._meta.concrete_fields
218
+ for f in cls._model_meta.concrete_fields
278
219
  ]
279
220
  new = cls(*values)
280
221
  new._state.adding = False
@@ -304,7 +245,10 @@ class Model(metaclass=ModelBase):
304
245
  def __reduce__(self) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:
305
246
  data = self.__getstate__()
306
247
  data[PLAIN_VERSION_PICKLE_KEY] = plain.runtime.__version__
307
- class_id = self._meta.package_label, self._meta.object_name
248
+ class_id = (
249
+ self.model_options.package_label,
250
+ self.model_options.object_name,
251
+ )
308
252
  return model_unpickle, (class_id,), data
309
253
 
310
254
  def __getstate__(self) -> dict[str, Any]:
@@ -351,7 +295,7 @@ class Model(metaclass=ModelBase):
351
295
  """
352
296
  return {
353
297
  f.attname
354
- for f in self._meta.concrete_fields
298
+ for f in self._model_meta.concrete_fields
355
299
  if f.attname not in self.__dict__
356
300
  }
357
301
 
@@ -386,7 +330,7 @@ class Model(metaclass=ModelBase):
386
330
  "are not allowed in fields."
387
331
  )
388
332
 
389
- db_instance_qs = self.__class__._meta.base_queryset.filter(id=self.id)
333
+ db_instance_qs = self._model_meta.base_queryset.filter(id=self.id)
390
334
 
391
335
  # Use provided fields, if not set then reload all non-deferred fields.
392
336
  deferred_fields = self.get_deferred_fields()
@@ -396,14 +340,14 @@ class Model(metaclass=ModelBase):
396
340
  elif deferred_fields:
397
341
  fields = [
398
342
  f.attname
399
- for f in self._meta.concrete_fields
343
+ for f in self._model_meta.concrete_fields
400
344
  if f.attname not in deferred_fields
401
345
  ]
402
346
  db_instance_qs = db_instance_qs.only(*fields)
403
347
 
404
348
  db_instance = db_instance_qs.get()
405
349
  non_loaded_fields = db_instance.get_deferred_fields()
406
- for field in self._meta.concrete_fields:
350
+ for field in self._model_meta.concrete_fields:
407
351
  if field.attname in non_loaded_fields:
408
352
  # This field wasn't refreshed - skip ahead.
409
353
  continue
@@ -413,7 +357,7 @@ class Model(metaclass=ModelBase):
413
357
  field.delete_cached_value(self)
414
358
 
415
359
  # Clear cached relations.
416
- for field in self._meta.related_objects:
360
+ for field in self._model_meta.related_objects:
417
361
  if field.is_cached(self):
418
362
  field.delete_cached_value(self)
419
363
 
@@ -429,7 +373,7 @@ class Model(metaclass=ModelBase):
429
373
  and not use this method.
430
374
  """
431
375
  try:
432
- field = self._meta.get_field(field_name)
376
+ field = self._model_meta.get_field(field_name)
433
377
  except FieldDoesNotExist:
434
378
  return getattr(self, field_name)
435
379
  return getattr(self, field.attname)
@@ -464,7 +408,7 @@ class Model(metaclass=ModelBase):
464
408
  return
465
409
 
466
410
  update_fields = frozenset(update_fields)
467
- field_names = self._meta._non_pk_concrete_field_names
411
+ field_names = self._model_meta._non_pk_concrete_field_names
468
412
  non_model_fields = update_fields.difference(field_names)
469
413
 
470
414
  if non_model_fields:
@@ -479,7 +423,7 @@ class Model(metaclass=ModelBase):
479
423
  # on the loaded fields.
480
424
  elif not force_insert and deferred_fields:
481
425
  field_names = set()
482
- for field in self._meta.concrete_fields:
426
+ for field in self._model_meta.concrete_fields:
483
427
  if not field.primary_key and not hasattr(field, "through"):
484
428
  field_names.add(field.attname)
485
429
  loaded_fields = field_names.difference(deferred_fields)
@@ -518,19 +462,20 @@ class Model(metaclass=ModelBase):
518
462
 
519
463
  with transaction.mark_for_rollback_on_error():
520
464
  self._save_table(
521
- raw,
522
- cls,
523
- force_insert,
524
- force_update,
525
- update_fields,
465
+ raw=raw,
466
+ cls=cls,
467
+ force_insert=force_insert,
468
+ force_update=force_update,
469
+ update_fields=update_fields,
526
470
  )
527
471
  # Once saved, this is no longer a to-be-added instance.
528
472
  self._state.adding = False
529
473
 
530
474
  def _save_table(
531
475
  self,
532
- raw: bool = False,
533
- cls: type[Model] | None = None,
476
+ *,
477
+ raw: bool,
478
+ cls: type[Model],
534
479
  force_insert: bool = False,
535
480
  force_update: bool = False,
536
481
  update_fields: Iterable[str] | None = None,
@@ -539,7 +484,7 @@ class Model(metaclass=ModelBase):
539
484
  Do the heavy-lifting involved in saving. Update or insert the data
540
485
  for a single table.
541
486
  """
542
- meta = cls._meta # type: ignore[union-attr]
487
+ meta = cls._model_meta
543
488
  non_pks = [f for f in meta.local_concrete_fields if not f.primary_key]
544
489
 
545
490
  if update_fields:
@@ -644,7 +589,7 @@ class Model(metaclass=ModelBase):
644
589
  ) -> None:
645
590
  # Ensure that a model instance without a PK hasn't been assigned to
646
591
  # a ForeignKey on this model. If the field is nullable, allowing the save would result in silent data loss.
647
- for field in self._meta.concrete_fields:
592
+ for field in self._model_meta.concrete_fields:
648
593
  if fields and field not in fields:
649
594
  continue
650
595
  # If the related field isn't cached, then an instance hasn't been
@@ -681,7 +626,7 @@ class Model(metaclass=ModelBase):
681
626
  def delete(self) -> tuple[int, dict[str, int]]:
682
627
  if self.id is None:
683
628
  raise ValueError(
684
- f"{self._meta.object_name} object can't be deleted because its id attribute is set "
629
+ f"{self.model_options.object_name} object can't be deleted because its id attribute is set "
685
630
  "to None."
686
631
  )
687
632
  collector = Collector(origin=self)
@@ -691,7 +636,7 @@ class Model(metaclass=ModelBase):
691
636
  def get_field_display(self, field_name: str) -> str:
692
637
  """Get the display value for a field, especially useful for fields with choices."""
693
638
  # Get the field object from the field name
694
- field = self._meta.get_field(field_name)
639
+ field = self._model_meta.get_field(field_name)
695
640
  value = getattr(self, field.attname)
696
641
 
697
642
  # If field has no choices, just return the value as string
@@ -705,11 +650,11 @@ class Model(metaclass=ModelBase):
705
650
  )
706
651
 
707
652
  def _get_field_value_map(
708
- self, meta: Options | None, exclude: set[str] | None = None
653
+ self, meta: Meta | None, exclude: set[str] | None = None
709
654
  ) -> dict[str, Value]:
710
655
  if exclude is None:
711
656
  exclude = set()
712
- meta = meta or self._meta
657
+ meta = meta or self._model_meta
713
658
  return {
714
659
  field.name: Value(getattr(self, field.attname), field)
715
660
  for field in meta.local_concrete_fields
@@ -759,7 +704,7 @@ class Model(metaclass=ModelBase):
759
704
  # Gather a list of checks for fields declared as unique and add them to
760
705
  # the list of checks.
761
706
 
762
- fields_with_class = [(self.__class__, self._meta.local_fields)]
707
+ fields_with_class = [(self.__class__, self._model_meta.local_fields)]
763
708
 
764
709
  for model_class, fields in fields_with_class:
765
710
  for f in fields:
@@ -782,7 +727,7 @@ class Model(metaclass=ModelBase):
782
727
 
783
728
  lookup_kwargs = {}
784
729
  for field_name in unique_check:
785
- f = self._meta.get_field(field_name)
730
+ f = self._model_meta.get_field(field_name)
786
731
  lookup_value = getattr(self, f.attname)
787
732
  # TODO: Handle multiple backends with different feature flags.
788
733
  if lookup_value is None:
@@ -818,19 +763,19 @@ class Model(metaclass=ModelBase):
818
763
  return errors
819
764
 
820
765
  def unique_error_message(
821
- self, model_class: type, unique_check: tuple[str, ...]
766
+ self, model_class: type[Model], unique_check: tuple[str, ...]
822
767
  ) -> ValidationError:
823
- opts = model_class._meta # type: ignore[attr-defined]
768
+ meta = model_class._model_meta
824
769
 
825
770
  params = {
826
771
  "model": self,
827
772
  "model_class": model_class,
828
- "model_name": opts.model_name,
773
+ "model_name": model_class.model_options.model_name,
829
774
  "unique_check": unique_check,
830
775
  }
831
776
 
832
777
  if len(unique_check) == 1:
833
- field = opts.get_field(unique_check[0])
778
+ field = meta.get_field(unique_check[0])
834
779
  params["field_label"] = field.name
835
780
  return ValidationError(
836
781
  message=field.error_messages["unique"],
@@ -838,7 +783,7 @@ class Model(metaclass=ModelBase):
838
783
  params=params,
839
784
  )
840
785
  else:
841
- field_names = [opts.get_field(f).name for f in unique_check]
786
+ field_names = [meta.get_field(f).name for f in unique_check]
842
787
 
843
788
  # Put an "and" before the last one
844
789
  field_names[-1] = f"and {field_names[-1]}"
@@ -851,7 +796,7 @@ class Model(metaclass=ModelBase):
851
796
  params["field_label"] = " ".join(field_names)
852
797
 
853
798
  # Use the first field as the message format...
854
- message = opts.get_field(unique_check[0]).error_messages["unique"]
799
+ message = meta.get_field(unique_check[0]).error_messages["unique"]
855
800
 
856
801
  return ValidationError(
857
802
  message=message,
@@ -860,7 +805,7 @@ class Model(metaclass=ModelBase):
860
805
  )
861
806
 
862
807
  def get_constraints(self) -> list[tuple[type, list[Any]]]:
863
- constraints = [(self.__class__, self._meta.constraints)]
808
+ constraints = [(self.__class__, self.model_options.constraints)]
864
809
  return constraints
865
810
 
866
811
  def validate_constraints(self, exclude: set[str] | None = None) -> None:
@@ -944,7 +889,7 @@ class Model(metaclass=ModelBase):
944
889
  exclude = set()
945
890
 
946
891
  errors = {}
947
- for f in self._meta.fields:
892
+ for f in self._model_meta.fields:
948
893
  if f.name in exclude:
949
894
  continue
950
895
  # Skip validation for empty fields with required=False. The developer
@@ -992,12 +937,12 @@ class Model(metaclass=ModelBase):
992
937
 
993
938
  @classmethod
994
939
  def _check_db_table_comment(cls) -> list[PreflightResult]:
995
- if not cls._meta.db_table_comment:
940
+ if not cls.model_options.db_table_comment:
996
941
  return []
997
942
  errors = []
998
943
  if not (
999
944
  db_connection.features.supports_comments
1000
- or "supports_comments" in cls._meta.required_db_features
945
+ or "supports_comments" in cls.model_options.required_db_features
1001
946
  ):
1002
947
  errors.append(
1003
948
  PreflightResult(
@@ -1014,9 +959,9 @@ class Model(metaclass=ModelBase):
1014
959
  def _check_fields(cls) -> list[PreflightResult]:
1015
960
  """Perform all field checks."""
1016
961
  errors = []
1017
- for field in cls._meta.local_fields:
962
+ for field in cls._model_meta.local_fields:
1018
963
  errors.extend(field.preflight(from_model=cls))
1019
- for field in cls._meta.local_many_to_many:
964
+ for field in cls._model_meta.local_many_to_many:
1020
965
  errors.extend(field.preflight(from_model=cls))
1021
966
  return errors
1022
967
 
@@ -1027,7 +972,7 @@ class Model(metaclass=ModelBase):
1027
972
  errors = []
1028
973
  seen_intermediary_signatures = []
1029
974
 
1030
- fields = cls._meta.local_many_to_many
975
+ fields = cls._model_meta.local_many_to_many
1031
976
 
1032
977
  # Skip when the target model wasn't found.
1033
978
  fields = (f for f in fields if isinstance(f.remote_field.model, ModelBase))
@@ -1046,7 +991,7 @@ class Model(metaclass=ModelBase):
1046
991
  errors.append(
1047
992
  PreflightResult(
1048
993
  fix="The model has two identical many-to-many relations "
1049
- f"through the intermediate model '{f.remote_field.through._meta.label}'.",
994
+ f"through the intermediate model '{f.remote_field.through.model_options.label}'.",
1050
995
  obj=cls,
1051
996
  id="models.duplicate_many_to_many_relations",
1052
997
  )
@@ -1059,7 +1004,9 @@ class Model(metaclass=ModelBase):
1059
1004
  def _check_id_field(cls) -> list[PreflightResult]:
1060
1005
  """Disallow user-defined fields named ``id``."""
1061
1006
  if any(
1062
- f for f in cls._meta.local_fields if f.name == "id" and not f.auto_created
1007
+ f
1008
+ for f in cls._model_meta.local_fields
1009
+ if f.name == "id" and not f.auto_created
1063
1010
  ):
1064
1011
  return [
1065
1012
  PreflightResult(
@@ -1076,7 +1023,7 @@ class Model(metaclass=ModelBase):
1076
1023
  errors = []
1077
1024
  used_fields = {} # name or attname -> field
1078
1025
 
1079
- for f in cls._meta.local_fields:
1026
+ for f in cls._model_meta.local_fields:
1080
1027
  clash = used_fields.get(f.name) or used_fields.get(f.attname) or None
1081
1028
  # Note that we may detect clash between user-defined non-unique
1082
1029
  # field "id" and automatically added unique field "id", both
@@ -1089,7 +1036,7 @@ class Model(metaclass=ModelBase):
1089
1036
  errors.append(
1090
1037
  PreflightResult(
1091
1038
  fix=f"The field '{f.name}' clashes with the field '{clash.name}' "
1092
- f"from model '{clash.model._meta}'.",
1039
+ f"from model '{clash.model.model_options}'.",
1093
1040
  obj=f,
1094
1041
  id="models.field_name_clash",
1095
1042
  )
@@ -1105,7 +1052,7 @@ class Model(metaclass=ModelBase):
1105
1052
  used_column_names = []
1106
1053
  errors = []
1107
1054
 
1108
- for f in cls._meta.local_fields:
1055
+ for f in cls._model_meta.local_fields:
1109
1056
  _, column_name = f.get_attname_column()
1110
1057
 
1111
1058
  # Ensure the column name is not already in use.
@@ -1152,10 +1099,10 @@ class Model(metaclass=ModelBase):
1152
1099
  cls,
1153
1100
  ) -> list[PreflightResult]:
1154
1101
  errors = []
1155
- property_names = cls._meta._property_names
1102
+ property_names = cls._model_meta._property_names
1156
1103
  related_field_accessors = (
1157
1104
  f.get_attname()
1158
- for f in cls._meta._get_fields(reverse=False)
1105
+ for f in cls._model_meta._get_fields(reverse=False)
1159
1106
  if f.is_relation and f.related_model is not None
1160
1107
  )
1161
1108
  for accessor in related_field_accessors:
@@ -1173,7 +1120,7 @@ class Model(metaclass=ModelBase):
1173
1120
  @classmethod
1174
1121
  def _check_single_primary_key(cls) -> list[PreflightResult]:
1175
1122
  errors = []
1176
- if sum(1 for f in cls._meta.local_fields if f.primary_key) > 1:
1123
+ if sum(1 for f in cls._model_meta.local_fields if f.primary_key) > 1:
1177
1124
  errors.append(
1178
1125
  PreflightResult(
1179
1126
  fix="The model cannot have more than one field with "
@@ -1189,7 +1136,7 @@ class Model(metaclass=ModelBase):
1189
1136
  """Check fields, names, and conditions of indexes."""
1190
1137
  errors = []
1191
1138
  references = set()
1192
- for index in cls._meta.indexes:
1139
+ for index in cls.model_options.indexes:
1193
1140
  # Index name can't start with an underscore or a number, restricted
1194
1141
  # for cross-database compatibility with Oracle.
1195
1142
  if index.name[0] == "_" or index.name[0].isdigit():
@@ -1217,8 +1164,8 @@ class Model(metaclass=ModelBase):
1217
1164
  )
1218
1165
  if not (
1219
1166
  db_connection.features.supports_partial_indexes
1220
- or "supports_partial_indexes" in cls._meta.required_db_features
1221
- ) and any(index.condition is not None for index in cls._meta.indexes):
1167
+ or "supports_partial_indexes" in cls.model_options.required_db_features
1168
+ ) and any(index.condition is not None for index in cls.model_options.indexes):
1222
1169
  errors.append(
1223
1170
  PreflightResult(
1224
1171
  fix=f"{db_connection.display_name} does not support indexes with conditions. "
@@ -1231,8 +1178,8 @@ class Model(metaclass=ModelBase):
1231
1178
  )
1232
1179
  if not (
1233
1180
  db_connection.features.supports_covering_indexes
1234
- or "supports_covering_indexes" in cls._meta.required_db_features
1235
- ) and any(index.include for index in cls._meta.indexes):
1181
+ or "supports_covering_indexes" in cls.model_options.required_db_features
1182
+ ) and any(index.include for index in cls.model_options.indexes):
1236
1183
  errors.append(
1237
1184
  PreflightResult(
1238
1185
  fix=f"{db_connection.display_name} does not support indexes with non-key columns. "
@@ -1245,8 +1192,8 @@ class Model(metaclass=ModelBase):
1245
1192
  )
1246
1193
  if not (
1247
1194
  db_connection.features.supports_expression_indexes
1248
- or "supports_expression_indexes" in cls._meta.required_db_features
1249
- ) and any(index.contains_expressions for index in cls._meta.indexes):
1195
+ or "supports_expression_indexes" in cls.model_options.required_db_features
1196
+ ) and any(index.contains_expressions for index in cls.model_options.indexes):
1250
1197
  errors.append(
1251
1198
  PreflightResult(
1252
1199
  fix=f"{db_connection.display_name} does not support indexes on expressions. "
@@ -1258,9 +1205,13 @@ class Model(metaclass=ModelBase):
1258
1205
  )
1259
1206
  )
1260
1207
  fields = [
1261
- field for index in cls._meta.indexes for field, _ in index.fields_orders
1208
+ field
1209
+ for index in cls.model_options.indexes
1210
+ for field, _ in index.fields_orders
1211
+ ]
1212
+ fields += [
1213
+ include for index in cls.model_options.indexes for include in index.include
1262
1214
  ]
1263
- fields += [include for index in cls._meta.indexes for include in index.include]
1264
1215
  fields += references
1265
1216
  errors.extend(cls._check_local_fields(fields, "indexes"))
1266
1217
  return errors
@@ -1274,7 +1225,7 @@ class Model(metaclass=ModelBase):
1274
1225
  # In order to avoid hitting the relation tree prematurely, we use our
1275
1226
  # own fields_map instead of using get_field()
1276
1227
  forward_fields_map = {}
1277
- for field in cls._meta._get_fields(reverse=False):
1228
+ for field in cls._model_meta._get_fields(reverse=False):
1278
1229
  forward_fields_map[field.name] = field
1279
1230
  if hasattr(field, "attname"):
1280
1231
  forward_fields_map[field.attname] = field
@@ -1301,11 +1252,11 @@ class Model(metaclass=ModelBase):
1301
1252
  id="models.m2m_field_in_meta_option",
1302
1253
  )
1303
1254
  )
1304
- elif field not in cls._meta.local_fields:
1255
+ elif field not in cls._model_meta.local_fields:
1305
1256
  errors.append(
1306
1257
  PreflightResult(
1307
1258
  fix=f"'{option}' refers to field '{field_name}' which is not local to model "
1308
- f"'{cls._meta.object_name}'. This issue may be caused by multi-table inheritance.",
1259
+ f"'{cls.model_options.object_name}'. This issue may be caused by multi-table inheritance.",
1309
1260
  obj=cls,
1310
1261
  id="models.non_local_field_reference",
1311
1262
  )
@@ -1319,10 +1270,10 @@ class Model(metaclass=ModelBase):
1319
1270
  exist?
1320
1271
  """
1321
1272
 
1322
- if not cls._meta.ordering:
1273
+ if not cls.model_options.ordering:
1323
1274
  return []
1324
1275
 
1325
- if not isinstance(cls._meta.ordering, list | tuple):
1276
+ if not isinstance(cls.model_options.ordering, list | tuple):
1326
1277
  return [
1327
1278
  PreflightResult(
1328
1279
  fix="'ordering' must be a tuple or list (even if you want to order by "
@@ -1333,7 +1284,7 @@ class Model(metaclass=ModelBase):
1333
1284
  ]
1334
1285
 
1335
1286
  errors = []
1336
- fields = cls._meta.ordering
1287
+ fields = cls.model_options.ordering
1337
1288
 
1338
1289
  # Skip expressions and '?' fields.
1339
1290
  fields = (f for f in fields if isinstance(f, str) and f != "?")
@@ -1357,9 +1308,9 @@ class Model(metaclass=ModelBase):
1357
1308
  fld = None
1358
1309
  for part in field.split(LOOKUP_SEP):
1359
1310
  try:
1360
- fld = _cls._meta.get_field(part)
1311
+ fld = _cls._model_meta.get_field(part)
1361
1312
  if fld.is_relation:
1362
- _cls = fld.path_infos[-1].to_opts.model
1313
+ _cls = fld.path_infos[-1].to_meta.model
1363
1314
  else:
1364
1315
  _cls = None
1365
1316
  except (FieldDoesNotExist, AttributeError):
@@ -1380,13 +1331,13 @@ class Model(metaclass=ModelBase):
1380
1331
 
1381
1332
  # Any field name that is not present in field_names does not exist.
1382
1333
  # Also, ordering by m2m fields is not allowed.
1383
- opts = cls._meta
1334
+ meta = cls._model_meta
1384
1335
  valid_fields = set(
1385
1336
  chain.from_iterable(
1386
1337
  (f.name, f.attname)
1387
1338
  if not (f.auto_created and not f.concrete)
1388
1339
  else (f.field.related_query_name(),)
1389
- for f in chain(opts.fields, opts.related_objects)
1340
+ for f in chain(meta.fields, meta.related_objects)
1390
1341
  )
1391
1342
  )
1392
1343
 
@@ -1419,7 +1370,7 @@ class Model(metaclass=ModelBase):
1419
1370
  if allowed_len is None:
1420
1371
  return errors
1421
1372
 
1422
- for f in cls._meta.local_fields:
1373
+ for f in cls._model_meta.local_fields:
1423
1374
  _, column_name = f.get_attname_column()
1424
1375
 
1425
1376
  # Check if auto-generated name for the field is too long
@@ -1439,14 +1390,14 @@ class Model(metaclass=ModelBase):
1439
1390
  )
1440
1391
  )
1441
1392
 
1442
- for f in cls._meta.local_many_to_many:
1393
+ for f in cls._model_meta.local_many_to_many:
1443
1394
  # Skip nonexistent models.
1444
1395
  if isinstance(f.remote_field.through, str):
1445
1396
  continue
1446
1397
 
1447
1398
  # Check if auto-generated name for the M2M field is too long
1448
1399
  # for the database.
1449
- for m2m in f.remote_field.through._meta.local_fields:
1400
+ for m2m in f.remote_field.through._model_meta.local_fields:
1450
1401
  _, rel_name = m2m.get_attname_column()
1451
1402
  if (
1452
1403
  m2m.db_column is None
@@ -1487,10 +1438,11 @@ class Model(metaclass=ModelBase):
1487
1438
  errors = []
1488
1439
  if not (
1489
1440
  db_connection.features.supports_table_check_constraints
1490
- or "supports_table_check_constraints" in cls._meta.required_db_features
1441
+ or "supports_table_check_constraints"
1442
+ in cls.model_options.required_db_features
1491
1443
  ) and any(
1492
1444
  isinstance(constraint, CheckConstraint)
1493
- for constraint in cls._meta.constraints
1445
+ for constraint in cls.model_options.constraints
1494
1446
  ):
1495
1447
  errors.append(
1496
1448
  PreflightResult(
@@ -1505,11 +1457,11 @@ class Model(metaclass=ModelBase):
1505
1457
 
1506
1458
  if not (
1507
1459
  db_connection.features.supports_partial_indexes
1508
- or "supports_partial_indexes" in cls._meta.required_db_features
1460
+ or "supports_partial_indexes" in cls.model_options.required_db_features
1509
1461
  ) and any(
1510
1462
  isinstance(constraint, UniqueConstraint)
1511
1463
  and constraint.condition is not None
1512
- for constraint in cls._meta.constraints
1464
+ for constraint in cls.model_options.constraints
1513
1465
  ):
1514
1466
  errors.append(
1515
1467
  PreflightResult(
@@ -1525,11 +1477,11 @@ class Model(metaclass=ModelBase):
1525
1477
  if not (
1526
1478
  db_connection.features.supports_deferrable_unique_constraints
1527
1479
  or "supports_deferrable_unique_constraints"
1528
- in cls._meta.required_db_features
1480
+ in cls.model_options.required_db_features
1529
1481
  ) and any(
1530
1482
  isinstance(constraint, UniqueConstraint)
1531
1483
  and constraint.deferrable is not None
1532
- for constraint in cls._meta.constraints
1484
+ for constraint in cls.model_options.constraints
1533
1485
  ):
1534
1486
  errors.append(
1535
1487
  PreflightResult(
@@ -1544,10 +1496,10 @@ class Model(metaclass=ModelBase):
1544
1496
 
1545
1497
  if not (
1546
1498
  db_connection.features.supports_covering_indexes
1547
- or "supports_covering_indexes" in cls._meta.required_db_features
1499
+ or "supports_covering_indexes" in cls.model_options.required_db_features
1548
1500
  ) and any(
1549
1501
  isinstance(constraint, UniqueConstraint) and constraint.include
1550
- for constraint in cls._meta.constraints
1502
+ for constraint in cls.model_options.constraints
1551
1503
  ):
1552
1504
  errors.append(
1553
1505
  PreflightResult(
@@ -1562,10 +1514,10 @@ class Model(metaclass=ModelBase):
1562
1514
 
1563
1515
  if not (
1564
1516
  db_connection.features.supports_expression_indexes
1565
- or "supports_expression_indexes" in cls._meta.required_db_features
1517
+ or "supports_expression_indexes" in cls.model_options.required_db_features
1566
1518
  ) and any(
1567
1519
  isinstance(constraint, UniqueConstraint) and constraint.contains_expressions
1568
- for constraint in cls._meta.constraints
1520
+ for constraint in cls.model_options.constraints
1569
1521
  ):
1570
1522
  errors.append(
1571
1523
  PreflightResult(
@@ -1580,22 +1532,23 @@ class Model(metaclass=ModelBase):
1580
1532
  fields = set(
1581
1533
  chain.from_iterable(
1582
1534
  (*constraint.fields, *constraint.include)
1583
- for constraint in cls._meta.constraints
1535
+ for constraint in cls.model_options.constraints
1584
1536
  if isinstance(constraint, UniqueConstraint)
1585
1537
  )
1586
1538
  )
1587
1539
  references = set()
1588
- for constraint in cls._meta.constraints:
1540
+ for constraint in cls.model_options.constraints:
1589
1541
  if isinstance(constraint, UniqueConstraint):
1590
1542
  if (
1591
1543
  db_connection.features.supports_partial_indexes
1592
- or "supports_partial_indexes" not in cls._meta.required_db_features
1544
+ or "supports_partial_indexes"
1545
+ not in cls.model_options.required_db_features
1593
1546
  ) and isinstance(constraint.condition, Q):
1594
1547
  references.update(cls._get_expr_references(constraint.condition))
1595
1548
  if (
1596
1549
  db_connection.features.supports_expression_indexes
1597
1550
  or "supports_expression_indexes"
1598
- not in cls._meta.required_db_features
1551
+ not in cls.model_options.required_db_features
1599
1552
  ) and constraint.contains_expressions:
1600
1553
  for expression in constraint.expressions:
1601
1554
  references.update(cls._get_expr_references(expression))
@@ -1603,7 +1556,7 @@ class Model(metaclass=ModelBase):
1603
1556
  if (
1604
1557
  db_connection.features.supports_table_check_constraints
1605
1558
  or "supports_table_check_constraints"
1606
- not in cls._meta.required_db_features
1559
+ not in cls.model_options.required_db_features
1607
1560
  ):
1608
1561
  if isinstance(constraint.check, Q):
1609
1562
  references.update(cls._get_expr_references(constraint.check))
@@ -1627,7 +1580,7 @@ class Model(metaclass=ModelBase):
1627
1580
  # If it has no lookups it cannot result in a JOIN.
1628
1581
  continue
1629
1582
  try:
1630
- field = cls._meta.get_field(field_name)
1583
+ field = cls._model_meta.get_field(field_name)
1631
1584
  if not field.is_relation or field.many_to_many or field.one_to_many:
1632
1585
  continue
1633
1586
  except FieldDoesNotExist: