X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonstat.git;a=blobdiff_plain;f=xonstat%2Fviews%2Fserver.py;h=f65cd13aefb925ea7b613697a7d726c7cedadfb8;hp=c2538feafa0e9893a24ee121734e125e667e6e11;hb=69bec456b27e9f5d5b2edc961328df18af5f1cb9;hpb=aea5b4dc3514804bd910fc0c58ef40a5b9736113 diff --git a/xonstat/views/server.py b/xonstat/views/server.py index c2538fe..f65cd13 100644 --- a/xonstat/views/server.py +++ b/xonstat/views/server.py @@ -1,8 +1,10 @@ import logging import sqlalchemy.sql.functions as func import sqlalchemy.sql.expression as expr +from collections import namedtuple from datetime import datetime, timedelta from pyramid.httpexceptions import HTTPNotFound +from sqlalchemy import func as fg from webhelpers.paginate import Page from xonstat.models import DBSession, Player, Server, Map, Game, PlayerGameStat from xonstat.util import page_url, html_colors @@ -14,6 +16,7 @@ log = logging.getLogger(__name__) # Defaults LEADERBOARD_LIFETIME = 30 LEADERBOARD_COUNT = 10 +INDEX_COUNT = 20 RECENT_GAMES_COUNT = 20 @@ -33,7 +36,7 @@ class ServerIndex(object): servers = Page(server_q, self.page, items_per_page=25, url=page_url) except: - servers = None + raise HTTPNotFound return servers @@ -53,7 +56,7 @@ class ServerIndex(object): class ServerInfoBase(object): """Baseline parameter parsing for Server URLs with a server_id in them.""" - def __init__(self, request): + def __init__(self, request, limit=None, last=None): """Common parameter parsing.""" self.request = request self.server_id = request.matchdict.get("id", None) @@ -62,40 +65,67 @@ class ServerInfoBase(object): LEADERBOARD_LIFETIME) self.lifetime = int(raw_lifetime) + self.limit = request.params.get("limit", limit) + self.last = request.params.get("last", last) self.now = datetime.utcnow() class ServerTopMaps(ServerInfoBase): """Returns the top maps played on a given server.""" - def __init__(self, request): + def __init__(self, request, limit=INDEX_COUNT, last=None): """Common parameter parsing.""" - super(ServerTopMaps, self).__init__(request) + super(ServerTopMaps, self).__init__(request, limit, last) + self.top_maps = self.raw() def raw(self): """Returns the raw data shared by all renderers.""" try: - top_maps = DBSession.query(Game.map_id, Map.name, func.count())\ + top_maps_q = DBSession.query( + fg.row_number().over(order_by=expr.desc(func.count())).label("rank"), + Game.map_id, Map.name, func.count().label("times_played"))\ .filter(Map.map_id==Game.map_id)\ .filter(Game.server_id==self.server_id)\ .filter(Game.create_dt > (self.now - timedelta(days=self.lifetime)))\ .group_by(Game.map_id)\ .group_by(Map.name) \ - .order_by(expr.desc(func.count()))\ - .limit(LEADERBOARD_COUNT)\ - .all() - except: - top_maps = None + .order_by(expr.desc(func.count())) + + if self.last: + top_maps_q = top_maps_q.offset(self.last) + + if self.limit: + top_maps_q = top_maps_q.limit(self.limit) + + top_maps = top_maps_q.all() + except Exception as e: + log.debug(e) + raise HTTPNotFound return top_maps + def html(self): + """Returns the HTML-ready representation.""" + + # build the query string + query = {} + if len(self.top_maps) > 1: + query['last'] = self.top_maps[-1].rank + + return { + "server_id": self.server_id, + "top_maps": self.top_maps, + "query": query, + } + def json(self): """For rendering this data using JSON.""" top_maps = [{ + "rank": tm.rank, "map_id": tm.map_id, "map_name": tm.name, - "times_played": tm[2], + "times_played": tm.times_played, } for tm in self.top_maps] return top_maps @@ -104,16 +134,19 @@ class ServerTopMaps(ServerInfoBase): class ServerTopScorers(ServerInfoBase): """Returns the top scorers on a given server.""" - def __init__(self, request): + def __init__(self, request, limit=INDEX_COUNT, last=None): """Common parameter parsing.""" - super(ServerTopScorers, self).__init__(request) + super(ServerTopScorers, self).__init__(request, limit, last) self.top_scorers = self.raw() def raw(self): """Top scorers on this server by total score.""" try: - top_scorers = DBSession.query(Player.player_id, Player.nick, - func.sum(PlayerGameStat.score))\ + top_scorers_q = DBSession.query( + fg.row_number().over( + order_by=expr.desc(func.sum(PlayerGameStat.score))).label("rank"), + Player.player_id, Player.nick, + func.sum(PlayerGameStat.score).label("total_score"))\ .filter(Player.player_id == PlayerGameStat.player_id)\ .filter(Game.game_id == PlayerGameStat.game_id)\ .filter(Game.server_id == self.server_id)\ @@ -122,21 +155,47 @@ class ServerTopScorers(ServerInfoBase): (self.now - timedelta(days=LEADERBOARD_LIFETIME)))\ .order_by(expr.desc(func.sum(PlayerGameStat.score)))\ .group_by(Player.nick)\ - .group_by(Player.player_id)\ - .limit(LEADERBOARD_COUNT)\ - .all() + .group_by(Player.player_id) - except: - top_scorers = None + if self.last: + top_scorers_q = top_scorers_q.offset(self.last) + + if self.limit: + top_scorers_q = top_scorers_q.limit(self.limit) + + top_scorers = top_scorers_q.all() + + except Exception as e: + log.debug(e) + raise HTTPNotFound return top_scorers + def html(self): + """Returns an HTML-ready representation.""" + TopScorer = namedtuple("TopScorer", ["rank", "player_id", "nick", "total_score"]) + + top_scorers = [TopScorer(ts.rank, ts.player_id, html_colors(ts.nick), ts.total_score) + for ts in self.top_scorers] + + # build the query string + query = {} + if len(top_scorers) > 1: + query['last'] = top_scorers[-1].rank + + return { + "server_id": self.server_id, + "top_scorers": top_scorers, + "query": query, + } + def json(self): """For rendering this data using JSON.""" top_scorers = [{ + "rank": ts.rank, "player_id": ts.player_id, "nick": ts.nick, - "score": ts[2], + "score": ts.total_score, } for ts in self.top_scorers] return top_scorers @@ -145,16 +204,19 @@ class ServerTopScorers(ServerInfoBase): class ServerTopPlayers(ServerInfoBase): """Returns the top players by playing time on a given server.""" - def __init__(self, request): + def __init__(self, request, limit=INDEX_COUNT, last=None): """Common parameter parsing.""" - super(ServerTopPlayers, self).__init__(request) + super(ServerTopPlayers, self).__init__(request, limit, last) self.top_players = self.raw() def raw(self): """Top players on this server by total playing time.""" try: - top_players = DBSession.query(Player.player_id, Player.nick, - func.sum(PlayerGameStat.alivetime))\ + top_players_q = DBSession.query( + fg.row_number().over( + order_by=expr.desc(func.sum(PlayerGameStat.alivetime))).label("rank"), + Player.player_id, Player.nick, + func.sum(PlayerGameStat.alivetime).label("alivetime"))\ .filter(Player.player_id == PlayerGameStat.player_id)\ .filter(Game.game_id == PlayerGameStat.game_id)\ .filter(Game.server_id == self.server_id)\ @@ -162,21 +224,47 @@ class ServerTopPlayers(ServerInfoBase): .filter(PlayerGameStat.create_dt > (self.now - timedelta(days=self.lifetime)))\ .order_by(expr.desc(func.sum(PlayerGameStat.alivetime)))\ .group_by(Player.nick)\ - .group_by(Player.player_id)\ - .limit(LEADERBOARD_COUNT)\ - .all() + .group_by(Player.player_id) - except: - top_players = None + if self.last: + top_players_q = top_players_q.offset(self.last) + + if self.limit: + top_players_q = top_players_q.limit(self.limit) + + top_players = top_players_q.all() + + except Exception as e: + log.debug(e) + raise HTTPNotFound return top_players + def html(self): + """Returns the HTML-ready representation.""" + TopPlayer = namedtuple("TopPlayer", ["rank", "player_id", "nick", "alivetime"]) + + top_players = [TopPlayer(tp.rank, tp.player_id, html_colors(tp.nick), tp.alivetime) + for tp in self.top_players] + + # build the query string + query = {} + if len(top_players) > 1: + query['last'] = top_players[-1].rank + + return { + "server_id": self.server_id, + "top_players": top_players, + "query": query, + } + def json(self): """For rendering this data using JSON.""" top_players = [{ + "rank": ts.rank, "player_id": ts.player_id, "nick": ts.nick, - "time": ts[2].total_seconds(), + "time": ts.alivetime.total_seconds(), } for ts in self.top_players] return top_players @@ -192,9 +280,9 @@ class ServerInfo(ServerInfoBase): # this view uses data from other views, so we'll save the data at that level try: self.server = DBSession.query(Server).filter_by(server_id=self.server_id).one() - self.top_maps_v = ServerTopMaps(self.request) - self.top_scorers_v = ServerTopScorers(self.request) - self.top_players_v = ServerTopPlayers(self.request) + self.top_maps_v = ServerTopMaps(self.request, limit=LEADERBOARD_COUNT) + self.top_scorers_v = ServerTopScorers(self.request, limit=LEADERBOARD_COUNT) + self.top_players_v = ServerTopPlayers(self.request, limit=LEADERBOARD_COUNT) rgs = recent_games_q(server_id=self.server_id).limit(RECENT_GAMES_COUNT).all() self.recent_games = [RecentGame(row) for row in rgs] @@ -213,22 +301,17 @@ class ServerInfo(ServerInfoBase): def html(self): """For rendering this data using something HTML-based.""" - server_info = self.raw() - - print(server_info) - - # convert the nick into HTML for both scorers and players - server_info["top_scorers"] = [(player_id, html_colors(nick), score) - for (player_id, nick, score) in server_info["top_scorers"]] - - server_info["top_players"] = [(player_id, html_colors(nick), score) - for (player_id, nick, score) in server_info["top_players"]] - - return server_info + return { + 'server': self.server, + 'top_players': self.top_players_v.html()["top_players"], + 'top_scorers': self.top_scorers_v.html()["top_scorers"], + 'top_maps': self.top_maps_v.html()["top_maps"], + 'recent_games': self.recent_games, + 'lifetime': self.lifetime, + } def json(self): """For rendering this data using JSON.""" - return { 'server': self.server.to_dict(), 'top_players': self.top_players_v.json(),