Merge pull request #17 from LQDJudge/master

Master -> VKU
This commit is contained in:
Phuoc Dinh Le 2022-03-19 06:17:53 -05:00 committed by GitHub
commit 7c2c6fea70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 724 additions and 124 deletions

View file

@ -438,9 +438,12 @@ def get_unread_boxes(request):
if (request.method != 'GET'): if (request.method != 'GET'):
return HttpResponseBadRequest() return HttpResponseBadRequest()
ignored_users = Ignore.get_ignored_users(request.profile)
mess = Message.objects.filter(room=OuterRef('room'), mess = Message.objects.filter(room=OuterRef('room'),
time__gte=OuterRef('last_seen'))\ time__gte=OuterRef('last_seen'))\
.exclude(author=request.profile)\ .exclude(author=request.profile)\
.exclude(author__in=ignored_users)\
.order_by().values('room')\ .order_by().values('room')\
.annotate(unread_count=Count('pk')).values('unread_count') .annotate(unread_count=Count('pk')).values('unread_count')

View file

@ -162,6 +162,7 @@ else:
'children': [ 'children': [
'judge.ProblemGroup', 'judge.ProblemGroup',
'judge.ProblemType', 'judge.ProblemType',
'judge.ProblemPointsVote',
], ],
}, },
{ {

View file

@ -139,6 +139,8 @@ urlpatterns = [
url(r'^/tickets$', ticket.ProblemTicketListView.as_view(), name='problem_ticket_list'), url(r'^/tickets$', ticket.ProblemTicketListView.as_view(), name='problem_ticket_list'),
url(r'^/tickets/new$', ticket.NewProblemTicketView.as_view(), name='new_problem_ticket'), url(r'^/tickets/new$', ticket.NewProblemTicketView.as_view(), name='new_problem_ticket'),
url(r'^/vote$', problem.Vote.as_view(), name='vote'),
url(r'^/manage/submission', include([ url(r'^/manage/submission', include([
url('^$', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'), url('^$', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
url('^/action$', problem_manage.ActionSubmissionsView.as_view(), name='problem_submissions_action'), url('^/action$', problem_manage.ActionSubmissionsView.as_view(), name='problem_submissions_action'),

View file

@ -5,7 +5,7 @@ from judge.admin.comments import CommentAdmin
from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin
from judge.admin.interface import BlogPostAdmin, LicenseAdmin, LogEntryAdmin, NavigationBarAdmin from judge.admin.interface import BlogPostAdmin, LicenseAdmin, LogEntryAdmin, NavigationBarAdmin
from judge.admin.organization import OrganizationAdmin, OrganizationRequestAdmin from judge.admin.organization import OrganizationAdmin, OrganizationRequestAdmin
from judge.admin.problem import ProblemAdmin from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin
from judge.admin.profile import ProfileAdmin from judge.admin.profile import ProfileAdmin
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
@ -13,7 +13,7 @@ from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
from judge.admin.ticket import TicketAdmin from judge.admin.ticket import TicketAdmin
from judge.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \ from judge.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \
ContestTag, Judge, Language, License, MiscConfig, NavigationBar, Organization, \ ContestTag, Judge, Language, License, MiscConfig, NavigationBar, Organization, \
OrganizationRequest, Problem, ProblemGroup, ProblemType, Profile, Submission, Ticket OrganizationRequest, Problem, ProblemGroup, ProblemPointsVote, ProblemType, Profile, Submission, Ticket
admin.site.register(BlogPost, BlogPostAdmin) admin.site.register(BlogPost, BlogPostAdmin)
admin.site.register(Comment, CommentAdmin) admin.site.register(Comment, CommentAdmin)
@ -31,6 +31,7 @@ admin.site.register(Organization, OrganizationAdmin)
admin.site.register(OrganizationRequest, OrganizationRequestAdmin) admin.site.register(OrganizationRequest, OrganizationRequestAdmin)
admin.site.register(Problem, ProblemAdmin) admin.site.register(Problem, ProblemAdmin)
admin.site.register(ProblemGroup, ProblemGroupAdmin) admin.site.register(ProblemGroup, ProblemGroupAdmin)
admin.site.register(ProblemPointsVote, ProblemPointsVoteAdmin)
admin.site.register(ProblemType, ProblemTypeAdmin) admin.site.register(ProblemType, ProblemTypeAdmin)
admin.site.register(Profile, ProfileAdmin) admin.site.register(Profile, ProfileAdmin)
admin.site.register(Submission, SubmissionAdmin) admin.site.register(Submission, SubmissionAdmin)

View file

@ -236,3 +236,33 @@ class ProblemAdmin(VersionAdmin):
if form.cleaned_data.get('change_message'): if form.cleaned_data.get('change_message'):
return form.cleaned_data['change_message'] return form.cleaned_data['change_message']
return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs) return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs)
class ProblemPointsVoteAdmin(admin.ModelAdmin):
list_display = ('vote_points', 'voter', 'problem_name', 'problem_code', 'problem_points', 'vote_time')
search_fields = ('voter__user__username', 'problem__code', 'problem__name')
readonly_fields = ('voter', 'problem', 'problem_code', 'problem_points', 'vote_time')
def has_change_permission(self, request, obj=None):
if obj is None:
return request.user.has_perm('judge.edit_own_problem')
return obj.problem.is_editable_by(request.user)
def problem_code(self, obj):
return obj.problem.code
problem_code.short_description = _('Problem code')
problem_code.admin_order_field = 'problem__code'
def problem_points(self, obj):
return obj.problem.points
problem_points.short_description = _('Points')
problem_points.admin_order_field = 'problem__points'
def problem_name(self, obj):
return obj.problem.name
problem_name.short_description = _('Problem name')
problem_name.admin_order_field = 'problem__name'
def vote_points(self, obj):
return obj.points
vote_points.short_description = _('Vote')

View file

@ -45,7 +45,7 @@ class TimezoneFilter(admin.SimpleListFilter):
class ProfileAdmin(VersionAdmin): class ProfileAdmin(VersionAdmin):
fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme', fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme',
'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'notes', 'is_totp_enabled', 'user_script', 'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'is_banned_problem_voting', 'notes', 'is_totp_enabled', 'user_script',
'current_contest') 'current_contest')
readonly_fields = ('user',) readonly_fields = ('user',)
list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full', list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full',

View file

@ -12,7 +12,7 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_ace import AceWidget from django_ace import AceWidget
from judge.models import Contest, Language, Organization, PrivateMessage, Problem, Profile, Submission from judge.models import Contest, Language, Organization, PrivateMessage, Problem, ProblemPointsVote, Profile, Submission
from judge.utils.subscription import newsletter_id from judge.utils.subscription import newsletter_id
from judge.widgets import HeavyPreviewPageDownWidget, MathJaxPagedownWidget, PagedownWidget, Select2MultipleWidget, \ from judge.widgets import HeavyPreviewPageDownWidget, MathJaxPagedownWidget, PagedownWidget, Select2MultipleWidget, \
Select2Widget Select2Widget
@ -161,4 +161,10 @@ class ContestCloneForm(Form):
key = self.cleaned_data['key'] key = self.cleaned_data['key']
if Contest.objects.filter(key=key).exists(): if Contest.objects.filter(key=key).exists():
raise ValidationError(_('Contest with key already exists.')) raise ValidationError(_('Contest with key already exists.'))
return key return key
class ProblemPointsVoteForm(ModelForm):
class Meta:
model = ProblemPointsVote
fields = ['points']

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.25 on 2022-03-05 22:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('judge', '0118_rating'),
]
operations = [
migrations.AlterField(
model_name='contest',
name='hide_problem_tags',
field=models.BooleanField(default=True, help_text='Whether problem tags should be hidden by default.', verbose_name='hide problem tags'),
),
]

View file

@ -0,0 +1,34 @@
# Generated by Django 2.2.25 on 2022-03-06 04:24
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('judge', '0119_auto_20220306_0512'),
]
operations = [
migrations.AddField(
model_name='profile',
name='is_banned_problem_voting',
field=models.BooleanField(default=False, help_text="User will not be able to vote on problems' point values.", verbose_name='banned from voting'),
),
migrations.CreateModel(
name='ProblemPointsVote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('points', models.IntegerField(help_text='The amount of points you think this problem deserves.', validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(600)], verbose_name='proposed point value')),
('vote_time', models.DateTimeField(auto_now_add=True, verbose_name='The time this vote was cast')),
('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='problem_points_votes', to='judge.Problem')),
('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='problem_points_votes', to='judge.Profile')),
],
options={
'verbose_name': 'vote',
'verbose_name_plural': 'votes',
},
),
]

View file

@ -7,7 +7,7 @@ from judge.models.contest import Contest, ContestMoss, ContestParticipation, Con
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
from judge.models.problem import LanguageLimit, License, Problem, ProblemClarification, ProblemGroup, \ from judge.models.problem import LanguageLimit, License, Problem, ProblemClarification, ProblemGroup, \
ProblemTranslation, ProblemType, Solution, TranslatedProblemForeignKeyQuerySet, TranslatedProblemQuerySet ProblemTranslation, ProblemType, Solution, TranslatedProblemForeignKeyQuerySet, TranslatedProblemQuerySet, ProblemPointsVote
from judge.models.problem_data import CHECKERS, ProblemData, ProblemTestCase, problem_data_storage, \ from judge.models.problem_data import CHECKERS, ProblemData, ProblemTestCase, problem_data_storage, \
problem_directory_file problem_directory_file
from judge.models.profile import Organization, OrganizationRequest, Profile, Friend from judge.models.profile import Organization, OrganizationRequest, Profile, Friend

View file

@ -101,7 +101,7 @@ class Contest(models.Model):
related_name='private_contestants+') related_name='private_contestants+')
hide_problem_tags = models.BooleanField(verbose_name=_('hide problem tags'), hide_problem_tags = models.BooleanField(verbose_name=_('hide problem tags'),
help_text=_('Whether problem tags should be hidden by default.'), help_text=_('Whether problem tags should be hidden by default.'),
default=False) default=True)
run_pretests_only = models.BooleanField(verbose_name=_('run pretests only'), run_pretests_only = models.BooleanField(verbose_name=_('run pretests only'),
help_text=_('Whether judges should grade pretests only, versus all ' help_text=_('Whether judges should grade pretests only, versus all '
'testcases. Commonly set during a contest, then unset ' 'testcases. Commonly set during a contest, then unset '

View file

@ -375,6 +375,26 @@ class Problem(models.Model):
save.alters_data = True save.alters_data = True
def can_vote(self, request):
user = request.user
if not user.is_authenticated:
return False
# If the user is in contest, nothing should be shown.
if request.in_contest_mode:
return False
# If the user is not allowed to vote
if user.profile.is_unlisted or user.profile.is_banned_problem_voting:
return False
# If the user is banned from submitting to the problem.
if self.banned_users.filter(pk=user.pk).exists():
return False
# If the user has a full AC submission to the problem (solved the problem).
return self.submission_set.filter(user=user.profile, result='AC', points=F('problem__points')).exists()
class Meta: class Meta:
permissions = ( permissions = (
('see_private_problem', 'See hidden problems'), ('see_private_problem', 'See hidden problems'),
@ -448,3 +468,29 @@ class Solution(models.Model):
) )
verbose_name = _('solution') verbose_name = _('solution')
verbose_name_plural = _('solutions') verbose_name_plural = _('solutions')
class ProblemPointsVote(models.Model):
points = models.IntegerField(
verbose_name=_('proposed point value'),
help_text=_('The amount of points you think this problem deserves.'),
validators=[
MinValueValidator(100),
MaxValueValidator(600),
],
)
voter = models.ForeignKey(Profile, related_name='problem_points_votes', on_delete=CASCADE, db_index=True)
problem = models.ForeignKey(Problem, related_name='problem_points_votes', on_delete=CASCADE, db_index=True)
vote_time = models.DateTimeField(
verbose_name=_('The time this vote was cast'),
auto_now_add=True,
blank=True,
)
class Meta:
verbose_name = _('vote')
verbose_name_plural = _('votes')
def __str__(self):
return f'{self.voter}: {self.points} for {self.problem.code}'

View file

@ -98,6 +98,11 @@ class Profile(models.Model):
default=False) default=False)
is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'), is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'),
default=False) default=False)
is_banned_problem_voting = models.BooleanField(
verbose_name=_('banned from voting'),
help_text=_("User will not be able to vote on problems' point values."),
default=False,
)
rating = models.IntegerField(null=True, default=None) rating = models.IntegerField(null=True, default=None)
user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536, user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536,
help_text=_('User-defined JavaScript for site customization.')) help_text=_('User-defined JavaScript for site customization.'))

View file

@ -12,7 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import Count, F, Prefetch, Q, Sum, Case, When, IntegerField from django.db.models import Count, F, Prefetch, Q, Sum, Case, When, IntegerField
from django.db.utils import ProgrammingError from django.db.utils import ProgrammingError
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.template.loader import get_template from django.template.loader import get_template
from django.urls import reverse from django.urls import reverse
@ -26,10 +26,10 @@ from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from judge.comments import CommentedDetailView from judge.comments import CommentedDetailView
from judge.forms import ProblemCloneForm, ProblemSubmitForm from judge.forms import ProblemCloneForm, ProblemSubmitForm, ProblemPointsVoteForm
from judge.models import ContestProblem, ContestSubmission, Judge, Language, Problem, ProblemClarification, \ from judge.models import ContestProblem, ContestSubmission, Judge, Language, Problem, ProblemClarification, \
ProblemGroup, ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource, \ ProblemGroup, ProblemTranslation, ProblemType, ProblemPointsVote, RuntimeVersion, Solution, Submission, SubmissionSource, \
TranslatedProblemForeignKeyQuerySet, Organization TranslatedProblemForeignKeyQuerySet, Organization
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
from judge.utils.diggpaginator import DiggPaginator from judge.utils.diggpaginator import DiggPaginator
from judge.utils.opengraph import generate_opengraph from judge.utils.opengraph import generate_opengraph
@ -216,8 +216,62 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView):
context['description'], 'problem') context['description'], 'problem')
context['meta_description'] = self.object.summary or metadata[0] context['meta_description'] = self.object.summary or metadata[0]
context['og_image'] = self.object.og_image or metadata[1] context['og_image'] = self.object.og_image or metadata[1]
context['can_vote'] = self.object.can_vote(self.request)
if context['can_vote']:
try:
context['vote'] = ProblemPointsVote.objects.get(voter=user.profile, problem=self.object)
except ObjectDoesNotExist:
context['vote'] = None
else:
context['vote'] = None
context['has_votes'] = False
if user.is_superuser:
all_votes = list(self.object.problem_points_votes.order_by('points').values_list('points', flat=True))
context['all_votes'] = all_votes
context['has_votes'] = len(all_votes) > 0
context['max_possible_vote'] = 600
context['min_possible_vote'] = 100
return context return context
class DeleteVote(ProblemMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
return HttpResponseForbidden(status=405, content_type='text/plain')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not request.user.is_authenticated:
return HttpResponseForbidden('Not signed in.', content_type='text/plain')
elif self.object.can_vote(request.user):
ProblemPointsVote.objects.filter(voter=request.profile, problem=self.object).delete()
return HttpResponse('success', content_type='text/plain')
else:
return HttpResponseForbidden('Not allowed to delete votes on this problem.', content_type='text/plain')
class Vote(ProblemMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
return HttpResponseForbidden(status=405, content_type='text/plain')
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.can_vote(request): # Not allowed to vote for some reason.
return HttpResponseForbidden('Not allowed to vote on this problem.', content_type='text/plain')
form = ProblemPointsVoteForm(request.POST)
if form.is_valid():
with transaction.atomic():
# Delete any pre existing votes.
ProblemPointsVote.objects.filter(voter=request.profile, problem=self.object).delete()
vote = form.save(commit=False)
vote.voter = request.profile
vote.problem = self.object
vote.save()
return JsonResponse({'points': vote.points})
else:
return JsonResponse(form.errors, status=400)
class ProblemComments(ProblemMixin, TitleMixin, CommentedDetailView): class ProblemComments(ProblemMixin, TitleMixin, CommentedDetailView):
context_object_name = 'problem' context_object_name = 'problem'

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: lqdoj2\n" "Project-Id-Version: lqdoj2\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-10 18:14+0700\n" "POT-Creation-Date: 2022-03-10 12:30+0700\n"
"PO-Revision-Date: 2021-07-20 03:44\n" "PO-Revision-Date: 2021-07-20 03:44\n"
"Last-Translator: Icyene\n" "Last-Translator: Icyene\n"
"Language-Team: Vietnamese\n" "Language-Team: Vietnamese\n"
@ -20,7 +20,7 @@ msgstr ""
#: chat_box/models.py:23 chat_box/models.py:41 chat_box/models.py:47 #: chat_box/models.py:23 chat_box/models.py:41 chat_box/models.py:47
#: judge/admin/interface.py:110 judge/models/contest.py:403 #: judge/admin/interface.py:110 judge/models/contest.py:403
#: judge/models/contest.py:528 judge/models/profile.py:215 #: judge/models/contest.py:528 judge/models/profile.py:220
msgid "user" msgid "user"
msgstr "người dùng" msgstr "người dùng"
@ -36,71 +36,71 @@ msgstr "nội dung bình luận"
msgid "last seen" msgid "last seen"
msgstr "xem lần cuối" msgstr "xem lần cuối"
#: chat_box/views.py:29 templates/chat/chat.html:4 templates/chat/chat.html:541 #: chat_box/views.py:29 templates/chat/chat.html:4 templates/chat/chat.html:548
msgid "Chat Box" msgid "Chat Box"
msgstr "Chat Box" msgstr "Chat Box"
#: dmoj/settings.py:358 #: dmoj/settings.py:359
msgid "German" msgid "German"
msgstr "" msgstr ""
#: dmoj/settings.py:359 #: dmoj/settings.py:360
msgid "English" msgid "English"
msgstr "" msgstr ""
#: dmoj/settings.py:360 #: dmoj/settings.py:361
msgid "Spanish" msgid "Spanish"
msgstr "" msgstr ""
#: dmoj/settings.py:361 #: dmoj/settings.py:362
msgid "French" msgid "French"
msgstr "" msgstr ""
#: dmoj/settings.py:362 #: dmoj/settings.py:363
msgid "Croatian" msgid "Croatian"
msgstr "" msgstr ""
#: dmoj/settings.py:363 #: dmoj/settings.py:364
msgid "Hungarian" msgid "Hungarian"
msgstr "" msgstr ""
#: dmoj/settings.py:364 #: dmoj/settings.py:365
msgid "Japanese" msgid "Japanese"
msgstr "" msgstr ""
#: dmoj/settings.py:365 #: dmoj/settings.py:366
msgid "Korean" msgid "Korean"
msgstr "" msgstr ""
#: dmoj/settings.py:366 #: dmoj/settings.py:367
msgid "Brazilian Portuguese" msgid "Brazilian Portuguese"
msgstr "" msgstr ""
#: dmoj/settings.py:367 #: dmoj/settings.py:368
msgid "Romanian" msgid "Romanian"
msgstr "" msgstr ""
#: dmoj/settings.py:368 #: dmoj/settings.py:369
msgid "Russian" msgid "Russian"
msgstr "" msgstr ""
#: dmoj/settings.py:369 #: dmoj/settings.py:370
msgid "Serbian (Latin)" msgid "Serbian (Latin)"
msgstr "" msgstr ""
#: dmoj/settings.py:370 #: dmoj/settings.py:371
msgid "Turkish" msgid "Turkish"
msgstr "" msgstr ""
#: dmoj/settings.py:371 #: dmoj/settings.py:372
msgid "Vietnamese" msgid "Vietnamese"
msgstr "Tiếng Việt" msgstr "Tiếng Việt"
#: dmoj/settings.py:372 #: dmoj/settings.py:373
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "" msgstr ""
#: dmoj/settings.py:373 #: dmoj/settings.py:374
msgid "Traditional Chinese" msgid "Traditional Chinese"
msgstr "" msgstr ""
@ -108,7 +108,7 @@ msgstr ""
msgid "Login" msgid "Login"
msgstr "Đăng nhập" msgstr "Đăng nhập"
#: dmoj/urls.py:106 templates/base.html:249 #: dmoj/urls.py:106 templates/base.html:251
msgid "Home" msgid "Home"
msgstr "Trang chủ" msgstr "Trang chủ"
@ -217,7 +217,7 @@ msgstr "Tính toán lại kết quả"
msgid "username" msgid "username"
msgstr "tên đăng nhập" msgstr "tên đăng nhập"
#: judge/admin/contest.py:320 templates/base.html:337 #: judge/admin/contest.py:320 templates/base.html:339
msgid "virtual" msgid "virtual"
msgstr "ảo" msgstr "ảo"
@ -587,9 +587,9 @@ msgstr "người bình luận"
msgid "associated page" msgid "associated page"
msgstr "trang tương ứng" msgstr "trang tương ứng"
#: judge/models/comment.py:46 #: judge/models/comment.py:46 judge/models/problem.py:492
msgid "votes" msgid "votes"
msgstr "" msgstr "bình chọn"
#: judge/models/comment.py:48 #: judge/models/comment.py:48
msgid "hide the comment" msgid "hide the comment"
@ -607,7 +607,7 @@ msgstr "bình luận"
msgid "comments" msgid "comments"
msgstr "" msgstr ""
#: judge/models/comment.py:137 judge/models/problem.py:443 #: judge/models/comment.py:137 judge/models/problem.py:462
#, python-format #, python-format
msgid "Editorial for %s" msgid "Editorial for %s"
msgstr "" msgstr ""
@ -725,7 +725,7 @@ msgstr ""
msgid "description" msgid "description"
msgstr "mô tả" msgstr "mô tả"
#: judge/models/contest.py:72 judge/models/problem.py:390 #: judge/models/contest.py:72 judge/models/problem.py:409
#: judge/models/runtime.py:138 #: judge/models/runtime.py:138
msgid "problems" msgid "problems"
msgstr "bài tập" msgstr "bài tập"
@ -739,7 +739,7 @@ msgid "end time"
msgstr "thời gian kết thúc" msgstr "thời gian kết thúc"
#: judge/models/contest.py:75 judge/models/problem.py:118 #: judge/models/contest.py:75 judge/models/problem.py:118
#: judge/models/problem.py:414 #: judge/models/problem.py:433
msgid "time limit" msgid "time limit"
msgstr "giới hạn thời gian" msgstr "giới hạn thời gian"
@ -1037,8 +1037,8 @@ msgid "contest participations"
msgstr "lần tham gia kỳ thi" msgstr "lần tham gia kỳ thi"
#: judge/models/contest.py:491 judge/models/contest.py:513 #: judge/models/contest.py:491 judge/models/contest.py:513
#: judge/models/contest.py:554 judge/models/problem.py:389 #: judge/models/contest.py:554 judge/models/problem.py:408
#: judge/models/problem.py:394 judge/models/problem.py:412 #: judge/models/problem.py:413 judge/models/problem.py:431
#: judge/models/problem_data.py:40 #: judge/models/problem_data.py:40
msgid "problem" msgid "problem"
msgstr "bài tập" msgstr "bài tập"
@ -1176,7 +1176,7 @@ msgstr "mục cha"
msgid "post title" msgid "post title"
msgstr "tiêu đề bài đăng" msgstr "tiêu đề bài đăng"
#: judge/models/interface.py:67 judge/models/problem.py:432 #: judge/models/interface.py:67 judge/models/problem.py:451
msgid "authors" msgid "authors"
msgstr "tác giả" msgstr "tác giả"
@ -1184,7 +1184,7 @@ msgstr "tác giả"
msgid "slug" msgid "slug"
msgstr "slug" msgstr "slug"
#: judge/models/interface.py:69 judge/models/problem.py:430 #: judge/models/interface.py:69 judge/models/problem.py:449
msgid "public visibility" msgid "public visibility"
msgstr "khả năng hiển thị công khai" msgstr "khả năng hiển thị công khai"
@ -1380,7 +1380,7 @@ msgid ""
"are supported." "are supported."
msgstr "" msgstr ""
#: judge/models/problem.py:123 judge/models/problem.py:417 #: judge/models/problem.py:123 judge/models/problem.py:436
msgid "memory limit" msgid "memory limit"
msgstr "" msgstr ""
@ -1453,67 +1453,85 @@ msgstr ""
msgid "If private, only these organizations may see the problem." msgid "If private, only these organizations may see the problem."
msgstr "" msgstr ""
#: judge/models/problem.py:395 judge/models/problem.py:413 #: judge/models/problem.py:414 judge/models/problem.py:432
#: judge/models/runtime.py:111 #: judge/models/runtime.py:111
msgid "language" msgid "language"
msgstr "" msgstr ""
#: judge/models/problem.py:396 #: judge/models/problem.py:415
msgid "translated name" msgid "translated name"
msgstr "" msgstr ""
#: judge/models/problem.py:397 #: judge/models/problem.py:416
msgid "translated description" msgid "translated description"
msgstr "" msgstr ""
#: judge/models/problem.py:401 #: judge/models/problem.py:420
msgid "problem translation" msgid "problem translation"
msgstr "" msgstr ""
#: judge/models/problem.py:402 #: judge/models/problem.py:421
msgid "problem translations" msgid "problem translations"
msgstr "" msgstr ""
#: judge/models/problem.py:406 #: judge/models/problem.py:425
msgid "clarified problem" msgid "clarified problem"
msgstr "" msgstr ""
#: judge/models/problem.py:407 #: judge/models/problem.py:426
msgid "clarification body" msgid "clarification body"
msgstr "" msgstr ""
#: judge/models/problem.py:408 #: judge/models/problem.py:427
msgid "clarification timestamp" msgid "clarification timestamp"
msgstr "" msgstr ""
#: judge/models/problem.py:423 #: judge/models/problem.py:442
msgid "language-specific resource limit" msgid "language-specific resource limit"
msgstr "" msgstr ""
#: judge/models/problem.py:424 #: judge/models/problem.py:443
msgid "language-specific resource limits" msgid "language-specific resource limits"
msgstr "" msgstr ""
#: judge/models/problem.py:428 #: judge/models/problem.py:447
msgid "associated problem" msgid "associated problem"
msgstr "" msgstr ""
#: judge/models/problem.py:431 #: judge/models/problem.py:450
msgid "publish date" msgid "publish date"
msgstr "" msgstr ""
#: judge/models/problem.py:433 #: judge/models/problem.py:452
msgid "editorial content" msgid "editorial content"
msgstr "nội dung lời giải" msgstr "nội dung lời giải"
#: judge/models/problem.py:449 #: judge/models/problem.py:468
msgid "solution" msgid "solution"
msgstr "lời giải" msgstr "lời giải"
#: judge/models/problem.py:450 #: judge/models/problem.py:469
msgid "solutions" msgid "solutions"
msgstr "lời giải" msgstr "lời giải"
#: judge/models/problem.py:474
#, fuzzy
#| msgid "point value"
msgid "proposed point value"
msgstr "điểm"
#: judge/models/problem.py:475
msgid "The amount of points you think this problem deserves."
msgstr ""
#: judge/models/problem.py:485
msgid "The time this vote was cast"
msgstr ""
#: judge/models/problem.py:491
msgid "vote"
msgstr ""
#: judge/models/problem_data.py:26 #: judge/models/problem_data.py:26
msgid "Standard" msgid "Standard"
msgstr "Tiêu chuẩn" msgstr "Tiêu chuẩn"
@ -1707,7 +1725,7 @@ msgid ""
msgstr "Ảnh này sẽ thay thế logo mặc định khi ở trong tổ chức." msgstr "Ảnh này sẽ thay thế logo mặc định khi ở trong tổ chức."
#: judge/models/profile.py:76 judge/models/profile.py:93 #: judge/models/profile.py:76 judge/models/profile.py:93
#: judge/models/profile.py:216 #: judge/models/profile.py:221
msgid "organization" msgid "organization"
msgstr "" msgstr ""
@ -1756,78 +1774,88 @@ msgid "User will not be ranked."
msgstr "" msgstr ""
#: judge/models/profile.py:102 #: judge/models/profile.py:102
#, fuzzy
#| msgid "Banned from joining"
msgid "banned from voting"
msgstr "Bị cấm tham gia"
#: judge/models/profile.py:103
msgid "User will not be able to vote on problems' point values."
msgstr ""
#: judge/models/profile.py:107
msgid "user script" msgid "user script"
msgstr "" msgstr ""
#: judge/models/profile.py:103 #: judge/models/profile.py:108
msgid "User-defined JavaScript for site customization." msgid "User-defined JavaScript for site customization."
msgstr "" msgstr ""
#: judge/models/profile.py:104 #: judge/models/profile.py:109
msgid "current contest" msgid "current contest"
msgstr "kỳ thi hiện tại" msgstr "kỳ thi hiện tại"
#: judge/models/profile.py:106 #: judge/models/profile.py:111
msgid "math engine" msgid "math engine"
msgstr "" msgstr ""
#: judge/models/profile.py:108 #: judge/models/profile.py:113
msgid "the rendering engine used to render math" msgid "the rendering engine used to render math"
msgstr "" msgstr ""
#: judge/models/profile.py:109 #: judge/models/profile.py:114
msgid "2FA enabled" msgid "2FA enabled"
msgstr "" msgstr ""
#: judge/models/profile.py:110 #: judge/models/profile.py:115
msgid "check to enable TOTP-based two factor authentication" msgid "check to enable TOTP-based two factor authentication"
msgstr "đánh dấu để sử dụng TOTP-based two factor authentication" msgstr "đánh dấu để sử dụng TOTP-based two factor authentication"
#: judge/models/profile.py:111 #: judge/models/profile.py:116
msgid "TOTP key" msgid "TOTP key"
msgstr "mã TOTP" msgstr "mã TOTP"
#: judge/models/profile.py:112 #: judge/models/profile.py:117
msgid "32 character base32-encoded key for TOTP" msgid "32 character base32-encoded key for TOTP"
msgstr "" msgstr ""
#: judge/models/profile.py:114 #: judge/models/profile.py:119
msgid "TOTP key must be empty or base32" msgid "TOTP key must be empty or base32"
msgstr "" msgstr ""
#: judge/models/profile.py:115 #: judge/models/profile.py:120
msgid "internal notes" msgid "internal notes"
msgstr "ghi chú nội bộ" msgstr "ghi chú nội bộ"
#: judge/models/profile.py:116 #: judge/models/profile.py:121
msgid "Notes for administrators regarding this user." msgid "Notes for administrators regarding this user."
msgstr "Ghi chú riêng cho quản trị viên." msgstr "Ghi chú riêng cho quản trị viên."
#: judge/models/profile.py:210 #: judge/models/profile.py:215
msgid "user profile" msgid "user profile"
msgstr "thông tin người dùng" msgstr "thông tin người dùng"
#: judge/models/profile.py:211 #: judge/models/profile.py:216
msgid "user profiles" msgid "user profiles"
msgstr "thông tin người dùng" msgstr "thông tin người dùng"
#: judge/models/profile.py:218 #: judge/models/profile.py:223
msgid "request time" msgid "request time"
msgstr "thời gian đăng ký" msgstr "thời gian đăng ký"
#: judge/models/profile.py:219 #: judge/models/profile.py:224
msgid "state" msgid "state"
msgstr "trạng thái" msgstr "trạng thái"
#: judge/models/profile.py:224 #: judge/models/profile.py:229
msgid "reason" msgid "reason"
msgstr "lý do" msgstr "lý do"
#: judge/models/profile.py:227 #: judge/models/profile.py:232
msgid "organization join request" msgid "organization join request"
msgstr "đơn đăng ký tham gia" msgstr "đơn đăng ký tham gia"
#: judge/models/profile.py:228 #: judge/models/profile.py:233
msgid "organization join requests" msgid "organization join requests"
msgstr "đơn đăng ký tham gia" msgstr "đơn đăng ký tham gia"
@ -2666,46 +2694,46 @@ msgstr "Hướng dẫn cho {0}"
msgid "Editorial for <a href=\"{1}\">{0}</a>" msgid "Editorial for <a href=\"{1}\">{0}</a>"
msgstr "Hướng dẫn cho <a href=\"{1}\">{0}</a>" msgstr "Hướng dẫn cho <a href=\"{1}\">{0}</a>"
#: judge/views/problem.py:227 #: judge/views/problem.py:284
#, python-brace-format #, python-brace-format
msgid "Disscuss {0}" msgid "Disscuss {0}"
msgstr "" msgstr ""
#: judge/views/problem.py:230 #: judge/views/problem.py:287
#, python-brace-format #, python-brace-format
msgid "Discuss <a href=\"{1}\">{0}</a>" msgid "Discuss <a href=\"{1}\">{0}</a>"
msgstr "Thảo luận <a href=\"{1}\">{0}</a>" msgstr "Thảo luận <a href=\"{1}\">{0}</a>"
#: judge/views/problem.py:298 templates/contest/contest.html:79 #: judge/views/problem.py:355 templates/contest/contest.html:79
#: templates/user/user-about.html:28 templates/user/user-tabs.html:5 #: templates/user/user-about.html:28 templates/user/user-tabs.html:5
#: templates/user/users-table.html:29 #: templates/user/users-table.html:29
msgid "Problems" msgid "Problems"
msgstr "Bài tập" msgstr "Bài tập"
#: judge/views/problem.py:598 #: judge/views/problem.py:655
msgid "Banned from submitting" msgid "Banned from submitting"
msgstr "Bị cấm nộp bài" msgstr "Bị cấm nộp bài"
#: judge/views/problem.py:599 #: judge/views/problem.py:656
msgid "" msgid ""
"You have been declared persona non grata for this problem. You are " "You have been declared persona non grata for this problem. You are "
"permanently barred from submitting this problem." "permanently barred from submitting this problem."
msgstr "Bạn đã bị cấm nộp bài này." msgstr "Bạn đã bị cấm nộp bài này."
#: judge/views/problem.py:613 #: judge/views/problem.py:670
msgid "Too many submissions" msgid "Too many submissions"
msgstr "Quá nhiều lần nộp" msgstr "Quá nhiều lần nộp"
#: judge/views/problem.py:614 #: judge/views/problem.py:671
msgid "You have exceeded the submission limit for this problem." msgid "You have exceeded the submission limit for this problem."
msgstr "Bạn đã vượt quá số lần nộp cho bài này." msgstr "Bạn đã vượt quá số lần nộp cho bài này."
#: judge/views/problem.py:674 judge/views/problem.py:677 #: judge/views/problem.py:731 judge/views/problem.py:734
#, python-format #, python-format
msgid "Submit to %(problem)s" msgid "Submit to %(problem)s"
msgstr "Nộp bài cho %(problem)s" msgstr "Nộp bài cho %(problem)s"
#: judge/views/problem.py:692 #: judge/views/problem.py:749
msgid "Clone Problem" msgid "Clone Problem"
msgstr "Nhân bản bài tập" msgstr "Nhân bản bài tập"
@ -2992,7 +3020,7 @@ msgid "Updated on site"
msgstr "Được cập nhật trên web" msgstr "Được cập nhật trên web"
#: judge/views/user.py:326 templates/admin/auth/user/change_form.html:14 #: judge/views/user.py:326 templates/admin/auth/user/change_form.html:14
#: templates/admin/auth/user/change_form.html:17 templates/base.html:297 #: templates/admin/auth/user/change_form.html:17 templates/base.html:299
#: templates/user/user-tabs.html:10 #: templates/user/user-tabs.html:10
msgid "Edit profile" msgid "Edit profile"
msgstr "Chỉnh sửa thông tin" msgstr "Chỉnh sửa thông tin"
@ -3177,15 +3205,22 @@ msgstr "Ngắt kết nối"
msgid "Terminate" msgid "Terminate"
msgstr "Dừng" msgstr "Dừng"
#: templates/admin/judge/problem/change_form.html:14 #: templates/admin/judge/problem/change_form.html:15
msgid "View Submissions" msgid "View Submissions"
msgstr "Xem Bài Nộp" msgstr "Xem Bài Nộp"
#: templates/admin/judge/problem/change_form.html:17 #: templates/admin/judge/problem/change_form.html:18
#: templates/user/user-base.html:112 #: templates/user/user-base.html:112
msgid "View submissions" msgid "View submissions"
msgstr "Xem bài nộp" msgstr "Xem bài nộp"
#: templates/admin/judge/problem/change_form.html:19
#: templates/admin/judge/problem/change_form.html:22
#, fuzzy
#| msgid "View on site"
msgid "View votes"
msgstr "Xem trên trang"
#: templates/admin/judge/profile/change_form.html:14 #: templates/admin/judge/profile/change_form.html:14
#: templates/admin/judge/profile/change_form.html:17 #: templates/admin/judge/profile/change_form.html:17
msgid "Edit user" msgid "Edit user"
@ -3197,20 +3232,20 @@ msgstr "Chỉnh sửa thông tin"
msgid "Rejudge" msgid "Rejudge"
msgstr "Chấm lại" msgstr "Chấm lại"
#: templates/base.html:266 templates/chat/chat.html:566 #: templates/base.html:268 templates/chat/chat.html:573
msgid "Chat" msgid "Chat"
msgstr "Chat" msgstr "Chat"
#: templates/base.html:272 #: templates/base.html:274
msgid "Notification" msgid "Notification"
msgstr "Thông báo" msgstr "Thông báo"
#: templates/base.html:289 #: templates/base.html:291
#, python-format #, python-format
msgid "Hello, <b>%(username)s</b>." msgid "Hello, <b>%(username)s</b>."
msgstr "Xin chào, <b>%(username)s</b>." msgstr "Xin chào, <b>%(username)s</b>."
#: templates/base.html:295 templates/chat/chat.html:20 #: templates/base.html:297 templates/chat/chat.html:20
#: templates/comments/list.html:89 templates/contest/contest-list-tabs.html:24 #: templates/comments/list.html:89 templates/contest/contest-list-tabs.html:24
#: templates/contest/ranking-table.html:53 #: templates/contest/ranking-table.html:53
#: templates/problem/problem-list-tabs.html:6 #: templates/problem/problem-list-tabs.html:6
@ -3219,36 +3254,36 @@ msgstr "Xin chào, <b>%(username)s</b>."
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
#: templates/base.html:304 #: templates/base.html:306
msgid "Log out" msgid "Log out"
msgstr "Đăng xuất" msgstr "Đăng xuất"
#: templates/base.html:313 #: templates/base.html:315
#: templates/registration/password_reset_complete.html:4 #: templates/registration/password_reset_complete.html:4
msgid "Log in" msgid "Log in"
msgstr "Đăng nhập" msgstr "Đăng nhập"
#: templates/base.html:314 templates/registration/registration_form.html:177 #: templates/base.html:316 templates/registration/registration_form.html:177
msgid "or" msgid "or"
msgstr "hoặc" msgstr "hoặc"
#: templates/base.html:315 #: templates/base.html:317
msgid "Sign up" msgid "Sign up"
msgstr "Đăng ký" msgstr "Đăng ký"
#: templates/base.html:331 #: templates/base.html:333
msgid "spectating" msgid "spectating"
msgstr "đang theo dõi" msgstr "đang theo dõi"
#: templates/base.html:343 #: templates/base.html:345
msgid "Compete" msgid "Compete"
msgstr "Thi" msgstr "Thi"
#: templates/base.html:345 #: templates/base.html:347
msgid "General" msgid "General"
msgstr "Chung" msgstr "Chung"
#: templates/base.html:352 #: templates/base.html:354
msgid "This site works best with JavaScript enabled." msgid "This site works best with JavaScript enabled."
msgstr "" msgstr ""
@ -3292,7 +3327,7 @@ msgid "News"
msgstr "Tin tức" msgstr "Tin tức"
#: templates/blog/list.html:115 templates/problem/list.html:347 #: templates/blog/list.html:115 templates/problem/list.html:347
#: templates/problem/problem.html:364 #: templates/problem/problem.html:370
msgid "Clarifications" msgid "Clarifications"
msgstr "Thông báo" msgstr "Thông báo"
@ -3301,7 +3336,7 @@ msgid "Add"
msgstr "Thêm mới" msgstr "Thêm mới"
#: templates/blog/list.html:140 templates/problem/list.html:369 #: templates/blog/list.html:140 templates/problem/list.html:369
#: templates/problem/problem.html:375 #: templates/problem/problem.html:381
msgid "No clarifications have been made at this time." msgid "No clarifications have been made at this time."
msgstr "Không có thông báo nào." msgstr "Không có thông báo nào."
@ -3349,24 +3384,24 @@ msgstr "Thành viên khác"
msgid "New message(s)" msgid "New message(s)"
msgstr "Tin nhắn mới" msgstr "Tin nhắn mới"
#: templates/chat/chat.html:507 templates/chat/chat.html:588 #: templates/chat/chat.html:514 templates/chat/chat.html:595
#: templates/user/base-users.html:14 templates/user/base-users.html:80 #: templates/user/base-users.html:14 templates/user/base-users.html:80
msgid "Search by handle..." msgid "Search by handle..."
msgstr "Tìm kiếm theo tên..." msgstr "Tìm kiếm theo tên..."
#: templates/chat/chat.html:568 templates/chat/chat.html:575 #: templates/chat/chat.html:575 templates/chat/chat.html:582
msgid "Online Users" msgid "Online Users"
msgstr "Trực tuyến" msgstr "Trực tuyến"
#: templates/chat/chat.html:576 #: templates/chat/chat.html:583
msgid "Refresh" msgid "Refresh"
msgstr "Làm mới" msgstr "Làm mới"
#: templates/chat/chat.html:609 #: templates/chat/chat.html:616
msgid "Emoji" msgid "Emoji"
msgstr "" msgstr ""
#: templates/chat/chat.html:610 #: templates/chat/chat.html:617
msgid "Enter your message" msgid "Enter your message"
msgstr "Nhập tin nhắn" msgstr "Nhập tin nhắn"
@ -3563,8 +3598,8 @@ msgstr "Lịch"
msgid "Info" msgid "Info"
msgstr "Thông tin" msgstr "Thông tin"
#: templates/contest/contest-tabs.html:6 templates/stats/base.html:9 #: templates/contest/contest-tabs.html:6 templates/problem/voting-stats.html:26
#: templates/submission/list.html:339 #: templates/stats/base.html:9 templates/submission/list.html:339
msgid "Statistics" msgid "Statistics"
msgstr "Thống kê" msgstr "Thống kê"
@ -3845,27 +3880,27 @@ msgstr "Bạn có chắc muốn hủy kết quả này?"
msgid "Are you sure you want to un-disqualify this participation?" msgid "Are you sure you want to un-disqualify this participation?"
msgstr "Bạn có chắc muốn khôi phục kết quả này?" msgstr "Bạn có chắc muốn khôi phục kết quả này?"
#: templates/contest/ranking.html:415 #: templates/contest/ranking.html:446
msgid "View user participation" msgid "View user participation"
msgstr "Xem các lần tham gia" msgstr "Xem các lần tham gia"
#: templates/contest/ranking.html:419 #: templates/contest/ranking.html:450
msgid "Show organizations" msgid "Show organizations"
msgstr "Hiển thị tổ chức" msgstr "Hiển thị tổ chức"
#: templates/contest/ranking.html:423 #: templates/contest/ranking.html:454
msgid "Show full name" msgid "Show full name"
msgstr "Hiển thị họ tên" msgstr "Hiển thị họ tên"
#: templates/contest/ranking.html:426 #: templates/contest/ranking.html:457
msgid "Show friends only" msgid "Show friends only"
msgstr "Chỉ hiển thị bạn bè" msgstr "Chỉ hiển thị bạn bè"
#: templates/contest/ranking.html:429 #: templates/contest/ranking.html:460
msgid "Total score only" msgid "Total score only"
msgstr "Chỉ hiển thị tổng điểm" msgstr "Chỉ hiển thị tổng điểm"
#: templates/contest/ranking.html:431 #: templates/contest/ranking.html:462
msgid "Show virtual participation" msgid "Show virtual participation"
msgstr "Hiển thị tham gia ảo" msgstr "Hiển thị tham gia ảo"
@ -4415,16 +4450,16 @@ msgstr[0] "Máy chấm:"
msgid "none available" msgid "none available"
msgstr "không có sẵn" msgstr "không có sẵn"
#: templates/problem/problem.html:328 #: templates/problem/problem.html:331
#, python-format #, python-format
msgid "This problem has %(length)s clarification(s)" msgid "This problem has %(length)s clarification(s)"
msgstr "Bài này có %(length)s thông báo" msgstr "Bài này có %(length)s thông báo"
#: templates/problem/problem.html:353 #: templates/problem/problem.html:359
msgid "Request clarification" msgid "Request clarification"
msgstr "Yêu cầu làm rõ đề" msgstr "Yêu cầu làm rõ đề"
#: templates/problem/problem.html:355 #: templates/problem/problem.html:361
msgid "Report an issue" msgid "Report an issue"
msgstr "Báo cáo một vấn đề" msgstr "Báo cáo một vấn đề"
@ -4522,6 +4557,50 @@ msgstr "Không có máy chấm có thể chấm bài này."
msgid "Submit!" msgid "Submit!"
msgstr "Nộp bài!" msgstr "Nộp bài!"
#: templates/problem/voting-controls.html:53
msgid "Edit difficulty"
msgstr "Thay đổi độ khó"
#: templates/problem/voting-controls.html:61
msgid "Vote difficulty"
msgstr "Bình chọn độ khó"
#: templates/problem/voting-form.html:21
msgid "How difficult is this problem?"
msgstr "Bạn thấy độ khó bài này thế nào?"
#: templates/problem/voting-form.html:35
msgid "This helps us improve the site"
msgstr "Bình chọn giúp admin cải thiện bài tập."
#: templates/problem/voting-form.html:38
msgid "Easy"
msgstr "Dễ"
#: templates/problem/voting-form.html:39
msgid "Hard"
msgstr "Khó"
#: templates/problem/voting-stats.html:29
msgid "Voting Statistics"
msgstr "Thống kê"
#: templates/problem/voting-stats.html:32
msgid "No Votes Available!"
msgstr "Không có bình chọn nào!"
#: templates/problem/voting-stats.html:35
msgid "Median:"
msgstr "Trung vị:"
#: templates/problem/voting-stats.html:37
msgid "Mean:"
msgstr "Trung bình:"
#: templates/problem/voting-stats.html:39 templates/submission/list.html:345
msgid "Total:"
msgstr "Tổng:"
#: templates/registration/activate.html:3 #: templates/registration/activate.html:3
#, python-format #, python-format
msgid "%(key)s is an invalid activation key." msgid "%(key)s is an invalid activation key."
@ -4779,10 +4858,6 @@ msgstr "Lọc theo kết quả..."
msgid "Filter by language..." msgid "Filter by language..."
msgstr "Lọc theo ngôn ngữ..." msgstr "Lọc theo ngôn ngữ..."
#: templates/submission/list.html:345
msgid "Total:"
msgstr "Tổng:"
#: templates/submission/list.html:355 #: templates/submission/list.html:355
msgid "You were disconnected. Refresh to show latest updates." msgid "You were disconnected. Refresh to show latest updates."
msgstr "Bạn bị ngắt kết nối. Hãy làm mới để xem cập nhật mới nhất." msgstr "Bạn bị ngắt kết nối. Hãy làm mới để xem cập nhật mới nhất."

View file

@ -5,6 +5,7 @@
<script> <script>
django.jQuery(function ($) { django.jQuery(function ($) {
$('.submissions-link').appendTo('div#bottombar').show(); $('.submissions-link').appendTo('div#bottombar').show();
$('.votes-link').appendTo('div#bottombar').show();
}); });
</script> </script>
{% endblock extrahead %} {% endblock extrahead %}
@ -15,6 +16,10 @@
href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}"> href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}">
<i class="fa fa-lg fa-search-plus"></i> <i class="fa fa-lg fa-search-plus"></i>
<span class="text">{% trans "View submissions" %}</span> <span class="text">{% trans "View submissions" %}</span>
<a style="display: none" title="{{ _('View votes') }}" class="button votes-link"
href="{% url 'admin:judge_problempointsvote_changelist' %}?problem__code={{ original.code }}">
<i class="fa fa-lg fa-envelope"></i>
<span class="text">{{ _('View votes') }}</span>
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -113,7 +113,7 @@
function checker_precision($checker) { function checker_precision($checker) {
var $td = $checker.parent(); var $td = $checker.parent();
var $args = $td.find('input'); var $args = $td.closest('table').find('#id_problem-data-checker_args');
var $precision = $('<input>', { var $precision = $('<input>', {
type: 'number', type: 'number',
value: try_parse_json($args.val()).precision || 6, value: try_parse_json($args.val()).precision || 6,

View file

@ -164,7 +164,7 @@
{% endif %} {% endif %}
<div><a href="{{ url('chronological_submissions', problem.code) }}">{{ _('All submissions') }}</a></div> <div><a href="{{ url('chronological_submissions', problem.code) }}">{{ _('All submissions') }}</a></div>
<div><a href="{{ url('ranked_submissions', problem.code) }}">{{ _('Best submissions') }}</a></div> <div><a href="{{ url('ranked_submissions', problem.code) }}">{{ _('Best submissions') }}</a></div>
{% if not contest_problem or not contest_problem.contest.use_clarifications %} {% if not contest_problem or not contest_problem.contest.use_clarifications %}
<hr> <hr>
<div><a href="{{ url('problem_comments', problem.code) }}">{{ _('Discuss') }}</a></div> <div><a href="{{ url('problem_comments', problem.code) }}">{{ _('Discuss') }}</a></div>
@ -321,6 +321,9 @@
{% endblock %} {% endblock %}
{% block description %} {% block description %}
{% if can_vote and not vote %}
{% include 'problem/voting-form.html' %}
{% endif %}
{% if contest_problem and contest_problem.contest.use_clarifications and has_clarifications %} {% if contest_problem and contest_problem.contest.use_clarifications and has_clarifications %}
<div id="clarification_header_container"> <div id="clarification_header_container">
<i class="fa fa-question-circle"></i> <i class="fa fa-question-circle"></i>
@ -346,6 +349,9 @@
{% endblock %} {% endblock %}
{% block post_description_end %} {% block post_description_end %}
{% if can_vote or request.user.is_superuser %}
{% include 'problem/voting-controls.html' %}
{% endif %}
{% if request.user.is_authenticated and not request.profile.mute %} {% if request.user.is_authenticated and not request.profile.mute %}
<a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify"> <a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify">
<i class="fa fa-flag" style="margin-right:0.5em"></i> <i class="fa fa-flag" style="margin-right:0.5em"></i>

View file

@ -0,0 +1,75 @@
<style>
.featherlight .featherlight-inner{
display: block !important;
}
.featherlight-content {
overflow: inherit !important;
}
.stars-container {
display: inline-block;
vertical-align: top;
}
.stars-container .star {
display: inline-block;
padding: 2px;
color: orange;
cursor: pointer;
font-size: 30px;
}
.stars-container .star:before {
content:"\f006";
}
.filled-star:before, .star:hover:before {
content:"\f005" !important;
}
</style>
{% if can_vote or request.user.is_superuser %}
<div class="vote-form" id="id_vote_form_box" style="display: none">
{% include 'problem/voting-form.html' %}
</div>
<span>
{% if can_vote %}
<a href="#" class="form-button" id="id_vote_button" data-featherlight="#id_vote_form_box"></a>
{% endif %}
{% if request.user.is_superuser %}
- {% include 'problem/voting-stats.html' %}
{% endif %}
</span>
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
<script>
let voted_points = null;
{% if vote is not none %}
let has_voted = true;
voted_points = {{ vote.points }};
{% else %}
let has_voted = false;
{% endif %}
function voteUpdate(){
$('#id_has_voted_prompt').show();
$('#id_current_vote_value').prop('innerText', voted_points);
$('#id_has_not_voted_prompt').hide();
$('#id_vote_button').prop('innerText', `{{ _('Edit difficulty') }} (` + voted_points + ')');
$('#id_points_error_box').hide();
has_voted = true;
}
function deleteVoteUpdate(){
$('#id_has_voted_prompt').hide();
$('#id_has_not_voted_prompt').show();
$('#id_vote_button').prop('innerText', `{{ _('Vote difficulty') }}`);
$('#id_points_error_box').hide();
has_voted = false;
}
if (has_voted) voteUpdate();
else deleteVoteUpdate();
$('#id_vote_form').on('submit', function (e) {
e.preventDefault();
});
</script>
{% endif %}

View file

@ -0,0 +1,93 @@
<style>
.vote_form {
border: 3px solid blue;
border-radius: 5px;
width: fit-content;
margin-left: auto;
margin-right: auto;
padding: 0.2em 1em;
background: #ebf0ff;
color: #2e69ff;
}
</style>
<form id="id_vote_form" class="vote_form">
{% csrf_token %}
<input id="id_points" class="vote-form-value" type="hidden" step="1"
min="{{ min_possible_vote }}" max="{{ max_possible_vote }}" name="points">
<table>
<tbody>
<tr>
<td style="padding-top: 1em; padding-right: 3em">
<span><b>{{_('How difficult is this problem?')}}</b></span>
</td>
<td>
<div class="vote-rating">
<span class="stars-container">
{% for i in range(1, (max_possible_vote - min_possible_vote) // 100 + 2) %}
<span id="star{{i}}" star-score="{{i * 100}}" class="fa star"></span>
{% endfor %}
</span>
</div>
</td>
</tr>
<tr>
<td>
<span>{{_('This helps us improve the site')}}</span>
</td>
<td>
<span style="padding-left: 0.2em"><b>{{min_possible_vote}}</b></span>
<span style="float: right; padding-right: 0.2em"><b>{{max_possible_vote}}</b></span>
</td>
</tr>
</tbody>
</table>
<div id="id_points_error_box">
<span id="id_points_error" class="voting-form-error"></span>
</div>
<div>
<input type="hidden" id="id_vote_form_submit_button">
</div>
</form>
<script>
$('.star').hover(
(e) => $(e.target).prevAll().addClass('filled-star'),
(e) => $(e.target).prevAll().removeClass('filled-star')
);
$('.star').on('click', function() {
$("#id_points").val($(this).attr('star-score'));
$('#id_vote_form_submit_button').click();
$('#id_vote_form').fadeOut(500);
})
$('#id_vote_form_submit_button').on('click', function (e) {
e.preventDefault();
$.ajax({
url: '{{ url('vote', object.code) }}',
type: 'POST',
data: $('#id_vote_form').serialize(),
success: function (data) {
{% if request.user.is_superuser %}
updateUserVote(voted_points, data.points);
{% endif %}
voted_points = data.points;
voteUpdate();
// Forms are auto disabled to prevent resubmission, but we need to allow resubmission here.
$('#id_vote_form_submit_button').removeAttr('disabled');
var current = $.featherlight.current();
if (current) current.close();
},
error: function (data) {
let errors = data.responseJSON;
if(errors === undefined) {
alert('Unable to delete vote: ' + data.responsetext);
}
if('points' in errors){
$('#id_points_error_box').show();
$('#id_points_error').prop('textContent', errors.points[0]);
} else {
$('#id_points_error_box').hide();
}
}
});
});
</script>

View file

@ -0,0 +1,146 @@
<style>
.vote-stats-background {
background-color: rgb(255,255,255);
padding: 20px;
border-radius: 25px;
justify-content: center;
align-items: center;
margin: auto;
text-align: center;
}
.canvas {
border: 1px;
border: solid;
border: #000000;
}
.vote-stats-value {
margin-right: 1em;
}
.vote-stats-info {
font-weight: 500;
}
</style>
<a href="#" class="form-button" id="id_vote_stats_button">{{ _('Statistics') }}</a>
<div class="vote-stats-background" id="id_vote_stats" style="display: none;">
<script src={{ static('libs/chart.js/Chart.js') }}></script>
<span class="vote-stats-info">{{ _('Voting Statistics') }}</span>
<br/>
<canvas class="canvas" id="id_vote_chart"></canvas>
<span id="id_no_votes_error" class="vote-stats-info no_votes_error">{{ _('No Votes Available!') }}</span>
<br/>
<div id="id_has_votes_footer" class="has_votes_footer">
<span class="vote-stats-info">{{ _('Median:') }}</span>
<span class="vote-stats-value median_vote" id="id_median_vote"></span>
<span class="vote-stats-info">{{ _('Mean:') }}</span>
<span class="vote-stats-value mean_vote" id="id_mean_vote"></span>
<span class="vote-stats-info">{{ _('Total:') }}</span>
<span class="vote-stats-value total_vote" id="id_num_of_votes"></span>
</div>
</div>
<script>
let voteChart = null;
let allVotes = {{ all_votes }};
function reload_vote_graph() {
if (voteChart !== null) voteChart.destroy();
if (allVotes.length === 0) {
$('.canvas').hide();
$('.has_votes_footer').hide();
$('.no_votes_error').show();
} else {
$('.canvas').show();
$('.has_votes_footer').show();
$('.no_votes_error').hide();
allVotes.sort(function(a, b){return a - b});
// Give the graph some padding on both sides.
let min_points = {{ min_possible_vote }};
let max_points = {{ max_possible_vote }};
let xlabels = [];
let voteFreq = [];
for (let i = min_points; i <= max_points; i += 100) {
xlabels.push(i);
voteFreq.push(0);
}
let max_number_of_votes = 0;
let total_votes = 0;
let mean = 0;
for (let i = 0; i < allVotes.length; i++) {
// Assume the allVotes is valid.
voteFreq[(allVotes[i] - min_points) / 100]++;
max_number_of_votes = Math.max(max_number_of_votes, voteFreq[(allVotes[i] - min_points) / 100]);
mean += allVotes[i];
total_votes++;
}
mean = mean / total_votes;
let half = Math.floor(total_votes / 2);
let median = allVotes[half];
if (total_votes % 2 === 0) {
median = (median + allVotes[half - 1]) / 2;
}
$('.mean_vote').prop('innerText', mean.toFixed(2));
$('.median_vote').prop('innerText', median.toFixed(2));
$('.total_vote').prop('innerText', total_votes);
const voteData = {
labels: xlabels,
datasets: [{
data: voteFreq,
backgroundColor: 'pink',
}]
};
const voteDataConfig = {
type: 'bar',
data: voteData,
options: {
legend: {
display: false
},
responsive: true,
scales: {
yAxes: [{
ticks: {
precision: 0,
suggestedMax: Math.ceil(max_number_of_votes * 1.2),
beginAtZero: true,
}
}],
xAxes: [{
ticks: {
beginAtZero: false,
}
}]
}
}
};
voteChart = new Chart($('.featherlight-inner .canvas'), voteDataConfig);
}
}
function updateUserVote(prev_voted_points, voted_points) {
let index = allVotes.indexOf(prev_voted_points);
if (index > -1) {
allVotes.splice(index, 1);
}
allVotes.push(voted_points);
}
function deleteUserVote(prev_voted_points) {
allVotes.splice(allVotes.indexOf(prev_voted_points), 1);
}
$(function() {
$('#id_vote_stats_button').featherlight('#id_vote_stats', {
afterOpen: reload_vote_graph,
})
});
</script>