diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index 25557e1..96dc6c7 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -296,7 +296,7 @@ _ParentModelT = TypeVar("_ParentModelT", bound=Model) class InlineModelAdmin(Generic[_ChildModelT, _ParentModelT], BaseModelAdmin[_ChildModelT]): model: Type[_ChildModelT] = ... fk_name: Optional[str] = ... - formset: Type[BaseInlineFormSet[_ChildModelT, forms.ModelForm[_ChildModelT]]] = ... + formset: Type[BaseInlineFormSet[_ChildModelT, _ParentModelT, forms.ModelForm[_ChildModelT]]] = ... extra: int = ... min_num: Optional[int] = ... max_num: Optional[int] = ... @@ -318,7 +318,7 @@ class InlineModelAdmin(Generic[_ChildModelT, _ParentModelT], BaseModelAdmin[_Chi def get_max_num(self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any) -> Optional[int]: ... def get_formset( self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any - ) -> Type[BaseInlineFormSet[_ChildModelT, forms.ModelForm[_ChildModelT]]]: ... + ) -> Type[BaseInlineFormSet[_ChildModelT, _ParentModelT, forms.ModelForm[_ChildModelT]]]: ... def get_queryset(self, request: HttpRequest) -> QuerySet[_ChildModelT]: ... def has_add_permission(self, request: HttpRequest, obj: Optional[_ParentModelT]) -> bool: ... # type: ignore def has_change_permission(self, request: HttpRequest, obj: Optional[_ParentModelT] = ...) -> bool: ... # type: ignore diff --git a/django-stubs/forms/models.pyi b/django-stubs/forms/models.pyi index 930f3d4..d77d6c6 100644 --- a/django-stubs/forms/models.pyi +++ b/django-stubs/forms/models.pyi @@ -50,6 +50,7 @@ _ErrorMessages = Dict[str, Dict[str, str]] _FormFieldCallback = Callable[[models.Field], Field] _M = TypeVar("_M", bound=Model) +_ParentM = TypeVar("_ParentM", bound=Model) def construct_instance( form: BaseForm, instance: _M, fields: Optional[Container[str]] = ..., exclude: Optional[Container[str]] = ... @@ -187,8 +188,8 @@ def modelformset_factory( can_delete_extra: bool = ..., ) -> Type[BaseModelFormSet[_M, _ModelFormT]]: ... -class BaseInlineFormSet(BaseModelFormSet[_M, _ModelFormT]): - instance: Model = ... +class BaseInlineFormSet(Generic[_M, _ParentM, _ModelFormT], BaseModelFormSet[_M, _ModelFormT]): + instance: _ParentM save_as_new: bool = ... unique_fields: Collection[str] = ... fk: ForeignKey # set by inlineformset_set @@ -196,7 +197,7 @@ class BaseInlineFormSet(BaseModelFormSet[_M, _ModelFormT]): self, data: Optional[_DataT] = ..., files: Optional[_FilesT] = ..., - instance: Optional[_M] = ..., + instance: Optional[_ParentM] = ..., save_as_new: bool = ..., prefix: Optional[str] = ..., queryset: Optional[QuerySet[_M]] = ..., @@ -210,7 +211,7 @@ class BaseInlineFormSet(BaseModelFormSet[_M, _ModelFormT]): def get_unique_error_message(self, unique_check: Sequence[str]) -> str: ... def inlineformset_factory( - parent_model: Type[Model], + parent_model: Type[_ParentM], model: Type[_M], form: Type[_ModelFormT] = ..., formset: Type[BaseInlineFormSet] = ..., @@ -233,7 +234,7 @@ def inlineformset_factory( field_classes: Optional[Mapping[str, Type[Field]]] = ..., absolute_max: Optional[int] = ..., can_delete_extra: bool = ..., -) -> Type[BaseInlineFormSet[_M, _ModelFormT]]: ... +) -> Type[BaseInlineFormSet[_M, _ParentM, _ModelFormT]]: ... class InlineForeignKeyField(Field): disabled: bool diff --git a/tests/typecheck/test_formsets.yml b/tests/typecheck/test_formsets.yml new file mode 100644 index 0000000..ed77314 --- /dev/null +++ b/tests/typecheck/test_formsets.yml @@ -0,0 +1,21 @@ +- case: inlineformset_factory + main: | + from typing import Any, Type + from django import forms + from myapp.models import Article, Category + ArticleFS: Type[forms.BaseInlineFormSet[Article, Category, Any]] = forms.inlineformset_factory(Category, Article) + ArticleFS(instance=Article()) # E: Argument "instance" to "BaseInlineFormSet" has incompatible type "Article"; expected "Optional[Category]" + fs = ArticleFS(instance=Category()) + reveal_type(fs.instance) # N: Revealed type is "myapp.models.Category*" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + + class Article(models.Model): + pass + class Category(models.Model): + pass