Skip to content

Commit 77849d6

Browse files
committed
Fully annotate Field.formfield keyword args
1 parent 9110cd4 commit 77849d6

8 files changed

Lines changed: 73 additions & 60 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ from django.core.validators import _ValidatorCallable
1616
from django.db.models import Model
1717
from django.db.models.expressions import Combinable, Expression
1818
from django.db.models.fields import NOT_PROVIDED, Field, _ErrorMessagesMapping
19+
from django.forms.widgets import Widget
1920
from django.utils.choices import _Choices
2021
from django.utils.functional import _StrOrPromise
2122
from typing_extensions import TypeVar, override
@@ -125,6 +126,19 @@ class GeometryField(BaseSpatialField[_ST, _GT]):
125126
self,
126127
*,
127128
form_class: type[forms.GeometryField] | None = ...,
129+
choices_form_class: type[forms.GeometryField] | None = ...,
130+
required: bool = ...,
131+
widget: Widget | type[Widget] | None = ...,
132+
label: _StrOrPromise | None = ...,
133+
initial: Any | None = ...,
134+
help_text: _StrOrPromise = ...,
135+
error_messages: _ErrorMessagesMapping | None = ...,
136+
show_hidden_initial: bool = ...,
137+
validators: Iterable[_ValidatorCallable] = ...,
138+
localize: bool = ...,
139+
disabled: bool = ...,
140+
label_suffix: str | None = ...,
141+
# GeometryField adds `geom_type` and `srid`
128142
geom_type: str = ...,
129143
srid: Any = ...,
130144
**kwargs: Any,

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,5 @@ class ArrayField(CheckPostgresInstalledMixin, CheckFieldDefaultMixin, Field[_ST,
6969
def get_placeholder(self, value: Unused, compiler: Unused, connection: BaseDatabaseWrapper) -> str: ...
7070
@override
7171
def get_transform(self, name: str) -> type[Transform] | None: ...
72-
@override
73-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]
7472

7573
__all__ = ["ArrayField"]

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ from typing_extensions import override
1111
class HStoreField(CheckPostgresInstalledMixin, CheckFieldDefaultMixin, Field):
1212
@override
1313
def get_transform(self, name: str) -> Any: ...
14-
@override
15-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]
1614

1715
class KeyTransform(Transform):
1816
output_field: ClassVar[TextField]

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ class RangeField(CheckPostgresInstalledMixin, models.Field[Any, _RangeT]):
4040
def to_python(self, value: Any) -> Any: ...
4141
@override
4242
def value_to_string(self, obj: models.Model) -> str | None: ... # type: ignore[override]
43-
@override
44-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]
4543

4644
class ContinuousRangeField(RangeField[_RangeT]):
4745
default_bounds: str

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

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ from django.db.models.fields.reverse_related import ForeignObjectRel
1515
from django.db.models.query import _OrderByFieldName
1616
from django.db.models.query_utils import Q, RegisterLookupMixin
1717
from django.db.models.sql.compiler import SQLCompiler, _AsSqlType, _ParamsT
18+
from django.forms.widgets import Widget
1819
from django.utils.choices import BlankChoiceIterator, _Choice, _ChoiceNamedGroup, _ChoicesCallable, _ChoicesInput
1920
from django.utils.datastructures import DictWrapper
2021
from django.utils.functional import _Getter, _StrOrPromise, cached_property
@@ -248,10 +249,25 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
248249
@property
249250
def flatchoices(self) -> list[_Choice]: ...
250251
def save_form_data(self, instance: Model, data: Any) -> None: ...
252+
# form_class/choices_form_class are positional at runtime, but every Django
253+
# and third-party caller passes them as keyword args. Marking them keyword-only
254+
# here avoids needing an identical override on every Field subclass.
251255
def formfield(
252256
self,
253-
form_class: type[forms.Field] | None = None,
254-
choices_form_class: type[forms.ChoiceField] | None = None,
257+
*,
258+
form_class: type[forms.Field] | None = ...,
259+
choices_form_class: type[forms.ChoiceField] | None = ...,
260+
required: bool = ...,
261+
widget: Widget | type[Widget] | None = ...,
262+
label: _StrOrPromise | None = ...,
263+
initial: Any | None = ...,
264+
help_text: _StrOrPromise = ...,
265+
error_messages: _ErrorMessagesMapping | None = ...,
266+
show_hidden_initial: bool = ...,
267+
validators: Iterable[_ValidatorCallable] = ...,
268+
localize: bool = ...,
269+
disabled: bool = ...,
270+
label_suffix: str | None = ...,
255271
**kwargs: Any,
256272
) -> forms.Field | None: ...
257273
def value_from_object(self, obj: Model) -> _GT: ...
@@ -261,8 +277,6 @@ class IntegerField(Field[_ST, _GT]):
261277
_pyi_private_set_type: float | int | str | Combinable
262278
_pyi_private_get_type: int
263279
_pyi_lookup_exact_type: str | int
264-
@override
265-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
266280

267281
class PositiveIntegerRelDbTypeMixin:
268282
def rel_db_type(self, connection: BaseDatabaseWrapper) -> str: ...
@@ -271,30 +285,20 @@ class SmallIntegerField(IntegerField[_ST, _GT]): ...
271285

272286
class BigIntegerField(IntegerField[_ST, _GT]):
273287
MAX_BIGINT: ClassVar[int]
274-
@override
275-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
276288

277289
class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]):
278290
integer_field_class: type[IntegerField]
279-
@override
280-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
281291

282292
class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField[_ST, _GT]):
283293
integer_field_class: type[SmallIntegerField]
284-
@override
285-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
286294

287295
class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField[_ST, _GT]):
288296
integer_field_class: type[BigIntegerField]
289-
@override
290-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
291297

292298
class FloatField(Field[_ST, _GT]):
293299
_pyi_private_set_type: float | int | str | Combinable
294300
_pyi_private_get_type: float
295301
_pyi_lookup_exact_type: float
296-
@override
297-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
298302

299303
class DecimalField(Field[_ST, _GT]):
300304
_pyi_private_set_type: str | float | decimal.Decimal | Combinable
@@ -330,8 +334,6 @@ class DecimalField(Field[_ST, _GT]):
330334
) -> None: ...
331335
@cached_property
332336
def context(self) -> decimal.Context: ...
333-
@override
334-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
335337

336338
class CharField(Field[_ST, _GT]):
337339
_pyi_private_set_type: str | int | Combinable
@@ -366,8 +368,6 @@ class CharField(Field[_ST, _GT]):
366368
*,
367369
db_collation: str | None = None,
368370
) -> None: ...
369-
@override
370-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
371371

372372
class CommaSeparatedIntegerField(CharField[_ST, _GT]): ...
373373

@@ -400,13 +400,9 @@ class SlugField(CharField[_ST, _GT]):
400400
db_index: bool = True,
401401
allow_unicode: bool = False,
402402
) -> None: ...
403-
@override
404-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
405403

406404
class EmailField(CharField[_ST, _GT]):
407405
_pyi_private_set_type: str | Combinable
408-
@override
409-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
410406

411407
class URLField(CharField[_ST, _GT]):
412408
def __init__(
@@ -437,8 +433,6 @@ class URLField(CharField[_ST, _GT]):
437433
validators: Iterable[_ValidatorCallable] = ...,
438434
error_messages: _ErrorMessagesMapping | None = ...,
439435
) -> None: ...
440-
@override
441-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
442436

443437
class TextField(Field[_ST, _GT]):
444438
_pyi_private_set_type: str | Combinable
@@ -473,15 +467,11 @@ class TextField(Field[_ST, _GT]):
473467
*,
474468
db_collation: str | None = None,
475469
) -> None: ...
476-
@override
477-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
478470

479471
class BooleanField(Field[_ST, _GT]):
480472
_pyi_private_set_type: bool | Combinable
481473
_pyi_private_get_type: bool
482474
_pyi_lookup_exact_type: bool
483-
@override
484-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
485475

486476
class NullBooleanField(BooleanField[_ST, _GT]):
487477
_pyi_private_set_type: bool | Combinable | None # type: ignore[assignment]
@@ -523,8 +513,6 @@ class GenericIPAddressField(Field[_ST, _GT]):
523513
validators: Iterable[_ValidatorCallable] = ...,
524514
error_messages: _ErrorMessagesMapping | None = ...,
525515
) -> None: ...
526-
@override
527-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
528516

529517
class DateTimeCheckMixin:
530518
def check(self, **kwargs: Any) -> list[CheckMessage]: ...
@@ -563,8 +551,6 @@ class DateField(DateTimeCheckMixin, Field[_ST, _GT]):
563551
) -> None: ...
564552
@override
565553
def contribute_to_class(self, cls: type[Model], name: str, **kwargs: Any) -> None: ... # type: ignore[override]
566-
@override
567-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
568554

569555
class TimeField(DateTimeCheckMixin, Field[_ST, _GT]):
570556
_pyi_private_set_type: str | time | real_datetime | Combinable
@@ -596,15 +582,11 @@ class TimeField(DateTimeCheckMixin, Field[_ST, _GT]):
596582
validators: Iterable[_ValidatorCallable] = ...,
597583
error_messages: _ErrorMessagesMapping | None = ...,
598584
) -> None: ...
599-
@override
600-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
601585

602586
class DateTimeField(DateField[_ST, _GT]):
603587
_pyi_private_set_type: str | real_datetime | date | Combinable
604588
_pyi_private_get_type: real_datetime
605589
_pyi_lookup_exact_type: str | real_datetime
606-
@override
607-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
608590

609591
class UUIDField(Field[_ST, _GT]):
610592
_pyi_private_set_type: str | uuid.UUID
@@ -638,8 +620,6 @@ class UUIDField(Field[_ST, _GT]):
638620
validators: Iterable[_ValidatorCallable] = ...,
639621
error_messages: _ErrorMessagesMapping | None = ...,
640622
) -> None: ...
641-
@override
642-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
643623

644624
class FilePathField(Field[_ST, _GT]):
645625
path: Any
@@ -676,17 +656,13 @@ class FilePathField(Field[_ST, _GT]):
676656
validators: Iterable[_ValidatorCallable] = ...,
677657
error_messages: _ErrorMessagesMapping | None = ...,
678658
) -> None: ...
679-
@override
680-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
681659

682660
class BinaryField(Field[_ST, _GT]):
683661
_pyi_private_get_type: bytes | memoryview
684662
def get_placeholder(self, value: Any, compiler: SQLCompiler, connection: BaseDatabaseWrapper) -> str: ...
685663

686664
class DurationField(Field[_ST, _GT]):
687665
_pyi_private_get_type: timedelta
688-
@override
689-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
690666

691667
class AutoFieldMixin:
692668
db_returning: bool

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ class FileField(Field[Any, Any]):
108108
@override
109109
def contribute_to_class(self, cls: type[Model], name: str, **kwargs: Any) -> None: ... # type: ignore[override]
110110
def generate_filename(self, instance: Model | None, filename: _PathCompatible) -> str: ...
111-
@override
112-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]
113111

114112
class ImageFileDescriptor(FileDescriptor):
115113
field: ImageField
@@ -145,5 +143,3 @@ class ImageField(FileField):
145143
@override
146144
def __get__(self, instance: Any, owner: Any) -> Self: ...
147145
def update_dimension_fields(self, instance: Model, force: bool = False, *args: Any, **kwargs: Any) -> None: ...
148-
@override
149-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ class JSONField(CheckFieldDefaultMixin, Field[_ST, _GT]):
3535
def get_transform(self, name: str) -> type[Transform] | KeyTransformFactory: ... # type: ignore[override]
3636
@override
3737
def value_to_string(self, obj: Model) -> Any: ...
38-
@override
39-
def formfield(self, **kwargs: Any) -> Any: ... # type: ignore[override]
4038

4139
class DataContains(FieldGetDbPrepValueMixin, PostgresOperatorLookup): ...
4240
class ContainedBy(FieldGetDbPrepValueMixin, PostgresOperatorLookup): ...

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

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ from django.db.models.fields.reverse_related import ManyToOneRel as ManyToOneRel
2121
from django.db.models.fields.reverse_related import OneToOneRel as OneToOneRel
2222
from django.db.models.query_utils import FilteredRelation, PathInfo, Q
2323
from django.db.models.sql.where import WhereNode
24+
from django.forms.widgets import Widget
2425
from django.utils.choices import _Choices
2526
from django.utils.functional import _StrOrPromise, cached_property
2627
from typing_extensions import Self, TypeVar, override
@@ -89,8 +90,6 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]):
8990
def set_attributes_from_rel(self) -> None: ...
9091
def do_related_class(self, other: type[Model], cls: type[Model]) -> None: ...
9192
def get_limit_choices_to(self) -> _LimitChoicesTo: ...
92-
@override
93-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
9493
def related_query_name(self) -> str: ...
9594
@property
9695
def target_field(self) -> Field: ...
@@ -225,7 +224,26 @@ class ForeignKey(ForeignObject[_ST, _GT]):
225224
@override
226225
def contribute_to_related_class(self, cls: type[Model], related: RelatedField) -> None: ...
227226
@override
228-
def formfield(self, *, using: str | None = None, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
227+
def formfield( # type: ignore[override]
228+
self,
229+
*,
230+
form_class: type[forms.Field] | None = ...,
231+
choices_form_class: type[forms.ChoiceField] | None = ...,
232+
required: bool = ...,
233+
widget: Widget | type[Widget] | None = ...,
234+
label: _StrOrPromise | None = ...,
235+
initial: Any | None = ...,
236+
help_text: _StrOrPromise = ...,
237+
error_messages: _ErrorMessagesMapping | None = ...,
238+
show_hidden_initial: bool = ...,
239+
validators: Iterable[_ValidatorCallable] = ...,
240+
localize: bool = ...,
241+
disabled: bool = ...,
242+
label_suffix: str | None = ...,
243+
# ForeignKey adds `using`
244+
using: str | None = ...,
245+
**kwargs: Any,
246+
) -> forms.Field | None: ...
229247
@override
230248
def cast_db_type(self, connection: BaseDatabaseWrapper) -> str | None: ...
231249
def convert_empty_strings(self, value: Any, expression: Expression, connection: BaseDatabaseWrapper) -> Any: ...
@@ -286,8 +304,6 @@ class OneToOneField(ForeignKey[_ST, _GT]):
286304
@overload
287305
@override
288306
def __get__(self, instance: Any, owner: Any) -> Self: ...
289-
@override
290-
def formfield(self, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
291307
forward_related_accessor_class: type[ForwardOneToOneDescriptor]
292308
related_accessor_class: type[ReverseOneToOneDescriptor] # type: ignore[assignment]
293309

@@ -368,7 +384,26 @@ class ManyToManyField(RelatedField[Any, Any], Generic[_To, _Through]):
368384
def m2m_target_field_name(self) -> str: ...
369385
def m2m_reverse_target_field_name(self) -> str: ...
370386
@override
371-
def formfield(self, *, using: str | None = None, **kwargs: Any) -> forms.Field | None: ... # type: ignore[override]
387+
def formfield( # type: ignore[override]
388+
self,
389+
*,
390+
form_class: type[forms.Field] | None = ...,
391+
choices_form_class: type[forms.ChoiceField] | None = ...,
392+
required: bool = ...,
393+
widget: Widget | type[Widget] | None = ...,
394+
label: _StrOrPromise | None = ...,
395+
initial: Any | None = ...,
396+
help_text: _StrOrPromise = ...,
397+
error_messages: _ErrorMessagesMapping | None = ...,
398+
show_hidden_initial: bool = ...,
399+
validators: Iterable[_ValidatorCallable] = ...,
400+
localize: bool = ...,
401+
disabled: bool = ...,
402+
label_suffix: str | None = ...,
403+
# ManyToManyField adds `using`
404+
using: str | None = ...,
405+
**kwargs: Any,
406+
) -> forms.Field | None: ...
372407
@cached_property
373408
def path_infos(self) -> list[PathInfo]: ...
374409
@cached_property

0 commit comments

Comments
 (0)