Organize contest list into timeline tabs (#111)
This commit is contained in:
parent
6c8926ec56
commit
9f4ae9f78f
9 changed files with 496 additions and 392 deletions
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -434,13 +434,11 @@ class OrganizationContests(
|
|||
args=[self.organization.id, self.organization.slug],
|
||||
)
|
||||
|
||||
for participation in context["active_participations"]:
|
||||
if self.current_tab == "active":
|
||||
for participation in context["contests"]:
|
||||
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"]:
|
||||
else:
|
||||
for contest in context["contests"]:
|
||||
self.set_editable_contest(contest)
|
||||
return context
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
||||
|
|
|
@ -528,7 +528,7 @@ a.edit-profile {
|
|||
.card-header {
|
||||
background-color: #f7f7f7;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')) }}
|
||||
{% if perms.judge.change_contest %}
|
||||
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_contest_changelist'), _('Admin')) }}
|
||||
{% endif %}
|
||||
</div>
|
|
@ -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)
|
||||
if (e.keyCode === 13) {
|
||||
$('#go').click();
|
||||
}
|
||||
});
|
||||
|
||||
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'});
|
||||
|
@ -202,14 +242,40 @@
|
|||
{% 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 %}
|
||||
<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>
|
||||
|
||||
{% 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">
|
||||
|
@ -240,16 +306,19 @@
|
|||
</div>
|
||||
{% endwith %}
|
||||
{% 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 active contest at this time.') }} </i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<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 current_tab == 'current' %}
|
||||
{% if contests %}
|
||||
{% for contest in contests %}
|
||||
<div class="list-contest">
|
||||
<div class="info-contest">
|
||||
<div class="contest-title"> {{ _('Contests') }} </div>
|
||||
|
@ -274,21 +343,19 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="toggled">
|
||||
<i> {{ _('There is no ongoing contest at this time.') }} </i>
|
||||
<br><br>
|
||||
{% 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 ongoing 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 %}
|
||||
{% if current_tab == 'future' %}
|
||||
{% if contests %}
|
||||
{% for contest in contests %}
|
||||
<div class="list-contest">
|
||||
<div class="info-contest">
|
||||
<div class="contest-title"> {{ _('Contests') }} </div>
|
||||
|
@ -310,26 +377,19 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="toggled">
|
||||
<i>{{ _('There is no scheduled contest at this time.') }}</i>
|
||||
<br><br>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<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;">
|
||||
<div style="margin-top: 10px;">
|
||||
{% include "list-pages.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i>{{ _('There is no scheduled contest at this time.') }}</i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% for contest in past_contests %}
|
||||
{% if current_tab == 'past' %}
|
||||
{% if contests %}
|
||||
{% for contest in contests %}
|
||||
<div class="list-contest">
|
||||
<div class="info-contest">
|
||||
<div class="contest-title"> {{ _('Contests') }} </div>
|
||||
|
@ -363,9 +423,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="toggled">
|
||||
<i> {{ _('There is no past contest.') }} </i>
|
||||
<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue