]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/main.py
Standardize the structure of both summary stat lines.
[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(request, 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             in_paren = ", ".join(["{} {}".format(
74                 g[1],
75                 "<a href='{}'>{}</a>".format(
76                     request.route_url("game_index", _query={'type':g[0]}),
77                     g[0]
78                 )
79             ) for g in games[:5]])
80
81             if len(games) > 5:
82                 in_paren += ", {} other".format(other_games)
83
84             stat_line = "{} active players and {} games ({})".format(
85                 active_players,
86                 total_games,
87                 in_paren
88             )
89
90     except Exception as e:
91         stat_line = None
92
93     return stat_line
94
95
96 @cache_region('hourly_term')
97 def get_ranks(game_type_cd):
98     """
99     Gets a set number of the top-ranked people for the specified game_type_cd.
100
101     The game_type_cd parameter is the type to fetch. Currently limited to
102     duel, dm, ctf, and tdm.
103     """
104     # how many ranks we want to fetch
105     leaderboard_count = 10
106
107     # only a few game modes are actually ranked
108     if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
109         return None
110
111     ranks = DBSession.query(PlayerRank).\
112             filter(PlayerRank.game_type_cd==game_type_cd).\
113             order_by(PlayerRank.rank).\
114             limit(leaderboard_count).all()
115
116     return ranks
117
118
119 def top_players_by_time_q(cutoff_days):
120     """
121     Query for the top players by the amount of time played during a date range.
122
123     Games older than cutoff_days days old are ignored.
124     """
125
126     # only games played during this range are considered
127     right_now = datetime.utcnow()
128     cutoff_dt = right_now - timedelta(days=cutoff_days)
129
130     top_players_q = DBSession.query(Player.player_id, Player.nick,
131             func.sum(PlayerGameStat.alivetime)).\
132             filter(Player.player_id == PlayerGameStat.player_id).\
133             filter(Player.player_id > 2).\
134             filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
135             order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
136             group_by(Player.nick).\
137             group_by(Player.player_id)
138
139     return top_players_q
140
141
142 @cache_region('hourly_term')
143 def get_top_players_by_time(cutoff_days):
144     """
145     The top players by the amount of time played during a date range.
146
147     Games older than cutoff_days days old are ignored.
148     """
149     # how many to retrieve
150     count = 10
151
152     # only games played during this range are considered
153     right_now = datetime.utcnow()
154     cutoff_dt = right_now - timedelta(days=cutoff_days)
155
156     top_players_q = top_players_by_time_q(cutoff_days)
157
158     top_players = top_players_q.limit(count).all()
159
160     top_players = [(player_id, html_colors(nick), score) \
161             for (player_id, nick, score) in top_players]
162
163     return top_players
164
165
166 def top_servers_by_players_q(cutoff_days):
167     """
168     Query to get the top servers by the amount of players active
169     during a date range.
170
171     Games older than cutoff_days days old are ignored.
172     """
173     # only games played during this range are considered
174     right_now = datetime.utcnow()
175     cutoff_dt = right_now - timedelta(days=cutoff_days)
176
177     top_servers_q = DBSession.query(Server.server_id, Server.name,
178         func.count()).\
179         filter(Game.server_id==Server.server_id).\
180         filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
181         order_by(expr.desc(func.count(Game.game_id))).\
182         group_by(Server.server_id).\
183         group_by(Server.name)
184
185     return top_servers_q
186
187
188 @cache_region('hourly_term')
189 def get_top_servers_by_players(cutoff_days):
190     """
191     The top servers by the amount of players active during a date range.
192
193     Games older than cutoff_days days old are ignored.
194     """
195     # how many to retrieve
196     count = 10
197
198     top_servers = top_servers_by_players_q(cutoff_days).limit(count).all()
199
200     return top_servers
201
202
203 def top_maps_by_times_played_q(cutoff_days):
204     """
205     Query to retrieve the top maps by the amount of times it was played
206     during a date range.
207
208     Games older than cutoff_days days old are ignored.
209     """
210     # only games played during this range are considered
211     right_now = datetime.utcnow()
212     cutoff_dt = right_now - timedelta(days=cutoff_days)
213
214     top_maps_q = DBSession.query(Game.map_id, Map.name,
215             func.count()).\
216             filter(Map.map_id==Game.map_id).\
217             filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
218             order_by(expr.desc(func.count())).\
219             group_by(Game.map_id).\
220             group_by(Map.name)
221
222     return top_maps_q
223
224
225 @cache_region('hourly_term')
226 def get_top_maps_by_times_played(cutoff_days):
227     """
228     The top maps by the amount of times it was played during a date range.
229
230     Games older than cutoff_days days old are ignored.
231     """
232     # how many to retrieve
233     count = 10
234
235     top_maps = top_maps_by_times_played_q(cutoff_days).limit(count).all()
236
237     return top_maps
238
239
240 def _main_index_data(request):
241     try:
242         leaderboard_lifetime = int(
243                 request.registry.settings['xonstat.leaderboard_lifetime'])
244     except:
245         leaderboard_lifetime = 30
246
247     leaderboard_count = 10
248     recent_games_count = 20
249
250     # summary statistics for the tagline
251     stat_line = get_summary_stats(request, None)
252     day_stat_line = get_summary_stats(request, 7)
253
254
255     # the three top ranks tables
256     ranks = []
257     for gtc in ['duel', 'ctf', 'dm', 'tdm']:
258         rank = get_ranks(gtc)
259         if len(rank) != 0:
260             ranks.append(rank)
261
262     right_now = datetime.utcnow()
263     back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
264
265     # top players by playing time
266     top_players = get_top_players_by_time(leaderboard_lifetime)
267
268     # top servers by number of total players played
269     top_servers = get_top_servers_by_players(leaderboard_lifetime)
270
271     # top maps by total times played
272     top_maps = get_top_maps_by_times_played(leaderboard_lifetime)
273
274     # recent games played in descending order
275     rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
276     recent_games = [RecentGame(row) for row in rgs]
277
278     return {'top_players':top_players,
279             'top_servers':top_servers,
280             'top_maps':top_maps,
281             'recent_games':recent_games,
282             'ranks':ranks,
283             'stat_line':stat_line,
284             'day_stat_line':day_stat_line,
285             }
286
287
288 def main_index(request):
289     """
290     Display the main page information.
291     """
292     mainindex_data =  _main_index_data(request)
293
294     # FIXME: code clone, should get these from _main_index_data
295     leaderboard_count = 10
296     recent_games_count = 20
297
298     for i in range(leaderboard_count-len(mainindex_data['top_players'])):
299         mainindex_data['top_players'].append(('-', '-', '-'))
300
301     for i in range(leaderboard_count-len(mainindex_data['top_servers'])):
302         mainindex_data['top_servers'].append(('-', '-', '-'))
303
304     for i in range(leaderboard_count-len(mainindex_data['top_maps'])):
305         mainindex_data['top_maps'].append(('-', '-', '-'))
306
307     return mainindex_data
308
309
310 def main_index_json(request):
311     """
312     JSON output of the main page information.
313     """
314     return [{'status':'not implemented'}]
315
316
317 def top_players_by_time(request):
318     current_page = request.params.get('page', 1)
319
320     cutoff_days = int(request.registry.settings.\
321         get('xonstat.leaderboard_lifetime', 30))
322
323     top_players_q = top_players_by_time_q(cutoff_days)
324
325     top_players = Page(top_players_q, current_page, items_per_page=25, url=page_url)
326
327     top_players.items = [(player_id, html_colors(nick), score) \
328             for (player_id, nick, score) in top_players.items]
329
330     return {'top_players':top_players}
331
332
333 def top_servers_by_players(request):
334     current_page = request.params.get('page', 1)
335
336     cutoff_days = int(request.registry.settings.\
337         get('xonstat.leaderboard_lifetime', 30))
338
339     top_servers_q = top_servers_by_players_q(cutoff_days)
340
341     top_servers = Page(top_servers_q, current_page, items_per_page=25, url=page_url)
342
343     return {'top_servers':top_servers}
344
345
346 def top_maps_by_times_played(request):
347     current_page = request.params.get('page', 1)
348
349     cutoff_days = int(request.registry.settings.\
350         get('xonstat.leaderboard_lifetime', 30))
351
352     top_maps_q = top_maps_by_times_played_q(cutoff_days)
353
354     top_maps = Page(top_maps_q, current_page, items_per_page=25, url=page_url)
355
356     return {'top_maps':top_maps}