mirror of
https://github.com/DMOJ/online-judge.git
synced 2024-11-25 16:32:37 +08:00
Add view for problem point voting
This commit is contained in:
parent
7b71dbbd3e
commit
8f259d1d97
@ -123,6 +123,10 @@ urlpatterns = [
|
|||||||
path('/tickets', ticket.ProblemTicketListView.as_view(), name='problem_ticket_list'),
|
path('/tickets', ticket.ProblemTicketListView.as_view(), name='problem_ticket_list'),
|
||||||
path('/tickets/new', ticket.NewProblemTicketView.as_view(), name='new_problem_ticket'),
|
path('/tickets/new', ticket.NewProblemTicketView.as_view(), name='new_problem_ticket'),
|
||||||
|
|
||||||
|
path('/vote', problem.ProblemVote.as_view(), name='problem_vote'),
|
||||||
|
path('/vote/delete', problem.DeleteProblemVote.as_view(), name='delete_problem_vote'),
|
||||||
|
path('/vote/stats', problem.ProblemVoteStats.as_view(), name='problem_vote_stats'),
|
||||||
|
|
||||||
path('/manage/submission', include([
|
path('/manage/submission', include([
|
||||||
path('', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
|
path('', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
|
||||||
path('/rejudge', problem_manage.RejudgeSubmissionsView.as_view(), name='problem_submissions_rejudge'),
|
path('/rejudge', problem_manage.RejudgeSubmissionsView.as_view(), name='problem_submissions_rejudge'),
|
||||||
|
@ -15,7 +15,8 @@ from django.utils.text import format_lazy
|
|||||||
from django.utils.translation import gettext_lazy as _, ngettext_lazy
|
from django.utils.translation import gettext_lazy as _, ngettext_lazy
|
||||||
|
|
||||||
from django_ace import AceWidget
|
from django_ace import AceWidget
|
||||||
from judge.models import Contest, Language, Organization, Problem, Profile, Submission, WebAuthnCredential
|
from judge.models import Contest, Language, Organization, Problem, ProblemPointsVote, Profile, Submission, \
|
||||||
|
WebAuthnCredential
|
||||||
from judge.utils.subscription import newsletter_id
|
from judge.utils.subscription import newsletter_id
|
||||||
from judge.widgets import HeavyPreviewPageDownWidget, Select2MultipleWidget, Select2Widget
|
from judge.widgets import HeavyPreviewPageDownWidget, Select2MultipleWidget, Select2Widget
|
||||||
|
|
||||||
@ -284,3 +285,11 @@ 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):
|
||||||
|
note = CharField(max_length=8192, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProblemPointsVote
|
||||||
|
fields = ['points', 'note']
|
||||||
|
@ -5,6 +5,7 @@ import shutil
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
from statistics import mean, median
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||||
@ -13,7 +14,7 @@ from django.db import transaction
|
|||||||
from django.db.models import BooleanField, Case, CharField, Count, F, FilteredRelation, Prefetch, Q, When
|
from django.db.models import BooleanField, Case, CharField, Count, F, FilteredRelation, Prefetch, Q, When
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
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
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -22,13 +23,13 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.html import escape, format_html
|
from django.utils.html import escape, format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext as _, gettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
from django.views.generic import ListView, View
|
from django.views.generic import DetailView, ListView, View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from reversion import revisions
|
from reversion import revisions
|
||||||
|
|
||||||
from judge.comments import CommentedDetailView
|
from judge.comments import CommentedDetailView
|
||||||
from judge.forms import ProblemCloneForm, ProblemSubmitForm
|
from judge.forms import ProblemCloneForm, ProblemPointsVoteForm, ProblemSubmitForm
|
||||||
from judge.models import ContestSubmission, Judge, Language, Problem, ProblemGroup, \
|
from judge.models import ContestSubmission, Judge, Language, Problem, ProblemGroup, ProblemPointsVote, \
|
||||||
ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource
|
ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource
|
||||||
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
|
||||||
@ -201,6 +202,89 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
|
|||||||
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['vote_perm'] = self.object.vote_permission_for_user(user)
|
||||||
|
if context['vote_perm'].can_vote():
|
||||||
|
try:
|
||||||
|
context['vote'] = ProblemPointsVote.objects.get(voter=user.profile, problem=self.object)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
context['vote'] = None
|
||||||
|
else:
|
||||||
|
context['vote'] = None
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemVote(ProblemMixin, DetailView):
|
||||||
|
context_object_name = 'problem'
|
||||||
|
template_name = 'problem/vote-ajax.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if not self.object.vote_permission_for_user(self.request.user).can_vote():
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
context['vote'] = ProblemPointsVote.objects.get(voter=self.request.profile, problem=self.object)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
context['vote'] = None
|
||||||
|
|
||||||
|
context['max_possible_vote'] = settings.DMOJ_PROBLEM_MAX_USER_POINTS_VOTE
|
||||||
|
context['min_possible_vote'] = settings.DMOJ_PROBLEM_MIN_USER_POINTS_VOTE
|
||||||
|
return context
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
problem = self.get_object()
|
||||||
|
if not problem.vote_permission_for_user(request.user).can_vote():
|
||||||
|
return JsonResponse({'message': _('Not allowed to vote on this problem.')}, status=403)
|
||||||
|
|
||||||
|
form = ProblemPointsVoteForm(request.POST)
|
||||||
|
if not form.is_valid():
|
||||||
|
return JsonResponse(form.errors, status=400)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# Delete any pre-existing votes.
|
||||||
|
ProblemPointsVote.objects.filter(voter=request.profile, problem=problem).delete()
|
||||||
|
vote = form.save(commit=False)
|
||||||
|
vote.voter = request.profile
|
||||||
|
vote.problem = problem
|
||||||
|
vote.save()
|
||||||
|
|
||||||
|
return JsonResponse({'points': vote.points})
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteProblemVote(ProblemMixin, SingleObjectMixin, View):
|
||||||
|
http_method_names = ['options', 'post'] # This disables GET requests, even though ProblemMixin.get exists.
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
problem = self.get_object()
|
||||||
|
if not problem.vote_permission_for_user(request.user).can_vote():
|
||||||
|
return JsonResponse({'message': _('Not allowed to delete votes on this problem.')}, status=403)
|
||||||
|
|
||||||
|
ProblemPointsVote.objects.filter(voter=request.profile, problem=problem).delete()
|
||||||
|
return JsonResponse({'message': _('success')})
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemVoteStats(ProblemMixin, DetailView):
|
||||||
|
context_object_name = 'problem'
|
||||||
|
template_name = 'problem/vote-stats-ajax.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if not self.object.vote_permission_for_user(self.request.user).can_view():
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
votes = list(self.object.problem_points_votes.order_by('points').values_list('points', flat=True))
|
||||||
|
context['votes'] = votes
|
||||||
|
|
||||||
|
if votes:
|
||||||
|
context['mean'] = mean(votes)
|
||||||
|
context['median'] = median(votes)
|
||||||
|
|
||||||
|
context['max_possible_vote'] = settings.DMOJ_PROBLEM_MAX_USER_POINTS_VOTE
|
||||||
|
context['min_possible_vote'] = settings.DMOJ_PROBLEM_MIN_USER_POINTS_VOTE
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,6 +36,9 @@ Disallow: /problem/*/submissions
|
|||||||
Disallow: /problem/*/submit
|
Disallow: /problem/*/submit
|
||||||
Disallow: /problem/*/test_data
|
Disallow: /problem/*/test_data
|
||||||
Disallow: /problem/*/tickets
|
Disallow: /problem/*/tickets
|
||||||
|
Disallow: /problem/*/vote
|
||||||
|
Disallow: /problem/*/vote/delete
|
||||||
|
Disallow: /problem/*/vote/stats
|
||||||
Disallow: /src
|
Disallow: /src
|
||||||
Disallow: /stats
|
Disallow: /stats
|
||||||
Disallow: /submission
|
Disallow: /submission
|
||||||
|
Loading…
Reference in New Issue
Block a user