From c43c19542023a02e56a966444690fed9dc697681 Mon Sep 17 00:00:00 2001 From: Andrey Vihrov Date: Sun, 4 Nov 2018 22:31:59 +0100 Subject: [PATCH 01/20] Fix whitespace in consecutive {% trans %} blocks Due to active Jinja whitespace handling, whitespace was removed between consecutive {% trans %} blocks, resulting in translated sentences being glued together. Fix this by disabling the lstrip_blocks behaviour in relevant places. --- cms/server/contest/templates/overview.html | 20 +++++++++---------- .../contest/templates/task_description.html | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html index 527fab45a..197f0ebbc 100644 --- a/cms/server/contest/templates/overview.html +++ b/cms/server/contest/templates/overview.html @@ -82,17 +82,17 @@

{% trans %}General information{% endtrans %}

{% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {% trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %}

{% elif tokens_contest == TOKEN_MODE_INFINITE %}

{% trans %}You have a distinct set of tokens for each task.{% endtrans %} - {% trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %} + {%+ trans type_pl=_("tokens") %}You can find the rules for the {{ type_pl }} on each task's description page.{% endtrans %}

{% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {% trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %}

{% elif tokens_tasks == TOKEN_MODE_INFINITE %}

@@ -102,7 +102,7 @@

{% trans %}General information{% endtrans %}

{% trans %}You can see the detailed result of a submission by using a token on it.{% endtrans %} - {% trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %}

{% else %}

@@ -113,7 +113,7 @@

{% trans %}General information{% endtrans %}

{% trans %}You can see the detailed result of a submission by using two tokens on it, one of each type.{% endtrans %} - {% trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %} + {%+ trans %}Your score for each task will be the maximum among the tokened submissions and the last one.{% endtrans %}

{% endif %} {% endif %} @@ -142,23 +142,23 @@

{% trans %}General information{% endtrans %}

{% if actual_phase == -2 %} {% trans %}As soon as the contest starts you can choose to start your time frame.{% endtrans %} - {% trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} {% elif actual_phase == -1 %} {% trans %}By clicking on the button below you can start your time frame.{% endtrans %} - {% trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {%+ trans %}Once you start, you can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} {% elif actual_phase == 0 %} {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }}.{% endtrans %} - {% trans %}You can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} + {%+ trans %}You can submit solutions until the end of the time frame or until the end of the contest, whatever comes first.{% endtrans %} {% elif actual_phase == +1 %} {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} - {% trans %}There's nothing you can do now.{% endtrans %} + {%+ trans %}There's nothing you can do now.{% endtrans %} {% elif actual_phase == +2 %} {% if participation.starting_time is none %} {% trans %}You never started your time frame. Now it's too late.{% endtrans %} {% else %} {% trans start_time=participation.starting_time|format_datetime_smart %}You started your time frame at {{ start_time }} and you already finished it.{% endtrans %} {% endif %} - {% trans %}There's nothing you can do now.{% endtrans %} + {%+ trans %}There's nothing you can do now.{% endtrans %} {% endif %}

diff --git a/cms/server/contest/templates/task_description.html b/cms/server/contest/templates/task_description.html index 761e54ecf..6d65c66f6 100644 --- a/cms/server/contest/templates/task_description.html +++ b/cms/server/contest/templates/task_description.html @@ -34,8 +34,8 @@

{% trans %}Statement{% endtrans %}

{% trans %}The statement for this task is available in multiple versions, in different languages.{% endtrans %} - {% trans %}You can see (and download) all of them using the list on the right.{% endtrans %} - {% trans %}Some suggested translations follow.{% endtrans %} + {%+ trans %}You can see (and download) all of them using the list on the right.{% endtrans %} + {%+ trans %}Some suggested translations follow.{% endtrans %}

{% for statement in itervalues(task.statements)|sort(attribute="language") %} {% if statement.language in task.primary_statements %} @@ -152,7 +152,7 @@

{% trans %}Some details{% endtrans %}

{% trans %}Remember that to see the detailed result of a submission you need to use both a contest-token and a task-token.{% endtrans %} - {% trans type_pl=_("contest-tokens"), contest_root=contest_url() %}You can find the rules for the {{ type_pl }} on the contest overview page.{% endtrans %} + {%+ trans type_pl=_("contest-tokens"), contest_root=contest_url() %}You can find the rules for the {{ type_pl }} on the contest overview page.{% endtrans %}

{% endif %} From 633959ad6a0133fd87425071016f9c2dba9fe4db Mon Sep 17 00:00:00 2001 From: Andrey Vihrov Date: Sun, 4 Nov 2018 22:34:24 +0100 Subject: [PATCH 02/20] HTML fixes Also add missing style for the empty print job table case. Perhaps the table may benefit from more extensive styling, like with the submission and the user test tables, but this change fixes the immediate visual problem. --- cms/server/contest/static/cws_style.css | 7 +++++++ cms/server/contest/templates/overview.html | 2 +- cms/server/contest/templates/printing.html | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cms/server/contest/static/cws_style.css b/cms/server/contest/static/cws_style.css index fba5321b3..23e1bf5c6 100644 --- a/cms/server/contest/static/cws_style.css +++ b/cms/server/contest/static/cws_style.css @@ -996,6 +996,13 @@ td.token_rules p:last-child { width: 100px; } +/** Printing interface */ + +#printjob_list tbody tr td.no_printjobs { + font-style: italic; + text-align: center !important; +} + /* Contest selection page */ .contest-list { diff --git a/cms/server/contest/templates/overview.html b/cms/server/contest/templates/overview.html index 197f0ebbc..d3fc9f2dc 100644 --- a/cms/server/contest/templates/overview.html +++ b/cms/server/contest/templates/overview.html @@ -166,7 +166,7 @@

{% trans %}General information{% endtrans %}

{{ xsrf_form_html|safe }} - +
{% endif %} diff --git a/cms/server/contest/templates/printing.html b/cms/server/contest/templates/printing.html index 2eb986c50..80f7498df 100644 --- a/cms/server/contest/templates/printing.html +++ b/cms/server/contest/templates/printing.html @@ -89,4 +89,6 @@

{% trans %}Previous print jobs{% endtrans %}

+
+ {% endblock core %} From 2df497eb65a0b86dae323a6b8edf4cd6197df762 Mon Sep 17 00:00:00 2001 From: Stefano Maggiolo Date: Sun, 4 Nov 2018 22:08:46 +0000 Subject: [PATCH 03/20] Fix potential bug in removing task from contest If alchemy returns a weird order for the tasks, an integrity error on task.num could happen. Thanks to Romeo Rizzi for pointing this out and fixing it in master. --- cms/server/admin/handlers/contesttask.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cms/server/admin/handlers/contesttask.py b/cms/server/admin/handlers/contesttask.py index 50f3a603d..bfd70c425 100644 --- a/cms/server/admin/handlers/contesttask.py +++ b/cms/server/admin/handlers/contesttask.py @@ -86,13 +86,16 @@ def post(self, contest_id): # Unassign the task to the contest. task.contest = None task.num = None # not strictly necessary + self.sql_session.flush() # Decrease by 1 the num of every subsequent task. for t in self.sql_session.query(Task)\ .filter(Task.contest == self.contest)\ .filter(Task.num > task_num)\ + .order_by(Task.num)\ .all(): t.num -= 1 + self.sql_session.flush() elif operation == self.MOVE_UP: task2 = self.sql_session.query(Task)\ From c3726efc31971d616763a580112674f268e6dfc7 Mon Sep 17 00:00:00 2001 From: Luca Chiodini Date: Tue, 13 Nov 2018 15:08:07 +0100 Subject: [PATCH 04/20] Fix encoding issues in cmscommon/terminal.py curses produces bytes, that should be decoded in a string before printing. --- cmscommon/terminal.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmscommon/terminal.py b/cmscommon/terminal.py index a4d913e81..c608f86f0 100644 --- a/cmscommon/terminal.py +++ b/cmscommon/terminal.py @@ -3,6 +3,7 @@ # Contest Management System - http://cms-dev.github.io/ # Copyright © 2015 Luca Versari +# Copyright © 2018 Luca Chiodini # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -119,17 +120,18 @@ def move_cursor(direction, amount=1, stream=sys.stdout, erase=False): """ if stream.isatty(): if direction == directions.UP: - print(curses.tparm(curses.tigetstr("cuu"), amount), + print(curses.tparm(curses.tigetstr("cuu"), amount).decode('ascii'), file=stream, end='') elif direction == directions.DOWN: - print(curses.tparm(curses.tigetstr("cud"), amount), + print(curses.tparm(curses.tigetstr("cud"), amount).decode('ascii'), file=stream, end='') elif direction == directions.LEFT: - print(curses.tparm(curses.tigetstr("cub"), amount), + print(curses.tparm(curses.tigetstr("cub"), amount).decode('ascii'), file=stream, end='') elif direction == directions.RIGHT: - print(curses.tparm(curses.tigetstr("cuf"), amount), + print(curses.tparm(curses.tigetstr("cuf"), amount).decode('ascii'), file=stream, end='') if erase: - print(curses.tparm(curses.tigetstr("el")), file=stream, end='') + print(curses.tparm(curses.tigetstr("el")).decode('ascii'), + file=stream, end='') stream.flush() From 78bd5dd90209f16c72bdd0839c42f673f576951c Mon Sep 17 00:00:00 2001 From: Stefano Maggiolo Date: Sun, 2 Dec 2018 22:32:29 +0000 Subject: [PATCH 05/20] Fix bug caused by shadowing of the admin render param Although scary since editing an admin made it look like being logged in as that admin, that was only a rendering issue - further request would be evaluated against the real logged in admin. Thanks to @andreyv and @PPakalns for the bugreport and the investigation. --- cms/server/admin/handlers/admin.py | 4 ++-- cms/server/admin/templates/admin.html | 6 +++--- .../admin/templates/fragments/admin_form.html | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cms/server/admin/handlers/admin.py b/cms/server/admin/handlers/admin.py index 099bac19b..bea231541 100644 --- a/cms/server/admin/handlers/admin.py +++ b/cms/server/admin/handlers/admin.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Contest Management System - http://cms-dev.github.io/ -# Copyright © 2015 Stefano Maggiolo +# Copyright © 2015-2018 Stefano Maggiolo # Copyright © 2016 Myungwoo Chun # # This program is free software: you can redistribute it and/or modify @@ -129,7 +129,7 @@ def get(self, admin_id): admin = self.safe_get_item(Admin, admin_id) self.r_params = self.render_params() - self.r_params["admin"] = admin + self.r_params["admin_being_edited"] = admin self.render("admin.html", **self.r_params) @require_permission(BaseHandler.PERMISSION_ALL, self_allowed=True) diff --git a/cms/server/admin/templates/admin.html b/cms/server/admin/templates/admin.html index 81d990aa5..da447e3a6 100644 --- a/cms/server/admin/templates/admin.html +++ b/cms/server/admin/templates/admin.html @@ -3,11 +3,11 @@ {% block core %}
-

{{ admin.username }} ({{ admin.name }})

+

{{ admin_being_edited.username }} ({{ admin_being_edited.name }})

-
+ {{ xsrf_form_html|safe }}

Admin information

@@ -24,7 +24,7 @@

Admin information

{% if admin.permission_all %}
-Delete this admin +Delete this admin {% endif %} {% endblock core %} diff --git a/cms/server/admin/templates/fragments/admin_form.html b/cms/server/admin/templates/fragments/admin_form.html index 9b02027fe..25f8ddf7e 100644 --- a/cms/server/admin/templates/fragments/admin_form.html +++ b/cms/server/admin/templates/fragments/admin_form.html @@ -1,11 +1,11 @@ -{# This snippet shows a form to add/edit an admin; if admin is set, its data will be used as defaults. #} +{# This snippet shows a form to add/edit an admin; if admin_being_edited is set, its data will be used as defaults. #} -{% if admin is defined %} - {% set admin_username = admin.username %} - {% set admin_name = admin.name %} - {% set admin_permission_all = admin.permission_all %} - {% set admin_permission_messaging = admin.permission_messaging %} - {% set admin_enabled = admin.enabled %} +{% if admin_being_edited is defined %} + {% set admin_username = admin_being_edited.username %} + {% set admin_name = admin_being_edited.name %} + {% set admin_permission_all = admin_being_edited.permission_all %} + {% set admin_permission_messaging = admin_being_edited.permission_messaging %} + {% set admin_enabled = admin_being_edited.enabled %} {% else %} {% set admin_username = "" %} {% set admin_name = "" %} From 89fae0c2a2e8a103ba98ef1f9a6dff71f2f143aa Mon Sep 17 00:00:00 2001 From: Andrey Vihrov Date: Mon, 3 Dec 2018 21:57:49 +0200 Subject: [PATCH 06/20] Fix reporting of used memory In previous psutil versions the "used" field of memory stats included also buffers and cache. However, this is no longer the case starting from psutil 4.4. Now the output matches the free(1) utility, which reports used memory without buffers and cache. Reference: giampaolo/psutil@ec7957c7bfd2abef1042bf4f2f72f586400c0d24 --- cms/service/ResourceService.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cms/service/ResourceService.py b/cms/service/ResourceService.py index b06dd7b3d..7c6d52a14 100644 --- a/cms/service/ResourceService.py +++ b/cms/service/ResourceService.py @@ -343,13 +343,11 @@ def percent_from_delta(v): data["cpu"]["num_cpu"] = psutil.cpu_count() self._prev_cpu_times = cpu_times - # Memory. The following relations hold (I think... I only - # verified them experimentally on a swap-less system): - # * vmem.free == vmem.available - vmem.cached - vmem.buffers - # * vmem.total == vmem.used + vmem.free - # That means that cache & buffers are counted both in .used - # and in .available. We want to partition the memory into - # types that sum up to vmem.total. + # Memory. The following equality should hold, as per psutil >= 4.4: + # vmem.total = vmem.used + vmem.buffers + vmem.cached + vmem.free + # Although psutil documentation describes the "used" field as + # platform-dependent, on Linux specifically it matches the output + # of the free(1) utility. vmem = psutil.virtual_memory() swap = psutil.swap_memory() data["memory"] = { @@ -357,7 +355,7 @@ def percent_from_delta(v): "ram_available": vmem.free / B_TO_MB, "ram_cached": vmem.cached / B_TO_MB, "ram_buffers": vmem.buffers / B_TO_MB, - "ram_used": (vmem.used - vmem.cached - vmem.buffers) / B_TO_MB, + "ram_used": vmem.used / B_TO_MB, "swap_total": swap.total / B_TO_MB, "swap_available": swap.free / B_TO_MB, "swap_used": swap.used / B_TO_MB, From ce567752658c268ceaf41956d4882ccab3fbc63c Mon Sep 17 00:00:00 2001 From: Stefano Maggiolo Date: Tue, 27 Nov 2018 09:46:02 +0000 Subject: [PATCH 07/20] Fix loading in task score not going away after a compilation failed Fixes #1077 --- cms/server/contest/handlers/tasksubmission.py | 49 ++++++++++--------- .../contest/templates/task_submissions.html | 13 +++-- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/cms/server/contest/handlers/tasksubmission.py b/cms/server/contest/handlers/tasksubmission.py index 5bfffc48b..fceb08a83 100644 --- a/cms/server/contest/handlers/tasksubmission.py +++ b/cms/server/contest/handlers/tasksubmission.py @@ -182,6 +182,14 @@ def get(self, task_name): class SubmissionStatusHandler(ContestHandler): + STATUS_TEXT = { + SubmissionResult.COMPILING: N_("Compiling..."), + SubmissionResult.COMPILATION_FAILED: N_("Compilation failed"), + SubmissionResult.EVALUATING: N_("Evaluating..."), + SubmissionResult.SCORING: N_("Scoring..."), + SubmissionResult.SCORED: N_("Evaluated"), + } + refresh_cookie = False def add_task_score(self, participation, task, data): @@ -242,39 +250,36 @@ def get(self, task_name, submission_num): else: data["status"] = sr.get_status() - if data["status"] == SubmissionResult.COMPILING: - data["status_text"] = self._("Compiling...") - elif data["status"] == SubmissionResult.COMPILATION_FAILED: - data["status_text"] = self._("Compilation failed") - elif data["status"] == SubmissionResult.EVALUATING: - data["status_text"] = self._("Evaluating...") - elif data["status"] == SubmissionResult.SCORING: - data["status_text"] = self._("Scoring...") - elif data["status"] == SubmissionResult.SCORED: - data["status_text"] = self._("Evaluated") + data["status_text"] = self._(self.STATUS_TEXT[data["status"]]) + + # For terminal statuses we add the scores information to the payload. + if data["status"] == SubmissionResult.COMPILATION_FAILED \ + or data["status"] == SubmissionResult.SCORED: self.add_task_score(submission.participation, task, data) score_type = task.active_dataset.score_type_object if score_type.max_public_score > 0: data["max_public_score"] = \ round(score_type.max_public_score, task.score_precision) - data["public_score"] = \ - round(sr.public_score, task.score_precision) - data["public_score_message"] = score_type.format_score( - sr.public_score, score_type.max_public_score, - sr.public_score_details, task.score_precision, - translation=self.translation) + if data["status"] == SubmissionResult.SCORED: + data["public_score"] = \ + round(sr.public_score, task.score_precision) + data["public_score_message"] = score_type.format_score( + sr.public_score, score_type.max_public_score, + sr.public_score_details, task.score_precision, + translation=self.translation) if score_type.max_public_score < score_type.max_score \ and (submission.token is not None or self.r_params["actual_phase"] == 3): data["max_score"] = \ round(score_type.max_score, task.score_precision) - data["score"] = \ - round(sr.score, task.score_precision) - data["score_message"] = score_type.format_score( - sr.score, score_type.max_score, - sr.score_details, task.score_precision, - translation=self.translation) + if data["status"] == SubmissionResult.SCORED: + data["score"] = \ + round(sr.score, task.score_precision) + data["score_message"] = score_type.format_score( + sr.score, score_type.max_score, + sr.score_details, task.score_precision, + translation=self.translation) self.write(data) diff --git a/cms/server/contest/templates/task_submissions.html b/cms/server/contest/templates/task_submissions.html index 655b64aea..41834e884 100644 --- a/cms/server/contest/templates/task_submissions.html +++ b/cms/server/contest/templates/task_submissions.html @@ -33,6 +33,11 @@ modal.modal("show"); }); +function is_status_terminal (status) { + return status == {{ SubmissionResult.COMPILATION_FAILED }} + || status == {{ SubmissionResult.SCORED }}; +}; + function get_score_class (score, max_score) { if (score <= 0) { return "score_0"; @@ -85,15 +90,15 @@ var row = $("#submission_list tbody tr[data-submission=\"" + submission_id + "\"]"); row.attr("data-status", data["status"]); row.children("td.status").text(data["status_text"]); - if (data["status"] != {{ SubmissionResult.COMPILATION_FAILED }} - && data["status"] != {{ SubmissionResult.SCORED }}) { + var terminal_status = is_status_terminal(data["status"]); + if (!terminal_status) { row.children("td.status").append( $("")); } else { row.children("td.status").append( $("{% trans %}details{% endtrans %}")); } - if (data["status"] == {{ SubmissionResult.SCORED }}) { + if (terminal_status) { update_score( row.children("td.public_score"), $("#task_score_public"), data["public_score"], data["public_score_message"], @@ -106,7 +111,7 @@ data["task_tokened_score"], data["task_tokened_score_message"], data["task_score_is_partial"], data["max_score"]); {% endif %} - } else if (data["status"] != {{ SubmissionResult.COMPILATION_FAILED }}) { + } else { schedule_update_scores(submission_id); } }; From 741997068287e5fa575c4b41ca36beb106783f17 Mon Sep 17 00:00:00 2001 From: Stefano Maggiolo Date: Sat, 24 Nov 2018 23:06:32 +0000 Subject: [PATCH 08/20] Fix score-dependant rendering in CWS when rounding happens With this approach, JS always sees rounded scores, whereas Python has to round. --- cms/grading/scoring.py | 15 +++++++++----- cms/server/admin/handlers/contestranking.py | 3 +-- cms/server/contest/formatting.py | 4 +++- cms/server/contest/handlers/tasksubmission.py | 8 ++++---- .../contest/templates/submission_row.html | 4 ++-- .../contest/templates/task_submissions.html | 4 ++-- .../unit_tests/grading/scoring_test.py | 20 ++++++++++++++++--- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/cms/grading/scoring.py b/cms/grading/scoring.py index 15ed04421..6969e79e0 100644 --- a/cms/grading/scoring.py +++ b/cms/grading/scoring.py @@ -109,7 +109,8 @@ def compare(a, b): # Computing global scores (for ranking). -def task_score(participation, task, public=False, only_tokened=False): +def task_score(participation, task, + public=False, only_tokened=False, rounded=False): """Return the score of a contest's user on a task. participation (Participation): the user and contest for which to @@ -122,6 +123,7 @@ def task_score(participation, task, public=False, only_tokened=False): at the results of tokened submissions (that is, the score that the user would obtain if all non-tokened submissions scored 0.0, or equivalently had not been scored yet). + rounded (bool): if True, round the score to the task's score_precision. return ((float, bool)): the score of user on task, and True if not all submissions of the participation in the task have been scored. @@ -169,13 +171,16 @@ def task_score(participation, task, public=False, only_tokened=False): score_details_tokened.append((score, score_details, s.tokened())) if task.score_mode == SCORE_MODE_MAX: - return _task_score_max(score_details_tokened), partial - if task.score_mode == SCORE_MODE_MAX_SUBTASK: - return _task_score_max_subtask(score_details_tokened), partial + score = _task_score_max(score_details_tokened) + elif task.score_mode == SCORE_MODE_MAX_SUBTASK: + score = _task_score_max_subtask(score_details_tokened) elif task.score_mode == SCORE_MODE_MAX_TOKENED_LAST: - return _task_score_max_tokened_last(score_details_tokened), partial + score = _task_score_max_tokened_last(score_details_tokened) else: raise ValueError("Unknown score mode '%s'" % task.score_mode) + if rounded: + score = round(score, task.score_precision) + return score, partial def _task_score_max_tokened_last(score_details_tokened): diff --git a/cms/server/admin/handlers/contestranking.py b/cms/server/admin/handlers/contestranking.py index 6ea250add..ba42f622f 100644 --- a/cms/server/admin/handlers/contestranking.py +++ b/cms/server/admin/handlers/contestranking.py @@ -74,8 +74,7 @@ def get(self, contest_id, format="online"): total_score = 0.0 partial = False for task in self.contest.tasks: - t_score, t_partial = task_score(p, task) - t_score = round(t_score, task.score_precision) + t_score, t_partial = task_score(p, task, rounded=True) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial diff --git a/cms/server/contest/formatting.py b/cms/server/contest/formatting.py index ca0c6ffcb..4ba1be0c7 100644 --- a/cms/server/contest/formatting.py +++ b/cms/server/contest/formatting.py @@ -134,7 +134,7 @@ def format_token_rules(tokens, t_type=None, translation=DEFAULT_TRANSLATION): return result -def get_score_class(score, max_score): +def get_score_class(score, max_score, score_precision): """Return a CSS class to visually represent the score/max_score score (float): the score of the submission. @@ -143,6 +143,8 @@ def get_score_class(score, max_score): return (unicode): class name """ + score = round(score, score_precision) + max_score = round(max_score, score_precision) if score <= 0: return "score_0" elif score >= max_score: diff --git a/cms/server/contest/handlers/tasksubmission.py b/cms/server/contest/handlers/tasksubmission.py index fceb08a83..07b57fdbc 100644 --- a/cms/server/contest/handlers/tasksubmission.py +++ b/cms/server/contest/handlers/tasksubmission.py @@ -135,9 +135,9 @@ def get(self, task_name): .all() public_score, is_public_score_partial = task_score( - participation, task, public=True) + participation, task, public=True, rounded=True) tokened_score, is_tokened_score_partial = task_score( - participation, task, only_tokened=True) + participation, task, only_tokened=True, rounded=True) # These two should be the same, anyway. is_score_partial = is_public_score_partial or is_tokened_score_partial @@ -213,9 +213,9 @@ def add_task_score(self, participation, task, data): .options(joinedload(Submission.results))\ .all() data["task_public_score"], public_score_is_partial = \ - task_score(participation, task, public=True) + task_score(participation, task, public=True, rounded=True) data["task_tokened_score"], tokened_score_is_partial = \ - task_score(participation, task, only_tokened=True) + task_score(participation, task, only_tokened=True, rounded=True) # These two should be the same, anyway. data["task_score_is_partial"] = \ public_score_is_partial or tokened_score_is_partial diff --git a/cms/server/contest/templates/submission_row.html b/cms/server/contest/templates/submission_row.html index b45fa2b68..89afb62ca 100644 --- a/cms/server/contest/templates/submission_row.html +++ b/cms/server/contest/templates/submission_row.html @@ -25,7 +25,7 @@ {% if score_type is defined and score_type.max_public_score > 0 %} {% if status == SubmissionResult.SCORED %} - + {{ score_type.format_score(sr.public_score, score_type.max_public_score, sr.public_score_details, task.score_precision, translation=translation) }} {% else %} @@ -36,7 +36,7 @@ {% endif %} {% if score_type is defined and score_type.max_public_score < score_type.max_score %} {% if status == SubmissionResult.SCORED and (s.token is not none or actual_phase == 3) %} - + {{ score_type.format_score(sr.score, score_type.max_score, sr.score_details, task.score_precision, translation=translation) }} {% else %} diff --git a/cms/server/contest/templates/task_submissions.html b/cms/server/contest/templates/task_submissions.html index 41834e884..bf850411e 100644 --- a/cms/server/contest/templates/task_submissions.html +++ b/cms/server/contest/templates/task_submissions.html @@ -162,7 +162,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name } {% if score_type.max_public_score > 0 %} {# Show the public score (alone, if everything is public or tokens are disabled, or together with the tokened score). #}
+ class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(public_score, score_type.max_public_score, task.score_precision) }}"> {% if score_type.max_public_score == score_type.max_score %} {% trans %}Score:{% endtrans %} @@ -183,7 +183,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name } {% if score_type.max_public_score < score_type.max_score %} {# Show the tokened score (alone if everything is non-public, or together with the public score). #}
+ class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(tokened_score, score_type.max_score, task.score_precision) if can_use_tokens else "undefined" }}"> {% if can_use_tokens %} {% trans %}Score of tokened submissions:{% endtrans %} diff --git a/cmstestsuite/unit_tests/grading/scoring_test.py b/cmstestsuite/unit_tests/grading/scoring_test.py index a16db54b8..bfd8678c7 100755 --- a/cmstestsuite/unit_tests/grading/scoring_test.py +++ b/cmstestsuite/unit_tests/grading/scoring_test.py @@ -46,7 +46,8 @@ class TaskScoreMixin(DatabaseMixin): def setUp(self): super(TaskScoreMixin, self).setUp() self.participation = self.add_participation() - self.task = self.add_task(contest=self.participation.contest) + self.task = self.add_task(contest=self.participation.contest, + score_precision=2) dataset = self.add_dataset(task=self.task) self.task.active_dataset = dataset self.timestamp = make_datetime() @@ -54,9 +55,10 @@ def setUp(self): def at(self, timestamp): return self.timestamp + timedelta(seconds=timestamp) - def call(self, public=False, only_tokened=False): + def call(self, public=False, only_tokened=False, rounded=False): return task_score(self.participation, self.task, - public=public, only_tokened=only_tokened) + public=public, only_tokened=only_tokened, + rounded=rounded) def add_result(self, timestamp, score, tokened=False, score_details=None, public_score=None, public_score_details=None): @@ -375,6 +377,18 @@ def test_only_tokened(self): self.session.flush() self.assertEqual(self.call(only_tokened=True), (44.4, False)) + def test_unrounded(self): + self.add_result(self.at(1), 44.44444, tokened=False) + self.add_result(self.at(2), 44.44443, tokened=False) + self.session.flush() + self.assertEqual(self.call(), (44.44444, False)) + + def test_rounded(self): + self.add_result(self.at(1), 44.44444, tokened=False) + self.add_result(self.at(2), 44.44443, tokened=False) + self.session.flush() + self.assertEqual(self.call(rounded=True), (44.44, False)) + if __name__ == "__main__": unittest.main() From 3816f0d6a37cf15c3c8fddf48aa140d2cb0157e9 Mon Sep 17 00:00:00 2001 From: Luca Wehrstedt Date: Mon, 18 Feb 2019 21:48:39 +0100 Subject: [PATCH 09/20] Regenerate cms.pot file I first reverted the `{%` -> `{%+` change as that doesn't appear to be compatible with Babel's extracter, then ran `./setup.pt extract_messages`. --- cms/locale/cms.pot | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cms/locale/cms.pot b/cms/locale/cms.pot index b202436d2..1a21a9029 100644 --- a/cms/locale/cms.pot +++ b/cms/locale/cms.pot @@ -1,15 +1,15 @@ # Translations template for Contest Management System. -# Copyright (C) 2018 CMS development group +# Copyright (C) 2019 CMS development group # This file is distributed under the same license as the Contest Management # System project. -# FIRST AUTHOR , 2018. +# FIRST AUTHOR , 2019. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Contest Management System 1.4.dev0\n" +"Project-Id-Version: Contest Management System 1.4rc1\n" "Report-Msgid-Bugs-To: contestms@googlegroups.com\n" -"POT-Creation-Date: 2018-10-01 09:06+0100\n" +"POT-Creation-Date: 2019-02-18 21:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -317,6 +317,9 @@ msgstr "" msgid "Evaluated" msgstr "" +msgid "status" +msgstr "" + msgid "Token request received" msgstr "" From 786f455aa5c92f57a6cd45c57589a97f558f98bf Mon Sep 17 00:00:00 2001 From: Andrey Vihrov Date: Sun, 25 Nov 2018 12:44:09 +0200 Subject: [PATCH 10/20] Translate Yes/No messages in the Official column of the submissions row The strings are already in .pot, so no .pot update is needed. --- cms/server/contest/templates/submission_row.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/server/contest/templates/submission_row.html b/cms/server/contest/templates/submission_row.html index 89afb62ca..a431547b4 100644 --- a/cms/server/contest/templates/submission_row.html +++ b/cms/server/contest/templates/submission_row.html @@ -48,9 +48,9 @@ {% if actual_phase >= +3 %} {% if s.official %} - Yes + {% trans %}Yes{% endtrans %} {% else %} - No + {% trans %}No{% endtrans %} {% endif %} {% endif %} From 8026966aee9cbcdc9488d39127f87d42d51a1836 Mon Sep 17 00:00:00 2001 From: Andrey Vihrov Date: Wed, 12 Dec 2018 14:51:44 +0200 Subject: [PATCH 11/20] Fix grammar in delete_sandbox() warning --- cms/grading/tasktypes/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/grading/tasktypes/util.py b/cms/grading/tasktypes/util.py index c343dbff7..61037b4e4 100644 --- a/cms/grading/tasktypes/util.py +++ b/cms/grading/tasktypes/util.py @@ -87,7 +87,7 @@ def delete_sandbox(sandbox, success=True, keep_sandbox=False): """ # If the job was not successful, we keep the sandbox around. if not success: - logger.warning("Sandbox %s kept around because job did not succeeded.", + logger.warning("Sandbox %s kept around because job did not succeed.", sandbox.get_root_path()) delete = success and not config.keep_sandbox and not keep_sandbox From c927117474cfd43442a1b36303f9c1d94402ba9a Mon Sep 17 00:00:00 2001 From: Luca Wehrstedt Date: Wed, 28 Nov 2018 00:01:50 +0100 Subject: [PATCH 12/20] Store hashed password in cookie to avoid re-hashing at every request The only drawback is that if admins change a user's password from plaintext to hashed or vice versa that user will have to log in again. Attackers knowing hashed passwords shouldn't be a problem as the server only accepts it if it's signed (together with a recent timestamp) with its secret key. Fixes #1076. --- cms/server/contest/authentication.py | 54 ++++++++++--------- .../server/contest/authentication_test.py | 7 --- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/cms/server/contest/authentication.py b/cms/server/contest/authentication.py index 326b7d47e..5f0e9e3ce 100644 --- a/cms/server/contest/authentication.py +++ b/cms/server/contest/authentication.py @@ -52,35 +52,18 @@ logger = logging.getLogger(__name__) -def safe_validate_password(participation, password): - """Check that the password is correct for the authentication. - - Validate the given password against the participation (using either - the global or the contest-specific password that is stored in the - database), and guard against a misconfiguration. +def get_password(participation): + """Return the password the participation can log in with. participation (Participation): a participation. - password (str): a password provided by someone trying to log in - claiming to be the given participation. - return (bool): whether the password matches the expected one. + return (str): the password that is on record for them. """ if participation.password is None: - correct_password = participation.user.password + return participation.user.password else: - correct_password = participation.password - - try: - password_valid = validate_password(correct_password, password) - except ValueError as e: - # This is either a programming or a configuration error. - logger.warning( - "Invalid password stored in database for user %s in contest %s: " - "%s", participation.user.username, participation.contest.name, e) - return False - - return password_valid + return participation.password def validate_login( @@ -132,7 +115,18 @@ def log_failed_attempt(msg, *args): log_failed_attempt("user not registered to contest") return None, None - if not safe_validate_password(participation, password): + correct_password = get_password(participation) + + try: + password_valid = validate_password(correct_password, password) + except ValueError as e: + # This is either a programming or a configuration error. + logger.warning( + "Invalid password stored in database for user %s in contest %s: " + "%s", participation.user.username, participation.contest.name, e) + return None, None + + if not password_valid: log_failed_attempt("wrong password") return None, None @@ -149,8 +143,10 @@ def log_failed_attempt(msg, *args): "contest %s, at %s", ip_address, username, contest.name, timestamp) + # If hashing is used, the cookie stores the hashed password so that + # the expensive bcrypt call doesn't need to be done at every request. return (participation, - json.dumps([username, password, make_timestamp(timestamp)]) + json.dumps([username, correct_password, make_timestamp(timestamp)]) .encode("utf-8")) @@ -349,7 +345,11 @@ def log_failed_attempt(msg, *args): log_failed_attempt("user not registered to contest") return None, None - if not safe_validate_password(participation, password): + correct_password = get_password(participation) + + # We compare hashed password because it would be too expensive to + # re-hash the user-provided plaintext password at every request. + if password != correct_password: log_failed_attempt("wrong password") return None, None @@ -357,6 +357,8 @@ def log_failed_attempt(msg, *args): "returning from %s, at %s", username, contest.name, last_update, timestamp) + # We store the hashed password (if hashing is used) so that the + # expensive bcrypt hashing doesn't need to be done at every request. return (participation, - json.dumps([username, password, make_timestamp(timestamp)]) + json.dumps([username, correct_password, make_timestamp(timestamp)]) .encode("utf-8")) diff --git a/cmstestsuite/unit_tests/server/contest/authentication_test.py b/cmstestsuite/unit_tests/server/contest/authentication_test.py index d1e0bd689..88d7344f0 100755 --- a/cmstestsuite/unit_tests/server/contest/authentication_test.py +++ b/cmstestsuite/unit_tests/server/contest/authentication_test.py @@ -261,14 +261,7 @@ def test_cookie_contains_password(self): # Cookies are of no use if one cannot login by password. self.contest.allow_password_authentication = False self.assertFailure() - - # The cookie works with all methods as it holds the plaintext password. self.contest.allow_password_authentication = True - self.user.password = hash_password("mypass", method="bcrypt") - self.assertSuccessAndCookieRefreshed() - - self.user.password = hash_password("mypass", method="plaintext") - self.assertSuccessAndCookieRefreshed() # Cookies contain the password, which is validated every time. self.user.password = build_password("newpass") From e7461b741730f9f3cab3395daf2c52d0b7ef659b Mon Sep 17 00:00:00 2001 From: briansu Date: Thu, 14 Mar 2019 20:57:35 +0800 Subject: [PATCH 13/20] Use contest id to map contests for CWS --- cms/server/contest/handlers/contest.py | 8 ++++---- cms/server/contest/server.py | 1 + cms/server/contest/templates/contest_list.html | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cms/server/contest/handlers/contest.py b/cms/server/contest/handlers/contest.py index ae334c58e..08de9b5ac 100644 --- a/cms/server/contest/handlers/contest.py +++ b/cms/server/contest/handlers/contest.py @@ -88,7 +88,7 @@ def prepare(self): super(ContestHandler, self).prepare() if self.is_multi_contest(): - self.contest_url = self.url[self.contest.name] + self.contest_url = self.url[self.contest.id] else: self.contest_url = self.url @@ -106,14 +106,14 @@ def choose_contest(self): if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 - contest_name = self.path_args[0] + contest_id = self.path_args[0] # Select the correct contest or return an error self.contest = self.sql_session.query(Contest)\ - .filter(Contest.name == contest_name).first() + .filter(Contest.id == contest_id).first() if self.contest is None: self.contest = Contest( - name=contest_name, description=contest_name) + name=contest_id, description=contest_id) # render_params in this class assumes the contest is loaded, # so we cannot call it without a fully defined contest. Luckily # the one from the base class is enough to display a 404 page. diff --git a/cms/server/contest/server.py b/cms/server/contest/server.py index 9e5f0c8b8..f489f2619 100644 --- a/cms/server/contest/server.py +++ b/cms/server/contest/server.py @@ -94,6 +94,7 @@ def __init__(self, shard, contest_id=None): if self.contest_id is None: HANDLERS.append((r"", MainHandler)) + HANDLERS.append((r"/", MainHandler)) handlers = [(r'/', ContestListHandler)] for h in HANDLERS: handlers.append((r'/([^/]+)' + h[0],) + h[1:]) diff --git a/cms/server/contest/templates/contest_list.html b/cms/server/contest/templates/contest_list.html index 4a097a94e..b41c22b3c 100644 --- a/cms/server/contest/templates/contest_list.html +++ b/cms/server/contest/templates/contest_list.html @@ -8,7 +8,7 @@

{% trans %}Choose a contest{% endtrans %}

From e251b98d488cfc4a1dc2f23e816a021d587a04fe Mon Sep 17 00:00:00 2001 From: briansu Date: Thu, 14 Mar 2019 22:34:40 +0800 Subject: [PATCH 14/20] Match only integer contest id --- cms/server/contest/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/server/contest/server.py b/cms/server/contest/server.py index f489f2619..6ba0f771f 100644 --- a/cms/server/contest/server.py +++ b/cms/server/contest/server.py @@ -97,7 +97,7 @@ def __init__(self, shard, contest_id=None): HANDLERS.append((r"/", MainHandler)) handlers = [(r'/', ContestListHandler)] for h in HANDLERS: - handlers.append((r'/([^/]+)' + h[0],) + h[1:]) + handlers.append((r'/(\d+)' + h[0],) + h[1:]) else: HANDLERS.append((r"/", MainHandler)) handlers = HANDLERS From 555e2cc8d0055f67ad85ba0c2b7fa3e079cf16d0 Mon Sep 17 00:00:00 2001 From: briansu Date: Sat, 16 Mar 2019 15:30:09 +0800 Subject: [PATCH 15/20] Not adding contest id in relative path --- cms/server/contest/handlers/contest.py | 8 ++++++-- cms/server/contest/server.py | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cms/server/contest/handlers/contest.py b/cms/server/contest/handlers/contest.py index 08de9b5ac..6b38ca038 100644 --- a/cms/server/contest/handlers/contest.py +++ b/cms/server/contest/handlers/contest.py @@ -45,7 +45,7 @@ from cms import config, TOKEN_MODE_MIXED from cms.db import Contest, Submission, Task, UserTest -from cms.server import FileHandlerMixin +from cms.server import FileHandlerMixin, Url from cms.locale import filter_language_codes from cms.server.contest.authentication import authenticate_request from cmscommon.datetime import get_timezone @@ -88,7 +88,11 @@ def prepare(self): super(ContestHandler, self).prepare() if self.is_multi_contest(): - self.contest_url = self.url[self.contest.id] + if self.url.url_root.count('..') > 1: + self.contest_url = Url(self.url.url_root[3:]) + else: + self.contest_url = Url(self.url.url_root[1:]) + self.contest_url.url_root += '/' else: self.contest_url = self.url diff --git a/cms/server/contest/server.py b/cms/server/contest/server.py index 6ba0f771f..fb3a085f1 100644 --- a/cms/server/contest/server.py +++ b/cms/server/contest/server.py @@ -93,9 +93,8 @@ def __init__(self, shard, contest_id=None): self.contest_id = contest_id if self.contest_id is None: - HANDLERS.append((r"", MainHandler)) HANDLERS.append((r"/", MainHandler)) - handlers = [(r'/', ContestListHandler)] + handlers = [] for h in HANDLERS: handlers.append((r'/(\d+)' + h[0],) + h[1:]) else: From a9354ce2c37773031e105d4054cba29faf1f5c3b Mon Sep 17 00:00:00 2001 From: briansu Date: Sat, 13 Apr 2019 22:10:52 +0800 Subject: [PATCH 16/20] Add option to view task statement in new tab --- cms/locale/zh_TW/LC_MESSAGES/cms.po | 3 +++ cms/server/contest/handlers/task.py | 5 ++++- cms/server/contest/static/cws_style.css | 4 ++++ cms/server/contest/templates/task_description.html | 1 + cms/server/file_middleware.py | 10 ++++++++-- cms/server/util.py | 4 +++- 6 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cms/locale/zh_TW/LC_MESSAGES/cms.po b/cms/locale/zh_TW/LC_MESSAGES/cms.po index 19c712416..1cae9a028 100644 --- a/cms/locale/zh_TW/LC_MESSAGES/cms.po +++ b/cms/locale/zh_TW/LC_MESSAGES/cms.po @@ -661,6 +661,9 @@ msgstr "本題沒有敘述" msgid "Download task statement" msgstr "下載題目敘述" +msgid "View task statement" +msgstr "觀看題目敘述" + msgid "" "The statement for this task is available in multiple versions, in different " "languages." diff --git a/cms/server/contest/handlers/task.py b/cms/server/contest/handlers/task.py index 7ba75c808..076b3ed69 100644 --- a/cms/server/contest/handlers/task.py +++ b/cms/server/contest/handlers/task.py @@ -89,7 +89,10 @@ def get(self, task_name, lang_code): else: filename = "%s.pdf" % task.name - self.fetch(statement, "application/pdf", filename) + if self.request.arguments.has_key('view'): + self.fetch(statement, "application/pdf", filename, view=True) + else: + self.fetch(statement, "application/pdf", filename) class TaskAttachmentViewHandler(FileHandler): diff --git a/cms/server/contest/static/cws_style.css b/cms/server/contest/static/cws_style.css index 23e1bf5c6..e8e52fc02 100644 --- a/cms/server/contest/static/cws_style.css +++ b/cms/server/contest/static/cws_style.css @@ -269,6 +269,10 @@ div.login_box { text-align: center; } +.statement.one_statement a { + margin: 0 15px; +} + @media (max-width: 767px) { .task_description .statement.many_statements .main_statements { margin-bottom: 15px; diff --git a/cms/server/contest/templates/task_description.html b/cms/server/contest/templates/task_description.html index 6d65c66f6..68371a432 100644 --- a/cms/server/contest/templates/task_description.html +++ b/cms/server/contest/templates/task_description.html @@ -25,6 +25,7 @@

{% trans %}Statement{% endtrans %}

diff --git a/cms/server/file_middleware.py b/cms/server/file_middleware.py index 4f2e9abd8..f65d03cef 100644 --- a/cms/server/file_middleware.py +++ b/cms/server/file_middleware.py @@ -55,6 +55,7 @@ class FileServerMiddleware(object): DIGEST_HEADER = "X-CMS-File-Digest" FILENAME_HEADER = "X-CMS-File-Filename" + STATEMENT_VIEW_HEADER = "X-CMS-Statement_View" def __init__(self, file_cacher, app): """Create an instance. @@ -94,6 +95,7 @@ def wsgi_app(self, environ, start_response): digest = original_response.headers.pop(self.DIGEST_HEADER) filename = original_response.headers.pop(self.FILENAME_HEADER, None) mimetype = original_response.mimetype + statement_view = original_response.headers.pop(self.STATEMENT_VIEW_HEADER, None) try: fobj = self.file_cacher.get_file(digest) @@ -110,8 +112,12 @@ def wsgi_app(self, environ, start_response): response.status_code = 200 response.mimetype = mimetype if filename is not None: - response.headers.add( - "Content-Disposition", "attachment", filename=filename) + if not statement_view: + response.headers.add( + "Content-Disposition", "attachment", filename=filename) + else: + response.headers.add( + "Content-Disposition", "inline", filename=filename) response.set_etag(digest) response.cache_control.max_age = SECONDS_IN_A_YEAR response.cache_control.private = True diff --git a/cms/server/util.py b/cms/server/util.py index 82f9511cf..17e380b2c 100644 --- a/cms/server/util.py +++ b/cms/server/util.py @@ -74,7 +74,7 @@ class FileHandlerMixin(RequestHandler): """ - def fetch(self, digest, content_type, filename): + def fetch(self, digest, content_type, filename, view=False): """Serve the file with the given digest. This will just add the headers required to trigger @@ -88,6 +88,8 @@ def fetch(self, digest, content_type, filename): self.set_header(FileServerMiddleware.DIGEST_HEADER, digest) self.set_header(FileServerMiddleware.FILENAME_HEADER, filename) self.set_header("Content-Type", content_type) + if view: + self.set_header(FileServerMiddleware.STATEMENT_VIEW_HEADER, "TRUE") self.finish() From 2fc67c896b87f023e07caca5218d8162253a507e Mon Sep 17 00:00:00 2001 From: briansu Date: Wed, 17 Apr 2019 11:01:14 +0800 Subject: [PATCH 17/20] Make proxy service sweep new ranking change every 5 sec --- cms/service/ProxyService.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cms/service/ProxyService.py b/cms/service/ProxyService.py index 1a3d91cab..a24a5c758 100644 --- a/cms/service/ProxyService.py +++ b/cms/service/ProxyService.py @@ -274,7 +274,8 @@ def __init__(self, shard, contest_id): # example. self.initialize() - self.start_sweeper(347.0) + sweeper_timeout = 5.0 # was 347.0 + self.start_sweeper(sweeper_timeout) def _missing_operations(self): """Return a generator of data to be sent to the rankings.. From d61f288a37748cec65952f4c8825e20dd2445ab3 Mon Sep 17 00:00:00 2001 From: briansu Date: Wed, 27 Nov 2019 19:19:13 +0800 Subject: [PATCH 18/20] Added C++14 --- cms/grading/languages/cpp14_gpp.py | 71 ++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cms/grading/languages/cpp14_gpp.py diff --git a/cms/grading/languages/cpp14_gpp.py b/cms/grading/languages/cpp14_gpp.py new file mode 100644 index 000000000..23854d82c --- /dev/null +++ b/cms/grading/languages/cpp14_gpp.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Contest Management System - http://cms-dev.github.io/ +# Copyright © 2016 Stefano Maggiolo +# +# 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 . + +"""C++ programming language definition.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from future.builtins.disabled import * # noqa +from future.builtins import * # noqa + +from cms.grading import CompiledLanguage + + +__all__ = ["Cpp14Gpp"] + + +class Cpp14Gpp(CompiledLanguage): + """This defines the C++ programming language, compiled with g++ (the + version available on the system) using the C++14 standard. + + """ + + @property + def name(self): + """See Language.name.""" + return "C++14 / g++" + + @property + def source_extensions(self): + """See Language.source_extensions.""" + return [".cpp", ".cc", ".cxx", ".c++", ".C"] + + @property + def header_extensions(self): + """See Language.source_extensions.""" + return [".h"] + + @property + def object_extensions(self): + """See Language.source_extensions.""" + return [".o"] + + def get_compilation_commands(self, + source_filenames, executable_filename, + for_evaluation=True): + """See Language.get_compilation_commands.""" + command = ["/usr/bin/g++"] + if for_evaluation: + command += ["-DEVAL"] + command += ["-std=gnu++14", "-O2", "-pipe", "-static", + "-s", "-o", executable_filename] + command += source_filenames + return [command] From dc8624ac759656b923afb7f9253c37dc2f8d4863 Mon Sep 17 00:00:00 2001 From: briansu Date: Wed, 27 Nov 2019 19:23:12 +0800 Subject: [PATCH 19/20] Added C++14 to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 3c8b72d2e..7ab3e52d1 100755 --- a/setup.py +++ b/setup.py @@ -194,6 +194,7 @@ def run(self): ], "cms.grading.languages": [ "C++11 / g++=cms.grading.languages.cpp11_gpp:Cpp11Gpp", + "C++14 / g++=cms.grading.languages.cpp14_gpp:Cpp14Gpp", "C11 / gcc=cms.grading.languages.c11_gcc:C11Gcc", "C# / Mono=cms.grading.languages.csharp_mono:CSharpMono", "Haskell / ghc=cms.grading.languages.haskell_ghc:HaskellGhc", From 5819187efac09392aed89508de8b3877a4d7925d Mon Sep 17 00:00:00 2001 From: ytp <105836676+tw-ytp@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:21:13 +0800 Subject: [PATCH 20/20] Revert "fix: sort subtasks using index" --- cmscontrib/loaders/tps.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmscontrib/loaders/tps.py b/cmscontrib/loaders/tps.py index 6ce992e71..ba5dc55a9 100644 --- a/cmscontrib/loaders/tps.py +++ b/cmscontrib/loaders/tps.py @@ -393,8 +393,7 @@ def get_file_list(files_dir, prefix, except_files): add_optional_name = data['add_optional_name'] if 'add_optional_name' in data else False - subtasks = sorted(subtasks_data['subtasks'].items(), key=lambda subtask: subtask[1]['index']) - for subtask, subtask_data in subtasks: + for subtask, subtask_data in subtasks_data['subtasks'].items(): subtask_no += 1 score = int(subtask_data["score"]) if use_mapping: