Skip to content

Commit 050f5d1

Browse files
committed
Add opaque_id to user tests
1 parent 821fed5 commit 050f5d1

14 files changed

Lines changed: 111 additions & 51 deletions

File tree

cms/db/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181

8282
# Instantiate or import these objects.
8383

84-
version = 46
84+
version = 47
8585

8686
engine = create_engine(config.database.url, echo=config.database.debug,
8787
pool_timeout=60, pool_recycle=120)

cms/db/submission.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"""
2828

2929
from datetime import datetime
30-
import random
3130
from sqlalchemy import Boolean
3231
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
3332
from sqlalchemy.orm import relationship
@@ -187,25 +186,6 @@ def tokened(self) -> bool:
187186
"""
188187
return self.token is not None
189188

190-
@classmethod
191-
def generate_opaque_id(cls, session, participation_id):
192-
randint_upper_bound = 2**63-1
193-
194-
opaque_id = random.randint(0, randint_upper_bound)
195-
196-
# Note that in theory this may cause the transaction to fail by
197-
# generating a non-actually-unique ID. This is however extremely
198-
# unlikely (prob. ~num_parallel_submissions_per_contestant^2/2**63).
199-
while (session
200-
.query(Submission)
201-
.filter(Submission.participation_id == participation_id)
202-
.filter(Submission.opaque_id == opaque_id)
203-
.first()
204-
is not None):
205-
opaque_id = random.randint(0, randint_upper_bound)
206-
207-
return opaque_id
208-
209189

210190
class File(Base):
211191
"""Class to store information about one file submitted within a

cms/db/usertest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,20 @@ class UserTest(Base):
4040
4141
"""
4242
__tablename__ = 'user_tests'
43+
__table_args__ = (
44+
UniqueConstraint("participation_id", "opaque_id"),
45+
)
4346

4447
# Auto increment primary key.
4548
id: int = Column(
4649
Integer,
4750
primary_key=True)
4851

52+
# Opaque ID to be used to refer to this user test.
53+
opaque_id: int = Column(
54+
BigInteger,
55+
nullable=False)
56+
4957
# User and Contest, thus Participation (id and object) that did the
5058
# submission.
5159
participation_id: int = Column(

cms/db/util.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
2424
"""
2525

26+
import random
2627
import sys
2728
import logging
2829

@@ -372,3 +373,24 @@ def enumerate_files(
372373
digests = set(r[0] for r in session.execute(union(*queries)))
373374
digests.discard(Digest.TOMBSTONE)
374375
return digests
376+
377+
def generate_opaque_id(
378+
cls: type[Submission | UserTest], session: Session, participation_id: int
379+
):
380+
randint_upper_bound = 2**63-1
381+
382+
opaque_id = random.randint(0, randint_upper_bound)
383+
384+
# Note that in theory this may cause the transaction to fail by
385+
# generating a non-actually-unique ID. This is however extremely
386+
# unlikely (prob. ~num_parallel_submissions_per_contestant^2/2**63).
387+
while (
388+
session.query(cls)
389+
.filter(cls.participation_id == participation_id)
390+
.filter(cls.opaque_id == opaque_id)
391+
.first()
392+
is not None
393+
):
394+
opaque_id = random.randint(0, randint_upper_bound)
395+
396+
return opaque_id

cms/server/contest/handlers/contest.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,12 @@ def get_task(self, task_name: str) -> Task | None:
263263
.one_or_none()
264264

265265
def get_submission(self, task: Task, opaque_id: str | int) -> Submission | None:
266-
"""Return the num-th contestant's submission on the given task.
266+
"""Return a contestant's specific submission on the given task.
267267
268268
task: a task for the contest that is being served.
269-
submission_num: a positive number, in decimal encoding.
269+
opaque_id: submission's opaque_id
270270
271-
return: the submission_num-th submission
272-
(1-based), in chronological order, that was sent by the
273-
currently logged in contestant on the given task (None if
274-
not found).
271+
return: Submission with this opaque_id, or None if not found.
275272
276273
"""
277274
return self.sql_session.query(Submission) \
@@ -280,22 +277,19 @@ def get_submission(self, task: Task, opaque_id: str | int) -> Submission | None:
280277
.filter(Submission.opaque_id == int(opaque_id)) \
281278
.first()
282279

283-
def get_user_test(self, task: Task, user_test_num: int) -> UserTest | None:
284-
"""Return the num-th contestant's test on the given task.
280+
def get_user_test(self, task: Task, opaque_id: str | int) -> UserTest | None:
281+
"""Return a contestant's specific user test on the given task.
285282
286283
task: a task for the contest that is being served.
287-
user_test_num: a positive number, in decimal encoding.
284+
opaque_id: user test's opaque_id
288285
289-
return: the user_test_num-th user test, in
290-
chronological order, that was sent by the currently logged
291-
in contestant on the given task (None if not found).
286+
return: User test with this opaque_id, or None if not found.
292287
293288
"""
294289
return self.sql_session.query(UserTest) \
295290
.filter(UserTest.participation == self.current_user) \
296291
.filter(UserTest.task == task) \
297-
.order_by(UserTest.timestamp) \
298-
.offset(int(user_test_num) - 1) \
292+
.filter(UserTest.opaque_id == int(opaque_id)) \
299293
.first()
300294

301295
def add_notification(

cms/server/contest/handlers/taskusertest.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,15 @@ class UserTestDetailsHandler(ContestHandler):
224224
@api_login_required
225225
@actual_phase_required(0)
226226
@multi_contest
227-
def get(self, task_name, user_test_num):
227+
def get(self, task_name, opaque_id):
228228
if not self.r_params["testing_enabled"]:
229229
raise tornado.web.HTTPError(404)
230230

231231
task = self.get_task(task_name)
232232
if task is None:
233233
raise tornado.web.HTTPError(404)
234234

235-
user_test = self.get_user_test(task, user_test_num)
235+
user_test = self.get_user_test(task, opaque_id)
236236
if user_test is None:
237237
raise tornado.web.HTTPError(404)
238238

@@ -249,15 +249,15 @@ class UserTestIOHandler(FileHandler):
249249
@tornado.web.authenticated
250250
@actual_phase_required(0)
251251
@multi_contest
252-
def get(self, task_name, user_test_num, io):
252+
def get(self, task_name, opaque_id, io):
253253
if not self.r_params["testing_enabled"]:
254254
raise tornado.web.HTTPError(404)
255255

256256
task = self.get_task(task_name)
257257
if task is None:
258258
raise tornado.web.HTTPError(404)
259259

260-
user_test = self.get_user_test(task, user_test_num)
260+
user_test = self.get_user_test(task, opaque_id)
261261
if user_test is None:
262262
raise tornado.web.HTTPError(404)
263263

@@ -283,15 +283,15 @@ class UserTestFileHandler(FileHandler):
283283
@tornado.web.authenticated
284284
@actual_phase_required(0)
285285
@multi_contest
286-
def get(self, task_name, user_test_num, filename):
286+
def get(self, task_name, opaque_id, filename):
287287
if not self.r_params["testing_enabled"]:
288288
raise tornado.web.HTTPError(404)
289289

290290
task = self.get_task(task_name)
291291
if task is None:
292292
raise tornado.web.HTTPError(404)
293293

294-
user_test = self.get_user_test(task, user_test_num)
294+
user_test = self.get_user_test(task, opaque_id)
295295
if user_test is None:
296296
raise tornado.web.HTTPError(404)
297297

cms/server/contest/submission/workflow.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import logging
3333
import typing
3434

35+
from cms.db.util import generate_opaque_id
36+
3537
if typing.TYPE_CHECKING:
3638
from tornado.httputil import HTTPFile
3739

@@ -270,7 +272,7 @@ def accept_submission(
270272

271273
submission = Submission(
272274
timestamp=timestamp,
273-
opaque_id=Submission.generate_opaque_id(sql_session, participation.id),
275+
opaque_id=generate_opaque_id(Submission, sql_session, participation.id),
274276
language=language.name if language is not None else None,
275277
task=task,
276278
participation=participation,
@@ -486,6 +488,7 @@ def accept_user_test(
486488

487489
user_test = UserTest(
488490
timestamp=timestamp,
491+
opaque_id=generate_opaque_id(UserTest, sql_session, participation.id),
489492
language=language.name if language is not None else None,
490493
input=digests["input"],
491494
participation=participation,

cms/server/contest/templates/macro/submission.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
{% else %}
8888
{% for s in submissions|sort(attribute="timestamp")|reverse %}
8989
{% if s.official == official %}
90-
{# loop.revindex is broken: https://github.com/pallets/jinja/issues/794 #}
9190
{{ row(
9291
url,
9392
contest_url,

cms/server/contest/templates/test_interface.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ <h2 style="margin: 40px 0 10px">{% trans %}Previous tests{% endtrans %}</h2>
214214
</thead>
215215
<tbody>
216216
{% for t in user_tests[task.id]|sort(attribute="timestamp")|reverse %}
217-
{# loop.revindex is broken: https://github.com/pallets/jinja/issues/794 #}
218-
{% set t_idx = user_tests[task.id]|length - loop.index0 %}
219217
{% set tr = t.get_result(t.task.active_dataset) or undefined %}
220218
{% include "user_test_row.html" %}
221219
{% else %}

cms/server/contest/templates/user_test_row.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set status = tr.get_status() if tr is defined else UserTestResult.COMPILING %}
2-
<tr data-user-test="{{ t_idx }}" data-status="{{ status }}">
2+
<tr data-user-test="{{ t.opaque_id }}" data-status="{{ status }}">
33
{% if show_date %}
44
<td class="datetime">{{ t.timestamp|format_datetime }}</td>
55
{% else %}
@@ -37,7 +37,7 @@
3737
</td>
3838
{% endif %}
3939
<td class="input">
40-
<a class="btn" href="{{ contest_url("tasks", task.name, "tests", t_idx, "input") }}">
40+
<a class="btn" href="{{ contest_url("tasks", task.name, "tests", t.opaque_id, "input") }}">
4141
{% trans %}Download{% endtrans %}
4242
</a>
4343
</td>
@@ -52,7 +52,7 @@
5252
{% trans %}N/A{% endtrans %}
5353
</a>
5454
{% else %}
55-
<a class="btn btn-primary" href="{{ contest_url("tasks", task.name, "tests", t_idx, "output") }}">
55+
<a class="btn btn-primary" href="{{ contest_url("tasks", task.name, "tests", t.opaque_id, "output") }}">
5656
{% trans %}Download{% endtrans %}
5757
</a>
5858
{% endif %}
@@ -70,7 +70,7 @@
7070
{% elif files|length == 1 %}
7171
{% set filename = next(iter(t.files.keys())) %}
7272
{% set real_filename = filename|replace(".%l", (t.language|to_language).source_extension) %}
73-
<a class="btn" href="{{ contest_url("tasks", task.name, "tests", t_idx, "files", real_filename) }}">
73+
<a class="btn" href="{{ contest_url("tasks", task.name, "tests", t.opaque_id, "files", real_filename) }}">
7474
{% trans %}Download{% endtrans %}
7575
</a>
7676
{% else %}
@@ -87,7 +87,7 @@
8787
{% set real_filename = filename %}
8888
{% endif %}
8989
<li>
90-
<a href="{{ contest_url("tasks", task.name, "tests", t_idx, "files", real_filename) }}">
90+
<a href="{{ contest_url("tasks", task.name, "tests", t.opaque_id, "files", real_filename) }}">
9191
{{ real_filename }}
9292
</a>
9393
</li>

0 commit comments

Comments
 (0)