plain.models 0.33.1__py3-none-any.whl → 0.34.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 +27 -0
- plain/models/README.md +8 -10
- plain/models/__init__.py +2 -6
- plain/models/backends/base/base.py +10 -18
- plain/models/backends/base/creation.py +3 -4
- plain/models/backends/base/introspection.py +2 -3
- plain/models/backends/base/schema.py +3 -9
- plain/models/backends/mysql/validation.py +1 -1
- plain/models/backends/postgresql/base.py +15 -23
- plain/models/backends/postgresql/schema.py +0 -2
- plain/models/backends/sqlite3/base.py +1 -1
- plain/models/backends/sqlite3/creation.py +2 -2
- plain/models/backends/sqlite3/features.py +1 -1
- plain/models/backends/sqlite3/schema.py +1 -1
- plain/models/backends/utils.py +2 -6
- plain/models/backups/core.py +15 -22
- plain/models/base.py +179 -225
- plain/models/cli.py +25 -62
- plain/models/connections.py +48 -165
- plain/models/constraints.py +10 -10
- plain/models/db.py +7 -15
- plain/models/default_settings.py +13 -20
- plain/models/deletion.py +14 -16
- plain/models/expressions.py +7 -10
- plain/models/fields/__init__.py +56 -76
- plain/models/fields/json.py +9 -12
- plain/models/fields/related.py +5 -17
- plain/models/fields/related_descriptors.py +43 -95
- plain/models/forms.py +2 -4
- plain/models/indexes.py +2 -3
- plain/models/lookups.py +0 -7
- plain/models/manager.py +1 -14
- plain/models/migrations/executor.py +0 -16
- plain/models/migrations/loader.py +1 -1
- plain/models/migrations/migration.py +1 -1
- plain/models/migrations/operations/base.py +4 -11
- plain/models/migrations/operations/fields.py +4 -4
- plain/models/migrations/operations/models.py +10 -10
- plain/models/migrations/operations/special.py +6 -14
- plain/models/migrations/recorder.py +1 -1
- plain/models/options.py +4 -7
- plain/models/preflight.py +25 -44
- plain/models/query.py +47 -102
- plain/models/query_utils.py +4 -4
- plain/models/sql/compiler.py +7 -11
- plain/models/sql/query.py +32 -42
- plain/models/sql/subqueries.py +6 -8
- plain/models/sql/where.py +1 -1
- plain/models/test/pytest.py +21 -32
- plain/models/test/utils.py +7 -143
- plain/models/transaction.py +66 -164
- {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/METADATA +9 -11
- {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/RECORD +56 -55
- {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/WHEEL +0 -0
- {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/entry_points.txt +0 -0
- {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/licenses/LICENSE +0 -0
plain/models/cli.py
CHANGED
@@ -14,7 +14,7 @@ from plain.utils.text import Truncator
|
|
14
14
|
from . import migrations
|
15
15
|
from .backups.cli import cli as backups_cli
|
16
16
|
from .backups.cli import create_backup
|
17
|
-
from .db import
|
17
|
+
from .db import OperationalError, db_connection
|
18
18
|
from .migrations.autodetector import MigrationAutodetector
|
19
19
|
from .migrations.executor import MigrationExecutor
|
20
20
|
from .migrations.loader import AmbiguityError, MigrationLoader
|
@@ -42,27 +42,18 @@ cli.add_command(backups_cli)
|
|
42
42
|
|
43
43
|
|
44
44
|
@cli.command()
|
45
|
-
@click.option(
|
46
|
-
"--database",
|
47
|
-
default=DEFAULT_DB_ALIAS,
|
48
|
-
help=(
|
49
|
-
"Nominates a database onto which to open a shell. Defaults to the "
|
50
|
-
'"default" database.'
|
51
|
-
),
|
52
|
-
)
|
53
45
|
@click.argument("parameters", nargs=-1)
|
54
|
-
def db_shell(
|
46
|
+
def db_shell(parameters):
|
55
47
|
"""Runs the command-line client for specified database, or the default database if none is provided."""
|
56
|
-
connection = connections[database]
|
57
48
|
try:
|
58
|
-
|
49
|
+
db_connection.client.runshell(parameters)
|
59
50
|
except FileNotFoundError:
|
60
51
|
# Note that we're assuming the FileNotFoundError relates to the
|
61
52
|
# command missing. It could be raised for some other reason, in
|
62
53
|
# which case this error message would be inaccurate. Still, this
|
63
54
|
# message catches the common case.
|
64
55
|
click.secho(
|
65
|
-
f"You appear not to have the {
|
56
|
+
f"You appear not to have the {db_connection.client.executable_name!r} program installed or on your path.",
|
66
57
|
fg="red",
|
67
58
|
err=True,
|
68
59
|
)
|
@@ -85,24 +76,23 @@ def db_wait():
|
|
85
76
|
attempts = 0
|
86
77
|
while True:
|
87
78
|
attempts += 1
|
88
|
-
waiting_for =
|
79
|
+
waiting_for = False
|
89
80
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
waiting_for.append(conn.alias)
|
81
|
+
try:
|
82
|
+
db_connection.ensure_connection()
|
83
|
+
except OperationalError:
|
84
|
+
waiting_for = True
|
95
85
|
|
96
86
|
if waiting_for:
|
97
87
|
if attempts > 1:
|
98
88
|
# After the first attempt, start printing them
|
99
89
|
click.secho(
|
100
|
-
f"Waiting for database (attempt {attempts})
|
90
|
+
f"Waiting for database (attempt {attempts})",
|
101
91
|
fg="yellow",
|
102
92
|
)
|
103
93
|
time.sleep(1.5)
|
104
94
|
else:
|
105
|
-
click.secho(
|
95
|
+
click.secho("Database ready", fg="green")
|
106
96
|
break
|
107
97
|
|
108
98
|
|
@@ -318,21 +308,8 @@ def makemigrations(
|
|
318
308
|
loader = MigrationLoader(None, ignore_no_migrations=True)
|
319
309
|
|
320
310
|
# Raise an error if any migrations are applied before their dependencies.
|
321
|
-
|
322
|
-
|
323
|
-
}
|
324
|
-
# Non-default databases are only checked if database routers used.
|
325
|
-
aliases_to_check = connections if settings.DATABASE_ROUTERS else [DEFAULT_DB_ALIAS]
|
326
|
-
for alias in sorted(aliases_to_check):
|
327
|
-
connection = connections[alias]
|
328
|
-
if any(
|
329
|
-
router.allow_migrate(
|
330
|
-
connection.alias, package_label, model_name=model._meta.object_name
|
331
|
-
)
|
332
|
-
for package_label in consistency_check_labels
|
333
|
-
for model in models_registry.get_models(package_label=package_label)
|
334
|
-
):
|
335
|
-
loader.check_consistent_history(connection)
|
311
|
+
# Only the default db_connection is supported.
|
312
|
+
loader.check_consistent_history(db_connection)
|
336
313
|
|
337
314
|
# Check for conflicts
|
338
315
|
conflicts = loader.detect_conflicts()
|
@@ -422,11 +399,6 @@ def makemigrations(
|
|
422
399
|
@cli.command()
|
423
400
|
@click.argument("package_label", required=False)
|
424
401
|
@click.argument("migration_name", required=False)
|
425
|
-
@click.option(
|
426
|
-
"--database",
|
427
|
-
default=DEFAULT_DB_ALIAS,
|
428
|
-
help="Nominates a database to synchronize. Defaults to the 'default' database.",
|
429
|
-
)
|
430
402
|
@click.option(
|
431
403
|
"--fake", is_flag=True, help="Mark migrations as run without actually running them."
|
432
404
|
)
|
@@ -468,7 +440,6 @@ def makemigrations(
|
|
468
440
|
def migrate(
|
469
441
|
package_label,
|
470
442
|
migration_name,
|
471
|
-
database,
|
472
443
|
fake,
|
473
444
|
fake_initial,
|
474
445
|
plan,
|
@@ -512,16 +483,14 @@ def migrate(
|
|
512
483
|
return prefix + operation.describe() + truncated.chars(40), is_error
|
513
484
|
|
514
485
|
# Get the database we're operating from
|
515
|
-
connection = connections[database]
|
516
|
-
|
517
486
|
# Hook for backends needing any database preparation
|
518
|
-
|
487
|
+
db_connection.prepare_database()
|
519
488
|
|
520
489
|
# Work out which packages have migrations and which do not
|
521
|
-
executor = MigrationExecutor(
|
490
|
+
executor = MigrationExecutor(db_connection, migration_progress_callback)
|
522
491
|
|
523
492
|
# Raise an error if any migrations are applied before their dependencies.
|
524
|
-
executor.loader.check_consistent_history(
|
493
|
+
executor.loader.check_consistent_history(db_connection)
|
525
494
|
|
526
495
|
# Before anything else, see if there's conflicting packages and drop out
|
527
496
|
# hard if there are any
|
@@ -864,11 +833,6 @@ def optimize_migration(package_label, migration_name, check, verbosity):
|
|
864
833
|
|
865
834
|
@cli.command()
|
866
835
|
@click.argument("package_labels", nargs=-1)
|
867
|
-
@click.option(
|
868
|
-
"--database",
|
869
|
-
default=DEFAULT_DB_ALIAS,
|
870
|
-
help="Nominates a database to show migrations for. Defaults to the 'default' database.",
|
871
|
-
)
|
872
836
|
@click.option(
|
873
837
|
"--format",
|
874
838
|
type=click.Choice(["list", "plan"]),
|
@@ -882,7 +846,7 @@ def optimize_migration(package_label, migration_name, check, verbosity):
|
|
882
846
|
default=1,
|
883
847
|
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
|
884
848
|
)
|
885
|
-
def show_migrations(package_labels,
|
849
|
+
def show_migrations(package_labels, format, verbosity):
|
886
850
|
"""Shows all available migrations for the current project"""
|
887
851
|
|
888
852
|
def _validate_package_names(package_names):
|
@@ -896,14 +860,14 @@ def show_migrations(package_labels, database, format, verbosity):
|
|
896
860
|
if has_bad_names:
|
897
861
|
sys.exit(2)
|
898
862
|
|
899
|
-
def show_list(
|
863
|
+
def show_list(db_connection, package_names):
|
900
864
|
"""
|
901
865
|
Show a list of all migrations on the system, or only those of
|
902
866
|
some named packages.
|
903
867
|
"""
|
904
868
|
# Load migrations from disk/DB
|
905
|
-
loader = MigrationLoader(
|
906
|
-
recorder = MigrationRecorder(
|
869
|
+
loader = MigrationLoader(db_connection, ignore_no_migrations=True)
|
870
|
+
recorder = MigrationRecorder(db_connection)
|
907
871
|
recorded_migrations = recorder.applied_migrations()
|
908
872
|
|
909
873
|
graph = loader.graph
|
@@ -972,13 +936,13 @@ def show_migrations(package_labels, database, format, verbosity):
|
|
972
936
|
for name in sorted(prunable_by_package[package]):
|
973
937
|
click.echo(f" - {name}")
|
974
938
|
|
975
|
-
def show_plan(
|
939
|
+
def show_plan(db_connection, package_names):
|
976
940
|
"""
|
977
941
|
Show all known migrations (or only those of the specified package_names)
|
978
942
|
in the order they will be applied.
|
979
943
|
"""
|
980
944
|
# Load migrations from disk/DB
|
981
|
-
loader = MigrationLoader(
|
945
|
+
loader = MigrationLoader(db_connection)
|
982
946
|
graph = loader.graph
|
983
947
|
if package_names:
|
984
948
|
_validate_package_names(package_names)
|
@@ -1017,12 +981,11 @@ def show_migrations(package_labels, database, format, verbosity):
|
|
1017
981
|
click.secho("(no migrations)", fg="red")
|
1018
982
|
|
1019
983
|
# Get the database we're operating from
|
1020
|
-
connection = connections[database]
|
1021
984
|
|
1022
985
|
if format == "plan":
|
1023
|
-
show_plan(
|
986
|
+
show_plan(db_connection, package_labels)
|
1024
987
|
else:
|
1025
|
-
show_list(
|
988
|
+
show_list(db_connection, package_labels)
|
1026
989
|
|
1027
990
|
|
1028
991
|
@cli.command()
|
@@ -1082,7 +1045,7 @@ def squash_migrations(
|
|
1082
1045
|
raise click.ClickException(str(err))
|
1083
1046
|
|
1084
1047
|
# Load the current graph state, check the app and migration they asked for exists
|
1085
|
-
loader = MigrationLoader(
|
1048
|
+
loader = MigrationLoader(db_connection)
|
1086
1049
|
if package_label not in loader.migrated_packages:
|
1087
1050
|
raise click.ClickException(
|
1088
1051
|
f"Package '{package_label}' does not have migrations (so squashmigrations on it makes no sense)"
|
plain/models/connections.py
CHANGED
@@ -1,15 +1,8 @@
|
|
1
|
-
from functools import cached_property
|
2
1
|
from importlib import import_module
|
3
2
|
from threading import local
|
4
3
|
from typing import Any, TypedDict
|
5
4
|
|
6
|
-
from plain.exceptions import ImproperlyConfigured
|
7
5
|
from plain.runtime import settings as plain_settings
|
8
|
-
from plain.utils.module_loading import import_string
|
9
|
-
|
10
|
-
from .exceptions import ConnectionDoesNotExist
|
11
|
-
|
12
|
-
DEFAULT_DB_ALIAS = "default"
|
13
6
|
|
14
7
|
|
15
8
|
class DatabaseConfig(TypedDict, total=False):
|
@@ -28,167 +21,57 @@ class DatabaseConfig(TypedDict, total=False):
|
|
28
21
|
USER: str
|
29
22
|
|
30
23
|
|
31
|
-
class
|
32
|
-
"""
|
33
|
-
Handler for database connections. Provides lazy connection creation
|
34
|
-
and convenience methods for managing multiple database connections.
|
35
|
-
"""
|
24
|
+
class DatabaseConnection:
|
25
|
+
"""Lazy access to the single configured database connection."""
|
36
26
|
|
37
|
-
|
38
|
-
self._settings: dict[str, DatabaseConfig] = {}
|
39
|
-
self._connections = local()
|
27
|
+
__slots__ = ("_settings", "_local")
|
40
28
|
|
41
|
-
|
42
|
-
|
43
|
-
self.
|
44
|
-
return self._settings
|
29
|
+
def __init__(self):
|
30
|
+
self._settings: DatabaseConfig = {}
|
31
|
+
self._local = local()
|
45
32
|
|
46
33
|
def configure_settings(self) -> DatabaseConfig:
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
test_settings = conn.setdefault("TEST", {})
|
65
|
-
default_test_settings = [
|
66
|
-
("CHARSET", None),
|
67
|
-
("COLLATION", None),
|
68
|
-
("MIRROR", None),
|
69
|
-
("NAME", None),
|
70
|
-
]
|
71
|
-
for key, value in default_test_settings:
|
72
|
-
test_settings.setdefault(key, value)
|
73
|
-
|
74
|
-
return databases
|
75
|
-
|
76
|
-
def create_connection(self, alias):
|
77
|
-
database_config = self.settings[alias]
|
78
|
-
backend = import_module(f"{database_config['ENGINE']}.base")
|
79
|
-
return backend.DatabaseWrapper(database_config, alias)
|
80
|
-
|
81
|
-
def __getitem__(self, alias):
|
82
|
-
try:
|
83
|
-
return getattr(self._connections, alias)
|
84
|
-
except AttributeError:
|
85
|
-
if alias not in self.settings:
|
86
|
-
raise ConnectionDoesNotExist(f"The connection '{alias}' doesn't exist.")
|
87
|
-
conn = self.create_connection(alias)
|
88
|
-
setattr(self._connections, alias, conn)
|
89
|
-
return conn
|
90
|
-
|
91
|
-
def __setitem__(self, key, value):
|
92
|
-
setattr(self._connections, key, value)
|
93
|
-
|
94
|
-
def __delitem__(self, key):
|
95
|
-
delattr(self._connections, key)
|
96
|
-
|
97
|
-
def __iter__(self):
|
98
|
-
return iter(self.settings)
|
99
|
-
|
100
|
-
def all(self, initialized_only=False):
|
101
|
-
return [
|
102
|
-
self[alias]
|
103
|
-
for alias in self
|
104
|
-
# If initialized_only is True, return only initialized connections.
|
105
|
-
if not initialized_only or hasattr(self._connections, alias)
|
34
|
+
database = plain_settings.DATABASE
|
35
|
+
|
36
|
+
database.setdefault("AUTOCOMMIT", True)
|
37
|
+
database.setdefault("CONN_MAX_AGE", 0)
|
38
|
+
database.setdefault("CONN_HEALTH_CHECKS", False)
|
39
|
+
database.setdefault("OPTIONS", {})
|
40
|
+
database.setdefault("TIME_ZONE", None)
|
41
|
+
for setting in ["NAME", "USER", "PASSWORD", "HOST", "PORT"]:
|
42
|
+
database.setdefault(setting, "")
|
43
|
+
|
44
|
+
test_settings = database.setdefault("TEST", {})
|
45
|
+
default_test_settings = [
|
46
|
+
("CHARSET", None),
|
47
|
+
("COLLATION", None),
|
48
|
+
("MIRROR", None),
|
49
|
+
("NAME", None),
|
106
50
|
]
|
51
|
+
for key, value in default_test_settings:
|
52
|
+
test_settings.setdefault(key, value)
|
53
|
+
|
54
|
+
return database
|
55
|
+
|
56
|
+
def create_connection(self):
|
57
|
+
database_config = self.configure_settings()
|
58
|
+
backend = import_module(f"{database_config['ENGINE']}.base")
|
59
|
+
return backend.DatabaseWrapper(database_config)
|
60
|
+
|
61
|
+
def has_connection(self):
|
62
|
+
return hasattr(self._local, "conn")
|
63
|
+
|
64
|
+
def __getattr__(self, attr):
|
65
|
+
if not self.has_connection():
|
66
|
+
self._local.conn = self.create_connection()
|
67
|
+
|
68
|
+
return getattr(self._local.conn, attr)
|
69
|
+
|
70
|
+
def __setattr__(self, name, value):
|
71
|
+
if name.startswith("_"):
|
72
|
+
super().__setattr__(name, value)
|
73
|
+
else:
|
74
|
+
if not self.has_connection():
|
75
|
+
self._local.conn = self.create_connection()
|
107
76
|
|
108
|
-
|
109
|
-
for conn in self.all(initialized_only=True):
|
110
|
-
conn.close()
|
111
|
-
|
112
|
-
|
113
|
-
class ConnectionRouter:
|
114
|
-
def __init__(self, routers=None):
|
115
|
-
"""
|
116
|
-
If routers is not specified, default to settings.DATABASE_ROUTERS.
|
117
|
-
"""
|
118
|
-
self._routers = routers
|
119
|
-
|
120
|
-
@cached_property
|
121
|
-
def routers(self):
|
122
|
-
if self._routers is None:
|
123
|
-
self._routers = plain_settings.DATABASE_ROUTERS
|
124
|
-
routers = []
|
125
|
-
for r in self._routers:
|
126
|
-
if isinstance(r, str):
|
127
|
-
router = import_string(r)()
|
128
|
-
else:
|
129
|
-
router = r
|
130
|
-
routers.append(router)
|
131
|
-
return routers
|
132
|
-
|
133
|
-
def _router_func(action):
|
134
|
-
def _route_db(self, model, **hints):
|
135
|
-
chosen_db = None
|
136
|
-
for router in self.routers:
|
137
|
-
try:
|
138
|
-
method = getattr(router, action)
|
139
|
-
except AttributeError:
|
140
|
-
# If the router doesn't have a method, skip to the next one.
|
141
|
-
pass
|
142
|
-
else:
|
143
|
-
chosen_db = method(model, **hints)
|
144
|
-
if chosen_db:
|
145
|
-
return chosen_db
|
146
|
-
instance = hints.get("instance")
|
147
|
-
if instance is not None and instance._state.db:
|
148
|
-
return instance._state.db
|
149
|
-
return DEFAULT_DB_ALIAS
|
150
|
-
|
151
|
-
return _route_db
|
152
|
-
|
153
|
-
db_for_read = _router_func("db_for_read")
|
154
|
-
db_for_write = _router_func("db_for_write")
|
155
|
-
|
156
|
-
def allow_relation(self, obj1, obj2, **hints):
|
157
|
-
for router in self.routers:
|
158
|
-
try:
|
159
|
-
method = router.allow_relation
|
160
|
-
except AttributeError:
|
161
|
-
# If the router doesn't have a method, skip to the next one.
|
162
|
-
pass
|
163
|
-
else:
|
164
|
-
allow = method(obj1, obj2, **hints)
|
165
|
-
if allow is not None:
|
166
|
-
return allow
|
167
|
-
return obj1._state.db == obj2._state.db
|
168
|
-
|
169
|
-
def allow_migrate(self, db, package_label, **hints):
|
170
|
-
for router in self.routers:
|
171
|
-
try:
|
172
|
-
method = router.allow_migrate
|
173
|
-
except AttributeError:
|
174
|
-
# If the router doesn't have a method, skip to the next one.
|
175
|
-
continue
|
176
|
-
|
177
|
-
allow = method(db, package_label, **hints)
|
178
|
-
|
179
|
-
if allow is not None:
|
180
|
-
return allow
|
181
|
-
return True
|
182
|
-
|
183
|
-
def allow_migrate_model(self, db, model):
|
184
|
-
return self.allow_migrate(
|
185
|
-
db,
|
186
|
-
model._meta.package_label,
|
187
|
-
model_name=model._meta.model_name,
|
188
|
-
model=model,
|
189
|
-
)
|
190
|
-
|
191
|
-
def get_migratable_models(self, models_registry, package_label, db):
|
192
|
-
"""Return app models allowed to be migrated on provided db."""
|
193
|
-
models = models_registry.get_models(package_label=package_label)
|
194
|
-
return [model for model in models if self.allow_migrate_model(db, model)]
|
77
|
+
setattr(self._local.conn, name, value)
|
plain/models/constraints.py
CHANGED
@@ -2,7 +2,7 @@ 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
|
5
|
+
from plain.models.db import db_connection
|
6
6
|
from plain.models.expressions import Exists, ExpressionList, F, OrderBy
|
7
7
|
from plain.models.indexes import IndexExpression
|
8
8
|
from plain.models.lookups import Exact
|
@@ -41,7 +41,7 @@ class BaseConstraint:
|
|
41
41
|
def remove_sql(self, model, schema_editor):
|
42
42
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
43
43
|
|
44
|
-
def validate(self, model, instance, exclude=None
|
44
|
+
def validate(self, model, instance, exclude=None):
|
45
45
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
46
46
|
|
47
47
|
def get_violation_error_message(self):
|
@@ -83,7 +83,7 @@ class CheckConstraint(BaseConstraint):
|
|
83
83
|
def _get_check_sql(self, model, schema_editor):
|
84
84
|
query = Query(model=model, alias_cols=False)
|
85
85
|
where = query.build_where(self.check)
|
86
|
-
compiler = query.get_compiler(
|
86
|
+
compiler = query.get_compiler()
|
87
87
|
sql, params = where.as_sql(compiler, schema_editor.connection)
|
88
88
|
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
89
89
|
|
@@ -98,10 +98,10 @@ class CheckConstraint(BaseConstraint):
|
|
98
98
|
def remove_sql(self, model, schema_editor):
|
99
99
|
return schema_editor._delete_check_sql(model, self.name)
|
100
100
|
|
101
|
-
def validate(self, model, instance, exclude=None
|
101
|
+
def validate(self, model, instance, exclude=None):
|
102
102
|
against = instance._get_field_value_map(meta=model._meta, exclude=exclude)
|
103
103
|
try:
|
104
|
-
if not Q(self.check).check(against
|
104
|
+
if not Q(self.check).check(against):
|
105
105
|
raise ValidationError(
|
106
106
|
self.get_violation_error_message(), code=self.violation_error_code
|
107
107
|
)
|
@@ -227,7 +227,7 @@ class UniqueConstraint(BaseConstraint):
|
|
227
227
|
return None
|
228
228
|
query = Query(model=model, alias_cols=False)
|
229
229
|
where = query.build_where(self.condition)
|
230
|
-
compiler = query.get_compiler(
|
230
|
+
compiler = query.get_compiler()
|
231
231
|
sql, params = where.as_sql(compiler, schema_editor.connection)
|
232
232
|
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
233
233
|
|
@@ -347,8 +347,8 @@ class UniqueConstraint(BaseConstraint):
|
|
347
347
|
kwargs["opclasses"] = self.opclasses
|
348
348
|
return path, self.expressions, kwargs
|
349
349
|
|
350
|
-
def validate(self, model, instance, exclude=None
|
351
|
-
queryset = model._default_manager
|
350
|
+
def validate(self, model, instance, exclude=None):
|
351
|
+
queryset = model._default_manager
|
352
352
|
if self.fields:
|
353
353
|
lookup_kwargs = {}
|
354
354
|
for field_name in self.fields:
|
@@ -358,7 +358,7 @@ class UniqueConstraint(BaseConstraint):
|
|
358
358
|
lookup_value = getattr(instance, field.attname)
|
359
359
|
if lookup_value is None or (
|
360
360
|
lookup_value == ""
|
361
|
-
and
|
361
|
+
and db_connection.features.interprets_empty_strings_as_nulls
|
362
362
|
):
|
363
363
|
# A composite constraint containing NULL value cannot cause
|
364
364
|
# a violation since NULL != NULL in SQL.
|
@@ -410,7 +410,7 @@ class UniqueConstraint(BaseConstraint):
|
|
410
410
|
against = instance._get_field_value_map(meta=model._meta, exclude=exclude)
|
411
411
|
try:
|
412
412
|
if (self.condition & Exists(queryset.filter(self.condition))).check(
|
413
|
-
against
|
413
|
+
against
|
414
414
|
):
|
415
415
|
raise ValidationError(
|
416
416
|
self.get_violation_error_message(),
|
plain/models/db.py
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
from plain import signals
|
2
2
|
|
3
|
-
from .connections import
|
4
|
-
DEFAULT_DB_ALIAS,
|
5
|
-
ConnectionHandler,
|
6
|
-
ConnectionRouter,
|
7
|
-
)
|
3
|
+
from .connections import DatabaseConnection
|
8
4
|
from .exceptions import (
|
9
5
|
ConnectionDoesNotExist,
|
10
6
|
DatabaseError,
|
@@ -22,15 +18,13 @@ from .exceptions import (
|
|
22
18
|
PLAIN_VERSION_PICKLE_KEY = "_plain_version"
|
23
19
|
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
router = ConnectionRouter()
|
21
|
+
db_connection = DatabaseConnection()
|
28
22
|
|
29
23
|
|
30
24
|
# Register an event to reset saved queries when a Plain request is started.
|
31
25
|
def reset_queries(**kwargs):
|
32
|
-
|
33
|
-
|
26
|
+
if db_connection.has_connection():
|
27
|
+
db_connection.queries_log.clear()
|
34
28
|
|
35
29
|
|
36
30
|
signals.request_started.connect(reset_queries)
|
@@ -39,8 +33,8 @@ signals.request_started.connect(reset_queries)
|
|
39
33
|
# Register an event to reset transaction state and close connections past
|
40
34
|
# their lifetime.
|
41
35
|
def close_old_connections(**kwargs):
|
42
|
-
|
43
|
-
|
36
|
+
if db_connection.has_connection():
|
37
|
+
db_connection.close_if_unusable_or_obsolete()
|
44
38
|
|
45
39
|
|
46
40
|
signals.request_started.connect(close_old_connections)
|
@@ -48,9 +42,7 @@ signals.request_finished.connect(close_old_connections)
|
|
48
42
|
|
49
43
|
|
50
44
|
__all__ = [
|
51
|
-
"
|
52
|
-
"router",
|
53
|
-
"DEFAULT_DB_ALIAS",
|
45
|
+
"db_connection",
|
54
46
|
"PLAIN_VERSION_PICKLE_KEY",
|
55
47
|
"Error",
|
56
48
|
"InterfaceError",
|
plain/models/default_settings.py
CHANGED
@@ -2,25 +2,18 @@ from os import environ
|
|
2
2
|
|
3
3
|
from . import database_url
|
4
4
|
|
5
|
-
# Make
|
6
|
-
|
5
|
+
# Make DATABASE a required setting
|
6
|
+
DATABASE: dict
|
7
7
|
|
8
|
-
# Automatically configure
|
8
|
+
# Automatically configure DATABASE if a DATABASE_URL was given in the environment
|
9
9
|
if "DATABASE_URL" in environ:
|
10
|
-
|
11
|
-
"
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
"1",
|
21
|
-
],
|
22
|
-
)
|
23
|
-
}
|
24
|
-
|
25
|
-
# Classes used to implement DB routing behavior.
|
26
|
-
DATABASE_ROUTERS = []
|
10
|
+
DATABASE = database_url.parse_database_url(
|
11
|
+
environ["DATABASE_URL"],
|
12
|
+
# Enable persistent connections by default
|
13
|
+
conn_max_age=int(environ.get("DATABASE_CONN_MAX_AGE", 600)),
|
14
|
+
conn_health_checks=environ.get("DATABASE_CONN_HEALTH_CHECKS", "true").lower()
|
15
|
+
in [
|
16
|
+
"true",
|
17
|
+
"1",
|
18
|
+
],
|
19
|
+
)
|