Add official contest

This commit is contained in:
cuom1999 2024-05-30 02:59:22 -05:00
parent 796a670cd7
commit 10e50795d9
25 changed files with 882 additions and 362 deletions

View file

@ -540,6 +540,10 @@ urlpatterns = [
r"^contests/summary/(?P<key>\w+)/", r"^contests/summary/(?P<key>\w+)/",
paged_list_view(contests.ContestsSummaryView, "contests_summary"), 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"^courses/", paged_list_view(course.CourseList, "course_list")),
url( url(
r"^course/(?P<slug>[\w-]*)", r"^course/(?P<slug>[\w-]*)",

View file

@ -20,7 +20,12 @@ from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin
from judge.admin.profile import ProfileAdmin, UserAdmin from judge.admin.profile import ProfileAdmin, UserAdmin
from judge.admin.runtime import JudgeAdmin, LanguageAdmin from judge.admin.runtime import JudgeAdmin, LanguageAdmin
from judge.admin.submission import SubmissionAdmin 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.ticket import TicketAdmin
from judge.admin.volunteer import VolunteerProblemVoteAdmin from judge.admin.volunteer import VolunteerProblemVoteAdmin
from judge.admin.course import CourseAdmin from judge.admin.course import CourseAdmin
@ -48,6 +53,8 @@ from judge.models import (
VolunteerProblemVote, VolunteerProblemVote,
Course, Course,
ContestsSummary, ContestsSummary,
OfficialContestCategory,
OfficialContestLocation,
) )
@ -77,3 +84,5 @@ admin.site.register(Course, CourseAdmin)
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
admin.site.register(ContestsSummary, ContestsSummaryAdmin) admin.site.register(ContestsSummary, ContestsSummaryAdmin)
admin.site.register(OfficialContestCategory, OfficialContestCategoryAdmin)
admin.site.register(OfficialContestLocation, OfficialContestLocationAdmin)

View file

@ -14,7 +14,14 @@ from reversion.admin import VersionAdmin
from reversion_compare.admin import CompareVersionAdmin from reversion_compare.admin import CompareVersionAdmin
from django_ace import AceWidget 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.ratings import rate_contest
from judge.widgets import ( from judge.widgets import (
AdminHeavySelect2MultipleWidget, 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): class ContestAdmin(CompareVersionAdmin):
fieldsets = ( fieldsets = (
(None, {"fields": ("key", "name", "authors", "curators", "testers")}), (None, {"fields": ("key", "name", "authors", "curators", "testers")}),
@ -223,7 +250,7 @@ class ContestAdmin(CompareVersionAdmin):
"user_count", "user_count",
) )
search_fields = ("key", "name") search_fields = ("key", "name")
inlines = [ContestProblemInline] inlines = [ContestProblemInline, OfficialContestInline]
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = ContestForm form = ContestForm

View file

@ -56,3 +56,11 @@ class ProblemTypeAdmin(admin.ModelAdmin):
[o.pk for o in obj.problem_set.all()] if obj else [] [o.pk for o in obj.problem_set.all()] if obj else []
) )
return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs) return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs)
class OfficialContestCategoryAdmin(admin.ModelAdmin):
fields = ("name",)
class OfficialContestLocationAdmin(admin.ModelAdmin):
fields = ("name",)

View file

@ -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",
},
),
]

View file

@ -15,6 +15,9 @@ from judge.models.contest import (
Rating, Rating,
ContestProblemClarification, ContestProblemClarification,
ContestsSummary, ContestsSummary,
OfficialContestCategory,
OfficialContestLocation,
OfficialContest,
) )
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
from judge.models.message import PrivateMessage, PrivateMessageThread from judge.models.message import PrivateMessage, PrivateMessageThread

View file

@ -39,6 +39,9 @@ __all__ = [
"Rating", "Rating",
"ContestProblemClarification", "ContestProblemClarification",
"ContestsSummary", "ContestsSummary",
"OfficialContest",
"OfficialContestCategory",
"OfficialContestLocation",
] ]
@ -974,3 +977,53 @@ class ContestsSummary(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("contests_summary", args=[self.key]) 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")

View file

@ -69,6 +69,8 @@ from judge.models import (
Submission, Submission,
ContestProblemClarification, ContestProblemClarification,
ContestsSummary, ContestsSummary,
OfficialContestCategory,
OfficialContestLocation,
) )
from judge.tasks import run_moss from judge.tasks import run_moss
from judge.utils.celery import redirect_to_task_status from judge.utils.celery import redirect_to_task_status
@ -106,6 +108,7 @@ __all__ = [
"base_contest_ranking_list", "base_contest_ranking_list",
"ContestClarificationView", "ContestClarificationView",
"update_contest_mode", "update_contest_mode",
"OfficialContestList",
] ]
@ -129,8 +132,15 @@ def _find_contest(request, key):
class ContestListMixin(object): class ContestListMixin(object):
official = False
def get_queryset(self): 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( class ContestList(
@ -158,39 +168,41 @@ class ContestList(
return request.session.get(key, False) return request.session.get(key, False)
return request.GET.get(key, None) == "1" 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): def get(self, request, *args, **kwargs):
default_tab = "active" default_tab = "active"
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
default_tab = "current" default_tab = "current"
self.current_tab = self.request.GET.get("tab", default_tab) self.current_tab = self.request.GET.get("tab", default_tab)
self.contest_query = None self.setup_contest_list(request)
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
return super(ContestList, self).get(request, *args, **kwargs) return super(ContestList, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
to_update = ("show_orgs",) to_update = ("hide_organization_contests",)
for key in to_update: for key in to_update:
if key in request.GET: if key in request.GET:
val = request.GET.get(key) == "1" val = request.GET.get(key) == "1"
@ -199,6 +211,9 @@ class ContestList(
request.session[key] = False request.session[key] = False
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
def extra_queryset_filters(self, queryset):
return queryset
def _get_queryset(self): def _get_queryset(self):
queryset = ( queryset = (
super(ContestList, self) super(ContestList, self)
@ -206,28 +221,25 @@ class ContestList(
.prefetch_related("tags", "organizations") .prefetch_related("tags", "organizations")
) )
if self.request.GET.get("contest"): if self.contest_query:
self.contest_query = query = " ".join(
self.request.GET.getlist("contest")
).strip()
if query:
substr_queryset = queryset.filter( substr_queryset = queryset.filter(
Q(key__icontains=query) | Q(name__icontains=query) Q(key__icontains=self.contest_query)
| Q(name__icontains=self.contest_query)
) )
if settings.ENABLE_FTS: if settings.ENABLE_FTS:
queryset = ( queryset = (
queryset.search(query).extra(order_by=["-relevance"]) queryset.search(self.contest_query).extra(order_by=["-relevance"])
| substr_queryset | substr_queryset
) )
else: else:
queryset = substr_queryset queryset = substr_queryset
if not self.org_query and self.request.organization: if not self.org_query and self.request.organization:
self.org_query = [self.request.organization.id] self.org_query = [self.request.organization.id]
if self.show_orgs: if self.hide_organization_contests:
queryset = queryset.filter(organizations=None) queryset = queryset.filter(organizations=None)
if self.org_query: if self.org_query:
queryset = queryset.filter(organizations__in=self.org_query) queryset = queryset.filter(organizations__in=self.org_query)
queryset = self.extra_queryset_filters(queryset)
return queryset return queryset
def _get_past_contests_queryset(self): def _get_past_contests_queryset(self):
@ -295,10 +307,19 @@ class ContestList(
context["first_page_href"] = "." context["first_page_href"] = "."
context["contest_query"] = self.contest_query context["contest_query"] = self.contest_query
context["org_query"] = self.org_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: if self.request.profile:
context["organizations"] = self.request.profile.organizations.all() context["organizations"] = self.request.profile.organizations.all()
context["page_type"] = "list" 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_context())
context.update(self.get_sort_paginate_context()) context.update(self.get_sort_paginate_context())
return 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) sorted_total_points.sort(key=lambda x: x.points, reverse=True)
total_rank = ranker(sorted_total_points) total_rank = ranker(sorted_total_points)
return [(rank, item._asdict()) for rank, item in total_rank] 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

View file

@ -414,7 +414,7 @@ class OrganizationContests(
def get_queryset(self): def get_queryset(self):
self.org_query = [self.organization_id] self.org_query = [self.organization_id]
self.show_orgs = False self.hide_organization_contests = False
return super().get_queryset() return super().get_queryset()
def set_editable_contest(self, contest): def set_editable_contest(self, contest):

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,9 @@ msgstr "Giới thiệu"
msgid "Status" msgid "Status"
msgstr "Máy chấm" msgstr "Máy chấm"
msgid "Courses"
msgstr "Khóa học"
msgid "Suggestions" msgid "Suggestions"
msgstr "Đề xuất ý tưởng" msgstr "Đề xuất ý tưởng"
@ -39,6 +42,9 @@ msgstr "Đăng ký tên"
msgid "Report" msgid "Report"
msgstr "Báo cáo tiêu cực" msgstr "Báo cáo tiêu cực"
msgid "Bug Report"
msgstr "Báo cáo lỗi"
msgid "2sat" msgid "2sat"
msgstr "" msgstr ""
@ -594,12 +600,6 @@ msgstr ""
msgid "z-function" msgid "z-function"
msgstr "" msgstr ""
#~ msgid "Courses"
#~ msgstr "Khóa học"
#~ msgid "Bug Report"
#~ msgstr "Báo cáo lỗi"
#~ msgid "Insert Image" #~ msgid "Insert Image"
#~ msgstr "Chèn hình ảnh" #~ msgstr "Chèn hình ảnh"

View file

@ -18,12 +18,6 @@
flex: 1; flex: 1;
} }
.participate-button {
display: flex;
justify-content: center;
align-items: center;
}
.contest-title { .contest-title {
font-size: 1.1em; font-size: 1.1em;
font-weight: 600; font-weight: 600;

View file

@ -1,7 +1,8 @@
<div class="left-sidebar"> <div class="left-sidebar">
{{ make_tab_item('list', 'fa fa-list', url('contest_list'), _('List')) }} {{ make_tab_item('list', 'fa fa-list', url('contest_list'), _('All')) }}
{{ make_tab_item('official', 'fa fa-check-circle-o', url('official_contest_list'), _('Official')) }}
{{ make_tab_item('calendar', 'fa fa-calendar', url('contest_calendar', now.year, now.month), _('Calendar')) }} {{ make_tab_item('calendar', 'fa fa-calendar', url('contest_calendar', now.year, now.month), _('Calendar')) }}
{% if perms.judge.change_contest %} {% if perms.judge.change_contest %}
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_contest_changelist'), _('Admin')) }} {{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_contest_changelist'), _('Admin'), force_new_page=True) }}
{% endif %} {% endif %}
</div> </div>

View file

@ -24,7 +24,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if request.user.is_superuser and contest_has_hidden_subtasks %} {% 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 %} {% endif %}
{% if show_final_ranking %} {% if show_final_ranking %}
{{ make_tab_item('final_ranking', 'fa fa-bar-chart', url('contest_final_ranking', contest.key), _('Final rankings')) }} {{ 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 %} {% if perms.judge.moss_contest and has_moss_api_key %}
{{ make_tab_item('moss', 'fa fa-gavel', url('contest_moss', contest.key), _('MOSS')) }} {{ make_tab_item('moss', 'fa fa-gavel', url('contest_moss', contest.key), _('MOSS')) }}
{% endif %} {% 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 %} {% endif %}
</div> </div>

View file

@ -21,7 +21,14 @@
#search-org { #search-org {
width: 100%; width: 100%;
} }
.contest-format-user {
flex: 0.5 !important;
}
.participate-button {
float: right;
}
</style> </style>
{% block contest_list_media %}{% endblock %}
{% endblock %} {% endblock %}
{% block three_col_js %} {% block three_col_js %}
@ -75,7 +82,7 @@
var $form = $('form#filter-form'); var $form = $('form#filter-form');
$('#show_orgs').click(function () { $('#hide_organization_contests').click(function () {
submitFormWithParams($form, "POST"); submitFormWithParams($form, "POST");
}); });
$('#go').click(function() { $('#go').click(function() {
@ -110,6 +117,8 @@
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'}); $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'});
$('#order').select2();
// var tooltip_classes = 'tooltipped tooltipped-e'; // var tooltip_classes = 'tooltipped tooltipped-e';
// //
// $('.contest-tag').each(function () { // $('.contest-tag').each(function () {
@ -122,6 +131,7 @@
// }); // });
}); });
</script> </script>
{% block contest_list_js %}{% endblock %}
{% endblock %} {% endblock %}
{% block left_sidebar %} {% block left_sidebar %}
@ -134,7 +144,31 @@
</div> </div>
{% endblock %} {% 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 %}
<button class="small" disabled>{{ _('In contest') }}</button>
{% elif is_past %}
<form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %}
<input type="submit" class="unselectable button full small"
value="{{ _('Virtual join') }}">
</form>
{% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %}
<form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %}
<input type="submit" class="unselectable button full small"
value="{{ _('Spectate') }}">
</form>
{% else %}
<form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %}
<input type="submit" class="unselectable button full small join-warning"
value="{{ _('Join') }}">
</form>
{% endif %}
{% endmacro %}
{% block middle_content %} {% block middle_content %}
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;"> <div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
@ -192,13 +226,13 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="info-contest" style="flex: 0.5;"> <div class="info-contest contest-format-user">
{{ contest_format_user(contest, request) }} {{ contest_format_user(contest, request, is_official=is_official) }}
</div>
<div class="participate-button"> <div class="participate-button">
{{ contest_join(contest, request) }} {{ contest_join(contest, request) }}
</div> </div>
</div> </div>
</div>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
{% if page_obj and page_obj.num_pages > 1 %} {% if page_obj and page_obj.num_pages > 1 %}
@ -230,13 +264,13 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="info-contest" style="flex: 0.5;"> <div class="info-contest contest-format-user">
{{ contest_format_user(contest, request) }} {{ contest_format_user(contest, request, is_official=is_official) }}
</div>
<div class="participate-button"> <div class="participate-button">
{{ contest_join(contest, request) }} {{ contest_join(contest, request) }}
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
{% if page_obj and page_obj.num_pages > 1 %} {% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
@ -267,8 +301,8 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="info-contest" style="flex: 0.5;"> <div class="info-contest contest-format-user">
{{ contest_format_user(contest, request, show_user=False) }} {{ contest_format_user(contest, request, show_user=False, is_official=is_official) }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -296,19 +330,11 @@
{{ time_left(contest) }} {{ time_left(contest) }}
</div> </div>
</div> </div>
<div class="info-contest" style="flex: 0.5;"> <div class="info-contest contest-format-user">
{{ contest_format_user(contest, request) }} {{ contest_format_user(contest, request, is_official=is_official) }}
</div>
<div class="participate-button"> <div class="participate-button">
{% if request.in_contest and request.participation.contest == contest %} {{ contest_join(contest, request, is_past=True) }}
<button class="small" disabled>{{ _('In contest') }}</button> </div>
{% else %}
<form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %}
<input type="submit" class="unselectable button full small"
value="{{ _('Virtual join') }}">
</form>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View file

@ -77,30 +77,24 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro contest_join(contest, request) %} {% macro contest_format_user(contest, request, show_user=True, is_official=False) %}
{% if request.in_contest and request.participation.contest == contest %} {% if is_official %}
<button class="small" disabled>{{ _('In contest') }}</button> <div style="display: flex; flex-direction: column;">
{% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %} <div><b>{{ _('Format') }}</b>: {{ contest.format.name }}</div>
<form action="{{ url('contest_join', contest.key) }}" method="post"> <div><b>{{ _('Category') }}</b>: {{ contest.official.category.name }}</div>
{% csrf_token %} <div><b>{{ _('Location') }}</b>: {{ contest.official.location.name }}</div>
<input type="submit" class="unselectable button full small" <div><b>{{ _('Year') }}</b>: {{ contest.official.year }}</div>
value="{{ _('Spectate') }}"> {% if show_user %}
</form> <div class="contest-title" style="margin-top: 0.4em">{{ user_count(contest, request.user) }}</div>
{% else %}
<form action="{{ url('contest_join', contest.key) }}" method="post">
{% csrf_token %}
<input type="submit" class="unselectable button full small join-warning"
value="{{ _('Join') }}">
</form>
{% endif %} {% endif %}
{% endmacro %} </div>
{% else %}
{% macro contest_format_user(contest, request, show_user=True) %} <div style="display: flex; flex-direction: column;">
<div style="display: flex; flex-direction: column; height: 100%;">
<div class="contest-title"> {{ _('Format') }} </div> <div class="contest-title"> {{ _('Format') }} </div>
<div style="flex-grow: 1">{{ contest.format.name }}</div> <div style="flex-grow: 1">{{ contest.format.name }}</div>
{% if show_user %} {% if show_user %}
<div class="contest-title">{{ user_count(contest, request.user) }}</div> <div class="contest-title" style="margin-top: 0.4em">{{ user_count(contest, request.user) }}</div>
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endmacro %} {% endmacro %}

View file

@ -0,0 +1,57 @@
<div class="sidebox">
<h3 class="colored-text"><i class="fa fa-search"></i>{{ _('Contest search') }}</h3>
<div class="sidebox-content">
<form id="filter-form" method="GET">
<input id="search-contest" type="text" name="contest" value="{{ contest_query or '' }}"
placeholder="{{ _('Search contests...') }}">
<div class="filter-form-group">
<label class="bold-text margin-label" for="year_from"><i class="non-italics">{{ _('Year') }}</i></label>
<div class="year-range">
<input type="number" name="year_from" id="year_from" value="{{ year_from or '' }}"
placeholder="{{ _('From') }}">
<input type="number" name="year_to" id="year_to" value="{{ year_to or '' }}"
placeholder="{{ _('To') }}" style="float: right">
</div>
</div>
<div class="filter-form-group">
<label class="bold-text margin-label" for="category"><i class="non-italics">{{ _('Category') }}</i></label>
<select id="category" name="category" multiple>
{% for cat in categories %}
<option value="{{ cat.id }}"{% if cat.id in selected_categories %} selected{% endif %}>
{{ cat.name }}
</option>
{% endfor %}
</select>
</div>
<div class="filter-form-group">
<label class="bold-text margin-label" for="location"><i class="non-italics">{{ _('Location') }}</i></label>
<select id="location" name="location" style="width: 100%" multiple>
{% for loc in locations %}
<option value="{{ loc.id }}"{% if loc.id in selected_locations %} selected{% endif %}>
{{ loc.name }}
</option>
{% endfor %}
</select>
</div>
<div class="filter-form-group">
<label for="order" class="bold-text margin-label">{{ _('Order by') }}</label>
<select id="order" name="order" style="width: 100%">
<option value="">---</option>
{% for value, name in all_sort_options %}
<option value="{{value}}"{% if selected_order == value%} selected{% endif %}>
{{ name }}
</option>
{% endfor %}
</select>
</div>
<div class="form-submit-group">
<a id="go" class="button small">{{ _('Go') }}</a>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,42 @@
{% extends "contest/list.html" %}
{% block contest_list_media %}
<style type="text/css">
.contest-format-user {
flex: 1 !important;
}
.year-range {
display: inline;
}
.year-range input {
width: 45%;
}
</style>
{% endblock %}
{% block contest_list_js %}
<script type="text/javascript">
$(function() {
$("#category").select2();
$("#location").select2();
$('#year_from').keypress(function (e) {
if (e.keyCode === 13) {
$('#go').click();
}
});
$('#year_to').keypress(function (e) {
if (e.keyCode === 13) {
$('#go').click();
}
});
});
</script>
{% endblock %}
{% block right_sidebar %}
<div class="right-sidebar">
{% include "contest/official-search-form.html" %}
</div>
{% endblock %}

View file

@ -6,8 +6,8 @@
placeholder="{{ _('Search contests...') }}"> placeholder="{{ _('Search contests...') }}">
{% if organizations %} {% if organizations %}
<div style="margin-bottom: 1em;"> <div style="margin-bottom: 1em;">
<input id="show_orgs" type="checkbox" name="show_orgs" value="1" {% if show_orgs %}checked{% endif %}> <input id="hide_organization_contests" type="checkbox" name="hide_organization_contests" value="1" {% if hide_organization_contests %}checked{% endif %}>
<label for="show_orgs">{{ _('Hide organization contests') }}</label> <label for="hide_organization_contests">{{ _('Hide organization contests') }}</label>
</div> </div>
<label for="search-org" class="bold-text margin-label">{{ _('Group') }}</label> <label for="search-org" class="bold-text margin-label">{{ _('Group') }}</label>
<select id="search-org" name="orgs" multiple> <select id="search-org" name="orgs" multiple>
@ -18,6 +18,17 @@
{% endfor %} {% endfor %}
</select> </select>
{% endif %} {% endif %}
<div class="filter-form-group">
<label for="order" class="bold-text margin-label">{{ _('Order by') }}</label>
<select id="order" name="order" style="width: 100%">
<option value="">---</option>
{% for value, name in all_sort_options %}
<option value="{{value}}"{% if selected_order == value%} selected{% endif %}>
{{ name }}
</option>
{% endfor %}
</select>
</div>
<div class="form-submit-group"> <div class="form-submit-group">
<a id="go" class="button small">{{ _('Go') }}</a> <a id="go" class="button small">{{ _('Go') }}</a>
{% if create_url %} {% if create_url %}

View file

@ -5,6 +5,6 @@
{{ make_tab_item('grades', 'fa fa-check-square-o', url('course_grades', course.slug), _('Grades')) }} {{ make_tab_item('grades', 'fa fa-check-square-o', url('course_grades', course.slug), _('Grades')) }}
{% endif %} {% endif %}
{% if perms.judge.change_course %} {% 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 %} {% endif %}
</div> </div>

View file

@ -9,6 +9,6 @@
{{ make_tab_item('users', 'fa fa-user', organization.get_users_url(), _('Members')) }} {{ make_tab_item('users', 'fa fa-user', organization.get_users_url(), _('Members')) }}
{% endif %} {% endif %}
{% if perms.judge.change_organization %} {% 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 %} {% endif %}
</div> </div>

View file

@ -3,7 +3,7 @@
{{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }} {{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }}
{{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }} {{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }}
{% if request.user.is_superuser %} {% 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 %}
</div> </div>
{% endif %} {% endif %}

View file

@ -391,7 +391,7 @@
{{ make_tab_item('friend_tab', 'fa fa-users', friend_submissions_link, _('Friends')) }} {{ make_tab_item('friend_tab', 'fa fa-users', friend_submissions_link, _('Friends')) }}
{% endif %} {% endif %}
{% if perms.judge.change_submission %} {% 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 %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -32,8 +32,15 @@
function navigateTo($elem, update_sidebar = false) { function navigateTo($elem, update_sidebar = false) {
var url = $elem.attr('href'); var url = $elem.attr('href');
var force_new_page = $elem.data('force_new_page');
if (url === '#') return; if (url === '#') return;
if (force_new_page) {
window.location.href = url;
return;
}
if (update_sidebar) { if (update_sidebar) {
$('.left-sidebar-item').removeClass('active'); $('.left-sidebar-item').removeClass('active');
$elem.addClass('active'); $elem.addClass('active');
@ -99,8 +106,8 @@
</script> </script>
{% endblock %} {% endblock %}
{% macro make_tab_item(name, fa, url, text) %} {% macro make_tab_item(name, fa, url, text, force_new_page=False) %}
<a class="left-sidebar-item {% if page_type == name %}active{% endif %}" href="{{ url }}" id="{{ name }}-tab"> <a class="left-sidebar-item {% if page_type == name %}active{% endif %}" href="{{ url }}" id="{{ name }}-tab" {% if force_new_page%}data-force_new_page="1"{% endif %}>
<span class="sidebar-icon"><i class="{{ fa }}"></i></span> <span class="sidebar-icon"><i class="{{ fa }}"></i></span>
<span class="sidebar-text">{{ text }}</span> <span class="sidebar-text">{{ text }}</span>
</a> </a>

View file

@ -3,6 +3,6 @@
{{ make_tab_item('friends', 'fa fa-users', url('user_list') + '?friend=true', _('Friends')) }} {{ make_tab_item('friends', 'fa fa-users', url('user_list') + '?friend=true', _('Friends')) }}
{{ make_tab_item('organizations', 'fa fa-university', url('organization_list'), _('Groups')) }} {{ make_tab_item('organizations', 'fa fa-university', url('organization_list'), _('Groups')) }}
{% if request.user.is_superuser %} {% 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 %} {% endif %}
</div> </div>