plain.models 0.49.2__py3-none-any.whl → 0.50.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +13 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +22 -12
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +29 -16
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +267 -165
- plain/models/backends/base/validation.py +12 -3
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +12 -3
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +106 -39
- plain/models/backends/mysql/schema.py +48 -24
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +109 -42
- plain/models/backends/postgresql/schema.py +85 -46
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +125 -42
- plain/models/backends/sqlite3/schema.py +82 -58
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +113 -74
- plain/models/cli.py +94 -63
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +65 -47
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +66 -43
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +440 -257
- plain/models/fields/__init__.py +253 -202
- plain/models/fields/json.py +120 -54
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +284 -252
- plain/models/fields/related_descriptors.py +31 -22
- plain/models/fields/related_lookups.py +23 -11
- plain/models/fields/related_managers.py +81 -47
- plain/models/fields/reverse_related.py +58 -55
- plain/models/forms.py +89 -63
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +52 -28
- plain/models/lookups.py +228 -153
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +37 -19
- plain/models/migrations/operations/fields.py +89 -42
- plain/models/migrations/operations/models.py +245 -143
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +18 -11
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +220 -133
- plain/models/migrations/utils.py +29 -13
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +63 -56
- plain/models/otel.py +16 -6
- plain/models/preflight.py +35 -12
- plain/models/query.py +323 -228
- plain/models/query_utils.py +93 -58
- plain/models/registry.py +34 -16
- plain/models/sql/compiler.py +146 -97
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +255 -169
- plain/models/sql/subqueries.py +32 -21
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +13 -5
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
- plain_models-0.50.0.dist-info/RECORD +122 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,10 @@ They also act as reverse fields for the purposes of the Meta API because
|
|
9
9
|
they're the closest concept currently available.
|
10
10
|
"""
|
11
11
|
|
12
|
+
from __future__ import annotations
|
13
|
+
|
12
14
|
from functools import cached_property
|
15
|
+
from typing import Any
|
13
16
|
|
14
17
|
from plain.models.exceptions import FieldDoesNotExist, FieldError
|
15
18
|
from plain.utils.hashable import make_hashable
|
@@ -38,12 +41,12 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
38
41
|
|
39
42
|
def __init__(
|
40
43
|
self,
|
41
|
-
field,
|
42
|
-
to,
|
43
|
-
related_name=None,
|
44
|
-
related_query_name=None,
|
45
|
-
limit_choices_to=None,
|
46
|
-
on_delete=None,
|
44
|
+
field: Any,
|
45
|
+
to: Any,
|
46
|
+
related_name: str | None = None,
|
47
|
+
related_query_name: str | None = None,
|
48
|
+
limit_choices_to: Any = None,
|
49
|
+
on_delete: Any = None,
|
47
50
|
):
|
48
51
|
self.field = field
|
49
52
|
self.model = to
|
@@ -60,19 +63,19 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
60
63
|
# before field.contribute_to_class() has been called will result in
|
61
64
|
# AttributeError
|
62
65
|
@cached_property
|
63
|
-
def hidden(self):
|
66
|
+
def hidden(self) -> bool:
|
64
67
|
return self.is_hidden()
|
65
68
|
|
66
69
|
@cached_property
|
67
|
-
def name(self):
|
70
|
+
def name(self) -> str:
|
68
71
|
return self.field.related_query_name()
|
69
72
|
|
70
73
|
@property
|
71
|
-
def remote_field(self):
|
74
|
+
def remote_field(self) -> Any:
|
72
75
|
return self.field
|
73
76
|
|
74
77
|
@property
|
75
|
-
def target_field(self):
|
78
|
+
def target_field(self) -> Any:
|
76
79
|
"""
|
77
80
|
When filtering against this relation, return the field on the remote
|
78
81
|
model against which the filtering should happen.
|
@@ -83,7 +86,7 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
83
86
|
return target_fields[0]
|
84
87
|
|
85
88
|
@cached_property
|
86
|
-
def related_model(self):
|
89
|
+
def related_model(self) -> Any:
|
87
90
|
if not self.field.model:
|
88
91
|
raise AttributeError(
|
89
92
|
"This property can't be accessed before self.field.contribute_to_class "
|
@@ -92,32 +95,32 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
92
95
|
return self.field.model
|
93
96
|
|
94
97
|
@cached_property
|
95
|
-
def many_to_many(self):
|
98
|
+
def many_to_many(self) -> bool:
|
96
99
|
return self.field.many_to_many
|
97
100
|
|
98
101
|
@cached_property
|
99
|
-
def many_to_one(self):
|
102
|
+
def many_to_one(self) -> bool:
|
100
103
|
return self.field.one_to_many
|
101
104
|
|
102
105
|
@cached_property
|
103
|
-
def one_to_many(self):
|
106
|
+
def one_to_many(self) -> bool:
|
104
107
|
return self.field.many_to_one
|
105
108
|
|
106
|
-
def get_lookup(self, lookup_name):
|
109
|
+
def get_lookup(self, lookup_name: str) -> Any:
|
107
110
|
return self.field.get_lookup(lookup_name)
|
108
111
|
|
109
|
-
def get_internal_type(self):
|
112
|
+
def get_internal_type(self) -> str:
|
110
113
|
return self.field.get_internal_type()
|
111
114
|
|
112
115
|
@property
|
113
|
-
def db_type(self):
|
116
|
+
def db_type(self) -> Any:
|
114
117
|
return self.field.db_type
|
115
118
|
|
116
|
-
def __repr__(self):
|
119
|
+
def __repr__(self) -> str:
|
117
120
|
return f"<{type(self).__name__}: {self.related_model._meta.package_label}.{self.related_model._meta.model_name}>"
|
118
121
|
|
119
122
|
@property
|
120
|
-
def identity(self):
|
123
|
+
def identity(self) -> tuple[Any, ...]:
|
121
124
|
return (
|
122
125
|
self.field,
|
123
126
|
self.model,
|
@@ -129,15 +132,15 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
129
132
|
self.multiple,
|
130
133
|
)
|
131
134
|
|
132
|
-
def __eq__(self, other):
|
135
|
+
def __eq__(self, other: object) -> bool:
|
133
136
|
if not isinstance(other, self.__class__):
|
134
137
|
return NotImplemented
|
135
138
|
return self.identity == other.identity
|
136
139
|
|
137
|
-
def __hash__(self):
|
140
|
+
def __hash__(self) -> int:
|
138
141
|
return hash(self.identity)
|
139
142
|
|
140
|
-
def __getstate__(self):
|
143
|
+
def __getstate__(self) -> dict[str, Any]:
|
141
144
|
state = self.__dict__.copy()
|
142
145
|
# Delete the path_infos cached property because it can be recalculated
|
143
146
|
# at first invocation after deserialization. The attribute must be
|
@@ -151,11 +154,11 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
151
154
|
|
152
155
|
def get_choices(
|
153
156
|
self,
|
154
|
-
include_blank=True,
|
155
|
-
blank_choice=BLANK_CHOICE_DASH,
|
156
|
-
limit_choices_to=None,
|
157
|
-
ordering=(),
|
158
|
-
):
|
157
|
+
include_blank: bool = True,
|
158
|
+
blank_choice: list[tuple[str, str]] = BLANK_CHOICE_DASH,
|
159
|
+
limit_choices_to: Any = None,
|
160
|
+
ordering: tuple[str, ...] = (),
|
161
|
+
) -> list[tuple[Any, str]]:
|
159
162
|
"""
|
160
163
|
Return choices with a default blank choices included, for use
|
161
164
|
as <select> choices for this field.
|
@@ -169,17 +172,17 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
169
172
|
qs = qs.order_by(*ordering)
|
170
173
|
return (blank_choice if include_blank else []) + [(x.id, str(x)) for x in qs]
|
171
174
|
|
172
|
-
def is_hidden(self):
|
175
|
+
def is_hidden(self) -> bool:
|
173
176
|
"""Should the related object be hidden?"""
|
174
177
|
return not self.related_name
|
175
178
|
|
176
|
-
def get_joining_columns(self):
|
179
|
+
def get_joining_columns(self) -> Any:
|
177
180
|
return self.field.get_reverse_joining_columns()
|
178
181
|
|
179
|
-
def get_extra_restriction(self, alias, related_alias):
|
182
|
+
def get_extra_restriction(self, alias: str, related_alias: str) -> Any:
|
180
183
|
return self.field.get_extra_restriction(related_alias, alias)
|
181
184
|
|
182
|
-
def set_field_name(self):
|
185
|
+
def set_field_name(self) -> None:
|
183
186
|
"""
|
184
187
|
Set the related field's name, this is not available until later stages
|
185
188
|
of app loading, so set_field_name is called from
|
@@ -189,7 +192,7 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
189
192
|
# example custom multicolumn joins currently have no remote field).
|
190
193
|
self.field_name = None
|
191
194
|
|
192
|
-
def get_accessor_name(self, model=None):
|
195
|
+
def get_accessor_name(self, model: Any = None) -> str | None:
|
193
196
|
# This method encapsulates the logic that decides what name to give an
|
194
197
|
# accessor descriptor that retrieves related many-to-one or
|
195
198
|
# many-to-many objects.
|
@@ -204,17 +207,17 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
204
207
|
return self.related_name
|
205
208
|
return None
|
206
209
|
|
207
|
-
def get_path_info(self, filtered_relation=None):
|
210
|
+
def get_path_info(self, filtered_relation: Any = None) -> Any:
|
208
211
|
if filtered_relation:
|
209
212
|
return self.field.get_reverse_path_info(filtered_relation)
|
210
213
|
else:
|
211
214
|
return self.field.reverse_path_infos
|
212
215
|
|
213
216
|
@cached_property
|
214
|
-
def path_infos(self):
|
217
|
+
def path_infos(self) -> Any:
|
215
218
|
return self.get_path_info()
|
216
219
|
|
217
|
-
def get_cache_name(self):
|
220
|
+
def get_cache_name(self) -> str | None:
|
218
221
|
"""
|
219
222
|
Return the name of the cache key to use for storing an instance of the
|
220
223
|
forward model on the reverse model.
|
@@ -239,12 +242,12 @@ class ManyToOneRel(ForeignObjectRel):
|
|
239
242
|
|
240
243
|
def __init__(
|
241
244
|
self,
|
242
|
-
field,
|
243
|
-
to,
|
244
|
-
related_name=None,
|
245
|
-
related_query_name=None,
|
246
|
-
limit_choices_to=None,
|
247
|
-
on_delete=None,
|
245
|
+
field: Any,
|
246
|
+
to: Any,
|
247
|
+
related_name: str | None = None,
|
248
|
+
related_query_name: str | None = None,
|
249
|
+
limit_choices_to: Any = None,
|
250
|
+
on_delete: Any = None,
|
248
251
|
):
|
249
252
|
super().__init__(
|
250
253
|
field,
|
@@ -257,16 +260,16 @@ class ManyToOneRel(ForeignObjectRel):
|
|
257
260
|
|
258
261
|
self.field_name = "id"
|
259
262
|
|
260
|
-
def __getstate__(self):
|
263
|
+
def __getstate__(self) -> dict[str, Any]:
|
261
264
|
state = super().__getstate__()
|
262
265
|
state.pop("related_model", None)
|
263
266
|
return state
|
264
267
|
|
265
268
|
@property
|
266
|
-
def identity(self):
|
269
|
+
def identity(self) -> tuple[Any, ...]:
|
267
270
|
return super().identity + (self.field_name,)
|
268
271
|
|
269
|
-
def get_related_field(self):
|
272
|
+
def get_related_field(self) -> Any:
|
270
273
|
"""
|
271
274
|
Return the Field in the 'to' object to which this relationship is tied.
|
272
275
|
"""
|
@@ -275,7 +278,7 @@ class ManyToOneRel(ForeignObjectRel):
|
|
275
278
|
raise FieldDoesNotExist("No related field named 'id'")
|
276
279
|
return field
|
277
280
|
|
278
|
-
def set_field_name(self):
|
281
|
+
def set_field_name(self) -> None:
|
279
282
|
pass
|
280
283
|
|
281
284
|
|
@@ -289,15 +292,15 @@ class ManyToManyRel(ForeignObjectRel):
|
|
289
292
|
|
290
293
|
def __init__(
|
291
294
|
self,
|
292
|
-
field,
|
293
|
-
to,
|
295
|
+
field: Any,
|
296
|
+
to: Any,
|
294
297
|
*,
|
295
|
-
through,
|
296
|
-
through_fields=None,
|
297
|
-
related_name=None,
|
298
|
-
related_query_name=None,
|
299
|
-
limit_choices_to=None,
|
300
|
-
symmetrical=True,
|
298
|
+
through: Any,
|
299
|
+
through_fields: tuple[str, str] | None = None,
|
300
|
+
related_name: str | None = None,
|
301
|
+
related_query_name: str | None = None,
|
302
|
+
limit_choices_to: Any = None,
|
303
|
+
symmetrical: bool = True,
|
301
304
|
):
|
302
305
|
super().__init__(
|
303
306
|
field,
|
@@ -314,14 +317,14 @@ class ManyToManyRel(ForeignObjectRel):
|
|
314
317
|
self.db_constraint = True
|
315
318
|
|
316
319
|
@property
|
317
|
-
def identity(self):
|
320
|
+
def identity(self) -> tuple[Any, ...]:
|
318
321
|
return super().identity + (
|
319
322
|
self.through,
|
320
323
|
make_hashable(self.through_fields),
|
321
324
|
self.db_constraint,
|
322
325
|
)
|
323
326
|
|
324
|
-
def get_related_field(self):
|
327
|
+
def get_related_field(self) -> Any:
|
325
328
|
"""
|
326
329
|
Return the field in the 'to' object to which this relationship is tied.
|
327
330
|
Provided for symmetry with ManyToOneRel.
|
plain/models/forms.py
CHANGED
@@ -3,7 +3,10 @@ Helper functions for creating Form classes from Plain models
|
|
3
3
|
and database field objects.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from __future__ import annotations
|
7
|
+
|
6
8
|
from itertools import chain
|
9
|
+
from typing import TYPE_CHECKING, Any
|
7
10
|
|
8
11
|
from plain.exceptions import (
|
9
12
|
NON_FIELD_ERRORS,
|
@@ -15,6 +18,9 @@ from plain.forms.fields import ChoiceField, Field
|
|
15
18
|
from plain.forms.forms import BaseForm, DeclarativeFieldsMetaclass
|
16
19
|
from plain.models.exceptions import FieldError
|
17
20
|
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
from plain.models.fields import Field as ModelField
|
23
|
+
|
18
24
|
__all__ = (
|
19
25
|
"ModelForm",
|
20
26
|
"BaseModelForm",
|
@@ -25,7 +31,11 @@ __all__ = (
|
|
25
31
|
)
|
26
32
|
|
27
33
|
|
28
|
-
def construct_instance(
|
34
|
+
def construct_instance(
|
35
|
+
form: BaseModelForm,
|
36
|
+
instance: Any,
|
37
|
+
fields: list[str] | tuple[str, ...] | None = None,
|
38
|
+
) -> Any:
|
29
39
|
"""
|
30
40
|
Construct and return a model instance from the bound ``form``'s
|
31
41
|
``cleaned_data``, but do not save the returned instance to the database.
|
@@ -65,7 +75,9 @@ def construct_instance(form, instance, fields=None):
|
|
65
75
|
# ModelForms #################################################################
|
66
76
|
|
67
77
|
|
68
|
-
def model_to_dict(
|
78
|
+
def model_to_dict(
|
79
|
+
instance: Any, fields: list[str] | tuple[str, ...] | None = None
|
80
|
+
) -> dict[str, Any]:
|
69
81
|
"""
|
70
82
|
Return a dict containing the data in ``instance`` suitable for passing as
|
71
83
|
a Form's ``initial`` keyword argument.
|
@@ -83,12 +95,12 @@ def model_to_dict(instance, fields=None):
|
|
83
95
|
|
84
96
|
|
85
97
|
def fields_for_model(
|
86
|
-
model,
|
87
|
-
fields=None,
|
88
|
-
formfield_callback=None,
|
89
|
-
error_messages=None,
|
90
|
-
field_classes=None,
|
91
|
-
):
|
98
|
+
model: type[Any],
|
99
|
+
fields: list[str] | tuple[str, ...] | None = None,
|
100
|
+
formfield_callback: Any = None,
|
101
|
+
error_messages: dict[str, Any] | None = None,
|
102
|
+
field_classes: dict[str, type[Field]] | None = None,
|
103
|
+
) -> dict[str, Field | None]:
|
92
104
|
"""
|
93
105
|
Return a dictionary containing form fields for the given model.
|
94
106
|
|
@@ -135,17 +147,28 @@ def fields_for_model(
|
|
135
147
|
|
136
148
|
|
137
149
|
class ModelFormOptions:
|
138
|
-
def __init__(self, options=None):
|
139
|
-
self.model = getattr(options, "model", None)
|
140
|
-
self.fields
|
141
|
-
|
142
|
-
|
143
|
-
self.
|
150
|
+
def __init__(self, options: Any = None) -> None:
|
151
|
+
self.model: type[Any] | None = getattr(options, "model", None)
|
152
|
+
self.fields: list[str] | tuple[str, ...] | None = getattr(
|
153
|
+
options, "fields", None
|
154
|
+
)
|
155
|
+
self.error_messages: dict[str, Any] | None = getattr(
|
156
|
+
options, "error_messages", None
|
157
|
+
)
|
158
|
+
self.field_classes: dict[str, type[Field]] | None = getattr(
|
159
|
+
options, "field_classes", None
|
160
|
+
)
|
161
|
+
self.formfield_callback: Any = getattr(options, "formfield_callback", None)
|
144
162
|
|
145
163
|
|
146
164
|
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
147
|
-
def __new__(
|
148
|
-
|
165
|
+
def __new__(
|
166
|
+
mcs: type[ModelFormMetaclass],
|
167
|
+
name: str,
|
168
|
+
bases: tuple[type, ...],
|
169
|
+
attrs: dict[str, Any],
|
170
|
+
) -> type[BaseModelForm]:
|
171
|
+
new_class = super().__new__(mcs, name, bases, attrs) # type: ignore[invalid-super-argument]
|
149
172
|
|
150
173
|
if bases == (BaseModelForm,):
|
151
174
|
return new_class
|
@@ -203,12 +226,12 @@ class BaseModelForm(BaseForm):
|
|
203
226
|
def __init__(
|
204
227
|
self,
|
205
228
|
*,
|
206
|
-
request,
|
207
|
-
auto_id="id_%s",
|
208
|
-
prefix=None,
|
209
|
-
initial=None,
|
210
|
-
instance=None,
|
211
|
-
):
|
229
|
+
request: Any,
|
230
|
+
auto_id: str = "id_%s",
|
231
|
+
prefix: str | None = None,
|
232
|
+
initial: dict[str, Any] | None = None,
|
233
|
+
instance: Any = None,
|
234
|
+
) -> None:
|
212
235
|
opts = self._meta
|
213
236
|
if opts.model is None:
|
214
237
|
raise ValueError("ModelForm has no model class specified.")
|
@@ -233,7 +256,7 @@ class BaseModelForm(BaseForm):
|
|
233
256
|
initial=object_data,
|
234
257
|
)
|
235
258
|
|
236
|
-
def _get_validation_exclusions(self):
|
259
|
+
def _get_validation_exclusions(self) -> set[str]:
|
237
260
|
"""
|
238
261
|
For backwards-compatibility, exclude several types of fields from model
|
239
262
|
validation. See tickets #12507, #12521, #12553.
|
@@ -276,11 +299,11 @@ class BaseModelForm(BaseForm):
|
|
276
299
|
exclude.add(f.name)
|
277
300
|
return exclude
|
278
301
|
|
279
|
-
def clean(self):
|
302
|
+
def clean(self) -> dict[str, Any]:
|
280
303
|
self._validate_unique = True
|
281
304
|
return self.cleaned_data
|
282
305
|
|
283
|
-
def _update_errors(self, errors):
|
306
|
+
def _update_errors(self, errors: ValidationError) -> None:
|
284
307
|
# Override any validation error messages defined at the model level
|
285
308
|
# with those defined at the form level.
|
286
309
|
opts = self._meta
|
@@ -313,7 +336,7 @@ class BaseModelForm(BaseForm):
|
|
313
336
|
|
314
337
|
self.add_error(None, errors)
|
315
338
|
|
316
|
-
def _post_clean(self):
|
339
|
+
def _post_clean(self) -> None:
|
317
340
|
opts = self._meta
|
318
341
|
|
319
342
|
exclude = self._get_validation_exclusions()
|
@@ -332,7 +355,7 @@ class BaseModelForm(BaseForm):
|
|
332
355
|
if self._validate_unique:
|
333
356
|
self.validate_unique()
|
334
357
|
|
335
|
-
def validate_unique(self):
|
358
|
+
def validate_unique(self) -> None:
|
336
359
|
"""
|
337
360
|
Call the instance's validate_unique() method and update the form's
|
338
361
|
validation errors if any were raised.
|
@@ -343,7 +366,7 @@ class BaseModelForm(BaseForm):
|
|
343
366
|
except ValidationError as e:
|
344
367
|
self._update_errors(e)
|
345
368
|
|
346
|
-
def _save_m2m(self):
|
369
|
+
def _save_m2m(self) -> None:
|
347
370
|
"""
|
348
371
|
Save the many-to-many fields and generic relations for this form.
|
349
372
|
"""
|
@@ -359,7 +382,7 @@ class BaseModelForm(BaseForm):
|
|
359
382
|
if f.name in cleaned_data:
|
360
383
|
f.save_form_data(self.instance, cleaned_data[f.name])
|
361
384
|
|
362
|
-
def save(self, commit=True):
|
385
|
+
def save(self, commit: bool = True) -> Any:
|
363
386
|
"""
|
364
387
|
Save this form's self.instance object if commit=True. Otherwise, add
|
365
388
|
a save_m2m() method to the form which can be called after the instance
|
@@ -391,28 +414,28 @@ class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
|
|
391
414
|
|
392
415
|
|
393
416
|
class ModelChoiceIteratorValue:
|
394
|
-
def __init__(self, value, instance):
|
417
|
+
def __init__(self, value: Any, instance: Any) -> None:
|
395
418
|
self.value = value
|
396
419
|
self.instance = instance
|
397
420
|
|
398
|
-
def __str__(self):
|
421
|
+
def __str__(self) -> str:
|
399
422
|
return str(self.value)
|
400
423
|
|
401
|
-
def __hash__(self):
|
424
|
+
def __hash__(self) -> int:
|
402
425
|
return hash(self.value)
|
403
426
|
|
404
|
-
def __eq__(self, other):
|
427
|
+
def __eq__(self, other: object) -> bool:
|
405
428
|
if isinstance(other, ModelChoiceIteratorValue):
|
406
429
|
other = other.value
|
407
430
|
return self.value == other
|
408
431
|
|
409
432
|
|
410
433
|
class ModelChoiceIterator:
|
411
|
-
def __init__(self, field):
|
434
|
+
def __init__(self, field: ModelChoiceField) -> None:
|
412
435
|
self.field = field
|
413
436
|
self.queryset = field.queryset
|
414
437
|
|
415
|
-
def __iter__(self):
|
438
|
+
def __iter__(self) -> Any:
|
416
439
|
if self.field.empty_label is not None:
|
417
440
|
yield ("", self.field.empty_label)
|
418
441
|
queryset = self.queryset
|
@@ -422,16 +445,16 @@ class ModelChoiceIterator:
|
|
422
445
|
for obj in queryset:
|
423
446
|
yield self.choice(obj)
|
424
447
|
|
425
|
-
def __len__(self):
|
448
|
+
def __len__(self) -> int:
|
426
449
|
# count() adds a query but uses less memory since the QuerySet results
|
427
450
|
# won't be cached. In most cases, the choices will only be iterated on,
|
428
451
|
# and __len__() won't be called.
|
429
452
|
return self.queryset.count() + (1 if self.field.empty_label is not None else 0)
|
430
453
|
|
431
|
-
def __bool__(self):
|
454
|
+
def __bool__(self) -> bool:
|
432
455
|
return self.field.empty_label is not None or self.queryset.exists()
|
433
456
|
|
434
|
-
def choice(self, obj):
|
457
|
+
def choice(self, obj: Any) -> tuple[ModelChoiceIteratorValue, str]:
|
435
458
|
return (
|
436
459
|
ModelChoiceIteratorValue(self.field.prepare_value(obj), obj),
|
437
460
|
str(obj),
|
@@ -450,13 +473,13 @@ class ModelChoiceField(ChoiceField):
|
|
450
473
|
|
451
474
|
def __init__(
|
452
475
|
self,
|
453
|
-
queryset,
|
476
|
+
queryset: Any,
|
454
477
|
*,
|
455
|
-
empty_label="---------",
|
456
|
-
required=True,
|
457
|
-
initial=None,
|
458
|
-
**kwargs,
|
459
|
-
):
|
478
|
+
empty_label: str | None = "---------",
|
479
|
+
required: bool = True,
|
480
|
+
initial: Any = None,
|
481
|
+
**kwargs: Any,
|
482
|
+
) -> None:
|
460
483
|
# Call Field instead of ChoiceField __init__() because we don't need
|
461
484
|
# ChoiceField.__init__().
|
462
485
|
Field.__init__(
|
@@ -471,22 +494,22 @@ class ModelChoiceField(ChoiceField):
|
|
471
494
|
self.empty_label = empty_label
|
472
495
|
self.queryset = queryset
|
473
496
|
|
474
|
-
def __deepcopy__(self, memo):
|
497
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> ModelChoiceField:
|
475
498
|
result = super(ChoiceField, self).__deepcopy__(memo)
|
476
499
|
# Need to force a new ModelChoiceIterator to be created, bug #11183
|
477
500
|
if self.queryset is not None:
|
478
501
|
result.queryset = self.queryset.all()
|
479
502
|
return result
|
480
503
|
|
481
|
-
def _get_queryset(self):
|
504
|
+
def _get_queryset(self) -> Any:
|
482
505
|
return self._queryset
|
483
506
|
|
484
|
-
def _set_queryset(self, queryset):
|
507
|
+
def _set_queryset(self, queryset: Any) -> None:
|
485
508
|
self._queryset = None if queryset is None else queryset.all()
|
486
509
|
|
487
510
|
queryset = property(_get_queryset, _set_queryset)
|
488
511
|
|
489
|
-
def _get_choices(self):
|
512
|
+
def _get_choices(self) -> ModelChoiceIterator:
|
490
513
|
# If self._choices is set, then somebody must have manually set
|
491
514
|
# the property self.choices. In this case, just return self._choices.
|
492
515
|
if hasattr(self, "_choices"):
|
@@ -503,12 +526,12 @@ class ModelChoiceField(ChoiceField):
|
|
503
526
|
|
504
527
|
choices = property(_get_choices, ChoiceField._set_choices)
|
505
528
|
|
506
|
-
def prepare_value(self, value):
|
529
|
+
def prepare_value(self, value: Any) -> Any:
|
507
530
|
if hasattr(value, "_meta"):
|
508
531
|
return value.id
|
509
532
|
return super().prepare_value(value)
|
510
533
|
|
511
|
-
def to_python(self, value):
|
534
|
+
def to_python(self, value: Any) -> Any:
|
512
535
|
if value in self.empty_values:
|
513
536
|
return None
|
514
537
|
try:
|
@@ -524,10 +547,10 @@ class ModelChoiceField(ChoiceField):
|
|
524
547
|
)
|
525
548
|
return value
|
526
549
|
|
527
|
-
def validate(self, value):
|
550
|
+
def validate(self, value: Any) -> None:
|
528
551
|
return Field.validate(self, value)
|
529
552
|
|
530
|
-
def has_changed(self, initial, data):
|
553
|
+
def has_changed(self, initial: Any, data: Any) -> bool:
|
531
554
|
initial_value = initial if initial is not None else ""
|
532
555
|
data_value = data if data is not None else ""
|
533
556
|
return str(self.prepare_value(initial_value)) != str(data_value)
|
@@ -542,15 +565,15 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
542
565
|
"invalid_id_value": "'%(id)s' is not a valid value.",
|
543
566
|
}
|
544
567
|
|
545
|
-
def __init__(self, queryset, **kwargs):
|
568
|
+
def __init__(self, queryset: Any, **kwargs: Any) -> None:
|
546
569
|
super().__init__(queryset, empty_label=None, **kwargs)
|
547
570
|
|
548
|
-
def to_python(self, value):
|
571
|
+
def to_python(self, value: Any) -> list[Any]:
|
549
572
|
if not value:
|
550
573
|
return []
|
551
574
|
return list(self._check_values(value))
|
552
575
|
|
553
|
-
def clean(self, value):
|
576
|
+
def clean(self, value: Any) -> Any:
|
554
577
|
value = self.prepare_value(value)
|
555
578
|
if self.required and not value:
|
556
579
|
raise ValidationError(self.error_messages["required"], code="required")
|
@@ -567,7 +590,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
567
590
|
self.run_validators(value)
|
568
591
|
return qs
|
569
592
|
|
570
|
-
def _check_values(self, value):
|
593
|
+
def _check_values(self, value: Any) -> Any:
|
571
594
|
"""
|
572
595
|
Given a list of possible PK values, return a QuerySet of the
|
573
596
|
corresponding objects. Raise a ValidationError if a given value is
|
@@ -603,7 +626,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
603
626
|
)
|
604
627
|
return qs
|
605
628
|
|
606
|
-
def prepare_value(self, value):
|
629
|
+
def prepare_value(self, value: Any) -> Any:
|
607
630
|
if (
|
608
631
|
hasattr(value, "__iter__")
|
609
632
|
and not isinstance(value, str)
|
@@ -613,7 +636,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
613
636
|
return [prepare_value(v) for v in value]
|
614
637
|
return super().prepare_value(value)
|
615
638
|
|
616
|
-
def has_changed(self, initial, data):
|
639
|
+
def has_changed(self, initial: Any, data: Any) -> bool:
|
617
640
|
if initial is None:
|
618
641
|
initial = []
|
619
642
|
if data is None:
|
@@ -624,13 +647,16 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
624
647
|
data_set = {str(value) for value in data}
|
625
648
|
return data_set != initial_set
|
626
649
|
|
627
|
-
def value_from_form_data(self, data, files, html_name):
|
650
|
+
def value_from_form_data(self, data: Any, files: Any, html_name: str) -> Any:
|
628
651
|
return data.getlist(html_name)
|
629
652
|
|
630
653
|
|
631
654
|
def modelfield_to_formfield(
|
632
|
-
modelfield
|
633
|
-
|
655
|
+
modelfield: ModelField,
|
656
|
+
form_class: type[Field] | None = None,
|
657
|
+
choices_form_class: type[Field] | None = None,
|
658
|
+
**kwargs: Any,
|
659
|
+
) -> Field | None:
|
634
660
|
defaults = {
|
635
661
|
"required": modelfield.required,
|
636
662
|
}
|
@@ -694,7 +720,7 @@ def modelfield_to_formfield(
|
|
694
720
|
**defaults,
|
695
721
|
)
|
696
722
|
|
697
|
-
if issubclass(modelfield.__class__, models.fields.PositiveIntegerRelDbTypeMixin):
|
723
|
+
if issubclass(modelfield.__class__, models.fields.PositiveIntegerRelDbTypeMixin): # type: ignore[attr-defined]
|
698
724
|
return fields.IntegerField(min_value=0, **defaults)
|
699
725
|
|
700
726
|
if isinstance(modelfield, models.TextField):
|
@@ -721,7 +747,7 @@ def modelfield_to_formfield(
|
|
721
747
|
|
722
748
|
if isinstance(modelfield, models.ForeignKey):
|
723
749
|
return ModelChoiceField(
|
724
|
-
queryset=modelfield.remote_field.model.query,
|
750
|
+
queryset=modelfield.remote_field.model.query, # type: ignore[attr-defined]
|
725
751
|
**defaults,
|
726
752
|
)
|
727
753
|
|