Skip to content

Commit

Permalink
Use new rank view everywhere the old annotate_with_rank call was used.
Browse files Browse the repository at this point in the history
This should fix #507, as well as simplifying some code to no longer need
rank_replays.py.
  • Loading branch information
n-rook committed Aug 24, 2024
1 parent da51b82 commit 5687a26
Show file tree
Hide file tree
Showing 10 changed files with 27 additions and 146 deletions.
32 changes: 1 addition & 31 deletions project/thscoreboard/replays/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
from django.utils import formats
from django.utils import timezone
from django.utils.translation import pgettext_lazy
from django.db.models import Q, F, Window, QuerySet, When, Case, Value
from django.db.models.functions import RowNumber
from django.db.models import Q, QuerySet

from replays import game_ids
from replays import limits
Expand Down Expand Up @@ -197,35 +196,6 @@ def filter_visible(self) -> "ReplayQuerySet":

return self.filter(Q(user__is_active=True) | Q(imported_username__isnull=False))

def annotate_with_rank(self) -> "ReplayQuerySet":
"""Annotate each standard replay with a rank, starting from 1 descending, with
separate ranks for each difficulty and shot. Set rank to -1 for other replays.
"""

return self.annotate(
rank=Case(
When(
Q(category=Category.STANDARD) & Q(replay_type=ReplayType.FULL_GAME),
then=Window(
expression=RowNumber(),
order_by=[
F("score").desc(),
F("created"),
F("id"),
],
partition_by=[
F("shot_id"),
F("difficulty"),
F("shot__game_id"),
F("route"),
],
),
),
default=Value(-1),
output_field=models.IntegerField(),
)
)

def ghosts_of(self, replay_hash: bytes) -> "ReplayQuerySet":
"""Matches ghosts of a given replay file.
Expand Down
20 changes: 0 additions & 20 deletions project/thscoreboard/replays/rank_replays.py

This file was deleted.

7 changes: 6 additions & 1 deletion project/thscoreboard/replays/replays_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ def _get_shot_name(self, shot: models.Shot) -> str:
def convert_replay_to_dict(self, replay: models.Replay) -> dict:
shot = replay.shot
game = self._get_game(shot)
score_prefix = _get_medal_emoji(replay.rank) if hasattr(replay, "rank") else ""

rank = replay.GetRank()
if rank is None:
score_prefix = ""
else:
score_prefix = _get_medal_emoji(rank)

json_dict = {}
json_dict["Id"] = replay.id
Expand Down
28 changes: 4 additions & 24 deletions project/thscoreboard/replays/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,11 @@ def testRanks(self):
)

replays = (
models.Replay.objects.order_by("-score")
.select_related("rank_view")
.annotate_with_rank()
.all()
models.Replay.objects.order_by("-score").select_related("rank_view").all()
)

self.assertEquals(replays[0].rank, 1)
self.assertEquals(replays[0].GetRank(), 1)
self.assertEquals(replays[1].rank, 2)
self.assertEquals(replays[1].GetRank(), 2)
self.assertEquals(replays[2].rank, 1)
self.assertEquals(replays[2].GetRank(), 1)

def testRanksTasReplay(self):
Expand All @@ -154,9 +148,8 @@ def testRanksTasReplay(self):
category=models.Category.TAS,
)

replay = models.Replay.objects.order_by("-score").annotate_with_rank().first()
replay = models.Replay.objects.order_by("-score").first()

self.assertEquals(replay.rank, -1)
self.assertIsNone(replay.GetRank())

def testRanksBreakTiesUsingUploadDate(self):
Expand Down Expand Up @@ -190,17 +183,11 @@ def testRanksBreakTiesUsingUploadDate(self):
)

replays = (
models.Replay.objects.select_related("rank_view")
.order_by("created")
.annotate_with_rank()
.all()
models.Replay.objects.select_related("rank_view").order_by("created").all()
)

self.assertEquals(replays[0].rank, 1)
self.assertEquals(replays[0].GetRank(), 1)
self.assertEquals(replays[1].rank, 2)
self.assertEquals(replays[1].GetRank(), 2)
self.assertEquals(replays[2].rank, 3)
self.assertEquals(replays[2].GetRank(), 3)

def testStagePracticeReplaysAreUnranked(self) -> None:
Expand All @@ -210,12 +197,7 @@ def testStagePracticeReplaysAreUnranked(self) -> None:
replay_type=models.ReplayType.STAGE_PRACTICE,
)

replay = (
models.Replay.objects.select_related("rank_view")
.annotate_with_rank()
.first()
)
self.assertEquals(replay.rank, -1)
replay = models.Replay.objects.select_related("rank_view").first()
self.assertIsNone(replay.GetRank())

def testRankCountsTasSeparately(self):
Expand Down Expand Up @@ -253,8 +235,6 @@ def testRankCountsTasSeparately(self):
.all()
)

# Note that .annotate_with_rank() performs incorrectly with this query,
# which is why we're moving to the join-with-view implementation instead.
self.assertEqual(len(replays), 3)
(returned_standard_1, returned_standard_2, returned_tas) = replays
self.assertEqual(returned_standard_1.category, models.Category.STANDARD)
Expand Down
52 changes: 0 additions & 52 deletions project/thscoreboard/replays/test_rank_replays.py

This file was deleted.

6 changes: 3 additions & 3 deletions project/thscoreboard/replays/test_replays_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def testConvertReplaysToJsonString(self):
imported_username="あ",
)

replays = models.Replay.objects.order_by("-score").annotate_with_rank()
replays = models.Replay.objects.order_by("-score")

with test_utilities.OverrideTranslations():
converter = ReplayToJsonConverter()
Expand Down Expand Up @@ -106,7 +106,7 @@ def testMedalEmojisSingleShot(self):
difficulty=0,
)

replays = models.Replay.objects.order_by("-score").annotate_with_rank()
replays = models.Replay.objects.order_by("-score")
with test_utilities.OverrideTranslations():
converter = ReplayToJsonConverter()
json_data = [converter.convert_replay_to_dict(replay) for replay in replays]
Expand All @@ -130,7 +130,7 @@ def testMedalEmojisMultipleShots(self):
difficulty=0,
)

replays = models.Replay.objects.order_by("-score").annotate_with_rank()
replays = models.Replay.objects.order_by("-score")

with test_utilities.OverrideTranslations():
converter = ReplayToJsonConverter()
Expand Down
6 changes: 2 additions & 4 deletions project/thscoreboard/replays/views/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
@http_decorators.require_safe
def index_json(request):
recent_replays = (
models.Replay.objects.filter(
category__in=[models.Category.STANDARD, models.Category.TAS]
)
models.Replay.objects.select_related("rank_view")
.filter(category__in=[models.Category.STANDARD, models.Category.TAS])
.filter(is_listed=True)
.filter_visible()
.annotate_with_rank()
.order_by("-created")[:50]
)
replay_jsons = convert_replays_to_json_bytes(recent_replays)
Expand Down
2 changes: 1 addition & 1 deletion project/thscoreboard/replays/views/replay_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,13 @@ def _get_all_replay_for_game(game_id: str) -> Manager[models.Replay]:
return (
models.Replay.objects.prefetch_related("shot")
.prefetch_related("route")
.select_related("rank_view")
.filter(category=models.Category.STANDARD)
.filter(shot__game=game_id)
.filter(replay_type=1)
.filter(is_listed=True)
.filter_visible()
.order_by("-score")
.annotate_with_rank()
)


Expand Down
9 changes: 4 additions & 5 deletions project/thscoreboard/replays/views/user.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""The public page for a user's information."""


from django.contrib import auth
from django.shortcuts import get_object_or_404, render
from django.views.decorators import http as http_decorators

from replays.rank_replays import add_global_rank_annotations
from replays import models
from replays.replays_to_json import convert_replays_to_json_bytes
from replays.views.replay_table_helpers import stream_json_bytes_to_http_reponse
Expand All @@ -14,10 +12,11 @@
@http_decorators.require_safe
def user_page_json(request, username: str):
user = get_object_or_404(auth.get_user_model(), username=username, is_active=True)
user_replays = models.Replay.objects.filter(user=user).order_by(
"shot__game_id", "shot_id", "created"
user_replays = (
models.Replay.objects.select_related("rank_view")
.filter(user=user)
.order_by("shot__game_id", "shot_id", "created")
)
add_global_rank_annotations(user_replays)
replay_jsons = convert_replays_to_json_bytes(user_replays)
return stream_json_bytes_to_http_reponse(replay_jsons)

Expand Down
11 changes: 6 additions & 5 deletions project/thscoreboard/users/views/rankings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,20 @@ def _get_all_player_rankings_for_games(
rankings = defaultdict(lambda: RankCount(0, 0, 0))
game_ids = [game.game_id for game in games]
top_3_replays = (
replay_models.Replay.objects.filter(category=replay_models.Category.STANDARD)
replay_models.Replay.objects.select_related("rank_view")
.filter(category=replay_models.Category.STANDARD)
.filter(shot__game__in=game_ids)
.filter(replay_type=1)
.filter(is_listed=True)
.annotate_with_rank()
.filter(rank__lte=3)
.filter(rank_view__place__lte=3)
)

for replay in top_3_replays:
player = replay.user if replay.user is not None else replay.imported_username
if replay.rank == 1:
rank = replay.GetRank()
if rank == 1:
rankings[player].first_place_count += 1
elif replay.rank == 2:
elif rank == 2:
rankings[player].second_place_count += 1
else:
rankings[player].third_place_count += 1
Expand Down

0 comments on commit 5687a26

Please sign in to comment.