add scoreboard freeze (#208)

* add scoreboard freeze

* delete excess div close tag

* filter out scores from team page when scoreboard freezes

* allow teams to see their full score and solves in team page

* fix unset place and score

* change parameter and filter out /solves for graph

* fix utils methods undefined

* add small notice about frozen scoreboard and resolve failing tests

* Update __init__.py

* Update scoreboard.py
selenium-screenshot-testing
Rakha Kanz Kautsar 2017-03-25 12:37:37 +07:00 committed by Kevin Chung
parent a3a7d75ae8
commit 9a9b775e57
10 changed files with 250 additions and 78 deletions

View File

@ -54,10 +54,13 @@ def admin_config():
if request.method == "POST":
start = None
end = None
freeze = None
if request.form.get('start'):
start = int(request.form['start'])
if request.form.get('end'):
end = int(request.form['end'])
if request.form.get('freeze'):
freeze = int(request.form['freeze'])
try:
view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
@ -103,6 +106,8 @@ def admin_config():
mg_base_url = utils.set_config("mg_base_url", request.form.get('mg_base_url', None))
mg_api_key = utils.set_config("mg_api_key", request.form.get('mg_api_key', None))
db_freeze = utils.set_config("freeze", freeze)
db_start = Config.query.filter_by(key='start').first()
db_start.value = start
@ -136,6 +141,7 @@ def admin_config():
view_after_ctf = utils.get_config('view_after_ctf')
start = utils.get_config('start')
end = utils.get_config('end')
freeze = utils.get_config('freeze')
mail_tls = utils.get_config('mail_tls')
mail_ssl = utils.get_config('mail_ssl')
@ -157,6 +163,7 @@ def admin_config():
ctf_theme_config=ctf_theme,
start=start,
end=end,
freeze=freeze,
hide_scores=hide_scores,
mail_server=mail_server,
mail_port=mail_port,

View File

@ -39,8 +39,8 @@ def admin_team(teamid):
.order_by(last_seen.desc()).all()
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.asc()).all()
awards = Awards.query.filter_by(teamid=teamid).order_by(Awards.date.asc()).all()
score = user.score()
place = user.place()
score = user.score(admin=True)
place = user.place(admin=True)
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
place=place, wrong_keys=wrong_keys, awards=awards)
elif request.method == 'POST':

View File

@ -111,8 +111,18 @@ def solves(teamid=None):
else:
return redirect(url_for('auth.login', next='solves'))
else:
solves = Solves.query.filter_by(teamid=teamid).all()
awards = Awards.query.filter_by(teamid=teamid).all()
solves = Solves.query.filter_by(teamid=teamid)
awards = Awards.query.filter_by(teamid=teamid)
freeze = utils.get_config('freeze')
if freeze:
freeze = utils.unix_time_to_utc(freeze)
if teamid != session.get('id'):
solves = solves.filter(Solves.date < freeze)
awards = awards.filter(Awards.date < freeze)
solves = solves.all()
awards = awards.all()
db.session.close()
json = {'solves': []}
for solve in solves:

View File

@ -159,20 +159,42 @@ class Teams(db.Model):
def __repr__(self):
return '<team %r>' % self.name
def score(self):
def score(self, admin=False):
score = db.func.sum(Challenges.value).label('score')
team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.banned == False, Teams.id == self.id).group_by(Solves.teamid).first()
team = db.session.query(Solves.teamid, score).join(Teams).join(Challenges).filter(Teams.banned == False, Teams.id == self.id)
award_score = db.func.sum(Awards.value).label('award_score')
award = db.session.query(award_score).filter_by(teamid=self.id).first()
award = db.session.query(award_score).filter_by(teamid=self.id)
if not admin:
freeze = Config.query.filter_by(key='freeze').first()
if freeze and freeze.value:
freeze = int(freeze.value)
freeze = datetime.datetime.utcfromtimestamp(freeze)
team = team.filter(Solves.date < freeze)
award = award.filter(Awards.date < freeze)
team = team.group_by(Solves.teamid).first()
award = award.first()
if team:
return int(team.score or 0) + int(award.award_score or 0)
else:
return 0
def place(self):
def place(self, admin=False):
score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid).join(Teams).join(Challenges).filter(Teams.banned == False).group_by(Solves.teamid).order_by(score.desc(), quickest).all()
teams = db.session.query(Solves.teamid).join(Teams).join(Challenges).filter(Teams.banned == False)
if not admin:
freeze = Config.query.filter_by(key='freeze').first()
if freeze and freeze.value:
freeze = int(freeze.value)
freeze = datetime.datetime.utcfromtimestamp(freeze)
teams = teams.filter(Solves.date < freeze)
teams = teams.group_by(Solves.teamid).order_by(score.desc(), quickest).all()
# http://codegolf.stackexchange.com/a/4712
try:
i = teams.index((self.id,)) + 1

View File

@ -9,23 +9,49 @@ scoreboard = Blueprint('scoreboard', __name__)
def get_standings(admin=False, count=None):
score = db.func.sum(Challenges.value).label('score')
date = db.func.max(Solves.date).label('date')
scores = db.session.query(Solves.teamid.label('teamid'), score, date).join(Challenges).group_by(Solves.teamid)
awards = db.session.query(Awards.teamid.label('teamid'), db.func.sum(Awards.value).label('score'), db.func.max(Awards.date).label('date')) \
.group_by(Awards.teamid)
scores = db.session.query(
Solves.teamid.label('teamid'),
db.func.sum(Challenges.value).label('score'),
db.func.max(Solves.date).label('date')
).join(Challenges).group_by(Solves.teamid)
awards = db.session.query(
Awards.teamid.label('teamid'),
db.func.sum(Awards.value).label('score'),
db.func.max(Awards.date).label('date')
).group_by(Awards.teamid)
freeze = utils.get_config('freeze')
if not admin and freeze:
scores = scores.filter(Solves.date < utils.unix_time_to_utc(freeze))
awards = awards.filter(Awards.date < utils.unix_time_to_utc(freeze))
results = union_all(scores, awards).alias('results')
sumscores = db.session.query(results.columns.teamid, db.func.sum(results.columns.score).label('score'), db.func.max(results.columns.date).label('date')) \
.group_by(results.columns.teamid).subquery()
sumscores = db.session.query(
results.columns.teamid,
db.func.sum(results.columns.score).label('score'),
db.func.max(results.columns.date).label('date')
).group_by(results.columns.teamid).subquery()
if admin:
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), Teams.banned, sumscores.columns.score) \
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
standings_query = db.session.query(
Teams.id.label('teamid'),
Teams.name.label('name'),
Teams.banned, sumscores.columns.score
)\
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
else:
standings_query = db.session.query(Teams.id.label('teamid'), Teams.name.label('name'), sumscores.columns.score) \
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.filter(Teams.banned == False) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
standings_query = db.session.query(
Teams.id.label('teamid'),
Teams.name.label('name'),
sumscores.columns.score
)\
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.filter(Teams.banned == False) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.date)
if count is None:
standings = standings_query.all()
else:
@ -41,7 +67,7 @@ def scoreboard_view():
if utils.hide_scores():
return render_template('scoreboard.html', errors=['Scores are currently hidden'])
standings = get_standings()
return render_template('scoreboard.html', teams=standings)
return render_template('scoreboard.html', teams=standings, score_frozen=utils.is_scoreboard_frozen())
@scoreboard.route('/scores')
@ -73,8 +99,20 @@ def topteams(count):
standings = get_standings(count=count)
for team in standings:
solves = Solves.query.filter_by(teamid=team.teamid).all()
awards = Awards.query.filter_by(teamid=team.teamid).all()
solves = Solves.query.filter_by(teamid=team.teamid)
awards = Awards.query.filter_by(teamid=team.teamid)
freeze = utils.get_config('freeze')
if freeze:
solves = solves.filter(Solves.date < utils.unix_time_to_utc(freeze))
awards = awards.filter(Awards.date < utils.unix_time_to_utc(freeze))
solves = solves.all()
awards = awards.all()
json['scores'][team.name] = []
for x in solves:
json['scores'][team.name].append({

View File

@ -179,6 +179,9 @@
<li role="presentation">
<a href="#end-date" aria-controls="end-date" role="tab" data-toggle="tab">End Time</a>
</li>
<li role="presentation">
<a href="#freeze-date" aria-controls="freeze-date" role="tab" data-toggle="tab">Freeze Time</a>
</li>
<sub style="float:right;">* All time fields required</sub>
</ul>
@ -307,6 +310,70 @@
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="freeze-date">
<div class="row" id="freeze-date">
<div class="form-group col-xs-2">
<label for="freeze-month">Month:</label>
<input class="form-control freeze-date" id='freeze-month' name='freeze-month' min="1" max="12"
type='number'>
</div>
<div class="form-group col-xs-2">
<label for="freeze-day">Day:</label>
<input class="form-control freeze-date" id='freeze-day' name='freeze-day' min="1" max="31"
type='number'>
</div>
<div class="form-group col-xs-2">
<label for="freeze-year">Year:</label>
<input class="form-control freeze-date" id='freeze-year' name='freeze-year' type='number'>
</div>
<div class="form-group col-xs-2">
<label for="freeze-hour">Hour:</label>
<input class="form-control freeze-date" id='freeze-hour' name='freeze-hour' min="0" max="23"
type='number'>
</div>
<div class="form-group col-xs-2">
<label for="freeze-minute">Minute:</label>
<input class="form-control freeze-date" id='freeze-minute' name='freeze-minute' min="0"
max="59" type='number'>
</div>
<div class="form-group col-xs-4">
<label for="freeze-timezone">Timezone:</label>
<select class="form-control freeze-date" id="freeze-timezone">
<script>
document.write('<option>' + moment.tz.guess() + '</option>');
var tz_names = moment.tz.names();
for (var i = 0; i < tz_names.length; i++) {
document.write('<option>' + tz_names[i] + '</option>');
}
</script>
</select>
</div>
<div class="form-group col-xs-12">
<label for="freeze-local">Local Time:</label>
<input class="form-control" id='freeze-local' type='text'
placeholder="Freeze Date (Local Time)" readonly>
</div>
<div class="form-group col-xs-12">
<label for="freeze-zonetime">Timezone Time:</label>
<input class="form-control" id='freeze-zonetime' type='text'
placeholder="Freeze Date (Timezone Time)" readonly>
</div>
<div class="form-group col-xs-12">
<label for="freeze">UTC Timestamp:</label>
<input class="form-control" id='freeze' name='freeze' type='text'
placeholder="Freeze Date (UTC timestamp)"
{% if freeze is defined and freeze != None %}value="{{ freeze }}"{% endif %} readonly>
</div>
</div>
</div>
</div>
<div class="checkbox">
@ -343,57 +410,30 @@
var timestamp = parseInt(timestamp);
}
var m = moment(timestamp * 1000);
if (place == 'start'){
console.log('Loading start');
console.log(timestamp);
console.log(m.toISOString());
console.log(m.unix());
var month = $('#start-month').val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
var day = $('#start-day').val(m.date());
var year = $('#start-year').val(m.year());
var hour = $('#start-hour').val(m.hour());
var minute = $('#start-minute').val(m.minute());
load_date_values('start');
} else if (place == 'end'){
console.log('Loading end');
console.log(timestamp);
console.log(m.toISOString());
console.log(m.unix());
var month = $('#end-month').val(m.month() + 1);
var day = $('#end-day').val(m.date());
var year = $('#end-year').val(m.year());
var hour = $('#end-hour').val(m.hour());
var minute = $('#end-minute').val(m.minute());
load_date_values('end');
}
console.log('Loading ' + place);
console.log(timestamp);
console.log(m.toISOString());
console.log(m.unix());
var month = $('#' + place + '-month').val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
var day = $('#' + place + '-day').val(m.date());
var year = $('#' + place + '-year').val(m.year());
var hour = $('#' + place + '-hour').val(m.hour());
var minute = $('#' + place + '-minute').val(m.minute());
load_date_values(place);
}
function load_date_values(place){
if (place == "start"){
var month = $('#start-month').val();
var day = $('#start-day').val();
var year = $('#start-year').val();
var hour = $('#start-hour').val();
var minute = $('#start-minute').val();
var timezone = $('#start-timezone').val();
} else if (place == "end") {
var month = $('#end-month').val();
var day = $('#end-day').val();
var year = $('#end-year').val();
var hour = $('#end-hour').val();
var minute = $('#end-minute').val();
var timezone = $('#end-timezone').val();
}
var month = $('#' + place + '-month').val();
var day = $('#' + place + '-day').val();
var year = $('#' + place + '-year').val();
var hour = $('#' + place + '-hour').val();
var minute = $('#' + place + '-minute').val();
var timezone = $('#' + place + '-timezone').val();
var utc = convert_date_to_moment(month, day, year, hour, minute, timezone);
if (place == "start") {
$('#start').val(utc.unix());
$('#start-local').val(utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
$('#start-zonetime').val(utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
} else if (place == "end") {
$('#end').val(utc.unix());
$('#end-local').val(utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
$('#end-zonetime').val(utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
}
$('#' + place).val(utc.unix());
$('#' + place + '-local').val(utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
$('#' + place + '-zonetime').val(utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz"));
}
function convert_date_to_moment(month, day, year, hour, minute, timezone){
@ -431,6 +471,10 @@
load_date_values('end');
});
$('.freeze-date').change(function () {
load_date_values('freeze');
});
$(function () {
var hash = window.location.hash;
@ -446,6 +490,7 @@
var start = $('#start').val();
var end = $('#end').val();
var freeze = $('#freeze').val();
console.log(start);
console.log(end);
if (start){
@ -454,6 +499,9 @@
if (end){
load_timestamp('end', end);
}
if (freeze) {
load_timestamp('freeze', freeze);
}
});
</script>
{% endblock %}

View File

@ -16,6 +16,15 @@
</div>
</div>
{% else %}
{% if score_frozen %}
<div class="container main-container">
<div class="row">
<h1>Scoreboard has been frozen.</h1>
</div>
</div>
{% endif %}
<div id="score-graph"></div>
<br>

View File

@ -17,6 +17,15 @@
{% endfor %}
</div>
{% else %}
{% if score_frozen %}
<div class="container main-container">
<div class="row">
<h1>Scoreboard has been frozen.</h1>
</div>
</div>
{% endif %}
<div class="team-info">
<h2 id="team-place" class="text-center">
{%if place %}

View File

@ -212,6 +212,18 @@ def view_after_ctf():
return bool(get_config('view_after_ctf'))
def is_scoreboard_frozen():
freeze = get_config('freeze')
if freeze:
freeze = int(freeze)
if freeze < time.time():
return True
return False
def ctftime():
""" Checks whether it's CTF time or not. """
@ -274,6 +286,10 @@ def unix_time_millis(dt):
return unix_time(dt) * 1000
def unix_time_to_utc(t):
return datetime.datetime.utcfromtimestamp(t)
def get_ip():
""" Returns the IP address of the currently in scope request. The approach is to define a list of trusted proxies
(in this case the local network), and only trust the most recently defined untrusted IP address.

View File

@ -56,11 +56,12 @@ def setup():
</div>""".format(request.script_root))
# max attempts per challenge
max_tries = utils.set_config("max_tries", 0)
max_tries = utils.set_config('max_tries', 0)
# Start time
start = utils.set_config('start', None)
end = utils.set_config('end', None)
freeze = utils.set_config('freeze', None)
# Challenges cannot be viewed by unregistered users
view_challenges_unregistered = utils.set_config('view_challenges_unregistered', None)
@ -102,7 +103,7 @@ def setup():
# Custom CSS handler
@views.route('/static/user.css')
def custom_css():
return Response(utils.get_config("css"), mimetype='text/css')
return Response(utils.get_config('css'), mimetype='text/css')
# Static HTML files
@ -139,11 +140,23 @@ def team(teamid):
if utils.get_config('view_scoreboard_if_utils.authed') and not utils.authed():
return redirect(url_for('auth.login', next=request.path))
errors = []
freeze = utils.get_config('freeze')
user = Teams.query.filter_by(id=teamid).first_or_404()
solves = Solves.query.filter_by(teamid=teamid)
awards = Awards.query.filter_by(teamid=teamid).all()
score = user.score()
awards = Awards.query.filter_by(teamid=teamid)
place = user.place()
score = user.score()
if freeze:
freeze = utils.unix_time_to_utc(freeze)
if teamid != session.get('id'):
solves = solves.filter(Solves.date < freeze)
awards = awards.filter(Awards.date < freeze)
solves = solves.all()
awards = awards.all()
db.session.close()
if utils.hide_scores() and teamid != session.get('id'):
@ -153,7 +166,7 @@ def team(teamid):
return render_template('team.html', team=user, errors=errors)
if request.method == 'GET':
return render_template('team.html', solves=solves, awards=awards, team=user, score=score, place=place)
return render_template('team.html', solves=solves, awards=awards, team=user, score=score, place=place, score_frozen=utils.is_scoreboard_frozen())
elif request.method == 'POST':
json = {'solves': []}
for x in solves: