Cloned DMOJ
This commit is contained in:
parent
f623974b58
commit
49dc9ff10c
513 changed files with 132349 additions and 39 deletions
37
judge/admin/__init__.py
Normal file
37
judge/admin/__init__.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin.models import LogEntry
|
||||
|
||||
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.profile import ProfileAdmin
|
||||
from judge.admin.runtime import JudgeAdmin, LanguageAdmin
|
||||
from judge.admin.submission import SubmissionAdmin
|
||||
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
|
||||
|
||||
admin.site.register(BlogPost, BlogPostAdmin)
|
||||
admin.site.register(Comment, CommentAdmin)
|
||||
admin.site.register(CommentLock)
|
||||
admin.site.register(Contest, ContestAdmin)
|
||||
admin.site.register(ContestParticipation, ContestParticipationAdmin)
|
||||
admin.site.register(ContestTag, ContestTagAdmin)
|
||||
admin.site.register(Judge, JudgeAdmin)
|
||||
admin.site.register(Language, LanguageAdmin)
|
||||
admin.site.register(License, LicenseAdmin)
|
||||
admin.site.register(LogEntry, LogEntryAdmin)
|
||||
admin.site.register(MiscConfig)
|
||||
admin.site.register(NavigationBar, NavigationBarAdmin)
|
||||
admin.site.register(Organization, OrganizationAdmin)
|
||||
admin.site.register(OrganizationRequest, OrganizationRequestAdmin)
|
||||
admin.site.register(Problem, ProblemAdmin)
|
||||
admin.site.register(ProblemGroup, ProblemGroupAdmin)
|
||||
admin.site.register(ProblemType, ProblemTypeAdmin)
|
||||
admin.site.register(Profile, ProfileAdmin)
|
||||
admin.site.register(Submission, SubmissionAdmin)
|
||||
admin.site.register(Ticket, TicketAdmin)
|
64
judge/admin/comments.py
Normal file
64
judge/admin/comments.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from django.forms import ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _, ungettext
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import Comment
|
||||
from judge.widgets import AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
|
||||
|
||||
class CommentForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'author': AdminHeavySelect2Widget(data_view='profile_select2'),
|
||||
'parent': AdminHeavySelect2Widget(data_view='comment_select2'),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('comment_preview'))
|
||||
|
||||
|
||||
class CommentAdmin(VersionAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('author', 'page', 'parent', 'score', 'hidden')}),
|
||||
('Content', {'fields': ('body',)}),
|
||||
)
|
||||
list_display = ['author', 'linked_page', 'time']
|
||||
search_fields = ['author__user__username', 'page', 'body']
|
||||
actions = ['hide_comment', 'unhide_comment']
|
||||
list_filter = ['hidden']
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = CommentForm
|
||||
date_hierarchy = 'time'
|
||||
|
||||
def get_queryset(self, request):
|
||||
return Comment.objects.order_by('-time')
|
||||
|
||||
def hide_comment(self, request, queryset):
|
||||
count = queryset.update(hidden=True)
|
||||
self.message_user(request, ungettext('%d comment successfully hidden.',
|
||||
'%d comments successfully hidden.',
|
||||
count) % count)
|
||||
hide_comment.short_description = _('Hide comments')
|
||||
|
||||
def unhide_comment(self, request, queryset):
|
||||
count = queryset.update(hidden=False)
|
||||
self.message_user(request, ungettext('%d comment successfully unhidden.',
|
||||
'%d comments successfully unhidden.',
|
||||
count) % count)
|
||||
unhide_comment.short_description = _('Unhide comments')
|
||||
|
||||
def linked_page(self, obj):
|
||||
link = obj.link
|
||||
if link is not None:
|
||||
return format_html('<a href="{0}">{1}</a>', link, obj.page)
|
||||
else:
|
||||
return format_html('{0}', obj.page)
|
||||
linked_page.short_description = _('Associated page')
|
||||
linked_page.admin_order_field = 'page'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(CommentAdmin, self).save_model(request, obj, form, change)
|
||||
if obj.hidden:
|
||||
obj.get_descendants().update(hidden=obj.hidden)
|
269
judge/admin/contest.py
Normal file
269
judge/admin/contest.py
Normal file
|
@ -0,0 +1,269 @@
|
|||
from django.conf.urls import url
|
||||
from django.contrib import admin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import Q, TextField
|
||||
from django.forms import ModelForm, ModelMultipleChoiceField
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _, ungettext
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating
|
||||
from judge.ratings import rate_contest
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminPagedownWidget, \
|
||||
AdminSelect2MultipleWidget, AdminSelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
|
||||
|
||||
class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
|
||||
@property
|
||||
def is_hidden(self):
|
||||
return False
|
||||
|
||||
|
||||
class ContestTagForm(ModelForm):
|
||||
contests = ModelMultipleChoiceField(
|
||||
label=_('Included contests'),
|
||||
queryset=Contest.objects.all(),
|
||||
required=False,
|
||||
widget=AdminHeavySelect2MultipleWidget(data_view='contest_select2'))
|
||||
|
||||
|
||||
class ContestTagAdmin(admin.ModelAdmin):
|
||||
fields = ('name', 'color', 'description', 'contests')
|
||||
list_display = ('name', 'color')
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ContestTagForm
|
||||
|
||||
if AdminPagedownWidget is not None:
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminPagedownWidget},
|
||||
}
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ContestTagAdmin, self).save_model(request, obj, form, change)
|
||||
obj.contests.set(form.cleaned_data['contests'])
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(ContestTagAdmin, self).get_form(request, obj, **kwargs)
|
||||
if obj is not None:
|
||||
form.base_fields['contests'].initial = obj.contests.all()
|
||||
return form
|
||||
|
||||
|
||||
class ContestProblemInlineForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {'problem': AdminHeavySelect2Widget(data_view='problem_select2')}
|
||||
|
||||
|
||||
class ContestProblemInline(admin.TabularInline):
|
||||
model = ContestProblem
|
||||
verbose_name = _('Problem')
|
||||
verbose_name_plural = 'Problems'
|
||||
fields = ('problem', 'points', 'partial', 'is_pretested', 'max_submissions', 'output_prefix_override', 'order',
|
||||
'rejudge_column')
|
||||
readonly_fields = ('rejudge_column',)
|
||||
form = ContestProblemInlineForm
|
||||
|
||||
def rejudge_column(self, obj):
|
||||
if obj.id is None:
|
||||
return ''
|
||||
return format_html('<a class="button rejudge-link" href="{}">Rejudge</a>',
|
||||
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)))
|
||||
rejudge_column.short_description = ''
|
||||
|
||||
|
||||
class ContestForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ContestForm, self).__init__(*args, **kwargs)
|
||||
if 'rate_exclude' in self.fields:
|
||||
if self.instance and self.instance.id:
|
||||
self.fields['rate_exclude'].queryset = \
|
||||
Profile.objects.filter(contest_history__contest=self.instance).distinct()
|
||||
else:
|
||||
self.fields['rate_exclude'].queryset = Profile.objects.none()
|
||||
self.fields['banned_users'].widget.can_add_related = False
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ContestForm, self).clean()
|
||||
cleaned_data['banned_users'].filter(current_contest__contest=self.instance).update(current_contest=None)
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
'organizers': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
|
||||
'private_contestants': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
|
||||
attrs={'style': 'width: 100%'}),
|
||||
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2'),
|
||||
'tags': AdminSelect2MultipleWidget,
|
||||
'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
|
||||
attrs={'style': 'width: 100%'}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('contest_preview'))
|
||||
|
||||
|
||||
class ContestAdmin(VersionAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('key', 'name', 'organizers')}),
|
||||
(_('Settings'), {'fields': ('is_visible', 'use_clarifications', 'hide_problem_tags', 'hide_scoreboard',
|
||||
'run_pretests_only')}),
|
||||
(_('Scheduling'), {'fields': ('start_time', 'end_time', 'time_limit')}),
|
||||
(_('Details'), {'fields': ('description', 'og_image', 'logo_override_image', 'tags', 'summary')}),
|
||||
(_('Format'), {'fields': ('format_name', 'format_config')}),
|
||||
(_('Rating'), {'fields': ('is_rated', 'rate_all', 'rating_floor', 'rating_ceiling', 'rate_exclude')}),
|
||||
(_('Access'), {'fields': ('access_code', 'is_private', 'private_contestants', 'is_organization_private',
|
||||
'organizations')}),
|
||||
(_('Justice'), {'fields': ('banned_users',)}),
|
||||
)
|
||||
list_display = ('key', 'name', 'is_visible', 'is_rated', 'start_time', 'end_time', 'time_limit', 'user_count')
|
||||
actions = ['make_visible', 'make_hidden']
|
||||
inlines = [ContestProblemInline]
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ContestForm
|
||||
change_list_template = 'admin/judge/contest/change_list.html'
|
||||
filter_horizontal = ['rate_exclude']
|
||||
date_hierarchy = 'start_time'
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = Contest.objects.all()
|
||||
if request.user.has_perm('judge.edit_all_contest'):
|
||||
return queryset
|
||||
else:
|
||||
return queryset.filter(organizers__id=request.profile.id)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
readonly = []
|
||||
if not request.user.has_perm('judge.contest_rating'):
|
||||
readonly += ['is_rated', 'rate_all', 'rate_exclude']
|
||||
if not request.user.has_perm('judge.contest_access_code'):
|
||||
readonly += ['access_code']
|
||||
if not request.user.has_perm('judge.create_private_contest'):
|
||||
readonly += ['is_private', 'private_contestants', 'is_organization_private', 'organizations']
|
||||
return readonly
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not request.user.has_perm('judge.edit_own_contest'):
|
||||
return False
|
||||
if request.user.has_perm('judge.edit_all_contest') or obj is None:
|
||||
return True
|
||||
return obj.organizers.filter(id=request.profile.id).exists()
|
||||
|
||||
def make_visible(self, request, queryset):
|
||||
count = queryset.update(is_visible=True)
|
||||
self.message_user(request, ungettext('%d contest successfully marked as visible.',
|
||||
'%d contests successfully marked as visible.',
|
||||
count) % count)
|
||||
make_visible.short_description = _('Mark contests as visible')
|
||||
|
||||
def make_hidden(self, request, queryset):
|
||||
count = queryset.update(is_visible=False)
|
||||
self.message_user(request, ungettext('%d contest successfully marked as hidden.',
|
||||
'%d contests successfully marked as hidden.',
|
||||
count) % count)
|
||||
make_hidden.short_description = _('Mark contests as hidden')
|
||||
|
||||
def get_urls(self):
|
||||
return [
|
||||
url(r'^rate/all/$', self.rate_all_view, name='judge_contest_rate_all'),
|
||||
url(r'^(\d+)/rate/$', self.rate_view, name='judge_contest_rate'),
|
||||
url(r'^(\d+)/judge/(\d+)/$', self.rejudge_view, name='judge_contest_rejudge'),
|
||||
] + super(ContestAdmin, self).get_urls()
|
||||
|
||||
def rejudge_view(self, request, contest_id, problem_id):
|
||||
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
|
||||
for model in queryset:
|
||||
model.submission.judge(rejudge=True)
|
||||
|
||||
self.message_user(request, ungettext('%d submission was successfully scheduled for rejudging.',
|
||||
'%d submissions were successfully scheduled for rejudging.',
|
||||
len(queryset)) % len(queryset))
|
||||
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
|
||||
|
||||
def rate_all_view(self, request):
|
||||
if not request.user.has_perm('judge.contest_rating'):
|
||||
raise PermissionDenied()
|
||||
with transaction.atomic():
|
||||
if connection.vendor == 'sqlite':
|
||||
Rating.objects.all().delete()
|
||||
else:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table)
|
||||
cursor.close()
|
||||
Profile.objects.update(rating=None)
|
||||
for contest in Contest.objects.filter(is_rated=True).order_by('end_time'):
|
||||
rate_contest(contest)
|
||||
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))
|
||||
|
||||
def rate_view(self, request, id):
|
||||
if not request.user.has_perm('judge.contest_rating'):
|
||||
raise PermissionDenied()
|
||||
contest = get_object_or_404(Contest, id=id)
|
||||
if not contest.is_rated:
|
||||
raise Http404()
|
||||
with transaction.atomic():
|
||||
contest.rate()
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse('admin:judge_contest_changelist')))
|
||||
|
||||
def get_form(self, *args, **kwargs):
|
||||
form = super(ContestAdmin, self).get_form(*args, **kwargs)
|
||||
perms = ('edit_own_contest', 'edit_all_contest')
|
||||
form.base_fields['organizers'].queryset = Profile.objects.filter(
|
||||
Q(user__is_superuser=True) |
|
||||
Q(user__groups__permissions__codename__in=perms) |
|
||||
Q(user__user_permissions__codename__in=perms),
|
||||
).distinct()
|
||||
return form
|
||||
|
||||
|
||||
class ContestParticipationForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'contest': AdminSelect2Widget(),
|
||||
'user': AdminHeavySelect2Widget(data_view='profile_select2'),
|
||||
}
|
||||
|
||||
|
||||
class ContestParticipationAdmin(admin.ModelAdmin):
|
||||
fields = ('contest', 'user', 'real_start', 'virtual', 'is_disqualified')
|
||||
list_display = ('contest', 'username', 'show_virtual', 'real_start', 'score', 'cumtime')
|
||||
actions = ['recalculate_results']
|
||||
actions_on_bottom = actions_on_top = True
|
||||
search_fields = ('contest__key', 'contest__name', 'user__user__username')
|
||||
form = ContestParticipationForm
|
||||
date_hierarchy = 'real_start'
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(ContestParticipationAdmin, self).get_queryset(request).only(
|
||||
'contest__name', 'contest__format_name', 'contest__format_config',
|
||||
'user__user__username', 'real_start', 'score', 'cumtime', 'virtual',
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
if form.changed_data and 'is_disqualified' in form.changed_data:
|
||||
obj.set_disqualified(obj.is_disqualified)
|
||||
|
||||
def recalculate_results(self, request, queryset):
|
||||
count = 0
|
||||
for participation in queryset:
|
||||
participation.recompute_results()
|
||||
count += 1
|
||||
self.message_user(request, ungettext('%d participation recalculated.',
|
||||
'%d participations recalculated.',
|
||||
count) % count)
|
||||
recalculate_results.short_description = _('Recalculate results')
|
||||
|
||||
def username(self, obj):
|
||||
return obj.user.username
|
||||
username.short_description = _('username')
|
||||
username.admin_order_field = 'user__user__username'
|
||||
|
||||
def show_virtual(self, obj):
|
||||
return obj.virtual or '-'
|
||||
show_virtual.short_description = _('virtual')
|
||||
show_virtual.admin_order_field = 'virtual'
|
151
judge/admin/interface.py
Normal file
151
judge/admin/interface.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
from django.forms import ModelForm
|
||||
from django.urls import NoReverseMatch, reverse, reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from mptt.admin import DraggableMPTTAdmin
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.dblock import LockModel
|
||||
from judge.models import NavigationBar
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
|
||||
|
||||
class NavigationBarAdmin(DraggableMPTTAdmin):
|
||||
list_display = DraggableMPTTAdmin.list_display + ('key', 'linked_path')
|
||||
fields = ('key', 'label', 'path', 'order', 'regex', 'parent')
|
||||
list_editable = () # Bug in SortableModelAdmin: 500 without list_editable being set
|
||||
mptt_level_indent = 20
|
||||
sortable = 'order'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NavigationBarAdmin, self).__init__(*args, **kwargs)
|
||||
self.__save_model_calls = 0
|
||||
|
||||
def linked_path(self, obj):
|
||||
return format_html(u'<a href="{0}" target="_blank">{0}</a>', obj.path)
|
||||
linked_path.short_description = _('link path')
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
self.__save_model_calls += 1
|
||||
return super(NavigationBarAdmin, self).save_model(request, obj, form, change)
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
self.__save_model_calls = 0
|
||||
with NavigationBar.objects.disable_mptt_updates():
|
||||
result = super(NavigationBarAdmin, self).changelist_view(request, extra_context)
|
||||
if self.__save_model_calls:
|
||||
with LockModel(write=(NavigationBar,)):
|
||||
NavigationBar.objects.rebuild()
|
||||
return result
|
||||
|
||||
|
||||
class BlogPostForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BlogPostForm, 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('blog_preview'))
|
||||
widgets['summary'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview'))
|
||||
|
||||
|
||||
class BlogPostAdmin(VersionAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('title', 'slug', 'authors', 'visible', 'sticky', 'publish_on')}),
|
||||
(_('Content'), {'fields': ('content', 'og_image')}),
|
||||
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}),
|
||||
)
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
list_display = ('id', 'title', 'visible', 'sticky', 'publish_on')
|
||||
list_display_links = ('id', 'title')
|
||||
ordering = ('-publish_on',)
|
||||
form = BlogPostForm
|
||||
date_hierarchy = 'publish_on'
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return (request.user.has_perm('judge.edit_all_post') or
|
||||
request.user.has_perm('judge.change_blogpost') and (
|
||||
obj is None or
|
||||
obj.authors.filter(id=request.profile.id).exists()))
|
||||
|
||||
|
||||
class SolutionForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SolutionForm, 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%'}),
|
||||
'problem': AdminHeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 250px'}),
|
||||
}
|
||||
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview'))
|
||||
|
||||
|
||||
class LicenseForm(ModelForm):
|
||||
class Meta:
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets = {'text': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('license_preview'))}
|
||||
|
||||
|
||||
class LicenseAdmin(admin.ModelAdmin):
|
||||
fields = ('key', 'link', 'name', 'display', 'icon', 'text')
|
||||
list_display = ('name', 'key')
|
||||
form = LicenseForm
|
||||
|
||||
|
||||
class UserListFilter(admin.SimpleListFilter):
|
||||
title = _('user')
|
||||
parameter_name = 'user'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return User.objects.filter(is_staff=True).values_list('id', 'username')
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value():
|
||||
return queryset.filter(user_id=self.value(), user__is_staff=True)
|
||||
return queryset
|
||||
|
||||
|
||||
class LogEntryAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ('user', 'content_type', 'object_id', 'object_repr', 'action_flag', 'change_message')
|
||||
list_display = ('__str__', 'action_time', 'user', 'content_type', 'object_link')
|
||||
search_fields = ('object_repr', 'change_message')
|
||||
list_filter = (UserListFilter, 'content_type')
|
||||
list_display_links = None
|
||||
actions = None
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return obj is None and request.user.is_superuser
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def object_link(self, obj):
|
||||
if obj.is_deletion():
|
||||
link = obj.object_repr
|
||||
else:
|
||||
ct = obj.content_type
|
||||
try:
|
||||
link = format_html('<a href="{1}">{0}</a>', obj.object_repr,
|
||||
reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(obj.object_id,)))
|
||||
except NoReverseMatch:
|
||||
link = obj.object_repr
|
||||
return link
|
||||
object_link.admin_order_field = 'object_repr'
|
||||
object_link.short_description = _('object')
|
||||
|
||||
def queryset(self, request):
|
||||
return super().queryset(request).prefetch_related('content_type')
|
66
judge/admin/organization.py
Normal file
66
judge/admin/organization.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from django.contrib import admin
|
||||
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 _
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from judge.models import Organization
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
|
||||
|
||||
class OrganizationForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'admins': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
|
||||
'registrant': AdminHeavySelect2Widget(data_view='profile_select2'),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['about'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('organization_preview'))
|
||||
|
||||
|
||||
class OrganizationAdmin(VersionAdmin):
|
||||
readonly_fields = ('creation_date',)
|
||||
fields = ('name', 'slug', 'short_name', 'is_open', 'about', 'logo_override_image', 'slots', 'registrant',
|
||||
'creation_date', 'admins')
|
||||
list_display = ('name', 'short_name', 'is_open', 'slots', 'registrant', 'show_public')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = OrganizationForm
|
||||
|
||||
def show_public(self, obj):
|
||||
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>',
|
||||
obj.get_absolute_url(), gettext('View on site'))
|
||||
|
||||
show_public.short_description = ''
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = self.readonly_fields
|
||||
if not request.user.has_perm('judge.organization_admin'):
|
||||
return fields + ('registrant', 'admins', 'is_open', 'slots')
|
||||
return fields
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = Organization.objects.all()
|
||||
if request.user.has_perm('judge.edit_all_organization'):
|
||||
return queryset
|
||||
else:
|
||||
return queryset.filter(admins=request.profile.id)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not request.user.has_perm('judge.change_organization'):
|
||||
return False
|
||||
if request.user.has_perm('judge.edit_all_organization') or obj is None:
|
||||
return True
|
||||
return obj.admins.filter(id=request.profile.id).exists()
|
||||
|
||||
|
||||
class OrganizationRequestAdmin(admin.ModelAdmin):
|
||||
list_display = ('username', 'organization', 'state', 'time')
|
||||
readonly_fields = ('user', 'organization')
|
||||
|
||||
def username(self, obj):
|
||||
return obj.user.user.username
|
||||
username.short_description = _('username')
|
||||
username.admin_order_field = 'user__user__username'
|
238
judge/admin/problem.py
Normal file
238
judge/admin/problem.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
from operator import attrgetter
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
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',)}),
|
||||
)
|
||||
list_display = ['code', 'name', 'show_authors', 'points', 'is_public', 'show_public']
|
||||
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')
|
||||
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):
|
||||
super(ProblemAdmin, self).save_model(request, obj, form, change)
|
||||
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)
|
118
judge/admin/profile.py
Normal file
118
judge/admin/profile.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from django.contrib import admin
|
||||
from django.forms import ModelForm
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from django_ace import AceWidget
|
||||
from judge.models import Profile
|
||||
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProfileForm, self).__init__(*args, **kwargs)
|
||||
if 'current_contest' in self.base_fields:
|
||||
# form.fields['current_contest'] does not exist when the user has only view permission on the model.
|
||||
self.fields['current_contest'].queryset = self.instance.contest_history.select_related('contest') \
|
||||
.only('contest__name', 'user_id', 'virtual')
|
||||
self.fields['current_contest'].label_from_instance = \
|
||||
lambda obj: '%s v%d' % (obj.contest.name, obj.virtual) if obj.virtual else obj.contest.name
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
'timezone': AdminSelect2Widget,
|
||||
'language': AdminSelect2Widget,
|
||||
'ace_theme': AdminSelect2Widget,
|
||||
'current_contest': AdminSelect2Widget,
|
||||
}
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets['about'] = AdminPagedownWidget
|
||||
|
||||
|
||||
class TimezoneFilter(admin.SimpleListFilter):
|
||||
title = _('timezone')
|
||||
parameter_name = 'timezone'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return Profile.objects.values_list('timezone', 'timezone').distinct().order_by('timezone')
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() is None:
|
||||
return queryset
|
||||
return queryset.filter(timezone=self.value())
|
||||
|
||||
|
||||
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',
|
||||
'current_contest')
|
||||
readonly_fields = ('user',)
|
||||
list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full',
|
||||
'date_joined', 'last_access', 'ip', 'show_public')
|
||||
ordering = ('user__username',)
|
||||
search_fields = ('user__username', 'ip', 'user__email')
|
||||
list_filter = ('language', TimezoneFilter)
|
||||
actions = ('recalculate_points',)
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ProfileForm
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(ProfileAdmin, self).get_queryset(request).select_related('user')
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
if request.user.has_perm('judge.totp'):
|
||||
fields = list(self.fields)
|
||||
fields.insert(fields.index('is_totp_enabled') + 1, 'totp_key')
|
||||
return tuple(fields)
|
||||
else:
|
||||
return self.fields
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = self.readonly_fields
|
||||
if not request.user.has_perm('judge.totp'):
|
||||
fields += ('is_totp_enabled',)
|
||||
return fields
|
||||
|
||||
def show_public(self, obj):
|
||||
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>',
|
||||
obj.get_absolute_url(), gettext('View on site'))
|
||||
show_public.short_description = ''
|
||||
|
||||
def admin_user_admin(self, obj):
|
||||
return obj.username
|
||||
admin_user_admin.admin_order_field = 'user__username'
|
||||
admin_user_admin.short_description = _('User')
|
||||
|
||||
def email(self, obj):
|
||||
return obj.user.email
|
||||
email.admin_order_field = 'user__email'
|
||||
email.short_description = _('Email')
|
||||
|
||||
def timezone_full(self, obj):
|
||||
return obj.timezone
|
||||
timezone_full.admin_order_field = 'timezone'
|
||||
timezone_full.short_description = _('Timezone')
|
||||
|
||||
def date_joined(self, obj):
|
||||
return obj.user.date_joined
|
||||
date_joined.admin_order_field = 'user__date_joined'
|
||||
date_joined.short_description = _('date joined')
|
||||
|
||||
def recalculate_points(self, request, queryset):
|
||||
count = 0
|
||||
for profile in queryset:
|
||||
profile.calculate_points()
|
||||
count += 1
|
||||
self.message_user(request, ungettext('%d user have scores recalculated.',
|
||||
'%d users have scores recalculated.',
|
||||
count) % count)
|
||||
recalculate_points.short_description = _('Recalculate scores')
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(ProfileAdmin, self).get_form(request, obj, **kwargs)
|
||||
if 'user_script' in form.base_fields:
|
||||
# form.base_fields['user_script'] does not exist when the user has only view permission on the model.
|
||||
form.base_fields['user_script'].widget = AceWidget('javascript', request.profile.ace_theme)
|
||||
return form
|
120
judge/admin/runtime.py
Normal file
120
judge/admin/runtime.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
from django.conf.urls import url
|
||||
from django.db.models import TextField
|
||||
from django.forms import ModelForm, ModelMultipleChoiceField, TextInput
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from django_ace import AceWidget
|
||||
from judge.models import Judge, Problem
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminPagedownWidget
|
||||
|
||||
|
||||
class LanguageForm(ModelForm):
|
||||
problems = ModelMultipleChoiceField(
|
||||
label=_('Disallowed problems'),
|
||||
queryset=Problem.objects.all(),
|
||||
required=False,
|
||||
help_text=_('These problems are NOT allowed to be submitted in this language'),
|
||||
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
|
||||
|
||||
class Meta:
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets = {'description': AdminPagedownWidget}
|
||||
|
||||
|
||||
class LanguageAdmin(VersionAdmin):
|
||||
fields = ('key', 'name', 'short_name', 'common_name', 'ace', 'pygments', 'info', 'description',
|
||||
'template', 'problems')
|
||||
list_display = ('key', 'name', 'common_name', 'info')
|
||||
form = LanguageForm
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(LanguageAdmin, self).save_model(request, obj, form, change)
|
||||
obj.problem_set.set(Problem.objects.exclude(id__in=form.cleaned_data['problems'].values('id')))
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
self.form.base_fields['problems'].initial = \
|
||||
Problem.objects.exclude(id__in=obj.problem_set.values('id')).values_list('pk', flat=True) if obj else []
|
||||
form = super(LanguageAdmin, self).get_form(request, obj, **kwargs)
|
||||
if obj is not None:
|
||||
form.base_fields['template'].widget = AceWidget(obj.ace, request.profile.ace_theme)
|
||||
return form
|
||||
|
||||
|
||||
class GenerateKeyTextInput(TextInput):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
text = super(TextInput, self).render(name, value, attrs)
|
||||
return mark_safe(text + format_html(
|
||||
'''\
|
||||
<a href="#" onclick="return false;" class="button" id="id_{0}_regen">Regenerate</a>
|
||||
<script type="text/javascript">
|
||||
django.jQuery(document).ready(function ($) {{
|
||||
$('#id_{0}_regen').click(function () {{
|
||||
var length = 100,
|
||||
charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_+-=|[]{{}};:,<>./?",
|
||||
key = "";
|
||||
for (var i = 0, n = charset.length; i < length; ++i) {{
|
||||
key += charset.charAt(Math.floor(Math.random() * n));
|
||||
}}
|
||||
$('#id_{0}').val(key);
|
||||
}});
|
||||
}});
|
||||
</script>
|
||||
''', name))
|
||||
|
||||
|
||||
class JudgeAdminForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {'auth_key': GenerateKeyTextInput}
|
||||
if AdminPagedownWidget is not None:
|
||||
widgets['description'] = AdminPagedownWidget
|
||||
|
||||
|
||||
class JudgeAdmin(VersionAdmin):
|
||||
form = JudgeAdminForm
|
||||
readonly_fields = ('created', 'online', 'start_time', 'ping', 'load', 'last_ip', 'runtimes', 'problems')
|
||||
fieldsets = (
|
||||
(None, {'fields': ('name', 'auth_key', 'is_blocked')}),
|
||||
(_('Description'), {'fields': ('description',)}),
|
||||
(_('Information'), {'fields': ('created', 'online', 'last_ip', 'start_time', 'ping', 'load')}),
|
||||
(_('Capabilities'), {'fields': ('runtimes', 'problems')}),
|
||||
)
|
||||
list_display = ('name', 'online', 'start_time', 'ping', 'load', 'last_ip')
|
||||
ordering = ['-online', 'name']
|
||||
|
||||
def get_urls(self):
|
||||
return ([url(r'^(\d+)/disconnect/$', self.disconnect_view, name='judge_judge_disconnect'),
|
||||
url(r'^(\d+)/terminate/$', self.terminate_view, name='judge_judge_terminate')] +
|
||||
super(JudgeAdmin, self).get_urls())
|
||||
|
||||
def disconnect_judge(self, id, force=False):
|
||||
judge = get_object_or_404(Judge, id=id)
|
||||
judge.disconnect(force=force)
|
||||
return HttpResponseRedirect(reverse('admin:judge_judge_changelist'))
|
||||
|
||||
def disconnect_view(self, request, id):
|
||||
return self.disconnect_judge(id)
|
||||
|
||||
def terminate_view(self, request, id):
|
||||
return self.disconnect_judge(id, force=True)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj is not None and obj.online:
|
||||
return self.readonly_fields + ('name',)
|
||||
return self.readonly_fields
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
result = super(JudgeAdmin, self).has_delete_permission(request, obj)
|
||||
if result and obj is not None:
|
||||
return not obj.online
|
||||
return result
|
||||
|
||||
if AdminPagedownWidget is not None:
|
||||
formfield_overrides = {
|
||||
TextField: {'widget': AdminPagedownWidget},
|
||||
}
|
251
judge/admin/submission.py
Normal file
251
judge/admin/submission.py
Normal file
|
@ -0,0 +1,251 @@
|
|||
from functools import partial
|
||||
from operator import itemgetter
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin, messages
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext, gettext_lazy as _, pgettext, ungettext
|
||||
|
||||
from django_ace import AceWidget
|
||||
from judge.models import ContestParticipation, ContestProblem, ContestSubmission, Profile, Submission, \
|
||||
SubmissionSource, SubmissionTestCase
|
||||
from judge.utils.raw_sql import use_straight_join
|
||||
|
||||
|
||||
class SubmissionStatusFilter(admin.SimpleListFilter):
|
||||
parameter_name = title = 'status'
|
||||
__lookups = (('None', _('None')), ('NotDone', _('Not done')), ('EX', _('Exceptional'))) + Submission.STATUS
|
||||
__handles = set(map(itemgetter(0), Submission.STATUS))
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return self.__lookups
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'None':
|
||||
return queryset.filter(status=None)
|
||||
elif self.value() == 'NotDone':
|
||||
return queryset.exclude(status__in=['D', 'IE', 'CE', 'AB'])
|
||||
elif self.value() == 'EX':
|
||||
return queryset.exclude(status__in=['D', 'CE', 'G', 'AB'])
|
||||
elif self.value() in self.__handles:
|
||||
return queryset.filter(status=self.value())
|
||||
|
||||
|
||||
class SubmissionResultFilter(admin.SimpleListFilter):
|
||||
parameter_name = title = 'result'
|
||||
__lookups = (('None', _('None')), ('BAD', _('Unaccepted'))) + Submission.RESULT
|
||||
__handles = set(map(itemgetter(0), Submission.RESULT))
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return self.__lookups
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'None':
|
||||
return queryset.filter(result=None)
|
||||
elif self.value() == 'BAD':
|
||||
return queryset.exclude(result='AC')
|
||||
elif self.value() in self.__handles:
|
||||
return queryset.filter(result=self.value())
|
||||
|
||||
|
||||
class SubmissionTestCaseInline(admin.TabularInline):
|
||||
fields = ('case', 'batch', 'status', 'time', 'memory', 'points', 'total')
|
||||
readonly_fields = ('case', 'batch', 'total')
|
||||
model = SubmissionTestCase
|
||||
can_delete = False
|
||||
max_num = 0
|
||||
|
||||
|
||||
class ContestSubmissionInline(admin.StackedInline):
|
||||
fields = ('problem', 'participation', 'points')
|
||||
model = ContestSubmission
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
|
||||
return super(ContestSubmissionInline, self).get_formset(request, obj, **kwargs)
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
submission = kwargs.pop('obj', None)
|
||||
label = None
|
||||
if submission:
|
||||
if db_field.name == 'participation':
|
||||
kwargs['queryset'] = ContestParticipation.objects.filter(user=submission.user,
|
||||
contest__problems=submission.problem) \
|
||||
.only('id', 'contest__name')
|
||||
|
||||
def label(obj):
|
||||
return obj.contest.name
|
||||
elif db_field.name == 'problem':
|
||||
kwargs['queryset'] = ContestProblem.objects.filter(problem=submission.problem) \
|
||||
.only('id', 'problem__name', 'contest__name')
|
||||
|
||||
def label(obj):
|
||||
return pgettext('contest problem', '%(problem)s in %(contest)s') % {
|
||||
'problem': obj.problem.name, 'contest': obj.contest.name,
|
||||
}
|
||||
field = super(ContestSubmissionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
if label is not None:
|
||||
field.label_from_instance = label
|
||||
return field
|
||||
|
||||
|
||||
class SubmissionSourceInline(admin.StackedInline):
|
||||
fields = ('source',)
|
||||
model = SubmissionSource
|
||||
can_delete = False
|
||||
extra = 0
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
kwargs.setdefault('widgets', {})['source'] = AceWidget(mode=obj and obj.language.ace,
|
||||
theme=request.profile.ace_theme)
|
||||
return super().get_formset(request, obj, **kwargs)
|
||||
|
||||
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ('user', 'problem', 'date')
|
||||
fields = ('user', 'problem', 'date', 'time', 'memory', 'points', 'language', 'status', 'result',
|
||||
'case_points', 'case_total', 'judged_on', 'error')
|
||||
actions = ('judge', 'recalculate_score')
|
||||
list_display = ('id', 'problem_code', 'problem_name', 'user_column', 'execution_time', 'pretty_memory',
|
||||
'points', 'language_column', 'status', 'result', 'judge_column')
|
||||
list_filter = ('language', SubmissionStatusFilter, SubmissionResultFilter)
|
||||
search_fields = ('problem__code', 'problem__name', 'user__user__username')
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
inlines = [SubmissionSourceInline, SubmissionTestCaseInline, ContestSubmissionInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = Submission.objects.select_related('problem', 'user__user', 'language').only(
|
||||
'problem__code', 'problem__name', 'user__user__username', 'language__name',
|
||||
'time', 'memory', 'points', 'status', 'result',
|
||||
)
|
||||
use_straight_join(queryset)
|
||||
if not request.user.has_perm('judge.edit_all_problem'):
|
||||
id = request.profile.id
|
||||
queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id)).distinct()
|
||||
return queryset
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not request.user.has_perm('judge.edit_own_problem'):
|
||||
return False
|
||||
if request.user.has_perm('judge.edit_all_problem') or obj is None:
|
||||
return True
|
||||
return obj.problem.is_editor(request.profile)
|
||||
|
||||
def lookup_allowed(self, key, value):
|
||||
return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in ('problem__code',)
|
||||
|
||||
def judge(self, request, queryset):
|
||||
if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'):
|
||||
self.message_user(request, gettext('You do not have the permission to rejudge submissions.'),
|
||||
level=messages.ERROR)
|
||||
return
|
||||
queryset = queryset.order_by('id')
|
||||
if not request.user.has_perm('judge.rejudge_submission_lot') and \
|
||||
queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT:
|
||||
self.message_user(request, gettext('You do not have the permission to rejudge THAT many submissions.'),
|
||||
level=messages.ERROR)
|
||||
return
|
||||
if not request.user.has_perm('judge.edit_all_problem'):
|
||||
id = request.profile.id
|
||||
queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id))
|
||||
judged = len(queryset)
|
||||
for model in queryset:
|
||||
model.judge(rejudge=True, batch_rejudge=True)
|
||||
self.message_user(request, ungettext('%d submission was successfully scheduled for rejudging.',
|
||||
'%d submissions were successfully scheduled for rejudging.',
|
||||
judged) % judged)
|
||||
judge.short_description = _('Rejudge the selected submissions')
|
||||
|
||||
def recalculate_score(self, request, queryset):
|
||||
if not request.user.has_perm('judge.rejudge_submission'):
|
||||
self.message_user(request, gettext('You do not have the permission to rejudge submissions.'),
|
||||
level=messages.ERROR)
|
||||
return
|
||||
submissions = list(queryset.defer(None).select_related(None).select_related('problem')
|
||||
.only('points', 'case_points', 'case_total', 'problem__partial', 'problem__points'))
|
||||
for submission in submissions:
|
||||
submission.points = round(submission.case_points / submission.case_total * submission.problem.points
|
||||
if submission.case_total else 0, 1)
|
||||
if not submission.problem.partial and submission.points < submission.problem.points:
|
||||
submission.points = 0
|
||||
submission.save()
|
||||
submission.update_contest()
|
||||
|
||||
for profile in Profile.objects.filter(id__in=queryset.values_list('user_id', flat=True).distinct()):
|
||||
profile.calculate_points()
|
||||
cache.delete('user_complete:%d' % profile.id)
|
||||
cache.delete('user_attempted:%d' % profile.id)
|
||||
|
||||
for participation in ContestParticipation.objects.filter(
|
||||
id__in=queryset.values_list('contest__participation_id')).prefetch_related('contest'):
|
||||
participation.recompute_results()
|
||||
|
||||
self.message_user(request, ungettext('%d submission were successfully rescored.',
|
||||
'%d submissions were successfully rescored.',
|
||||
len(submissions)) % len(submissions))
|
||||
recalculate_score.short_description = _('Rescore the selected submissions')
|
||||
|
||||
def problem_code(self, obj):
|
||||
return obj.problem.code
|
||||
problem_code.short_description = _('Problem code')
|
||||
problem_code.admin_order_field = 'problem__code'
|
||||
|
||||
def problem_name(self, obj):
|
||||
return obj.problem.name
|
||||
problem_name.short_description = _('Problem name')
|
||||
problem_name.admin_order_field = 'problem__name'
|
||||
|
||||
def user_column(self, obj):
|
||||
return obj.user.user.username
|
||||
user_column.admin_order_field = 'user__user__username'
|
||||
user_column.short_description = _('User')
|
||||
|
||||
def execution_time(self, obj):
|
||||
return round(obj.time, 2) if obj.time is not None else 'None'
|
||||
execution_time.short_description = _('Time')
|
||||
execution_time.admin_order_field = 'time'
|
||||
|
||||
def pretty_memory(self, obj):
|
||||
memory = obj.memory
|
||||
if memory is None:
|
||||
return gettext('None')
|
||||
if memory < 1000:
|
||||
return gettext('%d KB') % memory
|
||||
else:
|
||||
return gettext('%.2f MB') % (memory / 1024)
|
||||
pretty_memory.admin_order_field = 'memory'
|
||||
pretty_memory.short_description = _('Memory')
|
||||
|
||||
def language_column(self, obj):
|
||||
return obj.language.name
|
||||
language_column.admin_order_field = 'language__name'
|
||||
language_column.short_description = _('Language')
|
||||
|
||||
def judge_column(self, obj):
|
||||
return format_html('<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />', obj.id)
|
||||
judge_column.short_description = ''
|
||||
|
||||
def get_urls(self):
|
||||
return [
|
||||
url(r'^(\d+)/judge/$', self.judge_view, name='judge_submission_rejudge'),
|
||||
] + super(SubmissionAdmin, self).get_urls()
|
||||
|
||||
def judge_view(self, request, id):
|
||||
if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'):
|
||||
raise PermissionDenied()
|
||||
submission = get_object_or_404(Submission, id=id)
|
||||
if not request.user.has_perm('judge.edit_all_problem') and \
|
||||
not submission.problem.is_editor(request.profile):
|
||||
raise PermissionDenied()
|
||||
submission.judge(rejudge=True)
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
|
52
judge/admin/taxon.py
Normal file
52
judge/admin/taxon.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from django.contrib import admin
|
||||
from django.forms import ModelForm, ModelMultipleChoiceField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from judge.models import Problem
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget
|
||||
|
||||
|
||||
class ProblemGroupForm(ModelForm):
|
||||
problems = ModelMultipleChoiceField(
|
||||
label=_('Included problems'),
|
||||
queryset=Problem.objects.all(),
|
||||
required=False,
|
||||
help_text=_('These problems are included in this group of problems'),
|
||||
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
|
||||
|
||||
|
||||
class ProblemGroupAdmin(admin.ModelAdmin):
|
||||
fields = ('name', 'full_name', 'problems')
|
||||
form = ProblemGroupForm
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ProblemGroupAdmin, self).save_model(request, obj, form, change)
|
||||
obj.problem_set.set(form.cleaned_data['problems'])
|
||||
obj.save()
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else []
|
||||
return super(ProblemGroupAdmin, self).get_form(request, obj, **kwargs)
|
||||
|
||||
|
||||
class ProblemTypeForm(ModelForm):
|
||||
problems = ModelMultipleChoiceField(
|
||||
label=_('Included problems'),
|
||||
queryset=Problem.objects.all(),
|
||||
required=False,
|
||||
help_text=_('These problems are included in this type of problems'),
|
||||
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
|
||||
|
||||
|
||||
class ProblemTypeAdmin(admin.ModelAdmin):
|
||||
fields = ('name', 'full_name', 'problems')
|
||||
form = ProblemTypeForm
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ProblemTypeAdmin, self).save_model(request, obj, form, change)
|
||||
obj.problem_set.set(form.cleaned_data['problems'])
|
||||
obj.save()
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else []
|
||||
return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs)
|
39
judge/admin/ticket.py
Normal file
39
judge/admin/ticket.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.admin.options import StackedInline
|
||||
from django.forms import ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from judge.models import TicketMessage
|
||||
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
|
||||
|
||||
|
||||
class TicketMessageForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('ticket_preview'))
|
||||
|
||||
|
||||
class TicketMessageInline(StackedInline):
|
||||
model = TicketMessage
|
||||
form = TicketMessageForm
|
||||
fields = ('user', 'body')
|
||||
|
||||
|
||||
class TicketForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
'assignees': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
|
||||
}
|
||||
|
||||
|
||||
class TicketAdmin(ModelAdmin):
|
||||
fields = ('title', 'time', 'user', 'assignees', 'content_type', 'object_id', 'notes')
|
||||
readonly_fields = ('time',)
|
||||
list_display = ('title', 'user', 'time', 'linked_item')
|
||||
inlines = [TicketMessageInline]
|
||||
form = TicketForm
|
||||
date_hierarchy = 'time'
|
Loading…
Add table
Add a link
Reference in a new issue