From a35ea0f6d533c4c2447e4955e581ee50edf29f7b Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 18 Nov 2022 16:59:58 -0600 Subject: [PATCH] Add contest freeze --- judge/admin/contest.py | 8 +- judge/contest_format/atcoder.py | 13 +- judge/contest_format/base.py | 15 + judge/contest_format/default.py | 15 +- judge/contest_format/ecoo.py | 16 +- judge/contest_format/icpc.py | 13 +- judge/contest_format/ioi.py | 59 +-- judge/migrations/0139_contest_freeze_after.py | 23 ++ judge/models/contest.py | 8 + judge/timezone.py | 9 +- judge/views/submission.py | 12 +- locale/vi/LC_MESSAGES/django.po | 358 +++++++++--------- templates/contest/ranking-css.html | 4 + 13 files changed, 338 insertions(+), 215 deletions(-) create mode 100644 judge/migrations/0139_contest_freeze_after.py diff --git a/judge/admin/contest.py b/judge/admin/contest.py index 3ccad04..3a57793 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -158,7 +158,10 @@ class ContestAdmin(CompareVersionAdmin): ) }, ), - (_("Scheduling"), {"fields": ("start_time", "end_time", "time_limit")}), + ( + _("Scheduling"), + {"fields": ("start_time", "end_time", "time_limit", "freeze_after")}, + ), ( _("Details"), { @@ -274,7 +277,8 @@ class ContestAdmin(CompareVersionAdmin): # We need this flag because `save_related` deals with the inlines, but does not know if we have already rescored self._rescored = False if form.changed_data and any( - f in form.changed_data for f in ("format_config", "format_name") + f in form.changed_data + for f in ("format_config", "format_name", "freeze_after") ): self._rescore(obj.key) self._rescored = True diff --git a/judge/contest_format/atcoder.py b/judge/contest_format/atcoder.py index 64e0749..db42b73 100644 --- a/judge/contest_format/atcoder.py +++ b/judge/contest_format/atcoder.py @@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format -from judge.timezone import from_database_time +from judge.timezone import from_database_time, to_database_time from judge.utils.timedelta import nice_repr @@ -54,6 +54,10 @@ class AtCoderContestFormat(DefaultContestFormat): points = 0 format_data = {} + frozen_time = self.contest.end_time + if self.contest.freeze_after: + frozen_time = participation.start + self.contest.freeze_after + with connection.cursor() as cursor: cursor.execute( """ @@ -66,9 +70,10 @@ class AtCoderContestFormat(DefaultContestFormat): 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) + WHERE sub.date < %s GROUP BY cp.id """, - (participation.id, participation.id), + (participation.id, participation.id, to_database_time(frozen_time)), ) for score, time, prob in cursor.fetchall(): @@ -100,6 +105,7 @@ class AtCoderContestFormat(DefaultContestFormat): format_data[str(prob)] = {"time": dt, "points": score, "penalty": prev} points += score + self.handle_frozen_state(participation, format_data) participation.cumtime = cumtime + penalty participation.score = points participation.tiebreaker = 0 @@ -114,7 +120,7 @@ class AtCoderContestFormat(DefaultContestFormat): ' ({penalty})', penalty=floatformat(format_data["penalty"]), ) - if format_data["penalty"] + if format_data.get("penalty") else "" ) return format_html( @@ -129,6 +135,7 @@ class AtCoderContestFormat(DefaultContestFormat): + self.best_solution_state( format_data["points"], contest_problem.points ) + + (" frozen" if format_data.get("frozen") else "") ), url=reverse( "contest_user_submissions_ajax", diff --git a/judge/contest_format/base.py b/judge/contest_format/base.py index f27f2ac..848d81d 100644 --- a/judge/contest_format/base.py +++ b/judge/contest_format/base.py @@ -1,4 +1,5 @@ from abc import ABCMeta, abstractmethod, abstractproperty +from django.db.models import Max class abstractclassmethod(classmethod): @@ -95,3 +96,17 @@ class BaseContestFormat(metaclass=ABCMeta): if points == total: return "full-score" return "partial-score" + + def handle_frozen_state(self, participation, format_data): + if not self.contest.freeze_after: + return + queryset = participation.submissions.values("problem_id").annotate( + time=Max("submission__date") + ) + for result in queryset: + problem = str(result["problem_id"]) + if format_data.get(problem): + if result["time"] >= self.contest.freeze_after + participation.start: + format_data[problem]["frozen"] = True + else: + format_data[problem] = {"time": 0, "points": 0, "frozen": True} diff --git a/judge/contest_format/default.py b/judge/contest_format/default.py index 0c053e0..d8dd848 100644 --- a/judge/contest_format/default.py +++ b/judge/contest_format/default.py @@ -32,10 +32,19 @@ class DefaultContestFormat(BaseContestFormat): points = 0 format_data = {} - for result in participation.submissions.values("problem_id").annotate( + queryset = participation.submissions + + if self.contest.freeze_after: + queryset = queryset.filter( + submission__date__lt=participation.start + self.contest.freeze_after + ) + + queryset = queryset.values("problem_id").annotate( time=Max("submission__date"), points=Max("points"), - ): + ) + + for result in queryset: dt = (result["time"] - participation.start).total_seconds() if result["points"]: cumtime += dt @@ -45,6 +54,7 @@ class DefaultContestFormat(BaseContestFormat): } points += result["points"] + self.handle_frozen_state(participation, format_data) participation.cumtime = max(cumtime, 0) participation.score = points participation.tiebreaker = 0 @@ -66,6 +76,7 @@ class DefaultContestFormat(BaseContestFormat): + self.best_solution_state( format_data["points"], contest_problem.points ) + + (" frozen" if format_data.get("frozen") else "") ), url=reverse( "contest_user_submissions_ajax", diff --git a/judge/contest_format/ecoo.py b/judge/contest_format/ecoo.py index 007498c..149d00a 100644 --- a/judge/contest_format/ecoo.py +++ b/judge/contest_format/ecoo.py @@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format -from judge.timezone import from_database_time +from judge.timezone import from_database_time, to_database_time from judge.utils.timedelta import nice_repr @@ -60,6 +60,10 @@ class ECOOContestFormat(DefaultContestFormat): points = 0 format_data = {} + frozen_time = self.contest.end_time + if self.contest.freeze_after: + frozen_time = participation.start + self.contest.freeze_after + with connection.cursor() as cursor: cursor.execute( """ @@ -77,9 +81,15 @@ class ECOOContestFormat(DefaultContestFormat): 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) + WHERE sub.date < %s GROUP BY cp.id """, - (participation.id, participation.id, participation.id), + ( + participation.id, + participation.id, + participation.id, + to_database_time(frozen_time), + ), ) for score, time, prob, subs, max_score in cursor.fetchall(): @@ -105,6 +115,7 @@ class ECOOContestFormat(DefaultContestFormat): format_data[str(prob)] = {"time": dt, "points": score, "bonus": bonus} points += score + self.handle_frozen_state(participation, format_data) participation.cumtime = cumtime participation.score = points participation.tiebreaker = 0 @@ -134,6 +145,7 @@ class ECOOContestFormat(DefaultContestFormat): + self.best_solution_state( format_data["points"], contest_problem.points ) + + (" frozen" if format_data.get("frozen") else "") ), url=reverse( "contest_user_submissions_ajax", diff --git a/judge/contest_format/icpc.py b/judge/contest_format/icpc.py index aa76484..b366b01 100644 --- a/judge/contest_format/icpc.py +++ b/judge/contest_format/icpc.py @@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format -from judge.timezone import from_database_time +from judge.timezone import from_database_time, to_database_time from judge.utils.timedelta import nice_repr @@ -55,6 +55,10 @@ class ICPCContestFormat(DefaultContestFormat): score = 0 format_data = {} + frozen_time = self.contest.end_time + if self.contest.freeze_after: + frozen_time = participation.start + self.contest.freeze_after + with connection.cursor() as cursor: cursor.execute( """ @@ -67,9 +71,10 @@ class ICPCContestFormat(DefaultContestFormat): 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) + WHERE sub.date < %s GROUP BY cp.id """, - (participation.id, participation.id), + (participation.id, participation.id, to_database_time(frozen_time)), ) for points, time, prob in cursor.fetchall(): @@ -102,6 +107,7 @@ class ICPCContestFormat(DefaultContestFormat): format_data[str(prob)] = {"time": dt, "points": points, "penalty": prev} score += points + self.handle_frozen_state(participation, format_data) participation.cumtime = max(0, cumtime + penalty) participation.score = score participation.tiebreaker = last # field is sorted from least to greatest @@ -116,7 +122,7 @@ class ICPCContestFormat(DefaultContestFormat): ' +{penalty}', penalty=floatformat(format_data["penalty"]), ) - if format_data["penalty"] + if format_data.get("penalty") else "" ) return format_html( @@ -131,6 +137,7 @@ class ICPCContestFormat(DefaultContestFormat): + self.best_solution_state( format_data["points"], contest_problem.points ) + + (" frozen" if format_data.get("frozen") else "") ), url=reverse( "contest_user_submissions_ajax", diff --git a/judge/contest_format/ioi.py b/judge/contest_format/ioi.py index 9275f78..bcd18c3 100644 --- a/judge/contest_format/ioi.py +++ b/judge/contest_format/ioi.py @@ -12,6 +12,7 @@ from judge.contest_format.default import DefaultContestFormat from judge.contest_format.registry import register_contest_format from judge.timezone import from_database_time from judge.utils.timedelta import nice_repr +from django.db.models import Min, OuterRef, Subquery @register_contest_format("ioi") @@ -45,41 +46,42 @@ class IOIContestFormat(DefaultContestFormat): def update_participation(self, participation): cumtime = 0 - points = 0 + score = 0 format_data = {} - with connection.cursor() as cursor: - cursor.execute( - """ - SELECT MAX(cs.points) as `score`, ( - SELECT MIN(csub.date) - FROM judge_contestsubmission ccs LEFT OUTER JOIN - judge_submission csub ON (csub.id = ccs.submission_id) - WHERE ccs.problem_id = cp.id AND ccs.participation_id = %s AND ccs.points = MAX(cs.points) - ) AS `time`, cp.id AS `prob` - 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) - GROUP BY cp.id - """, - (participation.id, participation.id), + queryset = participation.submissions + if self.contest.freeze_after: + queryset = queryset.filter( + submission__date__lt=participation.start + self.contest.freeze_after ) - for score, time, prob in cursor.fetchall(): - if self.config["cumtime"]: - dt = ( - from_database_time(time) - participation.start - ).total_seconds() - if score: - cumtime += dt - else: - dt = 0 + queryset = ( + queryset.values("problem_id") + .filter( + points=Subquery( + queryset.filter(problem_id=OuterRef("problem_id")) + .order_by("-points") + .values("points")[:1] + ) + ) + .annotate(time=Min("submission__date")) + .values_list("problem_id", "time", "points") + ) - format_data[str(prob)] = {"time": dt, "points": score} - points += score + for problem_id, time, points in queryset: + if self.config["cumtime"]: + dt = (time - participation.start).total_seconds() + if points: + cumtime += dt + else: + dt = 0 + format_data[str(problem_id)] = {"points": points, "time": dt} + score += points + + self.handle_frozen_state(participation, format_data) participation.cumtime = max(cumtime, 0) - participation.score = points + participation.score = score participation.tiebreaker = 0 participation.format_data = format_data participation.save() @@ -99,6 +101,7 @@ class IOIContestFormat(DefaultContestFormat): + self.best_solution_state( format_data["points"], contest_problem.points ) + + (" frozen" if format_data.get("frozen") else "") ), url=reverse( "contest_user_submissions_ajax", diff --git a/judge/migrations/0139_contest_freeze_after.py b/judge/migrations/0139_contest_freeze_after.py new file mode 100644 index 0000000..97e4a25 --- /dev/null +++ b/judge/migrations/0139_contest_freeze_after.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.16 on 2022-11-18 06:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0138_bookmark_makebookmark"), + ] + + operations = [ + migrations.AddField( + model_name="contest", + name="freeze_after", + field=models.DurationField( + blank=True, + help_text="Format hh:mm:ss. For example, if you want to freeze contest after 2 hours, enter 02:00:00", + null=True, + verbose_name="freeze after", + ), + ), + ] diff --git a/judge/models/contest.py b/judge/models/contest.py index 1cd5795..0a7389b 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -128,6 +128,14 @@ class Contest(models.Model): "Format hh:mm:ss. For example, if you want a 2-hour contest, enter 02:00:00" ), ) + freeze_after = models.DurationField( + verbose_name=_("freeze after"), + blank=True, + null=True, + help_text=_( + "Format hh:mm:ss. For example, if you want to freeze contest after 2 hours, enter 02:00:00" + ), + ) is_visible = models.BooleanField( verbose_name=_("publicly visible"), default=False, diff --git a/judge/timezone.py b/judge/timezone.py index c3b5b04..c609649 100644 --- a/judge/timezone.py +++ b/judge/timezone.py @@ -2,7 +2,7 @@ import pytz from django.conf import settings from django.db import connection from django.utils import timezone -from django.utils.timezone import make_aware +from django.utils.timezone import make_aware, make_naive class TimezoneMiddleware(object): @@ -25,3 +25,10 @@ def from_database_time(datetime): if tz is None: return datetime return make_aware(datetime, tz) + + +def to_database_time(datetime): + tz = connection.timezone + if tz is None: + return datetime + return make_naive(datetime, tz) diff --git a/judge/views/submission.py b/judge/views/submission.py index 13b4651..3b22820 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -301,6 +301,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): template_name = "submission/list.html" context_object_name = "submissions" first_page_href = None + include_frozen = False def get_result_data(self): result = self._get_result_data() @@ -344,6 +345,10 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): queryset = queryset.filter(contest_object=self.contest) if not self.contest.can_see_full_scoreboard(self.request.user): queryset = queryset.filter(user=self.request.profile) + if self.contest.freeze_after and not self.include_frozen: + queryset = queryset.exclude( + date__gte=self.contest.freeze_after + self.contest.start_time + ) else: queryset = queryset.select_related("contest_object").defer( "contest_object__description" @@ -456,6 +461,9 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): self.selected_languages = set(request.GET.getlist("language")) self.selected_statuses = set(request.GET.getlist("status")) + if request.user.is_superuser: + self.include_frozen = True + if "results" in request.GET: return JsonResponse(self.get_result_data()) @@ -468,6 +476,8 @@ class UserMixin(object): raise ImproperlyConfigured("Must pass a user") self.profile = get_object_or_404(Profile, user__username=kwargs["user"]) self.username = kwargs["user"] + if self.profile == request.profile: + self.include_frozen = True return super(UserMixin, self).get(request, *args, **kwargs) @@ -837,8 +847,8 @@ class UserContestSubmissionsAjax(UserContestSubmissions): s.contest_time = nice_repr(contest_time, "noday") else: s.contest_time = None - points = floatformat(s.contest.points, -self.contest.points_precision) total = floatformat(contest_problem.points, -self.contest.points_precision) + points = floatformat(s.contest.points, -self.contest.points_precision) s.display_point = f"{points} / {total}" filtered_submissions.append(s) context["submissions"] = filtered_submissions diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index c9b34f3..800e099 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-18 05:10+0700\n" +"POT-Creation-Date: 2022-11-19 03:12+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -19,8 +19,8 @@ msgstr "" "X-Crowdin-File-ID: 5\n" #: chat_box/models.py:31 chat_box/models.py:51 chat_box/models.py:65 -#: judge/admin/interface.py:150 judge/models/contest.py:622 -#: judge/models/contest.py:815 judge/models/profile.py:354 +#: judge/admin/interface.py:150 judge/models/contest.py:630 +#: judge/models/contest.py:823 judge/models/profile.py:354 #: judge/models/profile.py:430 msgid "user" msgstr "người dùng" @@ -99,72 +99,72 @@ msgstr "Bài tập" msgid "Settings" msgstr "Cài đặt" -#: judge/admin/contest.py:161 +#: judge/admin/contest.py:162 msgid "Scheduling" msgstr "" -#: judge/admin/contest.py:163 +#: judge/admin/contest.py:166 msgid "Details" msgstr "Chi tiết" -#: judge/admin/contest.py:175 +#: judge/admin/contest.py:178 msgid "Format" msgstr "Thể thức" -#: judge/admin/contest.py:179 templates/contest/ranking-table.html:7 +#: judge/admin/contest.py:182 templates/contest/ranking-table.html:7 #: templates/user/user-about.html:15 templates/user/user-about.html:45 msgid "Rating" msgstr "" -#: judge/admin/contest.py:191 +#: judge/admin/contest.py:194 msgid "Access" msgstr "Truy cập" -#: judge/admin/contest.py:201 judge/admin/problem.py:219 +#: judge/admin/contest.py:204 judge/admin/problem.py:219 msgid "Justice" msgstr "Xử phạt" -#: judge/admin/contest.py:313 +#: judge/admin/contest.py:317 #, python-format msgid "%d contest successfully marked as visible." msgid_plural "%d contests successfully marked as visible." msgstr[0] "%d kỳ thi đã được đánh dấu hiển thị." -#: judge/admin/contest.py:320 +#: judge/admin/contest.py:324 msgid "Mark contests as visible" msgstr "Đánh dấu hiển thị các kỳ thi" -#: judge/admin/contest.py:331 +#: judge/admin/contest.py:335 #, python-format msgid "%d contest successfully marked as hidden." msgid_plural "%d contests successfully marked as hidden." msgstr[0] "%d kỳ thi đã được đánh dấu ẩn." -#: judge/admin/contest.py:338 +#: judge/admin/contest.py:342 msgid "Mark contests as hidden" msgstr "Ẩn các kỳ thi" -#: judge/admin/contest.py:359 judge/admin/submission.py:243 +#: judge/admin/contest.py:363 judge/admin/submission.py:243 #, python-format msgid "%d submission was successfully scheduled for rejudging." msgid_plural "%d submissions were successfully scheduled for rejudging." msgstr[0] "%d bài nộp đã được lên lịch thành công để chấm lại." -#: judge/admin/contest.py:467 +#: judge/admin/contest.py:471 #, python-format msgid "%d participation recalculated." msgid_plural "%d participations recalculated." msgstr[0] "%d thí sinh đã được tính điểm lại." -#: judge/admin/contest.py:474 +#: judge/admin/contest.py:478 msgid "Recalculate results" msgstr "Tính toán lại kết quả" -#: judge/admin/contest.py:479 judge/admin/organization.py:99 +#: judge/admin/contest.py:483 judge/admin/organization.py:99 msgid "username" msgstr "tên đăng nhập" -#: judge/admin/contest.py:485 templates/base.html:315 +#: judge/admin/contest.py:489 templates/base.html:315 msgid "virtual" msgstr "ảo" @@ -567,13 +567,13 @@ msgstr "Lưu" msgid "bookmarks" msgstr "Lưu" -#: judge/models/bookmark.py:56 +#: judge/models/bookmark.py:59 #, fuzzy #| msgid "Bookmark" msgid "make bookmark" msgstr "Lưu" -#: judge/models/bookmark.py:57 +#: judge/models/bookmark.py:60 #, fuzzy #| msgid "Bookmark" msgid "make bookmarks" @@ -696,7 +696,7 @@ msgstr "" msgid "contest tag" msgstr "" -#: judge/models/contest.py:74 judge/models/contest.py:234 +#: judge/models/contest.py:74 judge/models/contest.py:242 msgid "contest tags" msgstr "nhãn kỳ thi" @@ -751,7 +751,7 @@ msgstr "mô tả" msgid "problems" msgstr "bài tập" -#: judge/models/contest.py:121 judge/models/contest.py:627 +#: judge/models/contest.py:121 judge/models/contest.py:635 msgid "start time" msgstr "thời gian bắt đầu" @@ -771,11 +771,23 @@ msgstr "" "Định dạng hh:mm:ss (giờ:phút:giây). Ví dụ, nếu muốn tạo kỳ thi dài 2h, hãy " "nhập 02:00:00" -#: judge/models/contest.py:132 judge/models/problem.py:222 +#: judge/models/contest.py:132 +msgid "freeze after" +msgstr "đóng băng sau" + +#: judge/models/contest.py:136 +msgid "" +"Format hh:mm:ss. For example, if you want to freeze contest after 2 hours, " +"enter 02:00:00" +msgstr "" +"Định dạng hh:mm:ss (giờ:phút:giây). Ví dụ, nếu muốn đóng băng kỳ thi sau 2h, hãy " +"nhập 02:00:00" + +#: judge/models/contest.py:140 judge/models/problem.py:222 msgid "publicly visible" msgstr "công khai" -#: judge/models/contest.py:135 +#: judge/models/contest.py:143 msgid "" "Should be set even for organization-private contests, where it determines " "whether the contest is visible to members of the specified organizations." @@ -783,84 +795,84 @@ msgstr "" "Đánh dấu ngay cả với các kỳ thi riêng tư của nhóm, quyết định việc kỳ thi có " "được hiển thị với các thành viên hay không." -#: judge/models/contest.py:141 +#: judge/models/contest.py:149 msgid "contest rated" msgstr "kỳ thi được xếp hạng" -#: judge/models/contest.py:142 +#: judge/models/contest.py:150 msgid "Whether this contest can be rated." msgstr "Quyết định kỳ thi có được xếp hạng không." -#: judge/models/contest.py:146 +#: judge/models/contest.py:154 msgid "scoreboard visibility" msgstr "khả năng hiển thị của bảng điểm" -#: judge/models/contest.py:149 +#: judge/models/contest.py:157 msgid "Scoreboard visibility through the duration of the contest" msgstr "Khả năng hiển thị của bảng điểm trong thời gian kỳ thi" -#: judge/models/contest.py:154 +#: judge/models/contest.py:162 msgid "view contest scoreboard" msgstr "xem bảng điểm kỳ thi" -#: judge/models/contest.py:157 +#: judge/models/contest.py:165 msgid "These users will be able to view the scoreboard." msgstr "Những người dùng này được phép xem bảng điểm." -#: judge/models/contest.py:160 +#: judge/models/contest.py:168 msgid "no comments" msgstr "không bình luận" -#: judge/models/contest.py:161 +#: judge/models/contest.py:169 msgid "Use clarification system instead of comments." msgstr "Dùng hệ thống thông báo thay vì bình luận." -#: judge/models/contest.py:166 +#: judge/models/contest.py:174 msgid "Rating floor for contest" msgstr "Cận dưới rating được xếp hạng trong kỳ thi" -#: judge/models/contest.py:172 +#: judge/models/contest.py:180 msgid "Rating ceiling for contest" msgstr "Cận trên rating được xếp hạng trong kỳ thi" -#: judge/models/contest.py:177 +#: judge/models/contest.py:185 msgid "rate all" msgstr "xếp hạng tất cả" -#: judge/models/contest.py:178 +#: judge/models/contest.py:186 msgid "Rate all users who joined." msgstr "Xếp hạng tất cả người dùng đã tham gia (kể cả không nộp)." -#: judge/models/contest.py:183 +#: judge/models/contest.py:191 msgid "exclude from ratings" msgstr "không xếp hạng" -#: judge/models/contest.py:188 +#: judge/models/contest.py:196 msgid "private to specific users" msgstr "riêng tư với các người dùng này" -#: judge/models/contest.py:193 +#: judge/models/contest.py:201 msgid "private contestants" msgstr "thí sinh riêng tư" -#: judge/models/contest.py:194 +#: judge/models/contest.py:202 msgid "If private, only these users may see the contest" msgstr "Nếu riêng tư, chỉ những người dùng này mới thấy kỳ thi" -#: judge/models/contest.py:198 +#: judge/models/contest.py:206 msgid "hide problem tags" msgstr "ẩn nhãn kỳ thi" -#: judge/models/contest.py:199 +#: judge/models/contest.py:207 msgid "Whether problem tags should be hidden by default." msgstr "" "Quyết định việc nhãn bài tập (DP, Tham lam, ...) được ẩn trong kỳ thi không." -#: judge/models/contest.py:203 +#: judge/models/contest.py:211 msgid "run pretests only" msgstr "chỉ chạy pretests" -#: judge/models/contest.py:205 +#: judge/models/contest.py:213 msgid "" "Whether judges should grade pretests only, versus all testcases. Commonly " "set during a contest, then unset prior to rejudging user submissions when " @@ -869,50 +881,50 @@ msgstr "" "Quyết định việc các máy chấm chỉ chấm pretests thay vì tất cả các test. Sau " "kỳ thi, hãy bỏ đánh dấu ô này và chấm lại tất cả các bài." -#: judge/models/contest.py:212 judge/models/interface.py:92 +#: judge/models/contest.py:220 judge/models/interface.py:92 #: judge/models/problem.py:279 msgid "private to organizations" msgstr "riêng tư với các tổ chức" -#: judge/models/contest.py:217 judge/models/interface.py:88 +#: judge/models/contest.py:225 judge/models/interface.py:88 #: judge/models/problem.py:275 judge/models/profile.py:126 msgid "organizations" msgstr "tổ chức" -#: judge/models/contest.py:218 +#: judge/models/contest.py:226 msgid "If private, only these organizations may see the contest" msgstr "Nếu riêng tư, chỉ những tổ chức này thấy được kỳ thi" -#: judge/models/contest.py:221 judge/models/problem.py:253 +#: judge/models/contest.py:229 judge/models/problem.py:253 msgid "OpenGraph image" msgstr "Hình ảnh OpenGraph" -#: judge/models/contest.py:224 judge/models/profile.py:81 +#: judge/models/contest.py:232 judge/models/profile.py:81 msgid "Logo override image" msgstr "Hình ảnh ghi đè logo" -#: judge/models/contest.py:229 +#: judge/models/contest.py:237 msgid "" "This image will replace the default site logo for users inside the contest." msgstr "Ảnh này sẽ thay thế cho logo mặc định trong kỳ thi." -#: judge/models/contest.py:237 +#: judge/models/contest.py:245 msgid "the amount of live participants" msgstr "số lượng thí sinh thi trực tiếp" -#: judge/models/contest.py:241 +#: judge/models/contest.py:249 msgid "contest summary" msgstr "tổng kết kỳ thi" -#: judge/models/contest.py:243 judge/models/problem.py:259 +#: judge/models/contest.py:251 judge/models/problem.py:259 msgid "Plain-text, shown in meta description tag, e.g. for social media." msgstr "" -#: judge/models/contest.py:247 judge/models/profile.py:76 +#: judge/models/contest.py:255 judge/models/profile.py:76 msgid "access code" msgstr "mật khẩu truy cập" -#: judge/models/contest.py:252 +#: judge/models/contest.py:260 msgid "" "An optional code to prompt contestants before they are allowed to join the " "contest. Leave it blank to disable." @@ -920,267 +932,267 @@ msgstr "" "Mật khẩu truy cập cho các thí sinh muốn tham gia kỳ thi. Để trống nếu không " "dùng." -#: judge/models/contest.py:258 judge/models/problem.py:241 +#: judge/models/contest.py:266 judge/models/problem.py:241 msgid "personae non gratae" msgstr "Chặn tham gia" -#: judge/models/contest.py:260 +#: judge/models/contest.py:268 msgid "Bans the selected users from joining this contest." msgstr "Cấm những người dùng được chọn tham gia kỳ thi." -#: judge/models/contest.py:263 +#: judge/models/contest.py:271 msgid "contest format" msgstr "format kỳ thi" -#: judge/models/contest.py:267 +#: judge/models/contest.py:275 msgid "The contest format module to use." msgstr "Format kỳ thi sử dụng." -#: judge/models/contest.py:270 +#: judge/models/contest.py:278 msgid "contest format configuration" msgstr "Tùy chỉnh format kỳ thi" -#: judge/models/contest.py:274 +#: judge/models/contest.py:282 msgid "" "A JSON object to serve as the configuration for the chosen contest format " "module. Leave empty to use None. Exact format depends on the contest format " "selected." msgstr "" -#: judge/models/contest.py:287 +#: judge/models/contest.py:295 msgid "precision points" msgstr "Hiển thị điểm" -#: judge/models/contest.py:290 +#: judge/models/contest.py:298 msgid "Number of digits to round points to." msgstr "Số chữ số thập phân trên bảng điểm." -#: judge/models/contest.py:595 +#: judge/models/contest.py:603 msgid "See private contests" msgstr "" -#: judge/models/contest.py:596 +#: judge/models/contest.py:604 msgid "Edit own contests" msgstr "" -#: judge/models/contest.py:597 +#: judge/models/contest.py:605 msgid "Edit all contests" msgstr "" -#: judge/models/contest.py:598 +#: judge/models/contest.py:606 msgid "Clone contest" msgstr "" -#: judge/models/contest.py:599 templates/contest/moss.html:74 +#: judge/models/contest.py:607 templates/contest/moss.html:74 msgid "MOSS contest" msgstr "" -#: judge/models/contest.py:600 +#: judge/models/contest.py:608 msgid "Rate contests" msgstr "" -#: judge/models/contest.py:601 +#: judge/models/contest.py:609 msgid "Contest access codes" msgstr "" -#: judge/models/contest.py:602 +#: judge/models/contest.py:610 msgid "Create private contests" msgstr "" -#: judge/models/contest.py:603 +#: judge/models/contest.py:611 msgid "Change contest visibility" msgstr "" -#: judge/models/contest.py:604 +#: judge/models/contest.py:612 msgid "Edit contest problem label script" msgstr "Cách hiển thị thứ tự bài tập" -#: judge/models/contest.py:606 judge/models/contest.py:744 -#: judge/models/contest.py:818 judge/models/contest.py:848 +#: judge/models/contest.py:614 judge/models/contest.py:752 +#: judge/models/contest.py:826 judge/models/contest.py:856 #: judge/models/submission.py:116 msgid "contest" msgstr "kỳ thi" -#: judge/models/contest.py:607 +#: judge/models/contest.py:615 msgid "contests" msgstr "kỳ thi" -#: judge/models/contest.py:616 +#: judge/models/contest.py:624 msgid "associated contest" msgstr "" -#: judge/models/contest.py:629 +#: judge/models/contest.py:637 msgid "score" msgstr "điểm" -#: judge/models/contest.py:630 +#: judge/models/contest.py:638 msgid "cumulative time" msgstr "tổng thời gian" -#: judge/models/contest.py:632 +#: judge/models/contest.py:640 msgid "is disqualified" msgstr "đã bị loại" -#: judge/models/contest.py:634 +#: judge/models/contest.py:642 msgid "Whether this participation is disqualified." msgstr "Quyết định thí sinh có bị loại không." -#: judge/models/contest.py:636 +#: judge/models/contest.py:644 msgid "tie-breaking field" msgstr "" -#: judge/models/contest.py:638 +#: judge/models/contest.py:646 msgid "virtual participation id" msgstr "id lần tham gia ảo" -#: judge/models/contest.py:640 +#: judge/models/contest.py:648 msgid "0 means non-virtual, otherwise the n-th virtual participation." msgstr "0 nghĩa là tham gia chính thức, ngược lại là lần tham gia ảo thứ n." -#: judge/models/contest.py:643 +#: judge/models/contest.py:651 msgid "contest format specific data" msgstr "" -#: judge/models/contest.py:719 +#: judge/models/contest.py:727 #, python-format msgid "%s spectating in %s" msgstr "%s đang theo dõi trong %s" -#: judge/models/contest.py:724 +#: judge/models/contest.py:732 #, python-format msgid "%s in %s, v%d" msgstr "%s trong %s, v%d" -#: judge/models/contest.py:729 +#: judge/models/contest.py:737 #, python-format msgid "%s in %s" msgstr "%s trong %s" -#: judge/models/contest.py:732 +#: judge/models/contest.py:740 msgid "contest participation" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:733 +#: judge/models/contest.py:741 msgid "contest participations" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:740 judge/models/contest.py:789 -#: judge/models/contest.py:851 judge/models/problem.py:558 +#: judge/models/contest.py:748 judge/models/contest.py:797 +#: judge/models/contest.py:859 judge/models/problem.py:558 #: judge/models/problem.py:565 judge/models/problem.py:586 #: judge/models/problem.py:617 judge/models/problem_data.py:50 msgid "problem" msgstr "bài tập" -#: judge/models/contest.py:748 judge/models/contest.py:801 +#: judge/models/contest.py:756 judge/models/contest.py:809 #: judge/models/problem.py:206 msgid "points" msgstr "điểm" -#: judge/models/contest.py:749 +#: judge/models/contest.py:757 msgid "partial" msgstr "thành phần" -#: judge/models/contest.py:750 judge/models/contest.py:803 +#: judge/models/contest.py:758 judge/models/contest.py:811 msgid "is pretested" msgstr "dùng pretest" -#: judge/models/contest.py:751 judge/models/interface.py:43 +#: judge/models/contest.py:759 judge/models/interface.py:43 msgid "order" msgstr "thứ tự" -#: judge/models/contest.py:753 +#: judge/models/contest.py:761 msgid "0 to not show testcases, 1 to show" msgstr "0 để ẩn test, 1 để hiện" -#: judge/models/contest.py:754 +#: judge/models/contest.py:762 msgid "visible testcases" msgstr "hiển thị test" -#: judge/models/contest.py:761 +#: judge/models/contest.py:769 msgid "Maximum number of submissions for this problem, or 0 for no limit." msgstr "Số lần nộp tối đa, đặt là 0 nếu không có giới hạn." -#: judge/models/contest.py:763 +#: judge/models/contest.py:771 msgid "max submissions" msgstr "số lần nộp tối đa" -#: judge/models/contest.py:766 +#: judge/models/contest.py:774 msgid "Why include a problem you can't submit to?" msgstr "" -#: judge/models/contest.py:776 +#: judge/models/contest.py:784 msgid "contest problem" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:777 +#: judge/models/contest.py:785 msgid "contest problems" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:783 judge/models/submission.py:233 +#: judge/models/contest.py:791 judge/models/submission.py:233 msgid "submission" msgstr "bài nộp" -#: judge/models/contest.py:796 judge/models/contest.py:822 +#: judge/models/contest.py:804 judge/models/contest.py:830 msgid "participation" msgstr "lần tham gia" -#: judge/models/contest.py:804 +#: judge/models/contest.py:812 msgid "Whether this submission was ran only on pretests." msgstr "Quyết định bài nộp chỉ được chạy trên pretest không." -#: judge/models/contest.py:809 +#: judge/models/contest.py:817 msgid "contest submission" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:810 +#: judge/models/contest.py:818 msgid "contest submissions" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:826 +#: judge/models/contest.py:834 msgid "rank" msgstr "rank" -#: judge/models/contest.py:827 +#: judge/models/contest.py:835 msgid "rating" msgstr "rating" -#: judge/models/contest.py:828 +#: judge/models/contest.py:836 msgid "raw rating" msgstr "rating thật" -#: judge/models/contest.py:829 +#: judge/models/contest.py:837 msgid "contest performance" msgstr "" -#: judge/models/contest.py:830 +#: judge/models/contest.py:838 msgid "last rated" msgstr "lần cuối được xếp hạng" -#: judge/models/contest.py:834 +#: judge/models/contest.py:842 msgid "contest rating" msgstr "rating kỳ thi" -#: judge/models/contest.py:835 +#: judge/models/contest.py:843 msgid "contest ratings" msgstr "rating kỳ thi" -#: judge/models/contest.py:859 +#: judge/models/contest.py:867 msgid "contest moss result" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:860 +#: judge/models/contest.py:868 msgid "contest moss results" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:865 +#: judge/models/contest.py:873 msgid "clarified problem" msgstr "" -#: judge/models/contest.py:867 +#: judge/models/contest.py:875 msgid "clarification body" msgstr "" -#: judge/models/contest.py:869 +#: judge/models/contest.py:877 msgid "clarification timestamp" msgstr "" @@ -2554,7 +2566,7 @@ msgid "Editing comment" msgstr "Chỉnh sửa bình luận" #: judge/views/contests.py:119 judge/views/contests.py:367 -#: judge/views/contests.py:372 judge/views/contests.py:613 +#: judge/views/contests.py:372 judge/views/contests.py:619 msgid "No such contest" msgstr "Không có contest nào như vậy" @@ -2578,113 +2590,113 @@ msgstr "Không tìm thấy kỳ thi nào như vậy." msgid "Access to contest \"%s\" denied" msgstr "Truy cập tới kỳ thi \"%s\" bị từ chối" -#: judge/views/contests.py:418 +#: judge/views/contests.py:424 msgid "Clone Contest" msgstr "Nhân bản kỳ thi" -#: judge/views/contests.py:487 +#: judge/views/contests.py:493 msgid "Contest not ongoing" msgstr "Kỳ thi đang không diễn ra" -#: judge/views/contests.py:488 +#: judge/views/contests.py:494 #, python-format msgid "\"%s\" is not currently ongoing." msgstr "\"%s\" kỳ thi đang không diễn ra." -#: judge/views/contests.py:495 +#: judge/views/contests.py:501 msgid "Already in contest" msgstr "Đã ở trong kỳ thi" -#: judge/views/contests.py:496 +#: judge/views/contests.py:502 #, python-format msgid "You are already in a contest: \"%s\"." msgstr "Bạn đã ở trong kỳ thi: \"%s\"." -#: judge/views/contests.py:506 +#: judge/views/contests.py:512 msgid "Banned from joining" msgstr "Bị cấm tham gia" -#: judge/views/contests.py:508 +#: judge/views/contests.py:514 msgid "" "You have been declared persona non grata for this contest. You are " "permanently barred from joining this contest." msgstr "Bạn không được phép tham gia kỳ thi này." -#: judge/views/contests.py:597 +#: judge/views/contests.py:603 #, python-format msgid "Enter access code for \"%s\"" msgstr "Nhập mật khẩu truy cập cho \"%s\"" -#: judge/views/contests.py:614 +#: judge/views/contests.py:620 #, python-format msgid "You are not in contest \"%s\"." msgstr "Bạn không ở trong kỳ thi \"%s\"." -#: judge/views/contests.py:637 +#: judge/views/contests.py:643 msgid "ContestCalendar requires integer year and month" msgstr "Lịch thi yêu cầu giá trị cho năm và tháng là số nguyên" -#: judge/views/contests.py:695 +#: judge/views/contests.py:701 #, python-format msgid "Contests in %(month)s" msgstr "Các kỳ thi trong %(month)s" -#: judge/views/contests.py:696 +#: judge/views/contests.py:702 msgid "F Y" msgstr "F Y" -#: judge/views/contests.py:756 +#: judge/views/contests.py:762 #, python-format msgid "%s Statistics" msgstr "%s Thống kê" -#: judge/views/contests.py:1013 +#: judge/views/contests.py:1019 #, python-format msgid "%s Rankings" msgstr "%s Bảng điểm" -#: judge/views/contests.py:1024 +#: judge/views/contests.py:1030 msgid "???" msgstr "???" -#: judge/views/contests.py:1040 +#: judge/views/contests.py:1046 #, python-format msgid "Your participation in %s" msgstr "Lần tham gia trong %s" -#: judge/views/contests.py:1041 +#: judge/views/contests.py:1047 #, python-format msgid "%s's participation in %s" msgstr "Lần tham gia của %s trong %s" -#: judge/views/contests.py:1055 +#: judge/views/contests.py:1061 msgid "Live" msgstr "Trực tiếp" -#: judge/views/contests.py:1074 templates/contest/contest-tabs.html:13 +#: judge/views/contests.py:1080 templates/contest/contest-tabs.html:13 msgid "Participation" msgstr "Lần tham gia" -#: judge/views/contests.py:1123 +#: judge/views/contests.py:1129 #, python-format msgid "%s MOSS Results" msgstr "%s Kết quả MOSS" -#: judge/views/contests.py:1159 +#: judge/views/contests.py:1165 #, python-format msgid "Running MOSS for %s..." msgstr "Đang chạy MOSS cho %s..." -#: judge/views/contests.py:1182 +#: judge/views/contests.py:1188 #, python-format msgid "Contest tag: %s" msgstr "Nhãn kỳ thi: %s" -#: judge/views/contests.py:1197 judge/views/ticket.py:72 +#: judge/views/contests.py:1203 judge/views/ticket.py:72 msgid "Issue description" msgstr "Mô tả vấn đề" -#: judge/views/contests.py:1244 +#: judge/views/contests.py:1250 #, python-format msgid "New clarification for %s" msgstr "Thông báo mới cho %s" @@ -2930,41 +2942,41 @@ msgstr "Hướng dẫn cho {0}" msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" -#: judge/views/problem.py:443 templates/contest/contest.html:81 +#: judge/views/problem.py:447 templates/contest/contest.html:81 #: templates/organization/org-left-sidebar.html:4 #: templates/user/user-about.html:28 templates/user/user-bookmarks.html:32 #: templates/user/user-tabs.html:5 templates/user/users-table.html:19 msgid "Problems" msgstr "Bài tập" -#: judge/views/problem.py:823 +#: judge/views/problem.py:827 msgid "Problem feed" msgstr "Bài tập" -#: judge/views/problem.py:1061 +#: judge/views/problem.py:1065 msgid "Banned from submitting" msgstr "Bị cấm nộp bài" -#: judge/views/problem.py:1063 +#: judge/views/problem.py:1067 msgid "" "You have been declared persona non grata for this problem. You are " "permanently barred from submitting this problem." msgstr "Bạn đã bị cấm nộp bài này." -#: judge/views/problem.py:1086 +#: judge/views/problem.py:1090 msgid "Too many submissions" msgstr "Quá nhiều lần nộp" -#: judge/views/problem.py:1088 +#: judge/views/problem.py:1092 msgid "You have exceeded the submission limit for this problem." msgstr "Bạn đã vượt quá số lần nộp cho bài này." -#: judge/views/problem.py:1167 judge/views/problem.py:1172 +#: judge/views/problem.py:1171 judge/views/problem.py:1176 #, python-format msgid "Submit to %(problem)s" msgstr "Nộp bài cho %(problem)s" -#: judge/views/problem.py:1195 +#: judge/views/problem.py:1199 msgid "Clone Problem" msgstr "Nhân bản bài tập" @@ -3138,39 +3150,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:495 +#: judge/views/submission.py:505 msgid "All my submissions" msgstr "Tất cả bài nộp của tôi" -#: judge/views/submission.py:496 +#: judge/views/submission.py:506 #, python-format msgid "All submissions by %s" msgstr "Tất cả bài nộp của %s" -#: judge/views/submission.py:541 +#: judge/views/submission.py:551 #, python-format msgid "All submissions for %s" msgstr "Tất cả bài nộp cho %s" -#: judge/views/submission.py:569 +#: judge/views/submission.py:579 msgid "Must pass a problem" msgstr "Phải làm được một bài" -#: judge/views/submission.py:627 +#: judge/views/submission.py:637 #, python-format msgid "My submissions for %(problem)s" msgstr "Bài nộp của tôi cho %(problem)s" -#: judge/views/submission.py:628 +#: judge/views/submission.py:638 #, 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:758 +#: judge/views/submission.py:768 msgid "Must pass a contest" msgstr "Phải qua một kỳ thi" -#: judge/views/submission.py:788 +#: judge/views/submission.py:798 #, python-brace-format msgid "" "{0}'s submissions for {2} in {0} cho {2} trong {4}" -#: judge/views/submission.py:800 +#: judge/views/submission.py:810 #, python-brace-format msgid "" "{0}'s submissions for problem {2} in {3}" @@ -3188,7 +3200,7 @@ msgstr "" "Các bài nộp của {0} cho bài {2} trong {3}" "" -#: judge/views/submission.py:851 +#: judge/views/submission.py:861 #, fuzzy #| msgid "You do not have the permission to rejudge submissions." msgid "You don't have permission to access." @@ -3248,48 +3260,48 @@ msgstr "Hủy kích hoạt Two Factor Authentication" msgid "Perform Two Factor Authentication" msgstr "Thực hiện Two Factor Authentication" -#: judge/views/user.py:90 +#: judge/views/user.py:97 msgid "No such user" msgstr "Không người dùng nào như vậy" -#: judge/views/user.py:91 +#: judge/views/user.py:98 #, python-format msgid "No user handle \"%s\"." msgstr "Không tồn tại tên người dùng \"%s\"." -#: judge/views/user.py:96 +#: judge/views/user.py:103 msgid "My account" msgstr "Tài khoản của tôi" -#: judge/views/user.py:98 +#: judge/views/user.py:105 #, python-format msgid "User %s" msgstr "Thành viên %s" -#: judge/views/user.py:196 +#: judge/views/user.py:203 msgid "M j, Y" msgstr "j M, Y" -#: judge/views/user.py:231 +#: judge/views/user.py:238 msgid "M j, Y, G:i" msgstr "j M, Y, G:i" -#: judge/views/user.py:409 +#: judge/views/user.py:417 msgid "Updated on site" msgstr "Được cập nhật trên web" -#: judge/views/user.py:464 templates/admin/auth/user/change_form.html:14 +#: judge/views/user.py:472 templates/admin/auth/user/change_form.html:14 #: templates/admin/auth/user/change_form.html:17 templates/base.html:280 #: templates/user/user-tabs.html:11 msgid "Edit profile" msgstr "Chỉnh sửa thông tin" -#: judge/views/user.py:475 templates/user/user-left-sidebar.html:2 +#: judge/views/user.py:483 templates/user/user-left-sidebar.html:2 #: templates/user/user-list-tabs.html:4 msgid "Leaderboard" msgstr "Xếp hạng" -#: judge/views/user.py:574 +#: judge/views/user.py:582 msgid "Import Users" msgstr "" diff --git a/templates/contest/ranking-css.html b/templates/contest/ranking-css.html index 43215f0..54fd018 100644 --- a/templates/contest/ranking-css.html +++ b/templates/contest/ranking-css.html @@ -35,6 +35,10 @@ background: #00f9a1; } + .frozen { + background: #a5c5d9 !important; + } + .rank { min-width: 2.5em }