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.
- plain/models/CHANGELOG.md +24 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/backends/base/creation.py +2 -2
- plain/models/backends/base/introspection.py +8 -4
- plain/models/backends/base/schema.py +89 -71
- plain/models/backends/base/validation.py +1 -1
- plain/models/backends/mysql/compiler.py +1 -1
- plain/models/backends/mysql/operations.py +1 -1
- plain/models/backends/mysql/schema.py +4 -4
- plain/models/backends/postgresql/operations.py +1 -1
- plain/models/backends/postgresql/schema.py +3 -3
- plain/models/backends/sqlite3/operations.py +1 -1
- plain/models/backends/sqlite3/schema.py +61 -50
- plain/models/base.py +116 -163
- plain/models/cli.py +4 -4
- plain/models/constraints.py +14 -9
- plain/models/deletion.py +15 -14
- plain/models/expressions.py +21 -5
- plain/models/fields/__init__.py +20 -16
- plain/models/fields/json.py +3 -3
- plain/models/fields/related.py +73 -71
- plain/models/fields/related_descriptors.py +2 -2
- plain/models/fields/related_lookups.py +1 -1
- plain/models/fields/related_managers.py +21 -32
- plain/models/fields/reverse_related.py +8 -8
- plain/models/forms.py +12 -12
- plain/models/indexes.py +5 -4
- plain/models/meta.py +505 -0
- plain/models/migrations/operations/base.py +1 -1
- plain/models/migrations/operations/fields.py +6 -6
- plain/models/migrations/operations/models.py +18 -16
- plain/models/migrations/recorder.py +9 -5
- plain/models/migrations/state.py +35 -46
- plain/models/migrations/utils.py +1 -1
- plain/models/options.py +182 -518
- plain/models/preflight.py +7 -5
- plain/models/query.py +119 -65
- plain/models/query_utils.py +18 -13
- plain/models/registry.py +6 -5
- plain/models/sql/compiler.py +51 -37
- plain/models/sql/query.py +77 -68
- plain/models/sql/subqueries.py +4 -4
- plain/models/utils.py +4 -1
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/METADATA +27 -43
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/RECORD +49 -48
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/WHEEL +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/entry_points.txt +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/licenses/LICENSE +0 -0
@@ -117,7 +117,7 @@ class AddField(FieldOperation):
|
|
117
117
|
from_model = from_state.models_registry.get_model(
|
118
118
|
package_label, self.model_name
|
119
119
|
)
|
120
|
-
field = to_model.
|
120
|
+
field = to_model._model_meta.get_field(self.name)
|
121
121
|
if not self.preserve_default:
|
122
122
|
field.default = self.field.default
|
123
123
|
schema_editor.add_field(
|
@@ -187,7 +187,7 @@ class RemoveField(FieldOperation):
|
|
187
187
|
)
|
188
188
|
if self.allow_migrate_model(schema_editor.connection, from_model):
|
189
189
|
schema_editor.remove_field(
|
190
|
-
from_model, from_model.
|
190
|
+
from_model, from_model._model_meta.get_field(self.name)
|
191
191
|
)
|
192
192
|
|
193
193
|
def describe(self) -> str:
|
@@ -253,8 +253,8 @@ class AlterField(FieldOperation):
|
|
253
253
|
from_model = from_state.models_registry.get_model(
|
254
254
|
package_label, self.model_name
|
255
255
|
)
|
256
|
-
from_field = from_model.
|
257
|
-
to_field = to_model.
|
256
|
+
from_field = from_model._model_meta.get_field(self.name)
|
257
|
+
to_field = to_model._model_meta.get_field(self.name)
|
258
258
|
if not self.preserve_default:
|
259
259
|
to_field.default = self.field.default
|
260
260
|
schema_editor.alter_field(from_model, from_field, to_field)
|
@@ -334,8 +334,8 @@ class RenameField(FieldOperation):
|
|
334
334
|
)
|
335
335
|
schema_editor.alter_field(
|
336
336
|
from_model,
|
337
|
-
from_model.
|
338
|
-
to_model.
|
337
|
+
from_model._model_meta.get_field(self.old_name),
|
338
|
+
to_model._model_meta.get_field(self.new_name),
|
339
339
|
)
|
340
340
|
|
341
341
|
def describe(self) -> str:
|
@@ -70,8 +70,10 @@ class CreateModel(ModelOperation):
|
|
70
70
|
_check_for_duplicates(
|
71
71
|
"bases",
|
72
72
|
(
|
73
|
-
base.
|
74
|
-
if
|
73
|
+
base.model_options.label_lower
|
74
|
+
if not isinstance(base, str)
|
75
|
+
and base is not models.Model
|
76
|
+
and hasattr(base, "_model_meta")
|
75
77
|
else base.lower()
|
76
78
|
if isinstance(base, str)
|
77
79
|
else base
|
@@ -312,26 +314,26 @@ class RenameModel(ModelOperation):
|
|
312
314
|
# Move the main table
|
313
315
|
schema_editor.alter_db_table(
|
314
316
|
new_model,
|
315
|
-
old_model.
|
316
|
-
new_model.
|
317
|
+
old_model.model_options.db_table,
|
318
|
+
new_model.model_options.db_table,
|
317
319
|
)
|
318
320
|
# Alter the fields pointing to us
|
319
|
-
for related_object in old_model.
|
321
|
+
for related_object in old_model._model_meta.related_objects:
|
320
322
|
if related_object.related_model == old_model: # type: ignore[attr-defined]
|
321
323
|
model = new_model
|
322
324
|
related_key = (package_label, self.new_name_lower)
|
323
325
|
else:
|
324
326
|
model = related_object.related_model # type: ignore[attr-defined]
|
325
327
|
related_key = (
|
326
|
-
related_object.related_model.
|
327
|
-
related_object.related_model.
|
328
|
+
related_object.related_model.model_options.package_label,
|
329
|
+
related_object.related_model.model_options.model_name,
|
328
330
|
)
|
329
|
-
to_field = to_state.models_registry.get_model(
|
331
|
+
to_field = to_state.models_registry.get_model(
|
330
332
|
*related_key
|
331
|
-
).
|
333
|
+
)._model_meta.get_field(related_object.field.name)
|
332
334
|
schema_editor.alter_field(
|
333
335
|
model,
|
334
|
-
related_object.field,
|
336
|
+
related_object.field,
|
335
337
|
to_field,
|
336
338
|
)
|
337
339
|
|
@@ -410,8 +412,8 @@ class AlterModelTable(ModelOptionOperation):
|
|
410
412
|
old_model = from_state.models_registry.get_model(package_label, self.name) # type: ignore[attr-defined]
|
411
413
|
schema_editor.alter_db_table(
|
412
414
|
new_model,
|
413
|
-
old_model.
|
414
|
-
new_model.
|
415
|
+
old_model.model_options.db_table,
|
416
|
+
new_model.model_options.db_table,
|
415
417
|
)
|
416
418
|
|
417
419
|
def describe(self) -> str:
|
@@ -454,8 +456,8 @@ class AlterModelTableComment(ModelOptionOperation):
|
|
454
456
|
old_model = from_state.models_registry.get_model(package_label, self.name) # type: ignore[attr-defined]
|
455
457
|
schema_editor.alter_db_table_comment(
|
456
458
|
new_model,
|
457
|
-
old_model.
|
458
|
-
new_model.
|
459
|
+
old_model.model_options.db_table_comment,
|
460
|
+
new_model.model_options.db_table_comment,
|
459
461
|
)
|
460
462
|
|
461
463
|
def describe(self) -> str:
|
@@ -693,7 +695,7 @@ class RenameIndex(IndexOperation):
|
|
693
695
|
package_label, self.model_name
|
694
696
|
)
|
695
697
|
columns = [
|
696
|
-
from_model.
|
698
|
+
from_model._model_meta.get_field(field).column
|
697
699
|
for field in self.old_fields
|
698
700
|
]
|
699
701
|
matching_index_name = schema_editor._constraint_names(
|
@@ -703,7 +705,7 @@ class RenameIndex(IndexOperation):
|
|
703
705
|
raise ValueError(
|
704
706
|
"Found wrong number ({}) of indexes for {}({}).".format(
|
705
707
|
len(matching_index_name),
|
706
|
-
from_model.
|
708
|
+
from_model.model_options.db_table,
|
707
709
|
", ".join(columns),
|
708
710
|
)
|
709
711
|
)
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
5
5
|
from plain import models
|
6
6
|
from plain.models.db import DatabaseError
|
7
|
+
from plain.models.meta import Meta
|
7
8
|
from plain.models.registry import ModelsRegistry
|
8
9
|
from plain.utils.functional import classproperty
|
9
10
|
from plain.utils.timezone import now
|
@@ -44,10 +45,13 @@ class MigrationRecorder:
|
|
44
45
|
name = models.CharField(max_length=255)
|
45
46
|
applied = models.DateTimeField(default=now)
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
# Use isolated models registry for migrations
|
49
|
+
_model_meta = Meta(models_registry=_models_registry)
|
50
|
+
|
51
|
+
model_options = models.Options(
|
52
|
+
package_label="migrations",
|
53
|
+
db_table="plainmigrations",
|
54
|
+
)
|
51
55
|
|
52
56
|
def __str__(self) -> str:
|
53
57
|
return f"Migration {self.name} for {self.app}"
|
@@ -66,7 +70,7 @@ class MigrationRecorder:
|
|
66
70
|
"""Return True if the plainmigrations table exists."""
|
67
71
|
with self.connection.cursor() as cursor:
|
68
72
|
tables = self.connection.introspection.table_names(cursor)
|
69
|
-
return self.Migration.
|
73
|
+
return self.Migration.model_options.db_table in tables
|
70
74
|
|
71
75
|
def ensure_schema(self) -> None:
|
72
76
|
"""Ensure the table exists and has the correct schema."""
|
plain/models/migrations/state.py
CHANGED
@@ -10,8 +10,8 @@ from plain import models
|
|
10
10
|
from plain.models.exceptions import FieldDoesNotExist
|
11
11
|
from plain.models.fields import NOT_PROVIDED
|
12
12
|
from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
|
13
|
+
from plain.models.meta import Meta
|
13
14
|
from plain.models.migrations.utils import field_is_referenced, get_references
|
14
|
-
from plain.models.options import DEFAULT_NAMES
|
15
15
|
from plain.models.registry import ModelsRegistry
|
16
16
|
from plain.models.registry import models_registry as global_models
|
17
17
|
from plain.packages import packages_registry
|
@@ -32,7 +32,7 @@ def _get_package_label_and_model_name(
|
|
32
32
|
split = model.split(".", 1)
|
33
33
|
return tuple(split) if len(split) == 2 else (package_label, split[0]) # type: ignore[return-value]
|
34
34
|
else:
|
35
|
-
return model.
|
35
|
+
return model.model_options.package_label, model.model_options.model_name
|
36
36
|
|
37
37
|
|
38
38
|
def _get_related_models(m: type[models.Model]) -> list[type[models.Model]]:
|
@@ -43,7 +43,7 @@ def _get_related_models(m: type[models.Model]) -> list[type[models.Model]]:
|
|
43
43
|
if issubclass(subclass, models.Model)
|
44
44
|
]
|
45
45
|
related_fields_models = set()
|
46
|
-
for f in m.
|
46
|
+
for f in m._model_meta.get_fields(include_hidden=True):
|
47
47
|
if (
|
48
48
|
f.is_relation # type: ignore[attr-defined]
|
49
49
|
and f.related_model is not None # type: ignore[attr-defined]
|
@@ -60,7 +60,7 @@ def get_related_models_tuples(model: type[models.Model]) -> set[tuple[str, str]]
|
|
60
60
|
models for the given model.
|
61
61
|
"""
|
62
62
|
return {
|
63
|
-
(rel_mod.
|
63
|
+
(rel_mod.model_options.package_label, rel_mod.model_options.model_name)
|
64
64
|
for rel_mod in _get_related_models(model)
|
65
65
|
}
|
66
66
|
|
@@ -78,14 +78,14 @@ def get_related_models_recursive(model: type[models.Model]) -> set[tuple[str, st
|
|
78
78
|
queue = _get_related_models(model)
|
79
79
|
for rel_mod in queue:
|
80
80
|
rel_package_label, rel_model_name = (
|
81
|
-
rel_mod.
|
82
|
-
rel_mod.
|
81
|
+
rel_mod.model_options.package_label,
|
82
|
+
rel_mod.model_options.model_name,
|
83
83
|
)
|
84
84
|
if (rel_package_label, rel_model_name) in seen:
|
85
85
|
continue
|
86
86
|
seen.add((rel_package_label, rel_model_name))
|
87
87
|
queue.extend(_get_related_models(rel_mod))
|
88
|
-
return seen - {(model.
|
88
|
+
return seen - {(model.model_options.package_label, model.model_options.model_name)}
|
89
89
|
|
90
90
|
|
91
91
|
class ProjectState:
|
@@ -362,7 +362,7 @@ class ProjectState:
|
|
362
362
|
pass
|
363
363
|
else:
|
364
364
|
# Get all relations to and from the old model before reloading,
|
365
|
-
# as
|
365
|
+
# as _model_meta.models_registry may change
|
366
366
|
if delay:
|
367
367
|
related_models = get_related_models_tuples(old_model)
|
368
368
|
else:
|
@@ -645,7 +645,7 @@ class StateModelsRegistry(ModelsRegistry):
|
|
645
645
|
return clone
|
646
646
|
|
647
647
|
def register_model(self, package_label: str, model: type[models.Model]) -> None:
|
648
|
-
self.all_models[package_label][model.
|
648
|
+
self.all_models[package_label][model.model_options.model_name] = model
|
649
649
|
self.do_pending_operations(model)
|
650
650
|
self.clear_cache()
|
651
651
|
|
@@ -689,12 +689,14 @@ class ModelState:
|
|
689
689
|
f'ModelState.fields cannot be bound to a model - "{name}" is.'
|
690
690
|
)
|
691
691
|
# Sanity-check that relation fields are NOT referring to a model class.
|
692
|
-
if field.is_relation and hasattr(field.related_model, "
|
692
|
+
if field.is_relation and hasattr(field.related_model, "_model_meta"):
|
693
693
|
raise ValueError(
|
694
694
|
f'ModelState.fields cannot refer to a model class - "{name}.to" does. '
|
695
695
|
"Use a string reference instead."
|
696
696
|
)
|
697
|
-
if field.many_to_many and hasattr(
|
697
|
+
if field.many_to_many and hasattr(
|
698
|
+
field.remote_field.through, "_model_meta"
|
699
|
+
):
|
698
700
|
raise ValueError(
|
699
701
|
f'ModelState.fields cannot refer to a model class - "{name}.through" '
|
700
702
|
"does. Use a string reference instead."
|
@@ -721,7 +723,7 @@ class ModelState:
|
|
721
723
|
"""Given a model, return a ModelState representing it."""
|
722
724
|
# Deconstruct the fields
|
723
725
|
fields = []
|
724
|
-
for field in model.
|
726
|
+
for field in model._model_meta.local_fields:
|
725
727
|
if getattr(field, "remote_field", None) and exclude_rels:
|
726
728
|
continue
|
727
729
|
name = field.name # type: ignore[attr-defined]
|
@@ -729,37 +731,17 @@ class ModelState:
|
|
729
731
|
fields.append((name, field.clone())) # type: ignore[attr-defined]
|
730
732
|
except TypeError as e:
|
731
733
|
raise TypeError(
|
732
|
-
f"Couldn't reconstruct field {name} on {model.
|
734
|
+
f"Couldn't reconstruct field {name} on {model.model_options.label}: {e}"
|
733
735
|
)
|
734
736
|
if not exclude_rels:
|
735
|
-
for field in model.
|
737
|
+
for field in model._model_meta.local_many_to_many:
|
736
738
|
name = field.name # type: ignore[attr-defined]
|
737
739
|
try:
|
738
740
|
fields.append((name, field.clone())) # type: ignore[attr-defined]
|
739
741
|
except TypeError as e:
|
740
742
|
raise TypeError(
|
741
|
-
f"Couldn't reconstruct m2m field {name} on {model.
|
743
|
+
f"Couldn't reconstruct m2m field {name} on {model.model_options.object_name}: {e}"
|
742
744
|
)
|
743
|
-
# Extract the options
|
744
|
-
options = {}
|
745
|
-
for name in DEFAULT_NAMES:
|
746
|
-
# Ignore some special options
|
747
|
-
if name in ["models_registry", "package_label"]:
|
748
|
-
continue
|
749
|
-
elif name in model._meta.original_attrs: # type: ignore[attr-defined]
|
750
|
-
if name == "indexes":
|
751
|
-
indexes = [idx.clone() for idx in model._meta.indexes] # type: ignore[attr-defined]
|
752
|
-
for index in indexes:
|
753
|
-
if not index.name: # type: ignore[attr-defined]
|
754
|
-
index.set_name_with_model(model) # type: ignore[attr-defined]
|
755
|
-
options["indexes"] = indexes
|
756
|
-
elif name == "constraints":
|
757
|
-
options["constraints"] = [
|
758
|
-
con.clone()
|
759
|
-
for con in model._meta.constraints # type: ignore[attr-defined]
|
760
|
-
]
|
761
|
-
else:
|
762
|
-
options[name] = model._meta.original_attrs[name] # type: ignore[attr-defined]
|
763
745
|
|
764
746
|
def flatten_bases(model: type[models.Model]) -> list[type[models.Model]]:
|
765
747
|
bases = []
|
@@ -778,7 +760,13 @@ class ModelState:
|
|
778
760
|
|
779
761
|
# Make our record
|
780
762
|
bases = tuple(
|
781
|
-
(
|
763
|
+
(
|
764
|
+
base.model_options.label_lower
|
765
|
+
if not isinstance(base, str)
|
766
|
+
and base is not models.Model
|
767
|
+
and hasattr(base, "_model_meta")
|
768
|
+
else base
|
769
|
+
)
|
782
770
|
for base in flattened_bases
|
783
771
|
)
|
784
772
|
# Ensure at least one base inherits from models.Model
|
@@ -789,10 +777,10 @@ class ModelState:
|
|
789
777
|
|
790
778
|
# Construct the new ModelState
|
791
779
|
return cls(
|
792
|
-
model.
|
793
|
-
model.
|
780
|
+
model.model_options.package_label,
|
781
|
+
model.model_options.object_name,
|
794
782
|
fields,
|
795
|
-
|
783
|
+
model.model_options.export_for_migrations(),
|
796
784
|
bases,
|
797
785
|
)
|
798
786
|
|
@@ -811,13 +799,11 @@ class ModelState:
|
|
811
799
|
|
812
800
|
def render(self, models_registry: ModelsRegistry) -> type[models.Model]:
|
813
801
|
"""Create a Model object from our current state into the given packages."""
|
814
|
-
#
|
815
|
-
|
816
|
-
|
817
|
-
"models_registry": models_registry,
|
802
|
+
# Create Options instance with metadata
|
803
|
+
meta_options = models.Options(
|
804
|
+
package_label=self.package_label,
|
818
805
|
**self.options,
|
819
|
-
|
820
|
-
meta = type("Meta", (), meta_contents)
|
806
|
+
)
|
821
807
|
# Then, work out our bases
|
822
808
|
try:
|
823
809
|
bases = tuple(
|
@@ -830,7 +816,10 @@ class ModelState:
|
|
830
816
|
)
|
831
817
|
# Clone fields for the body, add other bits.
|
832
818
|
body = {name: field.clone() for name, field in self.fields.items()} # type: ignore[attr-defined]
|
833
|
-
body["
|
819
|
+
body["model_options"] = meta_options
|
820
|
+
body["_model_meta"] = Meta(
|
821
|
+
models_registry=models_registry
|
822
|
+
) # Use custom registry
|
834
823
|
body["__module__"] = "__fake__"
|
835
824
|
|
836
825
|
# Then, make a Model object (models_registry.register_model is called in __new__)
|
plain/models/migrations/utils.py
CHANGED
@@ -56,7 +56,7 @@ def resolve_relation(
|
|
56
56
|
"package_label must be provided to resolve unscoped model relationships."
|
57
57
|
)
|
58
58
|
return package_label, model.lower()
|
59
|
-
return model.
|
59
|
+
return model.model_options.package_label, model.model_options.model_name
|
60
60
|
|
61
61
|
|
62
62
|
def field_references(
|