diff --git a/dmoj/urls.py b/dmoj/urls.py index 9103a6a..bbacf43 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -543,7 +543,7 @@ urlpatterns = [ ), ), url( - r"^/submissions/(?P\w+)/(?P\w+)/ajax", + r"^/submissions/(?P\d+)/(?P\w+)/ajax", paged_list_view( submission.UserContestSubmissionsAjax, "contest_user_submissions_ajax", diff --git a/judge/contest_format/__init__.py b/judge/contest_format/__init__.py index ee57ccf..8d2ccc9 100644 --- a/judge/contest_format/__init__.py +++ b/judge/contest_format/__init__.py @@ -3,4 +3,5 @@ from judge.contest_format.default import DefaultContestFormat from judge.contest_format.ecoo import ECOOContestFormat from judge.contest_format.icpc import ICPCContestFormat from judge.contest_format.ioi import IOIContestFormat +from judge.contest_format.new_ioi import NewIOIContestFormat from judge.contest_format.registry import choices, formats diff --git a/judge/contest_format/atcoder.py b/judge/contest_format/atcoder.py index db42b73..ee5b18d 100644 --- a/judge/contest_format/atcoder.py +++ b/judge/contest_format/atcoder.py @@ -107,7 +107,7 @@ class AtCoderContestFormat(DefaultContestFormat): self.handle_frozen_state(participation, format_data) participation.cumtime = cumtime + penalty - participation.score = points + participation.score = round(points, self.contest.points_precision) participation.tiebreaker = 0 participation.format_data = format_data participation.save() @@ -141,7 +141,7 @@ class AtCoderContestFormat(DefaultContestFormat): "contest_user_submissions_ajax", args=[ self.contest.key, - participation.user.user.username, + participation.id, contest_problem.problem.code, ], ), diff --git a/judge/contest_format/default.py b/judge/contest_format/default.py index d8dd848..69292e0 100644 --- a/judge/contest_format/default.py +++ b/judge/contest_format/default.py @@ -56,7 +56,7 @@ class DefaultContestFormat(BaseContestFormat): self.handle_frozen_state(participation, format_data) participation.cumtime = max(cumtime, 0) - participation.score = points + participation.score = round(points, self.contest.points_precision) participation.tiebreaker = 0 participation.format_data = format_data participation.save() @@ -82,7 +82,7 @@ class DefaultContestFormat(BaseContestFormat): "contest_user_submissions_ajax", args=[ self.contest.key, - participation.user.user.username, + participation.id, contest_problem.problem.code, ], ), diff --git a/judge/contest_format/ecoo.py b/judge/contest_format/ecoo.py index 149d00a..f5429c7 100644 --- a/judge/contest_format/ecoo.py +++ b/judge/contest_format/ecoo.py @@ -117,7 +117,7 @@ class ECOOContestFormat(DefaultContestFormat): self.handle_frozen_state(participation, format_data) participation.cumtime = cumtime - participation.score = points + participation.score = round(points, self.contest.points_precision) participation.tiebreaker = 0 participation.format_data = format_data participation.save() @@ -151,7 +151,7 @@ class ECOOContestFormat(DefaultContestFormat): "contest_user_submissions_ajax", args=[ self.contest.key, - participation.user.user.username, + participation.id, contest_problem.problem.code, ], ), diff --git a/judge/contest_format/icpc.py b/judge/contest_format/icpc.py index b366b01..61495ae 100644 --- a/judge/contest_format/icpc.py +++ b/judge/contest_format/icpc.py @@ -109,7 +109,7 @@ class ICPCContestFormat(DefaultContestFormat): self.handle_frozen_state(participation, format_data) participation.cumtime = max(0, cumtime + penalty) - participation.score = score + participation.score = round(score, self.contest.points_precision) participation.tiebreaker = last # field is sorted from least to greatest participation.format_data = format_data participation.save() @@ -143,7 +143,7 @@ class ICPCContestFormat(DefaultContestFormat): "contest_user_submissions_ajax", args=[ self.contest.key, - participation.user.user.username, + participation.id, contest_problem.problem.code, ], ), diff --git a/judge/contest_format/ioi.py b/judge/contest_format/ioi.py index bcd18c3..2f51eb7 100644 --- a/judge/contest_format/ioi.py +++ b/judge/contest_format/ioi.py @@ -81,7 +81,7 @@ class IOIContestFormat(DefaultContestFormat): self.handle_frozen_state(participation, format_data) participation.cumtime = max(cumtime, 0) - participation.score = score + participation.score = round(score, self.contest.points_precision) participation.tiebreaker = 0 participation.format_data = format_data participation.save() @@ -107,7 +107,7 @@ class IOIContestFormat(DefaultContestFormat): "contest_user_submissions_ajax", args=[ self.contest.key, - participation.user.user.username, + participation.id, contest_problem.problem.code, ], ), diff --git a/judge/contest_format/new_ioi.py b/judge/contest_format/new_ioi.py new file mode 100644 index 0000000..848ea7f --- /dev/null +++ b/judge/contest_format/new_ioi.py @@ -0,0 +1,127 @@ +from django.db import connection +from django.utils.translation import gettext as _, gettext_lazy + +from judge.contest_format.ioi import IOIContestFormat +from judge.contest_format.registry import register_contest_format +from judge.timezone import from_database_time, to_database_time + + +@register_contest_format("ioi16") +class NewIOIContestFormat(IOIContestFormat): + name = gettext_lazy("New IOI") + config_defaults = {"cumtime": False} + """ + cumtime: Specify True if time penalties are to be computed. Defaults to False. + """ + + def get_results_by_subtask(self, participation, include_frozen=False): + frozen_time = self.contest.end_time + if self.contest.freeze_after and not include_frozen: + frozen_time = participation.start + self.contest.freeze_after + + with connection.cursor() as cursor: + cursor.execute( + """ + SELECT q.prob, + q.prob_points, + MIN(q.date) as `date`, + q.batch_points, + q.total_batch_points, + q.batch, + q.subid + FROM ( + SELECT cp.id as `prob`, + cp.points as `prob_points`, + sub.id as `subid`, + sub.date as `date`, + tc.points as `points`, + tc.batch as `batch`, + SUM(tc.points) as `batch_points`, + SUM(tc.total) as `total_batch_points` + FROM judge_contestproblem cp + INNER JOIN + judge_contestsubmission cs + ON (cs.problem_id = cp.id AND cs.participation_id = %s) + LEFT OUTER JOIN + judge_submission sub + ON (sub.id = cs.submission_id AND sub.status = 'D') + INNER JOIN judge_submissiontestcase tc + ON sub.id = tc.submission_id + WHERE sub.date < %s + GROUP BY cp.id, tc.batch, sub.id + ) q + INNER JOIN ( + SELECT prob, batch, MAX(r.batch_points) as max_batch_points + FROM ( + SELECT cp.id as `prob`, + tc.batch as `batch`, + SUM(tc.points) as `batch_points` + FROM judge_contestproblem cp + INNER JOIN + judge_contestsubmission cs + ON (cs.problem_id = cp.id AND cs.participation_id = %s) + LEFT OUTER JOIN + judge_submission sub + ON (sub.id = cs.submission_id AND sub.status = 'D') + INNER JOIN judge_submissiontestcase tc + ON sub.id = tc.submission_id + GROUP BY cp.id, tc.batch, sub.id + ) r + GROUP BY prob, batch + ) p + ON p.prob = q.prob AND (p.batch = q.batch OR p.batch is NULL AND q.batch is NULL) + WHERE p.max_batch_points = q.batch_points + GROUP BY q.prob, q.batch + """, + (participation.id, to_database_time(frozen_time), participation.id), + ) + + return cursor.fetchall() + + def update_participation(self, participation): + cumtime = 0 + score = 0 + format_data = {} + + for ( + problem_id, + problem_points, + time, + subtask_points, + total_subtask_points, + subtask, + sub_id, + ) in self.get_results_by_subtask(participation): + problem_id = str(problem_id) + time = from_database_time(time) + if self.config["cumtime"]: + dt = (time - participation.start).total_seconds() + else: + dt = 0 + + if format_data.get(problem_id) is None: + format_data[problem_id] = {"points": 0, "time": 0, "total_points": 0} + format_data[problem_id]["points"] += subtask_points + format_data[problem_id]["time"] = max(dt, format_data[problem_id]["time"]) + format_data[problem_id]["problem_points"] = problem_points + format_data[problem_id]["total_points"] += total_subtask_points + + for problem_data in format_data.values(): + if not problem_data["total_points"]: + continue + penalty = problem_data["time"] + problem_data["points"] = ( + problem_data["points"] + / problem_data["total_points"] + * problem_data["problem_points"] + ) + if self.config["cumtime"] and points: + cumtime += penalty + score += problem_data["points"] + + self.handle_frozen_state(participation, format_data) + participation.cumtime = max(cumtime, 0) + participation.score = round(score, self.contest.points_precision) + participation.tiebreaker = 0 + participation.format_data = format_data + participation.save() diff --git a/judge/migrations/0140_alter_contest_format_name.py b/judge/migrations/0140_alter_contest_format_name.py new file mode 100644 index 0000000..d146e3b --- /dev/null +++ b/judge/migrations/0140_alter_contest_format_name.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.16 on 2022-11-21 22:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0139_contest_freeze_after"), + ] + + operations = [ + migrations.AlterField( + model_name="contest", + name="format_name", + field=models.CharField( + choices=[ + ("atcoder", "AtCoder"), + ("default", "Default"), + ("ecoo", "ECOO"), + ("icpc", "ICPC"), + ("ioi", "IOI"), + ("ioi16", "New IOI"), + ], + default="default", + help_text="The contest format module to use.", + max_length=32, + verbose_name="contest format", + ), + ), + ] diff --git a/judge/views/submission.py b/judge/views/submission.py index 05deeea..d783c9b 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -33,7 +33,7 @@ from django.views.generic import ListView from judge import event_poster as event from judge.highlight_code import highlight_code -from judge.models import Contest +from judge.models import Contest, ContestParticipation from judge.models import Language from judge.models import Problem from judge.models import ProblemTestCase @@ -472,10 +472,17 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): class UserMixin(object): def get(self, request, *args, **kwargs): - if "user" not in kwargs: - raise ImproperlyConfigured("Must pass a user") - self.profile = get_object_or_404(Profile, user__username=kwargs["user"]) - self.username = kwargs["user"] + if "user" not in kwargs and "participation" not in kwargs: + raise ImproperlyConfigured("Must pass a user or participation") + if "user" in kwargs: + self.profile = get_object_or_404(Profile, user__username=kwargs["user"]) + self.username = kwargs["user"] + else: + self.participation = get_object_or_404( + ContestParticipation, id=kwargs["participation"] + ) + self.profile = self.participation.user + self.username = self.profile.user.username if self.profile == request.profile: self.include_frozen = True return super(UserMixin, self).get(request, *args, **kwargs) @@ -828,6 +835,53 @@ class UserContestSubmissionsAjax(UserContestSubmissions): return s.date - self.contest.start_time return None + def get_best_subtask_points(self): + if self.contest.format_name == "ioi16": + contest_problem = self.contest.contest_problems.get(problem=self.problem) + best_subtasks = {} + total_points = 0 + problem_points = 0 + achieved_points = 0 + + for ( + problem_id, + pp, + time, + subtask_points, + total_subtask_points, + subtask, + sub_id, + ) in self.contest.format.get_results_by_subtask( + self.participation, self.include_frozen + ): + if contest_problem.id != problem_id or total_subtask_points == 0: + continue + if not subtask: + subtask = 0 + problem_points = pp + submission = Submission.objects.get(id=sub_id) + best_subtasks[subtask] = { + "submission": submission, + "contest_time": nice_repr(self.contest_time(submission), "noday"), + "points": subtask_points, + "total": total_subtask_points, + } + total_points += total_subtask_points + achieved_points += subtask_points + for subtask in best_subtasks.values(): + subtask["points"] = floatformat( + subtask["points"] / total_points * problem_points, + -self.contest.points_precision, + ) + subtask["total"] = floatformat( + subtask["total"] / total_points * problem_points, + -self.contest.points_precision, + ) + achieved_points = achieved_points / total_points * problem_points + if best_subtasks: + return best_subtasks, achieved_points, problem_points + return None + def get_context_data(self, **kwargs): context = super(UserContestSubmissionsAjax, self).get_context_data(**kwargs) context["contest"] = self.contest @@ -852,6 +906,21 @@ class UserContestSubmissionsAjax(UserContestSubmissions): s.display_point = f"{points} / {total}" filtered_submissions.append(s) context["submissions"] = filtered_submissions + + best_subtasks = self.get_best_subtask_points() + if best_subtasks: + ( + context["best_subtasks"], + context["points"], + context["total"], + ) = best_subtasks + context["points"] = floatformat( + context["points"], -self.contest.points_precision + ) + context["total"] = floatformat( + context["total"], -self.contest.points_precision + ) + context["subtasks"] = sorted(context["best_subtasks"].keys()) return context def get(self, request, *args, **kwargs): diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index 142b92f..3259c8e 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: lqdoj2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-20 10:42+0700\n" +"POT-Creation-Date: 2022-11-22 10:54+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -214,7 +214,7 @@ msgid "Taxonomy" msgstr "" #: judge/admin/problem.py:216 judge/admin/problem.py:434 -#: templates/contest/contest.html:91 templates/problem/data.html:518 +#: templates/contest/contest.html:91 templates/problem/data.html:520 #: templates/problem/list.html:20 templates/problem/list.html:44 #: templates/user/base-users-table.html:10 templates/user/user-about.html:36 #: templates/user/user-about.html:52 templates/user/user-problems.html:58 @@ -467,6 +467,10 @@ msgstr "" msgid "IOI" msgstr "" +#: judge/contest_format/new_ioi.py:11 +msgid "New IOI" +msgstr "IOI mới" + #: judge/forms.py:70 msgid "Subscribe to contest updates" msgstr "Đăng ký để nhận thông báo về các kỳ thi" @@ -2577,7 +2581,7 @@ msgstr "Không tìm thấy kỳ thi với mã \"%s\"." #: judge/views/contests.py:139 judge/views/stats.py:178 #: templates/organization/org-left-sidebar.html:5 templates/stats/site.html:21 -#: templates/user/user-bookmarks.html:50 +#: templates/user/user-bookmarks.html:54 msgid "Contests" msgstr "Kỳ thi" @@ -2944,7 +2948,7 @@ msgstr "Hướng dẫn cho {0}" #: judge/views/problem.py:447 templates/contest/contest.html:86 #: templates/organization/org-left-sidebar.html:4 -#: templates/user/user-about.html:28 templates/user/user-bookmarks.html:32 +#: templates/user/user-about.html:28 templates/user/user-bookmarks.html:34 #: templates/user/user-tabs.html:5 templates/user/users-table.html:19 msgid "Problems" msgstr "Bài tập" @@ -3150,39 +3154,39 @@ msgstr "Bài nộp của %(user)s cho bài %(problem)s" msgid "All submissions" msgstr "Tất cả bài nộp" -#: judge/views/submission.py:505 +#: judge/views/submission.py:512 msgid "All my submissions" msgstr "Tất cả bài nộp của tôi" -#: judge/views/submission.py:506 +#: judge/views/submission.py:513 #, python-format msgid "All submissions by %s" msgstr "Tất cả bài nộp của %s" -#: judge/views/submission.py:551 +#: judge/views/submission.py:558 #, python-format msgid "All submissions for %s" msgstr "Tất cả bài nộp cho %s" -#: judge/views/submission.py:579 +#: judge/views/submission.py:586 msgid "Must pass a problem" msgstr "Phải làm được một bài" -#: judge/views/submission.py:637 +#: judge/views/submission.py:644 #, python-format msgid "My submissions for %(problem)s" msgstr "Bài nộp của tôi cho %(problem)s" -#: judge/views/submission.py:638 +#: judge/views/submission.py:645 #, python-format msgid "%(user)s's submissions for %(problem)s" msgstr "Các bài nộp của %(user)s cho %(problem)s" -#: judge/views/submission.py:768 +#: judge/views/submission.py:775 msgid "Must pass a contest" msgstr "Phải qua một kỳ thi" -#: judge/views/submission.py:798 +#: judge/views/submission.py:805 #, python-brace-format msgid "" "{0}'s submissions for {2} in {0} cho {2} trong {4}" -#: judge/views/submission.py:810 +#: judge/views/submission.py:817 #, python-brace-format msgid "" "{0}'s submissions for problem {2} in {3}" @@ -3200,7 +3204,7 @@ msgstr "" "Các bài nộp của {0} cho bài {2} trong {3}" "" -#: judge/views/submission.py:861 +#: judge/views/submission.py:927 #, fuzzy #| msgid "You do not have the permission to rejudge submissions." msgid "You don't have permission to access." @@ -4505,7 +4509,7 @@ msgid "There are no requests to approve." msgstr "Không có đơn đăng ký." #: templates/organization/requests/pending.html:17 -#: templates/problem/data.html:521 +#: templates/problem/data.html:523 msgid "Delete?" msgstr "Xóa?" @@ -4550,54 +4554,56 @@ msgstr "Nhập mã bài mới cho bài tập được nhân bản:" msgid "Instruction" msgstr "Hướng dẫn" -#: templates/problem/data.html:470 +#: templates/problem/data.html:472 msgid "View YAML" msgstr "Xem YAML" -#: templates/problem/data.html:487 +#: templates/problem/data.html:489 msgid "Autofill testcases" msgstr "Tự động điền test" -#: templates/problem/data.html:491 templates/problem/problem.html:310 +#: templates/problem/data.html:493 templates/problem/problem.html:310 msgid "Problem type" msgid_plural "Problem types" msgstr[0] "Dạng bài" -#: templates/problem/data.html:498 +#: templates/problem/data.html:500 msgid "Fill testcases" msgstr "Điền test" -#: templates/problem/data.html:502 +#: templates/problem/data.html:504 msgid "Batch start positions" msgstr "Vị trí bắt đầu nhóm" -#: templates/problem/data.html:506 +#: templates/problem/data.html:508 msgid "" "Leave empty if not use batch. If you want to divide to three batches [1, 4], " "[5, 8], [9, 10], enter: 1, 5, 9" -msgstr "Để trống nếu không dùng nhóm. Nếu muốn chia test thành các nhóm [1, 4], [5, 8], [9, 10], nhập: 1, 5, 9" +msgstr "" +"Để trống nếu không dùng nhóm. Nếu muốn chia test thành các nhóm [1, 4], [5, " +"8], [9, 10], nhập: 1, 5, 9" -#: templates/problem/data.html:510 templates/problem/data.html:561 +#: templates/problem/data.html:512 templates/problem/data.html:563 msgid "Apply!" msgstr "Lưu!" -#: templates/problem/data.html:515 +#: templates/problem/data.html:517 msgid "Type" msgstr "Kiểu" -#: templates/problem/data.html:516 +#: templates/problem/data.html:518 msgid "Input file" msgstr "File Input" -#: templates/problem/data.html:517 +#: templates/problem/data.html:519 msgid "Output file" msgstr "File Output" -#: templates/problem/data.html:519 +#: templates/problem/data.html:521 msgid "Pretest?" msgstr "Pretest?" -#: templates/problem/data.html:562 +#: templates/problem/data.html:564 msgid "Add new case" msgstr "Thêm test mới" @@ -5418,11 +5424,19 @@ msgstr "" msgid "Contest submissions of" msgstr "Các bài nộp của" -#: templates/submission/user-ajax.html:13 +#: templates/submission/user-ajax.html:11 +msgid "Subtask" +msgstr "Subtask" + +#: templates/submission/user-ajax.html:29 +msgid "Total" +msgstr "Tổng điểm" + +#: templates/submission/user-ajax.html:46 msgid "g:i a d/m/Y" msgstr "" -#: templates/submission/user-ajax.html:13 +#: templates/submission/user-ajax.html:46 #, fuzzy, python-format #| msgid "" #| "\n" @@ -5437,11 +5451,11 @@ msgstr "" " vào %(time)s\n" " " -#: templates/submission/user-ajax.html:22 +#: templates/submission/user-ajax.html:55 msgid "pretests" msgstr "pretests" -#: templates/submission/user-ajax.html:24 +#: templates/submission/user-ajax.html:57 msgid "main tests" msgstr "test chính thức" @@ -5736,7 +5750,7 @@ msgstr "Max rating:" msgid "Posts" msgstr "Bài đăng" -#: templates/user/user-bookmarks.html:69 +#: templates/user/user-bookmarks.html:75 msgid "Editorials" msgstr "Lời giải" diff --git a/templates/submission/status-testcases.html b/templates/submission/status-testcases.html index b13f36e..4ecc78c 100644 --- a/templates/submission/status-testcases.html +++ b/templates/submission/status-testcases.html @@ -28,7 +28,7 @@ {% for batch in batches %}
{% if batch.id %} - {{ _('Batch ') }}#{{ batch.id }}: + {{ _('Batch ') }}#{{ batch.id }} ({{batch.points|floatformat}} / {{batch.total|floatformat}}): {% endif %} {% for case in batch.cases %} diff --git a/templates/submission/user-ajax.html b/templates/submission/user-ajax.html index c9450af..e16bb6c 100644 --- a/templates/submission/user-ajax.html +++ b/templates/submission/user-ajax.html @@ -2,6 +2,39 @@ {{_('Contest submissions of')}} {{link_user(profile)}} #
+{% if best_subtasks and subtasks %} + + {% for subtask in subtasks %} + + {% set cur_subtask = best_subtasks[subtask] %} + + + + {% set can_view = submission_layout(cur_subtask.submission, profile_id, request.user, editable_problem_ids, completed_problem_ids) %} + {% if can_view %} + + {% endif %} + + {% endfor %} + + + + + +
+{% endif %} {% for submission in submissions %} @@ -15,8 +48,8 @@ {% endtrans %} {% endif %} - - + +