From 0570ee839bc32b284bed2a3e1ef797f566bb3214 Mon Sep 17 00:00:00 2001 From: Noelle Leigh <5957867+noelleleigh@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:23:46 -0400 Subject: [PATCH] forms/formsets: Improve types (#271) - Adds more specific types to all code in `django-stubs/forms/formsets.pyi`. - Removes unnecessary assignments from module and class properties. - `BaseFormSet` is now a generic class which contains a form type. --- django-stubs/contrib/admin/views/main.pyi | 3 +- django-stubs/forms/formsets.pyi | 161 +++++++++++++--------- django-stubs/forms/models.pyi | 2 +- 3 files changed, 101 insertions(+), 65 deletions(-) diff --git a/django-stubs/contrib/admin/views/main.pyi b/django-stubs/contrib/admin/views/main.pyi index 2f9b9d9ba..6b75538ef 100644 --- a/django-stubs/contrib/admin/views/main.pyi +++ b/django-stubs/contrib/admin/views/main.pyi @@ -12,6 +12,7 @@ from django.db.models.expressions import Combinable, CombinedExpression, OrderBy from django.db.models.options import Options from django.db.models.query import QuerySet from django.forms.formsets import BaseFormSet +from django.forms.models import ModelForm ALL_VAR: str ORDER_VAR: str @@ -47,7 +48,7 @@ class ChangeList: queryset: Any = ... title: Any = ... pk_attname: Any = ... - formset: BaseFormSet | None + formset: BaseFormSet[ModelForm] | None def __init__( self, request: WSGIRequest, diff --git a/django-stubs/forms/formsets.pyi b/django-stubs/forms/formsets.pyi index a23277d18..bf56232d6 100644 --- a/django-stubs/forms/formsets.pyi +++ b/django-stubs/forms/formsets.pyi @@ -1,90 +1,125 @@ -from collections.abc import Mapping, Sequence, Sized -from typing import Any +from collections.abc import Iterator +from typing import Any, Generic, TypeVar -from django.forms import Form +from django.forms import BaseForm, Form +from django.forms.renderers import BaseRenderer +from django.forms.utils import ErrorDict, ErrorList, RenderableFormMixin +from django.forms.widgets import CheckboxInput, Media, NumberInput, Widget -TOTAL_FORM_COUNT: str = ... -INITIAL_FORM_COUNT: str = ... -MIN_NUM_FORM_COUNT: str = ... -MAX_NUM_FORM_COUNT: str = ... -ORDERING_FIELD_NAME: str = ... -DELETION_FIELD_NAME: str = ... +TOTAL_FORM_COUNT: str +INITIAL_FORM_COUNT: str +MIN_NUM_FORM_COUNT: str +MAX_NUM_FORM_COUNT: str +ORDERING_FIELD_NAME: str +DELETION_FIELD_NAME: str -DEFAULT_MIN_NUM: int = ... -DEFAULT_MAX_NUM: int = ... +DEFAULT_MIN_NUM: int +DEFAULT_MAX_NUM: int -class ManagementForm(Form): - cleaned_data: dict[str, int | None] +_BaseFormT = TypeVar("_BaseFormT", bound=BaseForm) + +class ManagementForm(Form): ... + +class BaseFormSet(Generic[_BaseFormT], RenderableFormMixin): + deletion_widget: type[CheckboxInput] + ordering_widget: type[NumberInput] + default_error_messages: dict[str, str] + template_name_div: str + template_name_p: str + template_name_table: str + template_name_ul: str + + is_bound: bool + prefix: str + auto_id: str + data: dict[str, Any] + files: dict[str, Any] + initial: dict[str, Any] | None + form_kwargs: dict[str, Any] + error_class: type[ErrorList] + error_messages: dict[str, Any] -class BaseFormSet(Sized, Mapping[str, Any]): - is_bound: Any = ... - prefix: Any = ... - auto_id: Any = ... - data: Any = ... - files: Any = ... - initial: Any = ... - form_kwargs: Any = ... - error_class: Any = ... def __init__( self, - data: Any | None = ..., - files: Any | None = ..., + data: dict[str, Any] | None = ..., + files: dict[str, Any] | None = ..., auto_id: str = ..., - prefix: Any | None = ..., - initial: Any | None = ..., - error_class: Any = ..., - form_kwargs: Any | None = ..., + prefix: str | None = ..., + initial: dict[str, Any] | None = ..., + error_class: type[ErrorList] = ..., + form_kwargs: dict[str, Any] | None = ..., + error_messages: dict[str, Any] | None = ..., ) -> None: ... - def __iter__(self) -> Any: ... - def __getitem__(self, index: Any) -> Any: ... - def __len__(self) -> Any: ... - def __bool__(self) -> Any: ... - def management_form(self) -> Any: ... - def total_form_count(self) -> Any: ... - def initial_form_count(self) -> Any: ... + def __iter__(self) -> Iterator[_BaseFormT]: ... + def __getitem__(self, index: int) -> _BaseFormT: ... + def __len__(self) -> int: ... + def __bool__(self) -> bool: ... + @property + def management_form(self) -> ManagementForm: ... + def total_form_count(self) -> int: ... + def initial_form_count(self) -> int: ... @property - def forms(self) -> Any: ... - def get_form_kwargs(self, index: Any) -> Any: ... + def forms(self) -> list[_BaseFormT]: ... + def get_form_kwargs(self, index: int) -> dict[str, Any]: ... @property - def initial_forms(self) -> Any: ... + def initial_forms(self) -> list[_BaseFormT]: ... @property - def extra_forms(self) -> Any: ... + def extra_forms(self) -> list[_BaseFormT]: ... @property - def empty_form(self) -> Any: ... + def empty_form(self) -> _BaseFormT: ... @property - def cleaned_data(self) -> Any: ... + def cleaned_data(self) -> list[dict[str, Any]]: ... @property - def deleted_forms(self) -> Any: ... + def deleted_forms(self) -> list[_BaseFormT]: ... @property - def ordered_forms(self) -> Any: ... + def ordered_forms(self) -> list[_BaseFormT]: ... @classmethod - def get_default_prefix(cls) -> Any: ... - def non_form_errors(self) -> Any: ... + def get_default_prefix(cls) -> str: ... + @classmethod + def get_deletion_widget(cls) -> type[Widget]: ... + @classmethod + def get_ordering_widget(cls) -> type[Widget]: ... + def non_form_errors(self) -> ErrorList: ... @property - def errors(self) -> Any: ... - def total_error_count(self) -> Any: ... - def is_valid(self) -> Any: ... - def full_clean(self) -> Any: ... + def errors(self) -> list[ErrorDict]: ... + def total_error_count(self) -> int: ... + def is_valid(self) -> bool: ... + def full_clean(self) -> None: ... def clean(self) -> None: ... - def has_changed(self) -> Any: ... - def add_fields(self, form: Any, index: Any) -> None: ... - def add_prefix(self, index: Any) -> Any: ... - def is_multipart(self) -> Any: ... + def has_changed(self) -> bool: ... + def add_fields(self, form: _BaseFormT, index: int) -> None: ... + def add_prefix(self, index: int) -> str: ... + def is_multipart(self) -> bool: ... @property - def media(self) -> Any: ... - def as_table(self) -> Any: ... - def as_p(self) -> Any: ... - def as_ul(self) -> Any: ... + def media(self) -> Media: ... + def get_context(self) -> dict[str, Any]: ... + +# Dynamic class produced by formset_factory +class _FormSet(BaseFormSet[_BaseFormT]): + form: type[_BaseFormT] + extra: int + can_order: bool + can_delete: bool + can_delete_extra: bool + min_num: int + max_num: int + absolute_max: int + validate_min: bool + validate_max: bool + renderer: BaseRenderer def formset_factory( - form: Any, - formset: Any = ..., + form: _BaseFormT, + formset: type[BaseFormSet[_BaseFormT]] = ..., extra: int = ..., can_order: bool = ..., can_delete: bool = ..., - max_num: Any | None = ..., + max_num: int | None = ..., validate_max: bool = ..., - min_num: Any | None = ..., + min_num: int | None = ..., validate_min: bool = ..., -) -> Any: ... -def all_valid(formsets: Sequence[Any]) -> bool: ... + absolute_max: int | None = ..., + can_delete_extra: bool = ..., + renderer: BaseRenderer | None = ..., +) -> type[_FormSet[_BaseFormT]]: ... +def all_valid(formsets: Iterator[BaseFormSet[Any]]) -> bool: ... diff --git a/django-stubs/forms/models.pyi b/django-stubs/forms/models.pyi index 18d0ab110..6201ad8fc 100644 --- a/django-stubs/forms/models.pyi +++ b/django-stubs/forms/models.pyi @@ -115,7 +115,7 @@ def modelform_factory( field_classes: MutableMapping[str, type[Field]] | None = ..., ) -> type[ModelForm]: ... -class BaseModelFormSet(BaseFormSet): +class BaseModelFormSet(BaseFormSet[ModelForm]): model: Any = ... unique_fields: Any = ... queryset: Any = ...