NDOJ/judge/admin/problem.py

304 lines
12 KiB
Python
Raw Normal View History

2020-01-21 06:35:58 +00:00
from operator import attrgetter
2022-04-04 22:43:56 +00:00
import statistics
2020-01-21 06:35:58 +00:00
from django import forms
from django.contrib import admin
from django.db import transaction
2022-04-04 22:13:23 +00:00
from django.db.models import Q, Avg, Count
2022-04-04 22:04:40 +00:00
from django.db.models.aggregates import StdDev
2020-01-21 06:35:58 +00:00
from django.forms import ModelForm
from django.urls import reverse_lazy
from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _, ungettext
from reversion.admin import VersionAdmin
from judge.models import LanguageLimit, Problem, ProblemClarification, ProblemTranslation, Profile, Solution
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminSelect2MultipleWidget, AdminSelect2Widget, \
CheckboxSelectMultipleWithSelectAll, HeavyPreviewAdminPageDownWidget, HeavyPreviewPageDownWidget
class ProblemForm(ModelForm):
change_message = forms.CharField(max_length=256, label='Edit reason', required=False)
def __init__(self, *args, **kwargs):
super(ProblemForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False
self.fields['curators'].widget.can_add_related = False
self.fields['testers'].widget.can_add_related = False
self.fields['banned_users'].widget.can_add_related = False
self.fields['change_message'].widget.attrs.update({
'placeholder': gettext('Describe the changes you made (optional)'),
})
class Meta:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
attrs={'style': 'width: 100%'}),
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2',
attrs={'style': 'width: 100%'}),
'types': AdminSelect2MultipleWidget,
'group': AdminSelect2Widget,
}
if HeavyPreviewAdminPageDownWidget is not None:
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview'))
class ProblemCreatorListFilter(admin.SimpleListFilter):
title = parameter_name = 'creator'
def lookups(self, request, model_admin):
queryset = Profile.objects.exclude(authored_problems=None).values_list('user__username', flat=True)
return [(name, name) for name in queryset]
def queryset(self, request, queryset):
if self.value() is None:
return queryset
return queryset.filter(authors__user__username=self.value())
class LanguageLimitInlineForm(ModelForm):
class Meta:
widgets = {'language': AdminSelect2Widget}
class LanguageLimitInline(admin.TabularInline):
model = LanguageLimit
fields = ('language', 'time_limit', 'memory_limit')
form = LanguageLimitInlineForm
class ProblemClarificationForm(ModelForm):
class Meta:
if HeavyPreviewPageDownWidget is not None:
widgets = {'description': HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'))}
class ProblemClarificationInline(admin.StackedInline):
model = ProblemClarification
fields = ('description',)
form = ProblemClarificationForm
extra = 0
class ProblemSolutionForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProblemSolutionForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False
class Meta:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
}
if HeavyPreviewAdminPageDownWidget is not None:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview'))
class ProblemSolutionInline(admin.StackedInline):
model = Solution
fields = ('is_public', 'publish_on', 'authors', 'content')
form = ProblemSolutionForm
extra = 0
class ProblemTranslationForm(ModelForm):
class Meta:
if HeavyPreviewAdminPageDownWidget is not None:
widgets = {'description': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview'))}
class ProblemTranslationInline(admin.StackedInline):
model = ProblemTranslation
fields = ('language', 'name', 'description')
form = ProblemTranslationForm
extra = 0
class ProblemAdmin(VersionAdmin):
fieldsets = (
(None, {
'fields': (
'code', 'name', 'is_public', 'is_manually_managed', 'date', 'authors', 'curators', 'testers',
'is_organization_private', 'organizations', 'description', 'license',
),
}),
(_('Social Media'), {'classes': ('collapse',), 'fields': ('og_image', 'summary')}),
(_('Taxonomy'), {'fields': ('types', 'group')}),
(_('Points'), {'fields': (('points', 'partial'), 'short_circuit')}),
(_('Limits'), {'fields': ('time_limit', 'memory_limit')}),
(_('Language'), {'fields': ('allowed_languages',)}),
(_('Justice'), {'fields': ('banned_users',)}),
(_('History'), {'fields': ('change_message',)}),
)
2022-04-04 22:43:56 +00:00
list_display = ['code', 'name', 'show_authors', 'points', 'vote_cnt', 'vote_mean', 'vote_median', 'vote_std', 'is_public', 'show_public']
2020-01-21 06:35:58 +00:00
ordering = ['code']
search_fields = ('code', 'name', 'authors__user__username', 'curators__user__username')
inlines = [LanguageLimitInline, ProblemClarificationInline, ProblemSolutionInline, ProblemTranslationInline]
list_max_show_all = 1000
actions_on_top = True
actions_on_bottom = True
list_filter = ('is_public', ProblemCreatorListFilter)
form = ProblemForm
date_hierarchy = 'date'
def get_actions(self, request):
actions = super(ProblemAdmin, self).get_actions(request)
if request.user.has_perm('judge.change_public_visibility'):
func, name, desc = self.get_action('make_public')
actions[name] = (func, name, desc)
func, name, desc = self.get_action('make_private')
actions[name] = (func, name, desc)
return actions
def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
if not request.user.has_perm('judge.change_public_visibility'):
fields += ('is_public',)
if not request.user.has_perm('judge.change_manually_managed'):
fields += ('is_manually_managed',)
return fields
def show_authors(self, obj):
return ', '.join(map(attrgetter('user.username'), obj.authors.all()))
show_authors.short_description = _('Authors')
def show_public(self, obj):
return format_html('<a href="{1}">{0}</a>', gettext('View on site'), obj.get_absolute_url())
show_public.short_description = ''
def _rescore(self, request, problem_id):
from judge.tasks import rescore_problem
transaction.on_commit(rescore_problem.s(problem_id).delay)
def make_public(self, request, queryset):
count = queryset.update(is_public=True)
for problem_id in queryset.values_list('id', flat=True):
self._rescore(request, problem_id)
self.message_user(request, ungettext('%d problem successfully marked as public.',
'%d problems successfully marked as public.',
count) % count)
make_public.short_description = _('Mark problems as public')
def make_private(self, request, queryset):
count = queryset.update(is_public=False)
for problem_id in queryset.values_list('id', flat=True):
self._rescore(request, problem_id)
self.message_user(request, ungettext('%d problem successfully marked as private.',
'%d problems successfully marked as private.',
count) % count)
make_private.short_description = _('Mark problems as private')
def get_queryset(self, request):
queryset = Problem.objects.prefetch_related('authors__user')
2022-04-04 22:04:40 +00:00
queryset = queryset.annotate(
_vote_mean=Avg('problem_points_votes__points'),
2022-04-04 22:13:23 +00:00
_vote_std=StdDev('problem_points_votes__points'),
_vote_cnt=Count('problem_points_votes__points')
2022-04-04 22:04:40 +00:00
)
2020-01-21 06:35:58 +00:00
if request.user.has_perm('judge.edit_all_problem'):
return queryset
access = Q()
if request.user.has_perm('judge.edit_public_problem'):
access |= Q(is_public=True)
if request.user.has_perm('judge.edit_own_problem'):
access |= Q(authors__id=request.profile.id) | Q(curators__id=request.profile.id)
return queryset.filter(access).distinct() if access else queryset.none()
def has_change_permission(self, request, obj=None):
if request.user.has_perm('judge.edit_all_problem') or obj is None:
return True
if request.user.has_perm('judge.edit_public_problem') and obj.is_public:
return True
if not request.user.has_perm('judge.edit_own_problem'):
return False
return obj.is_editor(request.profile)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'allowed_languages':
kwargs['widget'] = CheckboxSelectMultipleWithSelectAll()
return super(ProblemAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
def get_form(self, *args, **kwargs):
form = super(ProblemAdmin, self).get_form(*args, **kwargs)
form.base_fields['authors'].queryset = Profile.objects.all()
return form
def save_model(self, request, obj, form, change):
2020-05-02 18:43:50 +00:00
super().save_model(request, obj, form, change)
2020-01-21 06:35:58 +00:00
if form.changed_data and any(f in form.changed_data for f in ('is_public', 'points', 'partial')):
self._rescore(request, obj.id)
def construct_change_message(self, request, form, *args, **kwargs):
if form.cleaned_data.get('change_message'):
return form.cleaned_data['change_message']
return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs)
2022-03-10 05:38:29 +00:00
2022-04-04 22:04:40 +00:00
def vote_mean(self, obj):
return round(obj._vote_mean, 1) if obj._vote_mean is not None else None
vote_mean.admin_order_field = '_vote_mean'
def vote_std(self, obj):
return round(obj._vote_std, 1) if obj._vote_std is not None else None
vote_std.admin_order_field = '_vote_std'
2022-04-04 22:13:23 +00:00
def vote_cnt(self, obj):
return obj._vote_cnt
vote_cnt.admin_order_field = '_vote_cnt'
2022-04-04 22:43:56 +00:00
def vote_median(self, obj):
votes = obj.problem_points_votes.values_list('points', flat=True)
return statistics.median(votes) if votes else None
2022-03-10 05:38:29 +00:00
class ProblemPointsVoteAdmin(admin.ModelAdmin):
2022-03-31 06:28:40 +00:00
list_display = ('vote_points', 'voter', 'voter_rating', 'voter_point', 'problem_name', 'problem_code', 'problem_points')
2022-03-10 17:37:18 +00:00
search_fields = ('voter__user__username', 'problem__code', 'problem__name')
2022-03-31 06:28:40 +00:00
readonly_fields = ('voter', 'problem', 'problem_code', 'problem_points', 'voter_rating', 'voter_point')
2022-03-10 05:38:29 +00:00
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)
2022-04-04 22:43:56 +00:00
def lookup_allowed(self, key, value):
return True
2022-03-10 17:27:52 +00:00
def problem_code(self, obj):
return obj.problem.code
2022-03-10 17:37:18 +00:00
problem_code.short_description = _('Problem code')
2022-03-10 17:27:52 +00:00
problem_code.admin_order_field = 'problem__code'
def problem_points(self, obj):
return obj.problem.points
2022-03-10 17:37:18 +00:00
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'
2022-03-10 17:27:52 +00:00
2022-03-31 06:28:40 +00:00
def voter_rating(self, obj):
return obj.voter.rating
voter_rating.short_description = _('Voter rating')
voter_rating.admin_order_field = 'voter__rating'
def voter_point(self, obj):
return round(obj.voter.performance_points)
2022-03-31 06:31:10 +00:00
voter_point.short_description = _('Voter point')
voter_point.admin_order_field = 'voter__performance_points'
2022-03-31 06:28:40 +00:00
2022-03-10 17:37:18 +00:00
def vote_points(self, obj):
return obj.points
vote_points.short_description = _('Vote')