From 2e3a45168e7bec372ca5fe3938f970046a493e3b Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 9 Mar 2022 23:38:29 -0600 Subject: [PATCH] Add problem vote --- dmoj/settings.py | 1 + dmoj/urls.py | 2 + judge/admin/__init__.py | 5 +- judge/admin/problem.py | 14 + judge/admin/profile.py | 2 +- judge/forms.py | 10 +- judge/migrations/0120_auto_20220306_1124.py | 34 ++ judge/models/__init__.py | 2 +- judge/models/problem.py | 45 +++ judge/models/profile.py | 5 + judge/views/problem.py | 65 +++- locale/vi/LC_MESSAGES/django.po | 297 +++++++++++------- .../admin/judge/problem/change_form.html | 5 + templates/problem/problem.html | 8 +- templates/problem/voting-controls.html | 73 +++++ templates/problem/voting-form.html | 93 ++++++ templates/problem/voting-stats.html | 146 +++++++++ 17 files changed, 685 insertions(+), 122 deletions(-) create mode 100644 judge/migrations/0120_auto_20220306_1124.py create mode 100644 templates/problem/voting-controls.html create mode 100644 templates/problem/voting-form.html create mode 100644 templates/problem/voting-stats.html diff --git a/dmoj/settings.py b/dmoj/settings.py index 86377a7..c01407d 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -162,6 +162,7 @@ else: 'children': [ 'judge.ProblemGroup', 'judge.ProblemType', + 'judge.ProblemPointsVote', ], }, { diff --git a/dmoj/urls.py b/dmoj/urls.py index ecc17b9..4869ab6 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -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'), diff --git a/judge/admin/__init__.py b/judge/admin/__init__.py index 51a7173..f079f32 100644 --- a/judge/admin/__init__.py +++ b/judge/admin/__init__.py @@ -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) diff --git a/judge/admin/problem.py b/judge/admin/problem.py index 164caa1..3e92e74 100644 --- a/judge/admin/problem.py +++ b/judge/admin/problem.py @@ -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',) \ No newline at end of file diff --git a/judge/admin/profile.py b/judge/admin/profile.py index e5fbf46..02a1f71 100644 --- a/judge/admin/profile.py +++ b/judge/admin/profile.py @@ -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', diff --git a/judge/forms.py b/judge/forms.py index 67ccd12..d704fc5 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -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 \ No newline at end of file + return key + + +class ProblemPointsVoteForm(ModelForm): + class Meta: + model = ProblemPointsVote + fields = ['points'] \ No newline at end of file diff --git a/judge/migrations/0120_auto_20220306_1124.py b/judge/migrations/0120_auto_20220306_1124.py new file mode 100644 index 0000000..849461c --- /dev/null +++ b/judge/migrations/0120_auto_20220306_1124.py @@ -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', + }, + ), + ] diff --git a/judge/models/__init__.py b/judge/models/__init__.py index 23e0734..a875103 100644 --- a/judge/models/__init__.py +++ b/judge/models/__init__.py @@ -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 diff --git a/judge/models/problem.py b/judge/models/problem.py index 4f7e545..28dd102 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -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}' \ No newline at end of file diff --git a/judge/models/profile.py b/judge/models/profile.py index 7e2dc7f..b00dc50 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -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.')) diff --git a/judge/views/problem.py b/judge/views/problem.py index e7f2763..78d538f 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -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' diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index b9d0339..36f657d 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -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 {0}" msgstr "Hướng dẫn cho {0}" -#: 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 {0}" msgstr "Thảo luận {0}" -#: 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, %(username)s." msgstr "Xin chào, %(username)s." -#: 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, %(username)s." 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." diff --git a/templates/admin/judge/problem/change_form.html b/templates/admin/judge/problem/change_form.html index 4d118f0..8731e7f 100644 --- a/templates/admin/judge/problem/change_form.html +++ b/templates/admin/judge/problem/change_form.html @@ -5,6 +5,7 @@ {% endblock extrahead %} @@ -15,6 +16,10 @@ href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}"> {% trans "View submissions" %} + {% endif %} {% endblock %} diff --git a/templates/problem/problem.html b/templates/problem/problem.html index a3719a0..c37ef86 100644 --- a/templates/problem/problem.html +++ b/templates/problem/problem.html @@ -164,7 +164,7 @@ {% endif %}
{{ _('All submissions') }}
{{ _('Best submissions') }}
- + {% if not contest_problem or not contest_problem.contest.use_clarifications %}
{{ _('Discuss') }}
@@ -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 %}
@@ -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 %} diff --git a/templates/problem/voting-controls.html b/templates/problem/voting-controls.html new file mode 100644 index 0000000..0d0d4ab --- /dev/null +++ b/templates/problem/voting-controls.html @@ -0,0 +1,73 @@ + + +{% if can_vote %} + + + + {% if request.user.is_superuser %} + - {% include 'problem/voting-stats.html' %} + {% endif %} + + + + +{% endif %} \ No newline at end of file diff --git a/templates/problem/voting-form.html b/templates/problem/voting-form.html new file mode 100644 index 0000000..0ed939e --- /dev/null +++ b/templates/problem/voting-form.html @@ -0,0 +1,93 @@ + +
+ {% csrf_token %} + + + + + + + + + + + + +
+ {{_('How difficult is this problem?')}} + +
+ + {% for i in range(1, (max_possible_vote - min_possible_vote) // 100 + 2) %} + + {% endfor %} + +
+
+ {{_('This helps us improve the site')}} + + {{_('Easy')}} + {{_('Hard')}} +
+
+ +
+
+ +
+
+ \ No newline at end of file diff --git a/templates/problem/voting-stats.html b/templates/problem/voting-stats.html new file mode 100644 index 0000000..57acd41 --- /dev/null +++ b/templates/problem/voting-stats.html @@ -0,0 +1,146 @@ + +{{ _('Statistics') }} + + + \ No newline at end of file