Skip to content

Commit da11939

Browse files
Add missing methods and fix inheritance in forms stubs (#3153)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
1 parent 69f9736 commit da11939

9 files changed

Lines changed: 33 additions & 63 deletions

File tree

django-stubs/forms/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ from .forms import Form as Form
3434
from .formsets import BaseFormSet as BaseFormSet
3535
from .formsets import all_valid as all_valid
3636
from .formsets import formset_factory as formset_factory
37+
from .models import ALL_FIELDS as ALL_FIELDS
3738
from .models import BaseInlineFormSet as BaseInlineFormSet
3839
from .models import BaseModelForm as BaseModelForm
3940
from .models import BaseModelFormSet as BaseModelFormSet

django-stubs/forms/boundfield.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ class BoundField(RenderableFieldMixin):
6262
def auto_id(self) -> str: ...
6363
@property
6464
def id_for_label(self) -> str: ...
65-
@property
65+
@cached_property
6666
def initial(self) -> Any: ...
6767
def build_widget_attrs(self, attrs: _AttrsT, widget: Widget | None = None) -> _AttrsT: ...
6868
@property
69+
def aria_describedby(self) -> str | None: ...
70+
@property
6971
def widget_type(self) -> str: ...
7072
@property
7173
def use_fieldset(self) -> bool: ...
72-
@property
73-
def aria_describedby(self) -> str | None: ...
7474

7575
class BoundWidget:
7676
parent_widget: Widget
@@ -84,5 +84,6 @@ class BoundWidget:
8484
def id_for_label(self) -> str: ...
8585
@property
8686
def choice_label(self) -> str: ...
87+
def __html__(self) -> SafeString: ...
8788

8889
__all__ = ("BoundField",)

django-stubs/forms/fields.pyi

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from django.forms.widgets import Widget
1414
from django.utils.choices import CallableChoiceIterator, _ChoicesCallable, _ChoicesInput
1515
from django.utils.datastructures import _PropertyDescriptor
1616
from django.utils.functional import _StrOrPromise
17+
from typing_extensions import Self
1718

1819
# Problem: attribute `widget` is always of type `Widget` after field instantiation.
1920
# However, on class level it can be set to `Type[Widget]` too.
@@ -67,6 +68,7 @@ class Field:
6768
def widget_attrs(self, widget: Widget) -> dict[str, Any]: ...
6869
def has_changed(self, initial: Any | None, data: Any | None) -> bool: ...
6970
def get_bound_field(self, form: BaseForm, field_name: str) -> BoundField: ...
71+
def __deepcopy__(self, memo: dict[int, Any]) -> Self: ...
7072

7173
class CharField(Field):
7274
max_length: int | None
@@ -275,9 +277,9 @@ class FileField(Field):
275277
disabled: bool = ...,
276278
label_suffix: str | None = ...,
277279
) -> None: ...
278-
def clean(self, data: Any, initial: Any | None = None) -> Any: ...
279280
def to_python(self, data: File | None) -> File | None: ...
280-
def bound_data(self, data: Any | None, initial: Any) -> Any: ...
281+
def clean(self, data: Any, initial: Any | None = None) -> Any: ...
282+
def bound_data(self, _: Any | None, initial: Any) -> Any: ...
281283
def has_changed(self, initial: Any | None, data: Any | None) -> bool: ...
282284

283285
class ImageField(FileField):
@@ -338,6 +340,7 @@ class ChoiceField(Field):
338340
disabled: bool = ...,
339341
label_suffix: str | None = ...,
340342
) -> None: ...
343+
def __deepcopy__(self, memo: dict[int, Any]) -> Self: ...
341344
# Real return type of `to_python` is `str`, but it results in errors when
342345
# subclassing `ModelChoiceField`: `# type: ignore[override]` is not inherited
343346
def to_python(self, value: Any | None) -> Any: ...
@@ -440,10 +443,11 @@ class MultiValueField(Field):
440443
disabled: bool = ...,
441444
label_suffix: str | None = ...,
442445
) -> None: ...
446+
def __deepcopy__(self, memo: dict[int, Any]) -> Self: ...
447+
def validate(self, value: Any) -> None: ...
448+
def clean(self, value: Any) -> Any: ...
443449
def compress(self, data_list: Any) -> Any: ...
444450
def has_changed(self, initial: Any | None, data: Any | None) -> bool: ...
445-
def clean(self, value: Any) -> Any: ...
446-
def validate(self, value: Any) -> None: ...
447451

448452
class FilePathField(ChoiceField):
449453
allow_files: bool

django-stubs/forms/forms.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class BaseForm(RenderableFormMixin):
4646
field_order: Iterable[str] | None = None,
4747
use_required_attribute: bool | None = None,
4848
renderer: BaseRenderer | None = None,
49+
bound_field_class: type[BoundField] | None = None,
4950
) -> None: ...
5051
def order_fields(self, field_order: Iterable[str] | None) -> None: ...
5152
def __iter__(self) -> Iterator[BoundField]: ...

django-stubs/forms/formsets.pyi

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ _F = TypeVar("_F", bound=BaseForm)
2222

2323
class ManagementForm(Form):
2424
cleaned_data: dict[str, int | None]
25-
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
2625
def clean(self) -> dict[str, int | None]: ...
2726

2827
class BaseFormSet(Sized, RenderableFormMixin, Generic[_F]):
@@ -62,8 +61,6 @@ class BaseFormSet(Sized, RenderableFormMixin, Generic[_F]):
6261
error_class: type[ErrorList] = ...,
6362
form_kwargs: dict[str, Any] | None = None,
6463
error_messages: Mapping[str, str] | None = None,
65-
form_renderer: BaseRenderer = ...,
66-
renderer: BaseRenderer = ...,
6764
) -> None: ...
6865
def __iter__(self) -> Iterator[_F]: ...
6966
def __getitem__(self, index: int) -> _F: ...
@@ -122,6 +119,7 @@ def formset_factory(
122119
validate_min: bool = False,
123120
absolute_max: int | None = None,
124121
can_delete_extra: bool = True,
122+
renderer: BaseRenderer | None = None,
125123
) -> type[BaseFormSet[_F]]: ...
126124
def all_valid(formsets: Sequence[BaseFormSet[_F]]) -> bool: ...
127125

django-stubs/forms/models.pyi

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from django.db.models.base import Model
88
from django.db.models.fields import _AllLimitChoicesTo, _LimitChoicesTo
99
from django.db.models.manager import Manager
1010
from django.db.models.query import QuerySet
11+
from django.db.models.utils import AltersData
1112
from django.forms.fields import ChoiceField, Field, _ClassLevelWidgetT
1213
from django.forms.forms import BaseForm, DeclarativeFieldsMetaclass
1314
from django.forms.formsets import BaseFormSet
@@ -17,6 +18,7 @@ from django.forms.widgets import Widget
1718
from django.utils.choices import BaseChoiceIterator, CallableChoiceIterator, _ChoicesCallable, _ChoicesInput
1819
from django.utils.datastructures import _PropertyDescriptor
1920
from django.utils.functional import _StrOrPromise
21+
from typing_extensions import Self
2022

2123
ALL_FIELDS: Literal["__all__"]
2224

@@ -67,7 +69,7 @@ class ModelFormOptions(Generic[_M]):
6769

6870
class ModelFormMetaclass(DeclarativeFieldsMetaclass): ...
6971

70-
class BaseModelForm(BaseForm, Generic[_M]):
72+
class BaseModelForm(BaseForm, AltersData, Generic[_M]):
7173
instance: _M
7274
_meta: ModelFormOptions[_M]
7375
def __init__(
@@ -109,7 +111,7 @@ def modelform_factory(
109111

110112
_ModelFormT = TypeVar("_ModelFormT", bound=ModelForm)
111113

112-
class BaseModelFormSet(BaseFormSet[_ModelFormT], Generic[_M, _ModelFormT]):
114+
class BaseModelFormSet(BaseFormSet[_ModelFormT], AltersData, Generic[_M, _ModelFormT]):
113115
model: type[_M]
114116
edit_only: bool
115117
unique_fields: Collection[str]
@@ -194,6 +196,14 @@ class BaseInlineFormSet(BaseModelFormSet[_M, _ModelFormT], Generic[_M, _ParentM,
194196
def add_fields(self, form: _ModelFormT, index: int | None) -> None: ...
195197
def get_unique_error_message(self, unique_check: Sequence[str]) -> str: ...
196198

199+
@overload
200+
def _get_foreign_key(
201+
parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[False] = ...
202+
) -> ForeignKey: ...
203+
@overload
204+
def _get_foreign_key(
205+
parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[True] = ...
206+
) -> ForeignKey | None: ...
197207
def inlineformset_factory(
198208
parent_model: type[_ParentM],
199209
model: type[_M],
@@ -282,6 +292,7 @@ class ModelChoiceField(ChoiceField, Generic[_M]):
282292
) -> None: ...
283293
def validate_no_null_characters(self, value: Any) -> None: ...
284294
def get_limit_choices_to(self) -> _LimitChoicesTo: ...
295+
def __deepcopy__(self, memo: dict[int, Any]) -> Self: ...
285296
def label_from_instance(self, obj: _M) -> str: ...
286297
choices: _PropertyDescriptor[
287298
_ChoicesInput | _ChoicesCallable | CallableChoiceIterator,
@@ -307,14 +318,6 @@ class ModelMultipleChoiceField(ModelChoiceField[_M]):
307318
def has_changed(self, initial: Collection[Any] | None, data: Collection[Any] | None) -> bool: ... # type: ignore[override]
308319

309320
def modelform_defines_fields(form_class: type[ModelForm]) -> bool: ...
310-
@overload
311-
def _get_foreign_key(
312-
parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[True] = True
313-
) -> ForeignKey | None: ...
314-
@overload
315-
def _get_foreign_key(
316-
parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[False] = False
317-
) -> ForeignKey: ...
318321

319322
__all__ = (
320323
"ALL_FIELDS",

django-stubs/forms/utils.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class ErrorList(UserList[ValidationError | _StrOrPromise], RenderableErrorMixin)
6262
template_name_ul: str
6363
error_class: str
6464
renderer: BaseRenderer
65-
field_name: str | None
6665
def __init__(
6766
self,
6867
initlist: ErrorList | Sequence[str | Exception] | None = None,

django-stubs/forms/widgets.pyi

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Media:
4545
@staticmethod
4646
def merge(*lists: Iterable[Any]) -> list[Any]: ...
4747
def __add__(self, other: Media) -> Media: ...
48+
def __html__(self) -> SafeString: ...
4849

4950
class MediaDefiningClass(type):
5051
def __new__(
@@ -81,7 +82,7 @@ class Widget(metaclass=MediaDefiningClass):
8182
def use_required_attribute(self, initial: Any) -> bool: ...
8283

8384
class Input(Widget):
84-
input_type: str
85+
input_type: str | None
8586
template_name: str
8687

8788
class TextInput(Input):
@@ -120,7 +121,6 @@ class PasswordInput(Input):
120121
def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ...
121122

122123
class HiddenInput(Input):
123-
choices: _Choices
124124
input_type: str
125125
template_name: str
126126

@@ -201,7 +201,6 @@ class ChoiceWidget(Widget):
201201
def optgroups(
202202
self, name: str, value: list[str], attrs: _OptAttrs | None = None
203203
) -> list[tuple[str | None, list[dict[str, Any]], int | None]]: ...
204-
def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ...
205204
def create_option(
206205
self,
207206
name: str,
@@ -212,6 +211,7 @@ class ChoiceWidget(Widget):
212211
subindex: int | None = None,
213212
attrs: _OptAttrs | None = None,
214213
) -> dict[str, Any]: ...
214+
def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ...
215215
def id_for_label(self, id_: str, index: str = "0") -> str: ...
216216
def value_from_datadict(self, data: _DataT, files: _FilesT, name: str) -> Any: ...
217217
def format_value(self, value: Any) -> list[str]: ... # type: ignore[override]
@@ -237,19 +237,17 @@ class SelectMultiple(Select):
237237
def value_omitted_from_data(self, data: _DataT, files: _FilesT, name: str) -> bool: ...
238238

239239
class RadioSelect(ChoiceWidget):
240-
can_add_related: bool
241240
input_type: str
242241
template_name: str
243242
option_template_name: str
243+
def id_for_label(self, id_: str, index: str | None = None) -> str: ...
244244

245-
class CheckboxSelectMultiple(ChoiceWidget):
246-
can_add_related: bool
245+
class CheckboxSelectMultiple(RadioSelect):
247246
input_type: str
248247
template_name: str
249248
option_template_name: str
250249
def use_required_attribute(self, initial: Any) -> bool: ...
251250
def value_omitted_from_data(self, data: _DataT, files: _FilesT, name: str) -> bool: ...
252-
def id_for_label(self, id_: str, index: str | None = None) -> str: ...
253251

254252
class MultiWidget(Widget):
255253
template_name: str

scripts/stubtest/allowlist_todo.txt

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -183,18 +183,7 @@ django.contrib.gis.db.models.functions.GeoFuncMixin.name
183183
django.contrib.gis.db.models.functions.GeoFuncMixin.resolve_expression
184184
django.contrib.gis.db.models.functions.Length.as_sql
185185
django.contrib.gis.db.models.lookups.RasterBandTransform.as_sql
186-
django.contrib.gis.forms.ALL_FIELDS
187-
django.contrib.gis.forms.BaseForm.__init__
188-
django.contrib.gis.forms.BaseFormSet.__init__
189186
django.contrib.gis.forms.BaseModelFormSet.model
190-
django.contrib.gis.forms.ChoiceField.__deepcopy__
191-
django.contrib.gis.forms.Field.__deepcopy__
192-
django.contrib.gis.forms.FileField.bound_data
193-
django.contrib.gis.forms.Media.__html__
194-
django.contrib.gis.forms.ModelChoiceField.__deepcopy__
195-
django.contrib.gis.forms.MultiValueField.__deepcopy__
196-
django.contrib.gis.forms.RadioSelect.id_for_label
197-
django.contrib.gis.forms.formset_factory
198187
django.contrib.gis.forms.inlineformset_factory
199188
django.contrib.gis.forms.modelformset_factory
200189
django.contrib.postgres.fields.ArrayField.formfield
@@ -449,37 +438,13 @@ django.db.models.fields.reverse_related.ManyToManyRel.identity
449438
django.db.models.fields.reverse_related.ManyToOneRel.identity
450439
django.db.models.indexes.IndexExpression.wrapper_classes
451440
django.db.utils.DatabaseErrorWrapper.__call__
452-
django.forms.ALL_FIELDS
453-
django.forms.BaseForm.__init__
454-
django.forms.BaseFormSet.__init__
455441
django.forms.BaseModelFormSet.model
456-
django.forms.ChoiceField.__deepcopy__
457-
django.forms.Field.__deepcopy__
458-
django.forms.FileField.bound_data
459-
django.forms.Media.__html__
460-
django.forms.ModelChoiceField.__deepcopy__
461-
django.forms.MultiValueField.__deepcopy__
462-
django.forms.RadioSelect.id_for_label
463-
django.forms.boundfield.BoundWidget.__html__
464-
django.forms.fields.ChoiceField.__deepcopy__
465-
django.forms.fields.Field.__deepcopy__
466-
django.forms.fields.FileField.bound_data
467-
django.forms.fields.MultiValueField.__deepcopy__
468-
django.forms.forms.BaseForm.__init__
469-
django.forms.formset_factory
470-
django.forms.formsets.BaseFormSet.__init__
471-
django.forms.formsets.ManagementForm.__init__
472-
django.forms.formsets.formset_factory
473442
django.forms.inlineformset_factory
474443
django.forms.modelformset_factory
475444
django.forms.models.BaseModelFormSet.model
476-
django.forms.models.ModelChoiceField.__deepcopy__
477445
django.forms.models.inlineformset_factory
478446
django.forms.models.modelformset_factory
479447
django.forms.widgets.ChoiceWidget.template_name
480-
django.forms.widgets.Input.input_type
481-
django.forms.widgets.Media.__html__
482-
django.forms.widgets.RadioSelect.id_for_label
483448
django.test.selenium.SeleniumTestCase
484449
django.test.selenium.SeleniumTestCase.tags
485450
django.urls.conf.path

0 commit comments

Comments
 (0)