2 from collections import namedtuple
3 from datetime import datetime, timedelta
5 import sqlalchemy.sql.expression as expr
6 import sqlalchemy.sql.functions as func
7 from pyramid.httpexceptions import HTTPNotFound
8 from sqlalchemy import func as fg
9 from webhelpers.paginate import Page
10 from xonstat.models import DBSession, Server, Map, Game, PlayerGameStat, Player, PlayerCaptime
11 from xonstat.models.map import MapCapTime
12 from xonstat.util import page_url, html_colors
13 from xonstat.views.helpers import RecentGame, recent_games_q
15 log = logging.getLogger(__name__)
19 LEADERBOARD_LIFETIME = 30
22 class MapIndex(object):
23 """Returns a list of maps."""
25 def __init__(self, request):
26 """Common parameter parsing."""
27 self.request = request
28 self.page = request.params.get("page", 1)
29 self.last = request.params.get("last", None)
31 # all views share this data, so we'll pre-calculate
32 self.maps = self.map_index()
35 """Returns the raw data shared by all renderers."""
37 map_q = DBSession.query(Map)
40 map_q = map_q.filter(Map.map_id < self.last)
42 map_q = map_q.order_by(Map.map_id.desc()).limit(INDEX_COUNT)
45 except Exception as e:
52 """For rendering this data using something HTML-based."""
53 # build the query string
55 if len(self.maps) > 1:
56 query['last'] = self.maps[-1].map_id
64 """For rendering this data using JSON."""
66 'maps': [m.to_dict() for m in self.maps],
71 class MapInfoBase(object):
72 """Base class for all map-based views with a map_id parameter in them."""
74 def __init__(self, request, limit=None, last=None):
75 """Common parameter parsing."""
76 self.request = request
77 self.map_id = request.matchdict.get("id", None)
79 raw_lifetime = request.registry.settings.get('xonstat.leaderboard_lifetime',
81 self.lifetime = int(raw_lifetime)
83 self.limit = request.params.get("limit", limit)
84 self.last = request.params.get("last", last)
85 self.now = datetime.utcnow()
88 class MapTopScorers(MapInfoBase):
89 """Returns the top scorers on a given map."""
91 def __init__(self, request, limit=INDEX_COUNT, last=None):
92 """Common parameter parsing."""
93 super(MapTopScorers, self).__init__(request, limit, last)
94 self.top_scorers = self.get_top_scorers()
96 def get_top_scorers(self):
97 """Top players by score. Shared by all renderers."""
98 cutoff = self.now - timedelta(days=self.lifetime)
99 cutoff = self.now - timedelta(days=120)
101 top_scorers_q = DBSession.query(
102 fg.row_number().over(order_by=expr.desc(func.sum(PlayerGameStat.score))).label("rank"),
103 Player.player_id, Player.nick, func.sum(PlayerGameStat.score).label("total_score"))\
104 .filter(Player.player_id == PlayerGameStat.player_id)\
105 .filter(Game.game_id == PlayerGameStat.game_id)\
106 .filter(Game.map_id == self.map_id)\
107 .filter(Player.player_id > 2)\
108 .filter(PlayerGameStat.create_dt > cutoff)\
109 .order_by(expr.desc(func.sum(PlayerGameStat.score)))\
110 .group_by(Player.nick)\
111 .group_by(Player.player_id)
114 top_scorers_q = top_scorers_q.offset(self.last)
117 top_scorers_q = top_scorers_q.limit(self.limit)
119 top_scorers = top_scorers_q.all()
124 """Returns an HTML-ready representation."""
125 TopScorer = namedtuple("TopScorer", ["rank", "player_id", "nick", "total_score"])
127 top_scorers = [TopScorer(ts.rank, ts.player_id, html_colors(ts.nick), ts.total_score)
128 for ts in self.top_scorers]
130 # build the query string
132 if len(top_scorers) > 1:
133 query['last'] = top_scorers[-1].rank
136 "map_id": self.map_id,
137 "top_scorers": top_scorers,
138 "lifetime": self.lifetime,
143 """For rendering this data using JSON."""
146 "player_id": ts.player_id,
148 "score": ts.total_score,
149 } for ts in self.top_scorers]
152 "map_id": self.map_id,
153 "top_scorers": top_scorers,
157 def _map_info_data(request):
158 map_id = int(request.matchdict['id'])
161 leaderboard_lifetime = int(
162 request.registry.settings['xonstat.leaderboard_lifetime'])
164 leaderboard_lifetime = 30
166 leaderboard_count = 10
167 recent_games_count = 20
170 Captime = namedtuple('Captime', ['player_id', 'nick_html_colors',
171 'fastest_cap', 'game_id'])
174 gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
176 # recent games played in descending order
177 rgs = recent_games_q(map_id=map_id).limit(recent_games_count).all()
178 recent_games = [RecentGame(row) for row in rgs]
180 # top players by score
181 top_scorers = DBSession.query(Player.player_id, Player.nick,
182 func.sum(PlayerGameStat.score)).\
183 filter(Player.player_id == PlayerGameStat.player_id).\
184 filter(Game.game_id == PlayerGameStat.game_id).\
185 filter(Game.map_id == map_id).\
186 filter(Player.player_id > 2).\
187 filter(PlayerGameStat.create_dt >
188 (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
189 order_by(expr.desc(func.sum(PlayerGameStat.score))).\
190 group_by(Player.nick).\
191 group_by(Player.player_id).all()[0:leaderboard_count]
193 top_scorers = [(player_id, html_colors(nick), score) \
194 for (player_id, nick, score) in top_scorers]
196 # top players by playing time
197 top_players = DBSession.query(Player.player_id, Player.nick,
198 func.sum(PlayerGameStat.alivetime)).\
199 filter(Player.player_id == PlayerGameStat.player_id).\
200 filter(Game.game_id == PlayerGameStat.game_id).\
201 filter(Game.map_id == map_id).\
202 filter(Player.player_id > 2).\
203 filter(PlayerGameStat.create_dt >
204 (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
205 order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
206 group_by(Player.nick).\
207 group_by(Player.player_id).all()[0:leaderboard_count]
209 top_players = [(player_id, html_colors(nick), score) \
210 for (player_id, nick, score) in top_players]
212 # top servers using/playing this map
213 top_servers = DBSession.query(Server.server_id, Server.name,
214 func.count(Game.game_id)).\
215 filter(Game.server_id == Server.server_id).\
216 filter(Game.map_id == map_id).\
217 filter(Game.create_dt >
218 (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
219 order_by(expr.desc(func.count(Game.game_id))).\
220 group_by(Server.name).\
221 group_by(Server.server_id).all()[0:leaderboard_count]
223 # TODO make this a configuration parameter to be set in the settings
225 captimes_raw = DBSession.query(Player.player_id, Player.nick,
226 PlayerCaptime.fastest_cap, PlayerCaptime.game_id).\
227 filter(PlayerCaptime.map_id == map_id).\
228 filter(Player.player_id == PlayerCaptime.player_id).\
229 order_by(PlayerCaptime.fastest_cap).\
233 captimes = [Captime(c.player_id, html_colors(c.nick),
234 c.fastest_cap, c.game_id) for c in captimes_raw]
236 except Exception as e:
239 'recent_games':recent_games,
240 'top_scorers':top_scorers,
241 'top_players':top_players,
242 'top_servers':top_servers,
247 def map_info(request):
249 List the information stored about a given map.
251 mapinfo_data = _map_info_data(request)
253 # FIXME: code clone, should get these from _map_info_data
254 leaderboard_count = 10
255 recent_games_count = 20
257 for i in range(leaderboard_count-len(mapinfo_data['top_scorers'])):
258 mapinfo_data['top_scorers'].append(('-', '-', '-'))
260 for i in range(leaderboard_count-len(mapinfo_data['top_players'])):
261 mapinfo_data['top_players'].append(('-', '-', '-'))
263 for i in range(leaderboard_count-len(mapinfo_data['top_servers'])):
264 mapinfo_data['top_servers'].append(('-', '-', '-'))
269 def map_info_json(request):
271 List the information stored about a given map. JSON.
273 return [{'status':'not implemented'}]
276 def map_captimes_data(request):
277 map_id = int(request.matchdict['id'])
279 current_page = request.params.get('page', 1)
282 mmap = DBSession.query(Map).filter_by(map_id=map_id).one()
284 mct_q = DBSession.query(PlayerCaptime.fastest_cap, PlayerCaptime.create_dt,
285 PlayerCaptime.player_id, PlayerCaptime.game_id,
286 Game.server_id, Server.name.label('server_name'),
287 PlayerGameStat.nick.label('player_nick')).\
288 filter(PlayerCaptime.map_id==map_id).\
289 filter(PlayerCaptime.game_id==Game.game_id).\
290 filter(PlayerCaptime.map_id==Map.map_id).\
291 filter(Game.server_id==Server.server_id).\
292 filter(PlayerCaptime.player_id==PlayerGameStat.player_id).\
293 filter(PlayerCaptime.game_id==PlayerGameStat.game_id).\
294 order_by(expr.asc(PlayerCaptime.fastest_cap))
296 except Exception as e:
299 map_captimes = Page(mct_q, current_page, items_per_page=20, url=page_url)
301 map_captimes.items = [MapCapTime(row) for row in map_captimes.items]
306 'captimes':map_captimes,
309 def map_captimes(request):
310 return map_captimes_data(request)
312 def map_captimes_json(request):
313 current_page = request.params.get('page', 1)
314 data = map_captimes_data(request)
317 "map": data["map"].to_dict(),
318 "captimes": [e.to_dict() for e in data["captimes"].items],
319 "page": current_page,