Skip to content

Commit e4c05b4

Browse files
authored
5.0: Add django.utils.choices (typeddjango#2075)
* 5.0: Add `django.utils.choices` * 5.0: Fix type errors
1 parent 11a1738 commit e4c05b4

File tree

13 files changed

+85
-74
lines changed

13 files changed

+85
-74
lines changed

django-stubs/contrib/admin/widgets.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ from typing import Any
44
from django import forms
55
from django.contrib.admin.sites import AdminSite
66
from django.core.files.base import File
7-
from django.db.models.fields import _FieldChoices
87
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
98
from django.forms.models import ModelChoiceIterator
109
from django.forms.widgets import _OptAttrs
10+
from django.utils.choices import _Choices
1111
from django.utils.functional import _StrOrPromise
1212

1313
class FilteredSelectMultiple(forms.SelectMultiple):
@@ -18,7 +18,7 @@ class FilteredSelectMultiple(forms.SelectMultiple):
1818
verbose_name: _StrOrPromise,
1919
is_stacked: bool,
2020
attrs: _OptAttrs | None = ...,
21-
choices: _FieldChoices = ...,
21+
choices: _Choices = ...,
2222
) -> None: ...
2323

2424
class AdminDateWidget(forms.DateInput):

django-stubs/contrib/gis/db/models/fields.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ from django.contrib.gis.geos import (
1414
)
1515
from django.core.validators import _ValidatorCallable
1616
from django.db.models.expressions import Combinable, Expression
17-
from django.db.models.fields import NOT_PROVIDED, Field, _ErrorMessagesMapping, _FieldChoices
17+
from django.db.models.fields import NOT_PROVIDED, Field, _ErrorMessagesMapping
18+
from django.utils.choices import _Choices
1819
from django.utils.functional import _StrOrPromise
1920

2021
# __set__ value type
@@ -58,7 +59,7 @@ class BaseSpatialField(Field[_ST, _GT]):
5859
unique_for_date: str | None = ...,
5960
unique_for_month: str | None = ...,
6061
unique_for_year: str | None = ...,
61-
choices: _FieldChoices | None = ...,
62+
choices: _Choices | None = ...,
6263
help_text: _StrOrPromise = ...,
6364
db_column: str | None = ...,
6465
db_comment: str | None = ...,
@@ -104,7 +105,7 @@ class GeometryField(BaseSpatialField[_ST, _GT]):
104105
unique_for_date: str | None = ...,
105106
unique_for_month: str | None = ...,
106107
unique_for_year: str | None = ...,
107-
choices: _FieldChoices | None = ...,
108+
choices: _Choices | None = ...,
108109
help_text: _StrOrPromise = ...,
109110
db_column: str | None = ...,
110111
db_comment: str | None = ...,

django-stubs/contrib/postgres/fields/array.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ from typing import Any, TypeVar
44
from django.core.validators import _ValidatorCallable
55
from django.db.models import Field, Transform
66
from django.db.models.expressions import Combinable, Expression
7-
from django.db.models.fields import NOT_PROVIDED, _ErrorMessagesDict, _ErrorMessagesMapping, _FieldChoices
7+
from django.db.models.fields import NOT_PROVIDED, _ErrorMessagesDict, _ErrorMessagesMapping
88
from django.db.models.fields.mixins import CheckFieldDefaultMixin
9+
from django.utils.choices import _Choices
910
from django.utils.functional import _StrOrPromise
1011

1112
# __set__ value type
@@ -44,7 +45,7 @@ class ArrayField(CheckFieldDefaultMixin, Field[_ST, _GT]):
4445
unique_for_date: str | None = ...,
4546
unique_for_month: str | None = ...,
4647
unique_for_year: str | None = ...,
47-
choices: _FieldChoices | None = ...,
48+
choices: _Choices | None = ...,
4849
help_text: _StrOrPromise = ...,
4950
db_column: str | None = ...,
5051
db_comment: str | None = ...,

django-stubs/db/models/fields/__init__.pyi

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ from django.db.models.expressions import Col, Combinable, Expression
1515
from django.db.models.fields.reverse_related import ForeignObjectRel
1616
from django.db.models.query_utils import Q, RegisterLookupMixin
1717
from django.forms import Widget
18+
from django.utils.choices import BlankChoiceIterator, _Choice, _ChoiceNamedGroup, _Choices, _ChoicesCallable
1819
from django.utils.datastructures import DictWrapper
1920
from django.utils.functional import _Getter, _StrOrPromise, cached_property
2021
from typing_extensions import Self, TypeAlias
@@ -24,19 +25,11 @@ class NOT_PROVIDED: ...
2425

2526
BLANK_CHOICE_DASH: list[tuple[str, str]]
2627

27-
_Choice: TypeAlias = tuple[Any, Any]
28-
29-
_ChoiceNamedGroup: TypeAlias = tuple[str, Iterable[_Choice]]
30-
_FieldChoices: TypeAlias = Iterable[_Choice | _ChoiceNamedGroup]
3128
_ChoicesList: TypeAlias = Sequence[_Choice] | Sequence[_ChoiceNamedGroup]
3229
_LimitChoicesTo: TypeAlias = Q | dict[str, Any]
3330

3431
_F = TypeVar("_F", bound=Field, covariant=True)
3532

36-
@type_check_only
37-
class _ChoicesCallable(Protocol):
38-
def __call__(self) -> _FieldChoices: ...
39-
4033
@type_check_only
4134
class _FieldDescriptor(Protocol[_F]):
4235
"""
@@ -175,7 +168,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
175168
unique_for_date: str | None = ...,
176169
unique_for_month: str | None = ...,
177170
unique_for_year: str | None = ...,
178-
choices: _FieldChoices | None = ...,
171+
choices: _Choices | None = ...,
179172
help_text: _StrOrPromise = ...,
180173
db_column: str | None = ...,
181174
db_tablespace: str | None = ...,
@@ -227,7 +220,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
227220
blank_choice: _ChoicesList = ...,
228221
limit_choices_to: _LimitChoicesTo | None = ...,
229222
ordering: Sequence[str] = ...,
230-
) -> _ChoicesList: ...
223+
) -> BlankChoiceIterator | _ChoicesList: ...
231224
def _get_flatchoices(self) -> list[_Choice]: ...
232225
@property
233226
def flatchoices(self) -> list[_Choice]: ...
@@ -288,7 +281,7 @@ class DecimalField(Field[_ST, _GT]):
288281
editable: bool = ...,
289282
auto_created: bool = ...,
290283
serialize: bool = ...,
291-
choices: _FieldChoices | None = ...,
284+
choices: _Choices | None = ...,
292285
help_text: _StrOrPromise = ...,
293286
db_column: str | None = ...,
294287
db_comment: str | None = ...,
@@ -320,7 +313,7 @@ class CharField(Field[_ST, _GT]):
320313
unique_for_date: str | None = ...,
321314
unique_for_month: str | None = ...,
322315
unique_for_year: str | None = ...,
323-
choices: _FieldChoices | None = ...,
316+
choices: _Choices | None = ...,
324317
help_text: _StrOrPromise = ...,
325318
db_column: str | None = ...,
326319
db_comment: str | None = ...,
@@ -350,7 +343,7 @@ class SlugField(CharField[_ST, _GT]):
350343
unique_for_date: str | None = ...,
351344
unique_for_month: str | None = ...,
352345
unique_for_year: str | None = ...,
353-
choices: _FieldChoices | None = ...,
346+
choices: _Choices | None = ...,
354347
help_text: _StrOrPromise = ...,
355348
db_column: str | None = ...,
356349
db_comment: str | None = ...,
@@ -385,7 +378,7 @@ class URLField(CharField[_ST, _GT]):
385378
unique_for_date: str | None = ...,
386379
unique_for_month: str | None = ...,
387380
unique_for_year: str | None = ...,
388-
choices: _FieldChoices | None = ...,
381+
choices: _Choices | None = ...,
389382
help_text: _StrOrPromise = ...,
390383
db_column: str | None = ...,
391384
db_comment: str | None = ...,
@@ -418,7 +411,7 @@ class TextField(Field[_ST, _GT]):
418411
unique_for_date: str | None = ...,
419412
unique_for_month: str | None = ...,
420413
unique_for_year: str | None = ...,
421-
choices: _FieldChoices | None = ...,
414+
choices: _Choices | None = ...,
422415
help_text: _StrOrPromise = ...,
423416
db_column: str | None = ...,
424417
db_comment: str | None = ...,
@@ -466,7 +459,7 @@ class GenericIPAddressField(Field[_ST, _GT]):
466459
editable: bool = ...,
467460
auto_created: bool = ...,
468461
serialize: bool = ...,
469-
choices: _FieldChoices | None = ...,
462+
choices: _Choices | None = ...,
470463
help_text: _StrOrPromise = ...,
471464
db_column: str | None = ...,
472465
db_comment: str | None = ...,
@@ -501,7 +494,7 @@ class DateField(DateTimeCheckMixin, Field[_ST, _GT]):
501494
editable: bool = ...,
502495
auto_created: bool = ...,
503496
serialize: bool = ...,
504-
choices: _FieldChoices | None = ...,
497+
choices: _Choices | None = ...,
505498
help_text: _StrOrPromise = ...,
506499
db_column: str | None = ...,
507500
db_comment: str | None = ...,
@@ -532,7 +525,7 @@ class TimeField(DateTimeCheckMixin, Field[_ST, _GT]):
532525
editable: bool = ...,
533526
auto_created: bool = ...,
534527
serialize: bool = ...,
535-
choices: _FieldChoices | None = ...,
528+
choices: _Choices | None = ...,
536529
help_text: _StrOrPromise = ...,
537530
db_column: str | None = ...,
538531
db_comment: str | None = ...,
@@ -569,7 +562,7 @@ class UUIDField(Field[_ST, _GT]):
569562
unique_for_date: str | None = ...,
570563
unique_for_month: str | None = ...,
571564
unique_for_year: str | None = ...,
572-
choices: _FieldChoices | None = ...,
565+
choices: _Choices | None = ...,
573566
help_text: _StrOrPromise = ...,
574567
db_column: str | None = ...,
575568
db_comment: str | None = ...,
@@ -606,7 +599,7 @@ class FilePathField(Field[_ST, _GT]):
606599
editable: bool = ...,
607600
auto_created: bool = ...,
608601
serialize: bool = ...,
609-
choices: _FieldChoices | None = ...,
602+
choices: _Choices | None = ...,
610603
help_text: _StrOrPromise = ...,
611604
db_column: str | None = ...,
612605
db_comment: str | None = ...,

django-stubs/db/models/fields/files.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ from django.core.files.images import ImageFile
77
from django.core.files.storage import Storage
88
from django.db.models.base import Model
99
from django.db.models.expressions import Expression
10-
from django.db.models.fields import NOT_PROVIDED, Field, _ErrorMessagesMapping, _FieldChoices
10+
from django.db.models.fields import NOT_PROVIDED, Field, _ErrorMessagesMapping
1111
from django.db.models.query_utils import DeferredAttribute
1212
from django.db.models.utils import AltersData
1313
from django.utils._os import _PathCompatible
14+
from django.utils.choices import _Choices
1415
from django.utils.functional import _StrOrPromise
1516
from typing_extensions import Self
1617

@@ -71,7 +72,7 @@ class FileField(Field):
7172
unique_for_date: str | None = ...,
7273
unique_for_month: str | None = ...,
7374
unique_for_year: str | None = ...,
74-
choices: _FieldChoices | None = ...,
75+
choices: _Choices | None = ...,
7576
help_text: _StrOrPromise = ...,
7677
db_column: str | None = ...,
7778
db_comment: str | None = ...,

django-stubs/db/models/fields/generated.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ from django.core.validators import _ValidatorCallable
44
from django.db import models
55
from django.db.backends.base.base import BaseDatabaseWrapper
66
from django.db.models.expressions import Expression
7-
from django.db.models.fields import _ErrorMessagesMapping, _FieldChoices
7+
from django.db.models.fields import _ErrorMessagesMapping
88
from django.db.models.sql import Query
9+
from django.utils.choices import _Choices
910
from django.utils.datastructures import DictWrapper
1011
from django.utils.functional import _StrOrPromise
1112

@@ -34,7 +35,7 @@ class GeneratedField(models.Field):
3435
unique_for_date: str | None = ...,
3536
unique_for_month: str | None = ...,
3637
unique_for_year: str | None = ...,
37-
choices: _FieldChoices | None = ...,
38+
choices: _Choices | None = ...,
3839
help_text: _StrOrPromise = ...,
3940
db_column: str | None = ...,
4041
db_comment: str | None = ...,

django-stubs/db/models/fields/related.pyi

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,7 @@ from uuid import UUID
55
from django.core import validators # due to weird mypy.stubtest error
66
from django.db.models.base import Model
77
from django.db.models.expressions import Combinable, Expression
8-
from django.db.models.fields import (
9-
NOT_PROVIDED,
10-
Field,
11-
_AllLimitChoicesTo,
12-
_ErrorMessagesMapping,
13-
_FieldChoices,
14-
_LimitChoicesTo,
15-
)
8+
from django.db.models.fields import NOT_PROVIDED, Field, _AllLimitChoicesTo, _ErrorMessagesMapping, _LimitChoicesTo
169
from django.db.models.fields.mixins import FieldCacheMixin
1710
from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor as ForwardManyToOneDescriptor
1811
from django.db.models.fields.related_descriptors import ForwardOneToOneDescriptor as ForwardOneToOneDescriptor
@@ -25,6 +18,7 @@ from django.db.models.fields.reverse_related import ManyToManyRel as ManyToManyR
2518
from django.db.models.fields.reverse_related import ManyToOneRel as ManyToOneRel
2619
from django.db.models.fields.reverse_related import OneToOneRel as OneToOneRel
2720
from django.db.models.query_utils import FilteredRelation, PathInfo, Q
21+
from django.utils.choices import _Choices
2822
from django.utils.functional import _StrOrPromise, cached_property
2923
from typing_extensions import Self
3024

@@ -69,7 +63,7 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]):
6963
unique_for_date: str | None = ...,
7064
unique_for_month: str | None = ...,
7165
unique_for_year: str | None = ...,
72-
choices: _FieldChoices | None = ...,
66+
choices: _Choices | None = ...,
7367
help_text: _StrOrPromise = ...,
7468
db_column: str | None = ...,
7569
db_tablespace: str | None = ...,
@@ -122,7 +116,7 @@ class ForeignObject(RelatedField[_ST, _GT]):
122116
editable: bool = ...,
123117
auto_created: bool = ...,
124118
serialize: bool = ...,
125-
choices: _FieldChoices | None = ...,
119+
choices: _Choices | None = ...,
126120
help_text: _StrOrPromise = ...,
127121
db_column: str | None = ...,
128122
db_tablespace: str | None = ...,
@@ -182,7 +176,7 @@ class ForeignKey(ForeignObject[_ST, _GT]):
182176
unique_for_date: str | None = ...,
183177
unique_for_month: str | None = ...,
184178
unique_for_year: str | None = ...,
185-
choices: _FieldChoices | None = ...,
179+
choices: _Choices | None = ...,
186180
help_text: _StrOrPromise = ...,
187181
db_column: str | None = ...,
188182
db_tablespace: str | None = ...,
@@ -224,7 +218,7 @@ class OneToOneField(ForeignKey[_ST, _GT]):
224218
unique_for_date: str | None = ...,
225219
unique_for_month: str | None = ...,
226220
unique_for_year: str | None = ...,
227-
choices: _FieldChoices | None = ...,
221+
choices: _Choices | None = ...,
228222
help_text: _StrOrPromise = ...,
229223
db_column: str | None = ...,
230224
db_tablespace: str | None = ...,
@@ -284,7 +278,7 @@ class ManyToManyField(RelatedField[Any, Any], Generic[_To, _Through]):
284278
unique_for_date: str | None = ...,
285279
unique_for_month: str | None = ...,
286280
unique_for_year: str | None = ...,
287-
choices: _FieldChoices | None = ...,
281+
choices: _Choices | None = ...,
288282
help_text: _StrOrPromise = ...,
289283
db_column: str | None = ...,
290284
db_tablespace: str | None = ...,

django-stubs/forms/fields.pyi

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@ from uuid import UUID
77

88
from django.core.files import File
99
from django.core.validators import _ValidatorCallable
10-
from django.db.models.fields import (
11-
_Choice,
12-
_ChoiceNamedGroup,
13-
_ChoicesCallable,
14-
_ErrorMessagesDict,
15-
_ErrorMessagesMapping,
16-
_FieldChoices,
17-
)
10+
from django.db.models.fields import _ErrorMessagesDict, _ErrorMessagesMapping
1811
from django.forms.boundfield import BoundField
1912
from django.forms.forms import BaseForm
2013
from django.forms.widgets import Widget
14+
from django.utils.choices import CallableChoiceIterator, _Choices, _ChoicesCallable
2115
from django.utils.datastructures import _PropertyDescriptor
2216
from django.utils.functional import _StrOrPromise
2317
from typing_extensions import TypeAlias
@@ -320,21 +314,16 @@ class NullBooleanField(BooleanField):
320314
def to_python(self, value: Any | None) -> bool | None: ... # type: ignore[override]
321315
def validate(self, value: Any) -> None: ...
322316

323-
class CallableChoiceIterator:
324-
choices_func: _ChoicesCallable
325-
def __init__(self, choices_func: _ChoicesCallable) -> None: ...
326-
def __iter__(self) -> Iterator[_Choice | _ChoiceNamedGroup]: ...
327-
328317
class ChoiceField(Field):
329318
choices: _PropertyDescriptor[
330-
_FieldChoices | _ChoicesCallable | CallableChoiceIterator,
331-
_FieldChoices | CallableChoiceIterator,
319+
_Choices | _ChoicesCallable | CallableChoiceIterator,
320+
_Choices | CallableChoiceIterator,
332321
]
333322
widget: _ClassLevelWidgetT
334323
def __init__(
335324
self,
336325
*,
337-
choices: _FieldChoices | _ChoicesCallable = ...,
326+
choices: _Choices | _ChoicesCallable = ...,
338327
required: bool = ...,
339328
widget: Widget | type[Widget] | None = ...,
340329
label: _StrOrPromise | None = ...,
@@ -365,7 +354,7 @@ class TypedChoiceField(ChoiceField):
365354
*,
366355
coerce: _CoerceCallable = ...,
367356
empty_value: str | None = ...,
368-
choices: _FieldChoices | _ChoicesCallable = ...,
357+
choices: _Choices | _ChoicesCallable = ...,
369358
required: bool = ...,
370359
widget: Widget | type[Widget] | None = ...,
371360
label: _StrOrPromise | None = ...,
@@ -393,7 +382,7 @@ class TypedMultipleChoiceField(MultipleChoiceField):
393382
*,
394383
coerce: _CoerceCallable = ...,
395384
empty_value: list[Any] | None = ...,
396-
choices: _FieldChoices | _ChoicesCallable = ...,
385+
choices: _Choices | _ChoicesCallable = ...,
397386
required: bool = ...,
398387
widget: Widget | type[Widget] | None = ...,
399388
label: _StrOrPromise | None = ...,
@@ -469,7 +458,7 @@ class FilePathField(ChoiceField):
469458
recursive: bool = ...,
470459
allow_files: bool = ...,
471460
allow_folders: bool = ...,
472-
choices: _FieldChoices | _ChoicesCallable = ...,
461+
choices: _Choices | _ChoicesCallable = ...,
473462
required: bool = ...,
474463
widget: Widget | type[Widget] | None = ...,
475464
label: _StrOrPromise | None = ...,

0 commit comments

Comments
 (0)