diff --git a/django-stubs/forms/__init__.pyi b/django-stubs/forms/__init__.pyi index fd7c1d1dd..fa6d23eff 100644 --- a/django-stubs/forms/__init__.pyi +++ b/django-stubs/forms/__init__.pyi @@ -34,6 +34,7 @@ from .forms import Form as Form from .formsets import BaseFormSet as BaseFormSet from .formsets import all_valid as all_valid from .formsets import formset_factory as formset_factory +from .models import ALL_FIELDS as ALL_FIELDS from .models import BaseInlineFormSet as BaseInlineFormSet from .models import BaseModelForm as BaseModelForm from .models import BaseModelFormSet as BaseModelFormSet diff --git a/django-stubs/forms/boundfield.pyi b/django-stubs/forms/boundfield.pyi index 61b48cd7b..13a3f0c40 100644 --- a/django-stubs/forms/boundfield.pyi +++ b/django-stubs/forms/boundfield.pyi @@ -62,15 +62,15 @@ class BoundField(RenderableFieldMixin): def auto_id(self) -> str: ... @property def id_for_label(self) -> str: ... - @property + @cached_property def initial(self) -> Any: ... def build_widget_attrs(self, attrs: _AttrsT, widget: Widget | None = None) -> _AttrsT: ... @property + def aria_describedby(self) -> str | None: ... + @property def widget_type(self) -> str: ... @property def use_fieldset(self) -> bool: ... - @property - def aria_describedby(self) -> str | None: ... class BoundWidget: parent_widget: Widget @@ -84,5 +84,6 @@ class BoundWidget: def id_for_label(self) -> str: ... @property def choice_label(self) -> str: ... + def __html__(self) -> SafeString: ... __all__ = ("BoundField",) diff --git a/django-stubs/forms/fields.pyi b/django-stubs/forms/fields.pyi index e41b1a09f..3c7312427 100644 --- a/django-stubs/forms/fields.pyi +++ b/django-stubs/forms/fields.pyi @@ -14,6 +14,7 @@ from django.forms.widgets import Widget from django.utils.choices import CallableChoiceIterator, _ChoicesCallable, _ChoicesInput from django.utils.datastructures import _PropertyDescriptor from django.utils.functional import _StrOrPromise +from typing_extensions import Self # Problem: attribute `widget` is always of type `Widget` after field instantiation. # However, on class level it can be set to `Type[Widget]` too. @@ -67,6 +68,7 @@ class Field: def widget_attrs(self, widget: Widget) -> dict[str, Any]: ... def has_changed(self, initial: Any | None, data: Any | None) -> bool: ... def get_bound_field(self, form: BaseForm, field_name: str) -> BoundField: ... + def __deepcopy__(self, memo: dict[int, Any]) -> Self: ... class CharField(Field): max_length: int | None @@ -275,9 +277,9 @@ class FileField(Field): disabled: bool = ..., label_suffix: str | None = ..., ) -> None: ... - def clean(self, data: Any, initial: Any | None = None) -> Any: ... def to_python(self, data: File | None) -> File | None: ... - def bound_data(self, data: Any | None, initial: Any) -> Any: ... + def clean(self, data: Any, initial: Any | None = None) -> Any: ... + def bound_data(self, _: Any | None, initial: Any) -> Any: ... def has_changed(self, initial: Any | None, data: Any | None) -> bool: ... class ImageField(FileField): @@ -338,6 +340,7 @@ class ChoiceField(Field): disabled: bool = ..., label_suffix: str | None = ..., ) -> None: ... + def __deepcopy__(self, memo: dict[int, Any]) -> Self: ... # Real return type of `to_python` is `str`, but it results in errors when # subclassing `ModelChoiceField`: `# type: ignore[override]` is not inherited def to_python(self, value: Any | None) -> Any: ... @@ -440,10 +443,11 @@ class MultiValueField(Field): disabled: bool = ..., label_suffix: str | None = ..., ) -> None: ... + def __deepcopy__(self, memo: dict[int, Any]) -> Self: ... + def validate(self, value: Any) -> None: ... + def clean(self, value: Any) -> Any: ... def compress(self, data_list: Any) -> Any: ... def has_changed(self, initial: Any | None, data: Any | None) -> bool: ... - def clean(self, value: Any) -> Any: ... - def validate(self, value: Any) -> None: ... class FilePathField(ChoiceField): allow_files: bool diff --git a/django-stubs/forms/forms.pyi b/django-stubs/forms/forms.pyi index 4e86ad21f..6af847e69 100644 --- a/django-stubs/forms/forms.pyi +++ b/django-stubs/forms/forms.pyi @@ -46,6 +46,7 @@ class BaseForm(RenderableFormMixin): field_order: Iterable[str] | None = None, use_required_attribute: bool | None = None, renderer: BaseRenderer | None = None, + bound_field_class: type[BoundField] | None = None, ) -> None: ... def order_fields(self, field_order: Iterable[str] | None) -> None: ... def __iter__(self) -> Iterator[BoundField]: ... diff --git a/django-stubs/forms/formsets.pyi b/django-stubs/forms/formsets.pyi index fbf221118..6b8bc8fa5 100644 --- a/django-stubs/forms/formsets.pyi +++ b/django-stubs/forms/formsets.pyi @@ -22,7 +22,6 @@ _F = TypeVar("_F", bound=BaseForm) class ManagementForm(Form): cleaned_data: dict[str, int | None] - def __init__(self, *args: Any, **kwargs: Any) -> None: ... def clean(self) -> dict[str, int | None]: ... class BaseFormSet(Sized, RenderableFormMixin, Generic[_F]): @@ -62,8 +61,6 @@ class BaseFormSet(Sized, RenderableFormMixin, Generic[_F]): error_class: type[ErrorList] = ..., form_kwargs: dict[str, Any] | None = None, error_messages: Mapping[str, str] | None = None, - form_renderer: BaseRenderer = ..., - renderer: BaseRenderer = ..., ) -> None: ... def __iter__(self) -> Iterator[_F]: ... def __getitem__(self, index: int) -> _F: ... @@ -122,6 +119,7 @@ def formset_factory( validate_min: bool = False, absolute_max: int | None = None, can_delete_extra: bool = True, + renderer: BaseRenderer | None = None, ) -> type[BaseFormSet[_F]]: ... def all_valid(formsets: Sequence[BaseFormSet[_F]]) -> bool: ... diff --git a/django-stubs/forms/models.pyi b/django-stubs/forms/models.pyi index 034ef44e9..d20832ee4 100644 --- a/django-stubs/forms/models.pyi +++ b/django-stubs/forms/models.pyi @@ -8,6 +8,7 @@ from django.db.models.base import Model from django.db.models.fields import _AllLimitChoicesTo, _LimitChoicesTo from django.db.models.manager import Manager from django.db.models.query import QuerySet +from django.db.models.utils import AltersData from django.forms.fields import ChoiceField, Field, _ClassLevelWidgetT from django.forms.forms import BaseForm, DeclarativeFieldsMetaclass from django.forms.formsets import BaseFormSet @@ -17,6 +18,7 @@ from django.forms.widgets import Widget from django.utils.choices import BaseChoiceIterator, CallableChoiceIterator, _ChoicesCallable, _ChoicesInput from django.utils.datastructures import _PropertyDescriptor from django.utils.functional import _StrOrPromise +from typing_extensions import Self ALL_FIELDS: Literal["__all__"] @@ -67,7 +69,7 @@ class ModelFormOptions(Generic[_M]): class ModelFormMetaclass(DeclarativeFieldsMetaclass): ... -class BaseModelForm(BaseForm, Generic[_M]): +class BaseModelForm(BaseForm, AltersData, Generic[_M]): instance: _M _meta: ModelFormOptions[_M] def __init__( @@ -109,7 +111,7 @@ def modelform_factory( _ModelFormT = TypeVar("_ModelFormT", bound=ModelForm) -class BaseModelFormSet(BaseFormSet[_ModelFormT], Generic[_M, _ModelFormT]): +class BaseModelFormSet(BaseFormSet[_ModelFormT], AltersData, Generic[_M, _ModelFormT]): model: type[_M] edit_only: bool unique_fields: Collection[str] @@ -194,6 +196,14 @@ class BaseInlineFormSet(BaseModelFormSet[_M, _ModelFormT], Generic[_M, _ParentM, def add_fields(self, form: _ModelFormT, index: int | None) -> None: ... def get_unique_error_message(self, unique_check: Sequence[str]) -> str: ... +@overload +def _get_foreign_key( + parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[False] = ... +) -> ForeignKey: ... +@overload +def _get_foreign_key( + parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[True] = ... +) -> ForeignKey | None: ... def inlineformset_factory( parent_model: type[_ParentM], model: type[_M], @@ -282,6 +292,7 @@ class ModelChoiceField(ChoiceField, Generic[_M]): ) -> None: ... def validate_no_null_characters(self, value: Any) -> None: ... def get_limit_choices_to(self) -> _LimitChoicesTo: ... + def __deepcopy__(self, memo: dict[int, Any]) -> Self: ... def label_from_instance(self, obj: _M) -> str: ... choices: _PropertyDescriptor[ _ChoicesInput | _ChoicesCallable | CallableChoiceIterator, @@ -307,14 +318,6 @@ class ModelMultipleChoiceField(ModelChoiceField[_M]): def has_changed(self, initial: Collection[Any] | None, data: Collection[Any] | None) -> bool: ... # type: ignore[override] def modelform_defines_fields(form_class: type[ModelForm]) -> bool: ... -@overload -def _get_foreign_key( - parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[True] = True -) -> ForeignKey | None: ... -@overload -def _get_foreign_key( - parent_model: type[Model], model: type[Model], fk_name: str | None = None, can_fail: Literal[False] = False -) -> ForeignKey: ... __all__ = ( "ALL_FIELDS", diff --git a/django-stubs/forms/utils.pyi b/django-stubs/forms/utils.pyi index dd1cde74b..eac15600d 100644 --- a/django-stubs/forms/utils.pyi +++ b/django-stubs/forms/utils.pyi @@ -62,7 +62,6 @@ class ErrorList(UserList[ValidationError | _StrOrPromise], RenderableErrorMixin) template_name_ul: str error_class: str renderer: BaseRenderer - field_name: str | None def __init__( self, initlist: ErrorList | Sequence[str | Exception] | None = None, diff --git a/django-stubs/forms/widgets.pyi b/django-stubs/forms/widgets.pyi index b1867a910..2bb9af73e 100644 --- a/django-stubs/forms/widgets.pyi +++ b/django-stubs/forms/widgets.pyi @@ -45,6 +45,7 @@ class Media: @staticmethod def merge(*lists: Iterable[Any]) -> list[Any]: ... def __add__(self, other: Media) -> Media: ... + def __html__(self) -> SafeString: ... class MediaDefiningClass(type): def __new__( @@ -81,7 +82,7 @@ class Widget(metaclass=MediaDefiningClass): def use_required_attribute(self, initial: Any) -> bool: ... class Input(Widget): - input_type: str + input_type: str | None template_name: str class TextInput(Input): @@ -120,7 +121,6 @@ class PasswordInput(Input): def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ... class HiddenInput(Input): - choices: _Choices input_type: str template_name: str @@ -201,7 +201,6 @@ class ChoiceWidget(Widget): def optgroups( self, name: str, value: list[str], attrs: _OptAttrs | None = None ) -> list[tuple[str | None, list[dict[str, Any]], int | None]]: ... - def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ... def create_option( self, name: str, @@ -212,6 +211,7 @@ class ChoiceWidget(Widget): subindex: int | None = None, attrs: _OptAttrs | None = None, ) -> dict[str, Any]: ... + def get_context(self, name: str, value: Any, attrs: _OptAttrs | None) -> dict[str, Any]: ... def id_for_label(self, id_: str, index: str = "0") -> str: ... def value_from_datadict(self, data: _DataT, files: _FilesT, name: str) -> Any: ... def format_value(self, value: Any) -> list[str]: ... # type: ignore[override] @@ -237,19 +237,17 @@ class SelectMultiple(Select): def value_omitted_from_data(self, data: _DataT, files: _FilesT, name: str) -> bool: ... class RadioSelect(ChoiceWidget): - can_add_related: bool input_type: str template_name: str option_template_name: str + def id_for_label(self, id_: str, index: str | None = None) -> str: ... -class CheckboxSelectMultiple(ChoiceWidget): - can_add_related: bool +class CheckboxSelectMultiple(RadioSelect): input_type: str template_name: str option_template_name: str def use_required_attribute(self, initial: Any) -> bool: ... def value_omitted_from_data(self, data: _DataT, files: _FilesT, name: str) -> bool: ... - def id_for_label(self, id_: str, index: str | None = None) -> str: ... class MultiWidget(Widget): template_name: str diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index 5647704fe..23bcebf09 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -183,18 +183,7 @@ django.contrib.gis.db.models.functions.GeoFuncMixin.name django.contrib.gis.db.models.functions.GeoFuncMixin.resolve_expression django.contrib.gis.db.models.functions.Length.as_sql django.contrib.gis.db.models.lookups.RasterBandTransform.as_sql -django.contrib.gis.forms.ALL_FIELDS -django.contrib.gis.forms.BaseForm.__init__ -django.contrib.gis.forms.BaseFormSet.__init__ django.contrib.gis.forms.BaseModelFormSet.model -django.contrib.gis.forms.ChoiceField.__deepcopy__ -django.contrib.gis.forms.Field.__deepcopy__ -django.contrib.gis.forms.FileField.bound_data -django.contrib.gis.forms.Media.__html__ -django.contrib.gis.forms.ModelChoiceField.__deepcopy__ -django.contrib.gis.forms.MultiValueField.__deepcopy__ -django.contrib.gis.forms.RadioSelect.id_for_label -django.contrib.gis.forms.formset_factory django.contrib.gis.forms.inlineformset_factory django.contrib.gis.forms.modelformset_factory django.contrib.postgres.fields.ArrayField.formfield @@ -449,37 +438,13 @@ django.db.models.fields.reverse_related.ManyToManyRel.identity django.db.models.fields.reverse_related.ManyToOneRel.identity django.db.models.indexes.IndexExpression.wrapper_classes django.db.utils.DatabaseErrorWrapper.__call__ -django.forms.ALL_FIELDS -django.forms.BaseForm.__init__ -django.forms.BaseFormSet.__init__ django.forms.BaseModelFormSet.model -django.forms.ChoiceField.__deepcopy__ -django.forms.Field.__deepcopy__ -django.forms.FileField.bound_data -django.forms.Media.__html__ -django.forms.ModelChoiceField.__deepcopy__ -django.forms.MultiValueField.__deepcopy__ -django.forms.RadioSelect.id_for_label -django.forms.boundfield.BoundWidget.__html__ -django.forms.fields.ChoiceField.__deepcopy__ -django.forms.fields.Field.__deepcopy__ -django.forms.fields.FileField.bound_data -django.forms.fields.MultiValueField.__deepcopy__ -django.forms.forms.BaseForm.__init__ -django.forms.formset_factory -django.forms.formsets.BaseFormSet.__init__ -django.forms.formsets.ManagementForm.__init__ -django.forms.formsets.formset_factory django.forms.inlineformset_factory django.forms.modelformset_factory django.forms.models.BaseModelFormSet.model -django.forms.models.ModelChoiceField.__deepcopy__ django.forms.models.inlineformset_factory django.forms.models.modelformset_factory django.forms.widgets.ChoiceWidget.template_name -django.forms.widgets.Input.input_type -django.forms.widgets.Media.__html__ -django.forms.widgets.RadioSelect.id_for_label django.test.selenium.SeleniumTestCase django.test.selenium.SeleniumTestCase.tags django.urls.conf.path