from django.conf import settings
from django.contrib import auth, messages
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.mail import mail_admins
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from oioioi.base.utils import ObjectWithMixins, is_ajax
from oioioi.base.utils.loaders import load_modules
from oioioi.contestexcl.models import ExclusivenessConfig
from oioioi.contests.middleware import activate_contest
from oioioi.contests.models import Contest
[docs]class ExclusiveContestsMiddleware(ObjectWithMixins):
"""Middleware which checks whether the user participate in an
exclusive contest, which is a contest that blocks other contests,
and sets the current contest to that contest.
It works as follows:
#. If ONLY_DEFAULT_CONTEST is set, only the default contest is taken
into account.
#. All contests with active
:class:`~oioioi.contestexcl.models.ExclusivenessConfig` instance are
acquired from the database.
#. They are filtered with a special selector function, which by default
checks if the user is not a contest admin. In addition,
``process_view`` accepts another selector function as an argument.
If it is present, the contest list is filtered with a logical
conjunction of the default selector and the selector passed
as an argument (it may be useful with mixins).
#. If there is only one contest left, the ``request.contest`` variable
is set to this contest or a redirect is made if necessary.
#. If there is more than one contest left, the user is logged out,
an error message is displayed and an e-mail describing the situation
is sent to the administrators.
"""
def __init__(self, get_response):
self.get_response = get_response
[docs] def __call__(self, request):
return self.get_response(request)
[docs] def process_view(self, request, view_func, view_args, view_kwargs, selector=None):
if not self._check_requirements(request):
return
def _default_selector(user, contest):
return not user.has_perm('contests.contest_admin', contest)
if selector is None:
final_selector = _default_selector
else:
final_selector = lambda user, contest: _default_selector(
user, contest
) and selector(user, contest)
if settings.ONLY_DEFAULT_CONTEST and \
(request.user is None or not request.user.is_superuser):
qs = [Contest.objects.get(id=settings.DEFAULT_CONTEST)]
else:
qs = ExclusivenessConfig.objects.get_active(
request.timestamp
).select_related('contest')
qs = [ex_cf.contest for ex_cf in qs]
qs = [cnst for cnst in qs if final_selector(request.user, cnst)]
if len(qs) > 1:
self._send_error_email(request, qs)
activate_contest(request, None)
auth.logout(request)
return TemplateResponse(
request, 'contestexcl/exclusive-contests-error.html'
)
elif len(qs) == 1:
contest = qs[0]
if request.contest != contest:
if is_ajax(request):
raise PermissionDenied
else:
messages.info(
request,
_(
"You have been redirected to this contest,"
" because you are not currently allowed to access"
" other contests."
),
)
return redirect(
reverse(
'default_contest_view', kwargs={'contest_id': contest.id}
)
)
request.contest_exclusive = True
else:
request.contest_exclusive = False
[docs] def _check_requirements(self, request):
if not hasattr(request, 'timestamp'):
raise ImproperlyConfigured(
"oioioi.base.middleware.TimestampingMiddleware is required."
" If you have it installed check if it comes before"
"ExclusiveContestsMiddleware in your MIDDLEWARE"
"setting"
)
if not hasattr(request, 'contest'):
raise ImproperlyConfigured(
"oioioi.contests.middleware.CurrentContestMiddleware is"
" required. If you have it installed check if it comes before "
"ExclusiveContestsMiddleware in your MIDDLEWARE"
"setting"
)
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"django.contrib.auth.middleware.AuthenticationMiddleware is"
"required. If you have it installed check if it comes before "
"ExclusiveContestsMiddleware in your MIDDLEWARE"
"setting"
)
return request.contest
[docs] def _send_error_email(self, request, contests):
context = self._error_email_context(request, contests)
message = self._error_email_message(context)
subject = render_to_string('contestexcl/exclusive-contests-error-subject.txt')
subject = ' '.join(subject.strip().splitlines())
mail_admins(subject, message)
[docs] def _error_email_message(self, context):
return render_to_string(
'contestexcl/exclusive-contests-error-email.txt', context
)
[docs] def _error_email_context(self, request, contests):
contests_data = [(cnst.name, cnst.id) for cnst in contests]
return {'contests': contests_data, 'username': request.user.username}
# This causes adding all mixins to ExclusiveContestMiddleware.
# This is needed as middlewares are instantiated before importing
# INSTALLED_APPS, so mixins would be late.
load_modules('middleware')