diff --git a/polylan_submitter/animations/api.py b/polylan_submitter/animations/api.py index 74dac23..32c4ec0 100644 --- a/polylan_submitter/animations/api.py +++ b/polylan_submitter/animations/api.py @@ -61,8 +61,14 @@ def results(request: HttpRequest) -> dict: @router.get("top-submissions", response=TournamentSubmissionsOut) -def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissionsOut: +def top_submissions(request: HttpRequest, limit: int = 5) -> dict: """Get tournament top submissions for each puzzle. Only available when tournament is closed.""" + cache_key = f"api:results:top_submissions:{limit}" + cached_data = cache.get(cache_key) + + if cached_data is not None: + return cached_data + collection = get_object_or_404(SteamCollection, is_active=True) # Only allow access when tournament is closed @@ -70,15 +76,16 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio raise HttpError(403, "Tournament is still accepting submissions") # Get all puzzles - puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by("order_index") + puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by( + "order_index" + ) # Build response submissions_list = [] for puzzle in puzzles: # Get the top N responses for this puzzle (ranked by points, highest first) top_responses = ( - PuzzleResponse.objects - .filter(puzzle=puzzle, needs_manual_validation=False) + PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False) .filter_user_best_response() .annotate_rank_points() .order_by("-rank_points")[:limit] @@ -92,23 +99,30 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio response_files = [ WinnerFileOut( file_url=file.file_url or "", - original_filename=file.original_filename + original_filename=file.original_filename, ) for file in files ] # Calculate total coefficient total_coef = None - if puzzle.points_factor and response.final_cost is not None and response.final_cycles is not None and response.final_area is not None: + if ( + puzzle.points_factor + and response.final_cost is not None + and response.final_cycles is not None + and response.final_area is not None + ): total_coef = ( - puzzle.points_factor.cost * response.final_cost + - puzzle.points_factor.cycles * response.final_cycles + - puzzle.points_factor.area * response.final_area + puzzle.points_factor.cost * response.final_cost + + puzzle.points_factor.cycles * response.final_cycles + + puzzle.points_factor.area * response.final_area ) submission_data = WinnerResponseOut( user_id=response.submission.user.id if response.submission.user else 0, - username=response.submission.user.username if response.submission.user else "Anonymous", + username=response.submission.user.username + if response.submission.user + else "Anonymous", final_cost=response.final_cost, final_cycles=response.final_cycles, final_area=response.final_area, @@ -126,12 +140,20 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio ) ) - return TournamentSubmissionsOut(submissions=submissions_list) + data = {"submissions": submissions_list} + cache.set(f"api:results:top_submissions:{limit}", data, 300) + return data @router.get("puzzle-results", response=TournamentPuzzleResultsOut) -def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResultsOut: +def puzzle_results(request: HttpRequest, limit: int = 5) -> dict: """Get tournament results organized by puzzle with coefficients. Only available when tournament is closed.""" + cache_key = f"api:results:puzzle_results:{limit}" + cached_data = cache.get(cache_key) + + if cached_data is not None: + return cached_data + collection = get_object_or_404(SteamCollection, is_active=True) # Only allow access when tournament is closed @@ -139,15 +161,16 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu raise HttpError(403, "Tournament is still accepting submissions") # Get all puzzles - puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by("order_index") + puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by( + "order_index" + ) # Build response results_list = [] for puzzle in puzzles: # Get the top N responses for this puzzle (ranked by points) top_responses = ( - PuzzleResponse.objects - .filter(puzzle=puzzle, needs_manual_validation=False) + PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False) .filter_user_best_response() .annotate_rank_points() .order_by("-rank_points")[:limit] @@ -161,24 +184,31 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu response_files = [ WinnerFileOut( file_url=file.file_url or "", - original_filename=file.original_filename + original_filename=file.original_filename, ) for file in files ] # Calculate total coefficient total_coef = None - if puzzle.points_factor and response.final_cost is not None and response.final_cycles is not None and response.final_area is not None: + if ( + puzzle.points_factor + and response.final_cost is not None + and response.final_cycles is not None + and response.final_area is not None + ): total_coef = ( - puzzle.points_factor.cost * response.final_cost + - puzzle.points_factor.cycles * response.final_cycles + - puzzle.points_factor.area * response.final_area + puzzle.points_factor.cost * response.final_cost + + puzzle.points_factor.cycles * response.final_cycles + + puzzle.points_factor.area * response.final_area ) submission_data = PuzzleSubmissionWithRankOut( rank=rank, user_id=response.submission.user.id if response.submission.user else 0, - username=response.submission.user.username if response.submission.user else "Anonymous", + username=response.submission.user.username + if response.submission.user + else "Anonymous", final_cost=response.final_cost, final_cycles=response.final_cycles, final_area=response.final_area, @@ -206,4 +236,6 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu ) ) - return TournamentPuzzleResultsOut(results=results_list) + data = {"results": results_list} + cache.set(f"api:results:puzzle_results:{limit}", data, 300) + return data diff --git a/polylan_submitter/submissions/migrations/0014_steamcollection_accepting_submissions.py b/polylan_submitter/submissions/migrations/0014_steamcollection_accepting_submissions.py index 1003f2e..6d92af2 100644 --- a/polylan_submitter/submissions/migrations/0014_steamcollection_accepting_submissions.py +++ b/polylan_submitter/submissions/migrations/0014_steamcollection_accepting_submissions.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("submissions", "0013_steamcollectionitem_points_value"), ] diff --git a/polylan_submitter/submissions/schemas.py b/polylan_submitter/submissions/schemas.py index 6032002..2ce691c 100644 --- a/polylan_submitter/submissions/schemas.py +++ b/polylan_submitter/submissions/schemas.py @@ -3,7 +3,13 @@ from typing import List, Optional from datetime import datetime from uuid import UUID -from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem, SteamCollection +from .models import ( + Submission, + PuzzleResponse, + SubmissionFile, + SteamCollectionItem, + SteamCollection, +) # Input Schemas