Migrate cotton to Python + template tag shims
Django CI/CD / test (push) Successful in 32s
Django CI/CD / build-and-push (push) Successful in 1m22s

This commit is contained in:
2026-06-02 22:19:55 +02:00
parent 94c3d9050a
commit ec1828b823
65 changed files with 1214 additions and 752 deletions
+14 -18
View File
@@ -4,10 +4,9 @@ from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
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.urls import reverse
from common.components import A, Button, Icon
from common.components import A, Button, ButtonGroup, Icon
from common.time import dateformat, local_strftime
from games.forms import DeviceForm
from games.models import Device
@@ -48,22 +47,19 @@ def list_devices(request: HttpRequest) -> HttpResponse:
device.name,
device.get_type_display(),
local_strftime(device.created_at, dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("games:edit_device", args=[device.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_device", args=[device.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
ButtonGroup(
[
{
"href": reverse("games:edit_device", args=[device.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_device", args=[device.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for device in devices
+53 -76
View File
@@ -11,9 +11,10 @@ from django.urls import reverse
from common.components import (
A,
Button,
ButtonGroup,
Div,
Form,
Icon,
SearchField,
LinkedPurchase,
NameWithIcon,
Popover,
@@ -78,17 +79,7 @@ def list_games(request: HttpRequest, search_string: str = "") -> HttpResponse:
"data": {
"header_action": Div(
children=[
Form(
children=[
render_to_string(
"cotton/search_field.html",
{
"id": "search_string",
"search_string": search_string,
},
)
]
),
SearchField(search_string=search_string),
A([], Button([], "Add game"), url_name="games:add_game"),
],
attributes=[("class", "flex justify-between")],
@@ -121,22 +112,19 @@ def list_games(request: HttpRequest, search_string: str = "") -> HttpResponse:
),
game.wikidata,
local_strftime(game.created_at, dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("games:edit_game", args=[game.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_game", args=[game.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
ButtonGroup(
[
{
"href": reverse("games:edit_game", args=[game.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_game", args=[game.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for game in games
@@ -255,22 +243,19 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
purchase.get_type_display(),
purchase.date_purchased.strftime(dateformat),
PurchasePrice(purchase),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("games:edit_purchase", args=[purchase.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_purchase", args=[purchase.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
ButtonGroup(
[
{
"href": reverse("games:edit_purchase", args=[purchase.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_purchase", args=[purchase.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for purchase in purchases
@@ -328,38 +313,30 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
[
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(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse(
"games:list_sessions_end_session", args=[session.pk]
),
"slot": Icon("end"),
"title": "Finish session now",
"color": "green",
"hover": "green",
}
if session.timestamp_end is None
# this only works without leaving an empty
# a element and wrong rounding of button edges
# because we check if button.href is not None
# in the button group component
else {},
{
"href": reverse("games:edit_session", args=[session.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_session", args=[session.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
session.duration_formatted_with_mark(),
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"),
"color": "gray",
},
{
"href": reverse("games:delete_session", args=[session.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for session in sessions
+14 -22
View File
@@ -4,10 +4,9 @@ from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
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.urls import reverse
from common.components import A, Button, Icon
from common.components import A, Button, ButtonGroup, Icon
from common.time import dateformat, local_strftime
from games.forms import PlatformForm
from games.models import Platform
@@ -51,26 +50,19 @@ def list_platforms(request: HttpRequest) -> HttpResponse:
Icon(platform.icon),
platform.group,
local_strftime(platform.created_at, dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse(
"games:edit_platform", args=[platform.pk]
),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse(
"games:delete_platform", args=[platform.pk]
),
"slot": Icon("delete"),
"color": "red",
},
]
},
ButtonGroup(
[
{
"href": reverse("games:edit_platform", args=[platform.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_platform", args=[platform.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for platform in platforms
+14 -18
View File
@@ -8,10 +8,9 @@ from django.db.models import QuerySet
from django.db.models.manager import BaseManager
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.urls import reverse
from common.components import A, Button, Icon
from common.components import A, Button, ButtonGroup, Icon
from common.time import dateformat, format_duration, local_strftime
from games.forms import PlayEventForm
from games.models import Game, PlayEvent, Session
@@ -53,22 +52,19 @@ def create_playevent_tabledata(
playevent.days_to_finish if playevent.days_to_finish else "-",
playevent.note,
local_strftime(playevent.created_at, dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("games:edit_playevent", args=[playevent.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_playevent", args=[playevent.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
ButtonGroup(
[
{
"href": reverse("games:edit_playevent", args=[playevent.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("games:delete_playevent", args=[playevent.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
),
]
for playevent in playevents
+29 -36
View File
@@ -9,12 +9,11 @@ from django.http import (
HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.http import require_POST
from common.components import A, Button, Icon, LinkedPurchase, PurchasePrice
from common.components import A, Button, ButtonGroup, Icon, LinkedPurchase, PurchasePrice, TableRow
from common.time import dateformat
from games.forms import PurchaseForm
from games.models import Game, Purchase
@@ -23,36 +22,33 @@ from games.views.general import use_custom_redirect
def _render_purchase_buttons(purchase_id, is_refunded):
"""Return button group HTML for a purchase row."""
return render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": "#",
"hx_get": reverse(
"games:refund_purchase_confirmation",
args=[purchase_id],
),
"hx_target": "#global-modal-container",
"slot": Icon("refund"),
"title": "Mark as refunded",
}
if not is_refunded
else {},
{
"href": reverse("games:edit_purchase", args=[purchase_id]),
"slot": Icon("edit"),
"title": "Edit",
"color": "gray",
},
{
"href": reverse("games:delete_purchase", args=[purchase_id]),
"slot": Icon("delete"),
"title": "Delete",
"color": "red",
},
]
},
return ButtonGroup(
[
{
"href": "#",
"hx_get": reverse(
"games:refund_purchase_confirmation",
args=[purchase_id],
),
"hx_target": "#global-modal-container",
"slot": Icon("refund"),
"title": "Mark as refunded",
}
if not is_refunded
else {},
{
"href": reverse("games:edit_purchase", args=[purchase_id]),
"slot": Icon("edit"),
"title": "Edit",
"color": "gray",
},
{
"href": reverse("games:delete_purchase", args=[purchase_id]),
"slot": Icon("delete"),
"title": "Delete",
"color": "red",
},
]
)
@@ -220,10 +216,7 @@ def refund_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
messages.success(request, "Purchase refunded")
row_data = _render_purchase_row(purchase)
row_html = render_to_string(
"cotton/table_row.html",
{"data": row_data},
)
row_html = str(TableRow(data=row_data))
modal_close = (
'<template id="refund-confirmation-modal" hx-swap-oob="outerHTML"></template>'
)
+30 -50
View File
@@ -12,9 +12,10 @@ from django.utils import timezone
from common.components import (
A,
Button,
ButtonGroup,
Div,
Form,
Icon,
SearchField,
NameWithIcon,
Popover,
)
@@ -67,17 +68,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
"data": {
"header_action": Div(
children=[
Form(
children=[
render_to_string(
"cotton/search_field.html",
{
"id": "search_string",
"search_string": search_string,
},
)
]
),
SearchField(search_string=search_string),
Div(
children=[
A(
@@ -133,7 +124,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
"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,
session.duration_formatted_with_mark(),
render_to_string(
"partials/sessiondevice_selector.html",
{
@@ -144,43 +135,32 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
request=request,
),
session.created_at.strftime(dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse(
"games:list_sessions_end_session", args=[session.pk]
),
"slot": Icon("end"),
"title": "Finish session now",
"color": "green",
"hover": "green",
}
if session.timestamp_end is None
# this only works without leaving an empty
# a element and wrong rounding of button edges
# because we check if button.href is not None
# in the button group component
else {},
{
"href": reverse("games:edit_session", args=[session.pk]),
"slot": Icon("edit"),
"title": "Edit",
# "color": "gray",
"hover": "green",
},
{
"href": reverse(
"games:delete_session", args=[session.pk]
),
"slot": Icon("delete"),
"title": "Delete",
"color": "red",
"hover": "red",
},
]
},
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",
},
]
),
],
}