from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin import SimpleListFilter
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.forms.models import BaseInlineFormSet
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.html import conditional_escape
from django.utils.translation import gettext_lazy as _
from oioioi.base.utils import make_html_link
from oioioi.contests.admin import ContestAdmin, ProblemInstanceAdmin, SubmissionAdmin
from oioioi.contests.models import ProblemInstance
from oioioi.contests.utils import is_contest_admin
from oioioi.programs.utils import get_submittable_languages
from oioioi.problems.admin import MainProblemInstanceAdmin, ProblemPackageAdmin
from oioioi.programs.forms import (
CompilerInlineForm,
ProblemAllowedLanguageInlineForm,
ProblemCompilerInlineForm,
)
from oioioi.programs.models import (
ContestCompiler,
LibraryProblemData,
ModelSolution,
OutputChecker,
ProblemAllowedLanguage,
ProblemCompiler,
ProgramsConfig,
ReportActionsConfig,
Test,
)
[docs]class ProgramsConfigInline(admin.TabularInline):
[docs] category = _("Advanced")
[docs] def has_add_permission(self, request, obj=None):
return False
[docs] def has_change_permission(self, request, obj=None):
return request.user.is_superuser
[docs] def has_delete_permission(self, request, obj=None):
return False
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs]class TestInline(admin.TabularInline):
[docs] template = 'programs/admin/tests_inline.html'
[docs] fields = (
'name',
'time_limit',
'memory_limit',
'max_score',
'kind',
'input_file_link',
'output_file_link',
'is_active',
)
[docs] readonly_fields = ('name', 'kind', 'group', 'input_file_link', 'output_file_link')
[docs] ordering = ('kind', 'order', 'name')
[docs] def has_add_permission(self, request, obj=None):
return False
[docs] def has_change_permission(self, request, obj=None):
# this view doesn't allow to add / remove tests
# so if there are no tests for this tasks we can skip showing it
# (for example quizzes have no tests and it would be confusing to show)
return obj is None or obj.test_set.count() != 0
[docs] def has_delete_permission(self, request, obj=None):
return False
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs] input_file_link.short_description = _("Input file")
[docs] def output_file_link(self, instance):
if instance.id is not None:
href = reverse('download_output_file', kwargs={'test_id': instance.id})
return make_html_link(href, instance.output_file.name.split('/')[-1])
return None
output_file_link.short_description = _("Output/hint file")
[docs]class ReportActionsConfigInline(admin.StackedInline):
[docs] model = ReportActionsConfig
[docs] inline_classes = ('collapse open',)
[docs] fields = ['can_user_generate_outs']
[docs] category = _("Advanced")
[docs] def has_add_permission(self, request, obj=None):
return False
[docs] def has_change_permission(self, request, obj=None):
return is_contest_admin(request)
[docs] def has_delete_permission(self, request, obj=None):
return False
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs]class OutputCheckerInline(admin.TabularInline):
[docs] fields = ['checker_link']
[docs] readonly_fields = ['checker_link']
[docs] category = _("Advanced")
[docs] def has_add_permission(self, request, obj=None):
return False
[docs] def has_change_permission(self, request, obj=None):
return is_contest_admin(request)
[docs] def has_delete_permission(self, request, obj=None):
return False
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs] def checker_link(self, instance):
if not instance.exe_file:
return _("No checker for this task.")
if instance.id is not None:
href = reverse(
'download_checker_file', kwargs={'checker_id': str(instance.id)}
)
return make_html_link(href, instance.exe_file.name.split('/')[-1])
return None
[docs] checker_link.short_description = _("Checker exe")
[docs]class LibraryProblemDataInline(admin.TabularInline):
[docs] model = LibraryProblemData
[docs] readonly_fields = ['libname']
[docs] category = _("Advanced")
[docs] def has_add_permission(self, request, obj=None):
return False
[docs] def has_change_permission(self, request, obj=None):
return True
[docs] def has_delete_permission(self, request, obj=None):
return False
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs]class ProblemCompilerInline(admin.StackedInline):
[docs] model = ProblemCompiler
[docs] category = _("Advanced")
[docs]class ProblemAllowedLanguageInline(admin.StackedInline):
[docs] model = ProblemAllowedLanguage
[docs] category = _("Advanced")
[docs]class ContestCompilerInline(admin.StackedInline):
[docs] model = ContestCompiler
[docs] category = _("Advanced")
def __init__(self, *args, **kwargs):
super(ContestCompilerInline, self).__init__(*args, **kwargs)
self.verbose_name_plural = _("Compiler overrides")
[docs] def has_add_permission(self, request, obj=None):
return is_contest_admin(request)
[docs] def has_change_permission(self, request, obj=None):
return is_contest_admin(request)
[docs] def has_delete_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs] def has_view_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
[docs]class ProgramsContestAdminMixin(object):
"""Adds :class:`~oioioi.programs.models.ProgramsConfig`
and :class:`~oioioi.programs.models.ContestCompiler` to an admin
panel.
"""
def __init__(self, *args, **kwargs):
super(ProgramsContestAdminMixin, self).__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (ProgramsConfigInline, ContestCompilerInline)
ContestAdmin.mix_in(ProgramsContestAdminMixin)
[docs]class LibraryProblemDataAdminMixin(object):
"""Adds :class:`~oioioi.programs.models.LibraryProblemData` to an admin
panel.
"""
def __init__(self, *args, **kwargs):
super(LibraryProblemDataAdminMixin, self).__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (LibraryProblemDataInline,)
[docs]class ProgrammingProblemAdminMixin(object):
"""Adds :class:`~oioioi.programs.models.ReportActionsConfig`,
:class:`~oioioi.programs.models.OutputChecker`,
:class:`~oioioi.programs.models.LibraryProblemData` and
:class:`~oioioi.programs.models.ProblemAllowedLanguage` and
:class:`~oioioi.programs.models.ProblemCompiler` to an admin panel.
"""
def __init__(self, *args, **kwargs):
super(ProgrammingProblemAdminMixin, self).__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (
ReportActionsConfigInline,
OutputCheckerInline,
LibraryProblemDataInline,
ProblemCompilerInline,
ProblemAllowedLanguageInline,
)
[docs]class ProgrammingProblemInstanceAdminMixin(object):
"""Adds :class:`~oioioi.programs.models.Test` to an admin panel."""
def __init__(self, *args, **kwargs):
super(ProgrammingProblemInstanceAdminMixin, self).__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (TestInline,)
ProblemInstanceAdmin.mix_in(ProgrammingProblemInstanceAdminMixin)
[docs]class ProgrammingMainProblemInstanceAdminMixin(object):
"""Adds :class:`~oioioi.programs.models.Test` to an admin panel."""
def __init__(self, *args, **kwargs):
super(ProgrammingMainProblemInstanceAdminMixin, self).__init__(*args, **kwargs)
self.inlines = tuple(self.inlines) + (TestInline,)
MainProblemInstanceAdmin.mix_in(ProgrammingMainProblemInstanceAdminMixin)
[docs]class ProblemPackageAdminMixin(object):
"""Adds model solutions action to an admin panel."""
[docs] def inline_actions(self, package_instance, contest):
actions = super(ProblemPackageAdminMixin, self).inline_actions(
package_instance, contest
)
if package_instance.status == 'OK':
try:
problem_instance = package_instance.problem.main_problem_instance
if not problem_instance:
problem_instance = ProblemInstance.objects.get(
problem=package_instance.problem, contest=contest
)
if problem_instance.contest and ModelSolution.objects.filter(
problem=problem_instance.problem
):
models_view = reverse(
'model_solutions', args=(problem_instance.id,)
)
actions.append((models_view, _("Model solutions")))
except ProblemInstance.DoesNotExist:
pass
return actions
ProblemPackageAdmin.mix_in(ProblemPackageAdminMixin)
[docs]class ModelSubmissionAdminMixin(object):
"""Adds model submission to an admin panel."""
[docs] def user_full_name(self, instance):
if not instance.user:
instance = instance.programsubmission
if instance:
instance = instance.modelprogramsubmission
if instance:
return '(%s)' % (
conditional_escape(force_str(instance.model_solution.name)),
)
return super(ModelSubmissionAdminMixin, self).user_full_name(instance)
[docs] user_full_name.short_description = SubmissionAdmin.user_full_name.short_description
[docs] user_full_name.admin_order_field = SubmissionAdmin.user_full_name.admin_order_field
SubmissionAdmin.mix_in(ModelSubmissionAdminMixin)
[docs]class ProgramSubmissionAdminMixin(object):
"""Adds submission diff action, language display and language filter to
an admin panel.
"""
def __init__(self, *args, **kwargs):
super(ProgramSubmissionAdminMixin, self).__init__(*args, **kwargs)
self.actions += ['submission_diff_action']
[docs] def get_list_display(self, request):
return super(ProgramSubmissionAdminMixin, self).get_list_display(request) + [
'language_display'
]
[docs] def get_list_filter(self, request):
return super(ProgramSubmissionAdminMixin, self).get_list_filter(request) + [
LanguageListFilter
]
[docs] def language_display(self, instance):
return instance.programsubmission.get_language_display()
[docs] language_display.short_description = _("Language")
[docs] def submission_diff_action(self, request, queryset):
if len(queryset) != 2:
messages.error(
request, _("You shall select exactly two submissions to diff")
)
return None
id_older, id_newer = [sub.id for sub in queryset.order_by('date')]
return redirect(
'source_diff',
contest_id=request.contest.id,
submission1_id=id_older,
submission2_id=id_newer,
)
submission_diff_action.short_description = _("Diff submissions")
SubmissionAdmin.mix_in(ProgramSubmissionAdminMixin)
[docs]class LanguageListFilter(SimpleListFilter):
[docs] parameter_name = 'lang'
[docs] def lookups(self, request, model_admin):
langs = get_submittable_languages()
return [(lang, lang_info['display_name']) for lang, lang_info in langs.items()]
[docs] def queryset(self, request, queryset):
exts = getattr(settings, 'SUBMITTABLE_EXTENSIONS', {})
if self.value() and self.value() in exts:
condition = Q()
for ext in exts[self.value()]:
condition |= Q(programsubmission__source_file__contains='.%s@' % ext)
return queryset.filter(condition)
else:
return queryset