比赛功能也太难写了

This commit is contained in:
masnn committed 2020-04-12 08:59:05 +08:00
1 parent f73859a42f
commit 32ad47e031
16 files changed
+195 -176

No files matched your search

+19 -11
View File
@@ -1,6 +1,6 @@
const
{ ContestNotLiveError, ValidationError, ProblemNotFoundError,
ContestNotAttendedError,ContestScoreboardHiddenError } = require('../error'),
ContestNotAttendedError, ContestScoreboardHiddenError } = require('../error'),
{ PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, PERM_CREATE_CONTEST,
PERM_EDIT_CONTEST } = require('../permission'),
{ constants } = require('../options'),
@@ -22,7 +22,7 @@ class ContestHandler extends Handler {
return false;
}
canShowScoreboard(tdoc, allowPermOverride = true) {
if (contest.RULES[tdoc.rule].showScoreboardFunc(tdoc, new Date())) return true;
if (contest.RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true;
return false;
}
@@ -37,12 +37,17 @@ class ContestHandler extends Handler {
return tdoc, rows, udict;
}
async verifyProblems(pids) {
let pdocs = await problem.getMulti({ _id: { $in: pids } }).sort({ doc_id: 1 }).toArray();
console.log(pids);
let pdocs = await problem.getMulti({
$or: [
{ _id: { $in: pids } }, { pid: { $in: pids } }
]
}).sort({ _id: 1 }).toArray();
if (pids.length != pdocs.length)
for (let pid of pids) {
let p = false;
for (let pdoc of pdocs)
if (pid == pdoc._id) {
if (pid == pdoc._id || pid == pdoc.pid) {
p = true;
break;
}
@@ -105,7 +110,7 @@ class ContestDetailHandler extends ContestHandler {
};
}
async post_attend() {
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id);
if (contest.is_done(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id);
await contest.attend(this.tdoc._id, this.uid);
this.back();
}
@@ -141,7 +146,7 @@ class ContestEditHandler extends ContestDetailHandler {
let dt = this.tdoc.beginAt;
this.response.body = {
rules, tdoc: this.tdoc, duration, path, pids: this.tdoc.pids.join(','),
date_text: `${dt.getFullYear()}-${dt.getMonth()}-${dt.getDate()}`,
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
page_name: 'contest_edit'
};
@@ -149,7 +154,7 @@ class ContestEditHandler extends ContestDetailHandler {
async post({ beginAtDate, beginAtTime, duration, title, content, rule, pids }) {
let beginAt, endAt;
try {
beginAt = Date.parse(`${beginAtDate}T${beginAtTime}+8:00`);
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
} catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime');
}
@@ -237,19 +242,22 @@ class ContestCreateHandler extends ContestHandler {
let dt = new Date(ts);
this.response.body = {
rules, page_name: 'contest_create',
date_text: `${dt.getFullYear()}-${dt.getMonth()}-${dt.getDate()}`,
time_text: `${dt.getHours()}-${dt.getMinutes()}`,
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
pids: '1000, 1001'
};
}
async post({ title, content, rule, beginAtDate, beginAtTime, duration, pids }) {
let beginAt, endAt;
console.log(beginAtDate, beginAtTime);
try {
beginAt = Date.parse(`${beginAtDate}T${beginAtTime}+8:00`);
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
console.log(beginAt, duration);
} catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime');
}
endAt = new Date(beginAt + duration * 3600 * 1000);
endAt = new Date(beginAt.getTime() + duration * 3600 * 1000);
console.log(endAt);
if (beginAt >= endAt) throw new ValidationError('duration');
await this.verifyProblems(pids);
let tid = await contest.add(title, content, this.user._id, rule, beginAt, endAt, pids);
+4 -7
View File
@@ -52,14 +52,11 @@ class ProblemRandomHandler extends ProblemHandler {
}
class ProblemDetailHandler extends ProblemHandler {
constructor(ctx) {
super(ctx);
this.uid = ctx.state.user._id;
this.pid = ctx.params.pid;
async _prepare({ pid }) {
this.response.template = 'problem_detail.html';
}
async _prepare() {
if (this.pid) this.pdoc = await problem.get(this);
this.uid = this.user._id;
this.pid = pid;
if (pid) this.pdoc = await problem.get(this);
if (this.pdoc.hidden && this.pdoc.owner != this.uid) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
if (this.pdoc) this.udoc = await user.getById(this.pdoc.owner);
this.response.body = {
+2 -2
View File
@@ -2,13 +2,13 @@ const md5 = require('./md5');
exports.gravatar_url = function (email, size) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size}`;
};
exports.datetime_span = function (dt, { relative = true } = {}) {
exports.datetime_span = function (dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (dt.generationTime) dt = new Date(dt.generationTime * 1000);
else if (typeof dt == 'number' || typeof dt == 'string') dt = new Date(dt);
return '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
relative ? ' relative' : '',
dt.getTime() / 1000,
dt.toLocaleString()
dt.format(format)
);
};
exports.paginate = function* (page, num_pages) {
+77 -63
View File
@@ -12,82 +12,96 @@ const RULES = {
acm: require('../module/contest/acm')
};
async function add(title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data = {}) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
this.RULES[rule].check(data);
let res = await coll.insertOne(data);
return res.insertedId;
}
async function edit(tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule)
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
if ($set.beginAt && $set.endAt)
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
await coll.findOneAndUpdate({ tid }, { $set });
return tdoc;
}
async function get(tid) {
let tdoc = await coll.findOne({ _id: tid });
if (!tdoc) throw new ContestNotFoundError(tid);
return tdoc;
}
async function get_random_id(query) {
let pdocs = coll.find(query);
let pcount = await pdocs.count();
if (pcount) {
let pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0];
return pdoc.pid;
} else return null;
}
async function getListStatus(uid, tids) {
let r = {};
for (let tid of tids) r[tid] = await this.getStatus(tid, uid);
return r;
}
async function attend(tid, uid) {
try {
await coll_status.insertOne({ tid, uid });
} catch (e) {
throw new ContestAlreadyAttendedError(tid, uid);
}
await coll.findOneAndUpdate({ _id: tid }, { $inc: { attend: 1 } });
}
function is_new(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now < readyAt - days * 24 * 3600 * 1000);
}
function is_upcoming(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now > readyAt + days * 24 * 3600 * 1000 && now < tdoc.beginAt);
}
function is_not_started(tdoc) {
return new Date() < tdoc.beginAt;
}
function is_ongoing(tdoc) {
let now = new Date();
return (tdoc.beginAt <= now && now < tdoc.endAt);
}
function is_done(tdoc) {
return tdoc.endAt <= new Date();
}
module.exports = {
RULES,
async add(title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
this.RULES[rule].check(data);
return await coll.insertOne(data);
},
RULES, add, getListStatus, attend, edit, get, get_random_id,
count: query => coll.find(query).count(),
async edit(tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule)
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
if ($set.beginAt && $set.endAt)
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
await coll.findOneAndUpdate({ tid }, { $set });
return tdoc;
},
async get(tid) {
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
return tdoc;
},
getMulti: query => coll.find(query),
getMultiStatus: query => coll_status.find(query),
async get_random_id(query) {
let pdocs = coll.find(query);
let pcount = await pdocs.count();
if (pcount) {
let pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0];
return pdoc.pid;
} else return null;
},
getStatus: (tid, uid) => coll_status.findOne({ tid, uid }),
setStatus: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set }),
async getListStatus(uid, tids) {
let r = {};
for (let tid of tids) r[tid] = await this.getStatus(tid, uid);
return r;
},
is_new(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now < readyAt - days * 24 * 3600 * 1000);
},
is_upcoming(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now > readyAt + days * 24 * 3600 * 1000 && now < tdoc.beginAt);
},
is_not_started: tdoc => new Date() < tdoc.beginAt,
is_ongoing(tdoc) {
let now = new Date();
return (tdoc.beginAt <= now && now < tdoc.endAt);
},
is_done: tdoc => tdoc.endAt <= new Date(),
is_new, is_upcoming, is_not_started, is_ongoing, is_done,
status_text: tdoc =>
this.is_new(tdoc)
is_new(tdoc)
? 'New'
: this.is_upcoming(tdoc)
: is_upcoming(tdoc)
? 'Ready (☆▽☆)'
: this.is_ongoing(tdoc)
: is_ongoing(tdoc)
? 'Live...'
: 'Done',
get_status: tdoc =>
this.is_not_started(tdoc)
is_not_started(tdoc)
? 'not_started'
: this.is_ongoing(tdoc)
: is_ongoing(tdoc)
? 'ongoing'
: 'finished',
};
+7 -1
View File
@@ -85,6 +85,11 @@ async function random(query) {
return pdoc.pid;
} else return null;
}
async function getList(pids) {
let r = {};
for (let pid of pids) r[pid] = await get({pid});
return r;
}
module.exports = {
add,
@@ -94,5 +99,6 @@ module.exports = {
count,
random,
getById,
getMulti
getMulti,
getList
};
+3
View File
@@ -10,6 +10,9 @@ module.exports = {
}
return { score, detail };
},
showScoreboard(tdoc,now){
return now>tdoc.endAt;
},
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, dudict, pdict) {
let columns = [
{ type: 'rank', value: _('Rank') },
+27 -30
View File
@@ -13,8 +13,7 @@ const
blacklist = require('../model/blacklist'),
token = require('../model/token'),
opcount = require('../model/opcount'),
{ UserNotFoundError, BlacklistedError, PermissionError,
NotFoundError } = require('../error');
{ UserNotFoundError, BlacklistedError, PermissionError } = require('../error');
const options = require('../options');
let http = options.listen.https ? require('https') : require('http');
@@ -128,40 +127,33 @@ class Handler {
async ___cleanup() {
try {
await this.renderBody();
} catch (e) {
try {
let error;
if (this.response.body && this.response.body.error) error = this.response.body.error;
else error = e;
if (error instanceof NotFoundError) this.response.status = 404;
if (error.name == 'Template render error') throw error;
if (this.preferJson) this.response.body = { error };
else await this.render('error.html', { error });
} catch (error) {
if (this.preferJson) this.response.body = { error };
else await this.render('bsod.html', { error });
}
} catch (error) {
if (this.preferJson) this.response.body = { error };
else await this.render('bsod.html', { error });
}
await this.putResponse();
await this.saveCookie();
}
async renderBody() {
if (this.response.body || this.response.template) {
if (this.request.query.noTemplate || this.preferJson) return;
if (this.request.query.template || this.response.template) {
this.response.body = this.response.body || {};
await this.render(this.request.query.template || this.response.template, this.response.body);
if (!this.preferJson)
if (this.response.body || this.response.template) {
if (this.request.query.noTemplate || this.preferJson) return;
if (this.request.query.template || this.response.template) {
this.response.body = this.response.body || {};
await this.render(this.request.query.template || this.response.template, this.response.body);
}
}
}
}
async putResponse() {
if (this.response.redirect) {
this.response.type = 'application/octet-stream';
this.redirect(this.response.redirect);
if (this.response.redirect && !this.preferJson) {
this.ctx.response.type = 'application/octet-stream';
this.ctx.response.status = 302;
this.ctx.redirect(this.response.redirect);
} else {
if (this.response.body) this.ctx.body = this.response.body;
if (this.response.type) this.ctx.response.type = this.response.type;
if (this.response.status) this.ctx.response.status = this.response.status;
}
if (this.response.body) this.ctx.body = this.response.body;
if (this.response.type) this.ctx.response.type = this.response.type;
if (this.response.status) this.ctx.response.status = this.response.status;
}
async saveCookie() {
if (this.session.sid)
@@ -184,9 +176,11 @@ class Handler {
}
this.ctx.cookies.set('sid', this.session.sid, cookie);
}
async onerror(e) {
console.error(e.message, e.params);
console.error(e.stack);
async onerror(error) {
console.error(error.message, error.params);
console.error(error.stack);
this.response.template = 'error.html';
this.response.body = { error };
await this.___cleanup();
}
}
@@ -203,6 +197,8 @@ function Route(route, handler) {
if (args.page) args.page = parseInt(args.page);
if (args.rid) args.rid = new ObjectID(args.rid);
if (args.tid) args.tid = new ObjectID(args.tid);
if (args.duration) args.duration = parseFloat(args.duration);
if (args.pids) args.pids = args.pids.split(',').map(i => i.trim());
if (h.___prepare) await h.___prepare(args);
if (h.__prepare) await h.__prepare(args);
@@ -330,6 +326,7 @@ exports.Connection = Connection;
exports.start = function start() {
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
app.use(router.routes()).use(router.allowedMethods());
Route('*', Handler);
server.listen(options.listen.port);
console.log('Server listening at: %s', options.listen.port);
};
+11
View File
@@ -27,3 +27,14 @@ Array.isDiff = function isDiff(a, b) {
if (a[i] != b[i]) return true;
return false;
};
Date.prototype.format = function (fmt = '%Y-%m-%d %H:%M:%S') {
return fmt
.replace('%Y', this.getFullYear())
.replace('%m', this.getMonth() + 1)
.replace('%D', this.getDate())
.replace('%d', this.getDate())
.replace('%H', this.getHours())
.replace('%M', this.getMinutes())
.replace('%S', this.getSeconds());
};
+2 -2
View File
@@ -1,8 +1,8 @@
{% import "components/user.html" as user with context %}
{% macro render_time(tdoc_time) %}
{{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M') }}
{{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }}
{% endmacro %}
{% macro render_duration(tdoc) %}
{{ ((tdoc['end_at'] - tdoc['begin_at']).total_seconds() / 3600)|round(1) }}
{{ ((tdoc.endAt.getTime() - tdoc.beginAt.getTime()) /1000 / 3600)|round(1) }}
{% endmacro %}
+11 -12
View File
@@ -1,3 +1,4 @@
{% set page_name = "contest_detail" %}
{% extends "layout/basic.html" %}
{% import "components/contest.html" as contest with context %}
{% import "components/record.html" as record with context %}
@@ -7,20 +8,20 @@
<div class="medium-9 columns">
<div class="section">
<div class="section__body typo">
{{ tdoc['content']|markdown }}
{% if handler.is_ongoing(tdoc) %}
{{ tdoc['content']|markdown|safe }}
{% if model.contest.is_ongoing(tdoc) %}
<blockquote class="warn">
<p>{{ _('We will check code similarity after this contest.') }}</p>
</blockquote>
{% endif %}
</div>
</div>
{% if handler.is_ongoing(tdoc) or handler.is_done(tdoc) %}
{% if model.contest.is_ongoing(tdoc) or model.contest.is_done(tdoc) %}
<div class="section">
<div class="section__header">
<h1 class="section__title">{{ _('Problem') }}</h1>
</div>
{% if attended or handler.is_done(tdoc) %}
{% if attended or model.contest.is_done(tdoc) %}
<div class="section__body no-padding">
<table class="data-table">
<colgroup>
@@ -40,18 +41,17 @@
</tr>
</thead>
<tbody>
{% for pid in tdoc['pids'] %}
{% for pid in tdoc.pids %}
<tr>
{% if handler.hasPerm(perm.PERM_LOGGEDIN) %}
{% if psdict[pid] and psdict[pid]['rid'] %}
{% with rdoc=rdict[psdict[pid]['rid']] %}
{% if psdict[pid] and psdict[pid].rid %}
{% set rdoc = rdict[psdict[pid].rid] %}
{% if handler.can_show_record(tdoc) %}
{{ record.render_status_td(rdoc) }}
{% else %}
<td class="col--status record-status--border"><a href="/r/{{ rdoc._id }}">{{ _('Submitted') }}</a></td>
{% endif %}
<td class="col--submit-at">{{ datetime_span(rdoc['_id'].generationTime) }}</td>
{% endwith %}
<td class="col--submit-at">{{ datetime_span(rdoc._id) }}</td>
{% else %}
<td class="col--status record-status--border">{{ _('No Submissions') }}</td>
<td class="col--submit-at">-</td>
@@ -73,9 +73,8 @@
{% endif %}
</div>
<div class="medium-3 columns">
{% with owner_udoc=udict[tdoc['owner_uid']], owner_dudoc=dudict[tdoc['owner_uid']] %}
{% include "partials/contest_sidebar.html" %}
{% endwith %}
{% set owner_udoc = udict[tdoc.owner] %}
{% include "partials/contest_sidebar.html" %}
</div>
</div>
{% endblock %}
+8 -8
View File
@@ -17,11 +17,11 @@
</li>
<li>
<span class="icon icon-calendar"></span>
{{ _('Start at') }}: {{ model.contest.render_time(tdoc['beginAt']) }}
{{ _('Start at') }}: {{ contest.render_time(tdoc.beginAt) }}
</li>
<li>
<span class="icon icon-schedule--fill"></span>
{{ _('Duration') }}: {{ model.contest.render_duration(tdoc) }} {{ _('hour(s)') }}
{{ _('Duration') }}: {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }}
</li>
<li>
<span class="icon icon-user--multiple"></span>
@@ -32,7 +32,7 @@
</div>
<div class="media__right middle">
<div class="section__body">
<a href="/t/{{ tdoc._id }}" class="detail-button">{{ _('View Details') }}</a>
<a href="/c/{{ tdoc._id }}" class="detail-button">{{ _('View Details') }}</a>
<div class="typo text-center">
{% if tsdict[tdoc._id].attend %}
<p><span class="icon icon-check"></span> {{ _('Attended') }}</p>
@@ -49,9 +49,9 @@
<div class="row">
<div class="medium-9 columns">
{% for tdoc in tdocs %}
{% if handler.is_ongoing(tdoc) %}
{% if model.contest.is_ongoing(tdoc) %}
{{ render_important_contest('live', 'Live...', tdoc) }}
{% elif handler.is_upcoming(tdoc) %}
{% elif model.contest.is_upcoming(tdoc) %}
{{ render_important_contest('ready', 'Ready (☆▽☆)', tdoc) }}
{% endif %}
{% endfor %}
@@ -83,12 +83,12 @@
<div class="media">
<div class="media__left medium">
<div class="contest__date numbox">
<div class="numbox__num large">{{ datetime_span(tdoc['beginAt'], false, '%d') }}</div>
<div class="numbox__text">{{ datetime_span(tdoc['beginAt'], false, '%Y-%m') }}</div>
<div class="numbox__num large">{{ datetime_span(tdoc['beginAt'], false, '%d')|safe }}</div>
<div class="numbox__text">{{ datetime_span(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
</div>
</div>
<div class="media__body medium">
<h1 class="contest__title"><a href="/t/{{ tdoc._id }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
<h1 class="contest__title"><a href="/c/{{ tdoc._id }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
<ul class="supplementary list">
<li>
<a href="?rule={{ tdoc['rule'] }}" class="contest-type-tag"><span class="icon icon-award"></span>{{ model.contest.RULES[tdoc.rule].TEXT }}</a>
+1 -1
View File
@@ -33,7 +33,7 @@
</div>
</div>
<div class="media__body medium">
<h1 class="contest__title"><a href="/t/{{ tdoc._id }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
<h1 class="contest__title"><a href="/c/{{ tdoc._id }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
<ul class="supplementary list">
<li>
<a href="{{ reverse_url('contest_main') }}?rule={{ tdoc['rule'] }}" class="contest-type-tag"><span class="icon icon-award"></span>{{ vj4.constant.contest.RULE_TEXTS[tdoc['rule']] }}</a>
+3 -3
View File
@@ -8,13 +8,13 @@
</div>
<div class="error__text-container">
<h1>{{ _('Oops!') }}</h1>
<p>{{ _(error.message).format(error.args) }}</p>
<p>{{ _(error.message).format(error.params) }}</p>
<p>{{ _('Technical Information') }}:</p>
<p>{{ _('Type') }}: {{ error.code }}</p>
<p>{{ _('Arguments') }}:
<ol>
{% for arg in error.args %}
<li>{{ arg }}</li>
{% for param in error.params %}
<li>{{ param }}</li>
{% endfor %}
</ol>
</p>
+18 -34
View File
@@ -2,7 +2,7 @@
{% import "components/contest.html" as contest with context %}
<div class="section side">
{% if page_name != 'contest_detail' %}
<a class="contest-sidebar__bg" href="/t/{{ tdoc._id }}">
<a class="contest-sidebar__bg" href="/c/{{ tdoc._id }}">
<div class="section__body">
<h1>{{ tdoc['title'] }}</h1>
<div class="contest-sidebar__status">
@@ -17,31 +17,15 @@
{% endif %}
<div>
<ol class="menu">
{% if page_name == 'discussion_node' %}
{% if handler.hasPerm(perm.PERM_CREATE_DISCUSSION) %}
<li class="menu__item"><a class="menu__link highlight" href="{{ reverse_url('discussion_create_document_as_node', doc_type=tdoc['doc_type'], doc_id=tdoc['doc_id']) }}">
<span class="icon icon-add"></span> {{ _('Create a Discussion') }}
</a></li>
{% else %}
{% if not handler.hasPerm(perm.PERM_LOGGEDIN) %}
<li class="menu__item"><a class="menu__link" href="javascript:showSignInDialog();">
<span class="icon icon-add"></span> {{ _('Login to Create a Discussion') }}</a></li>
{% else %}
<li class="menu__item"><a class="menu__link disabled" href="javascript:;">
<span class="icon icon-add"></span> {{ _('No Permission to Create a Discussion') }}</a></li>
{% endif %}
{% endif %}
<li class="menu__seperator"></li>
{% endif %}
{% if page_name != 'contest_detail' %}
<li class="menu__item"><a class="menu__link" href="/t/{{ tdoc._id }}">
<li class="menu__item"><a class="menu__link" href="/c/{{ tdoc._id }}">
<span class="icon icon-award"></span> {{ _('View Contest') }}
</a></li>
{% endif %}
{% if not attended and not handler.is_done(tdoc) %}
{% if not attended and not model.contest.is_done(tdoc) %}
<li class="menu__item">
{% if handler.hasPerm(perm.PERM_ATTEND_CONTEST) and handler.hasPerm(perm.PERM_LOGGEDIN) %}
<form action="/t/{{ tdoc._id }}" method="POST">
<form action="/c/{{ tdoc._id }}" method="POST">
<input type="hidden" name="operation" value="attend">
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
<button class="menu__link" type="submit">
@@ -59,26 +43,26 @@
{% endif %}
</li>
{% endif %}
{% if handler.can_show_scoreboard(tdoc, False) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('contest_scoreboard', tid=tdoc['doc_id']) }}">
{% if handler.canShowScoreboard(tdoc, False) %}
<li class="menu__item"><a class="menu__link" href="/c/{{ tdoc._id }}/scoreboard">
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
</a></li>
{% elif handler.can_view_hidden_scoreboard(tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('contest_scoreboard', tid=tdoc['doc_id']) }}">
{% elif handler.canViewHiddenScoreboard(tdoc) %}
<li class="menu__item"><a class="menu__link" href="/c/{{ tdoc._id }}/scoreboard">
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
</a></li>
{% endif %}
{% if handler.own(tdoc, perm.PERM_EDIT_CONTEST_SELF) or handler.hasPerm(perm.PERM_EDIT_CONTEST) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('contest_edit', tid=tdoc['doc_id']) }}">
{% if user._id == tdoc.owner or handler.hasPerm(perm.PERM_EDIT_CONTEST) %}
<li class="menu__item"><a class="menu__link" href="/c/{{ tdoc._id }}/edit">
<span class="icon icon-edit"></span> {{ _('Edit Contest') }}
</a></li>
{% endif %}
{% if handler.hasPerm(perm.PERM_READ_RECORD_CODE) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('contest_code', tid=tdoc['doc_id']) }}">
<li class="menu__item"><a class="menu__link" href="/c/{{ tdoc._id }}/code">
<span class="icon icon-download"></span> {{ _('Export All Code') }}
</a></li>
{% endif %}
<li class="menu__item"><a class="menu__link" href="/r?tid={{ tdoc['doc_id'] }}">
<li class="menu__item"><a class="menu__link" href="/r?tid={{ tdoc._id }}">
<span class="icon icon-flag"></span> {{ _('All Submissions') }}
</a></li>
<li class="menu__item"><a class="menu__link" href="/wiki/help#contest">
@@ -89,13 +73,13 @@
</div>
<div class="section__body typo">
<dl class="large horizontal">
<dt>{{ _('Status') }}</dt><dd>{{ _(handler.status_text(tdoc)) }}</dd>
<dt>{{ _('Rule') }}</dt><dd>{{ vj4.constant.contest.RULE_TEXTS[tdoc['rule']] }}</dd>
<dt>{{ _('Problem') }}</dt><dd>{{ tdoc['pids']|length }}</dd>
<dt>{{ _('Start at') }}</dt><dd>{{ contest.render_time(tdoc['begin_at']) }}</dd>
<dt>{{ _('End at') }}</dt><dd>{{ contest.render_time(tdoc['end_at']) }}</dd>
<dt>{{ _('Status') }}</dt><dd>{{ _(model.contest.status_text(tdoc)) }}</dd>
<dt>{{ _('Rule') }}</dt><dd>{{ model.contest.RULES[tdoc.rule].TEXT }}</dd>
<dt>{{ _('Problem') }}</dt><dd>{{ tdoc.pids.length }}</dd>
<dt>{{ _('Start at') }}</dt><dd>{{ contest.render_time(tdoc.beginAt) }}</dd>
<dt>{{ _('End at') }}</dt><dd>{{ contest.render_time(tdoc.endAt) }}</dd>
<dt>{{ _('Duration') }}</dt><dd>{{ contest.render_duration(tdoc) }} {{ _('hour(s)') }}</dd>
<dt>{{ _('Host') }}</dt><dd>{{ user.render_inline(owner_udoc, dudoc=owner_dudoc, badge=false) }}</dd>
<dt>{{ _('Host') }}</dt><dd>{{ user.render_inline(owner_udoc, badge=false) }}</dd>
<dt>{{ _('Partic.') }}</dt>
<dd>
{{ tdoc['attend']|default(0) }}
@@ -174,7 +174,7 @@
{% if ctdocs %}
<p>{{ _('In following contests') }}: </p>
{% for tdoc in ctdocs %}
<p><a href="/t/{{ tdoc._id }}">{{ tdoc['title'] }}</a></p>
<p><a href="/c/{{ tdoc._id }}">{{ tdoc['title'] }}</a></p>
{% endfor %}
{% endif %}
{% if htdocs %}
+1 -1
View File
@@ -67,7 +67,7 @@
{% if tdoc['doc_type'] == vj4.model.document.TYPE_CONTEST %}
<dt>{{ _('Contest') }}</dt>
<dd>
<a href="/t/{{ tdoc._id }}">{{ tdoc['title'] }}</a>
<a href="/c/{{ tdoc._id }}">{{ tdoc['title'] }}</a>
</dd>
{% else %}{# tdoc['doc_type'] == vj4.model.document.TYPE_CONTEST #}
<dt>{{ _('Homework') }}</dt>