2 from datetime import datetime, timedelta
4 from beaker.cache import cache_region
5 from sqlalchemy import text
6 from xonstat.models import DBSession, PlayerRank, ActivePlayer, ActiveServer, ActiveMap
7 from xonstat.views.helpers import RecentGame, recent_games_q
9 log = logging.getLogger(__name__)
12 @cache_region('hourly_term')
13 def summary_stats_data(scope="all"):
15 Gets the summary stats (number of active players, the game type, and the number of games)
18 :param scope: The scope to fetch from the table. May be "all" or "day".
21 if scope not in ["all", "day"]:
24 sql = text("SELECT num_players, game_type_cd, num_games, create_dt refresh_dt "
25 "FROM summary_stats_mv "
26 "WHERE scope = :scope "
27 "ORDER BY sort_order ")
30 ss = DBSession.query("num_players", "game_type_cd", "num_games", "refresh_dt").\
31 from_statement(sql).params(scope=scope).all()
34 except Exception as e:
39 def summary_stats_json(request):
40 ss = summary_stats_data(request.params.get("scope", "all"))
43 "players": r.num_players,
44 "game_type_cd": r.game_type_cd,
46 "refresh_dt": r.refresh_dt.isoformat(),
51 @cache_region('hourly_term')
52 def summary_stats_string(scope="all"):
54 Assembles the summary stats data into a readable line for direct inclusion in templates.
57 ss = summary_stats_data(scope)
63 # the number of players is constant in each row
64 total_players = row.num_players
66 total_games += row.num_games
68 # we can't show all game types on the single summary line, so any
69 # past the fifth one will get bundled in to an "other" count
71 other_games += row.num_games
75 # don't send anything if we don't have any activity
79 # This is ugly because we're doing template-like stuff within the
80 # view code. The alternative isn't any better, though: we would
81 # have to assemble the string inside the template by using a Python
82 # code block. For now I'll leave it like this since it is the lesser
84 # Also we need to hard-code the URL structure in here to allow caching,
86 in_paren = "; ".join(["{:2,d} {}".format(
88 "<a href='/games?type={0}'>{0}</a>".format(g.game_type_cd)
92 in_paren += "; {:2,d} other".format(other_games)
94 stat_line = "{:2,d} players and {:2,d} games ({})".format(
100 except Exception as e:
107 @cache_region('hourly_term')
108 def get_ranks(game_type_cd):
110 Gets a set number of the top-ranked people for the specified game_type_cd.
112 The game_type_cd parameter is the type to fetch. Currently limited to
113 duel, dm, ctf, and tdm.
115 # how many ranks we want to fetch
116 leaderboard_count = 10
118 # only a few game modes are actually ranked
119 if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
122 ranks = DBSession.query(PlayerRank).\
123 filter(PlayerRank.game_type_cd==game_type_cd).\
124 order_by(PlayerRank.rank).\
125 limit(leaderboard_count).all()
130 @cache_region('hourly_term')
131 def get_top_players_by_time(limit=None, start=None):
133 The top players by the amount of time played during a date range.
135 q = DBSession.query(ActivePlayer)
137 if start is not None:
138 q = q.filter(ActivePlayer.sort_order >= start)
140 q = q.order_by(ActivePlayer.sort_order)
142 if limit is not None:
148 @cache_region('hourly_term')
149 def get_top_servers_by_play_time(limit=None, start=None):
151 The top servers by the cumulative amount of time played on them during a given interval.
153 q = DBSession.query(ActiveServer)
155 if start is not None:
156 q = q.filter(ActiveServer.sort_order >= start)
158 q = q.order_by(ActiveServer.sort_order)
160 if limit is not None:
166 @cache_region('hourly_term')
167 def get_top_maps_by_games(limit=None, start=None):
169 The top maps by the number of games played during a date range.
171 q = DBSession.query(ActiveMap)
173 if start is not None:
174 q = q.filter(ActiveMap.sort_order >= start)
176 q = q.order_by(ActiveMap.sort_order)
178 if limit is not None:
184 def _main_index_data(request):
186 leaderboard_lifetime = int(
187 request.registry.settings['xonstat.leaderboard_lifetime'])
189 leaderboard_lifetime = 30
191 leaderboard_count = 10
192 recent_games_count = 20
194 # summary statistics for the tagline
195 stat_line = summary_stats_string("all")
196 day_stat_line = summary_stats_string("day")
199 # the three top ranks tables
201 for gtc in ['duel', 'ctf', 'dm', 'tdm']:
202 rank = get_ranks(gtc)
206 right_now = datetime.utcnow()
207 back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
209 # top players by playing time
210 top_players = get_top_players_by_time(10)
212 # top servers by number of games
213 top_servers = get_top_servers_by_play_time(10)
215 # top maps by total times played
216 top_maps = get_top_maps_by_games(10)
218 # recent games played in descending order
219 rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
220 recent_games = [RecentGame(row) for row in rgs]
222 return {'top_players':top_players,
223 'top_servers':top_servers,
225 'recent_games':recent_games,
227 'stat_line':stat_line,
228 'day_stat_line':day_stat_line,
232 def main_index(request):
234 Display the main page information.
236 return _main_index_data(request)
239 def main_index_json(request):
241 JSON output of the main page information.
243 return [{'status':'not implemented'}]
246 def top_players_index(request):
248 start = int(request.params.get('start', None))
252 top_players = get_top_players_by_time(20, start)
254 # building a query string
256 if len(top_players) > 1:
257 query['start'] = top_players[-1].sort_order + 1
260 'top_players':top_players,
266 def top_servers_index(request):
268 start = int(request.params.get('start', None))
272 top_servers = get_top_servers_by_play_time(20, start)
274 # building a query string
276 if len(top_servers) > 1:
277 query['start'] = top_servers[-1].sort_order + 1
280 'top_servers':top_servers,
286 def top_maps_index(request):
288 start = int(request.params.get('start', None))
292 top_maps = get_top_maps_by_games(20, start)
294 # building a query string
296 if len(top_maps) > 1:
297 query['start'] = top_maps[-1].sort_order + 1