Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion django-stubs/contrib/auth/views.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LoginView(RedirectURLMixin, FormView[AuthenticationForm]):
extra_context: Any
def get_redirect_url(self) -> str: ...

class LogoutView(RedirectURLMixin, TemplateView):
class LogoutView(RedirectURLMixin, TemplateView[HttpResponse]):
next_page: str | None
redirect_field_name: str
extra_context: Any
Expand Down
23 changes: 14 additions & 9 deletions django-stubs/views/generic/base.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import logging
from collections.abc import Callable, Mapping, Sequence
from typing import Any
from typing import Any, Generic, TypeVar

from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseBase
from django.template.response import TemplateResponse
from django.utils.functional import _Getter

logger: logging.Logger
Expand All @@ -12,31 +13,35 @@ class ContextMixin:
extra_context: Mapping[str, Any] | None
def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ...

class View:
_ViewResponse = TypeVar("_ViewResponse", bound=HttpResponseBase, default=HttpResponseBase)

class View(Generic[_ViewResponse]):
http_method_names: Sequence[str]
request: HttpRequest
args: Any
kwargs: Any
def __init__(self, **kwargs: Any) -> None: ...
view_is_async: _Getter[bool] | bool
@classmethod
def as_view(cls: Any, **initkwargs: Any) -> Callable[..., HttpResponseBase]: ...
def as_view(cls: Any, **initkwargs: Any) -> Callable[..., _ViewResponse]: ...
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None: ...
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponseBase: ...
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> _ViewResponse: ...
def http_method_not_allowed(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...
def options(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponseBase: ...

class TemplateResponseMixin:
_TemplateResponse = TypeVar("_TemplateResponse", bound=HttpResponse, default=TemplateResponse)

class TemplateResponseMixin(Generic[_TemplateResponse]):
template_name: str | None
template_engine: str | None
response_class: type[HttpResponse]
response_class: type[_TemplateResponse]
content_type: str | None
request: HttpRequest
def render_to_response(self, context: dict[str, Any], **response_kwargs: Any) -> HttpResponse: ...
def render_to_response(self, context: dict[str, Any], **response_kwargs: Any) -> _TemplateResponse: ...
def get_template_names(self) -> list[str]: ...

class TemplateView(TemplateResponseMixin, ContextMixin, View):
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: ...
class TemplateView(TemplateResponseMixin[_TemplateResponse], ContextMixin, View[_TemplateResponse]):
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> _TemplateResponse: ...

class RedirectView(View):
permanent: bool
Expand Down
4 changes: 4 additions & 0 deletions ext/django_stubs_ext/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from django.forms.models import BaseModelForm, BaseModelFormSet, ModelChoiceField, ModelFormOptions
from django.utils.connection import BaseConnectionHandler, ConnectionProxy
from django.utils.functional import classproperty
from django.views import View
from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeletionMixin, FormMixin
from django.views.generic.list import MultipleObjectMixin
Expand Down Expand Up @@ -84,6 +86,8 @@ def __repr__(self) -> str:
MPGeneric(ExpressionWrapper),
MPGeneric(ReverseManyToOneDescriptor),
MPGeneric(ModelIterable),
MPGeneric(View),
MPGeneric(TemplateResponseMixin),
# These types do have native `__class_getitem__` method since django 3.1:
MPGeneric(QuerySet, (3, 1)),
MPGeneric(BaseManager, (3, 1)),
Expand Down
20 changes: 20 additions & 0 deletions tests/typecheck/views/generic/test_template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,23 @@
class MyTemplateView(TemplateView):
template_name = "template.html"
extra_context = MappingProxyType({})

- case: template_view_returns_template_response
main: |
from typing import reveal_type

from django.views.generic import TemplateView
from django.test import RequestFactory


class MyView(TemplateView):
template_name = "template.html"


rf = RequestFactory()
request = rf.get("/")
view = MyView.as_view()
response = view(request)

reveal_type(response) # N: Revealed type is "django.template.response.TemplateResponse"
reveal_type(response.rendered_content) # N: Revealed type is "builtins.str"
Loading