mirror of https://github.com/JohnHammond/CTFd.git
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.pyselenium-screenshot-testing
parent
a3a7d75ae8
commit
9a9b775e57
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue