commit
7c2c6fea70
21 changed files with 724 additions and 124 deletions
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,7 @@ else:
|
||||||
'children': [
|
'children': [
|
||||||
'judge.ProblemGroup',
|
'judge.ProblemGroup',
|
||||||
'judge.ProblemType',
|
'judge.ProblemType',
|
||||||
|
'judge.ProblemPointsVote',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
@ -162,3 +162,9 @@ class ContestCloneForm(Form):
|
||||||
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']
|
18
judge/migrations/0119_auto_20220306_0512.py
Normal file
18
judge/migrations/0119_auto_20220306_0512.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
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.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
|
||||||
|
|
|
@ -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 '
|
||||||
|
|
|
@ -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}'
|
|
@ -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.'))
|
||||||
|
|
|
@ -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,9 +26,9 @@ 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
|
||||||
|
@ -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'
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
75
templates/problem/voting-controls.html
Normal file
75
templates/problem/voting-controls.html
Normal 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 %}
|
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>{{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>
|
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 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>
|
Loading…
Reference in a new issue