Skip to content

Commit 569afd6

Browse files
committed
Move view mixins to view inheritance for better typing
1 parent 334bbb6 commit 569afd6

File tree

19 files changed

+95
-98
lines changed

19 files changed

+95
-98
lines changed
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
from plain.auth.views import AuthViewMixin
1+
from plain.auth.views import AuthView
22
from plain.http import Response, ResponseForbidden, ResponseRedirect
3-
from plain.sessions.views import SessionViewMixin
4-
from plain.views import View
3+
from plain.sessions.views import SessionView
54

65
from .constants import IMPERSONATE_SESSION_KEY
76
from .permissions import can_be_impersonator
87
from .requests import get_request_impersonator
98

109

11-
class ImpersonateStartView(AuthViewMixin, View):
10+
class ImpersonateStartView(AuthView):
1211
def get(self) -> Response:
1312
# We *could* already be impersonating, so need to consider that
1413
impersonator = get_request_impersonator(self.request) or self.user
@@ -19,7 +18,7 @@ def get(self) -> Response:
1918
return ResponseForbidden()
2019

2120

22-
class ImpersonateStopView(SessionViewMixin, View):
21+
class ImpersonateStopView(SessionView):
2322
def get(self) -> Response:
2423
self.session.pop(IMPERSONATE_SESSION_KEY)
2524
return ResponseRedirect(self.request.query_params.get("next", "/"))

plain-admin/plain/admin/views/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import TYPE_CHECKING, Any, Optional
22

3-
from plain.auth.views import AuthViewMixin
3+
from plain.auth.views import AuthView
44
from plain.runtime import settings
55
from plain.urls import reverse
66
from plain.utils import timezone
@@ -13,15 +13,15 @@
1313
from .types import Img
1414

1515
if TYPE_CHECKING:
16-
from plain.http import Response
16+
from plain.http import ResponseBase
1717

1818
from ..cards import Card
1919

2020

2121
URL_NAMESPACE = "admin"
2222

2323

24-
class AdminView(AuthViewMixin, TemplateView):
24+
class AdminView(AuthView, TemplateView):
2525
admin_required = True
2626

2727
title: str = ""
@@ -44,7 +44,7 @@ class AdminView(AuthViewMixin, TemplateView):
4444
template_name = "admin/page.html"
4545
cards: list["Card"] = []
4646

47-
def get_response(self) -> "Response":
47+
def get_response(self) -> "ResponseBase":
4848
response = super().get_response()
4949
response.headers["Cache-Control"] = (
5050
"no-cache, no-store, must-revalidate, max-age=0"

plain-admin/plain/admin/views/objects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from functools import cached_property
22
from typing import TYPE_CHECKING, Any
33

4-
from plain.htmx.views import HTMXViewMixin
4+
from plain.htmx.views import HTMXView
55
from plain.http import Response, ResponseRedirect
66
from plain.models import Model
77
from plain.paginator import Paginator
@@ -18,7 +18,7 @@
1818
from plain.forms import BaseForm
1919

2020

21-
class AdminListView(HTMXViewMixin, AdminView):
21+
class AdminListView(HTMXView, AdminView):
2222
template_name = "admin/list.html"
2323
fields: list[str]
2424
actions: list[str] = []

plain-auth/plain/auth/views.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
from __future__ import annotations
22

33
from functools import cached_property
4-
from typing import TYPE_CHECKING, Any
4+
from typing import Any
55
from urllib.parse import urlparse, urlunparse
66

77
from plain.exceptions import PermissionDenied
88
from plain.http import (
99
Http404,
1010
QueryDict,
11-
Response,
11+
ResponseBase,
1212
ResponseRedirect,
1313
)
1414
from plain.runtime import settings
15-
from plain.sessions.views import SessionViewMixin
15+
from plain.sessions.views import SessionView
1616
from plain.urls import reverse
1717
from plain.utils.cache import patch_cache_control
1818
from plain.views import View
1919

2020
from .sessions import logout
2121
from .utils import resolve_url
2222

23-
if TYPE_CHECKING:
24-
from plain.http import Request
25-
2623
try:
2724
from plain.admin.impersonate import get_request_impersonator
2825
except ImportError:
@@ -35,13 +32,11 @@ def __init__(self, login_url: str | None = None, redirect_field_name: str = "nex
3532
self.redirect_field_name = redirect_field_name
3633

3734

38-
class AuthViewMixin(SessionViewMixin):
35+
class AuthView(SessionView):
3936
login_required = False
4037
admin_required = False # Implies login_required
4138
login_url = settings.AUTH_LOGIN_URL
4239

43-
request: Request
44-
4540
@cached_property
4641
def user(self) -> Any | None:
4742
"""Get the authenticated user for this request."""
@@ -84,7 +79,7 @@ def check_auth(self) -> None:
8479
# Show a 404 so we don't expose admin urls to non-admin users
8580
raise Http404()
8681

87-
def get_response(self) -> Response:
82+
def get_response(self) -> ResponseBase:
8883
try:
8984
self.check_auth()
9085
except LoginRequired as e:
@@ -110,8 +105,7 @@ def get_response(self) -> Response:
110105
else:
111106
raise PermissionDenied("Login required")
112107

113-
# Mixin expects to be used with View base class
114-
response = super().get_response() # type: ignore[misc]
108+
response = super().get_response()
115109

116110
if self.user:
117111
# Make sure it at least has private as a default

plain-auth/tests/app/urls.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from plain.auth.views import AuthViewMixin
1+
from plain.auth.views import AuthView
22
from plain.urls import Router, path
33
from plain.views import View
44

@@ -8,28 +8,28 @@ def get(self):
88
return "login"
99

1010

11-
class ProtectedView(AuthViewMixin, View):
11+
class ProtectedView(AuthView):
1212
login_required = True
1313

1414
def get(self):
1515
return "protected"
1616

1717

18-
class OpenView(AuthViewMixin, View):
18+
class OpenView(AuthView):
1919
# login_required = False
2020

2121
def get(self):
2222
return "open"
2323

2424

25-
class AdminView(AuthViewMixin, View):
25+
class AdminView(AuthView):
2626
admin_required = True
2727

2828
def get(self):
2929
return "admin"
3030

3131

32-
class NoLoginUrlView(AuthViewMixin, View):
32+
class NoLoginUrlView(AuthView):
3333
login_required = True
3434
login_url = None
3535

plain-htmx/plain/htmx/views.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
from collections.abc import Callable
44
from typing import Any
55

6-
from plain.http import Response
6+
from plain.http import ResponseBase
77
from plain.utils.cache import patch_vary_headers
8+
from plain.views import TemplateView
89

910
from .templates import render_template_fragment
1011

1112

12-
class HTMXViewMixin:
13-
"""Mixin for View classes to add HTMX-specific functionality."""
13+
class HTMXView(TemplateView):
14+
"""View with HTMX-specific functionality."""
1415

1516
def render_template(self) -> str:
16-
# These methods are provided by the View base class
17-
template = self.get_template() # type: ignore[attr-defined]
18-
context = self.get_template_context() # type: ignore[attr-defined]
17+
template = self.get_template()
18+
context = self.get_template_context()
1919

2020
if self.is_htmx_request() and self.get_htmx_fragment_name():
2121
return render_template_fragment(
@@ -26,20 +26,20 @@ def render_template(self) -> str:
2626

2727
return template.render(context)
2828

29-
def get_response(self) -> Response:
30-
response = super().get_response() # type: ignore[misc]
29+
def get_response(self) -> ResponseBase:
30+
response = super().get_response()
3131
# Tell browser caching to also consider the fragment header,
3232
# not just the url/cookie.
3333
patch_vary_headers(
3434
response, ["HX-Request", "Plain-HX-Fragment", "Plain-HX-Action"]
3535
)
3636
return response
3737

38-
def get_request_handler(self) -> Callable[..., Any]:
39-
if self.is_htmx_request():
38+
def get_request_handler(self) -> Callable[[], Any] | None:
39+
if self.is_htmx_request() and self.request.method:
4040
# You can use an htmx_{method} method on views
4141
# (or htmx_{method}_{action} for specific actions)
42-
method = f"htmx_{self.request.method.lower()}" # type: ignore[attr-defined]
42+
method = f"htmx_{self.request.method.lower()}"
4343

4444
if action := self.get_htmx_action_name():
4545
# If an action is specified, we throw an error if
@@ -52,14 +52,14 @@ def get_request_handler(self) -> Callable[..., Any]:
5252
# to a regular post method if it's not found
5353
return handler
5454

55-
return super().get_request_handler() # type: ignore[misc]
55+
return super().get_request_handler()
5656

5757
def is_htmx_request(self) -> bool:
58-
return self.request.headers.get("HX-Request") == "true" # type: ignore[attr-defined]
58+
return self.request.headers.get("HX-Request") == "true"
5959

6060
def get_htmx_fragment_name(self) -> str:
6161
# A custom header that we pass with the {% htmxfragment %} tag
62-
return self.request.headers.get("Plain-HX-Fragment", "") # type: ignore[attr-defined]
62+
return self.request.headers.get("Plain-HX-Fragment", "")
6363

6464
def get_htmx_action_name(self) -> str:
65-
return self.request.headers.get("Plain-HX-Action", "") # type: ignore[attr-defined]
65+
return self.request.headers.get("Plain-HX-Action", "")

plain-htmx/tests/test_views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from plain.htmx.views import HTMXViewMixin
1+
from plain.htmx.views import HTMXView
22
from plain.http import Response
33
from plain.test import RequestFactory
4-
from plain.views import View
54

65

7-
class V(HTMXViewMixin, View):
6+
class V(HTMXView):
87
def get(self):
98
return Response("Ok")
109

plain-loginlink/plain/loginlink/views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Any
44

55
from plain.auth import login, logout
6-
from plain.auth.views import AuthViewMixin
6+
from plain.auth.views import AuthView
77
from plain.forms import BaseForm
88
from plain.http import Response, ResponseRedirect
99
from plain.runtime import settings
@@ -19,7 +19,7 @@
1919
)
2020

2121

22-
class LoginLinkFormView(AuthViewMixin, FormView):
22+
class LoginLinkFormView(AuthView, FormView):
2323
form_class = LoginLinkForm
2424
success_url = reverse_lazy("loginlink:sent")
2525

@@ -44,7 +44,7 @@ def get_success_url(self, form: BaseForm) -> str:
4444
return self.success_url
4545

4646

47-
class LoginLinkSentView(AuthViewMixin, TemplateView):
47+
class LoginLinkSentView(AuthView, TemplateView):
4848
template_name = "loginlink/sent.html"
4949

5050
def get(self) -> Response:
@@ -66,7 +66,7 @@ def get_template_context(self) -> dict[str, Any]:
6666
return context
6767

6868

69-
class LoginLinkLoginView(AuthViewMixin, View):
69+
class LoginLinkLoginView(AuthView, View):
7070
success_url = "/"
7171

7272
def get(self) -> Response:

plain-oauth/plain/oauth/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
from plain.auth.requests import get_request_user
4-
from plain.auth.views import AuthViewMixin
4+
from plain.auth.views import AuthView
55
from plain.http import Response, ResponseRedirect
66
from plain.views import TemplateView, View
77

@@ -50,15 +50,15 @@ def get_template_context(self) -> dict:
5050
return context
5151

5252

53-
class OAuthConnectView(AuthViewMixin, View):
53+
class OAuthConnectView(AuthView):
5454
def post(self) -> Response:
5555
request = self.request
5656
provider = self.url_kwargs["provider"]
5757
provider_instance = get_oauth_provider_instance(provider_key=provider)
5858
return provider_instance.handle_connect_request(request=request)
5959

6060

61-
class OAuthDisconnectView(AuthViewMixin, View):
61+
class OAuthDisconnectView(AuthView):
6262
def post(self) -> Response:
6363
request = self.request
6464
provider = self.url_kwargs["provider"]

plain-oauth/tests/app/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from plain.auth.views import AuthViewMixin, LogoutView
1+
from plain.auth.views import AuthView, LogoutView
22
from plain.oauth.providers import get_provider_keys
33
from plain.oauth.urls import OAuthRouter
44
from plain.urls import Router, include, path
55
from plain.views import TemplateView
66

77

8-
class LoggedInView(AuthViewMixin, TemplateView):
8+
class LoggedInView(AuthView, TemplateView):
99
template_name = "index.html"
1010
login_required = True
1111

0 commit comments

Comments
 (0)