Add problem volunteer
This commit is contained in:
parent
e51129d36f
commit
e70618ed19
15 changed files with 396 additions and 5 deletions
11
dmoj/urls.py
11
dmoj/urls.py
|
@ -20,7 +20,7 @@ from judge.sitemap import BlogPostSitemap, ContestSitemap, HomePageSitemap, Orga
|
||||||
SolutionSitemap, UrlSitemap, UserSitemap
|
SolutionSitemap, UrlSitemap, UserSitemap
|
||||||
from judge.views import TitledTemplateView, about, api, blog, comment, contests, language, license, mailgun, \
|
from judge.views import TitledTemplateView, about, api, blog, comment, contests, language, license, mailgun, \
|
||||||
notification, organization, preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tasks, \
|
notification, organization, preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tasks, \
|
||||||
ticket, totp, user, widgets
|
ticket, totp, user, volunteer, widgets, internal
|
||||||
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
|
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
|
||||||
problem_data_file, problem_init_view, ProblemZipUploadView
|
problem_data_file, problem_init_view, ProblemZipUploadView
|
||||||
from judge.views.register import ActivationView, RegistrationView
|
from judge.views.register import ActivationView, RegistrationView
|
||||||
|
@ -118,6 +118,7 @@ urlpatterns = [
|
||||||
url(r'^problems/random/$', problem.RandomProblem.as_view(), name='problem_random'),
|
url(r'^problems/random/$', problem.RandomProblem.as_view(), name='problem_random'),
|
||||||
url(r'^problems/feed/', paged_list_view(problem.ProblemFeed, 'problem_feed', feed_type='for_you')),
|
url(r'^problems/feed/', paged_list_view(problem.ProblemFeed, 'problem_feed', feed_type='for_you')),
|
||||||
url(r'^problems/feed/new/', paged_list_view(problem.ProblemFeed, 'problem_feed_new', feed_type='new')),
|
url(r'^problems/feed/new/', paged_list_view(problem.ProblemFeed, 'problem_feed_new', feed_type='new')),
|
||||||
|
url(r'^problems/feed/volunteer/', paged_list_view(problem.ProblemFeed, 'problem_feed_volunteer', feed_type='volunteer')),
|
||||||
|
|
||||||
url(r'^problem/(?P<problem>[^/]+)', include([
|
url(r'^problem/(?P<problem>[^/]+)', include([
|
||||||
url(r'^$', problem.ProblemDetail.as_view(), name='problem_detail'),
|
url(r'^$', problem.ProblemDetail.as_view(), name='problem_detail'),
|
||||||
|
@ -396,6 +397,10 @@ urlpatterns = [
|
||||||
url(r'^get_unread_boxes$', chat.get_unread_boxes, name='get_unread_boxes'),
|
url(r'^get_unread_boxes$', chat.get_unread_boxes, name='get_unread_boxes'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
url(r'^internal/', include([
|
||||||
|
url(r'^problem$', internal.InternalProblem.as_view(), name='internal_problem'),
|
||||||
|
])),
|
||||||
|
|
||||||
url(r'^notifications/',
|
url(r'^notifications/',
|
||||||
login_required(notification.NotificationList.as_view()),
|
login_required(notification.NotificationList.as_view()),
|
||||||
name='notification'),
|
name='notification'),
|
||||||
|
@ -406,6 +411,10 @@ urlpatterns = [
|
||||||
url(r'submit/$', user.import_users_submit, name='import_users_submit'),
|
url(r'submit/$', user.import_users_submit, name='import_users_submit'),
|
||||||
url(r'sample/$', user.sample_import_users, name='import_users_sample')
|
url(r'sample/$', user.sample_import_users, name='import_users_sample')
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
url(r'^volunteer/', include([
|
||||||
|
url(r'^problem/vote$', volunteer.vote_problem, name='volunteer_problem_vote'),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
|
favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
|
||||||
|
|
|
@ -11,9 +11,11 @@ from judge.admin.runtime import JudgeAdmin, LanguageAdmin
|
||||||
from judge.admin.submission import SubmissionAdmin
|
from judge.admin.submission import SubmissionAdmin
|
||||||
from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
|
from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
|
||||||
from judge.admin.ticket import TicketAdmin
|
from judge.admin.ticket import TicketAdmin
|
||||||
|
from judge.admin.volunteer import VolunteerProblemVoteAdmin
|
||||||
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, ProblemPointsVote, ProblemType, Profile, Submission, Ticket
|
OrganizationRequest, Problem, ProblemGroup, ProblemPointsVote, ProblemType, Profile, Submission, Ticket, \
|
||||||
|
VolunteerProblemVote
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(BlogPost, BlogPostAdmin)
|
admin.site.register(BlogPost, BlogPostAdmin)
|
||||||
|
@ -37,3 +39,4 @@ 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)
|
||||||
admin.site.register(Ticket, TicketAdmin)
|
admin.site.register(Ticket, TicketAdmin)
|
||||||
|
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
|
18
judge/admin/volunteer.py
Normal file
18
judge/admin/volunteer.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||||
|
|
||||||
|
from judge.models import VolunteerProblemVote
|
||||||
|
|
||||||
|
class VolunteerProblemVoteAdmin(admin.ModelAdmin):
|
||||||
|
fields = ('voter', 'problem', 'time', 'thinking_points', 'knowledge_points', 'feedback')
|
||||||
|
readonly_fields = ('time', 'problem', 'voter')
|
||||||
|
list_display = ('voter', 'problem_link', 'time', 'thinking_points', 'knowledge_points', 'feedback')
|
||||||
|
date_hierarchy = 'time'
|
||||||
|
|
||||||
|
def problem_link(self, obj):
|
||||||
|
url = reverse('admin:judge_problem_change', args=(obj.problem.id,))
|
||||||
|
return format_html(f"<a href='{url}'>{obj.problem.code}</a>")
|
||||||
|
problem_link.short_description = _('Problem')
|
||||||
|
problem_link.admin_order_field = 'problem__code'
|
36
judge/migrations/0123_auto_20220502_2356.py
Normal file
36
judge/migrations/0123_auto_20220502_2356.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Generated by Django 2.2.25 on 2022-05-02 16:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0122_auto_20220425_1202'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='problem',
|
||||||
|
options={'permissions': (('see_private_problem', 'See hidden problems'), ('edit_own_problem', 'Edit own problems'), ('edit_all_problem', 'Edit all problems'), ('edit_public_problem', 'Edit all public problems'), ('clone_problem', 'Clone problem'), ('change_public_visibility', 'Change is_public field'), ('change_manually_managed', 'Change is_manually_managed field'), ('see_organization_problem', 'See organization-private problems'), ('suggest_problem_changes', 'Suggest changes to problem')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VolunteerProblemVote',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('time', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('knowledge_points', models.PositiveIntegerField(help_text='Points awarded by knowledge difficulty', verbose_name='knowledge points')),
|
||||||
|
('thinking_points', models.PositiveIntegerField(help_text='Points awarded by thinking difficulty', verbose_name='thinking points')),
|
||||||
|
('feedback', models.TextField(blank=True, verbose_name='feedback')),
|
||||||
|
('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volunteer_user_votes', to='judge.Problem')),
|
||||||
|
('types', models.ManyToManyField(help_text="The type of problem, as shown on the problem's page.", to='judge.ProblemType', verbose_name='problem types')),
|
||||||
|
('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volunteer_problem_votes', to='judge.Profile')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'volunteer vote',
|
||||||
|
'verbose_name_plural': 'volunteer votes',
|
||||||
|
'unique_together': {('voter', 'problem')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -14,6 +14,7 @@ from judge.models.profile import Organization, OrganizationRequest, Profile, Fri
|
||||||
from judge.models.runtime import Judge, Language, RuntimeVersion
|
from judge.models.runtime import Judge, Language, RuntimeVersion
|
||||||
from judge.models.submission import SUBMISSION_RESULT, Submission, SubmissionSource, SubmissionTestCase
|
from judge.models.submission import SUBMISSION_RESULT, Submission, SubmissionSource, SubmissionTestCase
|
||||||
from judge.models.ticket import Ticket, TicketMessage
|
from judge.models.ticket import Ticket, TicketMessage
|
||||||
|
from judge.models.volunteer import VolunteerProblemVote
|
||||||
|
|
||||||
revisions.register(Profile, exclude=['points', 'last_access', 'ip', 'rating'])
|
revisions.register(Profile, exclude=['points', 'last_access', 'ip', 'rating'])
|
||||||
revisions.register(Problem, follow=['language_limits'])
|
revisions.register(Problem, follow=['language_limits'])
|
||||||
|
|
|
@ -377,6 +377,7 @@ class Problem(models.Model):
|
||||||
save.alters_data = True
|
save.alters_data = True
|
||||||
|
|
||||||
def can_vote(self, request):
|
def can_vote(self, request):
|
||||||
|
return False
|
||||||
user = request.user
|
user = request.user
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
return False
|
return False
|
||||||
|
|
28
judge/models/volunteer.py
Normal file
28
judge/models/volunteer.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import CASCADE
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from judge.models import Profile, Problem, ProblemType
|
||||||
|
|
||||||
|
__all__ = ['VolunteerProblemVote']
|
||||||
|
|
||||||
|
class VolunteerProblemVote(models.Model):
|
||||||
|
voter = models.ForeignKey(Profile, related_name='volunteer_problem_votes', on_delete=CASCADE)
|
||||||
|
problem = models.ForeignKey(Problem, related_name='volunteer_user_votes', on_delete=CASCADE)
|
||||||
|
time = models.DateTimeField(auto_now_add=True)
|
||||||
|
knowledge_points = models.PositiveIntegerField(verbose_name=_('knowledge points'),
|
||||||
|
help_text=_('Points awarded by knowledge difficulty'))
|
||||||
|
thinking_points = models.PositiveIntegerField(verbose_name=_('thinking points'),
|
||||||
|
help_text=_('Points awarded by thinking difficulty'))
|
||||||
|
types = models.ManyToManyField(ProblemType, verbose_name=_('problem types'),
|
||||||
|
help_text=_('The type of problem, '
|
||||||
|
"as shown on the problem's page."))
|
||||||
|
feedback = models.TextField(verbose_name=_('feedback'), blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('volunteer vote')
|
||||||
|
verbose_name_plural = _('volunteer votes')
|
||||||
|
unique_together = ['voter', 'problem']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.voter} for {self.problem.code}'
|
35
judge/views/internal.py
Normal file
35
judge/views/internal.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.utils.translation import gettext as _, gettext_lazy
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.http import HttpResponseForbidden
|
||||||
|
|
||||||
|
from judge.utils.diggpaginator import DiggPaginator
|
||||||
|
from judge.models import VolunteerProblemVote, Problem
|
||||||
|
|
||||||
|
class InternalProblem(ListView):
|
||||||
|
model = Problem
|
||||||
|
title = _('Internal problems')
|
||||||
|
template_name = 'internal/base.html'
|
||||||
|
paginate_by = 100
|
||||||
|
context_object_name = 'problems'
|
||||||
|
|
||||||
|
def get_paginator(self, queryset, per_page, orphans=0,
|
||||||
|
allow_empty_first_page=True, **kwargs):
|
||||||
|
return DiggPaginator(queryset, per_page, body=6, padding=2, orphans=orphans,
|
||||||
|
allow_empty_first_page=allow_empty_first_page, **kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = Problem.objects.annotate(vote_count=Count('volunteer_user_votes')) \
|
||||||
|
.filter(vote_count__gte=1).order_by('-vote_count')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(InternalProblem, self).get_context_data(**kwargs)
|
||||||
|
context['page_type'] = 'problem'
|
||||||
|
context['title'] = self.title
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return super(InternalProblem, self).get(request, *args, **kwargs)
|
||||||
|
return HttpResponseForbidden()
|
|
@ -30,7 +30,7 @@ from judge.comments import CommentedDetailView
|
||||||
from judge.forms import ProblemCloneForm, ProblemSubmitForm, ProblemPointsVoteForm
|
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, ProblemPointsVote, RuntimeVersion, Solution, Submission, SubmissionSource, \
|
ProblemGroup, ProblemTranslation, ProblemType, ProblemPointsVote, RuntimeVersion, Solution, Submission, SubmissionSource, \
|
||||||
TranslatedProblemForeignKeyQuerySet, Organization
|
TranslatedProblemForeignKeyQuerySet, Organization , VolunteerProblemVote
|
||||||
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
|
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
|
||||||
from judge.utils.diggpaginator import DiggPaginator
|
from judge.utils.diggpaginator import DiggPaginator
|
||||||
from judge.utils.opengraph import generate_opengraph
|
from judge.utils.opengraph import generate_opengraph
|
||||||
|
@ -594,6 +594,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
|
|
||||||
cf_logger = logging.getLogger('judge.ml.collab_filter')
|
cf_logger = logging.getLogger('judge.ml.collab_filter')
|
||||||
|
|
||||||
|
|
||||||
class ProblemFeed(ProblemList):
|
class ProblemFeed(ProblemList):
|
||||||
model = Problem
|
model = Problem
|
||||||
context_object_name = 'problems'
|
context_object_name = 'problems'
|
||||||
|
@ -640,6 +641,9 @@ class ProblemFeed(ProblemList):
|
||||||
|
|
||||||
if self.feed_type == 'new':
|
if self.feed_type == 'new':
|
||||||
return queryset.order_by('-date')
|
return queryset.order_by('-date')
|
||||||
|
elif user and self.feed_type == 'volunteer':
|
||||||
|
voted_problems = user.volunteer_problem_votes.values_list('problem', flat=True)
|
||||||
|
return queryset.exclude(id__in=voted_problems).order_by('?')
|
||||||
if not settings.ML_OUTPUT_PATH or not user:
|
if not settings.ML_OUTPUT_PATH or not user:
|
||||||
return queryset.order_by('?')
|
return queryset.order_by('?')
|
||||||
|
|
||||||
|
|
33
judge/views/volunteer.py
Normal file
33
judge/views/volunteer.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from django.http import HttpResponseBadRequest, JsonResponse
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from judge.models import VolunteerProblemVote, Problem, ProblemType
|
||||||
|
|
||||||
|
|
||||||
|
def vote_problem(request):
|
||||||
|
if not request.user or not request.user.has_perm('judge.suggest_problem_changes'):
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
if not request.method == 'POST':
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
try:
|
||||||
|
types_id = request.POST.getlist('types[]')
|
||||||
|
types = ProblemType.objects.filter(id__in=types_id)
|
||||||
|
problem = Problem.objects.get(code=request.POST['problem'])
|
||||||
|
knowledge_points = request.POST['knowledge_points']
|
||||||
|
thinking_points = request.POST['thinking_points']
|
||||||
|
feedback = request.POST['feedback']
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
vote, _ = VolunteerProblemVote.objects.get_or_create(
|
||||||
|
voter=request.profile,
|
||||||
|
problem=problem,
|
||||||
|
defaults={'knowledge_points': 0, 'thinking_points': 0},
|
||||||
|
)
|
||||||
|
vote.knowledge_points = knowledge_points
|
||||||
|
vote.thinking_points = thinking_points
|
||||||
|
vote.feedback = feedback
|
||||||
|
vote.types.set(types)
|
||||||
|
vote.save()
|
||||||
|
return JsonResponse({})
|
|
@ -260,6 +260,9 @@
|
||||||
{% if request.user.is_staff or request.user.is_superuser %}
|
{% if request.user.is_staff or request.user.is_superuser %}
|
||||||
<li><a href="{{ url('admin:index') }}">{{ _('Admin') }}</a></li>
|
<li><a href="{{ url('admin:index') }}">{{ _('Admin') }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
<li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li>
|
||||||
|
{% endif %}
|
||||||
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
|
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
|
||||||
{% if request.user.is_impersonate %}
|
{% if request.user.is_impersonate %}
|
||||||
<li><a href="{{ url('impersonate-stop') }}">Stop impersonating</a></li>
|
<li><a href="{{ url('impersonate-stop') }}">Stop impersonating</a></li>
|
||||||
|
|
|
@ -214,7 +214,6 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="content-description">
|
<div class="content-description">
|
||||||
|
|
||||||
<form id="filter-form">
|
<form id="filter-form">
|
||||||
<input id="search-contest" type="text" name="contest" value="{{ contest_query or '' }}"
|
<input id="search-contest" type="text" name="contest" value="{{ contest_query or '' }}"
|
||||||
placeholder="{{ _('Search contests...') }}">
|
placeholder="{{ _('Search contests...') }}">
|
||||||
|
|
92
templates/internal/base.html
Normal file
92
templates/internal/base.html
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
{% extends "three-column-content.html" %}
|
||||||
|
{% block three_col_media %}
|
||||||
|
<style>
|
||||||
|
.middle-content {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block three_col_js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('.vote-detail').each(function() {
|
||||||
|
$(this).on('click', function() {
|
||||||
|
var pid = $(this).attr('pid');
|
||||||
|
$('.detail').hide();
|
||||||
|
$('#detail-'+pid).show();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block left_sidebar %}
|
||||||
|
<div class="left-sidebar">
|
||||||
|
{{ make_tab_item('problem', 'fa fa-list', url('internal_problem'), _('Problem')) }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block middle_content %}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{_('Problem')}}</th>
|
||||||
|
<th>{{_('Code')}}</th>
|
||||||
|
<th>{{_('Vote count')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for problem in problems %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{url('problem_detail', problem.code)}}">{{problem.name}}</a></td>
|
||||||
|
<td><a href="{{url('admin:judge_problem_change', problem.id)}}">{{problem.code}}</a></td>
|
||||||
|
<td><a href="#" class="vote-detail" pid="{{problem.id}}">{{problem.vote_count}}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if page_obj.num_pages > 1 %}
|
||||||
|
<div style="margin-top:10px;">{% include "list-pages.html" %}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block right_sidebar %}
|
||||||
|
<div style="display: block; width: 100%">
|
||||||
|
<div><a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a></div>
|
||||||
|
{% for problem in problems %}
|
||||||
|
<div class="detail" id="detail-{{problem.id}}" style="display: none;">
|
||||||
|
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
|
||||||
|
<ol>
|
||||||
|
{% for vote in problem.volunteer_user_votes.order_by('id') %}
|
||||||
|
<li>
|
||||||
|
<h4> {{link_user(vote.voter)}} </h4>
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:10%">{{_('Knowledge')}}</td>
|
||||||
|
<td>{{vote.knowledge_points}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{_('Thinking')}}</td>
|
||||||
|
<td>{{vote.thinking_points}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{_('Types')}}</td>
|
||||||
|
<td>{{vote.types.all() | join(', ')}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{_('Feedback')}}</td>
|
||||||
|
<td>{{vote.feedback}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -31,5 +31,68 @@
|
||||||
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
|
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
|
||||||
{{ problem.description|markdown("problem", MATH_ENGINE)|reference|str|safe }}
|
{{ problem.description|markdown("problem", MATH_ENGINE)|reference|str|safe }}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
|
{% if feed_type=='volunteer' and request.user.has_perm('judge.suggest_problem_changes') %}
|
||||||
|
<hr>
|
||||||
|
<center><h3>{{_('Volunteer form')}}</h3></center>
|
||||||
|
<br>
|
||||||
|
<button class="edit-btn" id="edit-{{problem.id}}" pid="{{problem.id}}" style="float: right">{{_('Edit')}}</button>
|
||||||
|
<form class="volunteer-form" id="form-{{problem.id}}" pid="{{problem.id}}" style="display: none;" method="POST">
|
||||||
|
<input type="submit" class="volunteer-submit-btn" id="submit-{{problem.id}}" pid="{{problem.id}}" pcode="{{problem.code}}" style="float: right" value="{{_('Submit')}}">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{{_('Field')}}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{{_('Value')}}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="30%">
|
||||||
|
<label for="knowledge_point-{{problem.id}}"><i>{{ _('Knowledge point') }}</i></label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="knowledge_point-{{problem.id}}" type="number" class="point-input" required>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="30%">
|
||||||
|
<label for="thinking_point-{{problem.id}}"><i>{{ _('Thinking point') }}</i></label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="thinking_point-{{problem.id}}" type="number" class="point-input" required>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="30%">
|
||||||
|
<label for="types"><i>{{ _('Problem types') }}</i></label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select id="volunteer-types-{{problem.id}}" name="types" multiple>
|
||||||
|
{% for type in problem_types %}
|
||||||
|
<option value="{{ type.id }}"{% if type in problem.types.all() %} selected{% endif %}>
|
||||||
|
{{ type.full_name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="30%">
|
||||||
|
<label for="feedback"><i>{{ _('Feedback') }}</i></label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea id="feedback-{{problem.id}}" rows="2" style="width: 100%" placeholder="{{_('Any additional note here')}}"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<center id="thank-{{problem.id}}" style="display: none; margin-top: 3em"></center>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
|
@ -37,6 +37,14 @@
|
||||||
width: 99%;
|
width: 99%;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
.volunteer-types {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-input {
|
||||||
|
height: 2em;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -151,6 +159,59 @@
|
||||||
$end.prop('disabled', end === point_values.max).val(end);
|
$end.prop('disabled', end === point_values.max).val(end);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
{% if feed_type=='volunteer' and request.user.has_perm('judge.suggest_problem_changes') %}
|
||||||
|
$(".edit-btn").on('click', function() {
|
||||||
|
var pid = $(this).attr('pid');
|
||||||
|
$('#volunteer-types-' + pid).css({'width': '100%'});
|
||||||
|
$('#volunteer-types-' + pid).select2({multiple: 1, placeholder: '{{ _('Add types...') }}'})
|
||||||
|
.css({'visibility': 'visible'});
|
||||||
|
|
||||||
|
$('#form-' + pid).show();
|
||||||
|
$('#submit-' + pid).show();
|
||||||
|
$(this).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
let isChecking = false;
|
||||||
|
$(".volunteer-submit-btn").on('click', function(e) {
|
||||||
|
var pid = $(this).attr('pid');
|
||||||
|
var pcode = $(this).attr('pcode');
|
||||||
|
var $form = $('#form-' + pid);
|
||||||
|
|
||||||
|
if (!$form[0].checkValidity()) {
|
||||||
|
if (isChecking) return;
|
||||||
|
isChecking = true;
|
||||||
|
// The form won't actually submit;
|
||||||
|
$(this).click();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isChecking = false;
|
||||||
|
}
|
||||||
|
if (isChecking) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
$('#volunteer-types-' + pid).select2({multiple: 1, placeholder: '{{ _('Add types...') }}'})
|
||||||
|
.css({'visibility': 'visible'});
|
||||||
|
$('#form-' + pid).hide();
|
||||||
|
$('#edit-' + pid).show();
|
||||||
|
$('#thank-' + pid).show();
|
||||||
|
$(this).hide();
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
problem: pcode,
|
||||||
|
types: $('#volunteer-types-' + pid).val(),
|
||||||
|
knowledge_points: $('#knowledge_point-' + pid).val(),
|
||||||
|
thinking_points: $('#thinking_point-' + pid).val(),
|
||||||
|
feedback: $('#feedback-' + pid).val(),
|
||||||
|
};
|
||||||
|
$.post("{{url('volunteer_problem_vote')}}", data)
|
||||||
|
.fail(function() {
|
||||||
|
$('#thank-' + pid).html("{{_('Fail to vote!')}}");
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
$('#thank-' + pid).html("{{_('Successful vote! Thank you!')}}");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
@ -381,6 +442,11 @@
|
||||||
<a href="{{url('problem_feed_new')}}" class="problem-feed-option-item {{'active' if feed_type=='new'}}">
|
<a href="{{url('problem_feed_new')}}" class="problem-feed-option-item {{'active' if feed_type=='new'}}">
|
||||||
{{_('NEW')}}
|
{{_('NEW')}}
|
||||||
</a>
|
</a>
|
||||||
|
{% if request.user.has_perm('judge.suggest_problem_changes') %}
|
||||||
|
<a href="{{url('problem_feed_volunteer')}}" class="problem-feed-option-item {{'active' if feed_type=='volunteer'}}">
|
||||||
|
{{_('VOLUNTEER')}}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% for problem in problems %}
|
{% for problem in problems %}
|
||||||
{% include "problem/feed.html" %}
|
{% include "problem/feed.html" %}
|
||||||
|
|
Loading…
Reference in a new issue