Skip to content

Commit

Permalink
Add limited 'global statement' functionality (inefficient) (#6)
Browse files Browse the repository at this point in the history
* Add limited 'global statement' functionality (inefficient)

---------

Co-authored-by: Kevin Atienza <[email protected]>
  • Loading branch information
kevinsogo and kcvajgf authored Oct 19, 2023
1 parent b78f5ad commit 0e6d3e2
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 8 deletions.
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 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
6 changes: 6 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,10 @@
(r"/printing", PrintingHandler),
(r"/documentation", DocumentationHandler),

# Contest stuff

(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

0 comments on commit 0e6d3e2

Please sign in to comment.