Migrate remaining Component() callers to Element; delete the shim
The legacy back-compat ``Component(tag_name=...)`` function (a thin
string-returning wrapper over ``Element``) was the last piece of the
pre-node-tree API. Migrate its ~18 call sites across the views to the node
builders and remove it:
- stats_content.py: the table helpers now use the whitelisted ``Td`` / ``Th``
/ ``Tr`` builders and ``Element`` for table/tbody/thead/h1; helper return
types are ``Node``.
- auth.py / statuschange.py / game.py / purchase.py: the hand-built
``<form>`` / ``<button>`` / ``<h1>`` / ``<h2>`` / ``<table>`` markup now uses
``Element("tag", ...)``.
- core.py: drop the ``Component()`` function and its back-compat note;
``common/components/__init__`` no longer exports it.
- Tests that exercised the shim now target ``Element`` directly
(test_components cache/escaping/edge-case classes; test_node_tree drops the
legacy-parity and legacy-bridge cases, which ``Element`` coverage subsumes).
- CLAUDE.md: drop the "legacy Component retained for back-compat" notes.
Full suite green (443; one obsolete legacy-bridge test removed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+9
-9
@@ -3,16 +3,16 @@ registration/login.html)."""
|
||||
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.http import HttpResponse
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from common.components import Component, CsrfInput, Div, Input
|
||||
from common.components import CsrfInput, Div, Element, Input, Node
|
||||
from common.components.primitives import Td, Tr
|
||||
from common.layout import render_page
|
||||
|
||||
|
||||
def _login_content(form, request) -> SafeText:
|
||||
table = Component(
|
||||
tag_name="table",
|
||||
def _login_content(form, request) -> Node:
|
||||
table = Element(
|
||||
"table",
|
||||
children=[
|
||||
CsrfInput(request),
|
||||
mark_safe(str(form.as_table())),
|
||||
@@ -31,13 +31,13 @@ def _login_content(form, request) -> SafeText:
|
||||
return Div(
|
||||
[("class", "flex items-center flex-col")],
|
||||
[
|
||||
Component(
|
||||
tag_name="h2",
|
||||
Element(
|
||||
"h2",
|
||||
attributes=[("class", "text-3xl text-white mb-8")],
|
||||
children=["Please log in to continue"],
|
||||
),
|
||||
Component(
|
||||
tag_name="form",
|
||||
Element(
|
||||
"form",
|
||||
attributes=[("method", "post")],
|
||||
children=[table],
|
||||
),
|
||||
|
||||
+7
-7
@@ -17,9 +17,9 @@ from common.components import (
|
||||
AddForm,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Component,
|
||||
CsrfInput,
|
||||
Div,
|
||||
Element,
|
||||
FilterBar,
|
||||
GameStatus,
|
||||
GameStatusSelector,
|
||||
@@ -201,8 +201,8 @@ def _delete_game_confirmation_modal(
|
||||
if not (session_count or purchase_count or playevent_count):
|
||||
data_items.append(Li(children=["No associated data"]))
|
||||
|
||||
form = Component(
|
||||
tag_name="form",
|
||||
form = Element(
|
||||
"form",
|
||||
attributes=[
|
||||
("hx-post", reverse("games:delete_game", args=[game.id])),
|
||||
("hx-replace-url", "true"),
|
||||
@@ -442,8 +442,8 @@ def _game_action_buttons(game: Game) -> SafeText:
|
||||
edit_link = A(
|
||||
href=reverse("games:edit_game", args=[game.id]),
|
||||
children=[
|
||||
Component(
|
||||
tag_name="button",
|
||||
Element(
|
||||
"button",
|
||||
attributes=[("type", "button"), ("class", edit_class)],
|
||||
children=["Edit"],
|
||||
)
|
||||
@@ -456,8 +456,8 @@ def _game_action_buttons(game: Game) -> SafeText:
|
||||
("hx-target", "#global-modal-container"),
|
||||
],
|
||||
children=[
|
||||
Component(
|
||||
tag_name="button",
|
||||
Element(
|
||||
"button",
|
||||
attributes=[("type", "button"), ("class", delete_class)],
|
||||
children=["Delete"],
|
||||
)
|
||||
|
||||
@@ -19,14 +19,15 @@ from common.components import (
|
||||
AddForm,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Component,
|
||||
CsrfInput,
|
||||
Div,
|
||||
Element,
|
||||
GameLink,
|
||||
Icon,
|
||||
LinkedPurchase,
|
||||
Modal,
|
||||
ModuleScript,
|
||||
Node,
|
||||
PriceConverted,
|
||||
PurchasePrice,
|
||||
TableRow,
|
||||
@@ -301,9 +302,9 @@ def drop_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
||||
return redirect("games:list_purchases")
|
||||
|
||||
|
||||
def _refund_confirmation_modal(purchase_id: int, request: HttpRequest) -> SafeText:
|
||||
form = Component(
|
||||
tag_name="form",
|
||||
def _refund_confirmation_modal(purchase_id: int, request: HttpRequest) -> Node:
|
||||
form = Element(
|
||||
"form",
|
||||
attributes=[
|
||||
("hx-post", reverse("games:refund_purchase", args=[purchase_id])),
|
||||
("hx-target", f"#purchase-row-{purchase_id}"),
|
||||
@@ -339,8 +340,8 @@ def _refund_confirmation_modal(purchase_id: int, request: HttpRequest) -> SafeTe
|
||||
return Modal(
|
||||
"refund-confirmation-modal",
|
||||
children=[
|
||||
Component(
|
||||
tag_name="h1",
|
||||
Element(
|
||||
"h1",
|
||||
attributes=[
|
||||
(
|
||||
"class",
|
||||
|
||||
@@ -9,9 +9,9 @@ from django.template.defaultfilters import date as date_filter
|
||||
from django.template.defaultfilters import floatformat
|
||||
from django.urls import reverse
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from common.components import A, Component, Div, GameLink, YearPicker
|
||||
from common.components import A, Div, Element, GameLink, Node, Td, Th, Tr, YearPicker
|
||||
from common.time import durationformat, format_duration
|
||||
|
||||
_CELL = "px-2 sm:px-4 md:px-6 md:py-2"
|
||||
@@ -19,41 +19,40 @@ _CELL_MONO = f"{_CELL} font-mono"
|
||||
_NAME_TH = f"{_CELL} purchase-name truncate max-w-20char"
|
||||
|
||||
|
||||
def _td(children, cls: str = _CELL_MONO) -> SafeText:
|
||||
def _td(children, cls: str = _CELL_MONO) -> Node:
|
||||
if not isinstance(children, list):
|
||||
children = [children]
|
||||
children = [c if isinstance(c, (str, SafeText)) else str(c) for c in children]
|
||||
return Component(tag_name="td", attributes=[("class", cls)], children=children)
|
||||
return Td(attributes=[("class", cls)], children=children)
|
||||
|
||||
|
||||
def _th(text: str, cls: str = _CELL) -> SafeText:
|
||||
return Component(tag_name="th", attributes=[("class", cls)], children=[text])
|
||||
def _th(text: str, cls: str = _CELL) -> Node:
|
||||
return Th(attributes=[("class", cls)], children=[text])
|
||||
|
||||
|
||||
def _tr(cells: list) -> SafeText:
|
||||
return Component(tag_name="tr", children=cells)
|
||||
def _tr(cells: list) -> Node:
|
||||
return Tr(children=cells)
|
||||
|
||||
|
||||
def _kv(label, value) -> SafeText:
|
||||
def _kv(label, value) -> Node:
|
||||
"""A label/value row: plain label cell + mono value cell."""
|
||||
return _tr([_td(label, _CELL), _td(value)])
|
||||
|
||||
|
||||
def _h1(title: str) -> SafeText:
|
||||
return Component(
|
||||
tag_name="h1",
|
||||
def _h1(title: str) -> Node:
|
||||
return Element(
|
||||
"h1",
|
||||
attributes=[("class", "text-3xl text-heading text-center my-6")],
|
||||
children=[title],
|
||||
)
|
||||
|
||||
|
||||
def _table(rows: list, thead: SafeText | None = None) -> SafeText:
|
||||
def _table(rows: list, thead: Node | None = None) -> Node:
|
||||
children = []
|
||||
if thead is not None:
|
||||
children.append(thead)
|
||||
children.append(Component(tag_name="tbody", children=rows))
|
||||
return Component(
|
||||
tag_name="table",
|
||||
children.append(Element("tbody", children=rows))
|
||||
return Element(
|
||||
"table",
|
||||
attributes=[("class", "responsive-table")],
|
||||
children=children,
|
||||
)
|
||||
@@ -63,7 +62,7 @@ def _dur(value) -> str:
|
||||
return format_duration(value, durationformat)
|
||||
|
||||
|
||||
def _purchase_name(purchase) -> SafeText:
|
||||
def _purchase_name(purchase) -> Node:
|
||||
"""Mirror of the `purchase-name` partial in the old template."""
|
||||
game_name = getattr(purchase, "game_name", None)
|
||||
first_game = purchase.first_game
|
||||
@@ -76,7 +75,7 @@ def _purchase_name(purchase) -> SafeText:
|
||||
return GameLink(first_game.id, name)
|
||||
|
||||
|
||||
def _year_nav(year, year_range, url_template) -> SafeText:
|
||||
def _year_nav(year, year_range, url_template) -> Node:
|
||||
# `year` is an int for a specific year, or "Alltime" (from compute_stats)
|
||||
# for the all-time view. Normalize to int-or-None so nothing downstream has
|
||||
# to know about the "Alltime" sentinel.
|
||||
@@ -107,7 +106,7 @@ def _year_nav(year, year_range, url_template) -> SafeText:
|
||||
)
|
||||
|
||||
|
||||
def _playtime_table(ctx) -> SafeText:
|
||||
def _playtime_table(ctx) -> Node:
|
||||
year = ctx.get("year")
|
||||
rows = [
|
||||
_kv("Hours", ctx.get("total_hours")),
|
||||
@@ -186,7 +185,7 @@ def _playtime_table(ctx) -> SafeText:
|
||||
return _table(rows)
|
||||
|
||||
|
||||
def _purchases_table(ctx) -> SafeText:
|
||||
def _purchases_table(ctx) -> Node:
|
||||
rows = [
|
||||
_kv("Total", ctx.get("all_purchased_this_year_count")),
|
||||
_kv(
|
||||
@@ -213,18 +212,18 @@ def _purchases_table(ctx) -> SafeText:
|
||||
return _table(rows)
|
||||
|
||||
|
||||
def _two_col_table(header: str, items, name_key, value_fn) -> SafeText:
|
||||
thead = Component(
|
||||
tag_name="thead",
|
||||
def _two_col_table(header: str, items, name_key, value_fn) -> Node:
|
||||
thead = Element(
|
||||
"thead",
|
||||
children=[_tr([_th(header), _th("Playtime")])],
|
||||
)
|
||||
rows = [_tr([_td(name_key(item)), _td(value_fn(item))]) for item in items]
|
||||
return _table(rows, thead)
|
||||
|
||||
|
||||
def _finished_table(purchases) -> SafeText:
|
||||
thead = Component(
|
||||
tag_name="thead",
|
||||
def _finished_table(purchases) -> Node:
|
||||
thead = Element(
|
||||
"thead",
|
||||
children=[_tr([_th("Name", _NAME_TH), _th("Date")])],
|
||||
)
|
||||
rows = [
|
||||
@@ -234,9 +233,9 @@ def _finished_table(purchases) -> SafeText:
|
||||
return _table(rows, thead)
|
||||
|
||||
|
||||
def _priced_table(purchases, currency) -> SafeText:
|
||||
thead = Component(
|
||||
tag_name="thead",
|
||||
def _priced_table(purchases, currency) -> Node:
|
||||
thead = Element(
|
||||
"thead",
|
||||
children=[
|
||||
_tr([_th("Name", _NAME_TH), _th(f"Price ({currency})"), _th("Date")])
|
||||
],
|
||||
@@ -254,7 +253,7 @@ def _priced_table(purchases, currency) -> SafeText:
|
||||
return _table(rows, thead)
|
||||
|
||||
|
||||
def stats_content(ctx: dict) -> SafeText:
|
||||
def stats_content(ctx: dict) -> Node:
|
||||
year = ctx.get("year")
|
||||
currency = ctx.get("total_spent_currency")
|
||||
# Build a navigation URL with an `__year__` placeholder the picker's JS
|
||||
|
||||
@@ -8,9 +8,9 @@ from common.components import (
|
||||
A,
|
||||
AddForm,
|
||||
Button,
|
||||
Component,
|
||||
CsrfInput,
|
||||
Div,
|
||||
Element,
|
||||
paginated_table_content,
|
||||
)
|
||||
from common.components.primitives import P
|
||||
@@ -89,8 +89,8 @@ def _delete_statuschange_content(statuschange, request: HttpRequest) -> SafeText
|
||||
),
|
||||
],
|
||||
)
|
||||
form = Component(
|
||||
tag_name="form",
|
||||
form = Element(
|
||||
"form",
|
||||
attributes=[("method", "post"), ("class", "dark:text-white")],
|
||||
children=[CsrfInput(request), inner],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user