]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/main.py
Add separate views for the top servers on a map.
[xonotic/xonstat.git] / xonstat / views / main.py
1 import logging
2 from datetime import datetime, timedelta
3
4 from beaker.cache import cache_region
5 from xonstat.models import DBSession, PlayerRank, ActivePlayer, ActiveServer, ActiveMap
6 from xonstat.views.helpers import RecentGame, recent_games_q
7
8 log = logging.getLogger(__name__)
9
10
11 @cache_region('hourly_term')
12 def get_summary_stats(scope="all"):
13     """
14     Gets the following aggregate statistics according to the provided scope:
15
16         - the number of active players
17         - the number of games per game type
18
19     Scope can be "all" or "day".
20
21     The fetched information is summarized into a string which is passed
22     directly to the template.
23     """
24     if scope not in ["all", "day"]:
25         scope = "all"
26
27     try:
28         ss = DBSession.query("num_players", "game_type_cd", "num_games").\
29                 from_statement(
30                         "SELECT num_players, game_type_cd, num_games "
31                         "FROM summary_stats_mv "
32                         "WHERE scope = :scope "
33                         "ORDER BY sort_order "
34                 ).params(scope=scope).all()
35
36         i = 1
37         total_games = 0
38         other_games = 0
39         for row in ss:
40             # the number of players is constant in each row
41             total_players = row.num_players
42
43             total_games += row.num_games
44
45             # we can't show all game types on the single summary line, so any
46             # past the fifth one will get bundled in to an "other" count
47             if i > 5:
48                 other_games += row.num_games
49
50             i += 1
51
52         # don't send anything if we don't have any activity
53         if total_games == 0:
54             stat_line = None
55         else:
56         # This is ugly because we're doing template-like stuff within the
57         # view code. The alternative isn't any better, though: we would
58         # have to assemble the string inside the template by using a Python
59         # code block. For now I'll leave it like this since it is the lesser
60         # of two evils IMO.
61         # Also we need to hard-code the URL structure in here to allow caching,
62         # which also sucks.
63             in_paren = "; ".join(["{:2,d} {}".format(
64                 g.num_games,
65                 "<a href='/games?type={0}'>{0}</a>".format(g.game_type_cd)
66             ) for g in ss[:5]])
67
68             if other_games > 0:
69                 in_paren += "; {:2,d} other".format(other_games)
70
71             stat_line = "{:2,d} players and {:2,d} games ({})".format(
72                 total_players,
73                 total_games,
74                 in_paren
75             )
76
77     except Exception as e:
78         stat_line = None
79         raise e
80
81     return stat_line
82
83
84 @cache_region('hourly_term')
85 def get_ranks(game_type_cd):
86     """
87     Gets a set number of the top-ranked people for the specified game_type_cd.
88
89     The game_type_cd parameter is the type to fetch. Currently limited to
90     duel, dm, ctf, and tdm.
91     """
92     # how many ranks we want to fetch
93     leaderboard_count = 10
94
95     # only a few game modes are actually ranked
96     if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
97         return None
98
99     ranks = DBSession.query(PlayerRank).\
100             filter(PlayerRank.game_type_cd==game_type_cd).\
101             order_by(PlayerRank.rank).\
102             limit(leaderboard_count).all()
103
104     return ranks
105
106
107 @cache_region('hourly_term')
108 def get_top_players_by_time(limit=None, start=None):
109     """
110     The top players by the amount of time played during a date range.
111     """
112     q = DBSession.query(ActivePlayer)
113
114     if start is not None:
115         q = q.filter(ActivePlayer.sort_order >= start)
116
117     q = q.order_by(ActivePlayer.sort_order)
118
119     if limit is not None:
120         q = q.limit(limit)
121
122     return q.all()
123
124
125 @cache_region('hourly_term')
126 def get_top_servers_by_games(limit=None, start=None):
127     """
128     The top servers by the number of games played during a date range.
129     """
130     q = DBSession.query(ActiveServer)
131
132     if start is not None:
133         q = q.filter(ActiveServer.sort_order >= start)
134
135     q = q.order_by(ActiveServer.sort_order)
136
137     if limit is not None:
138         q = q.limit(limit)
139
140     return q.all()
141
142
143 @cache_region('hourly_term')
144 def get_top_maps_by_games(limit=None, start=None):
145     """
146     The top maps by the number of games played during a date range.
147     """
148     q = DBSession.query(ActiveMap)
149
150     if start is not None:
151         q = q.filter(ActiveMap.sort_order >= start)
152
153     q = q.order_by(ActiveMap.sort_order)
154
155     if limit is not None:
156         q = q.limit(limit)
157
158     return q.all()
159
160
161 def _main_index_data(request):
162     try:
163         leaderboard_lifetime = int(
164                 request.registry.settings['xonstat.leaderboard_lifetime'])
165     except:
166         leaderboard_lifetime = 30
167
168     leaderboard_count = 10
169     recent_games_count = 20
170
171     # summary statistics for the tagline
172     stat_line = get_summary_stats("all")
173     day_stat_line = get_summary_stats("day")
174
175
176     # the three top ranks tables
177     ranks = []
178     for gtc in ['duel', 'ctf', 'dm', 'tdm']:
179         rank = get_ranks(gtc)
180         if len(rank) != 0:
181             ranks.append(rank)
182
183     right_now = datetime.utcnow()
184     back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
185
186     # top players by playing time
187     top_players = get_top_players_by_time(10)
188
189     # top servers by number of games
190     top_servers = get_top_servers_by_games(10)
191
192     # top maps by total times played
193     top_maps = get_top_maps_by_games(10)
194
195     # recent games played in descending order
196     rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
197     recent_games = [RecentGame(row) for row in rgs]
198
199     return {'top_players':top_players,
200             'top_servers':top_servers,
201             'top_maps':top_maps,
202             'recent_games':recent_games,
203             'ranks':ranks,
204             'stat_line':stat_line,
205             'day_stat_line':day_stat_line,
206             }
207
208
209 def main_index(request):
210     """
211     Display the main page information.
212     """
213     return _main_index_data(request)
214
215
216 def main_index_json(request):
217     """
218     JSON output of the main page information.
219     """
220     return [{'status':'not implemented'}]
221
222
223 def top_players_index(request):
224     try:
225         start = int(request.params.get('start', None))
226     except:
227         start = None
228
229     top_players = get_top_players_by_time(20, start)
230
231     # building a query string
232     query = {}
233     if len(top_players) > 1:
234         query['start'] = top_players[-1].sort_order + 1
235
236     return {
237             'top_players':top_players,
238             'query':query,
239             'start':start,
240             }
241
242
243 def top_servers_index(request):
244     try:
245         start = int(request.params.get('start', None))
246     except:
247         start = None
248
249     top_servers = get_top_servers_by_games(20, start)
250
251     # building a query string
252     query = {}
253     if len(top_servers) > 1:
254         query['start'] = top_servers[-1].sort_order + 1
255
256     return {
257             'top_servers':top_servers,
258             'query':query,
259             'start':start,
260             }
261
262
263 def top_maps_index(request):
264     try:
265         start = int(request.params.get('start', None))
266     except:
267         start = None
268
269     top_maps = get_top_maps_by_games(20, start)
270
271     # building a query string
272     query = {}
273     if len(top_maps) > 1:
274         query['start'] = top_maps[-1].sort_order + 1
275
276     return {
277             'top_maps':top_maps,
278             'query':query,
279             'start':start,
280             }