diff --git a/agagd/agagd_core/tables/games.py b/agagd/agagd_core/tables/games.py index dcf0fc8d..035a533c 100644 --- a/agagd/agagd_core/tables/games.py +++ b/agagd/agagd_core/tables/games.py @@ -3,82 +3,49 @@ import django_tables2 as tables -# Column for the Winner of the Game -class LinkFullMembersNameColumn(tables.Column): - def __init__( - self, - color="W", - viewname=None, - urlconf=None, - args=None, - kwargs=None, - current_app=None, - attrs=None, - **extra, - ): - super().__init__( - attrs=attrs, - linkify=dict( - viewname=viewname, - urlconf=urlconf, - args=args, - kwargs=kwargs, - current_app=current_app, - ), - **extra, - ) - self.color = color - - def render(self, value, record): - if record["result"] == self.color: - self.attrs["td"] = {"class": "winner"} - elif record["result"] != self.color: - self.attrs["td"] = {"class": "runner-up"} +def class_white(record): + """returns td class for white""" + return "winner" if record["result"] == "W" else "runner-up" - try: - member_name_and_id = agagd_models.Member.objects.values("full_name").get( - member_id=value - ) - value = f"{member_name_and_id['full_name']} ({value})" - except ObjectDoesNotExist: - value = None - return value +def class_black(record): + """returns td class for black""" + return "winner" if record["result"] == "B" else "runner-up" # Basic table which is use as as base for many of the game layouts. class GamesTable(tables.Table): - game_date = tables.Column( - verbose_name="Date", attrs=django_tables2_styles.default_bootstrap_column_attrs - ) - handicap = tables.Column( - attrs=django_tables2_styles.default_bootstrap_column_attrs, orderable=False + white = tables.Column( + linkify=("players_profile", [tables.A("white")]), + attrs={"td": {"class": class_white}}, ) - pin_player_1 = LinkFullMembersNameColumn( - color="W", - verbose_name="White", - viewname="players_profile", - kwargs={"player_id": tables.A("pin_player_1")}, + black = tables.Column( + linkify=("players_profile", [tables.A("black")]), + attrs={"td": {"class": class_black}}, ) - pin_player_2 = LinkFullMembersNameColumn( - color="B", - verbose_name="Black", - viewname="players_profile", - kwargs={"player_id": tables.A("pin_player_2")}, - ) - tournament_code = tables.Column( + tournament = tables.Column( verbose_name="Tournament", - linkify=("tournament_detail", [tables.A("tournament_code")]), + linkify=("tournament_detail", [tables.A("tournament")]), ) + def render_white(self, record): + name = record["white_name"] + pin = record["white"] + return f"{name} ({pin})" + + def render_black(self, record): + name = record["black_name"] + pin = record["black"] + return f"{name} ({pin})" + class Meta: attrs = django_tables2_styles.default_bootstrap_header_column_attrs fields = ( - "pin_player_1", - "pin_player_2", - "tournament_code", + "date", + "white", + "black", + "tournament", "handicap", - "game_date", ) model = agagd_models.Game orderable = False diff --git a/agagd/agagd_core/tables/players.py b/agagd/agagd_core/tables/players.py index a8dfa8c7..649ecfc7 100644 --- a/agagd/agagd_core/tables/players.py +++ b/agagd/agagd_core/tables/players.py @@ -20,11 +20,12 @@ class Meta: class PlayersOpponentTable(tables.Table): - opponent = tables.Column( + opponent_id = tables.Column( + verbose_name="Opponent", orderable=False, linkify={ "viewname": "players_profile", - "args": [tables.A("opponent.member_id")], + "args": [tables.A("opponent_id")], }, ) total = tables.Column(verbose_name="Games") @@ -32,9 +33,13 @@ class PlayersOpponentTable(tables.Table): lost = tables.Column(verbose_name="Lost") ratio = tables.Column(verbose_name="Rate", default=0, empty_values=(-1,)) + def render_opponent_id(self, record): + opponent_full_name = record["opponent_full_name"] + opponent_id = record["opponent_id"] + return f"{opponent_full_name} ({opponent_id})" + def render_ratio(self, record): ratio = record["won"] / record["total"] - return f"{ratio:.2f}" class Meta: @@ -44,16 +49,24 @@ class Meta: class PlayersTournamentTable(tables.Table): - tournament = tables.Column( - linkify=("tournament_detail", [tables.A("tournament.pk")]) + tournament_code = tables.Column( + verbose_name="Tournament", + linkify=("tournament_detail", [tables.A("tournament_code")]), ) - date = tables.Column(default="Unknown") + tournament_date = tables.Column(default="Unknown") won = tables.Column(verbose_name="Won", default=0) lost = tables.Column(verbose_name="Lost", default=0) + def render_tournament_code(self, record): + tournament_code = record["tournament_code"] + tournament_date = record["tournament_date"] + total_players = record["total_players"] + + return f"{tournament_code} - on {tournament_date} with {total_players} players" + class Meta: attrs = django_tables2_styles.default_bootstrap_header_column_attrs - fields = ("date", "tournament", "won", "lost") + fields = ("tournament_date", "tournament_code", "won", "lost") orderable = False template_name = "django_tables2/bootstrap4.html" sequence = fields diff --git a/agagd/agagd_core/tables/tournaments.py b/agagd/agagd_core/tables/tournaments.py index 73a50264..8728adc9 100644 --- a/agagd/agagd_core/tables/tournaments.py +++ b/agagd/agagd_core/tables/tournaments.py @@ -1,7 +1,6 @@ import agagd_core.defaults.styles.django_tables2 as django_tables2_styles import agagd_core.models as agagd_models import django_tables2 as tables -from agagd_core.tables.games import LinkFullMembersNameColumn class TournamentsTable(tables.Table): @@ -51,21 +50,37 @@ class Meta: template_name = "tournament_detail_information.html" +def class_white(record): + """returns td class for player 1 (white)""" + return "winner" if record["result"] == "W" else "runner-up" + + +def class_black(record): + """returns td class for player 2 (black)""" + return "winner" if record["result"] == "B" else "runner-up" + + class TournamentsGamesTable(tables.Table): - pin_player_1 = LinkFullMembersNameColumn( - color="W", - verbose_name="White", - viewname="players_profile", - kwargs={"player_id": tables.A("pin_player_1")}, + white = tables.Column( + linkify=("players_profile", [tables.A("white")]), + attrs={"td": {"class": class_white}}, ) - - pin_player_2 = LinkFullMembersNameColumn( - color="B", + black = tables.Column( verbose_name="Black", - viewname="players_profile", - kwargs={"player_id": tables.A("pin_player_2")}, + linkify=("players_profile", [tables.A("black")]), + attrs={"td": {"class": class_black}}, ) + def render_white(self, record): + name = record["white_name"] + pin = record["white"] + return f"{name} ({pin})" + + def render_black(self, record): + name = record["black_name"] + pin = record["black"] + return f"{name} ({pin})" + def render_result(self, value): if value == "W": return "White Wins" @@ -76,9 +91,9 @@ def render_result(self, value): class Meta: attrs = django_tables2_styles.default_bootstrap_header_column_attrs fields = ( - "game_date", - "pin_player_1", - "pin_player_2", + "date", + "white", + "black", "handicap", "komi", "result", diff --git a/agagd/agagd_core/views/frontpage.py b/agagd/agagd_core/views/frontpage.py index 64dff6a7..9e3e2a59 100644 --- a/agagd/agagd_core/views/frontpage.py +++ b/agagd/agagd_core/views/frontpage.py @@ -9,6 +9,7 @@ from agagd_core.tables.players import PlayersTournamentTable from agagd_core.tables.top_players import TopDanTable, TopKyuTable from agagd_core.tables.tournaments import TournamentsTable +from django.db.models import F from django.shortcuts import render from django.template.response import TemplateResponse @@ -21,12 +22,14 @@ class FrontPageView(DetailView): def __get_latest_games(self): latest_games = agagd_models.Game.objects.values( - "game_date", "handicap", - "pin_player_1", - "pin_player_2", - "tournament_code", "result", + date=F("game_date"), + tournament=F("tournament_code"), + white=F("pin_player_1"), + black=F("pin_player_2"), + white_name=F("pin_player_1__full_name"), + black_name=F("pin_player_2__full_name"), ).order_by("-game_date")[:20] return latest_games diff --git a/agagd/agagd_core/views/players_profile.py b/agagd/agagd_core/views/players_profile.py index 728f9f8a..a02c250e 100644 --- a/agagd/agagd_core/views/players_profile.py +++ b/agagd/agagd_core/views/players_profile.py @@ -1,6 +1,3 @@ -# Date Imports -from datetime import date - # AGAGD Models Imports import agagd_core.models as agagd_models from agagd_core.tables.games import GamesTable @@ -14,7 +11,7 @@ # Django Imports from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q +from django.db.models import Case, CharField, Count, F, IntegerField, Q, When from django.http import Http404 from django.template.response import TemplateResponse from django.views.generic.detail import DetailView @@ -45,65 +42,95 @@ def get(self, request, *args, **kwargs): Q(pin_player__exact=player_id) ).values("pin_player", "rating", "sigma") - # compute additional tables for opponents & tournament info. here - # TODO: refactor this into something nicer. - opponent_data = {} - tourney_data = {} - for game in player_games: - try: - t_dat = tourney_data.get(game.tournament_code.pk, {}) - t_dat["tournament"] = game.tournament_code - t_dat["won"] = t_dat.get("won", 0) - t_dat["lost"] = t_dat.get("lost", 0) - - # Set default game_date to None - game_date = None - - # Check for 0000-00-00 dates - if game.game_date != u"0000-00-00": - game_date = game.game_date - - t_dat["date"] = t_dat.get("date", game_date) - - op = game.player_other_than(player) - opp_dat = opponent_data.get(op, {}) - opp_dat["opponent"] = op - opp_dat["total"] = opp_dat.get("total", 0) + 1 - opp_dat["won"] = opp_dat.get("won", 0) - opp_dat["lost"] = opp_dat.get("lost", 0) - if game.won_by(player): - opp_dat["won"] += 1 - t_dat["won"] += 1 - else: - opp_dat["lost"] += 1 - t_dat["lost"] += 1 - opponent_data[op] = opp_dat - tourney_data[game.tournament_code.pk] = t_dat - except ObjectDoesNotExist: - print("failing game_id: %s" % game.pk) - - opp_table = PlayersOpponentTable(opponent_data.values()) - RequestConfig(request, paginate={"per_page": 10}).configure(opp_table) - - t_table = PlayersTournamentTable( - tourney_data.values(), - sorted( - tourney_data.values(), - key=lambda d: d.get("date", date.today()) or date.today(), - reverse=True, - ), - prefix="ts_played", + opponents_queryset = ( + agagd_models.Game.objects.filter( + Q(pin_player_1__exact=player_id) | Q(pin_player_2__exact=player_id) + ) + .annotate( + opponent_id=Case( + When( + pin_player_1__exact=player_id, + then=F("pin_player_2"), + ), + When( + pin_player_2__exact=player_id, + then=F("pin_player_1"), + ), + output_field=IntegerField(), + ), + opponent_full_name=Case( + When( + pin_player_1__exact=player_id, + then=F("pin_player_2__full_name"), + ), + When( + pin_player_2__exact=player_id, + then=F("pin_player_1__full_name"), + ), + output_field=CharField(), + ), + ) + .values("opponent_id", "opponent_full_name") + .annotate( + won=Count( + "game_id", + filter=Q(pin_player_1__exact=player_id, result__exact="W") + | Q(pin_player_2__exact=player_id, result__exact="B"), + ), + lost=Count( + "game_id", + filter=Q(pin_player_1__exact=player_id, result__exact="B") + | Q(pin_player_2__exact=player_id, result__exact="W"), + ), + total=F("won") + F("lost"), + ) + .order_by("-total", "-won") + ) + opponents_table = PlayersOpponentTable(opponents_queryset) + RequestConfig(request, paginate={"per_page": 10}).configure(opponents_table) + + tournaments_queryset = ( + agagd_models.Tournament.objects.filter() + .values("tournament_code", "tournament_date", "total_players") + .annotate( + won=Count( + "games_in_tourney__game_id", + filter=Q( + games_in_tourney__pin_player_1__exact=player_id, + games_in_tourney__result__exact="W", + ) + | Q( + games_in_tourney__pin_player_2__exact=player_id, + games_in_tourney__result__exact="B", + ), + ), + lost=Count( + "games_in_tourney__game_id", + filter=Q( + games_in_tourney__pin_player_1__exact=player_id, + games_in_tourney__result__exact="B", + ) + | Q( + games_in_tourney__pin_player_2__exact=player_id, + games_in_tourney__result__exact="W", + ), + ), + ) + .order_by("-tournament_date") ) - RequestConfig(request, paginate={"per_page": 10}).configure(t_table) + tournaments_table = PlayersTournamentTable(tournaments_queryset) + RequestConfig(request, paginate={"per_page": 10}).configure(tournaments_table) player_games_table = GamesTable( player_games.values( - "game_date", "handicap", - "pin_player_1", - "pin_player_2", - "tournament_code", "result", + date=F("game_date"), + tournament=F("tournament_code"), + white=F("pin_player_1"), + black=F("pin_player_2"), + white_name=F("pin_player_1__full_name"), + black_name=F("pin_player_2__full_name"), ) ) @@ -126,7 +153,7 @@ def get(self, request, *args, **kwargs): context["player_rating"] = player_rating[0] context["player_games_table"] = player_games_table context["players_information_table"] = players_information_table - context["player_opponents_table"] = opp_table - context["player_tournaments_table"] = t_table + context["player_opponents_table"] = opponents_table + context["player_tournaments_table"] = tournaments_table return TemplateResponse(request, self.template_name, context) diff --git a/agagd/agagd_core/views/tournament_detail.py b/agagd/agagd_core/views/tournament_detail.py index 7da15778..d6e1d628 100644 --- a/agagd/agagd_core/views/tournament_detail.py +++ b/agagd/agagd_core/views/tournament_detail.py @@ -3,6 +3,7 @@ TournamentsGamesTable, TournamentsInformationTable, ) +from django.db.models import F from django.http import Http404 from django.template.response import TemplateResponse from django.views.generic.detail import DetailView @@ -24,7 +25,14 @@ def get(self, request, *args, **kwargs): ) tournament_games = tournament.games_in_tourney.values( - "game_date", "pin_player_1", "pin_player_2", "handicap", "komi", "result" + "handicap", + "komi", + "result", + date=F("game_date"), + white=F("pin_player_1"), + black=F("pin_player_2"), + white_name=F("pin_player_1__full_name"), + black_name=F("pin_player_2__full_name"), ) tournament_games_table = TournamentsGamesTable(tournament_games)