From 4e3b0ddb0873a8148a8105e3fd50ac7b3278e8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 11 May 2026 12:54:42 +0200 Subject: [PATCH] Allow directly updating device in session list --- games/api.py | 19 ++- games/static/base.css | 16 +++ games/templates/cotton/table_row.html | 21 +++- .../partials/sessiondevice_selector.html | 56 +++++++++ games/views/session.py | 108 ++++++++++-------- 5 files changed, 170 insertions(+), 50 deletions(-) create mode 100644 games/templates/partials/sessiondevice_selector.html diff --git a/games/api.py b/games/api.py index a2fdec2..28f72c6 100644 --- a/games/api.py +++ b/games/api.py @@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404 from django.utils.timezone import now as django_timezone_now from ninja import Field, ModelSchema, NinjaAPI, Router, Schema -from games.models import Game, PlayEvent +from games.models import Game, PlayEvent, Session api = NinjaAPI() playevent_router = Router() @@ -93,3 +93,20 @@ def delete_playevent(request, playevent_id: int): api.add_router("/playevent", playevent_router) api.add_router("/games", game_router) +session_router = Router() + + +class SessionDeviceUpdate(Schema): + device_id: int + + +@session_router.patch("/{session_id}/device", response={204: None}) +def partial_update_session_device(request, session_id: int, payload: SessionDeviceUpdate): + session = get_object_or_404(Session, id=session_id) + session.device_id = payload.device_id + session.save() + return 204, None + + +api.add_router("/session", session_router) + diff --git a/games/static/base.css b/games/static/base.css index c72d81d..74f4fd9 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -2490,9 +2490,15 @@ .text-gray-900 { color: var(--color-gray-900); } + .text-green-600 { + color: var(--color-green-600); + } .text-heading { color: var(--color-heading); } + .text-red-600 { + color: var(--color-red-600); + } .text-slate-300 { color: var(--color-slate-300); } @@ -3333,6 +3339,16 @@ color: var(--color-gray-600); } } + .dark\:text-green-400 { + &:is(.dark *) { + color: var(--color-green-400); + } + } + .dark\:text-red-400 { + &:is(.dark *) { + color: var(--color-red-400); + } + } .dark\:text-slate-300 { &:is(.dark *) { color: var(--color-slate-300); diff --git a/games/templates/cotton/table_row.html b/games/templates/cotton/table_row.html index 1a91831..ffd07ab 100644 --- a/games/templates/cotton/table_row.html +++ b/games/templates/cotton/table_row.html @@ -1,11 +1,26 @@ - + {% if slot %} {{ slot }} + {% elif data.row_id %} + {% for td in data.cell_data %} + {% if forloop.first %} + {{ td }} + {% else %} + + {{ td }} + + {% endif %} + {% endfor %} {% else %} {% for td in data %} {% if forloop.first %} - {{ td }} + {{ td }} {% else %} {{ td }} diff --git a/games/templates/partials/sessiondevice_selector.html b/games/templates/partials/sessiondevice_selector.html new file mode 100644 index 0000000..683e7ee --- /dev/null +++ b/games/templates/partials/sessiondevice_selector.html @@ -0,0 +1,56 @@ +
+
+ +
+ + +
diff --git a/games/views/session.py b/games/views/session.py index 8b88c63..9ffb2d4 100644 --- a/games/views/session.py +++ b/games/views/session.py @@ -25,7 +25,7 @@ from common.time import ( ) from common.utils import truncate from games.forms import SessionForm -from games.models import Game, Session +from games.models import Device, Game, Session @login_required @@ -34,6 +34,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse page_number = request.GET.get("page", 1) limit = request.GET.get("limit", 10) sessions = Session.objects.order_by("-timestamp_start", "created_at") + device_list = Device.objects.order_by("name") search_string = request.GET.get("search_string", search_string) if search_string != "": sessions = sessions.filter( @@ -123,51 +124,66 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse "Actions", ], "rows": [ - [ - NameWithIcon(session_id=session.pk), - f"{local_strftime(session.timestamp_start)}{f' — {local_strftime(session.timestamp_end, timeformat)}' if session.timestamp_end else ''}", - session.duration_formatted_with_mark, - session.device, - session.created_at.strftime(dateformat), - render_to_string( - "cotton/button_group.html", - { - "buttons": [ - { - "href": reverse( - "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("edit_session", args=[session.pk]), - "slot": Icon("edit"), - "title": "Edit", - # "color": "gray", - "hover": "green", - }, - { - "href": reverse( - "delete_session", args=[session.pk] - ), - "slot": Icon("delete"), - "title": "Delete", - "color": "red", - "hover": "red", - }, - ] - }, - ), - ] + { + "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_id=session.pk), + 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), + render_to_string( + "cotton/button_group.html", + { + "buttons": [ + { + "href": reverse( + "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("edit_session", args=[session.pk]), + "slot": Icon("edit"), + "title": "Edit", + # "color": "gray", + "hover": "green", + }, + { + "href": reverse( + "delete_session", args=[session.pk] + ), + "slot": Icon("delete"), + "title": "Delete", + "color": "red", + "hover": "red", + }, + ] + }, + ), + ], + } for session in sessions ], },