]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/main.py
Remove the join to game stats for accuracy data.
[xonotic/xonstat.git] / xonstat / views / main.py
1 import logging
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
13
14
15 log = logging.getLogger(__name__)
16
17
18 @cache_region('hourly_term')
19 def get_summary_stats(cutoff_days=None):
20     """
21     Gets the following aggregate statistics about the past cutoff_days days:
22         - the number of active players
23         - the number of games per game type
24     If cutoff_days is None, the above stats are calculated for all time.
25
26     This information is then summarized into a string which is passed
27     directly to the template.
28     """
29     try:
30         if cutoff_days is not None:
31             # only games played during this range are considered
32             right_now = datetime.now()
33             cutoff_dt = right_now - timedelta(days=cutoff_days)
34
35             games = DBSession.query(Game.game_type_cd, func.count()).\
36                 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
37                 group_by(Game.game_type_cd).\
38                 order_by(expr.desc(func.count())).all()
39
40             active_players = DBSession.query(func.count(sa.distinct(PlayerGameStat.player_id))).\
41                 filter(PlayerGameStat.player_id > 2).\
42                 filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
43                 one()[0]
44         else:
45             games = DBSession.query(Game.game_type_cd, func.count()).\
46                 group_by(Game.game_type_cd).\
47                 order_by(expr.desc(func.count())).all()
48
49             active_players = DBSession.query(func.count(sa.distinct(PlayerGameStat.player_id))).\
50                 filter(PlayerGameStat.player_id > 2).\
51                 one()[0]
52
53         total_games = 0
54         for total in games:
55             total_games += total[1]
56
57         i = 1
58         other_games = 0
59         for total in games:
60             if i > 5:
61                 other_games += total[1]
62             i += 1
63
64         # don't send anything if we don't have any activity
65         if total_games == 0:
66             stat_line = None
67         else:
68         # This is ugly because we're doing template-like stuff within the
69         # view code. The alternative isn't any better, though: we would
70         # have to assemble the string inside the template by using a Python
71         # code block. For now I'll leave it like this since it is the lesser
72         # of two evils IMO.
73         # Also we need to hard-code the URL structure in here to allow caching,
74         # which also sucks.
75             in_paren = "; ".join(["{:2,d} {}".format(
76                 g[1],
77                 "<a href='/games?type={0}'>{0}</a>".format(g[0])
78             ) for g in games[:5]])
79
80             if len(games) > 5:
81                 in_paren += "; {:2,d} other".format(other_games)
82
83             stat_line = "{:2,d} active players and {:2,d} games ({})".format(
84                 active_players,
85                 total_games,
86                 in_paren
87             )
88
89     except Exception as e:
90         stat_line = None
91
92     return stat_line
93
94
95 @cache_region('hourly_term')
96 def get_ranks(game_type_cd):
97     """
98     Gets a set number of the top-ranked people for the specified game_type_cd.
99
100     The game_type_cd parameter is the type to fetch. Currently limited to
101     duel, dm, ctf, and tdm.
102     """
103     # how many ranks we want to fetch
104     leaderboard_count = 10
105
106     # only a few game modes are actually ranked
107     if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
108         return None
109
110     ranks = DBSession.query(PlayerRank).\
111             filter(PlayerRank.game_type_cd==game_type_cd).\
112             order_by(PlayerRank.rank).\
113             limit(leaderboard_count).all()
114
115     return ranks
116
117
118 def top_players_by_time_q(cutoff_days):
119     """
120     Query for the top players by the amount of time played during a date range.
121
122     Games older than cutoff_days days old are ignored.
123     """
124
125     # only games played during this range are considered
126     right_now = datetime.utcnow()
127     cutoff_dt = right_now - timedelta(days=cutoff_days)
128
129     top_players_q = DBSession.query(Player.player_id, Player.nick,
130             func.sum(PlayerGameStat.alivetime)).\
131             filter(Player.player_id == PlayerGameStat.player_id).\
132             filter(Player.player_id > 2).\
133             filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
134             order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
135             group_by(Player.nick).\
136             group_by(Player.player_id)
137
138     return top_players_q
139
140
141 @cache_region('hourly_term')
142 def get_top_players_by_time(cutoff_days):
143     """
144     The top players by the amount of time played during a date range.
145
146     Games older than cutoff_days days old are ignored.
147     """
148     # how many to retrieve
149     count = 10
150
151     # only games played during this range are considered
152     right_now = datetime.utcnow()
153     cutoff_dt = right_now - timedelta(days=cutoff_days)
154
155     top_players_q = top_players_by_time_q(cutoff_days)
156
157     top_players = top_players_q.limit(count).all()
158
159     top_players = [(player_id, html_colors(nick), score) \
160             for (player_id, nick, score) in top_players]
161
162     return top_players
163
164
165 def top_servers_by_players_q(cutoff_days):
166     """
167     Query to get the top servers by the amount of players active
168     during a date range.
169
170     Games older than cutoff_days days old are ignored.
171     """
172     # only games played during this range are considered
173     right_now = datetime.utcnow()
174     cutoff_dt = right_now - timedelta(days=cutoff_days)
175
176     top_servers_q = DBSession.query(Server.server_id, Server.name,
177         func.count()).\
178         filter(Game.server_id==Server.server_id).\
179         filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
180         order_by(expr.desc(func.count(Game.game_id))).\
181         group_by(Server.server_id).\
182         group_by(Server.name)
183
184     return top_servers_q
185
186
187 @cache_region('hourly_term')
188 def get_top_servers_by_players(cutoff_days):
189     """
190     The top servers by the amount of players active during a date range.
191
192     Games older than cutoff_days days old are ignored.
193     """
194     # how many to retrieve
195     count = 10
196
197     top_servers = top_servers_by_players_q(cutoff_days).limit(count).all()
198
199     return top_servers
200
201
202 def top_maps_by_times_played_q(cutoff_days):
203     """
204     Query to retrieve the top maps by the amount of times it was played
205     during a date range.
206
207     Games older than cutoff_days days old are ignored.
208     """
209     # only games played during this range are considered
210     right_now = datetime.utcnow()
211     cutoff_dt = right_now - timedelta(days=cutoff_days)
212
213     top_maps_q = DBSession.query(Game.map_id, Map.name,
214             func.count()).\
215             filter(Map.map_id==Game.map_id).\
216             filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
217             order_by(expr.desc(func.count())).\
218             group_by(Game.map_id).\
219             group_by(Map.name)
220
221     return top_maps_q
222
223
224 @cache_region('hourly_term')
225 def get_top_maps_by_times_played(cutoff_days):
226     """
227     The top maps by the amount of times it was played during a date range.
228
229     Games older than cutoff_days days old are ignored.
230     """
231     # how many to retrieve
232     count = 10
233
234     top_maps = top_maps_by_times_played_q(cutoff_days).limit(count).all()
235
236     return top_maps
237
238
239 def _main_index_data(request):
240     try:
241         leaderboard_lifetime = int(
242                 request.registry.settings['xonstat.leaderboard_lifetime'])
243     except:
244         leaderboard_lifetime = 30
245
246     leaderboard_count = 10
247     recent_games_count = 20
248
249     # summary statistics for the tagline
250     stat_line = get_summary_stats()
251     day_stat_line = get_summary_stats(1)
252
253
254     # the three top ranks tables
255     ranks = []
256     for gtc in ['duel', 'ctf', 'dm', 'tdm']:
257         rank = get_ranks(gtc)
258         if len(rank) != 0:
259             ranks.append(rank)
260
261     right_now = datetime.utcnow()
262     back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
263
264     # top players by playing time
265     top_players = get_top_players_by_time(leaderboard_lifetime)
266
267     # top servers by number of total players played
268     top_servers = get_top_servers_by_players(leaderboard_lifetime)
269
270     # top maps by total times played
271     top_maps = get_top_maps_by_times_played(leaderboard_lifetime)
272
273     # recent games played in descending order
274     rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
275     recent_games = [RecentGame(row) for row in rgs]
276
277     return {'top_players':top_players,
278             'top_servers':top_servers,
279             'top_maps':top_maps,
280             'recent_games':recent_games,
281             'ranks':ranks,
282             'stat_line':stat_line,
283             'day_stat_line':day_stat_line,
284             }
285
286
287 def main_index(request):
288     """
289     Display the main page information.
290     """
291     mainindex_data =  _main_index_data(request)
292
293     # FIXME: code clone, should get these from _main_index_data
294     leaderboard_count = 10
295     recent_games_count = 20
296
297     for i in range(leaderboard_count-len(mainindex_data['top_players'])):
298         mainindex_data['top_players'].append(('-', '-', '-'))
299
300     for i in range(leaderboard_count-len(mainindex_data['top_servers'])):
301         mainindex_data['top_servers'].append(('-', '-', '-'))
302
303     for i in range(leaderboard_count-len(mainindex_data['top_maps'])):
304         mainindex_data['top_maps'].append(('-', '-', '-'))
305
306     return mainindex_data
307
308
309 def main_index_json(request):
310     """
311     JSON output of the main page information.
312     """
313     return [{'status':'not implemented'}]
314
315
316 def top_players_by_time(request):
317     current_page = request.params.get('page', 1)
318
319     cutoff_days = int(request.registry.settings.\
320         get('xonstat.leaderboard_lifetime', 30))
321
322     top_players_q = top_players_by_time_q(cutoff_days)
323
324     top_players = Page(top_players_q, current_page, items_per_page=25, url=page_url)
325
326     top_players.items = [(player_id, html_colors(nick), score) \
327             for (player_id, nick, score) in top_players.items]
328
329     return {'top_players':top_players}
330
331
332 def top_servers_by_players(request):
333     current_page = request.params.get('page', 1)
334
335     cutoff_days = int(request.registry.settings.\
336         get('xonstat.leaderboard_lifetime', 30))
337
338     top_servers_q = top_servers_by_players_q(cutoff_days)
339
340     top_servers = Page(top_servers_q, current_page, items_per_page=25, url=page_url)
341
342     return {'top_servers':top_servers}
343
344
345 def top_maps_by_times_played(request):
346     current_page = request.params.get('page', 1)
347
348     cutoff_days = int(request.registry.settings.\
349         get('xonstat.leaderboard_lifetime', 30))
350
351     top_maps_q = top_maps_by_times_played_q(cutoff_days)
352
353     top_maps = Page(top_maps_q, current_page, items_per_page=25, url=page_url)
354
355     return {'top_maps':top_maps}