import json
import types
from collections import defaultdict
from datetime import datetime, timedelta # pylint: disable=E0611
from django.conf import settings
from django.contrib.auth.models import User
from django.forms import modelformset_factory
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls import reverse
from oioioi.base.permissions import enforce_condition, is_superuser
from oioioi.contests.controllers import (
PublicContestRegistrationController,
submission_template_context,
)
from oioioi.contests.models import (
ProblemInstance,
Round,
Submission,
UserResultForContest,
UserResultForProblem,
)
from oioioi.contests.permissions import can_create_contest
from oioioi.contests.utils import (
can_admin_contest,
contest_exists,
has_any_contest,
is_contest_basicadmin,
rounds_times,
visible_contests,
)
from oioioi.dashboard.contest_dashboard import register_contest_dashboard_view
from oioioi.portals.conditions import main_page_from_default_global_portal
from oioioi.portals.models import Portal
from oioioi.problems.utils import can_admin_problem
from oioioi.programs.admin import ValidationFormset
from oioioi.programs.models import Test
from oioioi.questions.models import Message
from oioioi.questions.views import messages_template_context, visible_messages
from oioioi.simpleui.forms import ProblemInstanceForm, TestForm
[docs]NUMBER_OF_RECENT_ACTIONS = 5
[docs]RECENT_ACTIVITY_DAYS = 7
[docs]MAX_CONTESTS_ON_PAGE = 6
[docs]def score_report_is_valid(score_report):
return (
score_report is not None
and score_report.score is not None
and score_report.max_score is not None
)
[docs]def get_round_context(request, round_pk):
selected_round = get_object_or_404(Round, pk=round_pk)
round = {'round': selected_round, 'problem_instances': []}
problem_instances = {}
controller = request.contest.controller
queryset = ProblemInstance.objects.filter(round=selected_round).select_related(
'problem'
)
visible_problem_instances = [
pi for pi in queryset if controller.can_see_problem(request, pi)
]
for pi in visible_problem_instances:
problem_instances[pi.pk] = {}
problem_instances[pi.pk]['problem_instance'] = pi
problem_instances[pi.pk]['submission_count'] = 0
problem_instances[pi.pk]['question_count'] = 0
problem_instances[pi.pk]['solved_count'] = 0
problem_instances[pi.pk]['tried_solving_count'] = 0
problem_instances[pi.pk]['users_with_score'] = defaultdict(int)
problem_instances[pi.pk]['max_score'] = 0
end_date = request.timestamp
start_date = end_date - timedelta(days=RECENT_ACTIVITY_DAYS)
last_week = [start_date, end_date]
questions = Message.objects.filter(round=selected_round, date__range=last_week)
for question in questions:
if question.problem_instance is not None:
problem_instance = problem_instances[question.problem_instance.pk]
problem_instance['question_count'] += 1
submissions = Submission.objects.filter(
problem_instance__round=selected_round, date__range=last_week
)
for submission in submissions:
problem_instance = problem_instances[submission.problem_instance.pk]
problem_instance['submission_count'] += 1
results = UserResultForProblem.objects.filter(
problem_instance__round=selected_round, submission_report__isnull=False
)
for result in results:
problem_instance = problem_instances[result.problem_instance.pk]
score_report = result.submission_report.score_report
if score_report_is_valid(score_report):
problem_instance['max_score'] = score_report.max_score.to_int()
problem_instance['tried_solving_count'] += 1
if score_report.score == score_report.max_score:
problem_instance['solved_count'] += 1
problem_instance['users_with_score'][score_report.score.to_int()] += 1
contest_data = {}
for _, pi in problem_instances.items():
scores = []
for score, users_with_score in pi['users_with_score'].items():
scores.append([score, users_with_score])
if pi['tried_solving_count'] == 0:
pi['solved'] = 'none'
else:
solved_ratio = float(pi['solved_count']) / pi['tried_solving_count']
if solved_ratio < 1.0 / 3:
pi['solved'] = 'low'
elif solved_ratio < 2.0 / 3:
pi['solved'] = 'medium'
else:
pi['solved'] = 'high'
pi['users_with_score'] = scores
contest_data[pi['problem_instance'].short_name] = {
'scores': scores,
'max_score': pi['max_score'],
}
round['problem_instances'].append(pi)
return {
'selected_round': round,
'contest_data': json.dumps(contest_data),
'RECENT_ACTIVITY_DAYS': RECENT_ACTIVITY_DAYS,
}
@enforce_condition(contest_exists & is_contest_basicadmin)
[docs]def contest_dashboard_view(request, round_pk=None):
if request.user.is_superuser:
return redirect('default_contest_view', contest_id=request.contest.id)
messages = messages_template_context(request, visible_messages(request))[
:NUMBER_OF_RECENT_ACTIONS
]
queryset = (
Submission.objects.filter(problem_instance__contest=request.contest)
.order_by('-date')
.select_related(
'user',
'problem_instance',
'problem_instance__contest',
'problem_instance__round',
'problem_instance__problem',
)
.prefetch_related('problem_instance__problem__names')
)
ss = [
submission_template_context(request, s)
for s in queryset[:NUMBER_OF_RECENT_ACTIONS]
]
rtimes = list(rounds_times(request, request.contest).items())
rtimes.sort(key=lambda r_rt: r_rt[0].start_date)
if round_pk is None and len(rtimes) > 0:
# First active round, or last one if there are no active ones
round_pk = next(
((r, rt) for r, rt in rtimes if rt.is_active(request.timestamp)), rtimes[-1]
)[0].pk
context = {
'round_times': rtimes,
'selected_round': None,
'records': messages,
'submissions': ss,
'contest_dashboard_url_name': 'simpleui_contest_dashboard',
'public_contest': isinstance(
request.contest.controller.registration_controller(),
PublicContestRegistrationController,
),
}
if round_pk is not None:
context.update(get_round_context(request, round_pk))
return TemplateResponse(request, 'simpleui/contest/contest.html', context)
@enforce_condition(can_create_contest | has_any_contest)
[docs]def user_dashboard_view(request):
contest_context = []
min_date = datetime.today() - timedelta(days=7)
contests = [contest for contest in visible_contests(request)]
are_contests_limited = len(contests) > MAX_CONTESTS_ON_PAGE
visible_contests_count = len(contests)
contests = [x for x in contests if can_admin_contest(request.user, x)]
if len(contests) < visible_contests_count:
are_contests_limited = True
contests.sort(key=lambda x: x.creation_date, reverse=True)
contests = contests[:MAX_CONTESTS_ON_PAGE]
if 'oioioi.portals' in settings.INSTALLED_APPS:
has_portal = main_page_from_default_global_portal(request)
else:
has_portal = False
for contest in contests:
scores = [
result.score.to_int()
for result in UserResultForContest.objects.filter(contest=contest).all()
if result.score is not None
]
max_score = 0
for problem_inst in ProblemInstance.objects.filter(contest=contest):
user_results = UserResultForProblem.objects.filter(
problem_instance=problem_inst, submission_report__isnull=False
)
if user_results.count() > 0:
for result in user_results:
score_report = result.submission_report.score_report
if score_report_is_valid(score_report):
max_score += score_report.max_score.to_int()
break
contest_dict = {
'id': contest.id,
'name': contest.name,
'round_count': Round.objects.filter(contest=contest).count(),
'task_count': ProblemInstance.objects.filter(contest=contest).count(),
'submission_count': Submission.objects.filter(
problem_instance__contest=contest
).count(),
'recent_submission_count': Submission.objects.filter(
problem_instance__contest=contest, date__gte=min_date
).count(),
'recent_question_count': Message.objects.filter(
contest=contest, kind='QUESTION', date__gte=min_date
).count(),
'max_score': max_score,
'scores': scores,
'contest_controller': contest.controller,
'dashboard_url': reverse(
'simpleui_contest_dashboard', kwargs={'contest_id': contest.id}
),
'public_contest': isinstance(
contest.controller.registration_controller(),
PublicContestRegistrationController,
),
}
if not contest_dict['public_contest']:
contest_dict['user_count'] = (
contest.controller.registration_controller()
.filter_participants(User.objects.all())
.count()
)
contest_context.append(contest_dict)
context = {
'contests': contest_context,
'are_contests_limited': are_contests_limited,
'has_portal': has_portal,
'can_create_contest': can_create_contest(request),
}
if has_portal:
context['portal_path'] = Portal.objects.filter(owner=None, link_name='default')[
0
].root.get_path()
return TemplateResponse(request, 'simpleui/main_dashboard/dashboard.html', context)
@enforce_condition(contest_exists & is_contest_basicadmin)
[docs]def problem_settings(request, problem_instance_id):
pi = get_object_or_404(
ProblemInstance, id=problem_instance_id, contest=request.contest
)
problem = pi.problem
tests = pi.test_set.all()
TestFormset = modelformset_factory(Test, form=TestForm, extra=0)
ProblemInstanceFormset = modelformset_factory(
ProblemInstance, form=ProblemInstanceForm, extra=0
)
if request.method == 'POST':
pi_formset = ProblemInstanceFormset(request.POST, prefix='pif')
test_formset = TestFormset(request.POST)
# Bind the clean method, which serves as a time limit and max
# scores equality validator.
# http://stackoverflow.com/questions/9646187
test_formset.get_time_limit_sum = types.MethodType(
ValidationFormset.__dict__['get_time_limit_sum'], test_formset
)
test_formset.validate_time_limit_sum = types.MethodType(
ValidationFormset.__dict__['validate_time_limit_sum'], test_formset
)
test_formset.validate_max_scores_in_group = types.MethodType(
ValidationFormset.__dict__['validate_max_scores_in_group'], test_formset
)
test_formset.clean = types.MethodType(
ValidationFormset.__dict__['clean'], test_formset
)
if pi_formset.is_valid() and test_formset.is_valid():
pi_formset.save()
test_formset.save()
return redirect(
reverse(
'simpleui_problem_settings',
kwargs={'problem_instance_id': problem_instance_id},
)
)
test_forms = test_formset
pi_form = pi_formset
else:
test_forms = TestFormset(queryset=tests)
pi_form = ProblemInstanceFormset(
queryset=ProblemInstance.objects.filter(id=pi.id),
prefix='pif',
)
context = {
'problem_instance': pi,
'problem': problem,
'can_admin_problem': can_admin_problem(request, problem),
'tests': tests,
'pi_form': pi_form,
'test_forms': test_forms,
}
return TemplateResponse(request, 'simpleui/problem_settings/settings.html', context)
@register_contest_dashboard_view(
order=100, condition=(contest_exists & is_contest_basicadmin & ~is_superuser)
)
[docs]def contest_dashboard_redirect(request):
return redirect(
reverse('simpleui_contest_dashboard', kwargs={'contest_id': request.contest.id})
)