config.add_view(view=MapIndex, route_name="map_index", attr="json", renderer="json",
accept="application/json")
+ config.add_route("map_top_scorers", "/map/{id:\d+}/topscorers")
+ config.add_view(view=MapTopScorers, route_name="map_top_scorers", attr="html",
+ renderer="map_top_scorers.mako", accept="text/html")
+ config.add_view(view=MapTopScorers, route_name="map_top_scorers", attr="json",
+ renderer="json", accept="application/json")
+
config.add_route("map_info", "/map/{id:\d+}")
config.add_view(map_info, route_name="map_info", renderer="map_info.mako")
--- /dev/null
+<%inherit file="base.mako"/>
+<%namespace name="nav" file="nav.mako" />
+
+<%block name="navigation">
+ ${nav.nav('maps')}
+</%block>
+
+<%block name="title">
+ Map Top Scorer Index
+</%block>
+
+% if not top_scorers and last is not None:
+ <h2 class="text-center">Sorry, no more maps!</h2>
+
+% elif not top_scorers and last is None:
+ <h2 class="text-center">No maps found. Yikes, get playing!</h2>
+
+% else:
+ <div class="row">
+ <div class="small-12 large-6 large-offset-3 columns">
+ <table class="table-hover table-condensed">
+ <thead>
+ <tr>
+ <th class="small-2">#</th>
+ <th class="small-7">Nick</th>
+ <th class="small-3">Score</th>
+ </tr>
+ </thead>
+ <tbody>
+ % for ts in top_scorers:
+ <tr>
+ <td>${ts.rank}</td>
+ <td class="no-stretch"><a href="${request.route_url('player_info', id=ts.player_id)}" title="Go to the player info page for this player">${ts.nick|n}</a></td>
+ <td>${ts.total_score}</td>
+ </tr>
+ % endfor
+ </tbody>
+ </table>
+ <p class="text-center"><small>Note: these figures are from the past ${lifetime} days</small></p>
+ </div>
+ </div>
+
+ % if len(top_scorers) == 20:
+ <div class="row">
+ <div class="small-12 large-6 large-offset-3 columns">
+ <ul class="pagination">
+ <li>
+ <a href="${request.route_url('map_top_scorers', id=map_id, _query=query)}" name="Next Page">Next <i class="fa fa-arrow-right"></i></a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ % endif
+
+% endif
import sqlalchemy.sql.expression as expr
import sqlalchemy.sql.functions as func
from pyramid.httpexceptions import HTTPNotFound
+from sqlalchemy import func as fg
from webhelpers.paginate import Page
from xonstat.models import DBSession, Server, Map, Game, PlayerGameStat, Player, PlayerCaptime
from xonstat.models.map import MapCapTime
# Defaults
INDEX_COUNT = 20
+LEADERBOARD_LIFETIME = 30
class MapIndex(object):
}
+class MapInfoBase(object):
+ """Base class for all map-based views with a map_id parameter in them."""
+
+ def __init__(self, request, limit=None, last=None):
+ """Common parameter parsing."""
+ self.request = request
+ self.map_id = request.matchdict.get("id", None)
+
+ raw_lifetime = request.registry.settings.get('xonstat.leaderboard_lifetime',
+ LEADERBOARD_LIFETIME)
+ self.lifetime = int(raw_lifetime)
+
+ self.limit = request.params.get("limit", limit)
+ self.last = request.params.get("last", last)
+ self.now = datetime.utcnow()
+
+
+class MapTopScorers(MapInfoBase):
+ """Returns the top scorers on a given map."""
+
+ def __init__(self, request, limit=INDEX_COUNT, last=None):
+ """Common parameter parsing."""
+ super(MapTopScorers, self).__init__(request, limit, last)
+ self.top_scorers = self.get_top_scorers()
+
+ def get_top_scorers(self):
+ """Top players by score. Shared by all renderers."""
+ cutoff = self.now - timedelta(days=self.lifetime)
+ cutoff = self.now - timedelta(days=120)
+
+ top_scorers_q = DBSession.query(
+ fg.row_number().over(order_by=expr.desc(func.sum(PlayerGameStat.score))).label("rank"),
+ Player.player_id, Player.nick, func.sum(PlayerGameStat.score).label("total_score"))\
+ .filter(Player.player_id == PlayerGameStat.player_id)\
+ .filter(Game.game_id == PlayerGameStat.game_id)\
+ .filter(Game.map_id == self.map_id)\
+ .filter(Player.player_id > 2)\
+ .filter(PlayerGameStat.create_dt > cutoff)\
+ .order_by(expr.desc(func.sum(PlayerGameStat.score)))\
+ .group_by(Player.nick)\
+ .group_by(Player.player_id)
+
+ if self.last:
+ top_scorers_q = top_scorers_q.offset(self.last)
+
+ if self.limit:
+ top_scorers_q = top_scorers_q.limit(self.limit)
+
+ top_scorers = top_scorers_q.all()
+
+ return top_scorers
+
+ def html(self):
+ """Returns an HTML-ready representation."""
+ TopScorer = namedtuple("TopScorer", ["rank", "player_id", "nick", "total_score"])
+
+ top_scorers = [TopScorer(ts.rank, ts.player_id, html_colors(ts.nick), ts.total_score)
+ for ts in self.top_scorers]
+
+ # build the query string
+ query = {}
+ if len(top_scorers) > 1:
+ query['last'] = top_scorers[-1].rank
+
+ return {
+ "map_id": self.map_id,
+ "top_scorers": top_scorers,
+ "lifetime": self.lifetime,
+ "query": query,
+ }
+
+ def json(self):
+ """For rendering this data using JSON."""
+ top_scorers = [{
+ "rank": ts.rank,
+ "player_id": ts.player_id,
+ "nick": ts.nick,
+ "score": ts.total_score,
+ } for ts in self.top_scorers]
+
+ return {
+ "map_id": self.map_id,
+ "top_scorers": top_scorers,
+ }
+
+
def _map_info_data(request):
map_id = int(request.matchdict['id'])