opus-stats-score
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import csv
|
||||||
|
|
||||||
from ninja import Router, File
|
from ninja import Router, File
|
||||||
from ninja.files import UploadedFile
|
from ninja.files import UploadedFile
|
||||||
from ninja.pagination import paginate
|
from ninja.pagination import paginate
|
||||||
@@ -5,6 +7,8 @@ from ninja.errors import HttpError
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from ninja.security import django_auth_is_staff
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from typing import List
|
from typing import List
|
||||||
@@ -395,3 +399,41 @@ def get_stats(request, event_path: str):
|
|||||||
if total_responses
|
if total_responses
|
||||||
else 0,
|
else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(f"{PREFIX}/points.csv", auth=django_auth_is_staff)
|
||||||
|
@decorate_view(require_game_enabled(OPUS_MAGNUM_APP_ID))
|
||||||
|
def points_csv(request: HttpRequest, event_path: str):
|
||||||
|
"""Admin-only: points per player for each puzzle of the active collection,
|
||||||
|
one column per puzzle (1 puzzle = 1 round), `;` separated."""
|
||||||
|
puzzles = list(
|
||||||
|
SteamCollectionItem.objects.filter(
|
||||||
|
collection__is_active=True, collection__event=request.event
|
||||||
|
).order_by("order_index")
|
||||||
|
)
|
||||||
|
|
||||||
|
points = {}
|
||||||
|
for response in (
|
||||||
|
PuzzleResponse.objects.filter(
|
||||||
|
needs_manual_validation=False,
|
||||||
|
submission__event=request.event,
|
||||||
|
submission__user__isnull=False,
|
||||||
|
puzzle__in=puzzles,
|
||||||
|
)
|
||||||
|
.filter_user_best_response()
|
||||||
|
.select_related("submission__user")
|
||||||
|
):
|
||||||
|
user_points = points.setdefault(response.submission.user.username, {})
|
||||||
|
user_points[response.puzzle_id] = response.rank_points or 0
|
||||||
|
|
||||||
|
# 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, delimiter=";")
|
||||||
|
writer.writerow(["username", *(puzzle.title for puzzle in puzzles)])
|
||||||
|
for username, user_points in sorted(
|
||||||
|
points.items(), key=lambda item: (-sum(item[1].values()), item[0])
|
||||||
|
):
|
||||||
|
writer.writerow(
|
||||||
|
[username, *(user_points.get(puzzle.id, 0) for puzzle in puzzles)]
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|||||||
@@ -1 +1,74 @@
|
|||||||
# Create your tests here.
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
from animations.models import PuzzlePointsFactor, PuzzlePointsValue
|
||||||
|
from events.models import Event
|
||||||
|
from games.models import EventGame, Game
|
||||||
|
|
||||||
|
from .api import OPUS_MAGNUM_APP_ID
|
||||||
|
from .models import PuzzleResponse, SteamCollection, SteamCollectionItem, Submission
|
||||||
|
|
||||||
|
URL = "/api/events/event/opus-magnum/points.csv"
|
||||||
|
|
||||||
|
|
||||||
|
class PointsCsvTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.event = Event.objects.create(name="Event", path="event")
|
||||||
|
game = Game.objects.create(name="Opus Magnum", steam_app_id=OPUS_MAGNUM_APP_ID)
|
||||||
|
EventGame.objects.create(event=self.event, game=game)
|
||||||
|
collection = SteamCollection.objects.create(
|
||||||
|
event=self.event, steam_id="1", url="https://example.com", is_active=True
|
||||||
|
)
|
||||||
|
factor = PuzzlePointsFactor.objects.create(cost=1, cycles=1, area=1)
|
||||||
|
value = PuzzlePointsValue.objects.create(points=[10, 5, 1])
|
||||||
|
self.p1, self.p2 = (
|
||||||
|
SteamCollectionItem.objects.create(
|
||||||
|
collection=collection,
|
||||||
|
steam_item_id=str(i),
|
||||||
|
title=f"Puzzle {i}",
|
||||||
|
order_index=i,
|
||||||
|
points_factor=factor,
|
||||||
|
points_value=value,
|
||||||
|
)
|
||||||
|
for i in (1, 2)
|
||||||
|
)
|
||||||
|
self.alice = CustomUser.objects.create_user(username="alice", password="x")
|
||||||
|
self.bob = CustomUser.objects.create_user(username="bob", password="x")
|
||||||
|
self.admin = CustomUser.objects.create_user(
|
||||||
|
username="admin", password="x", is_staff=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def respond(self, user, puzzle, score):
|
||||||
|
submission = Submission.objects.create(event=self.event, user=user)
|
||||||
|
PuzzleResponse.objects.create(
|
||||||
|
submission=submission,
|
||||||
|
puzzle=puzzle,
|
||||||
|
puzzle_name=puzzle.title,
|
||||||
|
validated_cost=score,
|
||||||
|
validated_cycles=0,
|
||||||
|
validated_area=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_requires_staff(self):
|
||||||
|
self.client.force_login(self.alice)
|
||||||
|
|
||||||
|
response = self.client.get(URL)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
|
def test_points_per_puzzle(self):
|
||||||
|
self.respond(self.alice, self.p1, 10)
|
||||||
|
self.respond(self.bob, self.p1, 20)
|
||||||
|
self.respond(self.bob, self.p2, 30)
|
||||||
|
self.client.force_login(self.admin)
|
||||||
|
|
||||||
|
response = self.client.get(URL)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response.content.decode().splitlines(),
|
||||||
|
[
|
||||||
|
"username;Puzzle 1;Puzzle 2",
|
||||||
|
"bob;5;10",
|
||||||
|
"alice;10;0",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user