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
@@ -62,7 +62,7 @@ def resolve_relation(
62
62
  # Look for an "app.Model" relation
63
63
  if isinstance(relation, str):
64
64
  if "." not in relation:
65
- relation = f"{scope_model._meta.package_label}.{relation}" # type: ignore[attr-defined]
65
+ relation = f"{scope_model.model_options.package_label}.{relation}"
66
66
 
67
67
  return relation
68
68
 
@@ -82,11 +82,11 @@ def lazy_related_operation(
82
82
  references will be resolved relative to `model`.
83
83
 
84
84
  This is a convenience wrapper for `Packages.lazy_model_operation` - the app
85
- registry model used is the one found in `model._meta.models_registry`.
85
+ registry model used is the one found in `model._model_meta.models_registry`.
86
86
  """
87
87
  models = [model] + [resolve_relation(model, rel) for rel in related_models]
88
88
  model_keys = (make_model_tuple(m) for m in models)
89
- models_registry = model._meta.models_registry # type: ignore[attr-defined]
89
+ models_registry = model._model_meta.models_registry
90
90
  return models_registry.lazy_model_operation(
91
91
  partial(function, **kwargs), *model_keys
92
92
  )
@@ -140,7 +140,7 @@ class RelatedField(FieldCacheMixin, Field):
140
140
  if not is_valid_id:
141
141
  return [
142
142
  PreflightResult(
143
- fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model._meta.object_name}.{self.name}. Related name must be a valid Python identifier.", # type: ignore[attr-defined]
143
+ fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model.model_options.object_name}.{self.name}. Related name must be a valid Python identifier.",
144
144
  obj=self,
145
145
  id="fields.invalid_related_name",
146
146
  )
@@ -180,13 +180,13 @@ class RelatedField(FieldCacheMixin, Field):
180
180
 
181
181
  def _check_relation_model_exists(self) -> list[PreflightResult]:
182
182
  rel_is_missing = (
183
- self.remote_field.model not in self.opts.models_registry.get_models() # type: ignore[attr-defined]
183
+ self.remote_field.model not in self.meta.models_registry.get_models() # type: ignore[attr-defined]
184
184
  )
185
185
  rel_is_string = isinstance(self.remote_field.model, str) # type: ignore[attr-defined]
186
186
  model_name = (
187
187
  self.remote_field.model # type: ignore[attr-defined]
188
188
  if rel_is_string
189
- else self.remote_field.model._meta.object_name # type: ignore[attr-defined]
189
+ else self.remote_field.model.model_options.object_name
190
190
  )
191
191
  if rel_is_missing and rel_is_string:
192
192
  return [
@@ -206,7 +206,6 @@ class RelatedField(FieldCacheMixin, Field):
206
206
  from plain.models.base import ModelBase
207
207
 
208
208
  errors: list[PreflightResult] = []
209
- opts = self.model._meta # type: ignore[attr-defined]
210
209
 
211
210
  # f.remote_field.model may be a string instead of a model. Skip if
212
211
  # model name is not resolved.
@@ -224,8 +223,9 @@ class RelatedField(FieldCacheMixin, Field):
224
223
  # foreign = models.ForeignKey(Target)
225
224
  # m2m = models.ManyToManyField(Target)
226
225
 
227
- # rel_opts.object_name == "Target"
228
- rel_opts = self.remote_field.model._meta # type: ignore[attr-defined]
226
+ # rel_options.object_name == "Target"
227
+ rel_meta = self.remote_field.model._model_meta
228
+ rel_options = self.remote_field.model.model_options
229
229
  # If the field doesn't install a backward relation on the target model
230
230
  # (so `is_hidden` returns True), then there are no clashes to check
231
231
  # and we can skip these fields.
@@ -235,20 +235,20 @@ class RelatedField(FieldCacheMixin, Field):
235
235
  ) # i. e. "model_set" # type: ignore[attr-defined]
236
236
  rel_query_name = self.related_query_name() # i. e. "model"
237
237
  # i.e. "package_label.Model.field".
238
- field_name = f"{opts.label}.{self.name}"
238
+ field_name = f"{self.model.model_options.label}.{self.name}"
239
239
 
240
240
  # Check clashes between accessor or reverse query name of `field`
241
241
  # and any other field name -- i.e. accessor for Model.foreign is
242
242
  # model_set and it clashes with Target.model_set.
243
- potential_clashes = rel_opts.fields + rel_opts.many_to_many
243
+ potential_clashes = rel_meta.fields + rel_meta.many_to_many
244
244
  for clash_field in potential_clashes:
245
245
  # i.e. "package_label.Target.model_set".
246
- clash_name = f"{rel_opts.label}.{clash_field.name}"
246
+ clash_name = f"{rel_options.label}.{clash_field.name}"
247
247
  if not rel_is_hidden and clash_field.name == rel_name:
248
248
  errors.append(
249
249
  PreflightResult(
250
250
  fix=(
251
- f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
251
+ f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
252
252
  f"for '{field_name}' clashes with field name "
253
253
  f"'{clash_name}'. "
254
254
  f"Rename field '{clash_name}', or add/change a related_name "
@@ -275,17 +275,15 @@ class RelatedField(FieldCacheMixin, Field):
275
275
  # Check clashes between accessors/reverse query names of `field` and
276
276
  # any other field accessor -- i. e. Model.foreign accessor clashes with
277
277
  # Model.m2m accessor.
278
- potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
278
+ potential_clashes = (r for r in rel_meta.related_objects if r.field is not self)
279
279
  for clash_field in potential_clashes:
280
280
  # i.e. "package_label.Model.m2m".
281
- clash_name = (
282
- f"{clash_field.related_model._meta.label}.{clash_field.field.name}"
283
- )
281
+ clash_name = f"{clash_field.related_model.model_options.label}.{clash_field.field.name}"
284
282
  if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
285
283
  errors.append(
286
284
  PreflightResult(
287
285
  fix=(
288
- f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
286
+ f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
289
287
  f"for '{field_name}' clashes with reverse accessor for "
290
288
  f"'{clash_name}'. "
291
289
  "Add or change a related_name argument "
@@ -320,21 +318,21 @@ class RelatedField(FieldCacheMixin, Field):
320
318
  def contribute_to_class(self, cls: type[Model], name: str) -> None:
321
319
  super().contribute_to_class(cls, name) # type: ignore[misc]
322
320
 
323
- self.opts = cls._meta # type: ignore[attr-defined]
321
+ self.meta = cls._model_meta
324
322
 
325
323
  if self.remote_field.related_name: # type: ignore[attr-defined]
326
324
  related_name = self.remote_field.related_name # type: ignore[attr-defined]
327
325
  related_name %= {
328
326
  "class": cls.__name__.lower(),
329
- "model_name": cls._meta.model_name.lower(), # type: ignore[union-attr]
330
- "package_label": cls._meta.package_label.lower(), # type: ignore[union-attr]
327
+ "model_name": cls.model_options.model_name.lower(),
328
+ "package_label": cls.model_options.package_label.lower(),
331
329
  }
332
330
  self.remote_field.related_name = related_name # type: ignore[attr-defined]
333
331
 
334
332
  if self.remote_field.related_query_name: # type: ignore[attr-defined]
335
333
  related_query_name = self.remote_field.related_query_name % { # type: ignore[attr-defined]
336
334
  "class": cls.__name__.lower(),
337
- "package_label": cls._meta.package_label.lower(), # type: ignore[union-attr]
335
+ "package_label": cls.model_options.package_label.lower(),
338
336
  }
339
337
  self.remote_field.related_query_name = related_query_name # type: ignore[attr-defined]
340
338
 
@@ -389,7 +387,9 @@ class RelatedField(FieldCacheMixin, Field):
389
387
  )
390
388
 
391
389
  def set_attributes_from_rel(self) -> None:
392
- self.name = self.name or (self.remote_field.model._meta.model_name + "_" + "id") # type: ignore[attr-defined]
390
+ self.name = self.name or (
391
+ self.remote_field.model.model_options.model_name + "_" + "id"
392
+ )
393
393
  self.remote_field.set_field_name() # type: ignore[attr-defined]
394
394
 
395
395
  def do_related_class(self, other: type[Model], cls: type[Model]) -> None:
@@ -415,7 +415,7 @@ class RelatedField(FieldCacheMixin, Field):
415
415
  return (
416
416
  self.remote_field.related_query_name # type: ignore[attr-defined]
417
417
  or self.remote_field.related_name # type: ignore[attr-defined]
418
- or self.opts.model_name # type: ignore[attr-defined]
418
+ or self.model.model_options.model_name # type: ignore[attr-defined]
419
419
  )
420
420
 
421
421
  @property
@@ -469,10 +469,10 @@ class ForeignKey(RelatedField):
469
469
  db_constraint: bool = True,
470
470
  **kwargs: Any,
471
471
  ):
472
- try:
473
- to._meta.model_name # type: ignore[attr-defined]
474
- except AttributeError:
475
- if not isinstance(to, str):
472
+ if not isinstance(to, str):
473
+ try:
474
+ to.model_options.model_name
475
+ except AttributeError:
476
476
  raise TypeError(
477
477
  f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be "
478
478
  f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
@@ -565,12 +565,12 @@ class ForeignKey(RelatedField):
565
565
 
566
566
  def get_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
567
567
  """Get path from this field to the related model."""
568
- opts = self.remote_field.model._meta # type: ignore[attr-defined]
569
- from_opts = self.model._meta # type: ignore[attr-defined]
568
+ meta = self.remote_field.model._model_meta
569
+ from_meta = self.model._model_meta
570
570
  return [
571
571
  PathInfo(
572
- from_opts=from_opts,
573
- to_opts=opts,
572
+ from_meta=from_meta,
573
+ to_meta=meta,
574
574
  target_fields=self.foreign_related_fields,
575
575
  join_field=self,
576
576
  m2m=False,
@@ -585,13 +585,13 @@ class ForeignKey(RelatedField):
585
585
 
586
586
  def get_reverse_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
587
587
  """Get path from the related model to this field's model."""
588
- opts = self.model._meta # type: ignore[attr-defined]
589
- from_opts = self.remote_field.model._meta # type: ignore[attr-defined]
588
+ meta = self.model._model_meta
589
+ from_meta = self.remote_field.model._model_meta
590
590
  return [
591
591
  PathInfo(
592
- from_opts=from_opts,
593
- to_opts=opts,
594
- target_fields=(opts.get_field("id"),),
592
+ from_meta=from_meta,
593
+ to_meta=meta,
594
+ target_fields=(meta.get_field("id"),),
595
595
  join_field=self.remote_field, # type: ignore[attr-defined]
596
596
  m2m=not self.primary_key, # type: ignore[attr-defined]
597
597
  direct=False,
@@ -619,7 +619,7 @@ class ForeignKey(RelatedField):
619
619
  # it along for later - this is too early because it's still
620
620
  # model load time.
621
621
  if self.remote_field.limit_choices_to: # type: ignore[attr-defined]
622
- cls._meta.related_fkey_lookups.append(
622
+ cls._model_meta.related_fkey_lookups.append(
623
623
  self.remote_field.limit_choices_to # type: ignore[attr-defined]
624
624
  )
625
625
 
@@ -669,7 +669,7 @@ class ForeignKey(RelatedField):
669
669
  else:
670
670
  kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
671
671
  else:
672
- kwargs["to"] = self.remote_field.model._meta.label_lower # type: ignore[attr-defined]
672
+ kwargs["to"] = self.remote_field.model.model_options.label_lower
673
673
 
674
674
  if self.db_index is not True:
675
675
  kwargs["db_index"] = self.db_index
@@ -691,7 +691,7 @@ class ForeignKey(RelatedField):
691
691
  if value is None:
692
692
  return None
693
693
 
694
- qs = self.remote_field.model._meta.base_queryset.filter( # type: ignore[attr-defined]
694
+ qs = self.remote_field.model._model_meta.base_queryset.filter(
695
695
  **{self.remote_field.field_name: value} # type: ignore[attr-defined]
696
696
  )
697
697
  qs = qs.complex_filter(self.get_limit_choices_to())
@@ -700,7 +700,7 @@ class ForeignKey(RelatedField):
700
700
  self.error_messages["invalid"], # type: ignore[attr-defined]
701
701
  code="invalid",
702
702
  params={
703
- "model": self.remote_field.model._meta.model_name, # type: ignore[attr-defined]
703
+ "model": self.remote_field.model.model_options.model_name,
704
704
  "id": value,
705
705
  "field": self.remote_field.field_name, # type: ignore[attr-defined]
706
706
  "value": value,
@@ -713,14 +713,14 @@ class ForeignKey(RelatedField):
713
713
  f"Related model {self.remote_field.model!r} cannot be resolved" # type: ignore[attr-defined]
714
714
  )
715
715
  from_field = self
716
- to_field = self.remote_field.model._meta.get_field("id") # type: ignore[attr-defined]
716
+ to_field = self.remote_field.model._model_meta.get_field("id")
717
717
  related_fields: list[tuple[ForeignKey, Field]] = [(from_field, to_field)]
718
718
 
719
719
  for from_field, to_field in related_fields:
720
720
  if to_field and to_field.model != self.remote_field.model: # type: ignore[attr-defined]
721
721
  raise FieldError(
722
- f"'{self.model._meta.label}.{self.name}' refers to field '{to_field.name}' which is not local to model " # type: ignore[attr-defined]
723
- f"'{self.remote_field.model._meta.label}'." # type: ignore[attr-defined]
722
+ f"'{self.model.model_options.label}.{self.name}' refers to field '{to_field.name}' which is not local to model "
723
+ f"'{self.remote_field.model.model_options.label}'."
724
724
  )
725
725
  return related_fields
726
726
 
@@ -823,10 +823,10 @@ class ManyToManyField(RelatedField):
823
823
  symmetrical: bool | None = None,
824
824
  **kwargs: Any,
825
825
  ):
826
- try:
827
- to._meta # type: ignore[attr-defined]
828
- except AttributeError:
829
- if not isinstance(to, str):
826
+ if not isinstance(to, str):
827
+ try:
828
+ to._model_meta
829
+ except AttributeError:
830
830
  raise TypeError(
831
831
  f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ManyToManyField "
832
832
  f"must be either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
@@ -915,14 +915,14 @@ class ManyToManyField(RelatedField):
915
915
  def _check_relationship_model(
916
916
  self, from_model: type[Model] | None = None, **kwargs: Any
917
917
  ) -> list[PreflightResult]:
918
- if hasattr(self.remote_field.through, "_meta"):
919
- qualified_model_name = f"{self.remote_field.through._meta.package_label}.{self.remote_field.through.__name__}"
918
+ if hasattr(self.remote_field.through, "_model_meta"):
919
+ qualified_model_name = f"{self.remote_field.through.model_options.package_label}.{self.remote_field.through.__name__}"
920
920
  else:
921
921
  qualified_model_name = self.remote_field.through
922
922
 
923
923
  errors = []
924
924
 
925
- if self.remote_field.through not in self.opts.models_registry.get_models():
925
+ if self.remote_field.through not in self.meta.models_registry.get_models():
926
926
  # The relationship model is not installed.
927
927
  errors.append(
928
928
  PreflightResult(
@@ -944,18 +944,20 @@ class ManyToManyField(RelatedField):
944
944
  )
945
945
  # Set some useful local variables
946
946
  to_model = resolve_relation(from_model, self.remote_field.model)
947
- from_model_name = from_model._meta.object_name
947
+ from_model_name = from_model.model_options.object_name
948
948
  if isinstance(to_model, str):
949
949
  to_model_name = to_model
950
950
  else:
951
- to_model_name = to_model._meta.object_name
952
- relationship_model_name = self.remote_field.through._meta.object_name
951
+ to_model_name = to_model.model_options.object_name
952
+ relationship_model_name = (
953
+ self.remote_field.through.model_options.object_name
954
+ )
953
955
  self_referential = from_model == to_model
954
956
  # Count foreign keys in intermediate model
955
957
  if self_referential:
956
958
  seen_self = sum(
957
959
  from_model == getattr(field.remote_field, "model", None)
958
- for field in self.remote_field.through._meta.fields
960
+ for field in self.remote_field.through._model_meta.fields
959
961
  )
960
962
 
961
963
  if seen_self > 2 and not self.remote_field.through_fields:
@@ -977,11 +979,11 @@ class ManyToManyField(RelatedField):
977
979
  # Count foreign keys in relationship model
978
980
  seen_from = sum(
979
981
  from_model == getattr(field.remote_field, "model", None)
980
- for field in self.remote_field.through._meta.fields
982
+ for field in self.remote_field.through._model_meta.fields
981
983
  )
982
984
  seen_to = sum(
983
985
  to_model == getattr(field.remote_field, "model", None)
984
- for field in self.remote_field.through._meta.fields
986
+ for field in self.remote_field.through._model_meta.fields
985
987
  )
986
988
 
987
989
  if seen_from > 1 and not self.remote_field.through_fields:
@@ -1078,7 +1080,7 @@ class ManyToManyField(RelatedField):
1078
1080
  (target_field_name, target),
1079
1081
  ):
1080
1082
  possible_field_names = []
1081
- for f in through._meta.fields:
1083
+ for f in through._model_meta.fields:
1082
1084
  if (
1083
1085
  hasattr(f, "remote_field")
1084
1086
  and getattr(f.remote_field, "model", None) == related_model
@@ -1088,7 +1090,7 @@ class ManyToManyField(RelatedField):
1088
1090
  fix = (
1089
1091
  "Did you mean one of the following foreign keys to '{}': "
1090
1092
  "{}?".format(
1091
- related_model._meta.object_name,
1093
+ related_model.model_options.object_name,
1092
1094
  ", ".join(possible_field_names),
1093
1095
  )
1094
1096
  )
@@ -1096,7 +1098,7 @@ class ManyToManyField(RelatedField):
1096
1098
  fix = ""
1097
1099
 
1098
1100
  try:
1099
- field = through._meta.get_field(field_name)
1101
+ field = through._model_meta.get_field(field_name)
1100
1102
  except FieldDoesNotExist:
1101
1103
  errors.append(
1102
1104
  PreflightResult(
@@ -1113,7 +1115,7 @@ class ManyToManyField(RelatedField):
1113
1115
  ):
1114
1116
  errors.append(
1115
1117
  PreflightResult(
1116
- fix=f"'{through._meta.object_name}.{field_name}' is not a foreign key to '{related_model._meta.object_name}'. {fix}",
1118
+ fix=f"'{through.model_options.object_name}.{field_name}' is not a foreign key to '{related_model.model_options.object_name}'. {fix}",
1117
1119
  obj=self,
1118
1120
  id="fields.m2m_through_field_not_fk_to_model",
1119
1121
  )
@@ -1125,15 +1127,15 @@ class ManyToManyField(RelatedField):
1125
1127
  if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
1126
1128
  return []
1127
1129
  registered_tables = {
1128
- model._meta.db_table: model
1129
- for model in self.opts.models_registry.get_models() # type: ignore[attr-defined]
1130
+ model.model_options.db_table: model
1131
+ for model in self.meta.models_registry.get_models() # type: ignore[attr-defined]
1130
1132
  if model != self.remote_field.through # type: ignore[attr-defined]
1131
1133
  }
1132
1134
  m2m_db_table = self.m2m_db_table() # type: ignore[attr-defined]
1133
1135
  model = registered_tables.get(m2m_db_table)
1134
1136
  # Check if there's already a m2m field using the same through model.
1135
1137
  if model and model != self.remote_field.through: # type: ignore[attr-defined]
1136
- clashing_obj = model._meta.label
1138
+ clashing_obj = model.model_options.label
1137
1139
  return [
1138
1140
  PreflightResult(
1139
1141
  fix=(
@@ -1161,12 +1163,12 @@ class ManyToManyField(RelatedField):
1161
1163
  else:
1162
1164
  kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
1163
1165
  else:
1164
- kwargs["to"] = self.remote_field.model._meta.label_lower # type: ignore[attr-defined]
1166
+ kwargs["to"] = self.remote_field.model.model_options.label_lower
1165
1167
 
1166
1168
  if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
1167
1169
  kwargs["through"] = self.remote_field.through # type: ignore[attr-defined]
1168
1170
  else:
1169
- kwargs["through"] = self.remote_field.through._meta.label # type: ignore[attr-defined]
1171
+ kwargs["through"] = self.remote_field.through.model_options.label
1170
1172
 
1171
1173
  return name, path, args, kwargs
1172
1174
 
@@ -1175,8 +1177,8 @@ class ManyToManyField(RelatedField):
1175
1177
  ) -> list[PathInfo]:
1176
1178
  """Called by both direct and indirect m2m traversal."""
1177
1179
  int_model = self.remote_field.through # type: ignore[attr-defined]
1178
- linkfield1 = int_model._meta.get_field(self.m2m_field_name()) # type: ignore[attr-defined]
1179
- linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name()) # type: ignore[attr-defined]
1180
+ linkfield1 = int_model._model_meta.get_field(self.m2m_field_name())
1181
+ linkfield2 = int_model._model_meta.get_field(self.m2m_reverse_field_name())
1180
1182
  if direct:
1181
1183
  join1infos = linkfield1.reverse_path_infos # type: ignore[attr-defined]
1182
1184
  if filtered_relation:
@@ -1211,7 +1213,7 @@ class ManyToManyField(RelatedField):
1211
1213
  Function that can be curried to provide the m2m table name for this
1212
1214
  relation.
1213
1215
  """
1214
- return self.remote_field.through._meta.db_table # type: ignore[attr-defined]
1216
+ return self.remote_field.through.model_options.db_table
1215
1217
 
1216
1218
  def _get_m2m_attr(self, related: Any, attr: str) -> Any:
1217
1219
  """
@@ -1225,7 +1227,7 @@ class ManyToManyField(RelatedField):
1225
1227
  link_field_name: str | None = self.remote_field.through_fields[0] # type: ignore[attr-defined]
1226
1228
  else:
1227
1229
  link_field_name = None
1228
- for f in self.remote_field.through._meta.fields: # type: ignore[attr-defined]
1230
+ for f in self.remote_field.through._model_meta.fields:
1229
1231
  if (
1230
1232
  f.is_relation # type: ignore[attr-defined]
1231
1233
  and f.remote_field.model == related.related_model # type: ignore[attr-defined]
@@ -1248,7 +1250,7 @@ class ManyToManyField(RelatedField):
1248
1250
  link_field_name: str | None = self.remote_field.through_fields[1] # type: ignore[attr-defined]
1249
1251
  else:
1250
1252
  link_field_name = None
1251
- for f in self.remote_field.through._meta.fields: # type: ignore[attr-defined]
1253
+ for f in self.remote_field.through._model_meta.fields:
1252
1254
  if f.is_relation and f.remote_field.model == related.model: # type: ignore[attr-defined]
1253
1255
  if link_field_name is None and related.related_model == related.model:
1254
1256
  # If this is an m2m-intermediate to self,
@@ -104,7 +104,7 @@ class ForwardManyToOneDescriptor:
104
104
  return self.field.is_cached(instance)
105
105
 
106
106
  def get_queryset(self) -> QuerySet:
107
- qs = self.field.remote_field.model._meta.base_queryset
107
+ qs = self.field.remote_field.model._model_meta.base_queryset
108
108
  return qs.all()
109
109
 
110
110
  def get_prefetch_queryset(
@@ -217,7 +217,7 @@ class ForwardManyToOneDescriptor:
217
217
  # An object must be an instance of the related class.
218
218
  if value is not None and not isinstance(value, self.field.remote_field.model):
219
219
  raise ValueError(
220
- f'Cannot assign "{value!r}": "{instance._meta.object_name}.{self.field.name}" must be a "{self.field.remote_field.model._meta.object_name}" instance.'
220
+ f'Cannot assign "{value!r}": "{instance.model_options.object_name}.{self.field.name}" must be a "{self.field.remote_field.model.model_options.object_name}" instance.'
221
221
  )
222
222
  remote_field = self.field.remote_field
223
223
  # If we're setting the value of a OneToOneField to None, we need to clear
@@ -55,7 +55,7 @@ def get_normalized_value(value: Any, lhs: Any) -> tuple[Any, ...]:
55
55
  sources = lhs.output_field.path_infos[-1].target_fields
56
56
  for source in sources:
57
57
  while not isinstance(value, source.model) and source.remote_field:
58
- source = source.remote_field.model._meta.get_field(
58
+ source = source.remote_field.model._model_meta.get_field(
59
59
  source.remote_field.field_name
60
60
  )
61
61
  try:
@@ -70,8 +70,6 @@ class ReverseManyToOneManager(BaseRelatedManager):
70
70
  self.instance = instance
71
71
  self.field = rel.field
72
72
  self.core_filters = {self.field.name: instance}
73
- # Store the base queryset class for this model
74
- self.base_queryset_class = rel.related_model._meta.queryset.__class__
75
73
  self.allow_null = rel.field.allow_null
76
74
 
77
75
  def _check_fk_val(self) -> None:
@@ -137,15 +135,14 @@ class ReverseManyToOneManager(BaseRelatedManager):
137
135
  self.field.remote_field.get_cache_name()
138
136
  ]
139
137
  except (AttributeError, KeyError):
140
- # Use the base queryset class for this model
141
- queryset = self.base_queryset_class(model=self.model)
138
+ queryset = self.model.query
142
139
  return self._apply_rel_filters(queryset)
143
140
 
144
141
  def get_prefetch_queryset(
145
142
  self, instances: Any, queryset: QuerySet | None = None
146
143
  ) -> tuple[QuerySet, Any, Any, bool, str, bool]:
147
144
  if queryset is None:
148
- queryset = self.base_queryset_class(model=self.model)
145
+ queryset = self.model.query
149
146
 
150
147
  rel_obj_attr = self.field.get_local_related_value
151
148
  instance_attr = self.field.get_foreign_related_value
@@ -168,7 +165,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
168
165
  def check_and_update_obj(obj: Any) -> None:
169
166
  if not isinstance(obj, self.model):
170
167
  raise TypeError(
171
- f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
168
+ f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
172
169
  )
173
170
  setattr(obj, self.field.name, self.instance)
174
171
 
@@ -182,7 +179,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
182
179
  "the object first."
183
180
  )
184
181
  ids.append(obj.id)
185
- self.model._meta.base_queryset.filter(id__in=ids).update(
182
+ self.model._model_meta.base_queryset.filter(id__in=ids).update(
186
183
  **{
187
184
  self.field.name: self.instance,
188
185
  }
@@ -196,17 +193,17 @@ class ReverseManyToOneManager(BaseRelatedManager):
196
193
  def create(self, **kwargs: Any) -> Any:
197
194
  self._check_fk_val()
198
195
  kwargs[self.field.name] = self.instance
199
- return self.base_queryset_class(model=self.model).create(**kwargs)
196
+ return self.model.query.create(**kwargs)
200
197
 
201
198
  def get_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
202
199
  self._check_fk_val()
203
200
  kwargs[self.field.name] = self.instance
204
- return self.base_queryset_class(model=self.model).get_or_create(**kwargs)
201
+ return self.model.query.get_or_create(**kwargs)
205
202
 
206
203
  def update_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
207
204
  self._check_fk_val()
208
205
  kwargs[self.field.name] = self.instance
209
- return self.base_queryset_class(model=self.model).update_or_create(**kwargs)
206
+ return self.model.query.update_or_create(**kwargs)
210
207
 
211
208
  def remove(self, *objs: Any, bulk: bool = True) -> None:
212
209
  # remove() is only provided if the ForeignKey can have a value of null
@@ -223,7 +220,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
223
220
  for obj in objs:
224
221
  if not isinstance(obj, self.model):
225
222
  raise TypeError(
226
- f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
223
+ f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
227
224
  )
228
225
  # Is obj actually part of this descriptor set?
229
226
  if self.field.get_local_related_value(obj) == val:
@@ -297,11 +294,9 @@ class BaseManyToManyManager(BaseRelatedManager):
297
294
  def __init__(self, instance: Any, rel: Any):
298
295
  self.instance = instance
299
296
  self.through = rel.through
300
- # Subclasses must set model before calling super().__init__
301
- self.base_queryset_class = self.model._meta.queryset.__class__
302
297
 
303
- self.source_field = self.through._meta.get_field(self.source_field_name)
304
- self.target_field = self.through._meta.get_field(self.target_field_name)
298
+ self.source_field = self.through._model_meta.get_field(self.source_field_name)
299
+ self.target_field = self.through._model_meta.get_field(self.target_field_name)
305
300
 
306
301
  self.core_filters = {}
307
302
  self.id_field_names = {}
@@ -338,14 +333,14 @@ class BaseManyToManyManager(BaseRelatedManager):
338
333
  try:
339
334
  return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
340
335
  except (AttributeError, KeyError):
341
- queryset = self.base_queryset_class(model=self.model)
336
+ queryset = self.model.query
342
337
  return self._apply_rel_filters(queryset)
343
338
 
344
339
  def get_prefetch_queryset(
345
340
  self, instances: Any, queryset: QuerySet | None = None
346
341
  ) -> tuple[QuerySet, Any, Any, bool, str, bool]:
347
342
  if queryset is None:
348
- queryset = self.base_queryset_class(model=self.model)
343
+ queryset = self.model.query
349
344
 
350
345
  queryset = _filter_prefetch_queryset(
351
346
  queryset._next_is_sticky(), self.query_field_name, instances
@@ -353,8 +348,8 @@ class BaseManyToManyManager(BaseRelatedManager):
353
348
 
354
349
  # M2M: need to annotate the query in order to get the primary model
355
350
  # that the secondary model was actually related to.
356
- fk = self.through._meta.get_field(self.source_field_name)
357
- join_table = fk.model._meta.db_table
351
+ fk = self.through._model_meta.get_field(self.source_field_name)
352
+ join_table = fk.model.model_options.db_table
358
353
  qn = db_connection.ops.quote_name
359
354
  queryset = queryset.extra(
360
355
  select={
@@ -380,9 +375,7 @@ class BaseManyToManyManager(BaseRelatedManager):
380
375
  def clear(self) -> None:
381
376
  with transaction.atomic(savepoint=False):
382
377
  self._remove_prefetched_objects()
383
- filters = self._build_remove_filters(
384
- self.base_queryset_class(model=self.model)
385
- )
378
+ filters = self._build_remove_filters(self.model.query)
386
379
  self.through.query.filter(filters).delete()
387
380
 
388
381
  def set(
@@ -425,16 +418,14 @@ class BaseManyToManyManager(BaseRelatedManager):
425
418
  def create(
426
419
  self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
427
420
  ) -> Any:
428
- new_obj = self.base_queryset_class(model=self.model).create(**kwargs)
421
+ new_obj = self.model.query.create(**kwargs)
429
422
  self.add(new_obj, through_defaults=through_defaults)
430
423
  return new_obj
431
424
 
432
425
  def get_or_create(
433
426
  self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
434
427
  ) -> tuple[Any, bool]:
435
- obj, created = self.base_queryset_class(model=self.model).get_or_create(
436
- **kwargs
437
- )
428
+ obj, created = self.model.query.get_or_create(**kwargs)
438
429
  # We only need to add() if created because if we got an object back
439
430
  # from get() then the relationship already exists.
440
431
  if created:
@@ -444,9 +435,7 @@ class BaseManyToManyManager(BaseRelatedManager):
444
435
  def update_or_create(
445
436
  self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
446
437
  ) -> tuple[Any, bool]:
447
- obj, created = self.base_queryset_class(model=self.model).update_or_create(
448
- **kwargs
449
- )
438
+ obj, created = self.model.query.update_or_create(**kwargs)
450
439
  # We only need to add() if created because if we got an object back
451
440
  # from get() then the relationship already exists.
452
441
  if created:
@@ -458,7 +447,7 @@ class BaseManyToManyManager(BaseRelatedManager):
458
447
  from plain.models import Model
459
448
 
460
449
  target_ids = set()
461
- target_field = self.through._meta.get_field(target_field_name)
450
+ target_field = self.through._model_meta.get_field(target_field_name)
462
451
  for obj in objs:
463
452
  if isinstance(obj, self.model):
464
453
  target_id = target_field.get_foreign_related_value(obj)[0]
@@ -469,7 +458,7 @@ class BaseManyToManyManager(BaseRelatedManager):
469
458
  target_ids.add(target_id)
470
459
  elif isinstance(obj, Model):
471
460
  raise TypeError(
472
- f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
461
+ f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
473
462
  )
474
463
  else:
475
464
  target_ids.add(target_field.get_prep_value(obj))
@@ -534,7 +523,7 @@ class BaseManyToManyManager(BaseRelatedManager):
534
523
  old_ids.add(obj)
535
524
 
536
525
  with transaction.atomic(savepoint=False):
537
- target_model_qs = self.base_queryset_class(model=self.model)
526
+ target_model_qs = self.model.query
538
527
  if target_model_qs._has_filters():
539
528
  old_vals = target_model_qs.filter(
540
529
  **{f"{self.target_field.target_field.attname}__in": old_ids}