diff --git a/dmoj/urls.py b/dmoj/urls.py index 9f1832d..df3d293 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -540,6 +540,10 @@ urlpatterns = [ r"^contests/summary/(?P\w+)/", paged_list_view(contests.ContestsSummaryView, "contests_summary"), ), + url( + r"^contests/official", + paged_list_view(contests.OfficialContestList, "official_contest_list"), + ), url(r"^courses/", paged_list_view(course.CourseList, "course_list")), url( r"^course/(?P[\w-]*)", diff --git a/judge/admin/__init__.py b/judge/admin/__init__.py index afeae86..d303048 100644 --- a/judge/admin/__init__.py +++ b/judge/admin/__init__.py @@ -20,7 +20,12 @@ from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin from judge.admin.profile import ProfileAdmin, UserAdmin from judge.admin.runtime import JudgeAdmin, LanguageAdmin from judge.admin.submission import SubmissionAdmin -from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin +from judge.admin.taxon import ( + ProblemGroupAdmin, + ProblemTypeAdmin, + OfficialContestCategoryAdmin, + OfficialContestLocationAdmin, +) from judge.admin.ticket import TicketAdmin from judge.admin.volunteer import VolunteerProblemVoteAdmin from judge.admin.course import CourseAdmin @@ -48,6 +53,8 @@ from judge.models import ( VolunteerProblemVote, Course, ContestsSummary, + OfficialContestCategory, + OfficialContestLocation, ) @@ -77,3 +84,5 @@ admin.site.register(Course, CourseAdmin) admin.site.unregister(User) admin.site.register(User, UserAdmin) admin.site.register(ContestsSummary, ContestsSummaryAdmin) +admin.site.register(OfficialContestCategory, OfficialContestCategoryAdmin) +admin.site.register(OfficialContestLocation, OfficialContestLocationAdmin) diff --git a/judge/admin/contest.py b/judge/admin/contest.py index 4e58ec4..5b157dc 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -14,7 +14,14 @@ from reversion.admin import VersionAdmin from reversion_compare.admin import CompareVersionAdmin from django_ace import AceWidget -from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating +from judge.models import ( + Contest, + ContestProblem, + ContestSubmission, + Profile, + Rating, + OfficialContest, +) from judge.ratings import rate_contest from judge.widgets import ( AdminHeavySelect2MultipleWidget, @@ -149,6 +156,26 @@ class ContestForm(ModelForm): ) +class OfficialContestInlineForm(ModelForm): + class Meta: + widgets = { + "category": AdminSelect2Widget, + "location": AdminSelect2Widget, + } + + +class OfficialContestInline(admin.StackedInline): + fields = ( + "category", + "year", + "location", + ) + model = OfficialContest + can_delete = True + form = OfficialContestInlineForm + extra = 0 + + class ContestAdmin(CompareVersionAdmin): fieldsets = ( (None, {"fields": ("key", "name", "authors", "curators", "testers")}), @@ -223,7 +250,7 @@ class ContestAdmin(CompareVersionAdmin): "user_count", ) search_fields = ("key", "name") - inlines = [ContestProblemInline] + inlines = [ContestProblemInline, OfficialContestInline] actions_on_top = True actions_on_bottom = True form = ContestForm diff --git a/judge/admin/taxon.py b/judge/admin/taxon.py index 4335efe..dfdc622 100644 --- a/judge/admin/taxon.py +++ b/judge/admin/taxon.py @@ -56,3 +56,11 @@ class ProblemTypeAdmin(admin.ModelAdmin): [o.pk for o in obj.problem_set.all()] if obj else [] ) return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs) + + +class OfficialContestCategoryAdmin(admin.ModelAdmin): + fields = ("name",) + + +class OfficialContestLocationAdmin(admin.ModelAdmin): + fields = ("name",) diff --git a/judge/migrations/0188_official_contest.py b/judge/migrations/0188_official_contest.py new file mode 100644 index 0000000..89414a7 --- /dev/null +++ b/judge/migrations/0188_official_contest.py @@ -0,0 +1,110 @@ +# Generated by Django 3.2.18 on 2024-05-30 04:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0187_profile_info"), + ] + + operations = [ + migrations.CreateModel( + name="OfficialContestCategory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + max_length=50, + unique=True, + verbose_name="official contest category", + ), + ), + ], + options={ + "verbose_name": "official contest category", + "verbose_name_plural": "official contest categories", + }, + ), + migrations.CreateModel( + name="OfficialContestLocation", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + max_length=50, + unique=True, + verbose_name="official contest location", + ), + ), + ], + options={ + "verbose_name": "official contest location", + "verbose_name_plural": "official contest locations", + }, + ), + migrations.CreateModel( + name="OfficialContest", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("year", models.PositiveIntegerField(verbose_name="year")), + ( + "category", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="judge.officialcontestcategory", + verbose_name="contest category", + ), + ), + ( + "contest", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="official", + to="judge.contest", + verbose_name="contest", + ), + ), + ( + "location", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="judge.officialcontestlocation", + verbose_name="contest location", + ), + ), + ], + options={ + "verbose_name": "official contest", + "verbose_name_plural": "official contests", + }, + ), + ] diff --git a/judge/models/__init__.py b/judge/models/__init__.py index c892e3d..0c69602 100644 --- a/judge/models/__init__.py +++ b/judge/models/__init__.py @@ -15,6 +15,9 @@ from judge.models.contest import ( Rating, ContestProblemClarification, ContestsSummary, + OfficialContestCategory, + OfficialContestLocation, + OfficialContest, ) from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex from judge.models.message import PrivateMessage, PrivateMessageThread diff --git a/judge/models/contest.py b/judge/models/contest.py index a83af5c..c6f4bb8 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -39,6 +39,9 @@ __all__ = [ "Rating", "ContestProblemClarification", "ContestsSummary", + "OfficialContest", + "OfficialContestCategory", + "OfficialContestLocation", ] @@ -974,3 +977,53 @@ class ContestsSummary(models.Model): def get_absolute_url(self): return reverse("contests_summary", args=[self.key]) + + +class OfficialContestCategory(models.Model): + name = models.CharField( + max_length=50, verbose_name=_("official contest category"), unique=True + ) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("official contest category") + verbose_name_plural = _("official contest categories") + + +class OfficialContestLocation(models.Model): + name = models.CharField( + max_length=50, verbose_name=_("official contest location"), unique=True + ) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("official contest location") + verbose_name_plural = _("official contest locations") + + +class OfficialContest(models.Model): + contest = models.OneToOneField( + Contest, + verbose_name=_("contest"), + related_name="official", + on_delete=CASCADE, + ) + category = models.ForeignKey( + OfficialContestCategory, + verbose_name=_("contest category"), + on_delete=CASCADE, + ) + year = models.PositiveIntegerField(verbose_name=_("year")) + location = models.ForeignKey( + OfficialContestLocation, + verbose_name=_("contest location"), + on_delete=CASCADE, + ) + + class Meta: + verbose_name = _("official contest") + verbose_name_plural = _("official contests") diff --git a/judge/views/contests.py b/judge/views/contests.py index 71e98e7..c0e8763 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -69,6 +69,8 @@ from judge.models import ( Submission, ContestProblemClarification, ContestsSummary, + OfficialContestCategory, + OfficialContestLocation, ) from judge.tasks import run_moss from judge.utils.celery import redirect_to_task_status @@ -106,6 +108,7 @@ __all__ = [ "base_contest_ranking_list", "ContestClarificationView", "update_contest_mode", + "OfficialContestList", ] @@ -129,8 +132,15 @@ def _find_contest(request, key): class ContestListMixin(object): + official = False + def get_queryset(self): - return Contest.get_visible_contests(self.request.user) + q = Contest.get_visible_contests(self.request.user) + if self.official: + q = q.filter(official__isnull=False).select_related( + "official", "official__category", "official__location" + ) + return q class ContestList( @@ -158,39 +168,41 @@ class ContestList( return request.session.get(key, False) return request.GET.get(key, None) == "1" + def setup_contest_list(self, request): + self.contest_query = request.GET.get("contest", "") + + self.hide_organization_contests = 0 + if self.GET_with_session(request, "hide_organization_contests"): + self.hide_organization_contests = 1 + + self.org_query = [] + if request.GET.get("orgs") and request.profile: + try: + self.org_query = list(map(int, request.GET.getlist("orgs"))) + if not request.user.is_superuser: + self.org_query = [ + i + for i in self.org_query + if i + in set( + request.profile.organizations.values_list("id", flat=True) + ) + ] + except ValueError: + pass + def get(self, request, *args, **kwargs): default_tab = "active" if not self.request.user.is_authenticated: default_tab = "current" self.current_tab = self.request.GET.get("tab", default_tab) - self.contest_query = None - self.org_query = [] - self.show_orgs = 0 - if self.GET_with_session(request, "show_orgs"): - self.show_orgs = 1 - - if self.request.GET.get("orgs") and self.request.profile: - try: - self.org_query = list(map(int, request.GET.getlist("orgs"))) - if not self.request.user.is_superuser: - self.org_query = [ - i - for i in self.org_query - if i - in set( - self.request.profile.organizations.values_list( - "id", flat=True - ) - ) - ] - except ValueError: - pass + self.setup_contest_list(request) return super(ContestList, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): - to_update = ("show_orgs",) + to_update = ("hide_organization_contests",) for key in to_update: if key in request.GET: val = request.GET.get(key) == "1" @@ -199,6 +211,9 @@ class ContestList( request.session[key] = False return HttpResponseRedirect(request.get_full_path()) + def extra_queryset_filters(self, queryset): + return queryset + def _get_queryset(self): queryset = ( super(ContestList, self) @@ -206,28 +221,25 @@ class ContestList( .prefetch_related("tags", "organizations") ) - if self.request.GET.get("contest"): - self.contest_query = query = " ".join( - self.request.GET.getlist("contest") - ).strip() - if query: - substr_queryset = queryset.filter( - Q(key__icontains=query) | Q(name__icontains=query) + if self.contest_query: + substr_queryset = queryset.filter( + Q(key__icontains=self.contest_query) + | Q(name__icontains=self.contest_query) + ) + if settings.ENABLE_FTS: + queryset = ( + queryset.search(self.contest_query).extra(order_by=["-relevance"]) + | substr_queryset ) - if settings.ENABLE_FTS: - queryset = ( - queryset.search(query).extra(order_by=["-relevance"]) - | substr_queryset - ) - else: - queryset = substr_queryset + else: + queryset = substr_queryset if not self.org_query and self.request.organization: self.org_query = [self.request.organization.id] - if self.show_orgs: + if self.hide_organization_contests: queryset = queryset.filter(organizations=None) if self.org_query: queryset = queryset.filter(organizations__in=self.org_query) - + queryset = self.extra_queryset_filters(queryset) return queryset def _get_past_contests_queryset(self): @@ -295,10 +307,19 @@ class ContestList( context["first_page_href"] = "." context["contest_query"] = self.contest_query context["org_query"] = self.org_query - context["show_orgs"] = int(self.show_orgs) + context["hide_organization_contests"] = int(self.hide_organization_contests) if self.request.profile: context["organizations"] = self.request.profile.organizations.all() context["page_type"] = "list" + context["selected_order"] = self.request.GET.get("order") + context["all_sort_options"] = [ + ("start_time", _("Start time (asc.)")), + ("-start_time", _("Start time (desc.)")), + ("name", _("Name (asc.)")), + ("-name", _("Name (desc.)")), + ("user_count", _("User count (asc.)")), + ("-user_count", _("User count (desc.)")), + ] context.update(self.get_sort_context()) context.update(self.get_sort_paginate_context()) return context @@ -1545,3 +1566,67 @@ def recalculate_contest_summary_result(contest_summary): sorted_total_points.sort(key=lambda x: x.points, reverse=True) total_rank = ranker(sorted_total_points) return [(rank, item._asdict()) for rank, item in total_rank] + + +class OfficialContestList(ContestList): + official = True + template_name = "contest/official_list.html" + + def setup_contest_list(self, request): + self.contest_query = request.GET.get("contest", "") + self.org_query = [] + self.hide_organization_contests = False + + self.selected_categories = [] + self.selected_locations = [] + self.year_from = None + self.year_to = None + + if "category" in request.GET: + try: + self.selected_categories = list( + map(int, request.GET.getlist("category")) + ) + except ValueError: + pass + if "location" in request.GET: + try: + self.selected_locations = list( + map(int, request.GET.getlist("location")) + ) + except ValueError: + pass + if "year_from" in request.GET: + try: + self.year_from = int(request.GET.get("year_from")) + except ValueError: + pass + if "year_to" in request.GET: + try: + self.year_to = int(request.GET.get("year_to")) + except ValueError: + pass + + def extra_queryset_filters(self, queryset): + if self.selected_categories: + queryset = queryset.filter(official__category__in=self.selected_categories) + if self.selected_locations: + queryset = queryset.filter(official__location__in=self.selected_locations) + if self.year_from: + queryset = queryset.filter(official__year__gte=self.year_from) + if self.year_to: + queryset = queryset.filter(official__year__lte=self.year_to) + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["page_type"] = "official" + context["is_official"] = True + context["categories"] = OfficialContestCategory.objects.all() + context["locations"] = OfficialContestLocation.objects.all() + context["selected_categories"] = self.selected_categories + context["selected_locations"] = self.selected_locations + context["year_from"] = self.year_from + context["year_to"] = self.year_to + + return context diff --git a/judge/views/organization.py b/judge/views/organization.py index dcfa906..56e13ee 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -414,7 +414,7 @@ class OrganizationContests( def get_queryset(self): self.org_query = [self.organization_id] - self.show_orgs = False + self.hide_organization_contests = False return super().get_queryset() def set_editable_contest(self, contest): diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index 6eec9bb..f3e764a 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: 2024-05-30 01:54+0700\n" +"POT-Creation-Date: 2024-05-30 14:36+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -23,8 +23,8 @@ msgid "last seen" msgstr "xem lần cuối" #: chat_box/models.py:54 chat_box/models.py:79 chat_box/models.py:95 -#: judge/admin/interface.py:150 judge/models/contest.py:690 -#: judge/models/contest.py:896 judge/models/course.py:129 +#: judge/admin/interface.py:150 judge/models/contest.py:693 +#: judge/models/contest.py:899 judge/models/course.py:129 #: judge/models/profile.py:462 judge/models/profile.py:536 msgid "user" msgstr "người dùng" @@ -48,7 +48,7 @@ msgstr "Gần đây" #: chat_box/views.py:448 templates/base.html:196 #: templates/comments/content-list.html:72 -#: templates/contest/contest-list-tabs.html:5 +#: templates/contest/contest-list-tabs.html:6 #: templates/contest/ranking-table.html:52 templates/course/left_sidebar.html:8 #: templates/internal/problem/problem.html:63 #: templates/organization/org-left-sidebar.html:12 @@ -126,77 +126,78 @@ msgstr "" msgid "Problem" msgstr "Bài tập" -#: judge/admin/contest.py:156 +#: judge/admin/contest.py:171 msgid "Settings" msgstr "Cài đặt" -#: judge/admin/contest.py:171 +#: judge/admin/contest.py:186 msgid "Scheduling" msgstr "" -#: judge/admin/contest.py:175 +#: judge/admin/contest.py:190 msgid "Details" msgstr "Chi tiết" -#: judge/admin/contest.py:187 templates/contest/macros.html:100 +#: judge/admin/contest.py:202 templates/contest/macros.html:83 +#: templates/contest/macros.html:93 msgid "Format" msgstr "Thể thức" -#: judge/admin/contest.py:191 templates/contest/ranking-table.html:5 +#: judge/admin/contest.py:206 templates/contest/ranking-table.html:5 #: templates/profile-table.html:14 templates/profile-table.html:38 #: templates/user/user-about.html:15 templates/user/user-about.html:45 msgid "Rating" msgstr "" -#: judge/admin/contest.py:203 +#: judge/admin/contest.py:218 msgid "Access" msgstr "Truy cập" -#: judge/admin/contest.py:213 judge/admin/problem.py:233 +#: judge/admin/contest.py:228 judge/admin/problem.py:233 msgid "Justice" msgstr "Xử phạt" -#: judge/admin/contest.py:341 +#: judge/admin/contest.py:356 #, 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:348 +#: judge/admin/contest.py:363 msgid "Mark contests as visible" msgstr "Đánh dấu hiển thị các kỳ thi" -#: judge/admin/contest.py:359 +#: judge/admin/contest.py:374 #, 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:366 +#: judge/admin/contest.py:381 msgid "Mark contests as hidden" msgstr "Ẩn các kỳ thi" -#: judge/admin/contest.py:387 judge/admin/submission.py:241 +#: judge/admin/contest.py:402 judge/admin/submission.py:241 #, 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:495 +#: judge/admin/contest.py:510 #, 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:502 +#: judge/admin/contest.py:517 msgid "Recalculate results" msgstr "Tính toán lại kết quả" -#: judge/admin/contest.py:507 judge/admin/organization.py:99 +#: judge/admin/contest.py:522 judge/admin/organization.py:99 msgid "username" msgstr "tên đăng nhập" -#: judge/admin/contest.py:513 templates/base.html:251 +#: judge/admin/contest.py:528 templates/base.html:251 msgid "virtual" msgstr "ảo" @@ -426,9 +427,9 @@ msgstr[0] "%d bài nộp đã được tính điểm lại." msgid "Rescore the selected submissions" msgstr "Tính điểm lại cái bài nộp" -#: judge/admin/submission.py:332 templates/contest/list.html:181 -#: templates/contest/list.html:223 templates/contest/list.html:260 -#: templates/contest/list.html:294 templates/notification/list.html:12 +#: judge/admin/submission.py:332 templates/contest/list.html:215 +#: templates/contest/list.html:257 templates/contest/list.html:294 +#: templates/contest/list.html:328 templates/notification/list.html:12 #: templates/organization/requests/log.html:10 #: templates/organization/requests/pending.html:20 #: templates/problem/list.html:154 @@ -582,7 +583,7 @@ msgstr "Mã bài phải có dạng ^[a-z0-9]+$" msgid "Problem with code already exists." msgstr "Mã bài đã tồn tại." -#: judge/forms.py:491 judge/models/contest.py:99 +#: judge/forms.py:491 judge/models/contest.py:102 msgid "Contest id must be ^[a-z0-9]+$" msgstr "Mã kỳ thi phải có dạng ^[a-z0-9]+$" @@ -702,116 +703,116 @@ msgstr "" msgid "Override comment lock" msgstr "" -#: judge/models/contest.py:46 +#: judge/models/contest.py:49 msgid "Invalid colour." msgstr "" -#: judge/models/contest.py:50 +#: judge/models/contest.py:53 msgid "tag name" msgstr "" -#: judge/models/contest.py:54 +#: judge/models/contest.py:57 msgid "Lowercase letters and hyphens only." msgstr "" -#: judge/models/contest.py:59 +#: judge/models/contest.py:62 msgid "tag colour" msgstr "" -#: judge/models/contest.py:61 +#: judge/models/contest.py:64 msgid "tag description" msgstr "" -#: judge/models/contest.py:82 +#: judge/models/contest.py:85 msgid "contest tag" msgstr "" -#: judge/models/contest.py:83 judge/models/contest.py:259 +#: judge/models/contest.py:86 judge/models/contest.py:262 msgid "contest tags" msgstr "nhãn kỳ thi" -#: judge/models/contest.py:91 +#: judge/models/contest.py:94 msgid "Visible" msgstr "Hiển thị" -#: judge/models/contest.py:92 +#: judge/models/contest.py:95 msgid "Hidden for duration of contest" msgstr "Ẩn trong thời gian kỳ thi" -#: judge/models/contest.py:93 +#: judge/models/contest.py:96 msgid "Hidden for duration of participation" msgstr "Ẩn trong thời gian tham gia" -#: judge/models/contest.py:97 +#: judge/models/contest.py:100 msgid "contest id" msgstr "ID kỳ thi" -#: judge/models/contest.py:102 +#: judge/models/contest.py:105 msgid "contest name" msgstr "tên kỳ thi" -#: judge/models/contest.py:106 judge/models/interface.py:80 +#: judge/models/contest.py:109 judge/models/interface.py:80 #: judge/models/problem.py:694 msgid "authors" msgstr "tác giả" -#: judge/models/contest.py:107 +#: judge/models/contest.py:110 msgid "These users will be able to edit the contest." msgstr "Những người dùng này có quyền chỉnh sửa kỳ thi." -#: judge/models/contest.py:112 judge/models/problem.py:157 +#: judge/models/contest.py:115 judge/models/problem.py:157 msgid "curators" msgstr "quản lý" -#: judge/models/contest.py:114 +#: judge/models/contest.py:117 msgid "" "These users will be able to edit the contest, but will not be listed as " "authors." msgstr "Những người dùng này là tác giả và có quyền chỉnh sửa kỳ thi." -#: judge/models/contest.py:122 judge/models/problem.py:167 +#: judge/models/contest.py:125 judge/models/problem.py:167 msgid "testers" msgstr "" -#: judge/models/contest.py:124 +#: judge/models/contest.py:127 msgid "These users will be able to view the contest, but not edit it." msgstr "" "Những người dùng này có thể thấy kỳ thi nhưng không có quyền chỉnh sửa." -#: judge/models/contest.py:129 judge/models/runtime.py:217 +#: judge/models/contest.py:132 judge/models/runtime.py:217 msgid "description" msgstr "mô tả" -#: judge/models/contest.py:131 judge/models/problem.py:610 +#: judge/models/contest.py:134 judge/models/problem.py:610 #: judge/models/runtime.py:222 msgid "problems" msgstr "bài tập" -#: judge/models/contest.py:133 judge/models/contest.py:695 +#: judge/models/contest.py:136 judge/models/contest.py:698 msgid "start time" msgstr "thời gian bắt đầu" -#: judge/models/contest.py:134 +#: judge/models/contest.py:137 msgid "end time" msgstr "thời gian kết thúc" -#: judge/models/contest.py:136 judge/models/problem.py:186 +#: judge/models/contest.py:139 judge/models/problem.py:186 #: judge/models/problem.py:645 msgid "time limit" msgstr "giới hạn thời gian" -#: judge/models/contest.py:140 +#: judge/models/contest.py:143 msgid "" "Format hh:mm:ss. For example, if you want a 2-hour contest, enter 02:00:00" 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:144 +#: judge/models/contest.py:147 msgid "freeze after" msgstr "đóng băng sau" -#: judge/models/contest.py:148 +#: judge/models/contest.py:151 msgid "" "Format hh:mm:ss. For example, if you want to freeze contest after 2 hours, " "enter 02:00:00" @@ -819,12 +820,12 @@ 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:152 judge/models/course.py:27 +#: judge/models/contest.py:155 judge/models/course.py:27 #: judge/models/problem.py:225 msgid "publicly visible" msgstr "công khai" -#: judge/models/contest.py:155 +#: judge/models/contest.py:158 msgid "" "Should be set even for organization-private contests, where it determines " "whether the contest is visible to members of the specified organizations." @@ -832,92 +833,92 @@ 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:161 +#: judge/models/contest.py:164 msgid "contest rated" msgstr "kỳ thi được xếp hạng" -#: judge/models/contest.py:162 +#: judge/models/contest.py:165 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:166 +#: judge/models/contest.py:169 msgid "scoreboard visibility" msgstr "khả năng hiển thị của bảng điểm" -#: judge/models/contest.py:169 +#: judge/models/contest.py:172 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:174 +#: judge/models/contest.py:177 msgid "view contest scoreboard" msgstr "xem bảng điểm kỳ thi" -#: judge/models/contest.py:177 +#: judge/models/contest.py:180 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:180 +#: judge/models/contest.py:183 msgid "public scoreboard" msgstr "công khai bảng điểm" -#: judge/models/contest.py:181 +#: judge/models/contest.py:184 msgid "Ranking page is public even for private contests." msgstr "Trang xếp hạng được công khai, kể cả cho kỳ thi riêng tư." -#: judge/models/contest.py:185 +#: judge/models/contest.py:188 msgid "no comments" msgstr "không bình luận" -#: judge/models/contest.py:186 +#: judge/models/contest.py:189 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:191 +#: judge/models/contest.py:194 msgid "Rating floor for contest" msgstr "Cận dưới rating được xếp hạng trong kỳ thi" -#: judge/models/contest.py:197 +#: judge/models/contest.py:200 msgid "Rating ceiling for contest" msgstr "Cận trên rating được xếp hạng trong kỳ thi" -#: judge/models/contest.py:202 +#: judge/models/contest.py:205 msgid "rate all" msgstr "xếp hạng tất cả" -#: judge/models/contest.py:203 +#: judge/models/contest.py:206 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:208 +#: judge/models/contest.py:211 msgid "exclude from ratings" msgstr "không xếp hạng" -#: judge/models/contest.py:213 +#: judge/models/contest.py:216 msgid "private to specific users" msgstr "riêng tư với các người dùng này" -#: judge/models/contest.py:218 +#: judge/models/contest.py:221 msgid "private contestants" msgstr "thí sinh riêng tư" -#: judge/models/contest.py:219 +#: judge/models/contest.py:222 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:223 +#: judge/models/contest.py:226 msgid "hide problem tags" msgstr "ẩn nhãn kỳ thi" -#: judge/models/contest.py:224 +#: judge/models/contest.py:227 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:228 +#: judge/models/contest.py:231 msgid "run pretests only" msgstr "chỉ chạy pretests" -#: judge/models/contest.py:230 +#: judge/models/contest.py:233 msgid "" "Whether judges should grade pretests only, versus all testcases. Commonly " "set during a contest, then unset prior to rejudging user submissions when " @@ -926,51 +927,51 @@ 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:237 judge/models/interface.py:97 +#: judge/models/contest.py:240 judge/models/interface.py:97 #: judge/models/problem.py:285 msgid "private to organizations" msgstr "riêng tư với các tổ chức" -#: judge/models/contest.py:242 judge/models/course.py:33 +#: judge/models/contest.py:245 judge/models/course.py:33 #: judge/models/interface.py:93 judge/models/problem.py:281 #: judge/models/profile.py:168 msgid "organizations" msgstr "tổ chức" -#: judge/models/contest.py:243 +#: judge/models/contest.py:246 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:246 judge/models/problem.py:256 +#: judge/models/contest.py:249 judge/models/problem.py:256 msgid "OpenGraph image" msgstr "Hình ảnh OpenGraph" -#: judge/models/contest.py:249 judge/models/profile.py:108 +#: judge/models/contest.py:252 judge/models/profile.py:108 msgid "Logo override image" msgstr "Hình ảnh ghi đè logo" -#: judge/models/contest.py:254 +#: judge/models/contest.py:257 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:262 +#: judge/models/contest.py:265 msgid "the amount of live participants" msgstr "số lượng thí sinh thi trực tiếp" -#: judge/models/contest.py:266 +#: judge/models/contest.py:269 msgid "contest summary" msgstr "tổng kết kỳ thi" -#: judge/models/contest.py:268 judge/models/problem.py:262 +#: judge/models/contest.py:271 judge/models/problem.py:262 msgid "Plain-text, shown in meta description tag, e.g. for social media." msgstr "" -#: judge/models/contest.py:272 judge/models/profile.py:103 +#: judge/models/contest.py:275 judge/models/profile.py:103 msgid "access code" msgstr "mật khẩu truy cập" -#: judge/models/contest.py:277 +#: judge/models/contest.py:280 msgid "" "An optional code to prompt contestants before they are allowed to join the " "contest. Leave it blank to disable." @@ -978,315 +979,351 @@ 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:283 judge/models/problem.py:244 +#: judge/models/contest.py:286 judge/models/problem.py:244 msgid "personae non gratae" msgstr "Chặn tham gia" -#: judge/models/contest.py:285 +#: judge/models/contest.py:288 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:288 +#: judge/models/contest.py:291 msgid "contest format" msgstr "format kỳ thi" -#: judge/models/contest.py:292 +#: judge/models/contest.py:295 msgid "The contest format module to use." msgstr "Format kỳ thi sử dụng." -#: judge/models/contest.py:295 +#: judge/models/contest.py:298 msgid "contest format configuration" msgstr "Tùy chỉnh format kỳ thi" -#: judge/models/contest.py:299 +#: judge/models/contest.py:302 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:312 +#: judge/models/contest.py:315 msgid "precision points" msgstr "Hiển thị điểm" -#: judge/models/contest.py:315 +#: judge/models/contest.py:318 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:318 +#: judge/models/contest.py:321 msgid "rate limit" msgstr "giới hạn bài nộp" -#: judge/models/contest.py:323 +#: judge/models/contest.py:326 msgid "" "Maximum number of submissions per minute. Leave empty if you don't want rate " "limit." msgstr "Số bài nộp tối đa mỗi phút. Để trống nếu không muốn giới hạn." -#: judge/models/contest.py:649 +#: judge/models/contest.py:652 msgid "See private contests" msgstr "" -#: judge/models/contest.py:650 +#: judge/models/contest.py:653 msgid "Edit own contests" msgstr "" -#: judge/models/contest.py:651 +#: judge/models/contest.py:654 msgid "Edit all contests" msgstr "" -#: judge/models/contest.py:652 +#: judge/models/contest.py:655 msgid "Clone contest" msgstr "" -#: judge/models/contest.py:653 templates/contest/moss.html:72 +#: judge/models/contest.py:656 templates/contest/moss.html:72 msgid "MOSS contest" msgstr "" -#: judge/models/contest.py:654 +#: judge/models/contest.py:657 msgid "Rate contests" msgstr "" -#: judge/models/contest.py:655 +#: judge/models/contest.py:658 msgid "Contest access codes" msgstr "" -#: judge/models/contest.py:656 +#: judge/models/contest.py:659 msgid "Create private contests" msgstr "" -#: judge/models/contest.py:657 +#: judge/models/contest.py:660 msgid "Change contest visibility" msgstr "" -#: judge/models/contest.py:658 +#: judge/models/contest.py:661 msgid "Edit contest problem label script" msgstr "Cách hiển thị thứ tự bài tập" -#: judge/models/contest.py:660 judge/models/contest.py:821 -#: judge/models/contest.py:899 judge/models/contest.py:929 -#: judge/models/submission.py:116 +#: judge/models/contest.py:663 judge/models/contest.py:824 +#: judge/models/contest.py:902 judge/models/contest.py:932 +#: judge/models/contest.py:1011 judge/models/submission.py:116 msgid "contest" msgstr "kỳ thi" -#: judge/models/contest.py:661 +#: judge/models/contest.py:664 msgid "contests" msgstr "kỳ thi" -#: judge/models/contest.py:684 +#: judge/models/contest.py:687 msgid "associated contest" msgstr "" -#: judge/models/contest.py:697 +#: judge/models/contest.py:700 msgid "score" msgstr "điểm" -#: judge/models/contest.py:698 +#: judge/models/contest.py:701 msgid "cumulative time" msgstr "tổng thời gian" -#: judge/models/contest.py:700 +#: judge/models/contest.py:703 msgid "is disqualified" msgstr "đã bị loại" -#: judge/models/contest.py:702 +#: judge/models/contest.py:705 msgid "Whether this participation is disqualified." msgstr "Quyết định thí sinh có bị loại không." -#: judge/models/contest.py:704 +#: judge/models/contest.py:707 msgid "tie-breaking field" msgstr "" -#: judge/models/contest.py:706 +#: judge/models/contest.py:709 msgid "virtual participation id" msgstr "id lần tham gia ảo" -#: judge/models/contest.py:708 +#: judge/models/contest.py:711 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:711 +#: judge/models/contest.py:714 msgid "contest format specific data" msgstr "" -#: judge/models/contest.py:714 +#: judge/models/contest.py:717 msgid "same as format_data, but including frozen results" msgstr "" -#: judge/models/contest.py:718 +#: judge/models/contest.py:721 #, fuzzy #| msgid "score" msgid "final score" msgstr "điểm" -#: judge/models/contest.py:720 +#: judge/models/contest.py:723 #, fuzzy #| msgid "cumulative time" msgid "final cumulative time" msgstr "tổng thời gian" -#: judge/models/contest.py:796 +#: judge/models/contest.py:799 #, python-format msgid "%s spectating in %s" msgstr "%s đang theo dõi trong %s" -#: judge/models/contest.py:801 +#: judge/models/contest.py:804 #, python-format msgid "%s in %s, v%d" msgstr "%s trong %s, v%d" -#: judge/models/contest.py:806 +#: judge/models/contest.py:809 #, python-format msgid "%s in %s" msgstr "%s trong %s" -#: judge/models/contest.py:809 +#: judge/models/contest.py:812 msgid "contest participation" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:810 +#: judge/models/contest.py:813 msgid "contest participations" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:817 judge/models/contest.py:870 -#: judge/models/contest.py:932 judge/models/course.py:165 +#: judge/models/contest.py:820 judge/models/contest.py:873 +#: judge/models/contest.py:935 judge/models/course.py:165 #: judge/models/problem.py:609 judge/models/problem.py:616 #: judge/models/problem.py:637 judge/models/problem.py:668 #: judge/models/problem_data.py:50 msgid "problem" msgstr "bài tập" -#: judge/models/contest.py:825 judge/models/contest.py:882 +#: judge/models/contest.py:828 judge/models/contest.py:885 #: judge/models/course.py:167 judge/models/problem.py:209 msgid "points" msgstr "điểm" -#: judge/models/contest.py:826 +#: judge/models/contest.py:829 msgid "partial" msgstr "thành phần" -#: judge/models/contest.py:827 judge/models/contest.py:884 +#: judge/models/contest.py:830 judge/models/contest.py:887 msgid "is pretested" msgstr "dùng pretest" -#: judge/models/contest.py:828 judge/models/course.py:166 +#: judge/models/contest.py:831 judge/models/course.py:166 #: judge/models/interface.py:48 msgid "order" msgstr "thứ tự" -#: judge/models/contest.py:830 +#: judge/models/contest.py:833 msgid "visible testcases" msgstr "hiển thị test" -#: judge/models/contest.py:835 +#: judge/models/contest.py:838 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:837 +#: judge/models/contest.py:840 msgid "max submissions" msgstr "số lần nộp tối đa" -#: judge/models/contest.py:840 +#: judge/models/contest.py:843 msgid "Why include a problem you can't submit to?" msgstr "" -#: judge/models/contest.py:844 +#: judge/models/contest.py:847 #, fuzzy #| msgid "Only for format new IOI. Separated by commas, e.g: 2, 3" msgid "Separated by commas, e.g: 2, 3" msgstr "" "Chỉ dùng với format IOI mới. Các sub cách nhau bởi dấu phẩy. Ví dụ: 2, 3" -#: judge/models/contest.py:845 +#: judge/models/contest.py:848 #, fuzzy #| msgid "frozen subtasks" msgid "hidden subtasks" msgstr "Đóng băng subtasks" -#: judge/models/contest.py:857 +#: judge/models/contest.py:860 msgid "contest problem" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:858 +#: judge/models/contest.py:861 msgid "contest problems" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:864 judge/models/submission.py:274 +#: judge/models/contest.py:867 judge/models/submission.py:274 msgid "submission" msgstr "bài nộp" -#: judge/models/contest.py:877 judge/models/contest.py:903 +#: judge/models/contest.py:880 judge/models/contest.py:906 msgid "participation" msgstr "lần tham gia" -#: judge/models/contest.py:885 +#: judge/models/contest.py:888 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:890 +#: judge/models/contest.py:893 msgid "contest submission" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:891 +#: judge/models/contest.py:894 msgid "contest submissions" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:907 +#: judge/models/contest.py:910 msgid "rank" msgstr "rank" -#: judge/models/contest.py:908 +#: judge/models/contest.py:911 msgid "rating" msgstr "rating" -#: judge/models/contest.py:909 +#: judge/models/contest.py:912 msgid "raw rating" msgstr "rating thật" -#: judge/models/contest.py:910 +#: judge/models/contest.py:913 msgid "contest performance" msgstr "" -#: judge/models/contest.py:911 +#: judge/models/contest.py:914 msgid "last rated" msgstr "lần cuối được xếp hạng" -#: judge/models/contest.py:915 +#: judge/models/contest.py:918 msgid "contest rating" msgstr "rating kỳ thi" -#: judge/models/contest.py:916 +#: judge/models/contest.py:919 msgid "contest ratings" msgstr "rating kỳ thi" -#: judge/models/contest.py:940 +#: judge/models/contest.py:943 msgid "contest moss result" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:941 +#: judge/models/contest.py:944 msgid "contest moss results" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:946 +#: judge/models/contest.py:949 msgid "clarified problem" msgstr "" -#: judge/models/contest.py:948 +#: judge/models/contest.py:951 msgid "clarification body" msgstr "" -#: judge/models/contest.py:950 +#: judge/models/contest.py:953 msgid "clarification timestamp" msgstr "" -#: judge/models/contest.py:969 +#: judge/models/contest.py:972 msgid "contests summary" msgstr "tổng kết kỳ thi" -#: judge/models/contest.py:970 +#: judge/models/contest.py:973 msgid "contests summaries" msgstr "tổng kết kỳ thi" +#: judge/models/contest.py:984 judge/models/contest.py:991 +msgid "official contest category" +msgstr "loại kỳ thi chính thức" + +#: judge/models/contest.py:992 +msgid "official contest categories" +msgstr "các loại kỳ thi chính thức" + +#: judge/models/contest.py:997 judge/models/contest.py:1004 +msgid "official contest location" +msgstr "địa điểm kỳ thi chính thức" + +#: judge/models/contest.py:1005 +msgid "official contest locations" +msgstr "các địa điểm kỳ thi chính thức" + +#: judge/models/contest.py:1017 +msgid "contest category" +msgstr "loại kỳ thi" + +#: judge/models/contest.py:1020 +msgid "year" +msgstr "năm" + +#: judge/models/contest.py:1023 +msgid "contest location" +msgstr "địa điểm kỳ thi" + +#: judge/models/contest.py:1028 +msgid "official contest" +msgstr "kỳ thi chính thức" + +#: judge/models/contest.py:1029 +msgid "official contests" +msgstr "các kỳ thi chính thức" + #: judge/models/course.py:12 templates/course/grades.html:88 msgid "Student" msgstr "Học sinh" @@ -2797,132 +2834,156 @@ msgstr "Bạn phải giải ít nhất một bài trước khi được phép b msgid "Posted comment" msgstr "Bình luận đã đăng" -#: judge/views/contests.py:122 judge/views/contests.py:434 -#: judge/views/contests.py:439 judge/views/contests.py:739 +#: judge/views/contests.py:125 judge/views/contests.py:453 +#: judge/views/contests.py:458 judge/views/contests.py:758 msgid "No such contest" msgstr "Không có contest nào như vậy" -#: judge/views/contests.py:123 judge/views/contests.py:435 +#: judge/views/contests.py:126 judge/views/contests.py:454 #, python-format msgid "Could not find a contest with the key \"%s\"." msgstr "Không tìm thấy kỳ thi với mã \"%s\"." -#: judge/views/contests.py:142 judge/views/contests.py:1510 -#: judge/views/stats.py:178 templates/contest/list.html:177 -#: templates/contest/list.html:219 templates/contest/list.html:256 -#: templates/contest/list.html:290 +#: judge/views/contests.py:149 judge/views/contests.py:1529 +#: judge/views/stats.py:178 templates/contest/list.html:211 +#: templates/contest/list.html:253 templates/contest/list.html:290 +#: templates/contest/list.html:324 #: templates/organization/org-left-sidebar.html:5 templates/stats/site.html:21 #: templates/user/user-bookmarks.html:19 templates/user/user-bookmarks.html:80 msgid "Contests" msgstr "Kỳ thi" -#: judge/views/contests.py:439 +#: judge/views/contests.py:314 +msgid "Start time (asc.)" +msgstr "Thời gian bắt đầu (tăng)" + +#: judge/views/contests.py:315 +msgid "Start time (desc.)" +msgstr "Thời gian bắt đầu (giảm)" + +#: judge/views/contests.py:316 +msgid "Name (asc.)" +msgstr "Tên (tăng)" + +#: judge/views/contests.py:317 +msgid "Name (desc.)" +msgstr "Tên (giảm)" + +#: judge/views/contests.py:318 +msgid "User count (asc.)" +msgstr "Số lượng tham gia (tăng)" + +#: judge/views/contests.py:319 +msgid "User count (desc.)" +msgstr "Số lượng tham gia (giảm)" + +#: judge/views/contests.py:458 msgid "Could not find such contest." msgstr "Không tìm thấy kỳ thi nào như vậy." -#: judge/views/contests.py:447 +#: judge/views/contests.py:466 #, python-format msgid "Access to contest \"%s\" denied" msgstr "Truy cập tới kỳ thi \"%s\" bị từ chối" -#: judge/views/contests.py:525 +#: judge/views/contests.py:544 msgid "Clone Contest" msgstr "Nhân bản kỳ thi" -#: judge/views/contests.py:617 +#: judge/views/contests.py:636 msgid "Contest not ongoing" msgstr "Kỳ thi đang không diễn ra" -#: judge/views/contests.py:618 +#: judge/views/contests.py:637 #, python-format msgid "\"%s\" is not currently ongoing." msgstr "\"%s\" kỳ thi đang không diễn ra." -#: judge/views/contests.py:631 +#: judge/views/contests.py:650 msgid "Banned from joining" msgstr "Bị cấm tham gia" -#: judge/views/contests.py:633 +#: judge/views/contests.py:652 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:723 +#: judge/views/contests.py:742 #, python-format msgid "Enter access code for \"%s\"" msgstr "Nhập mật khẩu truy cập cho \"%s\"" -#: judge/views/contests.py:740 +#: judge/views/contests.py:759 #, python-format msgid "You are not in contest \"%s\"." msgstr "Bạn không ở trong kỳ thi \"%s\"." -#: judge/views/contests.py:763 +#: judge/views/contests.py:782 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:821 +#: judge/views/contests.py:840 #, python-format msgid "Contests in %(month)s" msgstr "Các kỳ thi trong %(month)s" -#: judge/views/contests.py:822 +#: judge/views/contests.py:841 msgid "F Y" msgstr "F Y" -#: judge/views/contests.py:882 +#: judge/views/contests.py:901 #, python-format msgid "%s Statistics" msgstr "%s Thống kê" -#: judge/views/contests.py:1191 +#: judge/views/contests.py:1210 #, python-format msgid "%s Rankings" msgstr "%s Bảng điểm" -#: judge/views/contests.py:1202 +#: judge/views/contests.py:1221 msgid "???" msgstr "???" -#: judge/views/contests.py:1229 +#: judge/views/contests.py:1248 #, python-format msgid "Your participation in %s" msgstr "Lần tham gia trong %s" -#: judge/views/contests.py:1230 +#: judge/views/contests.py:1249 #, python-format msgid "%s's participation in %s" msgstr "Lần tham gia của %s trong %s" -#: judge/views/contests.py:1244 +#: judge/views/contests.py:1263 msgid "Live" msgstr "Trực tiếp" -#: judge/views/contests.py:1263 templates/contest/contest-tabs.html:21 +#: judge/views/contests.py:1282 templates/contest/contest-tabs.html:21 msgid "Participation" msgstr "Lần tham gia" -#: judge/views/contests.py:1312 +#: judge/views/contests.py:1331 #, python-format msgid "%s MOSS Results" msgstr "%s Kết quả MOSS" -#: judge/views/contests.py:1348 +#: judge/views/contests.py:1367 #, python-format msgid "Running MOSS for %s..." msgstr "Đang chạy MOSS cho %s..." -#: judge/views/contests.py:1371 +#: judge/views/contests.py:1390 #, python-format msgid "Contest tag: %s" msgstr "Nhãn kỳ thi: %s" -#: judge/views/contests.py:1386 judge/views/ticket.py:67 +#: judge/views/contests.py:1405 judge/views/ticket.py:67 msgid "Issue description" msgstr "Mô tả vấn đề" -#: judge/views/contests.py:1429 +#: judge/views/contests.py:1448 #, python-format msgid "New clarification for %s" msgstr "Thông báo mới cho %s" @@ -3065,7 +3126,7 @@ msgid "You are not allowed to access this organization." msgstr "Bạn không được phép chỉnh sửa tổ chức này." #: judge/views/organization.py:245 judge/views/stats.py:184 -#: templates/contest/list.html:111 templates/problem/list-base.html:90 +#: templates/contest/list.html:118 templates/problem/list-base.html:90 #: templates/stats/site.html:33 templates/user/user-left-sidebar.html:4 #: templates/user/user-list-tabs.html:6 msgid "Groups" @@ -3511,8 +3572,8 @@ msgstr "Bài nộp trong {1}" #: judge/views/submission.py:899 #, python-brace-format msgid "" -"{0}'s submissions for {2} in {4}" +"{0}'s submissions for {2} in {4}" msgstr "" "Các bài nộp của {0} cho {2} trong {4}" @@ -3758,8 +3819,7 @@ msgstr "Đăng ký" msgid "spectating" msgstr "đang theo dõi" -#: templates/base.html:257 templates/contest/list.html:304 -#: templates/contest/macros.html:82 +#: templates/base.html:257 templates/contest/list.html:151 msgid "In contest" msgstr "Trong kỳ thi" @@ -4033,7 +4093,7 @@ msgid "Saturday" msgstr "Thứ bảy" #: templates/contest/clarification.html:52 -#: templates/contest/search-form.html:24 templates/organization/new.html:10 +#: templates/contest/search-form.html:35 templates/organization/new.html:10 #: templates/ticket/new.html:38 msgid "Create" msgstr "Tạo mới" @@ -4092,8 +4152,8 @@ msgstr "G:i T, j F, Y" #: templates/contest/contest-datetime.html:32 #, python-format msgid "" -"%(time_limit)s window between %(start_time)s and " -"%(end_time)s" +"%(time_limit)s window between %(start_time)s and " +"%(end_time)s" msgstr "" "Dài %(time_limit)s từ %(start_time)s đến %(end_time)s" @@ -4117,6 +4177,10 @@ msgid "List" msgstr "Danh sách" #: templates/contest/contest-list-tabs.html:3 +msgid "Official" +msgstr "Chính thức" + +#: templates/contest/contest-list-tabs.html:4 msgid "Calendar" msgstr "Lịch" @@ -4144,7 +4208,7 @@ msgstr "MOSS" msgid "Leave contest" msgstr "Rời kỳ thi" -#: templates/contest/contest.html:43 templates/contest/list.html:309 +#: templates/contest/contest.html:43 templates/contest/list.html:156 msgid "Virtual join" msgstr "Tham gia ảo" @@ -4191,11 +4255,11 @@ msgstr "Rank" msgid "Name" msgstr "Tên" -#: templates/contest/list.html:91 templates/contest/media-js.html:152 +#: templates/contest/list.html:98 templates/contest/media-js.html:152 msgid "Are you sure you want to join?" msgstr "Bạn có chắc tham gia?" -#: templates/contest/list.html:92 +#: templates/contest/list.html:99 msgid "" "Joining a contest for the first time starts your timer, after which it " "becomes unstoppable." @@ -4203,49 +4267,57 @@ msgstr "" "Tham gia kỳ thi lần đầu sẽ kích hoạt thời gian đếm ngược, không thể dừng lại " "sau đó." -#: templates/contest/list.html:94 templates/contest/media-js.html:155 +#: templates/contest/list.html:101 templates/contest/media-js.html:155 msgid "By joining in this contest, you will automatically leave contest" msgstr "Khi tham gia kỳ thi này, bạn sẽ tự động rời khỏi kỳ thi" -#: templates/contest/list.html:144 +#: templates/contest/list.html:162 +msgid "Spectate" +msgstr "Theo dõi" + +#: templates/contest/list.html:168 templates/organization/home.html:23 +msgid "Join" +msgstr "Tham gia" + +#: templates/contest/list.html:178 msgid "Active" msgstr "Đang tham gia" -#: templates/contest/list.html:152 +#: templates/contest/list.html:186 msgid "Ongoing" msgstr "Đang diễn ra" -#: templates/contest/list.html:159 +#: templates/contest/list.html:193 msgid "Upcoming" msgstr "Sắp diễn ra" -#: templates/contest/list.html:166 +#: templates/contest/list.html:200 msgid "Past" msgstr "Đã diễn ra" -#: templates/contest/list.html:187 +#: templates/contest/list.html:221 #, python-format msgid "Window ends in %(countdown)s" msgstr "Cửa số thi còn %(countdown)s" -#: templates/contest/list.html:190 templates/contest/list.html:228 +#: templates/contest/list.html:224 templates/contest/list.html:262 #, python-format msgid "Ends in %(countdown)s" msgstr "Kết thúc trong %(countdown)s" -#: templates/contest/list.html:210 +#: templates/contest/list.html:244 msgid "There is no active contest at this time." msgstr "Không có kỳ thi nào đang tham gia." -#: templates/contest/list.html:247 +#: templates/contest/list.html:281 msgid "There is no ongoing contest at this time." msgstr "Không có kỳ thi nào đang diễn ra hiện tại." -#: templates/contest/list.html:281 +#: templates/contest/list.html:315 msgid "There is no scheduled contest at this time." msgstr "Không có kỳ thi nào được lên lịch hiện tại." -#: templates/contest/list.html:321 +#: templates/contest/list.html:347 msgid "There is no past contest." msgstr "Không có kỳ thi nào trong quá khứ." @@ -4283,13 +4355,22 @@ msgstr "%(time_limit)s" msgid "%(duration)s" msgstr "%(duration)s" -#: templates/contest/macros.html:87 -msgid "Spectate" -msgstr "Theo dõi" +#: templates/contest/macros.html:84 +#: templates/contest/official-search-form.html:19 +#: templates/problem/list.html:40 templates/problem/search-form.html:72 +#: templates/user/user-problems.html:57 +msgid "Category" +msgstr "Loại" -#: templates/contest/macros.html:93 templates/organization/home.html:23 -msgid "Join" -msgstr "Tham gia" +#: templates/contest/macros.html:85 +#: templates/contest/official-search-form.html:30 +msgid "Location" +msgstr "Địa điểm" + +#: templates/contest/macros.html:86 +#: templates/contest/official-search-form.html:9 +msgid "Year" +msgstr "Năm" #: templates/contest/media-js.html:147 msgid "Are you sure you want to leave?" @@ -4328,6 +4409,35 @@ msgstr "MOSS lại kỳ thi" msgid "Delete MOSS results" msgstr "Xóa kết quả MOSS" +#: templates/contest/official-search-form.html:2 +#: templates/contest/search-form.html:2 +msgid "Contest search" +msgstr "Tìm kiếm kỳ thi" + +#: templates/contest/official-search-form.html:6 +#: templates/contest/search-form.html:6 +msgid "Search contests..." +msgstr "Tìm kiếm kỳ thi..." + +#: templates/contest/official-search-form.html:12 +msgid "From" +msgstr "Từ" + +#: templates/contest/official-search-form.html:14 +msgid "To" +msgstr "Đến" + +#: templates/contest/official-search-form.html:41 +#: templates/contest/search-form.html:22 +msgid "Order by" +msgstr "Sắp xếp theo" + +#: templates/contest/official-search-form.html:53 +#: templates/contest/search-form.html:33 templates/problem/search-form.html:95 +#: templates/submission/list.html:355 templates/ticket/list.html:250 +msgid "Go" +msgstr "Lọc" + #: templates/contest/private.html:5 msgid "This contest is private to specific users." msgstr "Kỳ thi riêng tư với các thành viên này." @@ -4393,23 +4503,10 @@ msgstr "Hiển thị tham gia ảo" msgid "Download as CSV" msgstr "Tải file CSV" -#: templates/contest/search-form.html:2 -msgid "Contest search" -msgstr "Tìm kiếm kỳ thi" - -#: templates/contest/search-form.html:6 -msgid "Search contests..." -msgstr "Tìm kiếm kỳ thi..." - #: templates/contest/search-form.html:10 msgid "Hide organization contests" msgstr "Ẩn các kỳ thi riêng tư của nhóm" -#: templates/contest/search-form.html:22 templates/problem/search-form.html:95 -#: templates/submission/list.html:355 templates/ticket/list.html:250 -msgid "Go" -msgstr "Lọc" - #: templates/contest/stats.html:48 msgid "Problem Status Distribution" msgstr "Phân bố theo kết quả" @@ -4926,11 +5023,6 @@ msgstr "Hệ thống lỗi!" msgid "Successful vote! Thank you!" msgstr "Đã gửi thành công! Cảm ơn bạn!" -#: templates/problem/list.html:40 templates/problem/search-form.html:72 -#: templates/user/user-problems.html:57 -msgid "Category" -msgstr "Nhóm bài" - #: templates/problem/list.html:51 #, python-format msgid "AC %%" @@ -6265,9 +6357,6 @@ msgstr "Chọn tất cả" #~ msgid "Change your avatar" #~ msgstr "Đổi ảnh đại diện" -#~ msgid "From" -#~ msgstr "Đến từ" - #, fuzzy #~| msgid "How did you corrupt the custom checker path?" #~ msgid "How did you corrupt the interactor path?" @@ -6280,8 +6369,8 @@ msgstr "Chọn tất cả" #~ msgstr "bình luận nữa" #~ msgid "" -#~ "This comment is hidden due to too much negative feedback. Click here to view it." +#~ "This comment is hidden due to too much negative feedback. Click here to view it." #~ msgstr "" #~ "Bình luận bị ẩn vì nhiều phản hồi tiêu cực. Nhấp vào đây để mở." diff --git a/locale/vi/LC_MESSAGES/dmoj-user.po b/locale/vi/LC_MESSAGES/dmoj-user.po index 683b675..94d5264 100644 --- a/locale/vi/LC_MESSAGES/dmoj-user.po +++ b/locale/vi/LC_MESSAGES/dmoj-user.po @@ -24,6 +24,9 @@ msgstr "Giới thiệu" msgid "Status" msgstr "Máy chấm" +msgid "Courses" +msgstr "Khóa học" + msgid "Suggestions" msgstr "Đề xuất ý tưởng" @@ -39,6 +42,9 @@ msgstr "Đăng ký tên" msgid "Report" msgstr "Báo cáo tiêu cực" +msgid "Bug Report" +msgstr "Báo cáo lỗi" + msgid "2sat" msgstr "" @@ -594,12 +600,6 @@ msgstr "" msgid "z-function" msgstr "" -#~ msgid "Courses" -#~ msgstr "Khóa học" - -#~ msgid "Bug Report" -#~ msgstr "Báo cáo lỗi" - #~ msgid "Insert Image" #~ msgstr "Chèn hình ảnh" diff --git a/resources/contest.scss b/resources/contest.scss index 8c429fc..07983bb 100644 --- a/resources/contest.scss +++ b/resources/contest.scss @@ -18,12 +18,6 @@ flex: 1; } - .participate-button { - display: flex; - justify-content: center; - align-items: center; - } - .contest-title { font-size: 1.1em; font-weight: 600; diff --git a/templates/contest/contest-list-tabs.html b/templates/contest/contest-list-tabs.html index 8efaece..063a365 100644 --- a/templates/contest/contest-list-tabs.html +++ b/templates/contest/contest-list-tabs.html @@ -1,7 +1,8 @@ \ No newline at end of file diff --git a/templates/contest/contest-tabs.html b/templates/contest/contest-tabs.html index 3d02e13..b63adae 100644 --- a/templates/contest/contest-tabs.html +++ b/templates/contest/contest-tabs.html @@ -24,7 +24,7 @@ {% endif %} {% endif %} {% if request.user.is_superuser and contest_has_hidden_subtasks %} - {{ make_tab_item('resolver', 'fa fa-check', url('resolver', contest.key), _('Resolver')) }} + {{ make_tab_item('resolver', 'fa fa-check', url('resolver', contest.key), _('Resolver'), force_new_page=True) }} {% endif %} {% if show_final_ranking %} {{ make_tab_item('final_ranking', 'fa fa-bar-chart', url('contest_final_ranking', contest.key), _('Final rankings')) }} @@ -33,6 +33,6 @@ {% if perms.judge.moss_contest and has_moss_api_key %} {{ make_tab_item('moss', 'fa fa-gavel', url('contest_moss', contest.key), _('MOSS')) }} {% endif %} - {{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit')) }} + {{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit'), force_new_page=True) }} {% endif %} diff --git a/templates/contest/list.html b/templates/contest/list.html index f63fdc6..e3c1702 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -21,7 +21,14 @@ #search-org { width: 100%; } + .contest-format-user { + flex: 0.5 !important; + } + .participate-button { + float: right; + } + {% block contest_list_media %}{% endblock %} {% endblock %} {% block three_col_js %} @@ -75,7 +82,7 @@ var $form = $('form#filter-form'); - $('#show_orgs').click(function () { + $('#hide_organization_contests').click(function () { submitFormWithParams($form, "POST"); }); $('#go').click(function() { @@ -110,6 +117,8 @@ $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'}); + $('#order').select2(); + // var tooltip_classes = 'tooltipped tooltipped-e'; // // $('.contest-tag').each(function () { @@ -122,6 +131,7 @@ // }); }); + {% block contest_list_js %}{% endblock %} {% endblock %} {% block left_sidebar %} @@ -134,7 +144,31 @@ {% endblock %} -{% from "contest/macros.html" import contest_head, time_left, user_count, contest_join, contest_format_user %} +{% from "contest/macros.html" import contest_head, time_left, user_count, contest_format_user %} + +{% macro contest_join(contest, request, is_past=False) %} + {% if request.in_contest and request.participation.contest == contest %} + + {% elif is_past %} +
+ {% csrf_token %} + +
+ {% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %} +
+ {% csrf_token %} + +
+ {% else %} +
+ {% csrf_token %} + +
+ {% endif %} +{% endmacro %} {% block middle_content %}
@@ -192,11 +226,11 @@ {% endif %}
-
- {{ contest_format_user(contest, request) }} -
-
- {{ contest_join(contest, request) }} +
+ {{ contest_format_user(contest, request, is_official=is_official) }} +
+ {{ contest_join(contest, request) }} +
{% endwith %} @@ -230,11 +264,11 @@ {% endif %} -
- {{ contest_format_user(contest, request) }} -
-
- {{ contest_join(contest, request) }} +
+ {{ contest_format_user(contest, request, is_official=is_official) }} +
+ {{ contest_join(contest, request) }} +
{% endfor %} @@ -267,8 +301,8 @@ {% endif %} -
- {{ contest_format_user(contest, request, show_user=False) }} +
+ {{ contest_format_user(contest, request, show_user=False, is_official=is_official) }}
{% endfor %} @@ -296,19 +330,11 @@ {{ time_left(contest) }} -
- {{ contest_format_user(contest, request) }} -
-
- {% if request.in_contest and request.participation.contest == contest %} - - {% else %} -
- {% csrf_token %} - -
- {% endif %} +
+ {{ contest_format_user(contest, request, is_official=is_official) }} +
+ {{ contest_join(contest, request, is_past=True) }} +
{% endfor %} diff --git a/templates/contest/macros.html b/templates/contest/macros.html index eaf8ba3..0b683c6 100644 --- a/templates/contest/macros.html +++ b/templates/contest/macros.html @@ -77,30 +77,24 @@ {% endif %} {% endmacro %} -{% macro contest_join(contest, request) %} - {% if request.in_contest and request.participation.contest == contest %} - - {% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %} -
- {% csrf_token %} - -
+{% macro contest_format_user(contest, request, show_user=True, is_official=False) %} + {% if is_official %} +
+
{{ _('Format') }}: {{ contest.format.name }}
+
{{ _('Category') }}: {{ contest.official.category.name }}
+
{{ _('Location') }}: {{ contest.official.location.name }}
+
{{ _('Year') }}: {{ contest.official.year }}
+ {% if show_user %} +
{{ user_count(contest, request.user) }}
+ {% endif %} +
{% else %} -
- {% csrf_token %} - -
+
+
{{ _('Format') }}
+
{{ contest.format.name }}
+ {% if show_user %} +
{{ user_count(contest, request.user) }}
+ {% endif %} +
{% endif %} -{% endmacro %} - -{% macro contest_format_user(contest, request, show_user=True) %} -
-
{{ _('Format') }}
-
{{ contest.format.name }}
- {% if show_user %} -
{{ user_count(contest, request.user) }}
- {% endif %} -
{% endmacro %} \ No newline at end of file diff --git a/templates/contest/official-search-form.html b/templates/contest/official-search-form.html new file mode 100644 index 0000000..4d3393f --- /dev/null +++ b/templates/contest/official-search-form.html @@ -0,0 +1,57 @@ + \ No newline at end of file diff --git a/templates/contest/official_list.html b/templates/contest/official_list.html new file mode 100644 index 0000000..5b5023b --- /dev/null +++ b/templates/contest/official_list.html @@ -0,0 +1,42 @@ +{% extends "contest/list.html" %} + +{% block contest_list_media %} + +{% endblock %} + +{% block contest_list_js %} + +{% endblock %} + +{% block right_sidebar %} + +{% endblock %} \ No newline at end of file diff --git a/templates/contest/search-form.html b/templates/contest/search-form.html index 48f6abc..31732f5 100644 --- a/templates/contest/search-form.html +++ b/templates/contest/search-form.html @@ -6,8 +6,8 @@ placeholder="{{ _('Search contests...') }}"> {% if organizations %}
- - + +
{% endif %} +
+ + +
{{ _('Go') }} {% if create_url %} diff --git a/templates/course/left_sidebar.html b/templates/course/left_sidebar.html index a8e6375..b129b34 100644 --- a/templates/course/left_sidebar.html +++ b/templates/course/left_sidebar.html @@ -5,6 +5,6 @@ {{ make_tab_item('grades', 'fa fa-check-square-o', url('course_grades', course.slug), _('Grades')) }} {% endif %} {% if perms.judge.change_course %} - {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_course_change', course.id), _('Admin')) }} + {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_course_change', course.id), _('Admin'), force_new_page=True) }} {% endif %}
\ No newline at end of file diff --git a/templates/organization/org-left-sidebar.html b/templates/organization/org-left-sidebar.html index abdb11e..26bca4a 100644 --- a/templates/organization/org-left-sidebar.html +++ b/templates/organization/org-left-sidebar.html @@ -9,6 +9,6 @@ {{ make_tab_item('users', 'fa fa-user', organization.get_users_url(), _('Members')) }} {% endif %} {% if perms.judge.change_organization %} - {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_organization_change', organization.id), _('Admin')) }} + {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_organization_change', organization.id), _('Admin'), force_new_page=True) }} {% endif %} \ No newline at end of file diff --git a/templates/problem/left-sidebar.html b/templates/problem/left-sidebar.html index 3be6d24..0d2060e 100644 --- a/templates/problem/left-sidebar.html +++ b/templates/problem/left-sidebar.html @@ -3,7 +3,7 @@ {{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }} {{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }} {% if request.user.is_superuser %} - {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_problem_changelist'), _('Admin')) }} + {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_problem_changelist'), _('Admin'), force_new_page=True) }} {% endif %} {% endif %} \ No newline at end of file diff --git a/templates/submission/list.html b/templates/submission/list.html index b4ab3a8..da7b9ac 100644 --- a/templates/submission/list.html +++ b/templates/submission/list.html @@ -391,7 +391,7 @@ {{ make_tab_item('friend_tab', 'fa fa-users', friend_submissions_link, _('Friends')) }} {% endif %} {% if perms.judge.change_submission %} - {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_submission_changelist'), _('Admin')) }} + {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_submission_changelist'), _('Admin'), force_new_page=True) }} {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/three-column-content.html b/templates/three-column-content.html index 8f5512a..cceaaf7 100644 --- a/templates/three-column-content.html +++ b/templates/three-column-content.html @@ -32,8 +32,15 @@ function navigateTo($elem, update_sidebar = false) { var url = $elem.attr('href'); + var force_new_page = $elem.data('force_new_page'); if (url === '#') return; + + if (force_new_page) { + window.location.href = url; + return; + } + if (update_sidebar) { $('.left-sidebar-item').removeClass('active'); $elem.addClass('active'); @@ -99,8 +106,8 @@ {% endblock %} -{% macro make_tab_item(name, fa, url, text) %} - +{% macro make_tab_item(name, fa, url, text, force_new_page=False) %} + {{ text }} diff --git a/templates/user/user-left-sidebar.html b/templates/user/user-left-sidebar.html index c150f61..dfa6c18 100644 --- a/templates/user/user-left-sidebar.html +++ b/templates/user/user-left-sidebar.html @@ -3,6 +3,6 @@ {{ make_tab_item('friends', 'fa fa-users', url('user_list') + '?friend=true', _('Friends')) }} {{ make_tab_item('organizations', 'fa fa-university', url('organization_list'), _('Groups')) }} {% if request.user.is_superuser %} - {{ make_tab_item('import', 'fa fa-table', url('import_users'), _('Import')) }} + {{ make_tab_item('import', 'fa fa-table', url('import_users'), _('Import'), force_new_page=True) }} {% endif %} \ No newline at end of file