plain.models 0.0.0__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.0.0/LICENSE +61 -0
- plain_models-0.0.0/PKG-INFO +11 -0
- plain_models-0.0.0/plain/models/README.md +94 -0
- plain_models-0.0.0/plain/models/__init__.py +152 -0
- plain_models-0.0.0/plain/models/aggregates.py +202 -0
- plain_models-0.0.0/plain/models/backends/README.md +1 -0
- plain_models-0.0.0/plain/models/backends/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/base/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/base/base.py +783 -0
- plain_models-0.0.0/plain/models/backends/base/client.py +28 -0
- plain_models-0.0.0/plain/models/backends/base/creation.py +343 -0
- plain_models-0.0.0/plain/models/backends/base/features.py +386 -0
- plain_models-0.0.0/plain/models/backends/base/introspection.py +214 -0
- plain_models-0.0.0/plain/models/backends/base/operations.py +779 -0
- plain_models-0.0.0/plain/models/backends/base/schema.py +1810 -0
- plain_models-0.0.0/plain/models/backends/base/validation.py +29 -0
- plain_models-0.0.0/plain/models/backends/ddl_references.py +254 -0
- plain_models-0.0.0/plain/models/backends/dummy/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/dummy/base.py +74 -0
- plain_models-0.0.0/plain/models/backends/dummy/features.py +6 -0
- plain_models-0.0.0/plain/models/backends/mysql/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/mysql/base.py +436 -0
- plain_models-0.0.0/plain/models/backends/mysql/client.py +72 -0
- plain_models-0.0.0/plain/models/backends/mysql/compiler.py +83 -0
- plain_models-0.0.0/plain/models/backends/mysql/creation.py +86 -0
- plain_models-0.0.0/plain/models/backends/mysql/features.py +261 -0
- plain_models-0.0.0/plain/models/backends/mysql/introspection.py +349 -0
- plain_models-0.0.0/plain/models/backends/mysql/operations.py +456 -0
- plain_models-0.0.0/plain/models/backends/mysql/schema.py +255 -0
- plain_models-0.0.0/plain/models/backends/mysql/validation.py +70 -0
- plain_models-0.0.0/plain/models/backends/postgresql/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/postgresql/base.py +457 -0
- plain_models-0.0.0/plain/models/backends/postgresql/client.py +64 -0
- plain_models-0.0.0/plain/models/backends/postgresql/creation.py +86 -0
- plain_models-0.0.0/plain/models/backends/postgresql/features.py +111 -0
- plain_models-0.0.0/plain/models/backends/postgresql/introspection.py +298 -0
- plain_models-0.0.0/plain/models/backends/postgresql/operations.py +412 -0
- plain_models-0.0.0/plain/models/backends/postgresql/psycopg_any.py +108 -0
- plain_models-0.0.0/plain/models/backends/postgresql/schema.py +374 -0
- plain_models-0.0.0/plain/models/backends/signals.py +3 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/__init__.py +0 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/_functions.py +509 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/base.py +340 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/client.py +10 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/creation.py +157 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/features.py +91 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/introspection.py +436 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/operations.py +438 -0
- plain_models-0.0.0/plain/models/backends/sqlite3/schema.py +564 -0
- plain_models-0.0.0/plain/models/backends/utils.py +320 -0
- plain_models-0.0.0/plain/models/base.py +2368 -0
- plain_models-0.0.0/plain/models/cli.py +76 -0
- plain_models-0.0.0/plain/models/config.py +8 -0
- plain_models-0.0.0/plain/models/constants.py +12 -0
- plain_models-0.0.0/plain/models/constraints.py +443 -0
- plain_models-0.0.0/plain/models/database_url.py +188 -0
- plain_models-0.0.0/plain/models/db.py +295 -0
- plain_models-0.0.0/plain/models/default_settings.py +30 -0
- plain_models-0.0.0/plain/models/deletion.py +524 -0
- plain_models-0.0.0/plain/models/enums.py +92 -0
- plain_models-0.0.0/plain/models/expressions.py +1836 -0
- plain_models-0.0.0/plain/models/fields/README.md +1 -0
- plain_models-0.0.0/plain/models/fields/__init__.py +2459 -0
- plain_models-0.0.0/plain/models/fields/json.py +562 -0
- plain_models-0.0.0/plain/models/fields/mixins.py +60 -0
- plain_models-0.0.0/plain/models/fields/proxy.py +18 -0
- plain_models-0.0.0/plain/models/fields/related.py +1915 -0
- plain_models-0.0.0/plain/models/fields/related_descriptors.py +1424 -0
- plain_models-0.0.0/plain/models/fields/related_lookups.py +199 -0
- plain_models-0.0.0/plain/models/fields/reverse_related.py +396 -0
- plain_models-0.0.0/plain/models/forms.py +851 -0
- plain_models-0.0.0/plain/models/functions/__init__.py +190 -0
- plain_models-0.0.0/plain/models/functions/comparison.py +177 -0
- plain_models-0.0.0/plain/models/functions/datetime.py +430 -0
- plain_models-0.0.0/plain/models/functions/math.py +176 -0
- plain_models-0.0.0/plain/models/functions/mixins.py +41 -0
- plain_models-0.0.0/plain/models/functions/text.py +313 -0
- plain_models-0.0.0/plain/models/functions/window.py +120 -0
- plain_models-0.0.0/plain/models/indexes.py +297 -0
- plain_models-0.0.0/plain/models/lookups.py +740 -0
- plain_models-0.0.0/plain/models/management/commands/flush.py +93 -0
- plain_models-0.0.0/plain/models/management/commands/makemigrations.py +529 -0
- plain_models-0.0.0/plain/models/management/commands/migrate.py +526 -0
- plain_models-0.0.0/plain/models/management/commands/optimizemigration.py +131 -0
- plain_models-0.0.0/plain/models/management/commands/showmigrations.py +176 -0
- plain_models-0.0.0/plain/models/management/commands/squashmigrations.py +268 -0
- plain_models-0.0.0/plain/models/manager.py +211 -0
- plain_models-0.0.0/plain/models/migrations/README.md +1 -0
- plain_models-0.0.0/plain/models/migrations/__init__.py +2 -0
- plain_models-0.0.0/plain/models/migrations/autodetector.py +1646 -0
- plain_models-0.0.0/plain/models/migrations/exceptions.py +60 -0
- plain_models-0.0.0/plain/models/migrations/executor.py +411 -0
- plain_models-0.0.0/plain/models/migrations/graph.py +334 -0
- plain_models-0.0.0/plain/models/migrations/loader.py +388 -0
- plain_models-0.0.0/plain/models/migrations/migration.py +239 -0
- plain_models-0.0.0/plain/models/migrations/operations/__init__.py +42 -0
- plain_models-0.0.0/plain/models/migrations/operations/base.py +146 -0
- plain_models-0.0.0/plain/models/migrations/operations/fields.py +353 -0
- plain_models-0.0.0/plain/models/migrations/operations/models.py +1115 -0
- plain_models-0.0.0/plain/models/migrations/operations/special.py +208 -0
- plain_models-0.0.0/plain/models/migrations/optimizer.py +71 -0
- plain_models-0.0.0/plain/models/migrations/questioner.py +349 -0
- plain_models-0.0.0/plain/models/migrations/recorder.py +104 -0
- plain_models-0.0.0/plain/models/migrations/serializer.py +386 -0
- plain_models-0.0.0/plain/models/migrations/state.py +953 -0
- plain_models-0.0.0/plain/models/migrations/utils.py +129 -0
- plain_models-0.0.0/plain/models/migrations/writer.py +305 -0
- plain_models-0.0.0/plain/models/options.py +920 -0
- plain_models-0.0.0/plain/models/preflight.py +275 -0
- plain_models-0.0.0/plain/models/query.py +2482 -0
- plain_models-0.0.0/plain/models/query_utils.py +436 -0
- plain_models-0.0.0/plain/models/signals.py +56 -0
- plain_models-0.0.0/plain/models/sql/__init__.py +6 -0
- plain_models-0.0.0/plain/models/sql/compiler.py +2085 -0
- plain_models-0.0.0/plain/models/sql/constants.py +24 -0
- plain_models-0.0.0/plain/models/sql/datastructures.py +218 -0
- plain_models-0.0.0/plain/models/sql/query.py +2650 -0
- plain_models-0.0.0/plain/models/sql/subqueries.py +171 -0
- plain_models-0.0.0/plain/models/sql/where.py +355 -0
- plain_models-0.0.0/plain/models/transaction.py +323 -0
- plain_models-0.0.0/plain/models/utils.py +69 -0
- plain_models-0.0.0/pyproject.toml +17 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## Plain is released under the BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2023, Dropseed, LLC
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without
|
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
18
|
+
contributors may be used to endorse or promote products derived from
|
|
19
|
+
this software without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
22
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
23
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
24
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
25
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
26
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
27
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
28
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
29
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## This package contains code forked from github.com/django/django
|
|
34
|
+
|
|
35
|
+
Copyright (c) Django Software Foundation and individual contributors.
|
|
36
|
+
All rights reserved.
|
|
37
|
+
|
|
38
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
39
|
+
are permitted provided that the following conditions are met:
|
|
40
|
+
|
|
41
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
42
|
+
this list of conditions and the following disclaimer.
|
|
43
|
+
|
|
44
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
45
|
+
notice, this list of conditions and the following disclaimer in the
|
|
46
|
+
documentation and/or other materials provided with the distribution.
|
|
47
|
+
|
|
48
|
+
3. Neither the name of Django nor the names of its contributors may be used
|
|
49
|
+
to endorse or promote products derived from this software without
|
|
50
|
+
specific prior written permission.
|
|
51
|
+
|
|
52
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
53
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
54
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
55
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
56
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
57
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
58
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
59
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
60
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
61
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: plain.models
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Dave Gaeddert
|
|
6
|
+
Author-email: dave.gaeddert@dropseed.dev
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: sqlparse (>=0.3.1)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# plain-models
|
|
2
|
+
|
|
3
|
+
Model your data and store it in a database.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
# app/users/models.py
|
|
7
|
+
from plain import models
|
|
8
|
+
from plain.passwords.models import PasswordField
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class User(models.Model):
|
|
12
|
+
email = models.EmailField(unique=True)
|
|
13
|
+
password = PasswordField()
|
|
14
|
+
is_staff = models.BooleanField(default=False)
|
|
15
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return self.email
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Create, update, and delete instances of your models:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from .models import User
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Create a new user
|
|
28
|
+
user = User.objects.create(
|
|
29
|
+
email="test@example.com",
|
|
30
|
+
password="password",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Update a user
|
|
34
|
+
user.email = "new@example.com"
|
|
35
|
+
user.save()
|
|
36
|
+
|
|
37
|
+
# Delete a user
|
|
38
|
+
user.delete()
|
|
39
|
+
|
|
40
|
+
# Query for users
|
|
41
|
+
staff_users = User.objects.filter(is_staff=True)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# app/settings.py
|
|
48
|
+
INSTALLED_PACKAGES = [
|
|
49
|
+
...
|
|
50
|
+
"plain.models",
|
|
51
|
+
]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To connect to a database, you can provide a `DATABASE_URL` environment variable.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or you can manually define the `DATABASES` setting.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# app/settings.py
|
|
64
|
+
DATABASES = {
|
|
65
|
+
"default": {
|
|
66
|
+
"ENGINE": "plain.models.backends.postgresql",
|
|
67
|
+
"NAME": "dbname",
|
|
68
|
+
"USER": "user",
|
|
69
|
+
"PASSWORD": "password",
|
|
70
|
+
"HOST": "localhost",
|
|
71
|
+
"PORT": "5432",
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
[Multiple backends are supported, including Postgres, MySQL, and SQLite.](./backends/README.md)
|
|
77
|
+
|
|
78
|
+
## Querying
|
|
79
|
+
|
|
80
|
+
## Migrations
|
|
81
|
+
|
|
82
|
+
[Migration docs](./migrations/README.md)
|
|
83
|
+
|
|
84
|
+
## Fields
|
|
85
|
+
|
|
86
|
+
[Field docs](./fields/README.md)
|
|
87
|
+
|
|
88
|
+
## Validation
|
|
89
|
+
|
|
90
|
+
## Indexes and constraints
|
|
91
|
+
|
|
92
|
+
## Managers
|
|
93
|
+
|
|
94
|
+
## Forms
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from plain.exceptions import ObjectDoesNotExist
|
|
2
|
+
|
|
3
|
+
from . import (
|
|
4
|
+
preflight, # noqa
|
|
5
|
+
)
|
|
6
|
+
from .aggregates import * # NOQA
|
|
7
|
+
from .aggregates import __all__ as aggregates_all
|
|
8
|
+
from .constraints import * # NOQA
|
|
9
|
+
from .constraints import __all__ as constraints_all
|
|
10
|
+
from .db import (
|
|
11
|
+
DEFAULT_DB_ALIAS,
|
|
12
|
+
PLAIN_VERSION_PICKLE_KEY,
|
|
13
|
+
DatabaseError,
|
|
14
|
+
DataError,
|
|
15
|
+
Error,
|
|
16
|
+
IntegrityError,
|
|
17
|
+
InterfaceError,
|
|
18
|
+
InternalError,
|
|
19
|
+
NotSupportedError,
|
|
20
|
+
OperationalError,
|
|
21
|
+
ProgrammingError,
|
|
22
|
+
close_old_connections,
|
|
23
|
+
connection,
|
|
24
|
+
connections,
|
|
25
|
+
reset_queries,
|
|
26
|
+
router,
|
|
27
|
+
)
|
|
28
|
+
from .deletion import (
|
|
29
|
+
CASCADE,
|
|
30
|
+
DO_NOTHING,
|
|
31
|
+
PROTECT,
|
|
32
|
+
RESTRICT,
|
|
33
|
+
SET,
|
|
34
|
+
SET_DEFAULT,
|
|
35
|
+
SET_NULL,
|
|
36
|
+
ProtectedError,
|
|
37
|
+
RestrictedError,
|
|
38
|
+
)
|
|
39
|
+
from .enums import * # NOQA
|
|
40
|
+
from .enums import __all__ as enums_all
|
|
41
|
+
from .expressions import (
|
|
42
|
+
Case,
|
|
43
|
+
Exists,
|
|
44
|
+
Expression,
|
|
45
|
+
ExpressionList,
|
|
46
|
+
ExpressionWrapper,
|
|
47
|
+
F,
|
|
48
|
+
Func,
|
|
49
|
+
OrderBy,
|
|
50
|
+
OuterRef,
|
|
51
|
+
RowRange,
|
|
52
|
+
Subquery,
|
|
53
|
+
Value,
|
|
54
|
+
ValueRange,
|
|
55
|
+
When,
|
|
56
|
+
Window,
|
|
57
|
+
WindowFrame,
|
|
58
|
+
)
|
|
59
|
+
from .fields import * # NOQA
|
|
60
|
+
from .fields import __all__ as fields_all
|
|
61
|
+
from .fields.json import JSONField
|
|
62
|
+
from .fields.proxy import OrderWrt
|
|
63
|
+
from .indexes import * # NOQA
|
|
64
|
+
from .indexes import __all__ as indexes_all
|
|
65
|
+
from .lookups import Lookup, Transform
|
|
66
|
+
from .manager import Manager
|
|
67
|
+
from .query import Prefetch, QuerySet, prefetch_related_objects
|
|
68
|
+
from .query_utils import FilteredRelation, Q
|
|
69
|
+
|
|
70
|
+
# Imports that would create circular imports if sorted
|
|
71
|
+
from .base import DEFERRED, Model # isort:skip
|
|
72
|
+
from .fields.related import ( # isort:skip
|
|
73
|
+
ForeignKey,
|
|
74
|
+
ForeignObject,
|
|
75
|
+
OneToOneField,
|
|
76
|
+
ManyToManyField,
|
|
77
|
+
ForeignObjectRel,
|
|
78
|
+
ManyToOneRel,
|
|
79
|
+
ManyToManyRel,
|
|
80
|
+
OneToOneRel,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = aggregates_all + constraints_all + enums_all + fields_all + indexes_all
|
|
85
|
+
__all__ += [
|
|
86
|
+
"ObjectDoesNotExist",
|
|
87
|
+
"CASCADE",
|
|
88
|
+
"DO_NOTHING",
|
|
89
|
+
"PROTECT",
|
|
90
|
+
"RESTRICT",
|
|
91
|
+
"SET",
|
|
92
|
+
"SET_DEFAULT",
|
|
93
|
+
"SET_NULL",
|
|
94
|
+
"ProtectedError",
|
|
95
|
+
"RestrictedError",
|
|
96
|
+
"Case",
|
|
97
|
+
"Exists",
|
|
98
|
+
"Expression",
|
|
99
|
+
"ExpressionList",
|
|
100
|
+
"ExpressionWrapper",
|
|
101
|
+
"F",
|
|
102
|
+
"Func",
|
|
103
|
+
"OrderBy",
|
|
104
|
+
"OuterRef",
|
|
105
|
+
"RowRange",
|
|
106
|
+
"Subquery",
|
|
107
|
+
"Value",
|
|
108
|
+
"ValueRange",
|
|
109
|
+
"When",
|
|
110
|
+
"Window",
|
|
111
|
+
"WindowFrame",
|
|
112
|
+
"JSONField",
|
|
113
|
+
"OrderWrt",
|
|
114
|
+
"Lookup",
|
|
115
|
+
"Transform",
|
|
116
|
+
"Manager",
|
|
117
|
+
"Prefetch",
|
|
118
|
+
"Q",
|
|
119
|
+
"QuerySet",
|
|
120
|
+
"prefetch_related_objects",
|
|
121
|
+
"DEFERRED",
|
|
122
|
+
"Model",
|
|
123
|
+
"FilteredRelation",
|
|
124
|
+
"ForeignKey",
|
|
125
|
+
"ForeignObject",
|
|
126
|
+
"OneToOneField",
|
|
127
|
+
"ManyToManyField",
|
|
128
|
+
"ForeignObjectRel",
|
|
129
|
+
"ManyToOneRel",
|
|
130
|
+
"ManyToManyRel",
|
|
131
|
+
"OneToOneRel",
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# DB-related exports
|
|
135
|
+
__all__ += [
|
|
136
|
+
"connection",
|
|
137
|
+
"connections",
|
|
138
|
+
"router",
|
|
139
|
+
"reset_queries",
|
|
140
|
+
"close_old_connections",
|
|
141
|
+
"DatabaseError",
|
|
142
|
+
"IntegrityError",
|
|
143
|
+
"InternalError",
|
|
144
|
+
"ProgrammingError",
|
|
145
|
+
"DataError",
|
|
146
|
+
"NotSupportedError",
|
|
147
|
+
"Error",
|
|
148
|
+
"InterfaceError",
|
|
149
|
+
"OperationalError",
|
|
150
|
+
"DEFAULT_DB_ALIAS",
|
|
151
|
+
"PLAIN_VERSION_PICKLE_KEY",
|
|
152
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classes to represent the definitions of aggregate functions.
|
|
3
|
+
"""
|
|
4
|
+
from plain.exceptions import FieldError, FullResultSet
|
|
5
|
+
from plain.models.expressions import Case, Func, Star, Value, When
|
|
6
|
+
from plain.models.fields import IntegerField
|
|
7
|
+
from plain.models.functions.comparison import Coalesce
|
|
8
|
+
from plain.models.functions.mixins import (
|
|
9
|
+
FixDurationInputMixin,
|
|
10
|
+
NumericOutputFieldMixin,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Aggregate",
|
|
15
|
+
"Avg",
|
|
16
|
+
"Count",
|
|
17
|
+
"Max",
|
|
18
|
+
"Min",
|
|
19
|
+
"StdDev",
|
|
20
|
+
"Sum",
|
|
21
|
+
"Variance",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Aggregate(Func):
|
|
26
|
+
template = "%(function)s(%(distinct)s%(expressions)s)"
|
|
27
|
+
contains_aggregate = True
|
|
28
|
+
name = None
|
|
29
|
+
filter_template = "%s FILTER (WHERE %%(filter)s)"
|
|
30
|
+
window_compatible = True
|
|
31
|
+
allow_distinct = False
|
|
32
|
+
empty_result_set_value = None
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, *expressions, distinct=False, filter=None, default=None, **extra
|
|
36
|
+
):
|
|
37
|
+
if distinct and not self.allow_distinct:
|
|
38
|
+
raise TypeError("%s does not allow distinct." % self.__class__.__name__)
|
|
39
|
+
if default is not None and self.empty_result_set_value is not None:
|
|
40
|
+
raise TypeError(f"{self.__class__.__name__} does not allow default.")
|
|
41
|
+
self.distinct = distinct
|
|
42
|
+
self.filter = filter
|
|
43
|
+
self.default = default
|
|
44
|
+
super().__init__(*expressions, **extra)
|
|
45
|
+
|
|
46
|
+
def get_source_fields(self):
|
|
47
|
+
# Don't return the filter expression since it's not a source field.
|
|
48
|
+
return [e._output_field_or_none for e in super().get_source_expressions()]
|
|
49
|
+
|
|
50
|
+
def get_source_expressions(self):
|
|
51
|
+
source_expressions = super().get_source_expressions()
|
|
52
|
+
if self.filter:
|
|
53
|
+
return source_expressions + [self.filter]
|
|
54
|
+
return source_expressions
|
|
55
|
+
|
|
56
|
+
def set_source_expressions(self, exprs):
|
|
57
|
+
self.filter = self.filter and exprs.pop()
|
|
58
|
+
return super().set_source_expressions(exprs)
|
|
59
|
+
|
|
60
|
+
def resolve_expression(
|
|
61
|
+
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
|
|
62
|
+
):
|
|
63
|
+
# Aggregates are not allowed in UPDATE queries, so ignore for_save
|
|
64
|
+
c = super().resolve_expression(query, allow_joins, reuse, summarize)
|
|
65
|
+
c.filter = c.filter and c.filter.resolve_expression(
|
|
66
|
+
query, allow_joins, reuse, summarize
|
|
67
|
+
)
|
|
68
|
+
if not summarize:
|
|
69
|
+
# Call Aggregate.get_source_expressions() to avoid
|
|
70
|
+
# returning self.filter and including that in this loop.
|
|
71
|
+
expressions = super(Aggregate, c).get_source_expressions()
|
|
72
|
+
for index, expr in enumerate(expressions):
|
|
73
|
+
if expr.contains_aggregate:
|
|
74
|
+
before_resolved = self.get_source_expressions()[index]
|
|
75
|
+
name = (
|
|
76
|
+
before_resolved.name
|
|
77
|
+
if hasattr(before_resolved, "name")
|
|
78
|
+
else repr(before_resolved)
|
|
79
|
+
)
|
|
80
|
+
raise FieldError(
|
|
81
|
+
f"Cannot compute {c.name}('{name}'): '{name}' is an aggregate"
|
|
82
|
+
)
|
|
83
|
+
if (default := c.default) is None:
|
|
84
|
+
return c
|
|
85
|
+
if hasattr(default, "resolve_expression"):
|
|
86
|
+
default = default.resolve_expression(query, allow_joins, reuse, summarize)
|
|
87
|
+
if default._output_field_or_none is None:
|
|
88
|
+
default.output_field = c._output_field_or_none
|
|
89
|
+
else:
|
|
90
|
+
default = Value(default, c._output_field_or_none)
|
|
91
|
+
c.default = None # Reset the default argument before wrapping.
|
|
92
|
+
coalesce = Coalesce(c, default, output_field=c._output_field_or_none)
|
|
93
|
+
coalesce.is_summary = c.is_summary
|
|
94
|
+
return coalesce
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def default_alias(self):
|
|
98
|
+
expressions = self.get_source_expressions()
|
|
99
|
+
if len(expressions) == 1 and hasattr(expressions[0], "name"):
|
|
100
|
+
return f"{expressions[0].name}__{self.name.lower()}"
|
|
101
|
+
raise TypeError("Complex expressions require an alias")
|
|
102
|
+
|
|
103
|
+
def get_group_by_cols(self):
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
def as_sql(self, compiler, connection, **extra_context):
|
|
107
|
+
extra_context["distinct"] = "DISTINCT " if self.distinct else ""
|
|
108
|
+
if self.filter:
|
|
109
|
+
if connection.features.supports_aggregate_filter_clause:
|
|
110
|
+
try:
|
|
111
|
+
filter_sql, filter_params = self.filter.as_sql(compiler, connection)
|
|
112
|
+
except FullResultSet:
|
|
113
|
+
pass
|
|
114
|
+
else:
|
|
115
|
+
template = self.filter_template % extra_context.get(
|
|
116
|
+
"template", self.template
|
|
117
|
+
)
|
|
118
|
+
sql, params = super().as_sql(
|
|
119
|
+
compiler,
|
|
120
|
+
connection,
|
|
121
|
+
template=template,
|
|
122
|
+
filter=filter_sql,
|
|
123
|
+
**extra_context,
|
|
124
|
+
)
|
|
125
|
+
return sql, (*params, *filter_params)
|
|
126
|
+
else:
|
|
127
|
+
copy = self.copy()
|
|
128
|
+
copy.filter = None
|
|
129
|
+
source_expressions = copy.get_source_expressions()
|
|
130
|
+
condition = When(self.filter, then=source_expressions[0])
|
|
131
|
+
copy.set_source_expressions([Case(condition)] + source_expressions[1:])
|
|
132
|
+
return super(Aggregate, copy).as_sql(
|
|
133
|
+
compiler, connection, **extra_context
|
|
134
|
+
)
|
|
135
|
+
return super().as_sql(compiler, connection, **extra_context)
|
|
136
|
+
|
|
137
|
+
def _get_repr_options(self):
|
|
138
|
+
options = super()._get_repr_options()
|
|
139
|
+
if self.distinct:
|
|
140
|
+
options["distinct"] = self.distinct
|
|
141
|
+
if self.filter:
|
|
142
|
+
options["filter"] = self.filter
|
|
143
|
+
return options
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Avg(FixDurationInputMixin, NumericOutputFieldMixin, Aggregate):
|
|
147
|
+
function = "AVG"
|
|
148
|
+
name = "Avg"
|
|
149
|
+
allow_distinct = True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class Count(Aggregate):
|
|
153
|
+
function = "COUNT"
|
|
154
|
+
name = "Count"
|
|
155
|
+
output_field = IntegerField()
|
|
156
|
+
allow_distinct = True
|
|
157
|
+
empty_result_set_value = 0
|
|
158
|
+
|
|
159
|
+
def __init__(self, expression, filter=None, **extra):
|
|
160
|
+
if expression == "*":
|
|
161
|
+
expression = Star()
|
|
162
|
+
if isinstance(expression, Star) and filter is not None:
|
|
163
|
+
raise ValueError("Star cannot be used with filter. Please specify a field.")
|
|
164
|
+
super().__init__(expression, filter=filter, **extra)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class Max(Aggregate):
|
|
168
|
+
function = "MAX"
|
|
169
|
+
name = "Max"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Min(Aggregate):
|
|
173
|
+
function = "MIN"
|
|
174
|
+
name = "Min"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class StdDev(NumericOutputFieldMixin, Aggregate):
|
|
178
|
+
name = "StdDev"
|
|
179
|
+
|
|
180
|
+
def __init__(self, expression, sample=False, **extra):
|
|
181
|
+
self.function = "STDDEV_SAMP" if sample else "STDDEV_POP"
|
|
182
|
+
super().__init__(expression, **extra)
|
|
183
|
+
|
|
184
|
+
def _get_repr_options(self):
|
|
185
|
+
return {**super()._get_repr_options(), "sample": self.function == "STDDEV_SAMP"}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class Sum(FixDurationInputMixin, Aggregate):
|
|
189
|
+
function = "SUM"
|
|
190
|
+
name = "Sum"
|
|
191
|
+
allow_distinct = True
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Variance(NumericOutputFieldMixin, Aggregate):
|
|
195
|
+
name = "Variance"
|
|
196
|
+
|
|
197
|
+
def __init__(self, expression, sample=False, **extra):
|
|
198
|
+
self.function = "VAR_SAMP" if sample else "VAR_POP"
|
|
199
|
+
super().__init__(expression, **extra)
|
|
200
|
+
|
|
201
|
+
def _get_repr_options(self):
|
|
202
|
+
return {**super()._get_repr_options(), "sample": self.function == "VAR_SAMP"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Backends
|
|
File without changes
|
|
File without changes
|