plain.models 0.50.0__py3-none-any.whl → 0.51.0__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 +14 -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 +1 -1
- 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.0.dist-info}/METADATA +27 -43
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/RECORD +49 -48
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
plain/models/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# plain-models changelog
|
2
2
|
|
3
|
+
## [0.51.0](https://github.com/dropseed/plain/releases/plain-models@0.51.0) (2025-10-07)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Model metadata has been split into two separate descriptors: `model_options` for user-defined configuration and `_model_meta` for internal metadata ([73ba469](https://github.com/dropseed/plain/commit/73ba469ba0), [17a378d](https://github.com/dropseed/plain/commit/17a378dcfb))
|
8
|
+
- The `_meta` attribute has been replaced with `model_options` for user-defined options like indexes, constraints, and database settings ([17a378d](https://github.com/dropseed/plain/commit/17a378dcfb))
|
9
|
+
- Custom QuerySets are now assigned directly to the `query` class attribute instead of using `Meta.queryset_class` ([2578301](https://github.com/dropseed/plain/commit/2578301819))
|
10
|
+
- Added comprehensive type improvements to model metadata and related fields for better IDE support ([3b477a0](https://github.com/dropseed/plain/commit/3b477a0d43))
|
11
|
+
|
12
|
+
### Upgrade instructions
|
13
|
+
|
14
|
+
- Replace `Meta.queryset_class = CustomQuerySet` with `query = CustomQuerySet()` as a class attribute on your models
|
15
|
+
- Replace `class Meta:` with `model_options = models.Options(...)` in your models
|
16
|
+
|
3
17
|
## [0.50.0](https://github.com/dropseed/plain/releases/plain-models@0.50.0) (2025-10-06)
|
4
18
|
|
5
19
|
### What's changed
|
plain/models/README.md
CHANGED
@@ -200,31 +200,32 @@ class User(models.Model):
|
|
200
200
|
username = models.CharField(max_length=150)
|
201
201
|
age = models.IntegerField()
|
202
202
|
|
203
|
-
|
204
|
-
indexes
|
203
|
+
model_options = models.Options(
|
204
|
+
indexes=[
|
205
205
|
models.Index(fields=["email"]),
|
206
206
|
models.Index(fields=["-created_at"], name="user_created_idx"),
|
207
|
-
]
|
208
|
-
constraints
|
207
|
+
],
|
208
|
+
constraints=[
|
209
209
|
models.UniqueConstraint(fields=["email", "username"], name="unique_user"),
|
210
210
|
models.CheckConstraint(check=models.Q(age__gte=0), name="age_positive"),
|
211
|
-
]
|
211
|
+
],
|
212
|
+
)
|
212
213
|
```
|
213
214
|
|
214
215
|
## Custom QuerySets
|
215
216
|
|
216
|
-
With the Manager functionality now merged into QuerySet, you can customize [`QuerySet`](./query.py#QuerySet) classes to provide specialized query methods.
|
217
|
-
|
218
|
-
### Setting a default QuerySet for a model
|
217
|
+
With the Manager functionality now merged into QuerySet, you can customize [`QuerySet`](./query.py#QuerySet) classes to provide specialized query methods.
|
219
218
|
|
220
|
-
|
219
|
+
Define a custom QuerySet and assign it to your model's `query` attribute:
|
221
220
|
|
222
221
|
```python
|
223
|
-
|
224
|
-
|
222
|
+
from typing import Self
|
223
|
+
|
224
|
+
class PublishedQuerySet(models.QuerySet["Article"]):
|
225
|
+
def published_only(self) -> Self:
|
225
226
|
return self.filter(status="published")
|
226
227
|
|
227
|
-
def draft_only(self):
|
228
|
+
def draft_only(self) -> Self:
|
228
229
|
return self.filter(status="draft")
|
229
230
|
|
230
231
|
@models.register_model
|
@@ -232,50 +233,33 @@ class Article(models.Model):
|
|
232
233
|
title = models.CharField(max_length=200)
|
233
234
|
status = models.CharField(max_length=20)
|
234
235
|
|
235
|
-
|
236
|
-
queryset_class = PublishedQuerySet
|
236
|
+
query = PublishedQuerySet()
|
237
237
|
|
238
|
-
# Usage - all methods available on Article.
|
238
|
+
# Usage - all methods available on Article.query
|
239
239
|
all_articles = Article.query.all()
|
240
240
|
published_articles = Article.query.published_only()
|
241
241
|
draft_articles = Article.query.draft_only()
|
242
242
|
```
|
243
243
|
|
244
|
-
|
245
|
-
|
246
|
-
You can also use custom QuerySets manually without setting them as the default:
|
244
|
+
Custom methods can be chained with built-in QuerySet methods:
|
247
245
|
|
248
246
|
```python
|
249
|
-
|
250
|
-
|
251
|
-
return self.filter(special=True)
|
252
|
-
|
253
|
-
# Create and use the QuerySet manually
|
254
|
-
special_qs = SpecialQuerySet(model=Article)
|
255
|
-
special_articles = special_qs.special_filter()
|
247
|
+
# Chaining works naturally
|
248
|
+
recent_published = Article.query.published_only().order_by("-created_at")[:10]
|
256
249
|
```
|
257
250
|
|
258
|
-
###
|
251
|
+
### Programmatic QuerySet usage
|
259
252
|
|
260
|
-
For
|
253
|
+
For internal code that needs to create QuerySet instances programmatically, use `from_model()`:
|
261
254
|
|
262
255
|
```python
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
status = models.CharField(max_length=20)
|
267
|
-
|
268
|
-
@classmethod
|
269
|
-
def published(cls):
|
270
|
-
return PublishedQuerySet(model=cls).published_only()
|
271
|
-
|
272
|
-
@classmethod
|
273
|
-
def drafts(cls):
|
274
|
-
return PublishedQuerySet(model=cls).draft_only()
|
256
|
+
class SpecialQuerySet(models.QuerySet["Article"]):
|
257
|
+
def special_filter(self) -> Self:
|
258
|
+
return self.filter(special=True)
|
275
259
|
|
276
|
-
#
|
277
|
-
|
278
|
-
|
260
|
+
# Create and use the QuerySet programmatically
|
261
|
+
special_qs = SpecialQuerySet.from_model(Article)
|
262
|
+
special_articles = special_qs.special_filter()
|
279
263
|
```
|
280
264
|
|
281
265
|
## Forms
|
plain/models/__init__.py
CHANGED
@@ -63,6 +63,7 @@ from .registry import models_registry, register_model
|
|
63
63
|
|
64
64
|
# Imports that would create circular imports if sorted
|
65
65
|
from .base import DEFERRED, Model # isort:skip
|
66
|
+
from .options import Options # isort:skip
|
66
67
|
from .fields.related import ( # isort:skip
|
67
68
|
ForeignKey,
|
68
69
|
ManyToManyField,
|
@@ -104,6 +105,7 @@ __all__ += [
|
|
104
105
|
"JSONField",
|
105
106
|
"Lookup",
|
106
107
|
"Transform",
|
108
|
+
"Options",
|
107
109
|
"Prefetch",
|
108
110
|
"Q",
|
109
111
|
"QuerySet",
|
@@ -98,7 +98,7 @@ class BaseDatabaseCreation:
|
|
98
98
|
# and package_config.package_label in loader.migrated_packages
|
99
99
|
# ):
|
100
100
|
# for model in package_config.get_models():
|
101
|
-
# if model.
|
101
|
+
# if model.model_options.can_migrate(
|
102
102
|
# self.connection
|
103
103
|
# ) and router.allow_migrate_model(self.connection.alias, model):
|
104
104
|
# queryset = model._base_manager.using(
|
@@ -127,7 +127,7 @@ class BaseDatabaseCreation:
|
|
127
127
|
# "json", data, using=self.connection.alias
|
128
128
|
# ):
|
129
129
|
# obj.save()
|
130
|
-
# table_names.add(obj.object.
|
130
|
+
# table_names.add(obj.object.model_options.db_table)
|
131
131
|
# # Manually check for any invalid keys that might have been added,
|
132
132
|
# # because constraint checks were disabled.
|
133
133
|
# self.connection.check_constraints(table_names=table_names)
|
@@ -94,7 +94,7 @@ class BaseDatabaseIntrospection:
|
|
94
94
|
for model in models_registry.get_models(
|
95
95
|
package_label=package_config.package_label
|
96
96
|
)
|
97
|
-
if model.
|
97
|
+
if model.model_options.can_migrate(self.connection)
|
98
98
|
)
|
99
99
|
|
100
100
|
def plain_table_names(
|
@@ -108,8 +108,10 @@ class BaseDatabaseIntrospection:
|
|
108
108
|
"""
|
109
109
|
tables = set()
|
110
110
|
for model in self.get_migratable_models():
|
111
|
-
tables.add(model.
|
112
|
-
tables.update(
|
111
|
+
tables.add(model.model_options.db_table)
|
112
|
+
tables.update(
|
113
|
+
f.m2m_db_table() for f in model._model_meta.local_many_to_many
|
114
|
+
)
|
113
115
|
tables = list(tables)
|
114
116
|
if only_existing:
|
115
117
|
existing_tables = set(self.table_names(include_views=include_views))
|
@@ -128,7 +130,9 @@ class BaseDatabaseIntrospection:
|
|
128
130
|
for model in self.get_migratable_models():
|
129
131
|
sequence_list.extend(
|
130
132
|
self.get_sequences(
|
131
|
-
cursor,
|
133
|
+
cursor,
|
134
|
+
model.model_options.db_table,
|
135
|
+
model._model_meta.local_fields,
|
132
136
|
)
|
133
137
|
)
|
134
138
|
return sequence_list
|