Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add limited 'global statement' functionality (inefficient) #7

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@

from .util import test_db_connection, get_contest_list, is_contest_id, \
ask_for_contest, get_submissions, get_submission_results, \
get_datasets_to_judge, enumerate_files
get_datasets_to_judge, enumerate_files, get_global_statement


configure_mappers()
Expand Down
35 changes: 35 additions & 0 deletions cms/db/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,38 @@ def enumerate_files(
digests = set(r[0] for r in session.execute(union(*queries)))
digests.discard(Digest.TOMBSTONE)
return digests


def get_global_statement(session, contest=None):
"""Return the global statement of the contest, if it exists.

If this contest has more than one task, each having a single statement,
and they're all the same, return that. Otherwise, return None."""
if contest is None:
return None

# just get everything!
# There's probably a more efficient way to do this though...

# each task must have exactly one statement
if not all(len(task.statements) == 1 for task in contest.tasks):
return None

statements = [(lang_code, statement)
for task in contest.tasks
for lang_code, statement in task.statements.items()
]

# there must be exactly one language code
if len({lang_code for lang_code, statement in statements}) != 1:
return None

# there must be exactly one digest
if len({statement.digest for lang_code, statement in statements}) != 1:
return None

# there must be more than one statement
if len(statements) < 2:
return None

return next(statement for lang_code, statement in statements)
6 changes: 6 additions & 0 deletions cms/server/admin/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
QuestionClaimHandler
from .contestranking import \
RankingHandler
from .conteststatement import \
ContestStatementHandler
from .contestsubmission import \
ContestSubmissionsHandler, \
ContestUserTestsHandler
Expand Down Expand Up @@ -136,6 +138,10 @@
(r"/contest/([0-9]+)/tasks", ContestTasksHandler),
(r"/contest/([0-9]+)/tasks/add", AddContestTaskHandler),

# Contest's global statement

(r"/contest/([0-9]+)/globalstatement", ContestStatementHandler),

# Contest's submissions / user tests

(r"/contest/([0-9]+)/submissions", ContestSubmissionsHandler),
Expand Down
3 changes: 2 additions & 1 deletion cms/server/admin/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

from cms import __version__, config
from cms.db import Admin, Contest, Participation, Question, Submission, \
SubmissionResult, Task, Team, User, UserTest
SubmissionResult, Task, Team, User, UserTest, get_global_statement
from cms.grading.scoretypes import get_score_type_class
from cms.grading.tasktypes import get_task_type_class
from cms.server import CommonRequestHandler, FileHandlerMixin
Expand Down Expand Up @@ -316,6 +316,7 @@ def render_params(self):
params["task_list"] = self.sql_session.query(Task).all()
params["user_list"] = self.sql_session.query(User).all()
params["team_list"] = self.sql_session.query(Team).all()
params["global_statement"] = get_global_statement(self.sql_session, self.contest)
return params

def write_error(self, status_code, **kwargs):
Expand Down
104 changes: 104 additions & 0 deletions cms/server/admin/handlers/conteststatement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2010-2013 Giovanni Mascellani <[email protected]>
# Copyright © 2010-2018 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2012-2018 Luca Wehrstedt <[email protected]>
# Copyright © 2014 Artem Iglikov <[email protected]>
# Copyright © 2014 Fabian Gundlach <[email protected]>
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2023-2023 Kevin Atienza <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Statement-related handlers for AWS for a specific contest.

"""

try:
import tornado4.web as tornado_web
except ImportError:
import tornado.web as tornado_web

from cms.db import Contest, Session, Statement, Task
from cmscommon.datetime import make_datetime
from .base import BaseHandler, require_permission


class ContestStatementHandler(BaseHandler):
"""Add a global statement to a contest.

"""
@require_permission(BaseHandler.PERMISSION_ALL)
def get(self, contest_id):
self.contest = self.safe_get_item(Contest, contest_id)
self.r_params = self.render_params()
self.render("add_global_statement.html", **self.r_params)

@require_permission(BaseHandler.PERMISSION_ALL)
def post(self, contest_id):
fallback_page = self.url("contest", contest_id, "globalstatement")

contest = self.safe_get_item(Contest, contest_id)

language = self.get_argument("language", "")
if len(language) == 0:
self.service.add_notification(
make_datetime(),
"No language code specified",
"The language code can be any string.")
self.redirect(fallback_page)
return
statement = self.request.files["statement"][0]
if not statement["filename"].endswith(".pdf"):
self.service.add_notification(
make_datetime(),
"Invalid contest statement",
"The contest statement must be a .pdf file.")
self.redirect(fallback_page)
return

contest_name = contest.name
self.sql_session.close()

try:
digest = self.service.file_cacher.put_file_content(
statement["body"],
"Global statement for contest %s (lang: %s)" % (
contest_name,
language))
except Exception as error:
self.service.add_notification(
make_datetime(),
"Contest global statement storage failed",
repr(error))
self.redirect(fallback_page)
return

self.sql_session = Session()

contest = self.safe_get_item(Contest, contest_id)
for task in contest.tasks:
self.sql_session\
.query(Statement)\
.filter(Statement.task_id == task.id)\
.delete()
statement = Statement(language, digest, task=task)
self.sql_session.add(statement)

if self.try_commit():
self.redirect(self.url("contest", contest_id))
else:
self.redirect(fallback_page)
15 changes: 15 additions & 0 deletions cms/server/admin/templates/add_global_statement.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends "base.html" %}

{% block core %}
<div class="core_title">
<h1><a href="{{ url("contest", contest.id) }}">{{ contest.name }}</a> - Upload global statement</h1>
<h1>WARNING: THIS WILL OVERWRITE ALL EXISTING STATEMENTS!!</h1>
</div>
<form enctype="multipart/form-data" action="{{ url("contest", contest.id, "globalstatement") }}" method="POST">
{{ xsrf_form_html|safe }}
Language code: <input type="text" name="language"/><br/>
<input type="file" name="statement"/><br/>
<input type="submit" value="Upload">
<input type="Reset" >
</form>
{% endblock core %}
16 changes: 16 additions & 0 deletions cms/server/admin/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ <h1>Contest configuration</h1>
</td>
<td><input type="text" name="score_precision" value="{{ contest.score_precision }}"></td>
</tr>
<tr>
<td>
<span class="info" title="The global statement of this contest."></span>
Global statement
{% if admin.permission_all %}
[<a href="{{ url("contest", contest.id, "globalstatement") }}">set</a>]
{% endif %}
</td>
<td>
{% if global_statement %}
<a href="{{ url("file", global_statement.digest, "statement.pdf") }}">Global statement</a>
{% else %}
No global statement.
{% endif %}
</td>
</tr>

<tr><td colspan=2><h2>Logging in</h2></td></tr>
<tr>
Expand Down
4 changes: 4 additions & 0 deletions cms/server/contest/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from .communication import \
CommunicationHandler, \
QuestionHandler
from .contest import \
ContestStatementViewHandler
from .main import \
LoginHandler, \
LogoutHandler, \
Expand Down Expand Up @@ -66,6 +68,8 @@
(r"/printing", PrintingHandler),
(r"/documentation", DocumentationHandler),

(r"/globalstatement", ContestStatementViewHandler),

# Tasks

(r"/tasks/(.*)/description", TaskDescriptionHandler),
Expand Down
29 changes: 26 additions & 3 deletions cms/server/contest/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
import tornado.web as tornado_web

from cms import config, TOKEN_MODE_MIXED
from cms.db import Contest, Submission, Task, UserTest
from cms.db import Contest, Submission, Task, UserTest, get_global_statement
from cms.locale import filter_language_codes
from cms.server import FileHandlerMixin
from cms.server import FileHandlerMixin, multi_contest
from cms.server.contest.authentication import authenticate_request
from cmscommon.datetime import get_timezone
from .base import BaseHandler
from ..phase_management import compute_actual_phase
from ..phase_management import compute_actual_phase, actual_phase_required


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -161,6 +161,9 @@ def get_current_user(self):

return participation

def get_global_statement(self):
return get_global_statement(self.sql_session, self.contest)

def render_params(self):
ret = super().render_params()

Expand Down Expand Up @@ -287,3 +290,23 @@ def notify_error(self, subject, text, text_params=None):

class FileHandler(ContestHandler, FileHandlerMixin):
pass


class ContestStatementViewHandler(FileHandler):
"""Shows the global statement file of a contest.

"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@multi_contest
def get(self):
statement = get_global_statement(self.sql_session, self.contest)
if statement is None:
raise tornado_web.HTTPError(404)

digest = statement.digest
self.sql_session.close()

filename = "%s.pdf" % self.contest.name

self.fetch(digest, "application/pdf", filename)
7 changes: 6 additions & 1 deletion cms/server/contest/handlers/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ def get(self, task_name):
if task is None:
raise tornado_web.HTTPError(404)

self.render("task_description.html", task=task, **self.r_params)
global_statement = self.get_global_statement()
self.render(
"task_description.html",
task=task,
global_statement=global_statement,
**self.r_params)


class TaskStatementViewHandler(FileHandler):
Expand Down
12 changes: 10 additions & 2 deletions cms/server/contest/templates/task_description.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ <h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
</div>


<h2>{% trans %}Statement{% endtrans %}</h2>
<h2>{% if global_statement %}{% trans %}Statements{% endtrans %}{% else %}{% trans %}Statement{% endtrans %}{% endif %}</h2>

{% if task.statements|length == 0 %}
{% if global_statement %}
<div class="row statement one_statement">
<div class="span9">
{% for lang_code in task.statements %}
<a href="{{ contest_url("globalstatement") }}" class="btn btn-large btn-success">{% trans %}Download contest statements (all tasks){% endtrans %}</a>
{% endfor %}
</div>
</div>
{% elif task.statements|length == 0 %}
<div class="row statement no_statements">
<div class="span9">
{% trans %}no statement available{% endtrans %}
Expand Down