0c6c536d07
Tightens the child model so the type is honest end to end. Previously a ``SafeText``/``mark_safe`` string passed as a child rendered unescaped — a trusted-HTML-as-string backdoor that ``Child = Node | str`` couldn't express (every ``SafeText`` is a ``str``). Now ``_child_key`` escapes *every* string child; the only way to put trusted pre-rendered HTML into the tree is a ``Safe`` node. So a ``str`` child is always untrusted text — which is exactly what the renderer escapes. Converted the trusted-HTML children that relied on the old passthrough: - ``CsrfInput`` and the Alpine selectors (``GameStatusSelector`` / ``SessionDeviceSelector``) now return ``Safe`` nodes instead of ``mark_safe`` strings — they are always tree children. - ``popover_content`` is now a ``Child`` (it is rendered as a child); the one HTML caller (``LinkedPurchase``) passes ``Safe(...)``. - View-side children that were ``mark_safe`` strings → ``Safe(...)``: ``_played_row`` (game detail), the stat SVGs and `` `` spacer (game), the login table (auth), the manual session-form field/label markup (session), and ``_purchase_name`` (stats). - ``SimpleTable.header_action`` typed ``Child``. The script-tag string helpers (``ModuleScript`` / ``StaticScript`` / ``ExternalScript``) stay ``SafeText`` strings: they are only ever joined into the ``scripts=`` string, never used as tree children. ``Children`` regains a bare ``Node`` member (a single node child is valid); the one ``*children`` site (``Popover``) normalises via ``as_children`` first. Tests that asserted the old SafeText-passthrough now assert the new rule (mark_safe child escaped; ``Safe`` node passes through). Full suite green (445; +2 new escaping tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
1.5 KiB
Python
56 lines
1.5 KiB
Python
"""Authentication views rendered with the Python layout (replaces
|
|
registration/login.html)."""
|
|
|
|
from django.contrib.auth import views as auth_views
|
|
from django.http import HttpResponse
|
|
|
|
from common.components import CsrfInput, Div, Element, Input, Node, Safe
|
|
from common.components.primitives import Td, Tr
|
|
from common.layout import render_page
|
|
|
|
|
|
def _login_content(form, request) -> Node:
|
|
table = Element(
|
|
"table",
|
|
children=[
|
|
CsrfInput(request),
|
|
Safe(str(form.as_table())),
|
|
Tr(
|
|
children=[
|
|
Td(),
|
|
Td(
|
|
children=[
|
|
Input(type="submit", attributes=[("value", "Login")])
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
return Div(
|
|
[("class", "flex items-center flex-col")],
|
|
[
|
|
Element(
|
|
"h2",
|
|
attributes=[("class", "text-3xl text-white mb-8")],
|
|
children=["Please log in to continue"],
|
|
),
|
|
Element(
|
|
"form",
|
|
attributes=[("method", "post")],
|
|
children=[table],
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
class LoginView(auth_views.LoginView):
|
|
"""Django's LoginView, but the page body is built in Python."""
|
|
|
|
def render_to_response(self, context, **response_kwargs) -> HttpResponse:
|
|
return render_page(
|
|
self.request,
|
|
_login_content(context["form"], self.request),
|
|
title="Login",
|
|
)
|