Skip to content

Commit

Permalink
SIO-1641 Contest-free side of SIO2.
Browse files Browse the repository at this point in the history
The current contest mechanism has been overhauled completely.
The active contest isn't being held in the session anymore. A contest
is active if and only if the visited URL is prefixed with the contest's
ID.
Administrators can use a setting to decide if users should be put
inside a contest forcibly (which happens by redirections) or if they can
visit pages without participating in any contest, and in particular, if
they can visit pages that require no contest to be active.
Developers can write views that require a contest, that are neutral, or
that require no contest to be active. The decision is made in the
urlconf by placing patterns pointing to the views in one of three
variables. Also ModelAdmins can be registered in two different sites,
where one of them contains ModelAdmins that require a contest.
Read the docs for more information.

Change-Id: Iea9640060b9b580ae0bd76aafd987522e9fcddb6
  • Loading branch information
kbr- committed May 15, 2015
1 parent 4ef310b commit 0818f6b
Show file tree
Hide file tree
Showing 117 changed files with 1,033 additions and 494 deletions.
2 changes: 2 additions & 0 deletions oioioi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# apply monkey patch
from oioioi.contests import current_contest
5 changes: 3 additions & 2 deletions oioioi/balloons/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
BalloonsDisplay, BalloonsDeliveryAccessData
from oioioi.base.admin import system_admin_menu_registry
from oioioi.base.utils import make_html_link
from oioioi.contests.admin import ContestAdmin
from oioioi.contests.admin import ContestAdmin, contest_site
from oioioi.contests.models import ProblemInstance, Contest
from oioioi.contests.menu import contest_admin_menu_registry
from oioioi.contests.utils import is_contest_admin
Expand Down Expand Up @@ -50,7 +50,8 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
return super(ProblemBalloonsConfigAdmin, self) \
.formfield_for_foreignkey(db_field, request, **kwargs)

admin.site.register(ProblemBalloonsConfig, ProblemBalloonsConfigAdmin)
contest_site.contest_register(ProblemBalloonsConfig,
ProblemBalloonsConfigAdmin)
contest_admin_menu_registry.register('problemballoonsconfig_admin',
_("Balloons colors"), lambda request:
reverse('oioioiadmin:balloons_problemballoonsconfig_changelist'),
Expand Down
1 change: 0 additions & 1 deletion oioioi/balloons/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@
name='balloon_svg'),
url(r'^balloons/$', 'balloons_view', name='balloons'),
url(r'^balloons/body/$', 'balloons_body_view', name='balloons_body'),
url(r'^c/(?P<contest_id>[a-z0-9_-]+)/', include(contest_patterns)),
)
10 changes: 5 additions & 5 deletions oioioi/balloons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def balloons_body_view(request):

@enforce_condition(contest_exists & is_contest_admin)
@require_POST
def balloons_regenerate_delivery_key_view(request, contest_id):
def balloons_regenerate_delivery_key_view(request):
contest = get_object_or_404(Contest, id=request.contest.id)
access_data = BalloonsDeliveryAccessData.objects \
.get_or_create(contest=contest)[0]
Expand All @@ -97,7 +97,7 @@ def balloons_regenerate_delivery_key_view(request, contest_id):
quote(request.contest.id))


def balloons_access_cookie_view(request, contest_id, access_key):
def balloons_access_cookie_view(request, access_key):
access_data = get_object_or_404(
BalloonsDeliveryAccessData,
contest_id=request.contest.id,
Expand All @@ -117,13 +117,13 @@ def balloons_access_cookie_view(request, contest_id, access_key):


@enforce_condition(has_balloons_cookie, login_redirect=False)
def balloons_delivery_panel_view(request, contest_id):
def balloons_delivery_panel_view(request):
return TemplateResponse(request, 'balloons/balloons-delivery-panel.html')


@jsonify
@enforce_condition(has_balloons_cookie, login_redirect=False)
def get_new_balloon_requests_view(request, contest_id):
def get_new_balloon_requests_view(request):
try:
last_id = int(request.GET['last_id'])
except KeyError:
Expand Down Expand Up @@ -156,7 +156,7 @@ def get_new_balloon_requests_view(request, contest_id):
@jsonify
@enforce_condition(has_balloons_cookie, login_redirect=False)
@require_POST
def set_balloon_delivered_view(request, contest_id):
def set_balloon_delivered_view(request):
try:
new_delivered = request.POST['new_delivered'] == 'True'
old_delivered = not new_delivered
Expand Down
2 changes: 2 additions & 0 deletions oioioi/base/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ def test_login(self):
self.assertTrue(response['Location'].endswith('/test'))

def test_logout(self):
self.client.get('/', follow=True)
logout_url = reverse('logout')
response = self.client.get(logout_url)
self.assertEqual(405, response.status_code)
Expand Down Expand Up @@ -1076,6 +1077,7 @@ def test_message(self):
response.content)

def test_login_change(self):
self.client.get('/', follow=True)
url_index = reverse('index')
url_edit_profile = reverse('edit_profile')

Expand Down
11 changes: 9 additions & 2 deletions oioioi/base/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.conf.urls import patterns, url
from django.conf.urls import patterns, url, include

from oioioi.base import registration_backend, admin

urlpatterns = patterns('oioioi.base.views',
url(r'^$', 'index_view', name='index'),
Expand All @@ -8,8 +10,13 @@
url(r'^edit_profile/$', 'edit_profile_view', name='edit_profile'),
url(r'^logout/$', 'logout_view', name='logout'),
url(r'^translate/$', 'translate_view', name='translate'),
url(r'^admin/logout/$', 'logout_view'),
url(r'^login/$', 'login_view', name='login'),
url(r'^delete_account/$', 'delete_account_view', name='delete_account'),
url(r'^generate_key/$', 'generate_key_view', name='generate_key'),

# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/logout/$', 'logout_view'),
url(r'^admin/', include(admin.site.urls)),
)

urlpatterns += registration_backend.urlpatterns
6 changes: 3 additions & 3 deletions oioioi/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from oioioi.base.permissions import enforce_condition, not_anonymous
from oioioi.base.utils.redirect import safe_redirect
from oioioi.base.utils.user import has_valid_username
from oioioi.contests.views import default_contest_view
from oioioi.contests.models import Contest
from oioioi.base.forms import UserForm
from oioioi.base.utils import jsonify, generate_key
from oioioi.base.menu import account_menu_registry
Expand All @@ -37,9 +37,9 @@ def index_view(request):
return render_to_response("index.html",
context_instance=RequestContext(request))
except TemplateDoesNotExist:
if not request.contest:
if not Contest.objects.exists():
return TemplateResponse(request, "index-no-contests.html")
return default_contest_view(request, request.contest.id)
return redirect('select_contest')


def force_error_view(request):
Expand Down
8 changes: 6 additions & 2 deletions oioioi/clock/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django.test import TestCase
from django.test.utils import override_settings
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from oioioi.contests.models import Contest, Round, RoundTimeExtension
from oioioi.contests.current_contest import ContestMode
from django.utils.timezone import utc
from datetime import datetime
import calendar
Expand All @@ -13,6 +15,7 @@
class TestClock(TestCase):
fixtures = ['test_contest', 'test_users']

@override_settings(CONTEST_MODE=ContestMode.neutral)
def test_clock(self):
response = self.client.get(reverse('get_status'))
response = json.loads(response.content)
Expand All @@ -33,7 +36,7 @@ def test_countdown(self):
r1.save()
r2.save()

response = self.client.get(reverse('get_contest_status',
response = self.client.get(reverse('get_status',
kwargs={'contest_id': contest.id}))
response = json.loads(response.content)
round_start_date = response['round_start_date']
Expand All @@ -52,14 +55,15 @@ def test_countdown_with_extended_rounds(self):
RoundTimeExtension(user=user, round=r1, extra_time=10).save()

self.client.login(username='test_user')
response = self.client.get(reverse('get_contest_status',
response = self.client.get(reverse('get_status',
kwargs={'contest_id': contest.id}))
response = json.loads(response.content)
round_start_date = response['round_start_date']
round_end_date = response['round_end_date']
self.assertEqual(round_start_date, time.mktime(r1_start.timetuple()))
self.assertEqual(round_end_date, time.mktime(r1_end.timetuple()) + 600)

@override_settings(CONTEST_MODE=ContestMode.neutral)
def test_admin_time(self):
self.client.login(username='test_admin')
session = self.client.session
Expand Down
4 changes: 0 additions & 4 deletions oioioi/complaints/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@
url(r'^complaints/$', 'add_complaint_view', name='add_complaint'),
url(r'^complaint_sent/$', 'complaint_sent', name='complaint_sent'),
)

urlpatterns = patterns('oioioi.complaints.views',
url(r'^c/(?P<contest_id>[a-z0-9_-]+)/', include(contest_patterns)),
)
6 changes: 3 additions & 3 deletions oioioi/complaints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def notify_jury(request, body, message_id, ref_id):
message.send()


def complaint_sent(request, contest_id):
def complaint_sent(request):
return TemplateResponse(request, 'complaints/complaint_sent.html',
{'complaints_email': settings.COMPLAINTS_EMAIL})

Expand All @@ -106,7 +106,7 @@ def complaint_sent(request, contest_id):
reverse('add_complaint', kwargs={'contest_id': request.contest.id}),
order=400)
@enforce_condition(contest_exists & can_enter_contest & can_make_complaint)
def add_complaint_view(request, contest_id):
def add_complaint_view(request):
if not hasattr(settings, 'COMPLAINTS_EMAIL') \
or not hasattr(settings, 'COMPLAINTS_SUBJECT_PREFIX'):
raise ImproperlyConfigured('The oioioi.complaints module needs '
Expand All @@ -121,7 +121,7 @@ def add_complaint_view(request, contest_id):
jury_id, complainer_id)
notify_complainer(request, form.cleaned_data['complaint'],
complainer_id, jury_id)
return redirect('complaint_sent', contest_id=contest_id)
return redirect('complaint_sent', contest_id=request.contest.id)
else:
form = AddComplaintForm()
return TemplateResponse(request, 'complaints/make.html', {'form': form})
4 changes: 1 addition & 3 deletions oioioi/contestexcl/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class ExclusiveContestsMiddleware(ObjectWithMixins):
an error message is displayed and an e-mail describing the situation
is sent to the administrators.
"""

def process_view(self, request, view_func,
view_args, view_kwargs, selector=None):

Expand Down Expand Up @@ -70,8 +69,7 @@ def _default_selector(user, contest):
'contestexcl/exclusive_contests_error.html')
elif len(qs) == 1:
contest = qs[0]
activate_contest(request, contest)
if view_kwargs.get('contest_id', contest.id) != contest.id:
if request.contest != contest:
if request.is_ajax():
raise PermissionDenied
else:
Expand Down
4 changes: 0 additions & 4 deletions oioioi/contestlogo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,3 @@
url(r'^icons/(?P<icon_id>\d+)/$', 'icon_image_view',
name='icon_image_view'),
)

urlpatterns = patterns('oioioi.contestlogo.views',
url(r'^c/(?P<contest_id>[a-z0-9_-]+)/', include(contest_patterns)),
)
6 changes: 3 additions & 3 deletions oioioi/contestlogo/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ def stream_if_changed(request, image_object):


@cache_control(max_age=1200)
def logo_image_view(request, contest_id):
logo = get_object_or_404(ContestLogo, contest=contest_id)
def logo_image_view(request):
logo = get_object_or_404(ContestLogo, contest=request.contest.id)
return stream_if_changed(request, logo)


@cache_control(max_age=1200)
def icon_image_view(request, contest_id, icon_id):
def icon_image_view(request, icon_id):
icon = get_object_or_404(ContestIcon, id=icon_id)
return stream_if_changed(request, icon)
77 changes: 70 additions & 7 deletions oioioi/contests/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.conf.urls import patterns
from django.contrib.admin import AllValuesFieldListFilter, SimpleListFilter
from django.contrib.admin.sites import NotRegistered
from django.contrib.admin.util import unquote, quote
from django.core.urlresolvers import reverse
from django.forms.models import modelform_factory
Expand All @@ -22,9 +23,59 @@
Submission, ContestAttachment, RoundTimeExtension, ContestPermission, \
submission_kinds, ContestLink, SubmissionReport
from oioioi.contests.utils import is_contest_admin, is_contest_observer
from oioioi.contests.current_contest import set_cc_id
from oioioi.programs.models import Test, TestReport


class ContestProxyAdminSite(admin.AdminSite):
def __init__(self, orig):
super(ContestProxyAdminSite, self).__init__(orig.name, orig.app_name)
self._orig = orig

def register(self, model_or_iterable, admin_class=None, **options):
self._orig.register(model_or_iterable, admin_class, **options)

def unregister(self, model_or_iterable):
self._orig.unregister(model_or_iterable)
try:
super(ContestProxyAdminSite, self).unregister(model_or_iterable)
except NotRegistered:
pass

def contest_register(self, model_or_iterable, admin_class=None, **options):
super(ContestProxyAdminSite, self).register(model_or_iterable,
admin_class, **options)

def contest_unregister(self, model_or_iterable):
super(ContestProxyAdminSite, self).unregister(model_or_iterable)

def get_urls(self):
self._registry.update(self._orig._registry)
return super(ContestProxyAdminSite, self).get_urls()

def index(self, request, extra_context=None):
if request.contest:
return super(ContestProxyAdminSite, self).\
index(request, extra_context)
return self._orig.index(request, extra_context)

def app_index(self, request, app_label, extra_context=None):
if request.contest:
return super(ContestProxyAdminSite, self).\
app_index(request, app_label, extra_context)
return self._orig.app_index(request, app_label, extra_context)


#: Every contest-dependent model admin should be registered in this site
#: using the ``contest_register`` method. You can also register non-dependent
#: model admins like you would normally do using the ``register`` method.
#: Model admins registered using the ``contest_register`` method "don't exist"
#: when there is no active contest, that is, they can only be accessed
#: by a contest-prefixed URL and they don't show up in ``/admin/`` (but they
#: do in ``/c/<contest_id>/admin/``).
contest_site = ContestProxyAdminSite(admin.site)


class RoundInline(admin.StackedInline):
model = Round
extra = 0
Expand Down Expand Up @@ -148,12 +199,24 @@ def response_change(self, request, obj):
def response_add(self, request, obj, post_url_continue=None):
default_redirection = super(ContestAdmin, self).response_add(request,
obj, post_url_continue)
request.session['contest_id'] = obj.id
if '_continue' in request.POST or '_addanother' in request.POST:
return default_redirection
else:
return redirect('default_contest_view', contest_id=obj.id)

def response_delete(self, request):
set_cc_id(None)
return super(ContestAdmin, self).response_delete(request)

def change_view(self, request, object_id, form_url='', extra_context=None):
# The contest's edit view uses request.contest, so editing a contest
# when a different contest is active would produce weird results.
if not request.contest or request.contest.id != object_id:
return redirect('oioioiadmin:contests_contest_change',
object_id, contest_id=object_id)
return super(ContestAdmin, self).change_view(request,
object_id, form_url, extra_context)


class BaseContestAdmin(admin.MixinsAdmin):
default_model_admin = ContestAdmin
Expand All @@ -164,7 +227,7 @@ def _mixins_for_instance(self, request, instance=None):
return controller.mixins_for_admin() + \
controller.registration_controller().mixins_for_admin()

admin.site.register(Contest, BaseContestAdmin)
contest_site.register(Contest, BaseContestAdmin)

contest_admin_menu_registry.register('contest_change', _("Settings"),
lambda request: reverse('oioioiadmin:contests_contest_change',
Expand Down Expand Up @@ -194,7 +257,7 @@ def _problem_change_href(self, instance):
urllib.urlencode({'came_from': came_from})

def _problem_reupload_href(self, instance):
return reverse('add_or_update_contest_problem',
return reverse('add_or_update_problem',
kwargs={'contest_id': instance.contest.id}) + '?' + \
urllib.urlencode({'problem': instance.problem.id})

Expand Down Expand Up @@ -255,7 +318,7 @@ def get_queryset(self, request):
qs = qs.filter(contest=request.contest)
return qs

admin.site.register(ProblemInstance, ProblemInstanceAdmin)
contest_site.contest_register(ProblemInstance, ProblemInstanceAdmin)

contest_admin_menu_registry.register('problems_change',
_("Problems"), lambda request:
Expand Down Expand Up @@ -496,7 +559,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
return redirect('submission', contest_id=request.contest.id,
submission_id=unquote(object_id))

admin.site.register(Submission, SubmissionAdmin)
contest_site.contest_register(Submission, SubmissionAdmin)

contest_admin_menu_registry.register('submissions_admin', _("Submissions"),
lambda request: reverse('oioioiadmin:contests_submission_changelist'),
Expand Down Expand Up @@ -571,7 +634,7 @@ def get_list_select_related(self):
return super(RoundTimeExtensionAdmin, self).get_list_select_related() \
+ ['user', 'round__contest']

admin.site.register(RoundTimeExtension, RoundTimeExtensionAdmin)
contest_site.contest_register(RoundTimeExtension, RoundTimeExtensionAdmin)
contest_admin_menu_registry.register('roundtimeextension_admin',
_("Round extensions"), lambda request:
reverse('oioioiadmin:contests_roundtimeextension_changelist'),
Expand Down Expand Up @@ -606,7 +669,7 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
return super(ContestPermissionAdmin, self) \
.formfield_for_foreignkey(db_field, request, **kwargs)

admin.site.register(ContestPermission, ContestPermissionAdmin)
contest_site.register(ContestPermission, ContestPermissionAdmin)
admin.system_admin_menu_registry.register('contestspermission_admin',
_("Contest rights"), lambda request:
reverse('oioioiadmin:contests_contestpermission_changelist'),
Expand Down
Loading

0 comments on commit 0818f6b

Please sign in to comment.