Organize contest list into timeline tabs (#111)

This commit is contained in:
Phuoc Anh Kha Le 2024-05-25 13:27:20 -05:00 committed by GitHub
parent 6c8926ec56
commit 9f4ae9f78f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 496 additions and 392 deletions

View file

@ -114,13 +114,21 @@ def _get_result_data(results):
# Using gettext_noop here since this will be tacked into the cache, so it must be language neutral. # Using gettext_noop here since this will be tacked into the cache, so it must be language neutral.
# The caller, SubmissionList.get_result_data will run ugettext on the name. # The caller, SubmissionList.get_result_data will run ugettext on the name.
{"code": "AC", "name": gettext_noop("Accepted"), "count": results["AC"]}, {"code": "AC", "name": gettext_noop("Accepted"), "count": results["AC"]},
{"code": "WA", "name": gettext_noop("Wrong"), "count": results["WA"]}, {
"code": "WA",
"name": gettext_noop("Wrong Answer"),
"count": results["WA"],
},
{ {
"code": "CE", "code": "CE",
"name": gettext_noop("Compile Error"), "name": gettext_noop("Compile Error"),
"count": results["CE"], "count": results["CE"],
}, },
{"code": "TLE", "name": gettext_noop("Timeout"), "count": results["TLE"]}, {
"code": "TLE",
"name": gettext_noop("Time Limit Exceeded"),
"count": results["TLE"],
},
{ {
"code": "ERR", "code": "ERR",
"name": gettext_noop("Error"), "name": gettext_noop("Error"),

View file

@ -140,9 +140,9 @@ class ContestList(
paginate_by = 10 paginate_by = 10
template_name = "contest/list.html" template_name = "contest/list.html"
title = gettext_lazy("Contests") title = gettext_lazy("Contests")
context_object_name = "past_contests"
all_sorts = frozenset(("name", "user_count", "start_time")) all_sorts = frozenset(("name", "user_count", "start_time"))
default_desc = frozenset(("name", "user_count")) default_desc = frozenset(("name", "user_count"))
context_object_name = "contests"
def get_default_sort_order(self, request): def get_default_sort_order(self, request):
if request.GET.get("contest") and settings.ENABLE_FTS: if request.GET.get("contest") and settings.ENABLE_FTS:
@ -159,6 +159,11 @@ class ContestList(
return request.GET.get(key, None) == "1" return request.GET.get(key, None) == "1"
def get(self, request, *args, **kwargs): 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.contest_query = None
self.org_query = [] self.org_query = []
self.show_orgs = 0 self.show_orgs = 0
@ -225,43 +230,63 @@ class ContestList(
return queryset return queryset
def get_queryset(self): def _get_past_contests_queryset(self):
return ( return (
self._get_queryset() self._get_queryset()
.order_by(self.order, "key")
.filter(end_time__lt=self._now) .filter(end_time__lt=self._now)
.order_by(self.order, "key")
) )
def _active_participations(self):
return ContestParticipation.objects.filter(
virtual=0,
user=self.request.profile,
contest__start_time__lte=self._now,
contest__end_time__gte=self._now,
)
@cached_property
def _active_contests_ids(self):
return self._active_participations().values_list("contest_id", flat=True)
def _get_current_contests_queryset(self):
return (
self._get_queryset()
.exclude(id__in=self._active_contests_ids)
.filter(start_time__lte=self._now, end_time__gte=self._now)
.order_by(self.order, "key")
)
def _get_future_contests_queryset(self):
return (
self._get_queryset()
.filter(start_time__gt=self._now)
.order_by("start_time", "key")
)
def _get_active_participations_queryset(self):
active_contests = self._get_queryset().filter(id__in=self._active_contests_ids)
return self._active_participations().filter(contest_id__in=active_contests)
def get_queryset(self):
if self.current_tab == "past":
return self._get_past_contests_queryset()
elif self.current_tab == "current":
return self._get_current_contests_queryset()
elif self.current_tab == "future":
return self._get_future_contests_queryset()
else: # Default to active
return self._get_active_participations_queryset()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ContestList, self).get_context_data(**kwargs) context = super(ContestList, self).get_context_data(**kwargs)
present, active, future = [], [], []
if not context["page_obj"] or context["page_obj"].number == 1:
for contest in self._get_queryset().exclude(end_time__lt=self._now):
if contest.start_time > self._now:
future.append(contest)
else:
present.append(contest)
if self.request.user.is_authenticated: context["current_tab"] = self.current_tab
for participation in (
ContestParticipation.objects.filter(
virtual=0, user=self.request.profile, contest_id__in=present
)
.select_related("contest")
.annotate(key=F("contest__key"))
):
if not participation.ended:
active.append(participation)
present.remove(participation.contest)
if not ("contest" in self.request.GET and settings.ENABLE_FTS): context["current_count"] = self._get_current_contests_queryset().count()
active.sort(key=attrgetter("end_time", "key")) context["future_count"] = self._get_future_contests_queryset().count()
present.sort(key=attrgetter("end_time", "key")) context["active_count"] = self._get_active_participations_queryset().count()
future.sort(key=attrgetter("start_time"))
context["active_participations"] = active
context["current_contests"] = present
context["future_contests"] = future
context["now"] = self._now context["now"] = self._now
context["first_page_href"] = "." context["first_page_href"] = "."
context["contest_query"] = self.contest_query context["contest_query"] = self.contest_query

View file

@ -434,14 +434,12 @@ class OrganizationContests(
args=[self.organization.id, self.organization.slug], args=[self.organization.id, self.organization.slug],
) )
for participation in context["active_participations"]: if self.current_tab == "active":
self.set_editable_contest(participation.contest) for participation in context["contests"]:
for contest in context["past_contests"]: self.set_editable_contest(participation.contest)
self.set_editable_contest(contest) else:
for contest in context["current_contests"]: for contest in context["contests"]:
self.set_editable_contest(contest) self.set_editable_contest(contest)
for contest in context["future_contests"]:
self.set_editable_contest(contest)
return context return context

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,6 @@ 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"
@ -42,9 +39,6 @@ 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 ""
@ -600,6 +594,12 @@ 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

@ -528,7 +528,7 @@ a.edit-profile {
.card-header { .card-header {
background-color: #f7f7f7; background-color: #f7f7f7;
text-align: center; text-align: center;
padding: 5px; padding: 10px;
} }
.avatar { .avatar {

View file

@ -97,8 +97,8 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% include 'contests-countdown.html' %}
{% include 'profile-table.html' %} {% include 'profile-table.html' %}
{% include 'contests-countdown.html' %}
{% include 'top-users.html' %} {% include 'top-users.html' %}
{% include 'recent-organization.html' %} {% include 'recent-organization.html' %}
</div> </div>

View file

@ -1,5 +1,7 @@
<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'), _('List')) }}
{{ 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')) }}
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_contest_changelist'), _('Admin')) }} {% if perms.judge.change_contest %}
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_contest_changelist'), _('Admin')) }}
{% endif %}
</div> </div>

View file

@ -14,16 +14,6 @@
padding: 1px 6px; padding: 1px 6px;
} }
.contest-group-header {
margin-bottom: 1em;
}
{% if page_obj and page_obj.number > 1%}
#ongoing-table {
display: none;
}
{% endif %}
#search-contest { #search-contest {
width: 100%; width: 100%;
height: 2.3em; height: 2.3em;
@ -37,16 +27,59 @@
{% block three_col_js %} {% block three_col_js %}
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script> <script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
function changeTabParameter(newTab) {
const url = new URL(window.location);
const searchParams = new URLSearchParams(url.search);
searchParams.set('tab', newTab);
url.search = searchParams.toString();
return url.href;
}
function submitFormWithParams($form, method) {
const currentUrl = new URL(window.location.href);
const searchParams = new URLSearchParams(currentUrl.search);
const formData = $form.serialize();
const params = new URLSearchParams(formData);
if (searchParams.has('tab')) {
params.set('tab', searchParams.get('tab'));
}
const fullUrl = currentUrl.pathname + '?' + params.toString();
if (method === "GET") {
window.location.href = fullUrl;
}
else {
var $formToSubmit = $('<form>')
.attr('action', fullUrl)
.attr('method', 'POST')
.appendTo('body');
$formToSubmit.append($('<input>').attr({
type: 'hidden',
name: 'csrfmiddlewaretoken',
value: $.cookie('csrftoken')
}));
$formToSubmit.submit();
}
}
$(document).ready(function () { $(document).ready(function () {
$('#active-url').attr('href', changeTabParameter('active'));
$('#current-url').attr('href', changeTabParameter('current'));
$('#future-url').attr('href', changeTabParameter('future'));
$('#past-url').attr('href', changeTabParameter('past'));
var $form = $('form#filter-form'); var $form = $('form#filter-form');
$('#show_orgs').click(function () { $('#show_orgs').click(function () {
($('<form>').attr('action', window.location.pathname + '?' + $form.serialize()) submitFormWithParams($form, "POST");
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')
.attr('value', $.cookie('csrftoken')))
.attr('method', 'POST').appendTo($('body')).submit());
}); });
$('#go').click(function() { $('#go').click(function() {
$form.submit(); submitFormWithParams($form, "GET");
}); });
$('.time-remaining').each(function () { $('.time-remaining').each(function () {
count_down($(this)); count_down($(this));
@ -63,9 +96,16 @@
return confirm(q); return confirm(q);
}); });
$form.on('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
}
});
$('#search-contest').keypress(function (e) { $('#search-contest').keypress(function (e) {
if (e.keyCode == 13) if (e.keyCode === 13) {
$('#go').click(); $('#go').click();
}
}); });
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'}); $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'});
@ -202,54 +242,41 @@
{% endmacro %} {% endmacro %}
{% block middle_content %} {% block middle_content %}
<div id="ongoing-table"> <div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
{% if active_participations %} <ul>
<h3 class="toggle open contest-group-header"> {% if request.user.is_authenticated %}
<i class="fa fa-chevron-right fa-fw"></i> <li class="{{'active' if current_tab=='active'}}">
{{ _('Active Contests') }} <a id='active-url'>{{ _('Active') }}
</h3> {% if active_count %}
<div class="toggled"> ({{active_count}})
{% for participation in active_participations %} {% endif %}
{% with contest=participation.contest %} </a>
<div class="list-contest"> </li>
<div class="info-contest"> {% endif %}
<div class="contest-title"> {{ _('Contests') }} </div> <li class="{{'active' if current_tab=='current'}}">
{{ contest_head(contest) }} <a id='current-url'>{{ _('Ongoing') }}
</div> {% if current_count %}
<div class="info-contest"> ({{current_count}})
<div class="contest-title"> {{ _('Time') }} </div> {% endif %}
<div class="contest-block"> </a>
{% if contest.start_time %} </li>
{{ time_left(contest) }} <li class="{{'active' if current_tab=='future'}}">
{% if contest.time_limit %} <a id='future-url'>{{ _('Upcoming') }}
<span class="time"> {% if future_count %}
{% trans countdown=participation.end_time|as_countdown %}Window ends in {{countdown}}{% endtrans %} ({{future_count}})
</span> {% endif %}
{% elif contest.time_before_end %} </a>
<span class="time">{% trans countdown=contest.end_time|as_countdown %}Ends in {{countdown}}{% endtrans %}</span> </li>
{% endif %} <li class="{{'active' if current_tab=='past'}}">
{% endif %} <a id='past-url'>{{ _('Past') }}</a>
</div> </li>
</div> </ul>
<div class="info-contest" style="flex: 0.5;"> </div>
{{ contest_format_user(contest) }}
</div>
<div class="participate-button">
{{ contest_join(contest, request) }}
</div>
</div>
{% endwith %}
{% endfor %}
</div>
{% endif %}
<h3 class="toggle open contest-group-header"> {% if request.user.is_authenticated and current_tab == 'active' %}
<i class="fa fa-chevron-right fa-fw"></i> {% if contests %}
{{ _('Ongoing Contests') }} {% for participation in contests %}
</h3> {% with contest=participation.contest %}
{% if current_contests %}
<div class="toggled">
{% for contest in current_contests %}
<div class="list-contest"> <div class="list-contest">
<div class="info-contest"> <div class="info-contest">
<div class="contest-title"> {{ _('Contests') }} </div> <div class="contest-title"> {{ _('Contests') }} </div>
@ -260,7 +287,11 @@
<div class="contest-block"> <div class="contest-block">
{% if contest.start_time %} {% if contest.start_time %}
{{ time_left(contest) }} {{ time_left(contest) }}
{% if contest.time_before_end %} {% if contest.time_limit %}
<span class="time">
{% trans countdown=participation.end_time|as_countdown %}Window ends in {{countdown}}{% endtrans %}
</span>
{% elif contest.time_before_end %}
<span class="time">{% trans countdown=contest.end_time|as_countdown %}Ends in {{countdown}}{% endtrans %}</span> <span class="time">{% trans countdown=contest.end_time|as_countdown %}Ends in {{countdown}}{% endtrans %}</span>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -273,99 +304,126 @@
{{ contest_join(contest, request) }} {{ contest_join(contest, request) }}
</div> </div>
</div> </div>
{% endfor %} {% endwith %}
</div> {% endfor %}
{% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div>
{% endif %}
{% else %} {% else %}
<div class="toggled"> <i> {{ _('There is no active contest at this time.') }} </i>
<i> {{ _('There is no ongoing contest at this time.') }} </i>
<br><br>
</div>
{% endif %} {% endif %}
{% endif %}
<h3 class="toggle open contest-group-header"> {% if current_tab == 'current' %}
<i class="fa fa-chevron-right fa-fw"></i> {% if contests %}
{{ _('Upcoming Contests') }} {% for contest in contests %}
</h3> <div class="list-contest">
{% if future_contests %} <div class="info-contest">
<div class="toggled"> <div class="contest-title"> {{ _('Contests') }} </div>
{% for contest in future_contests %} {{ contest_head(contest) }}
<div class="list-contest"> </div>
<div class="info-contest"> <div class="info-contest">
<div class="contest-title"> {{ _('Contests') }} </div> <div class="contest-title"> {{ _('Time') }} </div>
{{ contest_head(contest) }} <div class="contest-block">
</div> {% if contest.start_time %}
<div class="info-contest"> {{ time_left(contest) }}
<div class="contest-title"> {{ _('Time') }} </div> {% if contest.time_before_end %}
<div class="contest-block"> <span class="time">{% trans countdown=contest.end_time|as_countdown %}Ends in {{countdown}}{% endtrans %}</span>
{% if contest.start_time %}
{{ time_left(contest) }}
{% if contest.time_before_start %}
<span class="time">{{ _('Starting in %(countdown)s.', countdown=contest.start_time|as_countdown) }}</span>
{% endif %}
{% endif %} {% endif %}
</div> {% endif %}
</div>
<div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest, show_user=False) }}
</div> </div>
</div> </div>
{% endfor %} <div class="info-contest" style="flex: 0.5;">
</div> {{ contest_format_user(contest) }}
</div>
<div class="participate-button">
{{ contest_join(contest, request) }}
</div>
</div>
{% endfor %}
{% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div>
{% endif %}
{% else %} {% else %}
<div class="toggled"> <i> {{ _('There is no ongoing contest at this time.') }} </i>
<i>{{ _('There is no scheduled contest at this time.') }}</i>
<br><br>
</div>
{% endif %} {% endif %}
</div> {% endif %}
<h3 class="toggle open contest-group-header"> {% if current_tab == 'future' %}
{{ _('Past Contests') }} {% if contests %}
</h3> {% for contest in contests %}
{% if past_contests %} <div class="list-contest">
{% if page_obj and page_obj.num_pages > 1 %} <div class="info-contest">
<div style="margin-bottom: 10px;"> <div class="contest-title"> {{ _('Contests') }} </div>
{% include "list-pages.html" %} {{ contest_head(contest) }}
</div> </div>
{% endif %} <div class="info-contest">
<div class="contest-title"> {{ _('Time') }} </div>
{% for contest in past_contests %} <div class="contest-block">
<div class="list-contest"> {% if contest.start_time %}
<div class="info-contest"> {{ time_left(contest) }}
<div class="contest-title"> {{ _('Contests') }} </div> {% if contest.time_before_start %}
{{ contest_head(contest) }} <span class="time">{{ _('Starting in %(countdown)s.', countdown=contest.start_time|as_countdown) }}</span>
</div> {% endif %}
<div class="info-contest"> {% endif %}
<div class="contest-title"> {{ _('Time') }} </div> </div>
<div class="contest-block"> </div>
{{ time_left(contest) }} <div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest, show_user=False) }}
</div> </div>
</div> </div>
<div class="info-contest" style="flex: 0.5;"> {% endfor %}
{{ contest_format_user(contest) }} {% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div> </div>
<div class="participate-button"> {% endif %}
{% if request.in_contest and request.participation.contest == contest %} {% else %}
<button class="small" disabled>{{ _('In contest') }}</button> <i>{{ _('There is no scheduled contest at this time.') }}</i>
{% 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>
{% endfor %}
{% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div>
{% endif %} {% endif %}
{% else %} {% endif %}
<div class="toggled">
{% if current_tab == 'past' %}
{% if contests %}
{% for contest in contests %}
<div class="list-contest">
<div class="info-contest">
<div class="contest-title"> {{ _('Contests') }} </div>
{{ contest_head(contest) }}
</div>
<div class="info-contest">
<div class="contest-title"> {{ _('Time') }} </div>
<div class="contest-block">
{{ time_left(contest) }}
</div>
</div>
<div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest) }}
</div>
<div class="participate-button">
{% if request.in_contest and request.participation.contest == contest %}
<button class="small" disabled>{{ _('In contest') }}</button>
{% 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>
{% endfor %}
{% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div>
{% endif %}
{% else %}
<i> {{ _('There is no past contest.') }} </i> <i> {{ _('There is no past contest.') }} </i>
<br> {% endif %}
</div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}