Move from HTML templates to pure Python

Remove cruft
This commit is contained in:
2026-06-06 07:11:46 +02:00
parent 09db54e940
commit d101aecd70
109 changed files with 2903 additions and 2949 deletions
+266 -143
View File
@@ -4,21 +4,29 @@ from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.middleware.csrf import get_token
from django.shortcuts import get_object_or_404, redirect
from django.template.defaultfilters import date as date_filter
from django.urls import reverse
from django.utils import timezone
from django.utils.safestring import SafeText, mark_safe
from common.components import (
A,
AddForm,
Button,
ButtonGroup,
Component,
Div,
Icon,
SearchField,
ModuleScript,
NameWithIcon,
Popover,
SearchField,
SessionDeviceSelector,
paginated_table_content,
)
from common.layout import render_page
from common.time import (
dateformat,
local_strftime,
@@ -31,7 +39,6 @@ from games.models import Device, Game, Session
@login_required
def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse:
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
sessions = Session.objects.order_by("-timestamp_start", "created_at")
@@ -55,120 +62,115 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
page_obj = paginator.get_page(page_number)
sessions = page_obj.object_list
context = {
"title": "Manage sessions",
"page_obj": page_obj or None,
"elided_page_range": (
page_obj.paginator.get_elided_page_range(
page_number, on_each_side=1, on_ends=1
)
if page_obj
else None
),
"data": {
"header_action": Div(
children=[
SearchField(search_string=search_string),
Div(
children=[
A(
url_name="games:add_session",
children=Button(
icon=True,
size="xs",
children=[Icon("play"), "LOG"],
),
elided_page_range = (
page_obj.paginator.get_elided_page_range(page_number, on_each_side=1, on_ends=1)
if page_obj
else None
)
data = {
"header_action": Div(
children=[
SearchField(search_string=search_string),
Div(
children=[
A(
url_name="games:add_session",
children=Button(
icon=True,
size="xs",
children=[Icon("play"), "LOG"],
),
A(
href=reverse(
"games:list_sessions_start_session_from_session",
args=[last_session.pk],
),
A(
href=reverse(
"games:list_sessions_start_session_from_session",
args=[last_session.pk],
),
children=Popover(
popover_content=last_session.game.name,
children=[
Button(
icon=True,
color="gray",
size="xs",
children=[
Icon("play"),
truncate(f"{last_session.game.name}"),
],
)
],
),
)
if last_session
else "",
]
),
],
attributes=[("class", "flex justify-between")],
),
"columns": [
"Name",
"Date",
"Duration",
"Device",
"Created",
"Actions",
],
"rows": [
{
"row_id": f"session-row-{session.pk}",
"hx_trigger": "device-changed from:body",
"hx_get": "",
"hx_select": f"#session-row-{session.pk}",
"hx_swap": "outerHTML",
"cell_data": [
NameWithIcon(session=session),
f"{local_strftime(session.timestamp_start)}{f'{local_strftime(session.timestamp_end, timeformat)}' if session.timestamp_end else ''}",
session.duration_formatted_with_mark(),
SessionDeviceSelector(session, device_list, get_token(request)),
session.created_at.strftime(dateformat),
ButtonGroup(
[
{
"href": reverse(
"games:list_sessions_end_session", args=[session.pk]
),
children=Popover(
popover_content=last_session.game.name,
children=[
Button(
icon=True,
color="gray",
size="xs",
children=[
Icon("play"),
truncate(f"{last_session.game.name}"),
],
)
],
"slot": Icon("end"),
"title": "Finish session now",
"color": "green",
}
if session.timestamp_end is None
else {},
{
"href": reverse(
"games:edit_session", args=[session.pk]
),
)
if last_session
else "",
"slot": Icon("edit"),
"title": "Edit",
},
{
"href": reverse(
"games:delete_session", args=[session.pk]
),
"slot": Icon("delete"),
"title": "Delete",
"color": "red",
},
]
),
],
attributes=[("class", "flex justify-between")],
),
"columns": [
"Name",
"Date",
"Duration",
"Device",
"Created",
"Actions",
],
"rows": [
{
"row_id": f"session-row-{session.pk}",
"hx_trigger": "device-changed from:body",
"hx_get": "",
"hx_select": f"#session-row-{session.pk}",
"hx_swap": "outerHTML",
"cell_data": [
NameWithIcon(session=session),
f"{local_strftime(session.timestamp_start)}{f'{local_strftime(session.timestamp_end, timeformat)}' if session.timestamp_end else ''}",
session.duration_formatted_with_mark(),
render_to_string(
"partials/sessiondevice_selector.html",
{
"session": session,
"session_device": session.device,
"session_devices": device_list,
},
request=request,
),
session.created_at.strftime(dateformat),
ButtonGroup(
[
{
"href": reverse(
"games:list_sessions_end_session", args=[session.pk]
),
"slot": Icon("end"),
"title": "Finish session now",
"color": "green",
}
if session.timestamp_end is None
else {},
{
"href": reverse("games:edit_session", args=[session.pk]),
"slot": Icon("edit"),
"title": "Edit",
},
{
"href": reverse(
"games:delete_session", args=[session.pk]
),
"slot": Icon("delete"),
"title": "Delete",
"color": "red",
},
]
),
],
}
for session in sessions
],
},
}
for session in sessions
],
}
return render(request, "list_purchases.html", context)
content = paginated_table_content(
data,
page_obj=page_obj,
elided_page_range=elided_page_range,
request=request,
)
return render_page(request, content, title="Manage sessions")
@login_required
@@ -176,13 +178,60 @@ def search_sessions(request: HttpRequest) -> HttpResponse:
return list_sessions(request, search_string=request.GET.get("search_string", ""))
def _session_fields(form) -> SafeText:
"""Manual per-field layout for the session form.
Mirrors the old add_session.html: each field gets its label and widget,
and the timestamp fields gain a row of now/toggle/copy helper buttons.
"""
rows: list[SafeText] = []
for field in form:
children: list[SafeText | str] = [
mark_safe(str(field.label_tag())),
mark_safe(str(field)),
]
if field.name in ("timestamp_start", "timestamp_end"):
this_side = "start" if field.name == "timestamp_start" else "end"
other_side = "end" if field.name == "timestamp_start" else "start"
children.append(
Component(
tag_name="span",
attributes=[
(
"class",
"form-row-button-group flex-row gap-3 justify-start mt-3",
),
("hx-boost", "false"),
],
children=[
Button(
[("data-target", field.name), ("data-type", "now")],
"Set to now",
size="xs",
),
Button(
[("data-target", field.name), ("data-type", "toggle")],
"Toggle text",
size="xs",
),
Button(
[("data-target", field.name), ("data-type", "copy")],
f"Copy {this_side} value to {other_side}",
size="xs",
),
],
)
)
rows.append(Div(children=children))
return mark_safe("\n".join(rows))
@login_required
def add_session(request: HttpRequest, game_id: int = 0) -> HttpResponse:
context = {}
initial: dict[str, Any] = {"timestamp_start": timezone.now()}
last = Session.objects.last()
if last != None:
if last is not None:
initial["game"] = last.game
if request.method == "POST":
@@ -202,25 +251,116 @@ def add_session(request: HttpRequest, game_id: int = 0) -> HttpResponse:
else:
form = SessionForm(initial=initial)
context["title"] = "Add New Session"
# TODO: re-add custom buttons #91
context["script_name"] = "add_session.js"
context["form"] = form
return render(request, "add_session.html", context)
return render_page(
request,
AddForm(form, request=request, fields=_session_fields(form), submit_class=""),
title="Add New Session",
scripts=ModuleScript("add_session.js"),
)
@login_required
def edit_session(request: HttpRequest, session_id: int) -> HttpResponse:
context = {}
session = get_object_or_404(Session, id=session_id)
form = SessionForm(request.POST or None, instance=session)
if form.is_valid():
form.save()
return redirect("games:list_sessions")
context["title"] = "Edit Session"
context["script_name"] = "add_session.js"
context["form"] = form
return render(request, "add_session.html", context)
return render_page(
request,
AddForm(form, request=request, fields=_session_fields(form), submit_class=""),
title="Edit Session",
scripts=ModuleScript("add_session.js"),
)
def _session_row_fragment(session: Session) -> SafeText:
"""A single session <tr> (the old list_sessions.html#session-row partial),
returned by the inline end/clone-session HTMX endpoints."""
name_link = Component(
tag_name="a",
attributes=[
(
"class",
"underline decoration-slate-500 sm:decoration-2 inline-block "
"truncate max-w-20char group-hover:absolute group-hover:max-w-none "
"group-hover:-top-8 group-hover:-left-6 group-hover:min-w-60 "
"group-hover:px-6 group-hover:py-3.5 group-hover:bg-purple-600 "
"group-hover:rounded-xs group-hover:outline-dashed "
"group-hover:outline-purple-400 group-hover:outline-4 "
"group-hover:decoration-purple-900 group-hover:text-purple-100",
),
("href", reverse("games:view_game", args=[session.game.id])),
],
children=[session.game.name],
)
name_td = Component(
tag_name="td",
attributes=[
(
"class",
"px-2 sm:px-4 md:px-6 md:py-2 purchase-name relative align-top "
"w-24 h-12 group",
)
],
children=[
Component(
tag_name="span",
attributes=[("class", "inline-block relative")],
children=[name_link],
)
],
)
start_td = Component(
tag_name="td",
attributes=[
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell")
],
children=[date_filter(session.timestamp_start, "d/m/Y H:i")],
)
if not session.timestamp_end:
end_url = reverse("games:list_sessions_end_session", args=[session.id])
end_inner: SafeText | str = Component(
tag_name="a",
attributes=[
("href", end_url),
("hx-get", end_url),
("hx-target", "closest tr"),
("hx-swap", "outerHTML"),
("hx-indicator", "#indicator"),
(
"onClick",
"document.querySelector('#last-session-start')"
".classList.remove('invisible')",
),
],
children=[
Component(
tag_name="span",
attributes=[("class", "text-yellow-300")],
children=["Finish now?"],
)
],
)
elif session.duration_manual:
end_inner = "--"
else:
end_inner = date_filter(session.timestamp_end, "d/m/Y H:i")
end_td = Component(
tag_name="td",
attributes=[
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell")
],
children=[end_inner],
)
duration_td = Component(
tag_name="td",
attributes=[("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono")],
children=[session.duration_formatted()],
)
return Component(tag_name="tr", children=[name_td, start_td, end_td, duration_td])
def clone_session_by_id(session_id: int) -> Session:
@@ -236,38 +376,21 @@ def clone_session_by_id(session_id: int) -> Session:
@login_required
def new_session_from_existing_session(
request: HttpRequest, session_id: int, template: str = ""
request: HttpRequest, session_id: int
) -> HttpResponse:
session = clone_session_by_id(session_id)
if request.htmx:
context = {
"session": session,
"session_count": int(request.GET.get("session_count", 0)) + 1,
}
return render(request, template, context)
return HttpResponse(_session_row_fragment(session))
return redirect("games:list_sessions")
@login_required
def end_session(
request: HttpRequest, session_id: int, template: str = ""
) -> HttpResponse:
def end_session(request: HttpRequest, session_id: int) -> HttpResponse:
session = get_object_or_404(Session, id=session_id)
session.timestamp_end = timezone.now()
session.save()
if request.htmx:
context = {
"session": session,
"session_count": request.GET.get("session_count", 0),
}
return render(request, template, context)
return redirect("games:list_sessions")
@login_required
def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse:
session = get_object_or_404(Session, id=session_id)
session.delete()
return HttpResponse(_session_row_fragment(session))
return redirect("games:list_sessions")