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.
# The caller, SubmissionList.get_result_data will run ugettext on the name.
{"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",
"name": gettext_noop("Compile Error"),
"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",
"name": gettext_noop("Error"),

View file

@ -140,9 +140,9 @@ class ContestList(
paginate_by = 10
template_name = "contest/list.html"
title = gettext_lazy("Contests")
context_object_name = "past_contests"
all_sorts = frozenset(("name", "user_count", "start_time"))
default_desc = frozenset(("name", "user_count"))
context_object_name = "contests"
def get_default_sort_order(self, request):
if request.GET.get("contest") and settings.ENABLE_FTS:
@ -159,6 +159,11 @@ class ContestList(
return request.GET.get(key, None) == "1"
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
@ -225,43 +230,63 @@ class ContestList(
return queryset
def get_queryset(self):
def _get_past_contests_queryset(self):
return (
self._get_queryset()
.order_by(self.order, "key")
.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):
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:
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)
context["current_tab"] = self.current_tab
if not ("contest" in self.request.GET and settings.ENABLE_FTS):
active.sort(key=attrgetter("end_time", "key"))
present.sort(key=attrgetter("end_time", "key"))
future.sort(key=attrgetter("start_time"))
context["current_count"] = self._get_current_contests_queryset().count()
context["future_count"] = self._get_future_contests_queryset().count()
context["active_count"] = self._get_active_participations_queryset().count()
context["active_participations"] = active
context["current_contests"] = present
context["future_contests"] = future
context["now"] = self._now
context["first_page_href"] = "."
context["contest_query"] = self.contest_query

View file

@ -434,14 +434,12 @@ class OrganizationContests(
args=[self.organization.id, self.organization.slug],
)
for participation in context["active_participations"]:
self.set_editable_contest(participation.contest)
for contest in context["past_contests"]:
self.set_editable_contest(contest)
for contest in context["current_contests"]:
self.set_editable_contest(contest)
for contest in context["future_contests"]:
self.set_editable_contest(contest)
if self.current_tab == "active":
for participation in context["contests"]:
self.set_editable_contest(participation.contest)
else:
for contest in context["contests"]:
self.set_editable_contest(contest)
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"
msgstr "Máy chấm"
msgid "Courses"
msgstr "Khóa học"
msgid "Suggestions"
msgstr "Đề xuất ý tưởng"
@ -42,9 +39,6 @@ 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 ""
@ -600,6 +594,12 @@ 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"

View file

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

View file

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

View file

@ -1,5 +1,7 @@
<div class="left-sidebar">
{{ 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('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>

View file

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