Source code for oioioi.contests.middleware

from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import NoReverseMatch, resolve, reverse

from oioioi.contests.current_contest import ContestMode, contest_re, set_cc_id
from oioioi.contests.models import Contest, ContestView
from oioioi.contests.utils import visible_contests


[docs]def activate_contest(request, contest): request.contest = contest set_cc_id(contest.id if contest else None) if not contest: return recent_contests = request.session.get('recent_contests', []) if not recent_contests or recent_contests[0] != contest.id: try: del recent_contests[recent_contests.index(contest.id)] except ValueError: pass recent_contests = [contest.id] + recent_contests recent_contests = recent_contests[: getattr(settings, 'NUM_RECENT_CONTESTS', 5)] request.session['recent_contests'] = recent_contests if not request.real_user.is_anonymous and not request.session.get( 'first_view_after_logging', False ): cv, created = ContestView.objects.get_or_create( user=request.real_user, contest=contest ) # Do not repeatedly update timestamp for latest contest. if cv != ContestView.objects.filter(user=request.real_user).latest() or created: cv.timestamp = request.timestamp cv.save()
[docs]class CurrentContestMiddleware(object): """Middleware which tracks the currently visited contest and stores it to be used in other parts of the current contest mechanism. It is assumed that all contest-specific url patterns are defined in the ``contest_patterns`` variable in each module's urlconf. These patterns are extended with non contest-specific patterns defined in the ``urlpatterns`` variable and then used to generate URLs prefixed with a contest ID (thus the non contest-specific URLs come in two versions, with and without a contest ID prefix). If a request matches a contest ID-prefixed URL and the ID is valid, the contest becomes the current contest. If the ID is not valid, a 404 Not Found is generated. After a contest becomes the current contest, the corresponding :class:`~oioioi.contests.models.Contest` instance is available in ``request.contest``. In addition to that, our custom :func:`~oioioi.contests.current_contest.reverse` function automatically prefixes generated URLs with the contest's ID if appropriate. Using ``settings.CONTEST_MODE``, the administrator may decide that users should, if possible, be forcibly put into a contest. Then, if there is no contest ID in a request's URL, but the URL also comes with a contest-specific version and a contest exists, a redirection is performed to one of the existing contests. Which one it is is determined by the following algorithm: #. If last contest is saved in session, this value is used. #. If the session value is not available or invalid, ``settings.DEFAULT_CONTEST`` is used. #. If not set, the most recently created contest will be chosen. URL patterns may be explicitly defined as requiring that no contest is given using the ``noncontest_patterns`` variable in each module's urlconf. Again, using ``settings.CONTEST_MODE``, the administrator may decide that if a contest is available, users cannot access those URLs. Trying to access them then generates a 403 Permission Denied unless one is a superuser. """ def __init__(self, get_response): self.get_response = get_response
[docs] def __call__(self, request): response = self._process_request(request) if response is None: return self.get_response(request) return response
[docs] def _get_contest(self, contest_id): try: return Contest.objects.get(id=contest_id) except Contest.DoesNotExist: return None
[docs] def _process_request(self, request): contest = None m = contest_re.match(request.path) if m is not None: contest_id = m.group('c_name') contest = get_object_or_404(Contest, id=contest_id) activate_contest(request, contest) if contest or settings.CONTEST_MODE == ContestMode.neutral: return # There was no contest, but CONTEST_MODE tells us we need # to try to put the user into a contest. recent_contests = request.session.get('recent_contests', []) if recent_contests: contest = self._get_contest(recent_contests[0]) if not contest and getattr(settings, 'DEFAULT_CONTEST', None): contest = self._get_contest(getattr(settings, 'DEFAULT_CONTEST')) if not contest: visible = visible_contests(request) if visible: # Get most recent visible contest contest = max(visible, key=lambda c: c.creation_date) if not contest: return # We found a contest. We will try to regenerate our URL, # this time using our contest's id. try: res = resolve(request.path) except Http404: # We still allow the request to continue, because # it could be a static URL. return if not res.url_name: return nonglobal = False if res.namespaces and res.namespaces[0] == 'noncontest': # It certainly isn't a global url because it has # a noncontest version. It could still be a neutral URL. nonglobal = True res.namespaces = res.namespaces[1:] assert 'contest_id' not in res.kwargs res.kwargs['contest_id'] = contest.id # If there is a contest-prefixed version of this url, # reverse will return it. assert not res.namespaces or res.namespaces[0] != 'contest' name = res.url_name if res.namespaces: name = ':'.join(res.namespaces + [name]) try: new_path = reverse(name, args=res.args, kwargs=res.kwargs) assert contest_re.match(new_path) if request.GET: new_path += '?' + request.GET.urlencode() return HttpResponseRedirect(new_path) except NoReverseMatch: if nonglobal: # That wasn't a global url and it doesn't have # a contest version. It must be a noncontest-only url. if ( settings.CONTEST_MODE == ContestMode.contest_only and not request.user.is_superuser ): raise PermissionDenied