Refactor the map_index views.
[xonotic/xonstat.git] / xonstat / views / map.py
1 import logging
2 from collections import namedtuple
3 from datetime import datetime, timedelta
4
5 import sqlalchemy.sql.expression as expr
6 import sqlalchemy.sql.functions as func
7 from pyramid.httpexceptions import HTTPNotFound
8 from webhelpers.paginate import Page
9 from xonstat.models import DBSession, Server, Map, Game, PlayerGameStat, Player, PlayerCaptime
10 from xonstat.models.map import MapCapTime
11 from xonstat.util import page_url, html_colors
12 from xonstat.views.helpers import RecentGame, recent_games_q
13
14 log = logging.getLogger(__name__)
15
16 # Defaults
17 INDEX_COUNT = 20
18
19
20 class MapIndex(object):
21     """Returns a list of maps."""
22
23     def __init__(self, request):
24         """Common parameter parsing."""
25         self.request = request
26         self.page = request.params.get("page", 1)
27
28         # all views share this data, so we'll precalculate
29         self.maps = self.map_index()
30
31     def map_index(self):
32         """Returns the raw data shared by all renderers."""
33         try:
34             map_q = DBSession.query(Map).order_by(Map.map_id.desc())
35             maps = Page(map_q, self.page, items_per_page=INDEX_COUNT, url=page_url)
36
37         except Exception as e:
38             log.debug(e)
39             raise HTTPNotFound
40
41         return maps
42
43     def html(self):
44         """For rendering this data using something HTML-based."""
45         return {
46             'maps': self.maps,
47         }
48
49     def json(self):
50         """For rendering this data using JSON."""
51         return {
52             'maps': [m.to_dict() for m in self.maps],
53         }
54
55
56 def _map_info_data(request):
57     map_id = int(request.matchdict['id'])
58
59     try:
60         leaderboard_lifetime = int(
61                 request.registry.settings['xonstat.leaderboard_lifetime'])
62     except:
63         leaderboard_lifetime = 30
64
65     leaderboard_count = 10
66     recent_games_count = 20
67
68     # captime tuples
69     Captime = namedtuple('Captime', ['player_id', 'nick_html_colors',
70         'fastest_cap', 'game_id'])
71
72     try:
73         gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
74
75         # recent games played in descending order
76         rgs = recent_games_q(map_id=map_id).limit(recent_games_count).all()
77         recent_games = [RecentGame(row) for row in rgs]
78
79         # top players by score
80         top_scorers = DBSession.query(Player.player_id, Player.nick,
81                 func.sum(PlayerGameStat.score)).\
82                 filter(Player.player_id == PlayerGameStat.player_id).\
83                 filter(Game.game_id == PlayerGameStat.game_id).\
84                 filter(Game.map_id == map_id).\
85                 filter(Player.player_id > 2).\
86                 filter(PlayerGameStat.create_dt >
87                         (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
88                 order_by(expr.desc(func.sum(PlayerGameStat.score))).\
89                 group_by(Player.nick).\
90                 group_by(Player.player_id).all()[0:leaderboard_count]
91
92         top_scorers = [(player_id, html_colors(nick), score) \
93                 for (player_id, nick, score) in top_scorers]
94
95         # top players by playing time
96         top_players = DBSession.query(Player.player_id, Player.nick,
97                 func.sum(PlayerGameStat.alivetime)).\
98                 filter(Player.player_id == PlayerGameStat.player_id).\
99                 filter(Game.game_id == PlayerGameStat.game_id).\
100                 filter(Game.map_id == map_id).\
101                 filter(Player.player_id > 2).\
102                 filter(PlayerGameStat.create_dt >
103                         (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
104                 order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
105                 group_by(Player.nick).\
106                 group_by(Player.player_id).all()[0:leaderboard_count]
107
108         top_players = [(player_id, html_colors(nick), score) \
109                 for (player_id, nick, score) in top_players]
110
111         # top servers using/playing this map
112         top_servers = DBSession.query(Server.server_id, Server.name,
113                 func.count(Game.game_id)).\
114                 filter(Game.server_id == Server.server_id).\
115                 filter(Game.map_id == map_id).\
116                 filter(Game.create_dt >
117                         (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
118                 order_by(expr.desc(func.count(Game.game_id))).\
119                 group_by(Server.name).\
120                 group_by(Server.server_id).all()[0:leaderboard_count]
121
122         # TODO make this a configuration parameter to be set in the settings
123         # top captimes
124         captimes_raw = DBSession.query(Player.player_id, Player.nick,
125             PlayerCaptime.fastest_cap, PlayerCaptime.game_id).\
126                 filter(PlayerCaptime.map_id == map_id).\
127                 filter(Player.player_id == PlayerCaptime.player_id).\
128                 order_by(PlayerCaptime.fastest_cap).\
129                 limit(10).\
130                 all()
131
132         captimes = [Captime(c.player_id, html_colors(c.nick),
133             c.fastest_cap, c.game_id) for c in captimes_raw]
134
135     except Exception as e:
136         gmap = None
137     return {'gmap':gmap,
138             'recent_games':recent_games,
139             'top_scorers':top_scorers,
140             'top_players':top_players,
141             'top_servers':top_servers,
142             'captimes':captimes,
143             }
144
145
146 def map_info(request):
147     """
148     List the information stored about a given map.
149     """
150     mapinfo_data =  _map_info_data(request)
151
152     # FIXME: code clone, should get these from _map_info_data
153     leaderboard_count = 10
154     recent_games_count = 20
155
156     for i in range(leaderboard_count-len(mapinfo_data['top_scorers'])):
157         mapinfo_data['top_scorers'].append(('-', '-', '-'))
158
159     for i in range(leaderboard_count-len(mapinfo_data['top_players'])):
160         mapinfo_data['top_players'].append(('-', '-', '-'))
161
162     for i in range(leaderboard_count-len(mapinfo_data['top_servers'])):
163         mapinfo_data['top_servers'].append(('-', '-', '-'))
164
165     return mapinfo_data
166
167
168 def map_info_json(request):
169     """
170     List the information stored about a given map. JSON.
171     """
172     return [{'status':'not implemented'}]
173
174
175 def map_captimes_data(request):
176     map_id = int(request.matchdict['id'])
177
178     current_page = request.params.get('page', 1)
179
180     try:
181         mmap = DBSession.query(Map).filter_by(map_id=map_id).one()
182
183         mct_q = DBSession.query(PlayerCaptime.fastest_cap, PlayerCaptime.create_dt,
184                 PlayerCaptime.player_id, PlayerCaptime.game_id,
185                 Game.server_id, Server.name.label('server_name'),
186                 PlayerGameStat.nick.label('player_nick')).\
187                 filter(PlayerCaptime.map_id==map_id).\
188                 filter(PlayerCaptime.game_id==Game.game_id).\
189                 filter(PlayerCaptime.map_id==Map.map_id).\
190                 filter(Game.server_id==Server.server_id).\
191                 filter(PlayerCaptime.player_id==PlayerGameStat.player_id).\
192                 filter(PlayerCaptime.game_id==PlayerGameStat.game_id).\
193                 order_by(expr.asc(PlayerCaptime.fastest_cap))
194
195     except Exception as e:
196         raise httpexceptions.HTTPNotFound
197
198     map_captimes = Page(mct_q, current_page, items_per_page=20, url=page_url)
199
200     map_captimes.items = [MapCapTime(row) for row in map_captimes.items]
201
202     return {
203             'map_id':map_id,
204             'map':mmap,
205             'captimes':map_captimes,
206         }
207
208 def map_captimes(request):
209     return map_captimes_data(request)
210
211 def map_captimes_json(request):
212     current_page = request.params.get('page', 1)
213     data = map_captimes_data(request)
214
215     return {
216             "map": data["map"].to_dict(),
217             "captimes": [e.to_dict() for e in data["captimes"].items],
218             "page": current_page,
219             }