plain.models 0.34.3__tar.gz → 0.34.4__tar.gz
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-0.34.3 → plain_models-0.34.4}/PKG-INFO +1 -1
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/CHANGELOG.md +11 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/features.py +0 -3
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/schema.py +14 -29
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/base.py +1 -4
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/constraints.py +1 -5
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/deletion.py +8 -8
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/__init__.py +2 -10
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/related.py +1 -7
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/related_descriptors.py +1 -4
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/forms.py +1 -7
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/query.py +2 -21
- {plain_models-0.34.3 → plain_models-0.34.4}/pyproject.toml +1 -1
- plain_models-0.34.4/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +101 -0
- plain_models-0.34.4/tests/app/examples/models.py +59 -0
- plain_models-0.34.4/tests/test_delete_behaviors.py +76 -0
- plain_models-0.34.3/tests/app/examples/models.py +0 -16
- {plain_models-0.34.3 → plain_models-0.34.4}/.gitignore +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/LICENSE +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/README.md +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/README.md +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/aggregates.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/base.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/client.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/creation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/introspection.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/operations.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/base/validation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/ddl_references.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/base.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/client.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/compiler.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/creation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/features.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/introspection.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/operations.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/schema.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/mysql/validation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/base.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/client.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/creation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/features.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/introspection.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/operations.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/schema.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/_functions.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/base.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/client.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/creation.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/features.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/introspection.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/operations.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/sqlite3/schema.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/utils.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backups/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backups/cli.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backups/clients.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backups/core.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/cli.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/config.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/connections.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/constants.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/database_url.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/db.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/default_settings.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/entrypoints.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/enums.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/exceptions.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/expressions.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/json.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/mixins.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/related_lookups.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/fields/reverse_related.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/comparison.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/datetime.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/math.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/mixins.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/text.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/functions/window.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/indexes.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/lookups.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/manager.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/autodetector.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/exceptions.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/executor.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/graph.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/loader.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/migration.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/operations/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/operations/base.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/operations/fields.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/operations/models.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/operations/special.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/optimizer.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/questioner.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/recorder.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/serializer.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/state.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/utils.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/migrations/writer.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/options.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/preflight.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/query.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/query_utils.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/registry.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/compiler.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/constants.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/datastructures.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/subqueries.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/sql/where.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/test/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/test/pytest.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/test/utils.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/transaction.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/plain/models/utils.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/app/settings.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/app/urls.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/test_database_url.py +0 -0
- {plain_models-0.34.3 → plain_models-0.34.4}/tests/test_models.py +0 -0
@@ -1,5 +1,16 @@
|
|
1
1
|
# plain-models changelog
|
2
2
|
|
3
|
+
## [0.34.4](https://github.com/dropseed/plain/releases/plain-models@0.34.4) (2025-07-02)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- The built-in `on_delete` behaviors (`CASCADE`, `PROTECT`, `RESTRICT`, `SET_NULL`, `SET_DEFAULT`, and the callables returned by `SET(...)`) no longer receive the legacy `using` argument. Their signatures are now `(collector, field, sub_objs)` ([20325a1](https://github.com/dropseed/plain/commit/20325a1)).
|
8
|
+
- Removed the unused `interprets_empty_strings_as_nulls` backend feature flag and the related fallback logic ([285378c](https://github.com/dropseed/plain/commit/285378c)).
|
9
|
+
|
10
|
+
### Upgrade instructions
|
11
|
+
|
12
|
+
- No changes required
|
13
|
+
|
3
14
|
## [0.34.3](https://github.com/dropseed/plain/releases/plain-models@0.34.3) (2025-06-29)
|
4
15
|
|
5
16
|
### What's changed
|
@@ -9,9 +9,6 @@ class BaseDatabaseFeatures:
|
|
9
9
|
empty_fetchmany_value = []
|
10
10
|
update_can_self_select = True
|
11
11
|
|
12
|
-
# Does the backend distinguish between '' and None?
|
13
|
-
interprets_empty_strings_as_nulls = False
|
14
|
-
|
15
12
|
# Does the backend support initially deferrable unique constraints?
|
16
13
|
supports_deferrable_unique_constraints = False
|
17
14
|
|
@@ -297,14 +297,6 @@ class BaseDatabaseSchemaEditor:
|
|
297
297
|
else:
|
298
298
|
yield column_default
|
299
299
|
params.append(default_value)
|
300
|
-
# Oracle treats the empty string ('') as null, so coerce the null
|
301
|
-
# option whenever '' is a possible value.
|
302
|
-
if (
|
303
|
-
field.empty_strings_allowed
|
304
|
-
and not field.primary_key
|
305
|
-
and self.connection.features.interprets_empty_strings_as_nulls
|
306
|
-
):
|
307
|
-
null = True
|
308
300
|
|
309
301
|
if not null:
|
310
302
|
yield "NOT NULL"
|
@@ -1042,27 +1034,20 @@ class BaseDatabaseSchemaEditor:
|
|
1042
1034
|
Return a (sql, params) fragment to set a column to null or non-null
|
1043
1035
|
as required by new_field, or None if no changes are required.
|
1044
1036
|
"""
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
self.
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
sql
|
1060
|
-
% {
|
1061
|
-
"column": self.quote_name(new_field.column),
|
1062
|
-
"type": new_db_params["type"],
|
1063
|
-
},
|
1064
|
-
[],
|
1065
|
-
)
|
1037
|
+
new_db_params = new_field.db_parameters(connection=self.connection)
|
1038
|
+
sql = (
|
1039
|
+
self.sql_alter_column_null
|
1040
|
+
if new_field.allow_null
|
1041
|
+
else self.sql_alter_column_not_null
|
1042
|
+
)
|
1043
|
+
return (
|
1044
|
+
sql
|
1045
|
+
% {
|
1046
|
+
"column": self.quote_name(new_field.column),
|
1047
|
+
"type": new_db_params["type"],
|
1048
|
+
},
|
1049
|
+
[],
|
1050
|
+
)
|
1066
1051
|
|
1067
1052
|
def _alter_column_default_sql(self, model, old_field, new_field, drop=False):
|
1068
1053
|
"""
|
@@ -833,10 +833,7 @@ class Model(metaclass=ModelBase):
|
|
833
833
|
f = self._meta.get_field(field_name)
|
834
834
|
lookup_value = getattr(self, f.attname)
|
835
835
|
# TODO: Handle multiple backends with different feature flags.
|
836
|
-
if lookup_value is None
|
837
|
-
lookup_value == ""
|
838
|
-
and db_connection.features.interprets_empty_strings_as_nulls
|
839
|
-
):
|
836
|
+
if lookup_value is None:
|
840
837
|
# no value, skip the lookup
|
841
838
|
continue
|
842
839
|
if f.primary_key and not self._state.adding:
|
@@ -2,7 +2,6 @@ from enum import Enum
|
|
2
2
|
from types import NoneType
|
3
3
|
|
4
4
|
from plain.exceptions import FieldError, ValidationError
|
5
|
-
from plain.models.db import db_connection
|
6
5
|
from plain.models.expressions import Exists, ExpressionList, F, OrderBy
|
7
6
|
from plain.models.indexes import IndexExpression
|
8
7
|
from plain.models.lookups import Exact
|
@@ -356,10 +355,7 @@ class UniqueConstraint(BaseConstraint):
|
|
356
355
|
return
|
357
356
|
field = model._meta.get_field(field_name)
|
358
357
|
lookup_value = getattr(instance, field.attname)
|
359
|
-
if lookup_value is None
|
360
|
-
lookup_value == ""
|
361
|
-
and db_connection.features.interprets_empty_strings_as_nulls
|
362
|
-
):
|
358
|
+
if lookup_value is None:
|
363
359
|
# A composite constraint containing NULL value cannot cause
|
364
360
|
# a violation since NULL != NULL in SQL.
|
365
361
|
return
|
@@ -24,7 +24,7 @@ class RestrictedError(IntegrityError):
|
|
24
24
|
super().__init__(msg, restricted_objects)
|
25
25
|
|
26
26
|
|
27
|
-
def CASCADE(collector, field, sub_objs
|
27
|
+
def CASCADE(collector, field, sub_objs):
|
28
28
|
collector.collect(
|
29
29
|
sub_objs,
|
30
30
|
source=field.remote_field.model,
|
@@ -35,7 +35,7 @@ def CASCADE(collector, field, sub_objs, using):
|
|
35
35
|
collector.add_field_update(field, None, sub_objs)
|
36
36
|
|
37
37
|
|
38
|
-
def PROTECT(collector, field, sub_objs
|
38
|
+
def PROTECT(collector, field, sub_objs):
|
39
39
|
raise ProtectedError(
|
40
40
|
f"Cannot delete some instances of model '{field.remote_field.model.__name__}' because they are "
|
41
41
|
f"referenced through a protected foreign key: '{sub_objs[0].__class__.__name__}.{field.name}'",
|
@@ -43,7 +43,7 @@ def PROTECT(collector, field, sub_objs, using):
|
|
43
43
|
)
|
44
44
|
|
45
45
|
|
46
|
-
def RESTRICT(collector, field, sub_objs
|
46
|
+
def RESTRICT(collector, field, sub_objs):
|
47
47
|
collector.add_restricted_objects(field, sub_objs)
|
48
48
|
collector.add_dependency(field.remote_field.model, field.model)
|
49
49
|
|
@@ -51,12 +51,12 @@ def RESTRICT(collector, field, sub_objs, using):
|
|
51
51
|
def SET(value):
|
52
52
|
if callable(value):
|
53
53
|
|
54
|
-
def set_on_delete(collector, field, sub_objs
|
54
|
+
def set_on_delete(collector, field, sub_objs):
|
55
55
|
collector.add_field_update(field, value(), sub_objs)
|
56
56
|
|
57
57
|
else:
|
58
58
|
|
59
|
-
def set_on_delete(collector, field, sub_objs
|
59
|
+
def set_on_delete(collector, field, sub_objs):
|
60
60
|
collector.add_field_update(field, value, sub_objs)
|
61
61
|
|
62
62
|
set_on_delete.deconstruct = lambda: ("plain.models.SET", (value,), {})
|
@@ -64,21 +64,21 @@ def SET(value):
|
|
64
64
|
return set_on_delete
|
65
65
|
|
66
66
|
|
67
|
-
def SET_NULL(collector, field, sub_objs
|
67
|
+
def SET_NULL(collector, field, sub_objs):
|
68
68
|
collector.add_field_update(field, None, sub_objs)
|
69
69
|
|
70
70
|
|
71
71
|
SET_NULL.lazy_sub_objs = True
|
72
72
|
|
73
73
|
|
74
|
-
def SET_DEFAULT(collector, field, sub_objs
|
74
|
+
def SET_DEFAULT(collector, field, sub_objs):
|
75
75
|
collector.add_field_update(field, field.get_default(), sub_objs)
|
76
76
|
|
77
77
|
|
78
78
|
SET_DEFAULT.lazy_sub_objs = True
|
79
79
|
|
80
80
|
|
81
|
-
def DO_NOTHING(collector, field, sub_objs
|
81
|
+
def DO_NOTHING(collector, field, sub_objs):
|
82
82
|
pass
|
83
83
|
|
84
84
|
|
@@ -365,11 +365,7 @@ class Field(RegisterLookupMixin):
|
|
365
365
|
return errors
|
366
366
|
|
367
367
|
def _check_null_allowed_for_primary_keys(self):
|
368
|
-
if
|
369
|
-
self.primary_key
|
370
|
-
and self.allow_null
|
371
|
-
and not db_connection.features.interprets_empty_strings_as_nulls
|
372
|
-
):
|
368
|
+
if self.primary_key and self.allow_null:
|
373
369
|
# We cannot reliably check this for backends like Oracle which
|
374
370
|
# consider NULL and '' to be equal (and thus set up
|
375
371
|
# character-based fields a little differently).
|
@@ -885,11 +881,7 @@ class Field(RegisterLookupMixin):
|
|
885
881
|
return self.default
|
886
882
|
return lambda: self.default
|
887
883
|
|
888
|
-
if
|
889
|
-
not self.empty_strings_allowed
|
890
|
-
or self.allow_null
|
891
|
-
and not db_connection.features.interprets_empty_strings_as_nulls
|
892
|
-
):
|
884
|
+
if not self.empty_strings_allowed or self.allow_null:
|
893
885
|
return return_None
|
894
886
|
return str # return empty string
|
895
887
|
|
@@ -974,11 +974,7 @@ class ForeignKey(ForeignObject):
|
|
974
974
|
|
975
975
|
def get_db_prep_save(self, value, connection):
|
976
976
|
if value is None or (
|
977
|
-
value == ""
|
978
|
-
and (
|
979
|
-
not self.target_field.empty_strings_allowed
|
980
|
-
or connection.features.interprets_empty_strings_as_nulls
|
981
|
-
)
|
977
|
+
value == "" and not self.target_field.empty_strings_allowed
|
982
978
|
):
|
983
979
|
return None
|
984
980
|
else:
|
@@ -1019,8 +1015,6 @@ class ForeignKey(ForeignObject):
|
|
1019
1015
|
|
1020
1016
|
def get_db_converters(self, connection):
|
1021
1017
|
converters = super().get_db_converters(connection)
|
1022
|
-
if connection.features.interprets_empty_strings_as_nulls:
|
1023
|
-
converters += [self.convert_empty_strings]
|
1024
1018
|
return converters
|
1025
1019
|
|
1026
1020
|
def get_col(self, alias, output_field=None):
|
@@ -369,15 +369,12 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|
369
369
|
"""
|
370
370
|
Filter the queryset for the instance this manager is bound to.
|
371
371
|
"""
|
372
|
-
empty_strings_as_null = (
|
373
|
-
db_connection.features.interprets_empty_strings_as_nulls
|
374
|
-
)
|
375
372
|
queryset._add_hints(instance=self.instance)
|
376
373
|
queryset._defer_next_filter = True
|
377
374
|
queryset = queryset.filter(**self.core_filters)
|
378
375
|
for field in self.field.foreign_related_fields:
|
379
376
|
val = getattr(self.instance, field.attname)
|
380
|
-
if val is None
|
377
|
+
if val is None:
|
381
378
|
return queryset.none()
|
382
379
|
if self.field.many_to_one:
|
383
380
|
# Guard against field-like objects such as GenericRelation
|
@@ -769,13 +769,7 @@ def modelfield_to_formfield(
|
|
769
769
|
# Passing max_length to forms.CharField means that the value's length
|
770
770
|
# will be validated twice. This is considered acceptable since we want
|
771
771
|
# the value in the form field (to pass into widget for example).
|
772
|
-
|
773
|
-
from plain.models.db import db_connection
|
774
|
-
|
775
|
-
if (
|
776
|
-
modelfield.allow_null
|
777
|
-
and not db_connection.features.interprets_empty_strings_as_nulls
|
778
|
-
):
|
772
|
+
if modelfield.allow_null:
|
779
773
|
defaults["empty_value"] = None
|
780
774
|
return fields.CharField(
|
781
775
|
max_length=modelfield.max_length,
|
@@ -1233,16 +1233,6 @@ class Query(BaseExpression):
|
|
1233
1233
|
raise ValueError("Cannot use None as a query value")
|
1234
1234
|
return lhs.get_lookup("isnull")(lhs, True)
|
1235
1235
|
|
1236
|
-
# For Oracle '' is equivalent to null. The check must be done at this
|
1237
|
-
# stage because join promotion can't be done in the compiler. A similar
|
1238
|
-
# thing is done in is_nullable(), too.
|
1239
|
-
if (
|
1240
|
-
lookup_name == "exact"
|
1241
|
-
and lookup.rhs == ""
|
1242
|
-
and db_connection.features.interprets_empty_strings_as_nulls
|
1243
|
-
):
|
1244
|
-
return lhs.get_lookup("isnull")(lhs, True)
|
1245
|
-
|
1246
1236
|
return lookup
|
1247
1237
|
|
1248
1238
|
def try_transform(self, lhs, name):
|
@@ -2466,20 +2456,11 @@ class Query(BaseExpression):
|
|
2466
2456
|
return trimmed_prefix, contains_louter
|
2467
2457
|
|
2468
2458
|
def is_nullable(self, field):
|
2469
|
-
"""
|
2470
|
-
Check if the given field should be treated as nullable.
|
2471
|
-
|
2472
|
-
Some backends treat '' as null and Plain treats such fields as
|
2473
|
-
nullable for those backends. In such situations field.allow_null can be
|
2474
|
-
False even if we should treat the field as nullable.
|
2475
|
-
"""
|
2459
|
+
"""Check if the given field should be treated as nullable."""
|
2476
2460
|
# QuerySet does not have knowledge of which connection is going to be
|
2477
2461
|
# used. For the single-database setup we always reference the default
|
2478
2462
|
# connection here.
|
2479
|
-
return field.allow_null
|
2480
|
-
field.empty_strings_allowed
|
2481
|
-
and db_connection.features.interprets_empty_strings_as_nulls
|
2482
|
-
)
|
2463
|
+
return field.allow_null
|
2483
2464
|
|
2484
2465
|
|
2485
2466
|
def get_order_dir(field, default="ASC"):
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Generated by Plain 0.52.2 on 2025-07-02 18:16
|
2
|
+
|
3
|
+
import plain.models.deletion
|
4
|
+
from plain import models
|
5
|
+
from plain.models import migrations
|
6
|
+
|
7
|
+
|
8
|
+
class Migration(migrations.Migration):
|
9
|
+
dependencies = [
|
10
|
+
("examples", "0002_test_field_removed"),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.CreateModel(
|
15
|
+
name="DeleteParent",
|
16
|
+
fields=[
|
17
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
18
|
+
("name", models.CharField(max_length=100)),
|
19
|
+
],
|
20
|
+
),
|
21
|
+
migrations.CreateModel(
|
22
|
+
name="ChildSetNull",
|
23
|
+
fields=[
|
24
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
25
|
+
(
|
26
|
+
"parent",
|
27
|
+
models.ForeignKey(
|
28
|
+
allow_null=True,
|
29
|
+
on_delete=plain.models.deletion.SET_NULL,
|
30
|
+
to="examples.deleteparent",
|
31
|
+
),
|
32
|
+
),
|
33
|
+
],
|
34
|
+
),
|
35
|
+
migrations.CreateModel(
|
36
|
+
name="ChildSetDefault",
|
37
|
+
fields=[
|
38
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
39
|
+
(
|
40
|
+
"parent",
|
41
|
+
models.ForeignKey(
|
42
|
+
default=1,
|
43
|
+
on_delete=plain.models.deletion.SET_DEFAULT,
|
44
|
+
to="examples.deleteparent",
|
45
|
+
),
|
46
|
+
),
|
47
|
+
],
|
48
|
+
),
|
49
|
+
migrations.CreateModel(
|
50
|
+
name="ChildDoNothing",
|
51
|
+
fields=[
|
52
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
53
|
+
(
|
54
|
+
"parent",
|
55
|
+
models.ForeignKey(
|
56
|
+
on_delete=plain.models.deletion.DO_NOTHING,
|
57
|
+
to="examples.deleteparent",
|
58
|
+
),
|
59
|
+
),
|
60
|
+
],
|
61
|
+
),
|
62
|
+
migrations.CreateModel(
|
63
|
+
name="ChildCascade",
|
64
|
+
fields=[
|
65
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
66
|
+
(
|
67
|
+
"parent",
|
68
|
+
models.ForeignKey(
|
69
|
+
on_delete=plain.models.deletion.CASCADE,
|
70
|
+
to="examples.deleteparent",
|
71
|
+
),
|
72
|
+
),
|
73
|
+
],
|
74
|
+
),
|
75
|
+
migrations.CreateModel(
|
76
|
+
name="ChildRestrict",
|
77
|
+
fields=[
|
78
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
79
|
+
(
|
80
|
+
"parent",
|
81
|
+
models.ForeignKey(
|
82
|
+
on_delete=plain.models.deletion.RESTRICT,
|
83
|
+
to="examples.deleteparent",
|
84
|
+
),
|
85
|
+
),
|
86
|
+
],
|
87
|
+
),
|
88
|
+
migrations.CreateModel(
|
89
|
+
name="ChildProtect",
|
90
|
+
fields=[
|
91
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True)),
|
92
|
+
(
|
93
|
+
"parent",
|
94
|
+
models.ForeignKey(
|
95
|
+
on_delete=plain.models.deletion.PROTECT,
|
96
|
+
to="examples.deleteparent",
|
97
|
+
),
|
98
|
+
),
|
99
|
+
],
|
100
|
+
),
|
101
|
+
]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from plain import models
|
2
|
+
|
3
|
+
|
4
|
+
@models.register_model
|
5
|
+
class Car(models.Model):
|
6
|
+
make = models.CharField(max_length=100)
|
7
|
+
model = models.CharField(max_length=100)
|
8
|
+
|
9
|
+
class Meta:
|
10
|
+
constraints = [
|
11
|
+
models.UniqueConstraint(fields=["make", "model"], name="unique_make_model"),
|
12
|
+
]
|
13
|
+
|
14
|
+
|
15
|
+
class UnregisteredModel(models.Model):
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
@models.register_model
|
20
|
+
class DeleteParent(models.Model):
|
21
|
+
name = models.CharField(max_length=100)
|
22
|
+
|
23
|
+
|
24
|
+
@models.register_model
|
25
|
+
class ChildCascade(models.Model):
|
26
|
+
parent = models.ForeignKey(DeleteParent, on_delete=models.CASCADE)
|
27
|
+
|
28
|
+
|
29
|
+
@models.register_model
|
30
|
+
class ChildProtect(models.Model):
|
31
|
+
parent = models.ForeignKey(DeleteParent, on_delete=models.PROTECT)
|
32
|
+
|
33
|
+
|
34
|
+
@models.register_model
|
35
|
+
class ChildRestrict(models.Model):
|
36
|
+
parent = models.ForeignKey(DeleteParent, on_delete=models.RESTRICT)
|
37
|
+
|
38
|
+
|
39
|
+
@models.register_model
|
40
|
+
class ChildSetNull(models.Model):
|
41
|
+
parent = models.ForeignKey(
|
42
|
+
DeleteParent,
|
43
|
+
on_delete=models.SET_NULL,
|
44
|
+
allow_null=True,
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
@models.register_model
|
49
|
+
class ChildSetDefault(models.Model):
|
50
|
+
parent = models.ForeignKey(
|
51
|
+
DeleteParent,
|
52
|
+
on_delete=models.SET_DEFAULT,
|
53
|
+
default=1,
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
@models.register_model
|
58
|
+
class ChildDoNothing(models.Model):
|
59
|
+
parent = models.ForeignKey(DeleteParent, on_delete=models.DO_NOTHING)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import pytest
|
2
|
+
from app.examples.models import (
|
3
|
+
ChildCascade,
|
4
|
+
ChildDoNothing,
|
5
|
+
ChildProtect,
|
6
|
+
ChildRestrict,
|
7
|
+
ChildSetDefault,
|
8
|
+
ChildSetNull,
|
9
|
+
DeleteParent,
|
10
|
+
)
|
11
|
+
|
12
|
+
from plain.models import (
|
13
|
+
IntegrityError,
|
14
|
+
ProtectedError,
|
15
|
+
RestrictedError,
|
16
|
+
db_connection,
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
def _create_parents():
|
21
|
+
default_parent = DeleteParent.objects.create(name="default")
|
22
|
+
parent = DeleteParent.objects.create(name="parent")
|
23
|
+
return default_parent, parent
|
24
|
+
|
25
|
+
|
26
|
+
def test_cascade_delete(db):
|
27
|
+
_create_parents()
|
28
|
+
parent = DeleteParent.objects.get(name="parent")
|
29
|
+
ChildCascade.objects.create(parent=parent)
|
30
|
+
parent.delete()
|
31
|
+
assert ChildCascade.objects.count() == 0
|
32
|
+
|
33
|
+
|
34
|
+
def test_protect_delete(db):
|
35
|
+
_create_parents()
|
36
|
+
parent = DeleteParent.objects.get(name="parent")
|
37
|
+
ChildProtect.objects.create(parent=parent)
|
38
|
+
with pytest.raises(ProtectedError):
|
39
|
+
parent.delete()
|
40
|
+
assert DeleteParent.objects.filter(pk=parent.pk).exists()
|
41
|
+
|
42
|
+
|
43
|
+
def test_restrict_delete(db):
|
44
|
+
_create_parents()
|
45
|
+
parent = DeleteParent.objects.get(name="parent")
|
46
|
+
ChildRestrict.objects.create(parent=parent)
|
47
|
+
with pytest.raises(RestrictedError):
|
48
|
+
parent.delete()
|
49
|
+
assert DeleteParent.objects.filter(pk=parent.pk).exists()
|
50
|
+
|
51
|
+
|
52
|
+
def test_set_null_delete(db):
|
53
|
+
_create_parents()
|
54
|
+
parent = DeleteParent.objects.get(name="parent")
|
55
|
+
child = ChildSetNull.objects.create(parent=parent)
|
56
|
+
parent.delete()
|
57
|
+
child.refresh_from_db()
|
58
|
+
assert child.parent_id is None
|
59
|
+
|
60
|
+
|
61
|
+
def test_set_default_delete(db):
|
62
|
+
default_parent, parent = _create_parents()
|
63
|
+
child = ChildSetDefault.objects.create(parent=parent)
|
64
|
+
parent.delete()
|
65
|
+
child.refresh_from_db()
|
66
|
+
assert child.parent_id == default_parent.pk
|
67
|
+
|
68
|
+
|
69
|
+
def test_do_nothing_delete(db):
|
70
|
+
default_parent, parent = _create_parents()
|
71
|
+
child = ChildDoNothing.objects.create(parent=parent)
|
72
|
+
parent.delete()
|
73
|
+
with pytest.raises(IntegrityError):
|
74
|
+
db_connection.check_constraints()
|
75
|
+
child.parent = default_parent
|
76
|
+
child.save(clean_and_validate=False)
|
@@ -1,16 +0,0 @@
|
|
1
|
-
from plain import models
|
2
|
-
|
3
|
-
|
4
|
-
@models.register_model
|
5
|
-
class Car(models.Model):
|
6
|
-
make = models.CharField(max_length=100)
|
7
|
-
model = models.CharField(max_length=100)
|
8
|
-
|
9
|
-
class Meta:
|
10
|
-
constraints = [
|
11
|
-
models.UniqueConstraint(fields=["make", "model"], name="unique_make_model"),
|
12
|
-
]
|
13
|
-
|
14
|
-
|
15
|
-
class UnregisteredModel(models.Model):
|
16
|
-
pass
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_models-0.34.3 → plain_models-0.34.4}/plain/models/backends/postgresql/introspection.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_models-0.34.3 → plain_models-0.34.4}/tests/app/examples/migrations/0002_test_field_removed.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|