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.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +31 -22
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {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(form, instance, fields=None):
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(instance, fields=None):
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 = getattr(options, "fields", None)
141
- self.error_messages = getattr(options, "error_messages", None)
142
- self.field_classes = getattr(options, "field_classes", None)
143
- self.formfield_callback = getattr(options, "formfield_callback", None)
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__(mcs, name, bases, attrs):
148
- new_class = super().__new__(mcs, name, bases, attrs)
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, form_class=None, choices_form_class=None, **kwargs
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