Add problem vote
This commit is contained in:
parent
68c6f13926
commit
2e3a45168e
17 changed files with 685 additions and 122 deletions
|
@ -162,6 +162,7 @@ else:
|
|||
'children': [
|
||||
'judge.ProblemGroup',
|
||||
'judge.ProblemType',
|
||||
'judge.ProblemPointsVote',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -139,6 +139,8 @@ urlpatterns = [
|
|||
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'^/vote$', problem.Vote.as_view(), name='vote'),
|
||||
|
||||
url(r'^/manage/submission', include([
|
||||
url('^$', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
|
||||
url('^/action$', problem_manage.ActionSubmissionsView.as_view(), name='problem_submissions_action'),
|
||||
|
|
|
@ -5,7 +5,7 @@ from judge.admin.comments import CommentAdmin
|
|||
from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin
|
||||
from judge.admin.interface import BlogPostAdmin, LicenseAdmin, LogEntryAdmin, NavigationBarAdmin
|
||||
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.runtime import JudgeAdmin, LanguageAdmin
|
||||
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.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \
|
||||
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(Comment, CommentAdmin)
|
||||
|
@ -31,6 +31,7 @@ admin.site.register(Organization, OrganizationAdmin)
|
|||
admin.site.register(OrganizationRequest, OrganizationRequestAdmin)
|
||||
admin.site.register(Problem, ProblemAdmin)
|
||||
admin.site.register(ProblemGroup, ProblemGroupAdmin)
|
||||
admin.site.register(ProblemPointsVote, ProblemPointsVoteAdmin)
|
||||
admin.site.register(ProblemType, ProblemTypeAdmin)
|
||||
admin.site.register(Profile, ProfileAdmin)
|
||||
admin.site.register(Submission, SubmissionAdmin)
|
||||
|
|
|
@ -236,3 +236,17 @@ class ProblemAdmin(VersionAdmin):
|
|||
if form.cleaned_data.get('change_message'):
|
||||
return form.cleaned_data['change_message']
|
||||
return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs)
|
||||
|
||||
|
||||
class ProblemPointsVoteAdmin(admin.ModelAdmin):
|
||||
list_display = ('points', 'voter', 'problem', 'vote_time')
|
||||
search_fields = ('voter', 'problem')
|
||||
readonly_fields = ('voter', 'problem', '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 lookup_allowed(self, key, value):
|
||||
return super().lookup_allowed(key, value) or key in ('problem__code',)
|
|
@ -45,7 +45,7 @@ class TimezoneFilter(admin.SimpleListFilter):
|
|||
|
||||
class ProfileAdmin(VersionAdmin):
|
||||
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')
|
||||
readonly_fields = ('user',)
|
||||
list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full',
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.urls import reverse_lazy
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
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.widgets import HeavyPreviewPageDownWidget, MathJaxPagedownWidget, PagedownWidget, Select2MultipleWidget, \
|
||||
Select2Widget
|
||||
|
@ -161,4 +161,10 @@ class ContestCloneForm(Form):
|
|||
key = self.cleaned_data['key']
|
||||
if Contest.objects.filter(key=key).exists():
|
||||
raise ValidationError(_('Contest with key already exists.'))
|
||||
return key
|
||||
return key
|
||||
|
||||
|
||||
class ProblemPointsVoteForm(ModelForm):
|
||||
class Meta:
|
||||
model = ProblemPointsVote
|
||||
fields = ['points']
|
34
judge/migrations/0120_auto_20220306_1124.py
Normal file
34
judge/migrations/0120_auto_20220306_1124.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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.message import PrivateMessage, PrivateMessageThread
|
||||
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, \
|
||||
problem_directory_file
|
||||
from judge.models.profile import Organization, OrganizationRequest, Profile, Friend
|
||||
|
|
|
@ -375,6 +375,25 @@ class Problem(models.Model):
|
|||
|
||||
save.alters_data = True
|
||||
|
||||
def can_vote(self, user):
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
|
||||
# If the user is in contest, nothing should be shown.
|
||||
if user.profile.current_contest:
|
||||
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:
|
||||
permissions = (
|
||||
('see_private_problem', 'See hidden problems'),
|
||||
|
@ -448,3 +467,29 @@ class Solution(models.Model):
|
|||
)
|
||||
verbose_name = _('solution')
|
||||
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}'
|
|
@ -98,6 +98,11 @@ class Profile(models.Model):
|
|||
default=False)
|
||||
is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'),
|
||||
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)
|
||||
user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536,
|
||||
help_text=_('User-defined JavaScript for site customization.'))
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
|||
from django.db import transaction
|
||||
from django.db.models import Count, F, Prefetch, Q, Sum, Case, When, IntegerField
|
||||
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.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
|
@ -26,10 +26,10 @@ from django.views.generic.base import TemplateResponseMixin
|
|||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
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, \
|
||||
ProblemGroup, ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource, \
|
||||
TranslatedProblemForeignKeyQuerySet, Organization
|
||||
ProblemGroup, ProblemTranslation, ProblemType, ProblemPointsVote, RuntimeVersion, Solution, Submission, SubmissionSource, \
|
||||
TranslatedProblemForeignKeyQuerySet, Organization
|
||||
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
|
||||
from judge.utils.diggpaginator import DiggPaginator
|
||||
from judge.utils.opengraph import generate_opengraph
|
||||
|
@ -216,8 +216,65 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView):
|
|||
context['description'], 'problem')
|
||||
context['meta_description'] = self.object.summary or metadata[0]
|
||||
context['og_image'] = self.object.og_image or metadata[1]
|
||||
|
||||
context['can_vote'] = self.object.can_vote(user)
|
||||
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
|
||||
|
||||
all_votes = list(self.object.problem_points_votes.order_by('points').values_list('points', flat=True))
|
||||
|
||||
context['has_votes'] = len(all_votes) > 0
|
||||
|
||||
# If the user is not currently in contest.
|
||||
if not user.is_authenticated or user.profile.current_contest is None:
|
||||
context['all_votes'] = all_votes
|
||||
|
||||
context['max_possible_vote'] = 600
|
||||
context['min_possible_vote'] = 100
|
||||
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.user): # 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):
|
||||
context_object_name = 'problem'
|
||||
|
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: lqdoj2\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"
|
||||
"Last-Translator: Icyene\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
|
||||
#: 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"
|
||||
msgstr "người dùng"
|
||||
|
||||
|
@ -36,71 +36,71 @@ msgstr "nội dung bình luận"
|
|||
msgid "last seen"
|
||||
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"
|
||||
msgstr "Chat Box"
|
||||
|
||||
#: dmoj/settings.py:358
|
||||
#: dmoj/settings.py:359
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:359
|
||||
#: dmoj/settings.py:360
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:360
|
||||
#: dmoj/settings.py:361
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:361
|
||||
#: dmoj/settings.py:362
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:362
|
||||
#: dmoj/settings.py:363
|
||||
msgid "Croatian"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:363
|
||||
#: dmoj/settings.py:364
|
||||
msgid "Hungarian"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:364
|
||||
#: dmoj/settings.py:365
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:365
|
||||
#: dmoj/settings.py:366
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:366
|
||||
#: dmoj/settings.py:367
|
||||
msgid "Brazilian Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:367
|
||||
#: dmoj/settings.py:368
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:368
|
||||
#: dmoj/settings.py:369
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:369
|
||||
#: dmoj/settings.py:370
|
||||
msgid "Serbian (Latin)"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:370
|
||||
#: dmoj/settings.py:371
|
||||
msgid "Turkish"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:371
|
||||
#: dmoj/settings.py:372
|
||||
msgid "Vietnamese"
|
||||
msgstr "Tiếng Việt"
|
||||
|
||||
#: dmoj/settings.py:372
|
||||
#: dmoj/settings.py:373
|
||||
msgid "Simplified Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: dmoj/settings.py:373
|
||||
#: dmoj/settings.py:374
|
||||
msgid "Traditional Chinese"
|
||||
msgstr ""
|
||||
|
||||
|
@ -108,7 +108,7 @@ msgstr ""
|
|||
msgid "Login"
|
||||
msgstr "Đăng nhập"
|
||||
|
||||
#: dmoj/urls.py:106 templates/base.html:249
|
||||
#: dmoj/urls.py:106 templates/base.html:251
|
||||
msgid "Home"
|
||||
msgstr "Trang chủ"
|
||||
|
||||
|
@ -217,7 +217,7 @@ msgstr "Tính toán lại kết quả"
|
|||
msgid "username"
|
||||
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"
|
||||
msgstr "ảo"
|
||||
|
||||
|
@ -587,9 +587,9 @@ msgstr "người bình luận"
|
|||
msgid "associated page"
|
||||
msgstr "trang tương ứng"
|
||||
|
||||
#: judge/models/comment.py:46
|
||||
#: judge/models/comment.py:46 judge/models/problem.py:492
|
||||
msgid "votes"
|
||||
msgstr ""
|
||||
msgstr "bình chọn"
|
||||
|
||||
#: judge/models/comment.py:48
|
||||
msgid "hide the comment"
|
||||
|
@ -607,7 +607,7 @@ msgstr "bình luận"
|
|||
msgid "comments"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/comment.py:137 judge/models/problem.py:443
|
||||
#: judge/models/comment.py:137 judge/models/problem.py:462
|
||||
#, python-format
|
||||
msgid "Editorial for %s"
|
||||
msgstr ""
|
||||
|
@ -725,7 +725,7 @@ msgstr ""
|
|||
msgid "description"
|
||||
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
|
||||
msgid "problems"
|
||||
msgstr "bài tập"
|
||||
|
@ -739,7 +739,7 @@ msgid "end time"
|
|||
msgstr "thời gian kết thúc"
|
||||
|
||||
#: judge/models/contest.py:75 judge/models/problem.py:118
|
||||
#: judge/models/problem.py:414
|
||||
#: judge/models/problem.py:433
|
||||
msgid "time limit"
|
||||
msgstr "giới hạn thời gian"
|
||||
|
||||
|
@ -1037,8 +1037,8 @@ msgid "contest participations"
|
|||
msgstr "lần tham gia kỳ thi"
|
||||
|
||||
#: judge/models/contest.py:491 judge/models/contest.py:513
|
||||
#: judge/models/contest.py:554 judge/models/problem.py:389
|
||||
#: judge/models/problem.py:394 judge/models/problem.py:412
|
||||
#: judge/models/contest.py:554 judge/models/problem.py:408
|
||||
#: judge/models/problem.py:413 judge/models/problem.py:431
|
||||
#: judge/models/problem_data.py:40
|
||||
msgid "problem"
|
||||
msgstr "bài tập"
|
||||
|
@ -1176,7 +1176,7 @@ msgstr "mục cha"
|
|||
msgid "post title"
|
||||
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"
|
||||
msgstr "tác giả"
|
||||
|
||||
|
@ -1184,7 +1184,7 @@ msgstr "tác giả"
|
|||
msgid "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"
|
||||
msgstr "khả năng hiển thị công khai"
|
||||
|
||||
|
@ -1380,7 +1380,7 @@ msgid ""
|
|||
"are supported."
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:123 judge/models/problem.py:417
|
||||
#: judge/models/problem.py:123 judge/models/problem.py:436
|
||||
msgid "memory limit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1453,67 +1453,85 @@ msgstr ""
|
|||
msgid "If private, only these organizations may see the problem."
|
||||
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
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:396
|
||||
#: judge/models/problem.py:415
|
||||
msgid "translated name"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:397
|
||||
#: judge/models/problem.py:416
|
||||
msgid "translated description"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:401
|
||||
#: judge/models/problem.py:420
|
||||
msgid "problem translation"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:402
|
||||
#: judge/models/problem.py:421
|
||||
msgid "problem translations"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:406
|
||||
#: judge/models/problem.py:425
|
||||
msgid "clarified problem"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:407
|
||||
#: judge/models/problem.py:426
|
||||
msgid "clarification body"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:408
|
||||
#: judge/models/problem.py:427
|
||||
msgid "clarification timestamp"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:423
|
||||
#: judge/models/problem.py:442
|
||||
msgid "language-specific resource limit"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:424
|
||||
#: judge/models/problem.py:443
|
||||
msgid "language-specific resource limits"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:428
|
||||
#: judge/models/problem.py:447
|
||||
msgid "associated problem"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:431
|
||||
#: judge/models/problem.py:450
|
||||
msgid "publish date"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/problem.py:433
|
||||
#: judge/models/problem.py:452
|
||||
msgid "editorial content"
|
||||
msgstr "nội dung lời giải"
|
||||
|
||||
#: judge/models/problem.py:449
|
||||
#: judge/models/problem.py:468
|
||||
msgid "solution"
|
||||
msgstr "lời giải"
|
||||
|
||||
#: judge/models/problem.py:450
|
||||
#: judge/models/problem.py:469
|
||||
msgid "solutions"
|
||||
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
|
||||
msgid "Standard"
|
||||
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."
|
||||
|
||||
#: judge/models/profile.py:76 judge/models/profile.py:93
|
||||
#: judge/models/profile.py:216
|
||||
#: judge/models/profile.py:221
|
||||
msgid "organization"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1756,78 +1774,88 @@ msgid "User will not be ranked."
|
|||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:103
|
||||
#: judge/models/profile.py:108
|
||||
msgid "User-defined JavaScript for site customization."
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:104
|
||||
#: judge/models/profile.py:109
|
||||
msgid "current contest"
|
||||
msgstr "kỳ thi hiện tại"
|
||||
|
||||
#: judge/models/profile.py:106
|
||||
#: judge/models/profile.py:111
|
||||
msgid "math engine"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:108
|
||||
#: judge/models/profile.py:113
|
||||
msgid "the rendering engine used to render math"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:109
|
||||
#: judge/models/profile.py:114
|
||||
msgid "2FA enabled"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:110
|
||||
#: judge/models/profile.py:115
|
||||
msgid "check to enable 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"
|
||||
msgstr "mã TOTP"
|
||||
|
||||
#: judge/models/profile.py:112
|
||||
#: judge/models/profile.py:117
|
||||
msgid "32 character base32-encoded key for TOTP"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:114
|
||||
#: judge/models/profile.py:119
|
||||
msgid "TOTP key must be empty or base32"
|
||||
msgstr ""
|
||||
|
||||
#: judge/models/profile.py:115
|
||||
#: judge/models/profile.py:120
|
||||
msgid "internal notes"
|
||||
msgstr "ghi chú nội bộ"
|
||||
|
||||
#: judge/models/profile.py:116
|
||||
#: judge/models/profile.py:121
|
||||
msgid "Notes for administrators regarding this user."
|
||||
msgstr "Ghi chú riêng cho quản trị viên."
|
||||
|
||||
#: judge/models/profile.py:210
|
||||
#: judge/models/profile.py:215
|
||||
msgid "user profile"
|
||||
msgstr "thông tin người dùng"
|
||||
|
||||
#: judge/models/profile.py:211
|
||||
#: judge/models/profile.py:216
|
||||
msgid "user profiles"
|
||||
msgstr "thông tin người dùng"
|
||||
|
||||
#: judge/models/profile.py:218
|
||||
#: judge/models/profile.py:223
|
||||
msgid "request time"
|
||||
msgstr "thời gian đăng ký"
|
||||
|
||||
#: judge/models/profile.py:219
|
||||
#: judge/models/profile.py:224
|
||||
msgid "state"
|
||||
msgstr "trạng thái"
|
||||
|
||||
#: judge/models/profile.py:224
|
||||
#: judge/models/profile.py:229
|
||||
msgid "reason"
|
||||
msgstr "lý do"
|
||||
|
||||
#: judge/models/profile.py:227
|
||||
#: judge/models/profile.py:232
|
||||
msgid "organization join request"
|
||||
msgstr "đơn đăng ký tham gia"
|
||||
|
||||
#: judge/models/profile.py:228
|
||||
#: judge/models/profile.py:233
|
||||
msgid "organization join requests"
|
||||
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>"
|
||||
msgstr "Hướng dẫn cho <a href=\"{1}\">{0}</a>"
|
||||
|
||||
#: judge/views/problem.py:227
|
||||
#: judge/views/problem.py:284
|
||||
#, python-brace-format
|
||||
msgid "Disscuss {0}"
|
||||
msgstr ""
|
||||
|
||||
#: judge/views/problem.py:230
|
||||
#: judge/views/problem.py:287
|
||||
#, python-brace-format
|
||||
msgid "Discuss <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/users-table.html:29
|
||||
msgid "Problems"
|
||||
msgstr "Bài tập"
|
||||
|
||||
#: judge/views/problem.py:598
|
||||
#: judge/views/problem.py:655
|
||||
msgid "Banned from submitting"
|
||||
msgstr "Bị cấm nộp bài"
|
||||
|
||||
#: judge/views/problem.py:599
|
||||
#: judge/views/problem.py:656
|
||||
msgid ""
|
||||
"You have been declared persona non grata for this problem. You are "
|
||||
"permanently barred from submitting this problem."
|
||||
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"
|
||||
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."
|
||||
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
|
||||
msgid "Submit to %(problem)s"
|
||||
msgstr "Nộp bài cho %(problem)s"
|
||||
|
||||
#: judge/views/problem.py:692
|
||||
#: judge/views/problem.py:749
|
||||
msgid "Clone Problem"
|
||||
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"
|
||||
|
||||
#: 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
|
||||
msgid "Edit profile"
|
||||
msgstr "Chỉnh sửa thông tin"
|
||||
|
@ -3177,15 +3205,22 @@ msgstr "Ngắt kết nối"
|
|||
msgid "Terminate"
|
||||
msgstr "Dừng"
|
||||
|
||||
#: templates/admin/judge/problem/change_form.html:14
|
||||
#: templates/admin/judge/problem/change_form.html:15
|
||||
msgid "View Submissions"
|
||||
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
|
||||
msgid "View submissions"
|
||||
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:17
|
||||
msgid "Edit user"
|
||||
|
@ -3197,20 +3232,20 @@ msgstr "Chỉnh sửa thông tin"
|
|||
msgid "Rejudge"
|
||||
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"
|
||||
msgstr "Chat"
|
||||
|
||||
#: templates/base.html:272
|
||||
#: templates/base.html:274
|
||||
msgid "Notification"
|
||||
msgstr "Thông báo"
|
||||
|
||||
#: templates/base.html:289
|
||||
#: templates/base.html:291
|
||||
#, python-format
|
||||
msgid "Hello, <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/contest/ranking-table.html:53
|
||||
#: templates/problem/problem-list-tabs.html:6
|
||||
|
@ -3219,36 +3254,36 @@ msgstr "Xin chào, <b>%(username)s</b>."
|
|||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
#: templates/base.html:304
|
||||
#: templates/base.html:306
|
||||
msgid "Log out"
|
||||
msgstr "Đăng xuất"
|
||||
|
||||
#: templates/base.html:313
|
||||
#: templates/base.html:315
|
||||
#: templates/registration/password_reset_complete.html:4
|
||||
msgid "Log in"
|
||||
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"
|
||||
msgstr "hoặc"
|
||||
|
||||
#: templates/base.html:315
|
||||
#: templates/base.html:317
|
||||
msgid "Sign up"
|
||||
msgstr "Đăng ký"
|
||||
|
||||
#: templates/base.html:331
|
||||
#: templates/base.html:333
|
||||
msgid "spectating"
|
||||
msgstr "đang theo dõi"
|
||||
|
||||
#: templates/base.html:343
|
||||
#: templates/base.html:345
|
||||
msgid "Compete"
|
||||
msgstr "Thi"
|
||||
|
||||
#: templates/base.html:345
|
||||
#: templates/base.html:347
|
||||
msgid "General"
|
||||
msgstr "Chung"
|
||||
|
||||
#: templates/base.html:352
|
||||
#: templates/base.html:354
|
||||
msgid "This site works best with JavaScript enabled."
|
||||
msgstr ""
|
||||
|
||||
|
@ -3292,7 +3327,7 @@ msgid "News"
|
|||
msgstr "Tin tức"
|
||||
|
||||
#: templates/blog/list.html:115 templates/problem/list.html:347
|
||||
#: templates/problem/problem.html:364
|
||||
#: templates/problem/problem.html:370
|
||||
msgid "Clarifications"
|
||||
msgstr "Thông báo"
|
||||
|
||||
|
@ -3301,7 +3336,7 @@ msgid "Add"
|
|||
msgstr "Thêm mới"
|
||||
|
||||
#: 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."
|
||||
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)"
|
||||
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
|
||||
msgid "Search by handle..."
|
||||
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"
|
||||
msgstr "Trực tuyến"
|
||||
|
||||
#: templates/chat/chat.html:576
|
||||
#: templates/chat/chat.html:583
|
||||
msgid "Refresh"
|
||||
msgstr "Làm mới"
|
||||
|
||||
#: templates/chat/chat.html:609
|
||||
#: templates/chat/chat.html:616
|
||||
msgid "Emoji"
|
||||
msgstr ""
|
||||
|
||||
#: templates/chat/chat.html:610
|
||||
#: templates/chat/chat.html:617
|
||||
msgid "Enter your message"
|
||||
msgstr "Nhập tin nhắn"
|
||||
|
||||
|
@ -3563,8 +3598,8 @@ msgstr "Lịch"
|
|||
msgid "Info"
|
||||
msgstr "Thông tin"
|
||||
|
||||
#: templates/contest/contest-tabs.html:6 templates/stats/base.html:9
|
||||
#: templates/submission/list.html:339
|
||||
#: templates/contest/contest-tabs.html:6 templates/problem/voting-stats.html:26
|
||||
#: templates/stats/base.html:9 templates/submission/list.html:339
|
||||
msgid "Statistics"
|
||||
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?"
|
||||
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"
|
||||
msgstr "Xem các lần tham gia"
|
||||
|
||||
#: templates/contest/ranking.html:419
|
||||
#: templates/contest/ranking.html:450
|
||||
msgid "Show organizations"
|
||||
msgstr "Hiển thị tổ chức"
|
||||
|
||||
#: templates/contest/ranking.html:423
|
||||
#: templates/contest/ranking.html:454
|
||||
msgid "Show full name"
|
||||
msgstr "Hiển thị họ tên"
|
||||
|
||||
#: templates/contest/ranking.html:426
|
||||
#: templates/contest/ranking.html:457
|
||||
msgid "Show friends only"
|
||||
msgstr "Chỉ hiển thị bạn bè"
|
||||
|
||||
#: templates/contest/ranking.html:429
|
||||
#: templates/contest/ranking.html:460
|
||||
msgid "Total score only"
|
||||
msgstr "Chỉ hiển thị tổng điểm"
|
||||
|
||||
#: templates/contest/ranking.html:431
|
||||
#: templates/contest/ranking.html:462
|
||||
msgid "Show virtual participation"
|
||||
msgstr "Hiển thị tham gia ảo"
|
||||
|
||||
|
@ -4415,16 +4450,16 @@ msgstr[0] "Máy chấm:"
|
|||
msgid "none available"
|
||||
msgstr "không có sẵn"
|
||||
|
||||
#: templates/problem/problem.html:328
|
||||
#: templates/problem/problem.html:331
|
||||
#, python-format
|
||||
msgid "This problem has %(length)s clarification(s)"
|
||||
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"
|
||||
msgstr "Yêu cầu làm rõ đề"
|
||||
|
||||
#: templates/problem/problem.html:355
|
||||
#: templates/problem/problem.html:361
|
||||
msgid "Report an issue"
|
||||
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!"
|
||||
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
|
||||
#, python-format
|
||||
msgid "%(key)s is an invalid activation key."
|
||||
|
@ -4779,10 +4858,6 @@ msgstr "Lọc theo kết quả..."
|
|||
msgid "Filter by language..."
|
||||
msgstr "Lọc theo ngôn ngữ..."
|
||||
|
||||
#: templates/submission/list.html:345
|
||||
msgid "Total:"
|
||||
msgstr "Tổng:"
|
||||
|
||||
#: templates/submission/list.html:355
|
||||
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."
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<script>
|
||||
django.jQuery(function ($) {
|
||||
$('.submissions-link').appendTo('div#bottombar').show();
|
||||
$('.votes-link').appendTo('div#bottombar').show();
|
||||
});
|
||||
</script>
|
||||
{% endblock extrahead %}
|
||||
|
@ -15,6 +16,10 @@
|
|||
href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}">
|
||||
<i class="fa fa-lg fa-search-plus"></i>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
{% endif %}
|
||||
<div><a href="{{ url('chronological_submissions', problem.code) }}">{{ _('All 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 %}
|
||||
<hr>
|
||||
<div><a href="{{ url('problem_comments', problem.code) }}">{{ _('Discuss') }}</a></div>
|
||||
|
@ -321,6 +321,9 @@
|
|||
{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
<div id="clarification_header_container">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
|
@ -346,6 +349,9 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block post_description_end %}
|
||||
{% if can_vote %}
|
||||
{% include 'problem/voting-controls.html' %}
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated and not request.profile.mute %}
|
||||
<a href="{{ url('new_problem_ticket', problem.code) }}" class="clarify">
|
||||
<i class="fa fa-flag" style="margin-right:0.5em"></i>
|
||||
|
|
73
templates/problem/voting-controls.html
Normal file
73
templates/problem/voting-controls.html
Normal file
|
@ -0,0 +1,73 @@
|
|||
<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 %}
|
||||
<div class="vote-form" id="id_vote_form_box" style="display: none">
|
||||
{% include 'problem/voting-form.html' %}
|
||||
</div>
|
||||
<span>
|
||||
<a href="#" class="form-button" id="id_vote_button" data-featherlight="#id_vote_form_box"></a>
|
||||
{% 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 %}
|
93
templates/problem/voting-form.html
Normal file
93
templates/problem/voting-form.html
Normal 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>{{_('Easy')}}</b></span>
|
||||
<span style="float: right; padding-right: 0.2em"><b>{{_('Hard')}}</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();
|
||||
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>
|
146
templates/problem/voting-stats.html
Normal file
146
templates/problem/voting-stats.html
Normal 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 data = {{ all_votes }};
|
||||
|
||||
function reload_vote_graph() {
|
||||
if (voteChart !== null) voteChart.destroy();
|
||||
|
||||
if (data.length === 0) {
|
||||
$('.canvas').hide();
|
||||
$('.has_votes_footer').hide();
|
||||
$('.no_votes_error').show();
|
||||
} else {
|
||||
$('.canvas').show();
|
||||
$('.has_votes_footer').show();
|
||||
$('.no_votes_error').hide();
|
||||
|
||||
data.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 < data.length; i++) {
|
||||
// Assume the data is valid.
|
||||
voteFreq[(data[i] - min_points) / 100]++;
|
||||
max_number_of_votes = Math.max(max_number_of_votes, voteFreq[(data[i] - min_points) / 100]);
|
||||
mean += data[i];
|
||||
total_votes++;
|
||||
}
|
||||
mean = mean / total_votes;
|
||||
let half = Math.floor(total_votes / 2);
|
||||
let median = data[half];
|
||||
if (total_votes % 2 === 0) {
|
||||
median = (median + data[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 = data.indexOf(prev_voted_points);
|
||||
if (index > -1) {
|
||||
data.splice(index, 1);
|
||||
}
|
||||
data.push(voted_points);
|
||||
}
|
||||
|
||||
function deleteUserVote(prev_voted_points) {
|
||||
data.splice(data.indexOf(prev_voted_points), 1);
|
||||
}
|
||||
$(function() {
|
||||
$('#id_vote_stats_button').featherlight('#id_vote_stats', {
|
||||
afterOpen: reload_vote_graph,
|
||||
})
|
||||
});
|
||||
</script>
|
Loading…
Reference in a new issue