Files
polylan-submitter/polylan_submitter/place/api.py
T
2026-10-24 02:11:09 +02:00

66 lines
2.4 KiB
Python

import csv
import struct
from django.db.models import Count
from django.http import HttpRequest, HttpResponse
from ninja import Router
from ninja.decorators import decorate_view
from ninja.security import django_auth_is_staff
from games.decorators import require_game_enabled
from .canvas import read_canvas
from .gif import build_evolution_gif
from .models import PixelPlacement, PlaceSettings
router = Router()
PREFIX = "/events/{event_path}/place"
# 4-byte little-endian header (uint16 width, uint16 height) prefixed to the
# canvas payload - the grid can be resized by an admin, so the frontend
# can't hardcode dimensions and needs to read them off the payload itself.
HEADER_FORMAT = "<HH"
@router.get(f"{PREFIX}/canvas")
@decorate_view(require_game_enabled(path="place"))
def canvas(request: HttpRequest, event_path: str):
"""The one-time full-grid load. Every placement after that arrives as a
single websocket message, never a re-fetch."""
place_settings = PlaceSettings.current(request.event)
header = struct.pack(HEADER_FORMAT, place_settings.width, place_settings.height)
pixels = read_canvas(event_path, place_settings.width, place_settings.height)
return HttpResponse(header + pixels, content_type="application/octet-stream")
@router.get(f"{PREFIX}/evolution.gif", auth=django_auth_is_staff)
@decorate_view(require_game_enabled(path="place"))
def evolution_gif(request: HttpRequest, event_path: str):
"""Admin-only: replays the full placement history as an animated GIF."""
return HttpResponse(build_evolution_gif(request.event), content_type="image/gif")
@router.get(f"{PREFIX}/placements.csv", auth=django_auth_is_staff)
@decorate_view(require_game_enabled(path="place"))
def placements_csv(request: HttpRequest, event_path: str):
"""Admin-only: pixel placement count per player, most active first."""
rows = (
PixelPlacement.objects.filter(event=request.event)
.values("player__username")
.annotate(count=Count("id"))
.order_by("-count", "player__username")
)
# text/plain so browsers render it inline - most force-download text/csv
response = HttpResponse(content_type="text/plain; charset=utf-8")
writer = csv.writer(response)
writer.writerow(["username", "placements"])
for row in rows:
writer.writerow(
[
row["player__username"],
row["count"],
]
)
return response