From f08b428027b53ab74f0f76c65edacd4becda65e1 Mon Sep 17 00:00:00 2001 From: proxy <51172302+3n-k1@users.noreply.github.com> Date: Sat, 31 Oct 2020 14:53:45 -0400 Subject: [PATCH] make FormMixin generic to allow proper typing for LoginView (#515) closes #514 --- django-stubs/contrib/auth/views.pyi | 3 ++- django-stubs/views/generic/edit.pyi | 18 ++++++++++-------- tests/typecheck/test_views.yml | 12 ++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 tests/typecheck/test_views.yml diff --git a/django-stubs/contrib/auth/views.pyi b/django-stubs/contrib/auth/views.pyi index 124c01c..04df664 100644 --- a/django-stubs/contrib/auth/views.pyi +++ b/django-stubs/contrib/auth/views.pyi @@ -1,6 +1,7 @@ from typing import Any, Optional, Set from django.contrib.auth.base_user import AbstractBaseUser +from django.contrib.auth.forms import AuthenticationForm from django.core.handlers.wsgi import WSGIRequest from django.http.request import HttpRequest from django.http.response import HttpResponseRedirect @@ -14,7 +15,7 @@ class SuccessURLAllowedHostsMixin: success_url_allowed_hosts: Any = ... def get_success_url_allowed_hosts(self) -> Set[str]: ... -class LoginView(SuccessURLAllowedHostsMixin, FormView): +class LoginView(SuccessURLAllowedHostsMixin, FormView[AuthenticationForm]): authentication_form: Any = ... redirect_field_name: Any = ... redirect_authenticated_user: bool = ... diff --git a/django-stubs/views/generic/edit.pyi b/django-stubs/views/generic/edit.pyi index 2a2f478..bd2baee 100644 --- a/django-stubs/views/generic/edit.pyi +++ b/django-stubs/views/generic/edit.pyi @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Optional, Sequence, Type, Union +from typing import Any, Callable, Dict, Generic, Optional, Sequence, Type, TypeVar, Union from django.forms.forms import BaseForm from django.forms.models import BaseModelForm @@ -8,6 +8,8 @@ from typing_extensions import Literal from django.http import HttpRequest, HttpResponse +_FormT = TypeVar('_FormT', bound=BaseForm) + class AbstractFormMixin(ContextMixin): initial: Dict[str, Any] = ... form_class: Optional[Type[BaseForm]] = ... @@ -18,11 +20,11 @@ class AbstractFormMixin(ContextMixin): def get_form_kwargs(self) -> Dict[str, Any]: ... def get_success_url(self) -> str: ... -class FormMixin(AbstractFormMixin): - def get_form_class(self) -> Type[BaseForm]: ... - def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ... - def form_valid(self, form: BaseForm) -> HttpResponse: ... - def form_invalid(self, form: BaseForm) -> HttpResponse: ... +class FormMixin(Generic[_FormT], AbstractFormMixin): + def get_form_class(self) -> Type[_FormT]: ... + def get_form(self, form_class: Optional[Type[_FormT]] = ...) -> BaseForm: ... + def form_valid(self, form: _FormT) -> HttpResponse: ... + def form_invalid(self, form: _FormT) -> HttpResponse: ... class ModelFormMixin(AbstractFormMixin, SingleObjectMixin): fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ... @@ -36,8 +38,8 @@ class ProcessFormView(View): def post(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ... def put(self, *args: str, **kwargs: Any) -> HttpResponse: ... -class BaseFormView(FormMixin, ProcessFormView): ... -class FormView(TemplateResponseMixin, BaseFormView): ... +class BaseFormView(FormMixin[_FormT], ProcessFormView): ... +class FormView(TemplateResponseMixin, BaseFormView[_FormT]): ... class BaseCreateView(ModelFormMixin, ProcessFormView): ... class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): ... class BaseUpdateView(ModelFormMixin, ProcessFormView): ... diff --git a/tests/typecheck/test_views.yml b/tests/typecheck/test_views.yml new file mode 100644 index 0000000..4e49280 --- /dev/null +++ b/tests/typecheck/test_views.yml @@ -0,0 +1,12 @@ +- case: login_form_form_valid_typechecks + main: | + from django.contrib.auth.views import LoginView + from django.contrib.auth import login as auth_login + from django.http import HttpResponseRedirect + from django.contrib.auth.forms import AuthenticationForm + + class MyLoginView(LoginView): + def form_valid(self, form: AuthenticationForm) -> HttpResponseRedirect: + """Ensure that form can have type AuthenticationForm.""" + form.get_user() + return HttpResponseRedirect(self.get_success_url())