<commit_before>from collections import namedtuple
from operator import attrgetter
from datetime import timedelta

from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import connection
from django.db.models import Max, Count
from django.http import HttpResponseRedirect, HttpResponseBadRequest, Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils import timezone
from django.utils.functional import SimpleLazyObject
from django.views.generic import ListView

from judge.comments import CommentedDetailView
from judge.models import Contest, ContestParticipation, ContestProblem, Profile
from judge.utils.ranker import ranker
from judge.utils.views import TitleMixin, generic_message
from judge import event_poster as event


__all__ = ['ContestList', 'ContestDetail', 'contest_ranking', 'join_contest', 'leave_contest', 'contest_ranking_ajax']


def _find_contest(request, key, private_check=True):
    try:
        contest = Contest.objects.get(key=key)
        if private_check and not contest.is_public and not request.user.has_perm('judge.see_private_contest'):
            raise ObjectDoesNotExist()
    except ObjectDoesNotExist:
        return generic_message(request, 'No such contest',
                               'Could not find a contest with the key "%s".' % key, status=404), False
    return contest, True


class ContestList(TitleMixin, ListView):
    model = Contest
    template_name = 'contest/list.jade'
    title = 'Contests'

    def get_queryset(self):
        queryset = Contest.objects.order_by('-start_time', 'key')
        if not self.request.user.has_perm('judge.see_private_contest'):
            queryset = queryset.filter(is_public=True)
        queryset = queryset.annotate(participation_count=Count('users'))
        return queryset

    def get_context_data(self, **kwargs):
        context = super(ContestList, self).get_context_data(**kwargs)
        now = timezone.now()
        past, present, future = [], [], []
        for contest in self.get_queryset():
            if contest.end_time < now:
                past.append(contest)
            elif contest.start_time > now:
                future.append(contest)
            else:
                present.append(contest)
        future.sort(key=attrgetter('start_time'))
        context['current_contests'] = present
        context['past_contests'] = past
        context['future_contests'] = future
        return context


class ContestMixin(object):
    context_object_name = 'contest'
    model = Contest
    slug_field = 'key'
    slug_url_kwarg = 'key'
    private_check = True

    def get_object(self, queryset=None):
        contest = super(ContestMixin, self).get_object(queryset)
        if self.private_check and not contest.is_public and not self.request.user.has_perm('judge.see_private_contest'):
            raise Http404()
        return contest

    def dispatch(self, request, *args, **kwargs):
        try:
            return super(ContestMixin, self).dispatch(request, *args, **kwargs)
        except Http404:
            key = kwargs.get(self.slug_url_kwarg, None)
            if key:
                return generic_message(request, 'No such contest',
                                       'Could not find a contest with the key "%s".' % key)
            else:
                return generic_message(request, 'No such contest',
                                       'Could not find such contest.')


class ContestDetail(ContestMixin, TitleMixin, CommentedDetailView):
    template_name = 'contest/contest.jade'

    def get_comment_page(self):
        return 'c:%s' % self.object.key

    def get_title(self):
        return self.object.name

    def get_context_data(self, **kwargs):
        context = super(ContestDetail, self).get_context_data(**kwargs)
        if self.request.user.is_authenticated():
            contest_profile = self.request.user.profile.contest
            try:
                context['participation'] = contest_profile.history.get(contest=self.object)
            except ContestParticipation.DoesNotExist:
                context['participating'] = False
                context['participation'] = None
            else:
                context['participating'] = True
            context['in_contest'] = contest_profile.current is not None and contest_profile.current.contest == self.object
        else:
            context['participating'] = False
            context['participation'] = None
            context['in_contest'] = False
        context['now'] = timezone.now()
        return context


@login_required
def join_contest(request, key):
    contest, exists = _find_contest(request, key)
    if not exists:
        return contest

    if not contest.can_join:
        return generic_message(request, 'Contest not ongoing',
                               '"%s" is not currently ongoing.' % contest.name)

    contest_profile = request.user.profile.contest
    if contest_profile.current is not None:
        return generic_message(request, 'Already in contest',
                               'You are already in a contest: "%s".' % contest_profile.current.contest.name)

    participation, created = ContestParticipation.objects.get_or_create(
        contest=contest, profile=contest_profile, defaults={
            'real_start': timezone.now()
        }
    )

    if not created and participation.ended:
        return generic_message(request, 'Time limit exceeded',
                               'Too late! You already used up your time limit for "%s".' % contest.name)

    contest_profile.current = participation
    contest_profile.save()
    return HttpResponseRedirect(reverse('problem_list'))


@login_required
def leave_contest(request, key):
    # No public checking because if we hide the contest people should still be able to leave.
    # No lock ins.
    contest, exists = _find_contest(request, key, False)
    if not exists:
        return contest

    contest_profile = request.user.profile.contest
    if contest_profile.current is None or contest_profile.current.contest != contest:
        return generic_message(request, 'No such contest',
                               'You are not in contest "%s".' % key, 404)
    contest_profile.current = None
    contest_profile.save()
    return HttpResponseRedirect(reverse('contest_view', args=(key,)))


ContestRankingProfile = namedtuple('ContestRankingProfile',
                                   'id user display_rank long_display_name points cumtime problems rating organization')
BestSolutionData = namedtuple('BestSolutionData', 'code points time state')


def contest_ranking_list(contest, problems):
    cursor = connection.cursor()
    cursor.execute('''
        SELECT part.id, prob.id, p.code, MAX(cs1.points) AS best,
               (SELECT MAX(sub.date)
                FROM judge_submission sub INNER JOIN judge_contestsubmission cs2 ON
                    (sub.id = cs2.submission_id)
                WHERE cs2.participation_id = part.id AND cs2.problem_id = prob.id) AS `last`
        FROM judge_contestproblem prob CROSS JOIN judge_contestparticipation part INNER JOIN
             judge_problem p ON (prob.problem_id = p.id) LEFT OUTER JOIN
             judge_contestsubmission cs1 ON (cs1.problem_id = prob.id AND cs1.participation_id = part.id)
        WHERE prob.contest_id = %s AND part.contest_id = %s
        GROUP BY prob.id, part.id
    ''', (contest.id, contest.id))
    data = {(part, prob): (code, best, last) for part, prob, code, best, last in cursor.fetchall()}
    problems = map(attrgetter('id', 'points'), problems)
    cursor.close()

    def make_ranking_profile(participation):
        contest_profile = participation.profile
        user = contest_profile.user
        part = participation.id<commit_msg>Save one subquery.<commit_after>from collections import namedtuple
from operator import attrgetter
from datetime import timedelta

from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import connection
from django.db.models import Max, Count
from django.http import HttpResponseRedirect, HttpResponseBadRequest, Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils import timezone
from django.utils.functional import SimpleLazyObject
from django.views.generic import ListView

from judge.comments import CommentedDetailView
from judge.models import Contest, ContestParticipation, ContestProblem, Profile
from judge.utils.ranker import ranker
from judge.utils.views import TitleMixin, generic_message
from judge import event_poster as event


__all__ = ['ContestList', 'ContestDetail', 'contest_ranking', 'join_contest', 'leave_contest', 'contest_ranking_ajax']


def _find_contest(request, key, private_check=True):
    try:
        contest = Contest.objects.get(key=key)
        if private_check and not contest.is_public and not request.user.has_perm('judge.see_private_contest'):
            raise ObjectDoesNotExist()
    except ObjectDoesNotExist:
        return generic_message(request, 'No such contest',
                               'Could not find a contest with the key "%s".' % key, status=404), False
    return contest, True


class ContestList(TitleMixin, ListView):
    model = Contest
    template_name = 'contest/list.jade'
    title = 'Contests'

    def get_queryset(self):
        queryset = Contest.objects.order_by('-start_time', 'key')
        if not self.request.user.has_perm('judge.see_private_contest'):
            queryset = queryset.filter(is_public=True)
        queryset = queryset.annotate(participation_count=Count('users'))
        return queryset

    def get_context_data(self, **kwargs):
        context = super(ContestList, self).get_context_data(**kwargs)
        now = timezone.now()
        past, present, future = [], [], []
        for contest in self.get_queryset():
            if contest.end_time < now:
                past.append(contest)
            elif contest.start_time > now:
                future.append(contest)
            else:
                present.append(contest)
        future.sort(key=attrgetter('start_time'))
        context['current_contests'] = present
        context['past_contests'] = past
        context['future_contests'] = future
        return context


class ContestMixin(object):
    context_object_name = 'contest'
    model = Contest
    slug_field = 'key'
    slug_url_kwarg = 'key'
    private_check = True

    def get_object(self, queryset=None):
        contest = super(ContestMixin, self).get_object(queryset)
        if self.private_check and not contest.is_public and not self.request.user.has_perm('judge.see_private_contest'):
            raise Http404()
        return contest

    def dispatch(self, request, *args, **kwargs):
        try:
            return super(ContestMixin, self).dispatch(request, *args, **kwargs)
        except Http404:
            key = kwargs.get(self.slug_url_kwarg, None)
            if key:
                return generic_message(request, 'No such contest',
                                       'Could not find a contest with the key "%s".' % key)
            else:
                return generic_message(request, 'No such contest',
                                       'Could not find such contest.')


class ContestDetail(ContestMixin, TitleMixin, CommentedDetailView):
    template_name = 'contest/contest.jade'

    def get_comment_page(self):
        return 'c:%s' % self.object.key

    def get_title(self):
        return self.object.name

    def get_context_data(self, **kwargs):
        context = super(ContestDetail, self).get_context_data(**kwargs)
        if self.request.user.is_authenticated():
            contest_profile = self.request.user.profile.contest
            try:
                context['participation'] = contest_profile.history.get(contest=self.object)
            except ContestParticipation.DoesNotExist:
                context['participating'] = False
                context['participation'] = None
            else:
                context['participating'] = True
            context['in_contest'] = contest_profile.current is not None and contest_profile.current.contest == self.object
        else:
            context['participating'] = False
            context['participation'] = None
            context['in_contest'] = False
        context['now'] = timezone.now()
        return context


@login_required
def join_contest(request, key):
    contest, exists = _find_contest(request, key)
    if not exists:
        return contest

    if not contest.can_join:
        return generic_message(request, 'Contest not ongoing',
                               '"%s" is not currently ongoing.' % contest.name)

    contest_profile = request.user.profile.contest
    if contest_profile.current is not None:
        return generic_message(request, 'Already in contest',
                               'You are already in a contest: "%s".' % contest_profile.current.contest.name)

    participation, created = ContestParticipation.objects.get_or_create(
        contest=contest, profile=contest_profile, defaults={
            'real_start': timezone.now()
        }
    )

    if not created and participation.ended:
        return generic_message(request, 'Time limit exceeded',
                               'Too late! You already used up your time limit for "%s".' % contest.name)

    contest_profile.current = participation
    contest_profile.save()
    return HttpResponseRedirect(reverse('problem_list'))


@login_required
def leave_contest(request, key):
    # No public checking because if we hide the contest people should still be able to leave.
    # No lock ins.
    contest, exists = _find_contest(request, key, False)
    if not exists:
        return contest

    contest_profile = request.user.profile.contest
    if contest_profile.current is None or contest_profile.current.contest != contest:
        return generic_message(request, 'No such contest',
                               'You are not in contest "%s".' % key, 404)
    contest_profile.current = None
    contest_profile.save()
    return HttpResponseRedirect(reverse('contest_view', args=(key,)))


ContestRankingProfile = namedtuple('ContestRankingProfile',
                                   'id user display_rank long_display_name points cumtime problems rating organization')
BestSolutionData = namedtuple('BestSolutionData', 'code points time state')


def contest_ranking_list(contest, problems):
    cursor = connection.cursor()
    cursor.execute('''
        SELECT part.id, cp.id, prob.code, MAX(cs.points) AS best, MAX(sub.date) AS `last`
        FROM judge_contestproblem cp CROSS JOIN judge_contestparticipation part INNER JOIN
             judge_problem prob ON (cp.problem_id = prob.id) LEFT OUTER JOIN
             judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = part.id) LEFT OUTER JOIN
             judge_submission sub ON (sub.id = cs.submission_id)
        WHERE cp.contest_id = 39 AND part.contest_id = 39
        GROUP BY cp.id, part.id
    ''', (contest.id, contest.id))
    data = {(part, prob): (code, best, last) for part, prob, code, best, last in cursor.fetchall()}
    problems = map(attrgetter('id', 'points'), problems)
    cursor.close()

    def make_ranking_profile(participation):
        contest_profile = participation.profile
        user = contest_profile.user
        part = participation.id
{'fraction_non_alphanumeric': 0.0895171537484117, 'fraction_numerical': 0.002668360864040661, 'mean_word_length': 2.825273390036452, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7114018', 'n_tokens_mistral': 4413, 'n_tokens_neox': 4228, 'n_words': 1142}