2 import sqlalchemy as sa
3 import sqlalchemy.sql.functions as func
4 import sqlalchemy.sql.expression as expr
5 from beaker.cache import cache_regions, cache_region
6 from collections import namedtuple
7 from datetime import datetime, timedelta
8 from pyramid.response import Response
9 from xonstat.models import *
10 from xonstat.util import *
11 from xonstat.views.helpers import RecentGame, recent_games_q
12 from webhelpers.paginate import Page
15 log = logging.getLogger(__name__)
18 @cache_region('hourly_term')
19 def get_summary_stats(scope="all"):
21 Gets the following aggregate statistics according to the provided scope:
23 - the number of active players
24 - the number of games per game type
26 Scope can be "all" or "day".
28 The fetched information is summarized into a string which is passed
29 directly to the template.
31 if scope not in ["all", "day"]:
35 ss = DBSession.query("num_players", "game_type_cd", "num_games").\
37 "SELECT num_players, game_type_cd, num_games "
38 "FROM summary_stats_mv "
39 "WHERE scope = :scope "
40 "ORDER BY sort_order "
41 ).params(scope=scope).all()
47 # the number of players is constant in each row
48 total_players = row.num_players
50 total_games += row.num_games
52 # we can't show all game types on the single summary line, so any
53 # past the fifth one will get bundled in to an "other" count
55 other_games += row.num_games
59 # don't send anything if we don't have any activity
63 # This is ugly because we're doing template-like stuff within the
64 # view code. The alternative isn't any better, though: we would
65 # have to assemble the string inside the template by using a Python
66 # code block. For now I'll leave it like this since it is the lesser
68 # Also we need to hard-code the URL structure in here to allow caching,
70 in_paren = "; ".join(["{:2,d} {}".format(
72 "<a href='/games?type={0}'>{0}</a>".format(g.game_type_cd)
76 in_paren += "; {:2,d} other".format(other_games)
78 stat_line = "{:2,d} players and {:2,d} games ({})".format(
84 except Exception as e:
91 @cache_region('hourly_term')
92 def get_ranks(game_type_cd):
94 Gets a set number of the top-ranked people for the specified game_type_cd.
96 The game_type_cd parameter is the type to fetch. Currently limited to
97 duel, dm, ctf, and tdm.
99 # how many ranks we want to fetch
100 leaderboard_count = 10
102 # only a few game modes are actually ranked
103 if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
106 ranks = DBSession.query(PlayerRank).\
107 filter(PlayerRank.game_type_cd==game_type_cd).\
108 order_by(PlayerRank.rank).\
109 limit(leaderboard_count).all()
114 @cache_region('hourly_term')
115 def get_top_players_by_time(limit=None, start=None):
117 The top players by the amount of time played during a date range.
119 q = DBSession.query(ActivePlayer)
121 if start is not None:
122 q = q.filter(ActivePlayer.sort_order >= start)
124 q = q.order_by(ActivePlayer.sort_order)
126 if limit is not None:
132 @cache_region('hourly_term')
133 def get_top_servers_by_games(limit=None, start=None):
135 The top servers by the number of games played during a date range.
137 q = DBSession.query(ActiveServer)
139 if start is not None:
140 q = q.filter(ActiveServer.sort_order >= start)
142 q = q.order_by(ActiveServer.sort_order)
144 if limit is not None:
150 @cache_region('hourly_term')
151 def get_top_maps_by_games(limit=None, start=None):
153 The top maps by the number of games played during a date range.
155 q = DBSession.query(ActiveMap)
157 if start is not None:
158 q = q.filter(ActiveMap.sort_order >= start)
160 q = q.order_by(ActiveMap.sort_order)
162 if limit is not None:
168 def _main_index_data(request):
170 leaderboard_lifetime = int(
171 request.registry.settings['xonstat.leaderboard_lifetime'])
173 leaderboard_lifetime = 30
175 leaderboard_count = 10
176 recent_games_count = 20
178 # summary statistics for the tagline
179 stat_line = get_summary_stats("all")
180 day_stat_line = get_summary_stats("day")
183 # the three top ranks tables
185 for gtc in ['duel', 'ctf', 'dm', 'tdm']:
186 rank = get_ranks(gtc)
190 right_now = datetime.utcnow()
191 back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
193 # top players by playing time
194 top_players = get_top_players_by_time(10)
196 # top servers by number of games
197 top_servers = get_top_servers_by_games(10)
199 # top maps by total times played
200 top_maps = get_top_maps_by_games(10)
202 # recent games played in descending order
203 rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
204 recent_games = [RecentGame(row) for row in rgs]
206 return {'top_players':top_players,
207 'top_servers':top_servers,
209 'recent_games':recent_games,
211 'stat_line':stat_line,
212 'day_stat_line':day_stat_line,
216 def main_index(request):
218 Display the main page information.
220 return _main_index_data(request)
223 def main_index_json(request):
225 JSON output of the main page information.
227 return [{'status':'not implemented'}]
230 def top_players_index(request):
232 start = int(request.params.get('start', None))
236 top_players = get_top_players_by_time(20, start)
238 # building a query string
240 if len(top_players) > 1:
241 query['start'] = top_players[-1].sort_order + 1
244 'top_players':top_players,
250 def top_servers_index(request):
252 start = int(request.params.get('start', None))
256 top_servers = get_top_servers_by_games(20, start)
258 # building a query string
260 if len(top_servers) > 1:
261 query['start'] = top_servers[-1].sort_order + 1
264 'top_servers':top_servers,
270 def top_maps_index(request):
272 start = int(request.params.get('start', None))
276 top_maps = get_top_maps_by_games(20, start)
278 # building a query string
280 if len(top_maps) > 1:
281 query['start'] = top_maps[-1].sort_order + 1