Skip to content

Commit

Permalink
Contest attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
vytisb committed Feb 20, 2024
1 parent cd632ab commit 0559bb4
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 8 deletions.
5 changes: 3 additions & 2 deletions cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013 Bernard Blackham <[email protected]>
# Copyright © 2013-2018 Luca Wehrstedt <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2016 Masaki Hara <[email protected]>
# Copyright © 2016 Amir Keivan Mohtashami <[email protected]>
Expand Down Expand Up @@ -53,7 +54,7 @@
# fsobject
"FSObject", "LargeObject",
# contest
"Contest", "Announcement",
"Contest", "Announcement", "ContestAttachment",
# user
"User", "Team", "Participation", "Message", "Question",
# admin
Expand Down Expand Up @@ -96,7 +97,7 @@
from .base import Base
from .fsobject import FSObject, LargeObject
from .admin import Admin
from .contest import Contest, Announcement
from .contest import Contest, Announcement, ContestAttachment
from .user import User, Team, Participation, Message, Question
from .task import Task, Statement, Attachment, Dataset, Manager, Testcase
from .submission import Submission, File, Token, SubmissionResult, \
Expand Down
48 changes: 46 additions & 2 deletions cms/db/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2012-2018 Luca Wehrstedt <[email protected]>
# Copyright © 2013 Bernard Blackham <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2016 Amir Keivan Mohtashami <[email protected]>
# Copyright © 2018 William Di Luigi <[email protected]>
Expand All @@ -32,12 +33,14 @@
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import relationship
from sqlalchemy.schema import Column, ForeignKey, CheckConstraint
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.schema import Column, ForeignKey, CheckConstraint, \
UniqueConstraint
from sqlalchemy.types import Integer, Unicode, DateTime, Interval, Enum, \
Boolean, String

from cms import TOKEN_MODE_DISABLED, TOKEN_MODE_FINITE, TOKEN_MODE_INFINITE
from . import Codename, Base, Admin
from . import Codename, Filename, Digest, Base, Admin


class Contest(Base):
Expand Down Expand Up @@ -276,6 +279,13 @@ class Contest(Base):
passive_deletes=True,
back_populates="contest")

attachments = relationship(
"ContestAttachment",
collection_class=attribute_mapped_collection("filename"),
cascade="all, delete-orphan",
passive_deletes=True,
back_populates="contest")

participations = relationship(
"Participation",
cascade="all, delete-orphan",
Expand Down Expand Up @@ -307,6 +317,40 @@ def phase(self, timestamp):
return 3


class ContestAttachment(Base):
"""Class to store contest related files to give to the user.
"""
__tablename__ = 'contest_attachments'
__table_args__ = (
UniqueConstraint('contest_id', 'filename'),
)

# Auto increment primary key.
id = Column(
Integer,
primary_key=True)

# Contest (id and object) owning the attachment.
contest_id = Column(
Integer,
ForeignKey(Contest.id,
onupdate="CASCADE", ondelete="CASCADE"),
nullable=False,
index=True)
contest = relationship(
Contest,
back_populates="attachments")

# Filename and digest of the provided attachment.
filename = Column(
Filename,
nullable=False)
digest = Column(
Digest,
nullable=False)


class Announcement(Base):
"""Class to store a messages sent by the contest managers to all
the users.
Expand Down
5 changes: 4 additions & 1 deletion cms/db/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Copyright © 2010-2012 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013-2018 Luca Wehrstedt <[email protected]>
# Copyright © 2014 Vytis Banaitis <[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
Expand Down Expand Up @@ -33,7 +34,7 @@
from . import SessionGen, Digest, Contest, Participation, Statement, \
Attachment, Task, Manager, Dataset, Testcase, Submission, File, \
SubmissionResult, Executable, UserTest, UserTestFile, UserTestManager, \
UserTestResult, UserTestExecutable, PrintJob
UserTestResult, UserTestExecutable, PrintJob, ContestAttachment


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -288,6 +289,8 @@ def enumerate_files(
contest_q = contest_q.filter(Contest.id == contest.id)

queries = list()
queries.append(contest_q.join(Contest.attachments)
.with_entities(ContestAttachment.digest))

task_q = contest_q.join(Contest.tasks)
queries.append(task_q.join(Task.statements).with_entities(Statement.digest))
Expand Down
5 changes: 5 additions & 0 deletions cms/server/admin/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2016 Peyman Jabbarzade Ganje <[email protected]>
# Copyright © 2016 Amir Keivan Mohtashami <[email protected]>
# Copyright © 2018 Vytis Banaitis <[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
Expand All @@ -30,6 +31,8 @@
from .contest import \
AddContestHandler, \
ContestHandler, \
AddContestAttachmentHandler, \
ContestAttachmentHandler, \
OverviewHandler, \
ResourcesListHandler, \
ContestListHandler, \
Expand Down Expand Up @@ -120,6 +123,8 @@
(r"/contests/([0-9]+)/remove", RemoveContestHandler),
(r"/contests/add", AddContestHandler),
(r"/contest/([0-9]+)", ContestHandler),
(r"/contest/([0-9]+)/attachments/add", AddContestAttachmentHandler),
(r"/contest/([0-9]+)/attachment/([0-9]+)", ContestAttachmentHandler),
(r"/contest/([0-9]+)/overview", OverviewHandler),
(r"/contest/([0-9]+)/resourceslist", ResourcesListHandler),

Expand Down
79 changes: 78 additions & 1 deletion cms/server/admin/handlers/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Copyright © 2012-2015 Luca Wehrstedt <[email protected]>
# Copyright © 2014 Artem Iglikov <[email protected]>
# Copyright © 2014 Fabian Gundlach <[email protected]>
# Copyright © 2014-2024 Vytis Banaitis <[email protected]>
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2016 Amir Keivan Mohtashami <[email protected]>
# Copyright © 2018 William Di Luigi <[email protected]>
Expand All @@ -28,8 +29,14 @@
"""

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

from cms import ServiceCoord, get_service_shards, get_service_address
from cms.db import Contest, Participation, Submission
from cms.db import Contest, Participation, Session, Submission, \
ContestAttachment
from cmscommon.datetime import make_datetime

from .base import BaseHandler, SimpleContestHandler, SimpleHandler, \
Expand Down Expand Up @@ -142,6 +149,76 @@ def post(self, contest_id):
self.redirect(self.url("contest", contest_id))


class AddContestAttachmentHandler(BaseHandler):
"""Add an attachment 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_contest_attachment.html", **self.r_params)

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

self.contest = self.safe_get_item(Contest, contest_id)

attachment = self.request.files["attachment"][0]
contest_name = self.contest.name
self.sql_session.close()

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

# TODO verify that there's no other Attachment with that filename
# otherwise we'd trigger an IntegrityError for constraint violation

self.sql_session = Session()
self.contest = self.safe_get_item(Contest, contest_id)

attachment = ContestAttachment(attachment["filename"], digest, contest=self.contest)
self.sql_session.add(attachment)

if self.try_commit():
self.redirect(self.url("contest", contest_id))
else:
self.redirect(fallback_page)


class ContestAttachmentHandler(BaseHandler):
"""Delete an attachment.
"""
# No page for single attachments.

@require_permission(BaseHandler.PERMISSION_ALL)
def delete(self, contest_id, attachment_id):
attachment = self.safe_get_item(ContestAttachment, attachment_id)
self.contest = self.safe_get_item(Contest, contest_id)

# Protect against URLs providing incompatible parameters.
if attachment.contest is not self.contest:
raise tornado_web.HTTPError(404)

self.sql_session.delete(attachment)
self.try_commit()

# Page to redirect to.
self.write("%s" % self.contest.id)


class OverviewHandler(BaseHandler):
"""Home page handler, with queue and workers statuses.
Expand Down
13 changes: 13 additions & 0 deletions cms/server/admin/templates/add_contest_attachment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}

{% block core %}
<div class="core_title">
<h1><a href="{{ url("contest", contest.id) }}">{{ contest.description }} ({{ contest.name }})</a> - Upload attachment</h1>
</div>
<form enctype="multipart/form-data" action="{{ url("contest", contest.id, "attachments", "add") }}" method="POST">
{{ xsrf_form_html|safe }}
<input type="file" name="attachment"/><br/>
<input type="submit" value="Upload">
<input type="Reset" >
</form>
{% endblock core %}
21 changes: 21 additions & 0 deletions cms/server/admin/templates/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,27 @@ <h1>Contest configuration</h1>
</td>
<td><input type="text" name="score_precision" value="{{ contest.score_precision }}"></td>
</tr>
<tr>
<td>
<span class="info" title="Attachments for this contest. Contestants can view and download attachments from the overview page."></span>
Attachments
{% if admin.permission_all %}
[<a href="{{ url("contest", contest.id, "attachments", "add") }}">add</a>]
{% endif %}
</td>
<td>
{% if contest.attachments|length == 0 %}
No attachments.
{% else %}
{% for attachment in contest.attachments.values() %}
<div class="attachment">
<a href="{{ url("file", attachment.digest, attachment.filename) }}">{{ attachment.filename }}</a>
- <a onclick="CMS.AWSUtils.ajax_delete('{{ url("contest", contest.id, "attachment", attachment.id) }}'); ">Delete</a>
</div>
{% endfor %}
{% endif %}
</td>
</tr>

<tr><td colspan=2><h2>Logging in</h2></td></tr>
<tr>
Expand Down
3 changes: 3 additions & 0 deletions cms/server/contest/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Copyright © 2013 Bernard Blackham <[email protected]>
# Copyright © 2014 Artem Iglikov <[email protected]>
# Copyright © 2014 Fabian Gundlach <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
# Copyright © 2015-2018 William Di Luigi <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -31,6 +32,7 @@
LogoutHandler, \
RegistrationHandler, \
StartHandler, \
ContestAttachmentViewHandler, \
NotificationsHandler, \
PrintingHandler, \
DocumentationHandler
Expand Down Expand Up @@ -62,6 +64,7 @@
(r"/logout", LogoutHandler),
(r"/register", RegistrationHandler),
(r"/start", StartHandler),
(r"/attachments/(.*)", ContestAttachmentViewHandler),
(r"/notifications", NotificationsHandler),
(r"/printing", PrintingHandler),
(r"/documentation", DocumentationHandler),
Expand Down
25 changes: 24 additions & 1 deletion cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Copyright © 2013 Bernard Blackham <[email protected]>
# Copyright © 2014 Artem Iglikov <[email protected]>
# Copyright © 2014 Fabian Gundlach <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
# Copyright © 2015-2018 William Di Luigi <[email protected]>
# Copyright © 2021 Grace Hawkins <[email protected]>
#
Expand Down Expand Up @@ -49,7 +50,8 @@
UnacceptablePrintJob
from cmscommon.crypto import hash_password, validate_password
from cmscommon.datetime import make_datetime, make_timestamp
from .contest import ContestHandler
from cmscommon.mimetypes import get_type_for_file_name
from .contest import ContestHandler, FileHandler
from ..phase_management import actual_phase_required


Expand Down Expand Up @@ -271,6 +273,27 @@ def post(self):
self.redirect(self.contest_url())


class ContestAttachmentViewHandler(FileHandler):
"""Shows an attachment file of a task in the contest.
"""
@tornado_web.authenticated
@actual_phase_required(0, 3)
@multi_contest
def get(self, filename):
if filename not in self.contest.attachments:
raise tornado_web.HTTPError(404)

attachment = self.contest.attachments[filename].digest
self.sql_session.close()

mimetype = get_type_for_file_name(filename)
if mimetype is None:
mimetype = 'application/octet-stream'

self.fetch(attachment, mimetype, filename)


class NotificationsHandler(ContestHandler):
"""Displays notifications.
Expand Down
Loading

0 comments on commit 0559bb4

Please sign in to comment.