Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dmoj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def paged_list_view(view, name):
path('contests.ics', contests.ContestICal.as_view(), name='contest_ical'),
path('contests/<int:year>/<int:month>/', contests.ContestCalendar.as_view(), name='contest_calendar'),
path('contests/new', contests.CreateContest.as_view(), name='contest_new'),
path('contests/reorder/', contests.ContestReorder.as_view(), name='contest_reorder'),
re_path(r'^contests/tag/(?P<key>[a-z-]+)', include([
path('', paged_list_view(contests.ContestTagList, 'contest_tag_list')),
])),
Expand Down
2 changes: 2 additions & 0 deletions judge/models/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Contest(models.Model):
(SCOREBOARD_AFTER_CONTEST, _('Hidden for duration of contest')),
(SCOREBOARD_AFTER_PARTICIPATION, _('Hidden for duration of participation')),
)
sort_order = models.IntegerField(verbose_name=_('sort order'), default=0, db_index=True)
key = models.CharField(max_length=32, verbose_name=_('contest id'), unique=True,
validators=[RegexValidator('^[a-z0-9_]+$', _('Contest id must be ^[a-z0-9_]+$'))])
name = models.CharField(max_length=100, verbose_name=_('contest name'), db_index=True)
Expand Down Expand Up @@ -553,6 +554,7 @@ class Meta:
)
verbose_name = _('contest')
verbose_name_plural = _('contests')
ordering = ('sort_order', 'key')


class ContestAnnouncement(models.Model):
Expand Down
65 changes: 58 additions & 7 deletions judge/views/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
from judge.utils.stats import get_bar_chart, get_pie_chart, get_stacked_bar_chart
from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, SingleObjectFormView, TitleMixin, \
add_file_response, generic_message
from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator

__all__ = ['ContestList', 'ContestDetail', 'ContestRanking', 'ContestJoin', 'ContestLeave', 'ContestCalendar',
'ContestClone', 'ContestStats', 'ContestMossView', 'ContestMossDelete',
Expand Down Expand Up @@ -114,7 +116,7 @@ def _get_queryset(self):

def get_queryset(self):
self.search_query = None
query_set = self._get_queryset().order_by(self.order, 'key').filter(end_time__lt=self._now)
query_set = self._get_queryset().order_by('sort_order', self.order, 'key').filter(end_time__lt=self._now)
if 'search' in self.request.GET:
self.search_query = search_query = ' '.join(self.request.GET.getlist('search')).strip()
if search_query:
Expand Down Expand Up @@ -147,9 +149,13 @@ def get_context_data(self, **kwargs):
active.append(participation)
present.remove(participation.contest)

active.sort(key=attrgetter('end_time', 'key'))
present.sort(key=attrgetter('end_time', 'key'))
future.sort(key=attrgetter('start_time'))
active.sort(key=lambda p: (p.contest.sort_order, p.contest.key))
present.sort(key=lambda c: (c.sort_order, c.key))
future.sort(key=lambda c: (c.sort_order, c.key))

# active.sort(key=attrgetter('end_time', 'key'))
# present.sort(key=attrgetter('end_time', 'key'))
# future.sort(key=attrgetter('start_time'))
context['active_participations'] = active
context['current_contests'] = present
context['future_contests'] = future
Expand Down Expand Up @@ -219,9 +225,13 @@ def get_context_data(self, **kwargs):
active.append(participation)
present.remove(participation.contest)

active.sort(key=attrgetter('end_time', 'key'))
present.sort(key=attrgetter('end_time', 'key'))
future.sort(key=attrgetter('start_time'))
active.sort(key=lambda p: (p.contest.sort_order, p.contest.key))
present.sort(key=lambda c: (c.sort_order, c.key))
future.sort(key=lambda c: (c.sort_order, c.key))

# active.sort(key=attrgetter('end_time', 'key'))
# present.sort(key=attrgetter('end_time', 'key'))
# future.sort(key=attrgetter('start_time'))
context['title'] = _('Các kỳ thi thuộc mục "%s"') % self.tag.display_name
context['active_participations'] = active
context['current_contests'] = present
Expand Down Expand Up @@ -1744,3 +1754,44 @@ def get(self, request, *args, **kwargs):
response['Content-Type'] = 'application/zip'
response['Content-Disposition'] = 'attachment; filename=%s-data.zip' % self.object.key
return response


@method_decorator(staff_member_required, name='dispatch')
class ContestReorder(View):
def get(self, request):
now = timezone.now()
contests = Contest.objects.prefetch_related('tags', 'organizations').order_by('sort_order', 'key')

year = request.GET.get('year')
tag_key = request.GET.get('tag')

if year:
contests = contests.filter(start_time__year=year)
if tag_key:
contests = contests.filter(tags__key=tag_key)

all_years = Contest.objects.dates('start_time', 'year', order='DESC')
all_tags = ContestTag.objects.all().order_by('key')
return render(request, 'contest/reorder.html', {
'title': _('Reorder contests'),
'future_contests': contests.filter(start_time__gt=now),
'current_contests': contests.filter(start_time__lte=now, end_time__gte=now),
'past_contests': contests.filter(end_time__lt=now),
'all_years': [d.year for d in all_years],
'all_tags': all_tags,
'current_year': year,
'current_tag': tag_key,
})

def post(self, request):
data = json.loads(request.body)
for section in ('future', 'present', 'past'):
keys = data.get(section, [])
if not keys:
continue
current_orders = sorted(
Contest.objects.filter(key__in=keys).values_list('sort_order', flat=True)
)
for key, sort_val in zip(keys, current_orders):
Contest.objects.filter(key=key).update(sort_order=sort_val)
return JsonResponse({'status': 'ok'})
66 changes: 66 additions & 0 deletions locale/vi/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7656,3 +7656,69 @@ msgstr "File logo"
#: judge/models/contest.py: 203
msgid "rank display options"
msgstr "Hiển thị đại diện trong rank"

#: judge/models/profile.py:170
msgid "unpublic logo"
msgstr "Không công khai logo"

#: judge/models/profile.py:171
msgid "If enabled, only selected users or users within the organization selected below will be able to use the logo. Leave the fields below blank so no one can see this logo"
msgstr "Nếu bật, chỉ những người dùng được chọn hoặc người dùng trong tổ chức được chọn bên dưới mới có thể sử dụng logo. Để trống những trường bên dưới để ẩn đi logo này"

#: judge/models/profile.py:175
msgid "users are permitted"
msgstr "Người dùng được phép"

#: judge/models/profile.py:176
msgid "If private, only these users may choose the logo"
msgstr "Nếu cho private, chỉ những người dùng trên mới được chọn logo"

#: judge/models/profile.py:178
msgid "If private, only these organizations may choose the logo"
msgstr "Nếu cho private, chỉ những tổ chức trên mới có thể chọn logo"

#: judge/forms.py:93
#, python-format
msgid "You are not allowed to use this logo"
msgstr ""
"Bạn không có quyền sử dụng logo này!"

#: judge/admin/profile.py:62
msgid "This user is not allowed to use this logo"
msgstr "Người dùng này không có quyền sử dụng logo này!"

#: judge/view/contest.py:1776
msgid "Reorder contests"
msgstr "Sắp xếp lại các cuộc thi"

#: templates/contest/reorder.html
msgid "Contest year"
msgstr "Năm tổ chức"

#: templates/contest/reorder.html
msgid "Contest tag"
msgstr "Thẻ kỳ thi"

#: templates/contest/reorder.html
msgid "Clear filters"
msgstr "Xoá bộ lọc"

#: templates/contest/reorder.html
msgid "Save order"
msgstr "Lưu thứ tự này"

#: templates/contest/reorder.html
msgid "No contests"
msgstr "Không có kỳ thi"

#: templates/contest/reorder.html
msgid "Order saved"
msgstr "Đã cập nhật thứ tự này"

#: templates/contest/reorder.html
msgid "Error while saving order"
msgstr "Lỗi khi cập nhật thứ tự mới"

#: templates/contest/reorder.html
msgid "Your changes may not have been saved yet. Do you want to leave the page"
msgstr "Các thay đổi của bạn có thể chưa được lưu. Bạn có muốn rời khỏi trang không"
2 changes: 2 additions & 0 deletions resources/Sortable.min.js

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions resources/contest.scss
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,79 @@ form.contest-join-pseudotab {
width: 260px !important;
}
}

form.contest-filter-form {
display: grid;
row-gap: 7px;
width: max-content;
margin: 8px 0px 16px 0px;
background: transparent;
}

form.contest-filter-form .contest-filter-row {
display: grid;
grid-template-columns: 105px auto;
align-items: center;
column-gap: 12px;
}

form.contest-filter-form .contest-filter-row label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
line-height: 1;
white-space: nowrap;
}

form.contest-filter-form .contest-filter-row label i {
font-size: 14px;
}

form.contest-filter-form .contest-filter-row select {
appearance: auto;
-webkit-appearance: auto;
-moz-appearance: auto;

min-width: 92px;
padding: 4px 8px;
border: 1px solid #b8b8b8;
border-radius: 4px;
background: #fff;
color: #111;
font-size: 14px;
outline: none;
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

form.contest-filter-form .contest-filter-row select:hover {
border-color: #7a7a7a;
}

form.contest-filter-form .contest-filter-row select:focus {
border-color: #1a73e8;
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.12);
}

form.contest-filter-form .contest-filter-row .button {
justify-self: start;
}

@media (max-width: 640px) {
form.contest-filter-row {
grid-template-columns: 1fr;
row-gap: 4px;
}

form.contest-filter-form .contest-filter-row label {
font-size: 13px;
}

form.contest-filter-form .contest-filter-row select {
min-width: 84px;
font-size: 13px;
padding: 4px 7px;
}
}
1 change: 1 addition & 0 deletions templates/contest/contest-list-tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
{{ make_tab('list', 'fa-list', url('contest_list'), _('List')) }}
{{ make_tab('calendar', 'fa-calendar', url('contest_calendar', now.year, now.month), _('Calendar')) }}
{% if request.user.is_staff and (perms.judge.edit_all_contest or perms.judge.edit_own_contest) %}
{{ make_tab('reorder', 'fa-sort', url('contest_reorder'), _('Reorder contests')) }}
{{ make_tab('admin', 'fa-edit', url('admin:judge_contest_changelist'), _('Admin')) }}
{% endif %}
{% endblock %}
Loading
Loading