plain.models 0.48.0__tar.gz → 0.49.1__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.48.0 → plain_models-0.49.1}/.gitignore +1 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/PKG-INFO +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/CHANGELOG.md +26 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/__init__.py +0 -3
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/aggregates.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/compiler.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/operations.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/base.py +36 -94
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/constraints.py +2 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/deletion.py +3 -5
- plain_models-0.49.1/plain/models/exceptions.py +196 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/expressions.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/__init__.py +3 -41
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/related.py +15 -22
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/related_descriptors.py +1 -3
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/related_managers.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/reverse_related.py +3 -5
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/forms.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/lookups.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/state.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/options.py +4 -16
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/query.py +12 -10
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/query_utils.py +2 -2
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/compiler.py +6 -6
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/datastructures.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/query.py +4 -4
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/subqueries.py +3 -3
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/where.py +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/pyproject.toml +1 -1
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_exceptions.py +1 -1
- plain_models-0.48.0/plain/models/exceptions.py +0 -88
- {plain_models-0.48.0 → plain_models-0.49.1}/LICENSE +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/README.md +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/AGENTS.md +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/README.md +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/base.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/client.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/creation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/features.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/introspection.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/operations.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/schema.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/base/validation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/ddl_references.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/base.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/client.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/creation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/features.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/introspection.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/operations.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/schema.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/mysql/validation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/base.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/client.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/creation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/features.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/introspection.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/operations.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/postgresql/schema.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/_functions.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/base.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/client.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/creation.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/features.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/introspection.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/sqlite3/schema.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backends/utils.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backups/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backups/cli.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backups/clients.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/backups/core.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/cli.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/config.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/connections.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/constants.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/database_url.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/db.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/default_settings.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/entrypoints.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/enums.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/json.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/mixins.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/fields/related_lookups.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/comparison.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/datetime.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/math.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/mixins.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/text.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/functions/window.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/indexes.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/autodetector.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/exceptions.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/executor.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/graph.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/loader.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/migration.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/operations/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/operations/base.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/operations/fields.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/operations/models.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/operations/special.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/optimizer.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/questioner.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/recorder.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/serializer.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/utils.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/migrations/writer.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/otel.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/preflight.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/registry.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/sql/constants.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/test/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/test/pytest.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/test/utils.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/transaction.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/plain/models/utils.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/examples/models.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/settings.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/app/urls.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_database_url.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_delete_behaviors.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_manager_assignment.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_models.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_related_descriptors.py +0 -0
- {plain_models-0.48.0 → plain_models-0.49.1}/tests/test_related_manager_api.py +0 -0
@@ -1,5 +1,31 @@
|
|
1
1
|
# plain-models changelog
|
2
2
|
|
3
|
+
## [0.49.1](https://github.com/dropseed/plain/releases/plain-models@0.49.1) (2025-09-29)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Fixed `get_field_display()` method to accept field name as string instead of field object ([1c20405](https://github.com/dropseed/plain/commit/1c20405ac3))
|
8
|
+
|
9
|
+
### Upgrade instructions
|
10
|
+
|
11
|
+
- No changes required
|
12
|
+
|
13
|
+
## [0.49.0](https://github.com/dropseed/plain/releases/plain-models@0.49.0) (2025-09-29)
|
14
|
+
|
15
|
+
### What's changed
|
16
|
+
|
17
|
+
- Model exceptions (`FieldDoesNotExist`, `FieldError`, `ObjectDoesNotExist`, `MultipleObjectsReturned`, `EmptyResultSet`, `FullResultSet`) have been moved from `plain.exceptions` to `plain.models.exceptions` ([1c02564](https://github.com/dropseed/plain/commit/1c02564561))
|
18
|
+
- The `get_FOO_display()` methods for fields with choices have been replaced with a single `get_field_display(field_name)` method ([e796e71](https://github.com/dropseed/plain/commit/e796e71e02))
|
19
|
+
- The `get_next_by_*` and `get_previous_by_*` methods for date fields have been removed ([3a5b8a8](https://github.com/dropseed/plain/commit/3a5b8a89d1))
|
20
|
+
- The `id` primary key field is now defined directly on the Model base class instead of being added dynamically via Options ([e164dc7](https://github.com/dropseed/plain/commit/e164dc7982))
|
21
|
+
- Model `DoesNotExist` and `MultipleObjectsReturned` exceptions now use descriptors for better performance ([8f54ea3](https://github.com/dropseed/plain/commit/8f54ea3a62))
|
22
|
+
|
23
|
+
### Upgrade instructions
|
24
|
+
|
25
|
+
- Update imports for model exceptions from `plain.exceptions` to `plain.models.exceptions` (e.g., `from plain.exceptions import ObjectDoesNotExist` becomes `from plain.models.exceptions import ObjectDoesNotExist`)
|
26
|
+
- Replace any usage of `instance.get_FOO_display()` with `instance.get_field_display("FOO")` where FOO is the field name
|
27
|
+
- Remove any usage of `get_next_by_*` and `get_previous_by_*` methods - use QuerySet ordering instead (e.g., `Model.query.filter(date__gt=obj.date).order_by("date").first()`)
|
28
|
+
|
3
29
|
## [0.48.0](https://github.com/dropseed/plain/releases/plain-models@0.48.0) (2025-09-26)
|
4
30
|
|
5
31
|
### What's changed
|
@@ -1,5 +1,3 @@
|
|
1
|
-
from plain.exceptions import ObjectDoesNotExist
|
2
|
-
|
3
1
|
from . import (
|
4
2
|
preflight, # noqa
|
5
3
|
)
|
@@ -78,7 +76,6 @@ from .fields.reverse_related import ( # isort:skip
|
|
78
76
|
|
79
77
|
__all__ = aggregates_all + constraints_all + enums_all + fields_all + indexes_all
|
80
78
|
__all__ += [
|
81
|
-
"ObjectDoesNotExist",
|
82
79
|
"CASCADE",
|
83
80
|
"DO_NOTHING",
|
84
81
|
"PROTECT",
|
@@ -2,7 +2,7 @@
|
|
2
2
|
Classes to represent the definitions of aggregate functions.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from plain.exceptions import FieldError, FullResultSet
|
5
|
+
from plain.models.exceptions import FieldError, FullResultSet
|
6
6
|
from plain.models.expressions import Case, Func, Star, Value, When
|
7
7
|
from plain.models.fields import IntegerField
|
8
8
|
from plain.models.functions.comparison import Coalesce
|
@@ -4,10 +4,10 @@ import uuid
|
|
4
4
|
from functools import cached_property, lru_cache
|
5
5
|
|
6
6
|
from plain import models
|
7
|
-
from plain.exceptions import FieldError
|
8
7
|
from plain.models.backends.base.operations import BaseDatabaseOperations
|
9
8
|
from plain.models.constants import OnConflict
|
10
9
|
from plain.models.db import DatabaseError, NotSupportedError
|
10
|
+
from plain.models.exceptions import FieldError
|
11
11
|
from plain.models.expressions import Col
|
12
12
|
from plain.utils import timezone
|
13
13
|
from plain.utils.dateparse import parse_date, parse_datetime, parse_time
|
@@ -4,13 +4,7 @@ import warnings
|
|
4
4
|
from itertools import chain
|
5
5
|
|
6
6
|
import plain.runtime
|
7
|
-
from plain.exceptions import
|
8
|
-
NON_FIELD_ERRORS,
|
9
|
-
FieldDoesNotExist,
|
10
|
-
MultipleObjectsReturned,
|
11
|
-
ObjectDoesNotExist,
|
12
|
-
ValidationError,
|
13
|
-
)
|
7
|
+
from plain.exceptions import NON_FIELD_ERRORS, ValidationError
|
14
8
|
from plain.models import models_registry, transaction
|
15
9
|
from plain.models.constants import LOOKUP_SEP
|
16
10
|
from plain.models.constraints import CheckConstraint, UniqueConstraint
|
@@ -20,8 +14,13 @@ from plain.models.db import (
|
|
20
14
|
db_connection,
|
21
15
|
)
|
22
16
|
from plain.models.deletion import Collector
|
17
|
+
from plain.models.exceptions import (
|
18
|
+
DoesNotExistDescriptor,
|
19
|
+
FieldDoesNotExist,
|
20
|
+
MultipleObjectsReturnedDescriptor,
|
21
|
+
)
|
23
22
|
from plain.models.expressions import RawSQL, Value
|
24
|
-
from plain.models.fields import NOT_PROVIDED
|
23
|
+
from plain.models.fields import NOT_PROVIDED, PrimaryKeyField
|
25
24
|
from plain.models.fields.reverse_related import ForeignObjectRel
|
26
25
|
from plain.models.options import Options
|
27
26
|
from plain.models.query import F, Q, QuerySet
|
@@ -42,11 +41,6 @@ class Deferred:
|
|
42
41
|
DEFERRED = Deferred()
|
43
42
|
|
44
43
|
|
45
|
-
def _has_contribute_to_class(value):
|
46
|
-
# Only call contribute_to_class() if it's bound.
|
47
|
-
return not inspect.isclass(value) and hasattr(value, "contribute_to_class")
|
48
|
-
|
49
|
-
|
50
44
|
class ModelBase(type):
|
51
45
|
"""Metaclass for all models."""
|
52
46
|
|
@@ -71,7 +65,6 @@ class ModelBase(type):
|
|
71
65
|
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
72
66
|
|
73
67
|
new_class._setup_meta()
|
74
|
-
new_class._add_exceptions()
|
75
68
|
|
76
69
|
# Now go back over all the attrs on this class see if they have a contribute_to_class() method.
|
77
70
|
# Attributes with contribute_to_class are fields and meta options.
|
@@ -79,7 +72,9 @@ class ModelBase(type):
|
|
79
72
|
if attr_name.startswith("_"):
|
80
73
|
continue
|
81
74
|
|
82
|
-
if
|
75
|
+
if not inspect.isclass(attr_value) and hasattr(
|
76
|
+
attr_value, "contribute_to_class"
|
77
|
+
):
|
83
78
|
if attr_name not in attrs:
|
84
79
|
# If the field came from an inherited class/mixin,
|
85
80
|
# we need to make a copy of it to avoid altering the
|
@@ -87,25 +82,17 @@ class ModelBase(type):
|
|
87
82
|
field = copy.deepcopy(attr_value)
|
88
83
|
else:
|
89
84
|
field = attr_value
|
90
|
-
|
91
|
-
|
92
|
-
new_class._meta.concrete_model = new_class
|
93
|
-
|
94
|
-
# Copy indexes so that index names are unique when models extend another class.
|
95
|
-
new_class._meta.indexes = [
|
96
|
-
copy.deepcopy(idx) for idx in new_class._meta.indexes
|
97
|
-
]
|
85
|
+
field.contribute_to_class(new_class, attr_name)
|
98
86
|
|
99
|
-
|
87
|
+
# Set the name of _meta.indexes. This can't be done in
|
88
|
+
# Options.contribute_to_class() because fields haven't been added to
|
89
|
+
# the model at that point.
|
90
|
+
for index in new_class._meta.indexes:
|
91
|
+
if not index.name:
|
92
|
+
index.set_name_with_model(new_class)
|
100
93
|
|
101
94
|
return new_class
|
102
95
|
|
103
|
-
def add_to_class(cls, name, value):
|
104
|
-
if _has_contribute_to_class(value):
|
105
|
-
value.contribute_to_class(cls, name)
|
106
|
-
else:
|
107
|
-
setattr(cls, name, value)
|
108
|
-
|
109
96
|
def _setup_meta(cls):
|
110
97
|
name = cls.__name__
|
111
98
|
module = cls.__module__
|
@@ -127,45 +114,7 @@ class ModelBase(type):
|
|
127
114
|
else:
|
128
115
|
package_label = package_config.package_label
|
129
116
|
|
130
|
-
|
131
|
-
|
132
|
-
def _add_exceptions(cls):
|
133
|
-
cls.DoesNotExist = type(
|
134
|
-
"DoesNotExist",
|
135
|
-
(ObjectDoesNotExist,),
|
136
|
-
{
|
137
|
-
"__module__": cls.__module__,
|
138
|
-
"__qualname__": f"{cls.__qualname__}.DoesNotExist",
|
139
|
-
},
|
140
|
-
)
|
141
|
-
|
142
|
-
cls.MultipleObjectsReturned = type(
|
143
|
-
"MultipleObjectsReturned",
|
144
|
-
(MultipleObjectsReturned,),
|
145
|
-
{
|
146
|
-
"__module__": cls.__module__,
|
147
|
-
"__qualname__": f"{cls.__qualname__}.MultipleObjectsReturned",
|
148
|
-
},
|
149
|
-
)
|
150
|
-
|
151
|
-
def _prepare(cls):
|
152
|
-
"""Create some methods once self._meta has been populated."""
|
153
|
-
opts = cls._meta
|
154
|
-
opts._prepare(cls)
|
155
|
-
|
156
|
-
# Give the class a docstring -- its definition.
|
157
|
-
if cls.__doc__ is None:
|
158
|
-
cls.__doc__ = "{}({})".format(
|
159
|
-
cls.__name__,
|
160
|
-
", ".join(f.name for f in opts.fields),
|
161
|
-
)
|
162
|
-
|
163
|
-
# Set the name of _meta.indexes. This can't be done in
|
164
|
-
# Options.contribute_to_class() because fields haven't been added to
|
165
|
-
# the model at that point.
|
166
|
-
for index in cls._meta.indexes:
|
167
|
-
if not index.name:
|
168
|
-
index.set_name_with_model(cls)
|
117
|
+
Options(meta, package_label).contribute_to_class(cls, "_meta")
|
169
118
|
|
170
119
|
@property
|
171
120
|
def query(cls) -> QuerySet:
|
@@ -194,8 +143,13 @@ class ModelState:
|
|
194
143
|
|
195
144
|
class Model(metaclass=ModelBase):
|
196
145
|
_meta: Options
|
197
|
-
|
198
|
-
|
146
|
+
|
147
|
+
# Use descriptors for exception classes instead of metaclass generation
|
148
|
+
DoesNotExist = DoesNotExistDescriptor()
|
149
|
+
MultipleObjectsReturned = MultipleObjectsReturnedDescriptor()
|
150
|
+
|
151
|
+
# Every model gets an automatic id field
|
152
|
+
id = PrimaryKeyField()
|
199
153
|
|
200
154
|
def __init__(self, *args, **kwargs):
|
201
155
|
# Alias some things as locals to avoid repeat global lookups
|
@@ -327,7 +281,7 @@ class Model(metaclass=ModelBase):
|
|
327
281
|
def __eq__(self, other):
|
328
282
|
if not isinstance(other, Model):
|
329
283
|
return NotImplemented
|
330
|
-
if self.
|
284
|
+
if self.__class__ != other.__class__:
|
331
285
|
return False
|
332
286
|
my_id = self.id
|
333
287
|
if my_id is None:
|
@@ -711,34 +665,22 @@ class Model(metaclass=ModelBase):
|
|
711
665
|
collector.collect([self])
|
712
666
|
return collector.delete()
|
713
667
|
|
714
|
-
def
|
668
|
+
def get_field_display(self, field_name: str) -> str:
|
669
|
+
"""Get the display value for a field, especially useful for fields with choices."""
|
670
|
+
# Get the field object from the field name
|
671
|
+
field = self._meta.get_field(field_name)
|
715
672
|
value = getattr(self, field.attname)
|
673
|
+
|
674
|
+
# If field has no choices, just return the value as string
|
675
|
+
if not hasattr(field, "flatchoices") or not field.flatchoices:
|
676
|
+
return force_str(value, strings_only=True)
|
677
|
+
|
678
|
+
# For fields with choices, look up the display value
|
716
679
|
choices_dict = dict(make_hashable(field.flatchoices))
|
717
|
-
# force_str() to coerce lazy strings.
|
718
680
|
return force_str(
|
719
681
|
choices_dict.get(make_hashable(value), value), strings_only=True
|
720
682
|
)
|
721
683
|
|
722
|
-
def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
|
723
|
-
if not self.id:
|
724
|
-
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
|
725
|
-
op = "gt" if is_next else "lt"
|
726
|
-
order = "" if is_next else "-"
|
727
|
-
param = getattr(self, field.attname)
|
728
|
-
q = Q.create([(field.name, param), (f"id__{op}", self.id)], connector=Q.AND)
|
729
|
-
q = Q.create([q, (f"{field.name}__{op}", param)], connector=Q.OR)
|
730
|
-
qs = (
|
731
|
-
self.__class__.query.filter(**kwargs)
|
732
|
-
.filter(q)
|
733
|
-
.order_by(f"{order}{field.name}", f"{order}id")
|
734
|
-
)
|
735
|
-
try:
|
736
|
-
return qs[0]
|
737
|
-
except IndexError:
|
738
|
-
raise self.DoesNotExist(
|
739
|
-
f"{self.__class__._meta.object_name} matching query does not exist."
|
740
|
-
)
|
741
|
-
|
742
684
|
def _get_field_value_map(self, meta, exclude=None):
|
743
685
|
if exclude is None:
|
744
686
|
exclude = set()
|
@@ -1,7 +1,8 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from types import NoneType
|
3
3
|
|
4
|
-
from plain.exceptions import
|
4
|
+
from plain.exceptions import ValidationError
|
5
|
+
from plain.models.exceptions import FieldError
|
5
6
|
from plain.models.expressions import Exists, ExpressionList, F, OrderBy
|
6
7
|
from plain.models.indexes import IndexExpression
|
7
8
|
from plain.models.lookups import Exact
|
@@ -139,9 +139,7 @@ class Collector:
|
|
139
139
|
def add_dependency(self, model, dependency, reverse_dependency=False):
|
140
140
|
if reverse_dependency:
|
141
141
|
model, dependency = dependency, model
|
142
|
-
self.dependencies[model
|
143
|
-
dependency._meta.concrete_model
|
144
|
-
)
|
142
|
+
self.dependencies[model].add(dependency)
|
145
143
|
self.data.setdefault(dependency, self.data.default_factory())
|
146
144
|
|
147
145
|
def add_field_update(self, field, value, objs):
|
@@ -363,10 +361,10 @@ class Collector:
|
|
363
361
|
for model in models:
|
364
362
|
if model in sorted_models:
|
365
363
|
continue
|
366
|
-
dependencies = self.dependencies.get(model
|
364
|
+
dependencies = self.dependencies.get(model)
|
367
365
|
if not (dependencies and dependencies.difference(concrete_models)):
|
368
366
|
sorted_models.append(model)
|
369
|
-
concrete_models.add(model
|
367
|
+
concrete_models.add(model)
|
370
368
|
found = True
|
371
369
|
if not found:
|
372
370
|
return
|
@@ -0,0 +1,196 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
# MARK: Database Query Exceptions
|
4
|
+
|
5
|
+
|
6
|
+
class EmptyResultSet(Exception):
|
7
|
+
"""A database query predicate is impossible."""
|
8
|
+
|
9
|
+
pass
|
10
|
+
|
11
|
+
|
12
|
+
class FullResultSet(Exception):
|
13
|
+
"""A database query predicate is matches everything."""
|
14
|
+
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
# MARK: Model and Field Errors
|
19
|
+
|
20
|
+
|
21
|
+
class FieldDoesNotExist(Exception):
|
22
|
+
"""The requested model field does not exist"""
|
23
|
+
|
24
|
+
pass
|
25
|
+
|
26
|
+
|
27
|
+
class FieldError(Exception):
|
28
|
+
"""Some kind of problem with a model field."""
|
29
|
+
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class ObjectDoesNotExist(Exception):
|
34
|
+
"""The requested object does not exist"""
|
35
|
+
|
36
|
+
pass
|
37
|
+
|
38
|
+
|
39
|
+
class MultipleObjectsReturned(Exception):
|
40
|
+
"""The query returned multiple objects when only one was expected."""
|
41
|
+
|
42
|
+
pass
|
43
|
+
|
44
|
+
|
45
|
+
# MARK: Model Exception Descriptors
|
46
|
+
|
47
|
+
|
48
|
+
class DoesNotExistDescriptor:
|
49
|
+
"""Descriptor that creates a unique DoesNotExist exception class per model."""
|
50
|
+
|
51
|
+
def __init__(self) -> None:
|
52
|
+
self._exceptions_by_class: dict[type, type[ObjectDoesNotExist]] = {}
|
53
|
+
|
54
|
+
def __get__(self, instance: Any, owner: type | None) -> type[ObjectDoesNotExist]:
|
55
|
+
if owner is None:
|
56
|
+
return ObjectDoesNotExist # Return base class as fallback
|
57
|
+
|
58
|
+
# Create a unique exception class for this model if we haven't already
|
59
|
+
if owner not in self._exceptions_by_class:
|
60
|
+
exc_class = type(
|
61
|
+
"DoesNotExist",
|
62
|
+
(ObjectDoesNotExist,),
|
63
|
+
{
|
64
|
+
"__module__": owner.__module__,
|
65
|
+
"__qualname__": f"{owner.__qualname__}.DoesNotExist",
|
66
|
+
},
|
67
|
+
)
|
68
|
+
self._exceptions_by_class[owner] = exc_class
|
69
|
+
|
70
|
+
return self._exceptions_by_class[owner]
|
71
|
+
|
72
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
73
|
+
raise AttributeError("Cannot set DoesNotExist")
|
74
|
+
|
75
|
+
|
76
|
+
class MultipleObjectsReturnedDescriptor:
|
77
|
+
"""Descriptor that creates a unique MultipleObjectsReturned exception class per model."""
|
78
|
+
|
79
|
+
def __init__(self) -> None:
|
80
|
+
self._exceptions_by_class: dict[type, type[MultipleObjectsReturned]] = {}
|
81
|
+
|
82
|
+
def __get__(
|
83
|
+
self, instance: Any, owner: type | None
|
84
|
+
) -> type[MultipleObjectsReturned]:
|
85
|
+
if owner is None:
|
86
|
+
return MultipleObjectsReturned # Return base class as fallback
|
87
|
+
|
88
|
+
# Create a unique exception class for this model if we haven't already
|
89
|
+
if owner not in self._exceptions_by_class:
|
90
|
+
exc_class = type(
|
91
|
+
"MultipleObjectsReturned",
|
92
|
+
(MultipleObjectsReturned,),
|
93
|
+
{
|
94
|
+
"__module__": owner.__module__,
|
95
|
+
"__qualname__": f"{owner.__qualname__}.MultipleObjectsReturned",
|
96
|
+
},
|
97
|
+
)
|
98
|
+
self._exceptions_by_class[owner] = exc_class
|
99
|
+
|
100
|
+
return self._exceptions_by_class[owner]
|
101
|
+
|
102
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
103
|
+
raise AttributeError("Cannot set MultipleObjectsReturned")
|
104
|
+
|
105
|
+
|
106
|
+
# MARK: Database Exceptions (PEP-249)
|
107
|
+
|
108
|
+
|
109
|
+
class Error(Exception):
|
110
|
+
pass
|
111
|
+
|
112
|
+
|
113
|
+
class InterfaceError(Error):
|
114
|
+
pass
|
115
|
+
|
116
|
+
|
117
|
+
class DatabaseError(Error):
|
118
|
+
pass
|
119
|
+
|
120
|
+
|
121
|
+
class DataError(DatabaseError):
|
122
|
+
pass
|
123
|
+
|
124
|
+
|
125
|
+
class OperationalError(DatabaseError):
|
126
|
+
pass
|
127
|
+
|
128
|
+
|
129
|
+
class IntegrityError(DatabaseError):
|
130
|
+
pass
|
131
|
+
|
132
|
+
|
133
|
+
class InternalError(DatabaseError):
|
134
|
+
pass
|
135
|
+
|
136
|
+
|
137
|
+
class ProgrammingError(DatabaseError):
|
138
|
+
pass
|
139
|
+
|
140
|
+
|
141
|
+
class NotSupportedError(DatabaseError):
|
142
|
+
pass
|
143
|
+
|
144
|
+
|
145
|
+
class ConnectionDoesNotExist(Exception):
|
146
|
+
pass
|
147
|
+
|
148
|
+
|
149
|
+
class DatabaseErrorWrapper:
|
150
|
+
"""
|
151
|
+
Context manager and decorator that reraises backend-specific database
|
152
|
+
exceptions using Plain's common wrappers.
|
153
|
+
"""
|
154
|
+
|
155
|
+
def __init__(self, wrapper):
|
156
|
+
"""
|
157
|
+
wrapper is a database wrapper.
|
158
|
+
|
159
|
+
It must have a Database attribute defining PEP-249 exceptions.
|
160
|
+
"""
|
161
|
+
self.wrapper = wrapper
|
162
|
+
|
163
|
+
def __enter__(self):
|
164
|
+
pass
|
165
|
+
|
166
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
167
|
+
if exc_type is None:
|
168
|
+
return
|
169
|
+
for plain_exc_type in (
|
170
|
+
DataError,
|
171
|
+
OperationalError,
|
172
|
+
IntegrityError,
|
173
|
+
InternalError,
|
174
|
+
ProgrammingError,
|
175
|
+
NotSupportedError,
|
176
|
+
DatabaseError,
|
177
|
+
InterfaceError,
|
178
|
+
Error,
|
179
|
+
):
|
180
|
+
db_exc_type = getattr(self.wrapper.Database, plain_exc_type.__name__)
|
181
|
+
if issubclass(exc_type, db_exc_type):
|
182
|
+
plain_exc_value = plain_exc_type(*exc_value.args)
|
183
|
+
# Only set the 'errors_occurred' flag for errors that may make
|
184
|
+
# the connection unusable.
|
185
|
+
if plain_exc_type not in (DataError, IntegrityError):
|
186
|
+
self.wrapper.errors_occurred = True
|
187
|
+
raise plain_exc_value.with_traceback(traceback) from exc_value
|
188
|
+
|
189
|
+
def __call__(self, func):
|
190
|
+
# Note that we are intentionally not using @wraps here for performance
|
191
|
+
# reasons. Refs #21109.
|
192
|
+
def inner(*args, **kwargs):
|
193
|
+
with self:
|
194
|
+
return func(*args, **kwargs)
|
195
|
+
|
196
|
+
return inner
|
@@ -8,7 +8,6 @@ from functools import cached_property
|
|
8
8
|
from types import NoneType
|
9
9
|
from uuid import UUID
|
10
10
|
|
11
|
-
from plain.exceptions import EmptyResultSet, FieldError, FullResultSet
|
12
11
|
from plain.models import fields
|
13
12
|
from plain.models.constants import LOOKUP_SEP
|
14
13
|
from plain.models.db import (
|
@@ -16,6 +15,7 @@ from plain.models.db import (
|
|
16
15
|
NotSupportedError,
|
17
16
|
db_connection,
|
18
17
|
)
|
18
|
+
from plain.models.exceptions import EmptyResultSet, FieldError, FullResultSet
|
19
19
|
from plain.models.query_utils import Q
|
20
20
|
from plain.utils.deconstruct import deconstructible
|
21
21
|
from plain.utils.hashable import make_hashable
|
@@ -7,7 +7,7 @@ import operator
|
|
7
7
|
import uuid
|
8
8
|
import warnings
|
9
9
|
from base64 import b64decode, b64encode
|
10
|
-
from functools import cached_property,
|
10
|
+
from functools import cached_property, total_ordering
|
11
11
|
|
12
12
|
from plain import exceptions, validators
|
13
13
|
from plain.models.constants import LOOKUP_SEP
|
@@ -759,29 +759,15 @@ class Field(RegisterLookupMixin):
|
|
759
759
|
self.attname, self.column = self.get_attname_column()
|
760
760
|
self.concrete = self.column is not None
|
761
761
|
|
762
|
-
def contribute_to_class(self, cls, name
|
762
|
+
def contribute_to_class(self, cls, name):
|
763
763
|
"""
|
764
764
|
Register the field with the model class it belongs to.
|
765
|
-
|
766
|
-
If private_only is True, create a separate instance of this field
|
767
|
-
for every subclass of cls, even if cls is not an abstract model.
|
768
765
|
"""
|
769
766
|
self.set_attributes_from_name(name)
|
770
767
|
self.model = cls
|
771
|
-
cls._meta.add_field(self
|
768
|
+
cls._meta.add_field(self)
|
772
769
|
if self.column:
|
773
770
|
setattr(cls, self.attname, self.descriptor_class(self))
|
774
|
-
if self.choices is not None:
|
775
|
-
# Don't override a get_FOO_display() method defined explicitly on
|
776
|
-
# this class, but don't check methods derived from inheritance, to
|
777
|
-
# allow overriding inherited choices. For more complex inheritance
|
778
|
-
# structures users should override contribute_to_class().
|
779
|
-
if f"get_{self.name}_display" not in cls.__dict__:
|
780
|
-
setattr(
|
781
|
-
cls,
|
782
|
-
f"get_{self.name}_display",
|
783
|
-
partialmethod(cls._get_FIELD_display, field=self),
|
784
|
-
)
|
785
771
|
|
786
772
|
def get_attname(self):
|
787
773
|
return self.name
|
@@ -1200,24 +1186,6 @@ class DateField(DateTimeCheckMixin, Field):
|
|
1200
1186
|
else:
|
1201
1187
|
return super().pre_save(model_instance, add)
|
1202
1188
|
|
1203
|
-
def contribute_to_class(self, cls, name, **kwargs):
|
1204
|
-
super().contribute_to_class(cls, name, **kwargs)
|
1205
|
-
if not self.allow_null:
|
1206
|
-
setattr(
|
1207
|
-
cls,
|
1208
|
-
f"get_next_by_{self.name}",
|
1209
|
-
partialmethod(
|
1210
|
-
cls._get_next_or_previous_by_FIELD, field=self, is_next=True
|
1211
|
-
),
|
1212
|
-
)
|
1213
|
-
setattr(
|
1214
|
-
cls,
|
1215
|
-
f"get_previous_by_{self.name}",
|
1216
|
-
partialmethod(
|
1217
|
-
cls._get_next_or_previous_by_FIELD, field=self, is_next=False
|
1218
|
-
),
|
1219
|
-
)
|
1220
|
-
|
1221
1189
|
def get_prep_value(self, value):
|
1222
1190
|
value = super().get_prep_value(value)
|
1223
1191
|
return self.to_python(value)
|
@@ -1319,9 +1287,6 @@ class DateTimeField(DateField):
|
|
1319
1287
|
else:
|
1320
1288
|
return super().pre_save(model_instance, add)
|
1321
1289
|
|
1322
|
-
# contribute_to_class is inherited from DateField, it registers
|
1323
|
-
# get_next_by_FOO and get_prev_by_FOO
|
1324
|
-
|
1325
1290
|
def get_prep_value(self, value):
|
1326
1291
|
value = super().get_prep_value(value)
|
1327
1292
|
value = self.to_python(value)
|
@@ -2145,9 +2110,6 @@ class PrimaryKeyField(BigIntegerField):
|
|
2145
2110
|
value = connection.ops.validate_autopk_value(value)
|
2146
2111
|
return value
|
2147
2112
|
|
2148
|
-
def contribute_to_class(self, cls, name, **kwargs):
|
2149
|
-
super().contribute_to_class(cls, name, **kwargs)
|
2150
|
-
|
2151
2113
|
def get_internal_type(self):
|
2152
2114
|
return "PrimaryKeyField"
|
2153
2115
|
|