Reformat using black

This commit is contained in:
cuom1999 2022-05-14 12:57:27 -05:00
parent efee4ad081
commit a87fb49918
221 changed files with 19127 additions and 7310 deletions

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class ChatBoxConfig(AppConfig): class ChatBoxConfig(AppConfig):
name = 'chat_box' name = "chat_box"

View file

@ -9,22 +9,43 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('judge', '0100_auto_20200127_0059'), ("judge", "0100_auto_20200127_0059"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Message', name="Message",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('time', models.DateTimeField(auto_now_add=True, verbose_name='posted time')), "id",
('body', models.TextField(max_length=8192, verbose_name='body of comment')), models.AutoField(
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='user')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"time",
models.DateTimeField(auto_now_add=True, verbose_name="posted time"),
),
(
"body",
models.TextField(max_length=8192, verbose_name="body of comment"),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="judge.Profile",
verbose_name="user",
),
),
], ],
options={ options={
'verbose_name': 'message', "verbose_name": "message",
'verbose_name_plural': 'messages', "verbose_name_plural": "messages",
'ordering': ('-time',), "ordering": ("-time",),
}, },
), ),
] ]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('chat_box', '0001_initial'), ("chat_box", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='message', model_name="message",
name='hidden', name="hidden",
field=models.BooleanField(default=False, verbose_name='is hidden'), field=models.BooleanField(default=False, verbose_name="is hidden"),
), ),
] ]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('chat_box', '0002_message_hidden'), ("chat_box", "0002_message_hidden"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='message', model_name="message",
name='hidden', name="hidden",
field=models.BooleanField(default=True, verbose_name='is hidden'), field=models.BooleanField(default=True, verbose_name="is hidden"),
), ),
] ]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('chat_box', '0003_auto_20200505_2306'), ("chat_box", "0003_auto_20200505_2306"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='message', model_name="message",
name='hidden', name="hidden",
field=models.BooleanField(default=False, verbose_name='is hidden'), field=models.BooleanField(default=False, verbose_name="is hidden"),
), ),
] ]

View file

@ -7,22 +7,52 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0116_auto_20211011_0645'), ("judge", "0116_auto_20211011_0645"),
('chat_box', '0004_auto_20200505_2336'), ("chat_box", "0004_auto_20200505_2336"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Room', name="Room",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('user_one', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_one', to='judge.Profile', verbose_name='user 1')), "id",
('user_two', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_two', to='judge.Profile', verbose_name='user 2')), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"user_one",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_one",
to="judge.Profile",
verbose_name="user 1",
),
),
(
"user_two",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_two",
to="judge.Profile",
verbose_name="user 2",
),
),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='message', model_name="message",
name='room', name="room",
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id'), field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="chat_box.Room",
verbose_name="room id",
),
), ),
] ]

View file

@ -7,18 +7,42 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0116_auto_20211011_0645'), ("judge", "0116_auto_20211011_0645"),
('chat_box', '0005_auto_20211011_0714'), ("chat_box", "0005_auto_20211011_0714"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='UserRoom', name="UserRoom",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('last_seen', models.DateTimeField(verbose_name='last seen')), "id",
('room', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id')), models.AutoField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='user')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("last_seen", models.DateTimeField(verbose_name="last seen")),
(
"room",
models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="chat_box.Room",
verbose_name="room id",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="judge.Profile",
verbose_name="user",
),
),
], ],
), ),
] ]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('chat_box', '0006_userroom'), ("chat_box", "0006_userroom"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='userroom', model_name="userroom",
name='last_seen', name="last_seen",
field=models.DateTimeField(auto_now_add=True, verbose_name='last seen'), field=models.DateTimeField(auto_now_add=True, verbose_name="last seen"),
), ),
] ]

View file

@ -7,17 +7,33 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0116_auto_20211011_0645'), ("judge", "0116_auto_20211011_0645"),
('chat_box', '0007_auto_20211112_1255'), ("chat_box", "0007_auto_20211112_1255"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Ignore', name="Ignore",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('ignored_users', models.ManyToManyField(to='judge.Profile')), "id",
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ignored_chat_users', to='judge.Profile', verbose_name='user')), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("ignored_users", models.ManyToManyField(to="judge.Profile")),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="ignored_chat_users",
to="judge.Profile",
verbose_name="user",
),
),
], ],
), ),
] ]

View file

@ -6,25 +6,35 @@ from django.utils.translation import gettext_lazy as _
from judge.models.profile import Profile from judge.models.profile import Profile
__all__ = ['Message'] __all__ = ["Message"]
class Room(models.Model): class Room(models.Model):
user_one = models.ForeignKey(Profile, related_name="user_one", verbose_name='user 1', on_delete=CASCADE) user_one = models.ForeignKey(
user_two = models.ForeignKey(Profile, related_name="user_two", verbose_name='user 2', on_delete=CASCADE) Profile, related_name="user_one", verbose_name="user 1", on_delete=CASCADE
)
user_two = models.ForeignKey(
Profile, related_name="user_two", verbose_name="user 2", on_delete=CASCADE
)
def contain(self, profile): def contain(self, profile):
return self.user_one == profile or self.user_two == profile return self.user_one == profile or self.user_two == profile
def other_user(self, profile): def other_user(self, profile):
return self.user_one if profile == self.user_two else self.user_two return self.user_one if profile == self.user_two else self.user_two
def users(self): def users(self):
return [self.user_one, self.user_two] return [self.user_one, self.user_two]
class Message(models.Model): class Message(models.Model):
author = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE) author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True) time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192) body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
hidden = models.BooleanField(verbose_name='is hidden', default=False) hidden = models.BooleanField(verbose_name="is hidden", default=False)
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True) room = models.ForeignKey(
Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
new_message = self.id new_message = self.id
@ -32,26 +42,37 @@ class Message(models.Model):
super(Message, self).save(*args, **kwargs) super(Message, self).save(*args, **kwargs)
class Meta: class Meta:
app_label = 'chat_box' app_label = "chat_box"
verbose_name = 'message' verbose_name = "message"
verbose_name_plural = 'messages' verbose_name_plural = "messages"
ordering = ('-time',) ordering = ("-time",)
class UserRoom(models.Model): class UserRoom(models.Model):
user = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE) user = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True) room = models.ForeignKey(
last_seen = models.DateTimeField(verbose_name=_('last seen'), auto_now_add=True) Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
)
last_seen = models.DateTimeField(verbose_name=_("last seen"), auto_now_add=True)
class Ignore(models.Model): class Ignore(models.Model):
user = models.ForeignKey(Profile, related_name="ignored_chat_users", verbose_name=_('user'), on_delete=CASCADE) user = models.ForeignKey(
Profile,
related_name="ignored_chat_users",
verbose_name=_("user"),
on_delete=CASCADE,
)
ignored_users = models.ManyToManyField(Profile) ignored_users = models.ManyToManyField(Profile)
@classmethod @classmethod
def is_ignored(self, current_user, new_friend): def is_ignored(self, current_user, new_friend):
try: try:
return current_user.ignored_chat_users.get().ignored_users \ return (
.filter(id=new_friend.id).exists() current_user.ignored_chat_users.get()
.ignored_users.filter(id=new_friend.id)
.exists()
)
except: except:
return False return False
@ -64,21 +85,17 @@ class Ignore(models.Model):
@classmethod @classmethod
def add_ignore(self, current_user, friend): def add_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create( ignore, created = self.objects.get_or_create(user=current_user)
user = current_user
)
ignore.ignored_users.add(friend) ignore.ignored_users.add(friend)
@classmethod @classmethod
def remove_ignore(self, current_user, friend): def remove_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create( ignore, created = self.objects.get_or_create(user=current_user)
user = current_user
)
ignore.ignored_users.remove(friend) ignore.ignored_users.remove(friend)
@classmethod @classmethod
def toggle_ignore(self, current_user, friend): def toggle_ignore(self, current_user, friend):
if (self.is_ignored(current_user, friend)): if self.is_ignored(current_user, friend):
self.remove_ignore(current_user, friend) self.remove_ignore(current_user, friend)
else: else:
self.add_ignore(current_user, friend) self.add_ignore(current_user, friend)

View file

@ -5,14 +5,16 @@ from django.conf import settings
secret_key = settings.CHAT_SECRET_KEY secret_key = settings.CHAT_SECRET_KEY
fernet = Fernet(secret_key) fernet = Fernet(secret_key)
def encrypt_url(creator_id, other_id): def encrypt_url(creator_id, other_id):
message = str(creator_id) + '_' + str(other_id) message = str(creator_id) + "_" + str(other_id)
return fernet.encrypt(message.encode()).decode() return fernet.encrypt(message.encode()).decode()
def decrypt_url(message_encrypted): def decrypt_url(message_encrypted):
try: try:
dec_message = fernet.decrypt(message_encrypted.encode()).decode() dec_message = fernet.decrypt(message_encrypted.encode()).decode()
creator_id, other_id = dec_message.split('_') creator_id, other_id = dec_message.split("_")
return int(creator_id), int(other_id) return int(creator_id), int(other_id)
except Exception as e: except Exception as e:
return None, None return None, None

View file

@ -1,11 +1,27 @@
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from django.views.generic import ListView
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect, HttpResponseRedirect from django.http import (
HttpResponse,
JsonResponse,
HttpResponseBadRequest,
HttpResponsePermanentRedirect,
HttpResponseRedirect,
)
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import render from django.shortcuts import render
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from django.db.models import Case, BooleanField, When, Q, Subquery, OuterRef, Exists, Count, IntegerField from django.db.models import (
Case,
BooleanField,
When,
Q,
Subquery,
OuterRef,
Exists,
Count,
IntegerField,
)
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -24,9 +40,9 @@ import json
class ChatView(ListView): class ChatView(ListView):
context_object_name = 'message' context_object_name = "message"
template_name = 'chat/chat.html' template_name = "chat/chat.html"
title = _('Chat Box') title = _("Chat Box")
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -40,8 +56,8 @@ class ChatView(ListView):
return self.messages return self.messages
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
request_room = kwargs['room_id'] request_room = kwargs["room_id"]
page = request.GET.get('page') page = request.GET.get("page")
if request_room: if request_room:
try: try:
@ -64,45 +80,51 @@ class ChatView(ListView):
cur_page = self.paginator.get_page(page) cur_page = self.paginator.get_page(page)
return render(request, 'chat/message_list.html', { return render(
'object_list': cur_page.object_list, request,
'num_pages': self.paginator.num_pages "chat/message_list.html",
}) {
"object_list": cur_page.object_list,
"num_pages": self.paginator.num_pages,
},
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['title'] = self.title context["title"] = self.title
context['last_msg'] = event.last() context["last_msg"] = event.last()
context['status_sections'] = get_status_context(self.request) context["status_sections"] = get_status_context(self.request)
context['room'] = self.room_id context["room"] = self.room_id
context['unread_count_lobby'] = get_unread_count(None, self.request.profile) context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
if self.room: if self.room:
users_room = [self.room.user_one, self.room.user_two] users_room = [self.room.user_one, self.room.user_two]
users_room.remove(self.request.profile) users_room.remove(self.request.profile)
context['other_user'] = users_room[0] context["other_user"] = users_room[0]
context['other_online'] = get_user_online_status(context['other_user']) context["other_online"] = get_user_online_status(context["other_user"])
context['is_ignored'] = Ignore.is_ignored(self.request.profile, context['other_user']) context["is_ignored"] = Ignore.is_ignored(
self.request.profile, context["other_user"]
)
else: else:
context['online_count'] = get_online_count() context["online_count"] = get_online_count()
context['message_template'] = { context["message_template"] = {
'author': self.request.profile, "author": self.request.profile,
'id': '$id', "id": "$id",
'time': timezone.now(), "time": timezone.now(),
'body': '$body' "body": "$body",
} }
return context return context
def delete_message(request): def delete_message(request):
ret = {'delete': 'done'} ret = {"delete": "done"}
if request.method == 'GET': if request.method == "GET":
return JsonResponse(ret) return JsonResponse(ret)
if request.user.is_staff: if request.user.is_staff:
try: try:
messid = int(request.POST.get('message')) messid = int(request.POST.get("message"))
mess = Message.objects.get(id=messid) mess = Message.objects.get(id=messid)
except: except:
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -117,56 +139,62 @@ def delete_message(request):
@login_required @login_required
def post_message(request): def post_message(request):
ret = {'msg': 'posted'} ret = {"msg": "posted"}
if request.method != 'POST': if request.method != "POST":
return HttpResponseBadRequest() return HttpResponseBadRequest()
if len(request.POST['body']) > 5000: if len(request.POST["body"]) > 5000:
return HttpResponseBadRequest() return HttpResponseBadRequest()
room = None room = None
if request.POST['room']: if request.POST["room"]:
room = Room.objects.get(id=request.POST['room']) room = Room.objects.get(id=request.POST["room"])
if not can_access_room(request, room) or request.profile.mute: if not can_access_room(request, room) or request.profile.mute:
return HttpResponseBadRequest() return HttpResponseBadRequest()
new_message = Message(author=request.profile, new_message = Message(author=request.profile, body=request.POST["body"], room=room)
body=request.POST['body'],
room=room)
new_message.save() new_message.save()
if not room: if not room:
event.post('chat_lobby', { event.post(
'type': 'lobby', "chat_lobby",
'author_id': request.profile.id, {
'message': new_message.id, "type": "lobby",
'room': 'None', "author_id": request.profile.id,
'tmp_id': request.POST.get('tmp_id') "message": new_message.id,
}) "room": "None",
"tmp_id": request.POST.get("tmp_id"),
},
)
else: else:
for user in room.users(): for user in room.users():
event.post('chat_' + str(user.id), { event.post(
'type': 'private', "chat_" + str(user.id),
'author_id': request.profile.id, {
'message': new_message.id, "type": "private",
'room': room.id, "author_id": request.profile.id,
'tmp_id': request.POST.get('tmp_id') "message": new_message.id,
}) "room": room.id,
"tmp_id": request.POST.get("tmp_id"),
},
)
return JsonResponse(ret) return JsonResponse(ret)
def can_access_room(request, room): def can_access_room(request, room):
return not room or room.user_one == request.profile or room.user_two == request.profile return (
not room or room.user_one == request.profile or room.user_two == request.profile
)
@login_required @login_required
def chat_message_ajax(request): def chat_message_ajax(request):
if request.method != 'GET': if request.method != "GET":
return HttpResponseBadRequest() return HttpResponseBadRequest()
try: try:
message_id = request.GET['message'] message_id = request.GET["message"]
except KeyError: except KeyError:
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -174,22 +202,26 @@ def chat_message_ajax(request):
message = Message.objects.filter(hidden=False).get(id=message_id) message = Message.objects.filter(hidden=False).get(id=message_id)
room = message.room room = message.room
if room and not room.contain(request.profile): if room and not room.contain(request.profile):
return HttpResponse('Unauthorized', status=401) return HttpResponse("Unauthorized", status=401)
except Message.DoesNotExist: except Message.DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
return render(request, 'chat/message.html', { return render(
'message': message, request,
}) "chat/message.html",
{
"message": message,
},
)
@login_required @login_required
def update_last_seen(request, **kwargs): def update_last_seen(request, **kwargs):
if 'room_id' in kwargs: if "room_id" in kwargs:
room_id = kwargs['room_id'] room_id = kwargs["room_id"]
elif request.method == 'GET': elif request.method == "GET":
room_id = request.GET.get('room') room_id = request.GET.get("room")
elif request.method == 'POST': elif request.method == "POST":
room_id = request.POST.get('room') room_id = request.POST.get("room")
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -210,11 +242,11 @@ def update_last_seen(request, **kwargs):
user_room.last_seen = timezone.now() user_room.last_seen = timezone.now()
user_room.save() user_room.save()
return JsonResponse({'msg': 'updated'}) return JsonResponse({"msg": "updated"})
def get_online_count(): def get_online_count():
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2) last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
return Profile.objects.filter(last_access__gte=last_two_minutes).count() return Profile.objects.filter(last_access__gte=last_two_minutes).count()
@ -225,10 +257,10 @@ def get_user_online_status(user):
def user_online_status_ajax(request): def user_online_status_ajax(request):
if request.method != 'GET': if request.method != "GET":
return HttpResponseBadRequest() return HttpResponseBadRequest()
user_id = request.GET.get('user') user_id = request.GET.get("user")
if user_id: if user_id:
try: try:
@ -238,37 +270,45 @@ def user_online_status_ajax(request):
return HttpResponseBadRequest() return HttpResponseBadRequest()
is_online = get_user_online_status(user) is_online = get_user_online_status(user)
return render(request, 'chat/user_online_status.html', { return render(
'other_user': user, request,
'other_online': is_online, "chat/user_online_status.html",
'is_ignored': Ignore.is_ignored(request.profile, user) {
}) "other_user": user,
"other_online": is_online,
"is_ignored": Ignore.is_ignored(request.profile, user),
},
)
else: else:
return render(request, 'chat/user_online_status.html', { return render(
'online_count': get_online_count(), request,
}) "chat/user_online_status.html",
{
"online_count": get_online_count(),
},
)
def get_online_status(request_user, queryset, rooms=None): def get_online_status(request_user, queryset, rooms=None):
if not queryset: if not queryset:
return None return None
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2) last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
ret = [] ret = []
if rooms: if rooms:
unread_count = get_unread_count(rooms, request_user) unread_count = get_unread_count(rooms, request_user)
count = {} count = {}
for i in unread_count: for i in unread_count:
count[i['other_user']] = i['unread_count'] count[i["other_user"]] = i["unread_count"]
for user in queryset: for user in queryset:
is_online = False is_online = False
if (user.last_access >= last_two_minutes): if user.last_access >= last_two_minutes:
is_online = True is_online = True
user_dict = {'user': user, 'is_online': is_online} user_dict = {"user": user, "is_online": is_online}
if rooms and user.id in count: if rooms and user.id in count:
user_dict['unread_count'] = count[user.id] user_dict["unread_count"] = count[user.id]
user_dict['url'] = encrypt_url(request_user.id, user.id) user_dict["url"] = encrypt_url(request_user.id, user.id)
ret.append(user_dict) ret.append(user_dict)
return ret return ret
@ -281,66 +321,82 @@ def get_status_context(request, include_ignored=False):
ignored_users = Ignore.get_ignored_users(request.profile) ignored_users = Ignore.get_ignored_users(request.profile)
queryset = Profile.objects.exclude(id__in=ignored_users) queryset = Profile.objects.exclude(id__in=ignored_users)
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2) last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
recent_profile = Room.objects.filter( recent_profile = (
Q(user_one=request.profile) | Q(user_two=request.profile) Room.objects.filter(Q(user_one=request.profile) | Q(user_two=request.profile))
).annotate( .annotate(
last_msg_time=Subquery( last_msg_time=Subquery(
Message.objects.filter(room=OuterRef('pk')).values('time')[:1] Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
), ),
other_user=Case( other_user=Case(
When(user_one=request.profile, then='user_two'), When(user_one=request.profile, then="user_two"),
default='user_one', default="user_one",
) ),
).filter(last_msg_time__isnull=False)\ )
.exclude(other_user__in=ignored_users)\ .filter(last_msg_time__isnull=False)
.order_by('-last_msg_time').values('other_user', 'id')[:20] .exclude(other_user__in=ignored_users)
.order_by("-last_msg_time")
.values("other_user", "id")[:20]
)
recent_profile_id = [str(i['other_user']) for i in recent_profile] recent_profile_id = [str(i["other_user"]) for i in recent_profile]
joined_id = ','.join(recent_profile_id) joined_id = ",".join(recent_profile_id)
recent_rooms = [int(i['id']) for i in recent_profile] recent_rooms = [int(i["id"]) for i in recent_profile]
recent_list = None recent_list = None
if joined_id: if joined_id:
recent_list = Profile.objects.raw( recent_list = Profile.objects.raw(
f'SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})') f"SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})"
friend_list = Friend.get_friend_profiles(request.profile).exclude(id__in=recent_profile_id)\ )
.exclude(id__in=ignored_users)\ friend_list = (
.order_by('-last_access') Friend.get_friend_profiles(request.profile)
admin_list = queryset.filter(display_rank='admin')\ .exclude(id__in=recent_profile_id)
.exclude(id__in=friend_list).exclude(id__in=recent_profile_id) .exclude(id__in=ignored_users)
all_user_status = queryset\ .order_by("-last_access")
.filter(display_rank='user', )
last_access__gte = last_two_minutes)\ admin_list = (
.annotate(is_online=Case(default=True,output_field=BooleanField()))\ queryset.filter(display_rank="admin")
.order_by('-rating').exclude(id__in=friend_list).exclude(id__in=admin_list)\ .exclude(id__in=friend_list)
.exclude(id__in=recent_profile_id)
)
all_user_status = (
queryset.filter(display_rank="user", last_access__gte=last_two_minutes)
.annotate(is_online=Case(default=True, output_field=BooleanField()))
.order_by("-rating")
.exclude(id__in=friend_list)
.exclude(id__in=admin_list)
.exclude(id__in=recent_profile_id)[:30] .exclude(id__in=recent_profile_id)[:30]
)
return [ return [
{ {
'title': 'Recent', "title": "Recent",
'user_list': get_online_status(request.profile, recent_list, recent_rooms), "user_list": get_online_status(request.profile, recent_list, recent_rooms),
}, },
{ {
'title': 'Following', "title": "Following",
'user_list': get_online_status(request.profile, friend_list), "user_list": get_online_status(request.profile, friend_list),
}, },
{ {
'title': 'Admin', "title": "Admin",
'user_list': get_online_status(request.profile, admin_list), "user_list": get_online_status(request.profile, admin_list),
}, },
{ {
'title': 'Other', "title": "Other",
'user_list': get_online_status(request.profile, all_user_status), "user_list": get_online_status(request.profile, all_user_status),
}, },
] ]
@login_required @login_required
def online_status_ajax(request): def online_status_ajax(request):
return render(request, 'chat/online_status.html', { return render(
'status_sections': get_status_context(request), request,
'unread_count_lobby': get_unread_count(None, request.profile), "chat/online_status.html",
}) {
"status_sections": get_status_context(request),
"unread_count_lobby": get_unread_count(None, request.profile),
},
)
@login_required @login_required
@ -353,10 +409,10 @@ def get_room(user_one, user_two):
@login_required @login_required
def get_or_create_room(request): def get_or_create_room(request):
if request.method == 'GET': if request.method == "GET":
decrypted_other_id = request.GET.get('other') decrypted_other_id = request.GET.get("other")
elif request.method == 'POST': elif request.method == "POST":
decrypted_other_id = request.POST.get('other') decrypted_other_id = request.POST.get("other")
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -382,47 +438,60 @@ def get_or_create_room(request):
user_room.last_seen = timezone.now() user_room.last_seen = timezone.now()
user_room.save() user_room.save()
if request.method == 'GET': if request.method == "GET":
return JsonResponse({'room': room.id, 'other_user_id': other_user.id}) return JsonResponse({"room": room.id, "other_user_id": other_user.id})
return HttpResponseRedirect(reverse('chat', kwargs={'room_id': room.id})) return HttpResponseRedirect(reverse("chat", kwargs={"room_id": room.id}))
def get_unread_count(rooms, user): def get_unread_count(rooms, user):
if rooms: if rooms:
mess = Message.objects.filter(room=OuterRef('room'), mess = (
time__gte=OuterRef('last_seen'))\ Message.objects.filter(
.exclude(author=user)\ room=OuterRef("room"), time__gte=OuterRef("last_seen")
.order_by().values('room')\ )
.annotate(unread_count=Count('pk')).values('unread_count') .exclude(author=user)
.order_by()
.values("room")
.annotate(unread_count=Count("pk"))
.values("unread_count")
)
return UserRoom.objects\ return (
.filter(user=user, room__in=rooms)\ UserRoom.objects.filter(user=user, room__in=rooms)
.annotate( .annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
other_user=Case( other_user=Case(
When(room__user_one=user, then='room__user_two'), When(room__user_one=user, then="room__user_two"),
default='room__user_one', default="room__user_one",
) ),
).filter(unread_count__gte=1).values('other_user', 'unread_count') )
else: # lobby .filter(unread_count__gte=1)
mess = Message.objects.filter(room__isnull=True, .values("other_user", "unread_count")
time__gte=OuterRef('last_seen'))\ )
.exclude(author=user)\ else: # lobby
.order_by().values('room')\ mess = (
.annotate(unread_count=Count('pk')).values('unread_count') Message.objects.filter(room__isnull=True, time__gte=OuterRef("last_seen"))
.exclude(author=user)
.order_by()
.values("room")
.annotate(unread_count=Count("pk"))
.values("unread_count")
)
res = UserRoom.objects\ res = (
.filter(user=user, room__isnull=True)\ UserRoom.objects.filter(user=user, room__isnull=True)
.annotate( .annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
).values_list('unread_count', flat=True) )
.values_list("unread_count", flat=True)
)
return res[0] if len(res) else 0 return res[0] if len(res) else 0
@login_required @login_required
def toggle_ignore(request, **kwargs): def toggle_ignore(request, **kwargs):
user_id = kwargs['user_id'] user_id = kwargs["user_id"]
if not user_id: if not user_id:
return HttpResponseBadRequest() return HttpResponseBadRequest()
try: try:
@ -431,28 +500,34 @@ def toggle_ignore(request, **kwargs):
return HttpResponseBadRequest() return HttpResponseBadRequest()
Ignore.toggle_ignore(request.profile, other_user) Ignore.toggle_ignore(request.profile, other_user)
next_url = request.GET.get('next', '/') next_url = request.GET.get("next", "/")
return HttpResponseRedirect(next_url) return HttpResponseRedirect(next_url)
@login_required @login_required
def get_unread_boxes(request): def get_unread_boxes(request):
if (request.method != 'GET'): if request.method != "GET":
return HttpResponseBadRequest() return HttpResponseBadRequest()
ignored_users = Ignore.get_ignored_users(request.profile) ignored_users = Ignore.get_ignored_users(request.profile)
mess = Message.objects.filter(room=OuterRef('room'), mess = (
time__gte=OuterRef('last_seen'))\ Message.objects.filter(room=OuterRef("room"), time__gte=OuterRef("last_seen"))
.exclude(author=request.profile)\ .exclude(author=request.profile)
.exclude(author__in=ignored_users)\ .exclude(author__in=ignored_users)
.order_by().values('room')\ .order_by()
.annotate(unread_count=Count('pk')).values('unread_count') .values("room")
.annotate(unread_count=Count("pk"))
.values("unread_count")
)
unread_boxes = UserRoom.objects\ unread_boxes = (
.filter(user=request.profile, room__isnull=False)\ UserRoom.objects.filter(user=request.profile, room__isnull=False)
.annotate( .annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
).filter(unread_count__gte=1).count() )
.filter(unread_count__gte=1)
.count()
)
return JsonResponse({'unread_boxes': unread_boxes}) return JsonResponse({"unread_boxes": unread_boxes})

View file

@ -12,6 +12,6 @@ if (2, 2) <= django.VERSION < (3,):
# attribute where the exact query sent to the database is saved. # attribute where the exact query sent to the database is saved.
# See MySQLdb/cursors.py in the source distribution. # See MySQLdb/cursors.py in the source distribution.
# MySQLdb returns string, PyMySQL bytes. # MySQLdb returns string, PyMySQL bytes.
return force_text(getattr(cursor, '_executed', None), errors='replace') return force_text(getattr(cursor, "_executed", None), errors="replace")
DatabaseOperations.last_executed_query = last_executed_query DatabaseOperations.last_executed_query = last_executed_query

View file

@ -11,8 +11,17 @@ from django.utils.safestring import mark_safe
class AceWidget(forms.Textarea): class AceWidget(forms.Textarea):
def __init__(self, mode=None, theme=None, wordwrap=False, width='100%', height='300px', def __init__(
no_ace_media=False, *args, **kwargs): self,
mode=None,
theme=None,
wordwrap=False,
width="100%",
height="300px",
no_ace_media=False,
*args,
**kwargs
):
self.mode = mode self.mode = mode
self.theme = theme self.theme = theme
self.wordwrap = wordwrap self.wordwrap = wordwrap
@ -23,10 +32,10 @@ class AceWidget(forms.Textarea):
@property @property
def media(self): def media(self):
js = [urljoin(settings.ACE_URL, 'ace.js')] if self.ace_media else [] js = [urljoin(settings.ACE_URL, "ace.js")] if self.ace_media else []
js.append('django_ace/widget.js') js.append("django_ace/widget.js")
css = { css = {
'screen': ['django_ace/widget.css'], "screen": ["django_ace/widget.css"],
} }
return forms.Media(js=js, css=css) return forms.Media(js=js, css=css)
@ -34,24 +43,28 @@ class AceWidget(forms.Textarea):
attrs = attrs or {} attrs = attrs or {}
ace_attrs = { ace_attrs = {
'class': 'django-ace-widget loading', "class": "django-ace-widget loading",
'style': 'width:%s; height:%s' % (self.width, self.height), "style": "width:%s; height:%s" % (self.width, self.height),
'id': 'ace_%s' % name, "id": "ace_%s" % name,
} }
if self.mode: if self.mode:
ace_attrs['data-mode'] = self.mode ace_attrs["data-mode"] = self.mode
if self.theme: if self.theme:
ace_attrs['data-theme'] = self.theme ace_attrs["data-theme"] = self.theme
if self.wordwrap: if self.wordwrap:
ace_attrs['data-wordwrap'] = 'true' ace_attrs["data-wordwrap"] = "true"
attrs.update(style='width: 100%; min-width: 100%; max-width: 100%; resize: none') attrs.update(
style="width: 100%; min-width: 100%; max-width: 100%; resize: none"
)
textarea = super(AceWidget, self).render(name, value, attrs) textarea = super(AceWidget, self).render(name, value, attrs)
html = '<div%s><div></div></div>%s' % (flatatt(ace_attrs), textarea) html = "<div%s><div></div></div>%s" % (flatatt(ace_attrs), textarea)
# add toolbar # add toolbar
html = ('<div class="django-ace-editor"><div style="width: 100%%" class="django-ace-toolbar">' html = (
'<a href="./" class="django-ace-max_min"></a></div>%s</div>') % html '<div class="django-ace-editor"><div style="width: 100%%" class="django-ace-toolbar">'
'<a href="./" class="django-ace-max_min"></a></div>%s</div>'
) % html
return mark_safe(html) return mark_safe(html)

View file

@ -4,24 +4,30 @@ import socket
from celery import Celery from celery import Celery
from celery.signals import task_failure from celery.signals import task_failure
app = Celery('dmoj') app = Celery("dmoj")
from django.conf import settings # noqa: E402, I202, django must be imported here from django.conf import settings # noqa: E402, I202, django must be imported here
app.config_from_object(settings, namespace='CELERY')
if hasattr(settings, 'CELERY_BROKER_URL_SECRET'): app.config_from_object(settings, namespace="CELERY")
if hasattr(settings, "CELERY_BROKER_URL_SECRET"):
app.conf.broker_url = settings.CELERY_BROKER_URL_SECRET app.conf.broker_url = settings.CELERY_BROKER_URL_SECRET
if hasattr(settings, 'CELERY_RESULT_BACKEND_SECRET'): if hasattr(settings, "CELERY_RESULT_BACKEND_SECRET"):
app.conf.result_backend = settings.CELERY_RESULT_BACKEND_SECRET app.conf.result_backend = settings.CELERY_RESULT_BACKEND_SECRET
# Load task modules from all registered Django app configs. # Load task modules from all registered Django app configs.
app.autodiscover_tasks() app.autodiscover_tasks()
# Logger to enable errors be reported. # Logger to enable errors be reported.
logger = logging.getLogger('judge.celery') logger = logging.getLogger("judge.celery")
@task_failure.connect() @task_failure.connect()
def celery_failure_log(sender, task_id, exception, traceback, *args, **kwargs): def celery_failure_log(sender, task_id, exception, traceback, *args, **kwargs):
logger.error('Celery Task %s: %s on %s', sender.name, task_id, socket.gethostname(), # noqa: G201 logger.error(
exc_info=(type(exception), exception, traceback)) "Celery Task %s: %s on %s",
sender.name,
task_id,
socket.gethostname(), # noqa: G201
exc_info=(type(exception), exception, traceback),
)

View file

@ -1,12 +1,14 @@
import time import time
def timeit(method): def timeit(method):
def timed(*args, **kw): def timed(*args, **kw):
ts = time.time() ts = time.time()
result = method(*args, **kw) result = method(*args, **kw)
te = time.time() te = time.time()
if 'log_time' in kw: if "log_time" in kw:
name = kw.get('log_name', method.__name__.upper()) name = kw.get("log_name", method.__name__.upper())
kw['log_time'][name] = int((te - ts) * 1000) kw["log_time"][name] = int((te - ts) * 1000)
return result return result
return timed return timed

View file

@ -22,7 +22,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '5*9f5q57mqmlz2#f$x1h76&jxy#yortjl1v+l*6hd18$d*yx#0' SECRET_KEY = "5*9f5q57mqmlz2#f$x1h76&jxy#yortjl1v+l*6hd18$d*yx#0"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -30,8 +30,8 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
SITE_ID = 1 SITE_ID = 1
SITE_NAME = 'LQDOJ' SITE_NAME = "LQDOJ"
SITE_LONG_NAME = 'LQDOJ: Le Quy Don Online Judge' SITE_LONG_NAME = "LQDOJ: Le Quy Don Online Judge"
SITE_ADMIN_EMAIL = False SITE_ADMIN_EMAIL = False
DMOJ_REQUIRE_STAFF_2FA = True DMOJ_REQUIRE_STAFF_2FA = True
@ -44,13 +44,15 @@ DMOJ_SSL = 0
# Refer to dmoj.ca/post/103-point-system-rework # Refer to dmoj.ca/post/103-point-system-rework
DMOJ_PP_STEP = 0.95 DMOJ_PP_STEP = 0.95
DMOJ_PP_ENTRIES = 100 DMOJ_PP_ENTRIES = 100
DMOJ_PP_BONUS_FUNCTION = lambda n: 300 * (1 - 0.997 ** n) # noqa: E731 DMOJ_PP_BONUS_FUNCTION = lambda n: 300 * (1 - 0.997**n) # noqa: E731
NODEJS = '/usr/bin/node' NODEJS = "/usr/bin/node"
EXIFTOOL = '/usr/bin/exiftool' EXIFTOOL = "/usr/bin/exiftool"
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3' ACE_URL = "//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3"
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js' SELECT2_JS_URL = "//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"
DEFAULT_SELECT2_CSS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css' DEFAULT_SELECT2_CSS = (
"//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css"
)
DMOJ_CAMO_URL = None DMOJ_CAMO_URL = None
DMOJ_CAMO_KEY = None DMOJ_CAMO_KEY = None
@ -74,14 +76,14 @@ DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT = 7
DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1 DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1
DMOJ_USER_MAX_ORGANIZATION_COUNT = 10 DMOJ_USER_MAX_ORGANIZATION_COUNT = 10
DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5 DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5
DMOJ_PDF_PROBLEM_CACHE = '' DMOJ_PDF_PROBLEM_CACHE = ""
DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir() DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir()
DMOJ_STATS_SUBMISSION_RESULT_COLORS = { DMOJ_STATS_SUBMISSION_RESULT_COLORS = {
'TLE': '#a3bcbd', "TLE": "#a3bcbd",
'AC': '#00a92a', "AC": "#00a92a",
'WA': '#ed4420', "WA": "#ed4420",
'CE': '#42586d', "CE": "#42586d",
'ERR': '#ffa71c', "ERR": "#ffa71c",
} }
MARKDOWN_STYLES = {} MARKDOWN_STYLES = {}
@ -90,14 +92,14 @@ MARKDOWN_DEFAULT_STYLE = {}
MATHOID_URL = False MATHOID_URL = False
MATHOID_GZIP = False MATHOID_GZIP = False
MATHOID_MML_CACHE = None MATHOID_MML_CACHE = None
MATHOID_CSS_CACHE = 'default' MATHOID_CSS_CACHE = "default"
MATHOID_DEFAULT_TYPE = 'auto' MATHOID_DEFAULT_TYPE = "auto"
MATHOID_MML_CACHE_TTL = 86400 MATHOID_MML_CACHE_TTL = 86400
MATHOID_CACHE_ROOT = tempfile.gettempdir() + '/mathoidCache' MATHOID_CACHE_ROOT = tempfile.gettempdir() + "/mathoidCache"
MATHOID_CACHE_URL = False MATHOID_CACHE_URL = False
TEXOID_GZIP = False TEXOID_GZIP = False
TEXOID_META_CACHE = 'default' TEXOID_META_CACHE = "default"
TEXOID_META_CACHE_TTL = 86400 TEXOID_META_CACHE_TTL = 86400
DMOJ_NEWSLETTER_ID_ON_REGISTER = 1 DMOJ_NEWSLETTER_ID_ON_REGISTER = 1
@ -110,31 +112,33 @@ TIMEZONE_MAP = None
TIMEZONE_DETECT_BACKEND = None TIMEZONE_DETECT_BACKEND = None
TERMS_OF_SERVICE_URL = None TERMS_OF_SERVICE_URL = None
DEFAULT_USER_LANGUAGE = 'PY3' DEFAULT_USER_LANGUAGE = "PY3"
PHANTOMJS = '' PHANTOMJS = ""
PHANTOMJS_PDF_ZOOM = 0.75 PHANTOMJS_PDF_ZOOM = 0.75
PHANTOMJS_PDF_TIMEOUT = 5.0 PHANTOMJS_PDF_TIMEOUT = 5.0
PHANTOMJS_PAPER_SIZE = 'Letter' PHANTOMJS_PAPER_SIZE = "Letter"
SLIMERJS = '' SLIMERJS = ""
SLIMERJS_PDF_ZOOM = 0.75 SLIMERJS_PDF_ZOOM = 0.75
SLIMERJS_FIREFOX_PATH = '' SLIMERJS_FIREFOX_PATH = ""
SLIMERJS_PAPER_SIZE = 'Letter' SLIMERJS_PAPER_SIZE = "Letter"
PUPPETEER_MODULE = '/usr/lib/node_modules/puppeteer' PUPPETEER_MODULE = "/usr/lib/node_modules/puppeteer"
PUPPETEER_PAPER_SIZE = 'Letter' PUPPETEER_PAPER_SIZE = "Letter"
USE_SELENIUM = False USE_SELENIUM = False
SELENIUM_CUSTOM_CHROME_PATH = None SELENIUM_CUSTOM_CHROME_PATH = None
SELENIUM_CHROMEDRIVER_PATH = 'chromedriver' SELENIUM_CHROMEDRIVER_PATH = "chromedriver"
PYGMENT_THEME = 'pygment-github.css' PYGMENT_THEME = "pygment-github.css"
INLINE_JQUERY = True INLINE_JQUERY = True
INLINE_FONTAWESOME = True INLINE_FONTAWESOME = True
JQUERY_JS = '//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js' JQUERY_JS = "//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"
FONTAWESOME_CSS = '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css' FONTAWESOME_CSS = (
DMOJ_CANONICAL = '' "//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
)
DMOJ_CANONICAL = ""
# Application definition # Application definition
@ -146,130 +150,130 @@ except ImportError:
pass pass
else: else:
del wpadmin del wpadmin
INSTALLED_APPS += ('wpadmin',) INSTALLED_APPS += ("wpadmin",)
WPADMIN = { WPADMIN = {
'admin': { "admin": {
'title': 'LQDOJ Admin', "title": "LQDOJ Admin",
'menu': { "menu": {
'top': 'wpadmin.menu.menus.BasicTopMenu', "top": "wpadmin.menu.menus.BasicTopMenu",
'left': 'wpadmin.menu.custom.CustomModelLeftMenuWithDashboard', "left": "wpadmin.menu.custom.CustomModelLeftMenuWithDashboard",
}, },
'custom_menu': [ "custom_menu": [
{ {
'model': 'judge.Problem', "model": "judge.Problem",
'icon': 'fa-question-circle', "icon": "fa-question-circle",
'children': [ "children": [
'judge.ProblemGroup', "judge.ProblemGroup",
'judge.ProblemType', "judge.ProblemType",
'judge.ProblemPointsVote', "judge.ProblemPointsVote",
], ],
}, },
{ {
'model': 'judge.Submission', "model": "judge.Submission",
'icon': 'fa-check-square-o', "icon": "fa-check-square-o",
'children': [ "children": [
'judge.Language', "judge.Language",
'judge.Judge', "judge.Judge",
], ],
}, },
{ {
'model': 'judge.Contest', "model": "judge.Contest",
'icon': 'fa-bar-chart', "icon": "fa-bar-chart",
'children': [ "children": [
'judge.ContestParticipation', "judge.ContestParticipation",
'judge.ContestTag', "judge.ContestTag",
], ],
}, },
{ {
'model': 'auth.User', "model": "auth.User",
'icon': 'fa-user', "icon": "fa-user",
'children': [ "children": [
'auth.Group', "auth.Group",
'registration.RegistrationProfile', "registration.RegistrationProfile",
], ],
}, },
{ {
'model': 'judge.Profile', "model": "judge.Profile",
'icon': 'fa-user-plus', "icon": "fa-user-plus",
'children': [ "children": [
'judge.Organization', "judge.Organization",
'judge.OrganizationRequest', "judge.OrganizationRequest",
], ],
}, },
{ {
'model': 'judge.NavigationBar', "model": "judge.NavigationBar",
'icon': 'fa-bars', "icon": "fa-bars",
'children': [ "children": [
'judge.MiscConfig', "judge.MiscConfig",
'judge.License', "judge.License",
'sites.Site', "sites.Site",
'redirects.Redirect', "redirects.Redirect",
], ],
}, },
('judge.BlogPost', 'fa-rss-square'), ("judge.BlogPost", "fa-rss-square"),
('judge.Comment', 'fa-comment-o'), ("judge.Comment", "fa-comment-o"),
('judge.Ticket', 'fa-exclamation-circle'), ("judge.Ticket", "fa-exclamation-circle"),
('flatpages.FlatPage', 'fa-file-text-o'), ("flatpages.FlatPage", "fa-file-text-o"),
('judge.Solution', 'fa-pencil'), ("judge.Solution", "fa-pencil"),
], ],
'dashboard': { "dashboard": {
'breadcrumbs': True, "breadcrumbs": True,
}, },
}, },
} }
INSTALLED_APPS += ( INSTALLED_APPS += (
'django.contrib.admin', "django.contrib.admin",
'judge', "judge",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.flatpages', "django.contrib.flatpages",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.redirects', "django.contrib.redirects",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django.contrib.sites', "django.contrib.sites",
'django.contrib.sitemaps', "django.contrib.sitemaps",
'registration', "registration",
'mptt', "mptt",
'reversion', "reversion",
'reversion_compare', "reversion_compare",
'django_social_share', "django_social_share",
'social_django', "social_django",
'compressor', "compressor",
'django_ace', "django_ace",
'pagedown', "pagedown",
'sortedm2m', "sortedm2m",
'statici18n', "statici18n",
'impersonate', "impersonate",
'django_jinja', "django_jinja",
'chat_box', "chat_box",
'newsletter', "newsletter",
'django.forms', "django.forms",
) )
MIDDLEWARE = ( MIDDLEWARE = (
'judge.middleware.ShortCircuitMiddleware', "judge.middleware.ShortCircuitMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.locale.LocaleMiddleware', "django.middleware.locale.LocaleMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'judge.middleware.DMOJLoginMiddleware', "judge.middleware.DMOJLoginMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
'judge.user_log.LogUserAccessMiddleware', "judge.user_log.LogUserAccessMiddleware",
'judge.timezone.TimezoneMiddleware', "judge.timezone.TimezoneMiddleware",
'impersonate.middleware.ImpersonateMiddleware', "impersonate.middleware.ImpersonateMiddleware",
'judge.middleware.DMOJImpersonationMiddleware', "judge.middleware.DMOJImpersonationMiddleware",
'judge.middleware.ContestMiddleware', "judge.middleware.ContestMiddleware",
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
'judge.social_auth.SocialAuthExceptionMiddleware', "judge.social_auth.SocialAuthExceptionMiddleware",
'django.contrib.redirects.middleware.RedirectFallbackMiddleware', "django.contrib.redirects.middleware.RedirectFallbackMiddleware",
) )
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
IMPERSONATE_REQUIRE_SUPERUSER = True IMPERSONATE_REQUIRE_SUPERUSER = True
IMPERSONATE_DISABLE_LOGGING = True IMPERSONATE_DISABLE_LOGGING = True
@ -278,226 +282,229 @@ ACCOUNT_ACTIVATION_DAYS = 7
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'judge.utils.pwned.PwnedPasswordsValidator', "NAME": "judge.utils.pwned.PwnedPasswordsValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
SILENCED_SYSTEM_CHECKS = ['urls.W002', 'fields.W342'] SILENCED_SYSTEM_CHECKS = ["urls.W002", "fields.W342"]
ROOT_URLCONF = 'dmoj.urls' ROOT_URLCONF = "dmoj.urls"
LOGIN_REDIRECT_URL = '/user' LOGIN_REDIRECT_URL = "/user"
WSGI_APPLICATION = 'dmoj.wsgi.application' WSGI_APPLICATION = "dmoj.wsgi.application"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django_jinja.backend.Jinja2', "BACKEND": "django_jinja.backend.Jinja2",
'DIRS': [ "DIRS": [
os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, "templates"),
], ],
'APP_DIRS': False, "APP_DIRS": False,
'OPTIONS': { "OPTIONS": {
'match_extension': ('.html', '.txt'), "match_extension": (".html", ".txt"),
'match_regex': '^(?!admin/)', "match_regex": "^(?!admin/)",
'context_processors': [ "context_processors": [
'django.template.context_processors.media', "django.template.context_processors.media",
'django.template.context_processors.tz', "django.template.context_processors.tz",
'django.template.context_processors.i18n', "django.template.context_processors.i18n",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
'judge.template_context.comet_location', "judge.template_context.comet_location",
'judge.template_context.get_resource', "judge.template_context.get_resource",
'judge.template_context.general_info', "judge.template_context.general_info",
'judge.template_context.site', "judge.template_context.site",
'judge.template_context.site_name', "judge.template_context.site_name",
'judge.template_context.misc_config', "judge.template_context.misc_config",
'judge.template_context.math_setting', "judge.template_context.math_setting",
'social_django.context_processors.backends', "social_django.context_processors.backends",
'social_django.context_processors.login_redirect', "social_django.context_processors.login_redirect",
], ],
'autoescape': select_autoescape(['html', 'xml']), "autoescape": select_autoescape(["html", "xml"]),
'trim_blocks': True, "trim_blocks": True,
'lstrip_blocks': True, "lstrip_blocks": True,
'extensions': DEFAULT_EXTENSIONS + [ "extensions": DEFAULT_EXTENSIONS
'compressor.contrib.jinja2ext.CompressorExtension', + [
'judge.jinja2.DMOJExtension', "compressor.contrib.jinja2ext.CompressorExtension",
'judge.jinja2.spaceless.SpacelessExtension', "judge.jinja2.DMOJExtension",
"judge.jinja2.spaceless.SpacelessExtension",
], ],
}, },
}, },
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'APP_DIRS': True, "APP_DIRS": True,
'DIRS': [ "DIRS": [
os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, "templates"),
], ],
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.template.context_processors.media', "django.template.context_processors.media",
'django.template.context_processors.tz', "django.template.context_processors.tz",
'django.template.context_processors.i18n', "django.template.context_processors.i18n",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
LOCALE_PATHS = [ LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'), os.path.join(BASE_DIR, "locale"),
] ]
LANGUAGES = [ LANGUAGES = [
('de', _('German')), ("de", _("German")),
('en', _('English')), ("en", _("English")),
('es', _('Spanish')), ("es", _("Spanish")),
('fr', _('French')), ("fr", _("French")),
('hr', _('Croatian')), ("hr", _("Croatian")),
('hu', _('Hungarian')), ("hu", _("Hungarian")),
('ja', _('Japanese')), ("ja", _("Japanese")),
('ko', _('Korean')), ("ko", _("Korean")),
('pt', _('Brazilian Portuguese')), ("pt", _("Brazilian Portuguese")),
('ro', _('Romanian')), ("ro", _("Romanian")),
('ru', _('Russian')), ("ru", _("Russian")),
('sr-latn', _('Serbian (Latin)')), ("sr-latn", _("Serbian (Latin)")),
('tr', _('Turkish')), ("tr", _("Turkish")),
('vi', _('Vietnamese')), ("vi", _("Vietnamese")),
('zh-hans', _('Simplified Chinese')), ("zh-hans", _("Simplified Chinese")),
('zh-hant', _('Traditional Chinese')), ("zh-hant", _("Traditional Chinese")),
] ]
MARKDOWN_ADMIN_EDITABLE_STYLE = { MARKDOWN_ADMIN_EDITABLE_STYLE = {
'safe_mode': False, "safe_mode": False,
'use_camo': True, "use_camo": True,
'texoid': True, "texoid": True,
'math': True, "math": True,
} }
MARKDOWN_DEFAULT_STYLE = { MARKDOWN_DEFAULT_STYLE = {
'safe_mode': True, "safe_mode": True,
'nofollow': True, "nofollow": True,
'use_camo': True, "use_camo": True,
'math': True, "math": True,
} }
MARKDOWN_USER_LARGE_STYLE = { MARKDOWN_USER_LARGE_STYLE = {
'safe_mode': True, "safe_mode": True,
'nofollow': True, "nofollow": True,
'use_camo': True, "use_camo": True,
'math': True, "math": True,
} }
MARKDOWN_STYLES = { MARKDOWN_STYLES = {
'comment': MARKDOWN_DEFAULT_STYLE, "comment": MARKDOWN_DEFAULT_STYLE,
'self-description': MARKDOWN_USER_LARGE_STYLE, "self-description": MARKDOWN_USER_LARGE_STYLE,
'problem': MARKDOWN_ADMIN_EDITABLE_STYLE, "problem": MARKDOWN_ADMIN_EDITABLE_STYLE,
'contest': MARKDOWN_ADMIN_EDITABLE_STYLE, "contest": MARKDOWN_ADMIN_EDITABLE_STYLE,
'language': MARKDOWN_ADMIN_EDITABLE_STYLE, "language": MARKDOWN_ADMIN_EDITABLE_STYLE,
'license': MARKDOWN_ADMIN_EDITABLE_STYLE, "license": MARKDOWN_ADMIN_EDITABLE_STYLE,
'judge': MARKDOWN_ADMIN_EDITABLE_STYLE, "judge": MARKDOWN_ADMIN_EDITABLE_STYLE,
'blog': MARKDOWN_ADMIN_EDITABLE_STYLE, "blog": MARKDOWN_ADMIN_EDITABLE_STYLE,
'solution': MARKDOWN_ADMIN_EDITABLE_STYLE, "solution": MARKDOWN_ADMIN_EDITABLE_STYLE,
'contest_tag': MARKDOWN_ADMIN_EDITABLE_STYLE, "contest_tag": MARKDOWN_ADMIN_EDITABLE_STYLE,
'organization-about': MARKDOWN_USER_LARGE_STYLE, "organization-about": MARKDOWN_USER_LARGE_STYLE,
'ticket': MARKDOWN_USER_LARGE_STYLE, "ticket": MARKDOWN_USER_LARGE_STYLE,
} }
# Database # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}, },
} }
ENABLE_FTS = False ENABLE_FTS = False
# Bridged configuration # Bridged configuration
BRIDGED_JUDGE_ADDRESS = [('localhost', 9999)] BRIDGED_JUDGE_ADDRESS = [("localhost", 9999)]
BRIDGED_JUDGE_PROXIES = None BRIDGED_JUDGE_PROXIES = None
BRIDGED_DJANGO_ADDRESS = [('localhost', 9998)] BRIDGED_DJANGO_ADDRESS = [("localhost", 9998)]
BRIDGED_DJANGO_CONNECT = None BRIDGED_DJANGO_CONNECT = None
# Event Server configuration # Event Server configuration
EVENT_DAEMON_USE = False EVENT_DAEMON_USE = False
EVENT_DAEMON_POST = 'ws://localhost:9997/' EVENT_DAEMON_POST = "ws://localhost:9997/"
EVENT_DAEMON_GET = 'ws://localhost:9996/' EVENT_DAEMON_GET = "ws://localhost:9996/"
EVENT_DAEMON_POLL = '/channels/' EVENT_DAEMON_POLL = "/channels/"
EVENT_DAEMON_KEY = None EVENT_DAEMON_KEY = None
EVENT_DAEMON_AMQP_EXCHANGE = 'dmoj-events' EVENT_DAEMON_AMQP_EXCHANGE = "dmoj-events"
EVENT_DAEMON_SUBMISSION_KEY = '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww' EVENT_DAEMON_SUBMISSION_KEY = (
"6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww"
)
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # https://docs.djangoproject.com/en/1.11/topics/i18n/
# Whatever you do, this better be one of the entries in `LANGUAGES`. # Whatever you do, this better be one of the entries in `LANGUAGES`.
LANGUAGE_CODE = 'vi' LANGUAGE_CODE = "vi"
TIME_ZONE = 'Asia/Ho_Chi_Minh' TIME_ZONE = "Asia/Ho_Chi_Minh"
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh' DEFAULT_USER_TIME_ZONE = "Asia/Ho_Chi_Minh"
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True
USE_TZ = True USE_TZ = True
# Cookies # Cookies
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/ # https://docs.djangoproject.com/en/1.11/howto/static-files/
DMOJ_RESOURCES = os.path.join(BASE_DIR, 'resources') DMOJ_RESOURCES = os.path.join(BASE_DIR, "resources")
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
) )
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'resources'), os.path.join(BASE_DIR, "resources"),
] ]
STATIC_URL = '/static/' STATIC_URL = "/static/"
# Define a cache # Define a cache
CACHES = {} CACHES = {}
# Authentication # Authentication
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2', "social_core.backends.google.GoogleOAuth2",
'social_core.backends.facebook.FacebookOAuth2', "social_core.backends.facebook.FacebookOAuth2",
'judge.social_auth.GitHubSecureEmailOAuth2', "judge.social_auth.GitHubSecureEmailOAuth2",
'django.contrib.auth.backends.ModelBackend', "django.contrib.auth.backends.ModelBackend",
) )
SOCIAL_AUTH_PIPELINE = ( SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details', "social_core.pipeline.social_auth.social_details",
'social_core.pipeline.social_auth.social_uid', "social_core.pipeline.social_auth.social_uid",
'social_core.pipeline.social_auth.auth_allowed', "social_core.pipeline.social_auth.auth_allowed",
'judge.social_auth.verify_email', "judge.social_auth.verify_email",
'social_core.pipeline.social_auth.social_user', "social_core.pipeline.social_auth.social_user",
'social_core.pipeline.user.get_username', "social_core.pipeline.user.get_username",
'social_core.pipeline.social_auth.associate_by_email', "social_core.pipeline.social_auth.associate_by_email",
'judge.social_auth.choose_username', "judge.social_auth.choose_username",
'social_core.pipeline.user.create_user', "social_core.pipeline.user.create_user",
'judge.social_auth.make_profile', "judge.social_auth.make_profile",
'social_core.pipeline.social_auth.associate_user', "social_core.pipeline.social_auth.associate_user",
'social_core.pipeline.social_auth.load_extra_data', "social_core.pipeline.social_auth.load_extra_data",
'social_core.pipeline.user.user_details', "social_core.pipeline.user.user_details",
) )
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['first_name', 'last_name'] SOCIAL_AUTH_PROTECTED_USER_FIELDS = ["first_name", "last_name"]
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['email', 'username'] SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ["email", "username"]
SOCIAL_AUTH_GITHUB_SECURE_SCOPE = ['user:email'] SOCIAL_AUTH_GITHUB_SECURE_SCOPE = ["user:email"]
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email'] SOCIAL_AUTH_FACEBOOK_SCOPE = ["email"]
SOCIAL_AUTH_SLUGIFY_USERNAMES = True SOCIAL_AUTH_SLUGIFY_USERNAMES = True
SOCIAL_AUTH_SLUGIFY_FUNCTION = 'judge.social_auth.slugify_username' SOCIAL_AUTH_SLUGIFY_FUNCTION = "judge.social_auth.slugify_username"
JUDGE_AMQP_PATH = None JUDGE_AMQP_PATH = None
@ -529,7 +536,7 @@ NEWSLETTER_BATCH_SIZE = 100
REGISTER_NAME_URL = None REGISTER_NAME_URL = None
try: try:
with open(os.path.join(os.path.dirname(__file__), 'local_settings.py')) as f: with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f:
exec(f.read(), globals()) exec(f.read(), globals())
except IOError: except IOError:
pass pass

View file

@ -8,8 +8,8 @@ DEFAULT_THROTTLE = (10, 60)
def new_email(): def new_email():
cache.add('error_email_throttle', 0, settings.DMOJ_EMAIL_THROTTLING[1]) cache.add("error_email_throttle", 0, settings.DMOJ_EMAIL_THROTTLING[1])
return cache.incr('error_email_throttle') return cache.incr("error_email_throttle")
class ThrottledEmailHandler(AdminEmailHandler): class ThrottledEmailHandler(AdminEmailHandler):

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
import os import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
try: try:
import MySQLdb # noqa: F401, imported for side effect import MySQLdb # noqa: F401, imported for side effect
@ -8,5 +9,8 @@ except ImportError:
pymysql.install_as_MySQLdb() pymysql.install_as_MySQLdb()
from django.core.wsgi import get_wsgi_application # noqa: E402, django must be imported here from django.core.wsgi import (
get_wsgi_application,
) # noqa: E402, django must be imported here
application = get_wsgi_application() application = get_wsgi_application()

View file

@ -2,13 +2,17 @@ import os
import gevent.monkey # noqa: I100, gevent must be imported here import gevent.monkey # noqa: I100, gevent must be imported here
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
gevent.monkey.patch_all() gevent.monkey.patch_all()
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import dmoj_install_pymysql # noqa: F401, I100, I202, imported for side effect import dmoj_install_pymysql # noqa: F401, I100, I202, imported for side effect
from django.core.wsgi import get_wsgi_application # noqa: E402, I100, I202, django must be imported here from django.core.wsgi import (
get_wsgi_application,
) # noqa: E402, I100, I202, django must be imported here
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import django_2_2_pymysql_patch # noqa: I100, F401, I202, imported for side effect import django_2_2_pymysql_patch # noqa: I100, F401, I202, imported for side effect
application = get_wsgi_application() application = get_wsgi_application()

View file

@ -2,19 +2,22 @@ import os
import gevent.monkey # noqa: I100, gevent must be imported here import gevent.monkey # noqa: I100, gevent must be imported here
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
gevent.monkey.patch_all() gevent.monkey.patch_all()
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import dmoj_install_pymysql # noqa: E402, F401, I100, I202, imported for side effect import dmoj_install_pymysql # noqa: E402, F401, I100, I202, imported for side effect
import django # noqa: E402, F401, I100, I202, django must be imported here import django # noqa: E402, F401, I100, I202, django must be imported here
django.setup() django.setup()
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import django_2_2_pymysql_patch # noqa: E402, I100, F401, I202, imported for side effect import django_2_2_pymysql_patch # noqa: E402, I100, F401, I202, imported for side effect
from judge.bridge.daemon import judge_daemon # noqa: E402, I100, I202, django code must be imported here from judge.bridge.daemon import (
judge_daemon,
) # noqa: E402, I100, I202, django code must be imported here
if __name__ == '__main__': if __name__ == "__main__":
judge_daemon() judge_daemon()

View file

@ -6,7 +6,7 @@ except ImportError:
import dmoj_install_pymysql # noqa: F401, imported for side effect import dmoj_install_pymysql # noqa: F401, imported for side effect
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import django_2_2_pymysql_patch # noqa: I100, F401, I202, imported for side effect import django_2_2_pymysql_patch # noqa: I100, F401, I202, imported for side effect

View file

@ -1 +1 @@
default_app_config = 'judge.apps.JudgeAppConfig' default_app_config = "judge.apps.JudgeAppConfig"

View file

@ -3,7 +3,12 @@ from django.contrib.admin.models import LogEntry
from judge.admin.comments import CommentAdmin from judge.admin.comments import CommentAdmin
from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin
from judge.admin.interface import BlogPostAdmin, LicenseAdmin, LogEntryAdmin, NavigationBarAdmin from judge.admin.interface import (
BlogPostAdmin,
LicenseAdmin,
LogEntryAdmin,
NavigationBarAdmin,
)
from judge.admin.organization import OrganizationAdmin, OrganizationRequestAdmin from judge.admin.organization import OrganizationAdmin, OrganizationRequestAdmin
from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin
from judge.admin.profile import ProfileAdmin from judge.admin.profile import ProfileAdmin
@ -12,10 +17,29 @@ from judge.admin.submission import SubmissionAdmin
from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
from judge.admin.ticket import TicketAdmin from judge.admin.ticket import TicketAdmin
from judge.admin.volunteer import VolunteerProblemVoteAdmin from judge.admin.volunteer import VolunteerProblemVoteAdmin
from judge.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \ from judge.models import (
ContestTag, Judge, Language, License, MiscConfig, NavigationBar, Organization, \ BlogPost,
OrganizationRequest, Problem, ProblemGroup, ProblemPointsVote, ProblemType, Profile, Submission, Ticket, \ Comment,
VolunteerProblemVote CommentLock,
Contest,
ContestParticipation,
ContestTag,
Judge,
Language,
License,
MiscConfig,
NavigationBar,
Organization,
OrganizationRequest,
Problem,
ProblemGroup,
ProblemPointsVote,
ProblemType,
Profile,
Submission,
Ticket,
VolunteerProblemVote,
)
admin.site.register(BlogPost, BlogPostAdmin) admin.site.register(BlogPost, BlogPostAdmin)

View file

@ -11,53 +11,70 @@ from judge.widgets import AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidg
class CommentForm(ModelForm): class CommentForm(ModelForm):
class Meta: class Meta:
widgets = { widgets = {
'author': AdminHeavySelect2Widget(data_view='profile_select2'), "author": AdminHeavySelect2Widget(data_view="profile_select2"),
'parent': AdminHeavySelect2Widget(data_view='comment_select2'), "parent": AdminHeavySelect2Widget(data_view="comment_select2"),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('comment_preview')) widgets["body"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("comment_preview")
)
class CommentAdmin(VersionAdmin): class CommentAdmin(VersionAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('author', 'page', 'parent', 'score', 'hidden')}), (None, {"fields": ("author", "page", "parent", "score", "hidden")}),
('Content', {'fields': ('body',)}), ("Content", {"fields": ("body",)}),
) )
list_display = ['author', 'linked_page', 'time'] list_display = ["author", "linked_page", "time"]
search_fields = ['author__user__username', 'page', 'body'] search_fields = ["author__user__username", "page", "body"]
readonly_fields = ['score'] readonly_fields = ["score"]
actions = ['hide_comment', 'unhide_comment'] actions = ["hide_comment", "unhide_comment"]
list_filter = ['hidden'] list_filter = ["hidden"]
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = CommentForm form = CommentForm
date_hierarchy = 'time' date_hierarchy = "time"
def get_queryset(self, request): def get_queryset(self, request):
return Comment.objects.order_by('-time') return Comment.objects.order_by("-time")
def hide_comment(self, request, queryset): def hide_comment(self, request, queryset):
count = queryset.update(hidden=True) count = queryset.update(hidden=True)
self.message_user(request, ungettext('%d comment successfully hidden.', self.message_user(
'%d comments successfully hidden.', request,
count) % count) ungettext(
hide_comment.short_description = _('Hide comments') "%d comment successfully hidden.",
"%d comments successfully hidden.",
count,
)
% count,
)
hide_comment.short_description = _("Hide comments")
def unhide_comment(self, request, queryset): def unhide_comment(self, request, queryset):
count = queryset.update(hidden=False) count = queryset.update(hidden=False)
self.message_user(request, ungettext('%d comment successfully unhidden.', self.message_user(
'%d comments successfully unhidden.', request,
count) % count) ungettext(
unhide_comment.short_description = _('Unhide comments') "%d comment successfully unhidden.",
"%d comments successfully unhidden.",
count,
)
% count,
)
unhide_comment.short_description = _("Unhide comments")
def linked_page(self, obj): def linked_page(self, obj):
link = obj.link link = obj.link
if link is not None: if link is not None:
return format_html('<a href="{0}">{1}</a>', link, obj.page) return format_html('<a href="{0}">{1}</a>', link, obj.page)
else: else:
return format_html('{0}', obj.page) return format_html("{0}", obj.page)
linked_page.short_description = _('Associated page')
linked_page.admin_order_field = 'page' linked_page.short_description = _("Associated page")
linked_page.admin_order_field = "page"
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(CommentAdmin, self).save_model(request, obj, form, change) super(CommentAdmin, self).save_model(request, obj, form, change)

View file

@ -16,8 +16,14 @@ from reversion_compare.admin import CompareVersionAdmin
from django_ace import AceWidget from django_ace import AceWidget
from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating
from judge.ratings import rate_contest from judge.ratings import rate_contest
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminPagedownWidget, \ from judge.widgets import (
AdminSelect2MultipleWidget, AdminSelect2Widget, HeavyPreviewAdminPageDownWidget AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
AdminPagedownWidget,
AdminSelect2MultipleWidget,
AdminSelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class AdminHeavySelect2Widget(AdminHeavySelect2Widget): class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
@ -28,159 +34,252 @@ class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
class ContestTagForm(ModelForm): class ContestTagForm(ModelForm):
contests = ModelMultipleChoiceField( contests = ModelMultipleChoiceField(
label=_('Included contests'), label=_("Included contests"),
queryset=Contest.objects.all(), queryset=Contest.objects.all(),
required=False, required=False,
widget=AdminHeavySelect2MultipleWidget(data_view='contest_select2')) widget=AdminHeavySelect2MultipleWidget(data_view="contest_select2"),
)
class ContestTagAdmin(admin.ModelAdmin): class ContestTagAdmin(admin.ModelAdmin):
fields = ('name', 'color', 'description', 'contests') fields = ("name", "color", "description", "contests")
list_display = ('name', 'color') list_display = ("name", "color")
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = ContestTagForm form = ContestTagForm
if AdminPagedownWidget is not None: if AdminPagedownWidget is not None:
formfield_overrides = { formfield_overrides = {
TextField: {'widget': AdminPagedownWidget}, TextField: {"widget": AdminPagedownWidget},
} }
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(ContestTagAdmin, self).save_model(request, obj, form, change) super(ContestTagAdmin, self).save_model(request, obj, form, change)
obj.contests.set(form.cleaned_data['contests']) obj.contests.set(form.cleaned_data["contests"])
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
form = super(ContestTagAdmin, self).get_form(request, obj, **kwargs) form = super(ContestTagAdmin, self).get_form(request, obj, **kwargs)
if obj is not None: if obj is not None:
form.base_fields['contests'].initial = obj.contests.all() form.base_fields["contests"].initial = obj.contests.all()
return form return form
class ContestProblemInlineForm(ModelForm): class ContestProblemInlineForm(ModelForm):
class Meta: class Meta:
widgets = {'problem': AdminHeavySelect2Widget(data_view='problem_select2')} widgets = {"problem": AdminHeavySelect2Widget(data_view="problem_select2")}
class ContestProblemInline(admin.TabularInline): class ContestProblemInline(admin.TabularInline):
model = ContestProblem model = ContestProblem
verbose_name = _('Problem') verbose_name = _("Problem")
verbose_name_plural = 'Problems' verbose_name_plural = "Problems"
fields = ('problem', 'points', 'partial', 'is_pretested', 'max_submissions', 'output_prefix_override', 'order', fields = (
'rejudge_column') "problem",
readonly_fields = ('rejudge_column',) "points",
"partial",
"is_pretested",
"max_submissions",
"output_prefix_override",
"order",
"rejudge_column",
)
readonly_fields = ("rejudge_column",)
form = ContestProblemInlineForm form = ContestProblemInlineForm
def rejudge_column(self, obj): def rejudge_column(self, obj):
if obj.id is None: if obj.id is None:
return '' return ""
return format_html('<a class="button rejudge-link" href="{}">Rejudge</a>', return format_html(
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id))) '<a class="button rejudge-link" href="{}">Rejudge</a>',
rejudge_column.short_description = '' reverse("admin:judge_contest_rejudge", args=(obj.contest.id, obj.id)),
)
rejudge_column.short_description = ""
class ContestForm(ModelForm): class ContestForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ContestForm, self).__init__(*args, **kwargs) super(ContestForm, self).__init__(*args, **kwargs)
if 'rate_exclude' in self.fields: if "rate_exclude" in self.fields:
if self.instance and self.instance.id: if self.instance and self.instance.id:
self.fields['rate_exclude'].queryset = \ self.fields["rate_exclude"].queryset = Profile.objects.filter(
Profile.objects.filter(contest_history__contest=self.instance).distinct() contest_history__contest=self.instance
).distinct()
else: else:
self.fields['rate_exclude'].queryset = Profile.objects.none() self.fields["rate_exclude"].queryset = Profile.objects.none()
self.fields['banned_users'].widget.can_add_related = False self.fields["banned_users"].widget.can_add_related = False
self.fields['view_contest_scoreboard'].widget.can_add_related = False self.fields["view_contest_scoreboard"].widget.can_add_related = False
def clean(self): def clean(self):
cleaned_data = super(ContestForm, self).clean() cleaned_data = super(ContestForm, self).clean()
cleaned_data['banned_users'].filter(current_contest__contest=self.instance).update(current_contest=None) cleaned_data["banned_users"].filter(
current_contest__contest=self.instance
).update(current_contest=None)
class Meta: class Meta:
widgets = { widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2'), "authors": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2'), "curators": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2'), "testers": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
'private_contestants': AdminHeavySelect2MultipleWidget(data_view='profile_select2', "private_contestants": AdminHeavySelect2MultipleWidget(
attrs={'style': 'width: 100%'}), data_view="profile_select2", attrs={"style": "width: 100%"}
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2'), ),
'tags': AdminSelect2MultipleWidget, "organizations": AdminHeavySelect2MultipleWidget(
'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2', data_view="organization_select2"
attrs={'style': 'width: 100%'}), ),
'view_contest_scoreboard': AdminHeavySelect2MultipleWidget(data_view='profile_select2', "tags": AdminSelect2MultipleWidget,
attrs={'style': 'width: 100%'}), "banned_users": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"view_contest_scoreboard": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('contest_preview')) widgets["description"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("contest_preview")
)
class ContestAdmin(CompareVersionAdmin): class ContestAdmin(CompareVersionAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('key', 'name', 'authors', 'curators', 'testers')}), (None, {"fields": ("key", "name", "authors", "curators", "testers")}),
(_('Settings'), {'fields': ('is_visible', 'use_clarifications', 'hide_problem_tags', 'scoreboard_visibility', (
'run_pretests_only', 'points_precision')}), _("Settings"),
(_('Scheduling'), {'fields': ('start_time', 'end_time', 'time_limit')}), {
(_('Details'), {'fields': ('description', 'og_image', 'logo_override_image', 'tags', 'summary')}), "fields": (
(_('Format'), {'fields': ('format_name', 'format_config', 'problem_label_script')}), "is_visible",
(_('Rating'), {'fields': ('is_rated', 'rate_all', 'rating_floor', 'rating_ceiling', 'rate_exclude')}), "use_clarifications",
(_('Access'), {'fields': ('access_code', 'is_private', 'private_contestants', 'is_organization_private', "hide_problem_tags",
'organizations', 'view_contest_scoreboard')}), "scoreboard_visibility",
(_('Justice'), {'fields': ('banned_users',)}), "run_pretests_only",
"points_precision",
)
},
),
(_("Scheduling"), {"fields": ("start_time", "end_time", "time_limit")}),
(
_("Details"),
{
"fields": (
"description",
"og_image",
"logo_override_image",
"tags",
"summary",
)
},
),
(
_("Format"),
{"fields": ("format_name", "format_config", "problem_label_script")},
),
(
_("Rating"),
{
"fields": (
"is_rated",
"rate_all",
"rating_floor",
"rating_ceiling",
"rate_exclude",
)
},
),
(
_("Access"),
{
"fields": (
"access_code",
"is_private",
"private_contestants",
"is_organization_private",
"organizations",
"view_contest_scoreboard",
)
},
),
(_("Justice"), {"fields": ("banned_users",)}),
) )
list_display = ('key', 'name', 'is_visible', 'is_rated', 'start_time', 'end_time', 'time_limit', 'user_count') list_display = (
search_fields = ('key', 'name') "key",
"name",
"is_visible",
"is_rated",
"start_time",
"end_time",
"time_limit",
"user_count",
)
search_fields = ("key", "name")
inlines = [ContestProblemInline] inlines = [ContestProblemInline]
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = ContestForm form = ContestForm
change_list_template = 'admin/judge/contest/change_list.html' change_list_template = "admin/judge/contest/change_list.html"
filter_horizontal = ['rate_exclude'] filter_horizontal = ["rate_exclude"]
date_hierarchy = 'start_time' date_hierarchy = "start_time"
def get_actions(self, request): def get_actions(self, request):
actions = super(ContestAdmin, self).get_actions(request) actions = super(ContestAdmin, self).get_actions(request)
if request.user.has_perm('judge.change_contest_visibility') or \ if request.user.has_perm(
request.user.has_perm('judge.create_private_contest'): "judge.change_contest_visibility"
for action in ('make_visible', 'make_hidden'): ) or request.user.has_perm("judge.create_private_contest"):
for action in ("make_visible", "make_hidden"):
actions[action] = self.get_action(action) actions[action] = self.get_action(action)
return actions return actions
def get_queryset(self, request): def get_queryset(self, request):
queryset = Contest.objects.all() queryset = Contest.objects.all()
if request.user.has_perm('judge.edit_all_contest'): if request.user.has_perm("judge.edit_all_contest"):
return queryset return queryset
else: else:
return queryset.filter(Q(authors=request.profile) | Q(curators=request.profile)).distinct() return queryset.filter(
Q(authors=request.profile) | Q(curators=request.profile)
).distinct()
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
readonly = [] readonly = []
if not request.user.has_perm('judge.contest_rating'): if not request.user.has_perm("judge.contest_rating"):
readonly += ['is_rated', 'rate_all', 'rate_exclude'] readonly += ["is_rated", "rate_all", "rate_exclude"]
if not request.user.has_perm('judge.contest_access_code'): if not request.user.has_perm("judge.contest_access_code"):
readonly += ['access_code'] readonly += ["access_code"]
if not request.user.has_perm('judge.create_private_contest'): if not request.user.has_perm("judge.create_private_contest"):
readonly += ['is_private', 'private_contestants', 'is_organization_private', 'organizations'] readonly += [
if not request.user.has_perm('judge.change_contest_visibility'): "is_private",
readonly += ['is_visible'] "private_contestants",
if not request.user.has_perm('judge.contest_problem_label'): "is_organization_private",
readonly += ['problem_label_script'] "organizations",
]
if not request.user.has_perm("judge.change_contest_visibility"):
readonly += ["is_visible"]
if not request.user.has_perm("judge.contest_problem_label"):
readonly += ["problem_label_script"]
return readonly return readonly
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
# `is_visible` will not appear in `cleaned_data` if user cannot edit it # `is_visible` will not appear in `cleaned_data` if user cannot edit it
if form.cleaned_data.get('is_visible') and not request.user.has_perm('judge.change_contest_visibility'): if form.cleaned_data.get("is_visible") and not request.user.has_perm(
if not form.cleaned_data['is_private'] and not form.cleaned_data['is_organization_private']: "judge.change_contest_visibility"
):
if (
not form.cleaned_data["is_private"]
and not form.cleaned_data["is_organization_private"]
):
raise PermissionDenied raise PermissionDenied
if not request.user.has_perm('judge.create_private_contest'): if not request.user.has_perm("judge.create_private_contest"):
raise PermissionDenied raise PermissionDenied
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
# We need this flag because `save_related` deals with the inlines, but does not know if we have already rescored # We need this flag because `save_related` deals with the inlines, but does not know if we have already rescored
self._rescored = False self._rescored = False
if form.changed_data and any(f in form.changed_data for f in ('format_config', 'format_name')): if form.changed_data and any(
f in form.changed_data for f in ("format_config", "format_name")
):
self._rescore(obj.key) self._rescore(obj.key)
self._rescored = True self._rescored = True
@ -188,10 +287,10 @@ class ContestAdmin(CompareVersionAdmin):
super().save_related(request, form, formsets, change) super().save_related(request, form, formsets, change)
# Only rescored if we did not already do so in `save_model` # Only rescored if we did not already do so in `save_model`
if not self._rescored and any(formset.has_changed() for formset in formsets): if not self._rescored and any(formset.has_changed() for formset in formsets):
self._rescore(form.cleaned_data['key']) self._rescore(form.cleaned_data["key"])
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
if not request.user.has_perm('judge.edit_own_contest'): if not request.user.has_perm("judge.edit_own_contest"):
return False return False
if obj is None: if obj is None:
return True return True
@ -199,76 +298,113 @@ class ContestAdmin(CompareVersionAdmin):
def _rescore(self, contest_key): def _rescore(self, contest_key):
from judge.tasks import rescore_contest from judge.tasks import rescore_contest
transaction.on_commit(rescore_contest.s(contest_key).delay) transaction.on_commit(rescore_contest.s(contest_key).delay)
def make_visible(self, request, queryset): def make_visible(self, request, queryset):
if not request.user.has_perm('judge.change_contest_visibility'): if not request.user.has_perm("judge.change_contest_visibility"):
queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True)) queryset = queryset.filter(
Q(is_private=True) | Q(is_organization_private=True)
)
count = queryset.update(is_visible=True) count = queryset.update(is_visible=True)
self.message_user(request, ungettext('%d contest successfully marked as visible.', self.message_user(
'%d contests successfully marked as visible.', request,
count) % count) ungettext(
make_visible.short_description = _('Mark contests as visible') "%d contest successfully marked as visible.",
"%d contests successfully marked as visible.",
count,
)
% count,
)
make_visible.short_description = _("Mark contests as visible")
def make_hidden(self, request, queryset): def make_hidden(self, request, queryset):
if not request.user.has_perm('judge.change_contest_visibility'): if not request.user.has_perm("judge.change_contest_visibility"):
queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True)) queryset = queryset.filter(
Q(is_private=True) | Q(is_organization_private=True)
)
count = queryset.update(is_visible=True) count = queryset.update(is_visible=True)
self.message_user(request, ungettext('%d contest successfully marked as hidden.', self.message_user(
'%d contests successfully marked as hidden.', request,
count) % count) ungettext(
make_hidden.short_description = _('Mark contests as hidden') "%d contest successfully marked as hidden.",
"%d contests successfully marked as hidden.",
count,
)
% count,
)
make_hidden.short_description = _("Mark contests as hidden")
def get_urls(self): def get_urls(self):
return [ return [
url(r'^rate/all/$', self.rate_all_view, name='judge_contest_rate_all'), url(r"^rate/all/$", self.rate_all_view, name="judge_contest_rate_all"),
url(r'^(\d+)/rate/$', self.rate_view, name='judge_contest_rate'), url(r"^(\d+)/rate/$", self.rate_view, name="judge_contest_rate"),
url(r'^(\d+)/judge/(\d+)/$', self.rejudge_view, name='judge_contest_rejudge'), url(
r"^(\d+)/judge/(\d+)/$", self.rejudge_view, name="judge_contest_rejudge"
),
] + super(ContestAdmin, self).get_urls() ] + super(ContestAdmin, self).get_urls()
def rejudge_view(self, request, contest_id, problem_id): def rejudge_view(self, request, contest_id, problem_id):
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission') queryset = ContestSubmission.objects.filter(
problem_id=problem_id
).select_related("submission")
for model in queryset: for model in queryset:
model.submission.judge(rejudge=True) model.submission.judge(rejudge=True)
self.message_user(request, ungettext('%d submission was successfully scheduled for rejudging.', self.message_user(
'%d submissions were successfully scheduled for rejudging.', request,
len(queryset)) % len(queryset)) ungettext(
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,))) "%d submission was successfully scheduled for rejudging.",
"%d submissions were successfully scheduled for rejudging.",
len(queryset),
)
% len(queryset),
)
return HttpResponseRedirect(
reverse("admin:judge_contest_change", args=(contest_id,))
)
def rate_all_view(self, request): def rate_all_view(self, request):
if not request.user.has_perm('judge.contest_rating'): if not request.user.has_perm("judge.contest_rating"):
raise PermissionDenied() raise PermissionDenied()
with transaction.atomic(): with transaction.atomic():
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table) cursor.execute("TRUNCATE TABLE `%s`" % Rating._meta.db_table)
Profile.objects.update(rating=None) Profile.objects.update(rating=None)
for contest in Contest.objects.filter(is_rated=True, end_time__lte=timezone.now()).order_by('end_time'): for contest in Contest.objects.filter(
is_rated=True, end_time__lte=timezone.now()
).order_by("end_time"):
rate_contest(contest) rate_contest(contest)
return HttpResponseRedirect(reverse('admin:judge_contest_changelist')) return HttpResponseRedirect(reverse("admin:judge_contest_changelist"))
def rate_view(self, request, id): def rate_view(self, request, id):
if not request.user.has_perm('judge.contest_rating'): if not request.user.has_perm("judge.contest_rating"):
raise PermissionDenied() raise PermissionDenied()
contest = get_object_or_404(Contest, id=id) contest = get_object_or_404(Contest, id=id)
if not contest.is_rated or not contest.ended: if not contest.is_rated or not contest.ended:
raise Http404() raise Http404()
with transaction.atomic(): with transaction.atomic():
contest.rate() contest.rate()
return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse('admin:judge_contest_changelist'))) return HttpResponseRedirect(
request.META.get("HTTP_REFERER", reverse("admin:judge_contest_changelist"))
)
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
form = super(ContestAdmin, self).get_form(request, obj, **kwargs) form = super(ContestAdmin, self).get_form(request, obj, **kwargs)
if 'problem_label_script' in form.base_fields: if "problem_label_script" in form.base_fields:
# form.base_fields['problem_label_script'] does not exist when the user has only view permission # form.base_fields['problem_label_script'] does not exist when the user has only view permission
# on the model. # on the model.
form.base_fields['problem_label_script'].widget = AceWidget('lua', request.profile.ace_theme) form.base_fields["problem_label_script"].widget = AceWidget(
"lua", request.profile.ace_theme
)
perms = ('edit_own_contest', 'edit_all_contest') perms = ("edit_own_contest", "edit_all_contest")
form.base_fields['curators'].queryset = Profile.objects.filter( form.base_fields["curators"].queryset = Profile.objects.filter(
Q(user__is_superuser=True) | Q(user__is_superuser=True)
Q(user__groups__permissions__codename__in=perms) | | Q(user__groups__permissions__codename__in=perms)
Q(user__user_permissions__codename__in=perms), | Q(user__user_permissions__codename__in=perms),
).distinct() ).distinct()
return form return form
@ -276,29 +412,48 @@ class ContestAdmin(CompareVersionAdmin):
class ContestParticipationForm(ModelForm): class ContestParticipationForm(ModelForm):
class Meta: class Meta:
widgets = { widgets = {
'contest': AdminSelect2Widget(), "contest": AdminSelect2Widget(),
'user': AdminHeavySelect2Widget(data_view='profile_select2'), "user": AdminHeavySelect2Widget(data_view="profile_select2"),
} }
class ContestParticipationAdmin(admin.ModelAdmin): class ContestParticipationAdmin(admin.ModelAdmin):
fields = ('contest', 'user', 'real_start', 'virtual', 'is_disqualified') fields = ("contest", "user", "real_start", "virtual", "is_disqualified")
list_display = ('contest', 'username', 'show_virtual', 'real_start', 'score', 'cumtime', 'tiebreaker') list_display = (
actions = ['recalculate_results'] "contest",
"username",
"show_virtual",
"real_start",
"score",
"cumtime",
"tiebreaker",
)
actions = ["recalculate_results"]
actions_on_bottom = actions_on_top = True actions_on_bottom = actions_on_top = True
search_fields = ('contest__key', 'contest__name', 'user__user__username') search_fields = ("contest__key", "contest__name", "user__user__username")
form = ContestParticipationForm form = ContestParticipationForm
date_hierarchy = 'real_start' date_hierarchy = "real_start"
def get_queryset(self, request): def get_queryset(self, request):
return super(ContestParticipationAdmin, self).get_queryset(request).only( return (
'contest__name', 'contest__format_name', 'contest__format_config', super(ContestParticipationAdmin, self)
'user__user__username', 'real_start', 'score', 'cumtime', 'tiebreaker', 'virtual', .get_queryset(request)
.only(
"contest__name",
"contest__format_name",
"contest__format_config",
"user__user__username",
"real_start",
"score",
"cumtime",
"tiebreaker",
"virtual",
)
) )
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
if form.changed_data and 'is_disqualified' in form.changed_data: if form.changed_data and "is_disqualified" in form.changed_data:
obj.set_disqualified(obj.is_disqualified) obj.set_disqualified(obj.is_disqualified)
def recalculate_results(self, request, queryset): def recalculate_results(self, request, queryset):
@ -306,17 +461,26 @@ class ContestParticipationAdmin(admin.ModelAdmin):
for participation in queryset: for participation in queryset:
participation.recompute_results() participation.recompute_results()
count += 1 count += 1
self.message_user(request, ungettext('%d participation recalculated.', self.message_user(
'%d participations recalculated.', request,
count) % count) ungettext(
recalculate_results.short_description = _('Recalculate results') "%d participation recalculated.",
"%d participations recalculated.",
count,
)
% count,
)
recalculate_results.short_description = _("Recalculate results")
def username(self, obj): def username(self, obj):
return obj.user.username return obj.user.username
username.short_description = _('username')
username.admin_order_field = 'user__user__username' username.short_description = _("username")
username.admin_order_field = "user__user__username"
def show_virtual(self, obj): def show_virtual(self, obj):
return obj.virtual or '-' return obj.virtual or "-"
show_virtual.short_description = _('virtual')
show_virtual.admin_order_field = 'virtual' show_virtual.short_description = _("virtual")
show_virtual.admin_order_field = "virtual"

View file

@ -11,23 +11,28 @@ from reversion_compare.admin import CompareVersionAdmin
from judge.dblock import LockModel from judge.dblock import LockModel
from judge.models import NavigationBar from judge.models import NavigationBar
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class NavigationBarAdmin(DraggableMPTTAdmin): class NavigationBarAdmin(DraggableMPTTAdmin):
list_display = DraggableMPTTAdmin.list_display + ('key', 'linked_path') list_display = DraggableMPTTAdmin.list_display + ("key", "linked_path")
fields = ('key', 'label', 'path', 'order', 'regex', 'parent') fields = ("key", "label", "path", "order", "regex", "parent")
list_editable = () # Bug in SortableModelAdmin: 500 without list_editable being set list_editable = () # Bug in SortableModelAdmin: 500 without list_editable being set
mptt_level_indent = 20 mptt_level_indent = 20
sortable = 'order' sortable = "order"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NavigationBarAdmin, self).__init__(*args, **kwargs) super(NavigationBarAdmin, self).__init__(*args, **kwargs)
self.__save_model_calls = 0 self.__save_model_calls = 0
def linked_path(self, obj): def linked_path(self, obj):
return format_html(u'<a href="{0}" target="_blank">{0}</a>', obj.path) return format_html('<a href="{0}" target="_blank">{0}</a>', obj.path)
linked_path.short_description = _('link path')
linked_path.short_description = _("link path")
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
self.__save_model_calls += 1 self.__save_model_calls += 1
@ -36,7 +41,9 @@ class NavigationBarAdmin(DraggableMPTTAdmin):
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
self.__save_model_calls = 0 self.__save_model_calls = 0
with NavigationBar.objects.disable_mptt_updates(): with NavigationBar.objects.disable_mptt_updates():
result = super(NavigationBarAdmin, self).changelist_view(request, extra_context) result = super(NavigationBarAdmin, self).changelist_view(
request, extra_context
)
if self.__save_model_calls: if self.__save_model_calls:
with LockModel(write=(NavigationBar,)): with LockModel(write=(NavigationBar,)):
NavigationBar.objects.rebuild() NavigationBar.objects.rebuild()
@ -46,74 +53,105 @@ class NavigationBarAdmin(DraggableMPTTAdmin):
class BlogPostForm(ModelForm): class BlogPostForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BlogPostForm, self).__init__(*args, **kwargs) super(BlogPostForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False self.fields["authors"].widget.can_add_related = False
class Meta: class Meta:
widgets = { widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "authors": AdminHeavySelect2MultipleWidget(
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2', data_view="profile_select2", attrs={"style": "width: 100%"}
attrs={'style': 'width: 100%'}), ),
"organizations": AdminHeavySelect2MultipleWidget(
data_view="organization_select2", attrs={"style": "width: 100%"}
),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview')) widgets["content"] = HeavyPreviewAdminPageDownWidget(
widgets['summary'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview')) preview=reverse_lazy("blog_preview")
)
widgets["summary"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("blog_preview")
)
class BlogPostAdmin(CompareVersionAdmin): class BlogPostAdmin(CompareVersionAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('title', 'slug', 'authors', 'visible', 'sticky', 'publish_on', (
'is_organization_private', 'organizations')}), None,
(_('Content'), {'fields': ('content', 'og_image')}), {
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}), "fields": (
"title",
"slug",
"authors",
"visible",
"sticky",
"publish_on",
"is_organization_private",
"organizations",
)
},
),
(_("Content"), {"fields": ("content", "og_image")}),
(_("Summary"), {"classes": ("collapse",), "fields": ("summary",)}),
) )
prepopulated_fields = {'slug': ('title',)} prepopulated_fields = {"slug": ("title",)}
list_display = ('id', 'title', 'visible', 'sticky', 'publish_on') list_display = ("id", "title", "visible", "sticky", "publish_on")
list_display_links = ('id', 'title') list_display_links = ("id", "title")
ordering = ('-publish_on',) ordering = ("-publish_on",)
form = BlogPostForm form = BlogPostForm
date_hierarchy = 'publish_on' date_hierarchy = "publish_on"
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
return (request.user.has_perm('judge.edit_all_post') or return (
request.user.has_perm('judge.change_blogpost') and ( request.user.has_perm("judge.edit_all_post")
obj is None or or request.user.has_perm("judge.change_blogpost")
obj.authors.filter(id=request.profile.id).exists())) and (obj is None or obj.authors.filter(id=request.profile.id).exists())
)
class SolutionForm(ModelForm): class SolutionForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SolutionForm, self).__init__(*args, **kwargs) super(SolutionForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False self.fields["authors"].widget.can_add_related = False
class Meta: class Meta:
widgets = { widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "authors": AdminHeavySelect2MultipleWidget(
'problem': AdminHeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 250px'}), data_view="profile_select2", attrs={"style": "width: 100%"}
),
"problem": AdminHeavySelect2Widget(
data_view="problem_select2", attrs={"style": "width: 250px"}
),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview')) widgets["content"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("solution_preview")
)
class LicenseForm(ModelForm): class LicenseForm(ModelForm):
class Meta: class Meta:
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets = {'text': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('license_preview'))} widgets = {
"text": HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("license_preview")
)
}
class LicenseAdmin(admin.ModelAdmin): class LicenseAdmin(admin.ModelAdmin):
fields = ('key', 'link', 'name', 'display', 'icon', 'text') fields = ("key", "link", "name", "display", "icon", "text")
list_display = ('name', 'key') list_display = ("name", "key")
form = LicenseForm form = LicenseForm
class UserListFilter(admin.SimpleListFilter): class UserListFilter(admin.SimpleListFilter):
title = _('user') title = _("user")
parameter_name = 'user' parameter_name = "user"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return User.objects.filter(is_staff=True).values_list('id', 'username') return User.objects.filter(is_staff=True).values_list("id", "username")
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value(): if self.value():
@ -122,10 +160,24 @@ class UserListFilter(admin.SimpleListFilter):
class LogEntryAdmin(admin.ModelAdmin): class LogEntryAdmin(admin.ModelAdmin):
readonly_fields = ('user', 'content_type', 'object_id', 'object_repr', 'action_flag', 'change_message') readonly_fields = (
list_display = ('__str__', 'action_time', 'user', 'content_type', 'object_link', 'diff_link') "user",
search_fields = ('object_repr', 'change_message') "content_type",
list_filter = (UserListFilter, 'content_type') "object_id",
"object_repr",
"action_flag",
"change_message",
)
list_display = (
"__str__",
"action_time",
"user",
"content_type",
"object_link",
"diff_link",
)
search_fields = ("object_repr", "change_message")
list_filter = (UserListFilter, "content_type")
list_display_links = None list_display_links = None
actions = None actions = None
@ -144,25 +196,35 @@ class LogEntryAdmin(admin.ModelAdmin):
else: else:
ct = obj.content_type ct = obj.content_type
try: try:
link = format_html('<a href="{1}">{0}</a>', obj.object_repr, link = format_html(
reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(obj.object_id,))) '<a href="{1}">{0}</a>',
obj.object_repr,
reverse(
"admin:%s_%s_change" % (ct.app_label, ct.model),
args=(obj.object_id,),
),
)
except NoReverseMatch: except NoReverseMatch:
link = obj.object_repr link = obj.object_repr
return link return link
object_link.admin_order_field = 'object_repr'
object_link.short_description = _('object') object_link.admin_order_field = "object_repr"
object_link.short_description = _("object")
def diff_link(self, obj): def diff_link(self, obj):
if obj.is_deletion(): if obj.is_deletion():
return None return None
ct = obj.content_type ct = obj.content_type
try: try:
url = reverse('admin:%s_%s_history' % (ct.app_label, ct.model), args=(obj.object_id,)) url = reverse(
link = format_html('<a href="{1}">{0}</a>', _('Diff'), url) "admin:%s_%s_history" % (ct.app_label, ct.model), args=(obj.object_id,)
)
link = format_html('<a href="{1}">{0}</a>', _("Diff"), url)
except NoReverseMatch: except NoReverseMatch:
link = None link = None
return link return link
diff_link.short_description = _('diff')
diff_link.short_description = _("diff")
def queryset(self, request): def queryset(self, request):
return super().queryset(request).prefetch_related('content_type') return super().queryset(request).prefetch_related("content_type")

View file

@ -6,61 +6,88 @@ from django.utils.translation import gettext, gettext_lazy as _
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from judge.models import Organization from judge.models import Organization
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class OrganizationForm(ModelForm): class OrganizationForm(ModelForm):
class Meta: class Meta:
widgets = { widgets = {
'admins': AdminHeavySelect2MultipleWidget(data_view='profile_select2'), "admins": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
'registrant': AdminHeavySelect2Widget(data_view='profile_select2'), "registrant": AdminHeavySelect2Widget(data_view="profile_select2"),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['about'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('organization_preview')) widgets["about"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("organization_preview")
)
class OrganizationAdmin(VersionAdmin): class OrganizationAdmin(VersionAdmin):
readonly_fields = ('creation_date',) readonly_fields = ("creation_date",)
fields = ('name', 'slug', 'short_name', 'is_open', 'about', 'logo_override_image', 'slots', 'registrant', fields = (
'creation_date', 'admins') "name",
list_display = ('name', 'short_name', 'is_open', 'slots', 'registrant', 'show_public') "slug",
prepopulated_fields = {'slug': ('name',)} "short_name",
"is_open",
"about",
"logo_override_image",
"slots",
"registrant",
"creation_date",
"admins",
)
list_display = (
"name",
"short_name",
"is_open",
"slots",
"registrant",
"show_public",
)
prepopulated_fields = {"slug": ("name",)}
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = OrganizationForm form = OrganizationForm
def show_public(self, obj): def show_public(self, obj):
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>', return format_html(
obj.get_absolute_url(), gettext('View on site')) '<a href="{0}" style="white-space:nowrap;">{1}</a>',
obj.get_absolute_url(),
gettext("View on site"),
)
show_public.short_description = '' show_public.short_description = ""
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields fields = self.readonly_fields
if not request.user.has_perm('judge.organization_admin'): if not request.user.has_perm("judge.organization_admin"):
return fields + ('registrant', 'admins', 'is_open', 'slots') return fields + ("registrant", "admins", "is_open", "slots")
return fields return fields
def get_queryset(self, request): def get_queryset(self, request):
queryset = Organization.objects.all() queryset = Organization.objects.all()
if request.user.has_perm('judge.edit_all_organization'): if request.user.has_perm("judge.edit_all_organization"):
return queryset return queryset
else: else:
return queryset.filter(admins=request.profile.id) return queryset.filter(admins=request.profile.id)
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
if not request.user.has_perm('judge.change_organization'): if not request.user.has_perm("judge.change_organization"):
return False return False
if request.user.has_perm('judge.edit_all_organization') or obj is None: if request.user.has_perm("judge.edit_all_organization") or obj is None:
return True return True
return obj.admins.filter(id=request.profile.id).exists() return obj.admins.filter(id=request.profile.id).exists()
class OrganizationRequestAdmin(admin.ModelAdmin): class OrganizationRequestAdmin(admin.ModelAdmin):
list_display = ('username', 'organization', 'state', 'time') list_display = ("username", "organization", "state", "time")
readonly_fields = ('user', 'organization') readonly_fields = ("user", "organization")
def username(self, obj): def username(self, obj):
return obj.user.user.username return obj.user.user.username
username.short_description = _('username')
username.admin_order_field = 'user__user__username' username.short_description = _("username")
username.admin_order_field = "user__user__username"

View file

@ -14,45 +14,74 @@ from reversion.admin import VersionAdmin
from reversion_compare.admin import CompareVersionAdmin from reversion_compare.admin import CompareVersionAdmin
from judge.models import LanguageLimit, Problem, ProblemClarification, ProblemTranslation, Profile, Solution from judge.models import (
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminSelect2MultipleWidget, AdminSelect2Widget, \ LanguageLimit,
CheckboxSelectMultipleWithSelectAll, HeavyPreviewAdminPageDownWidget, HeavyPreviewPageDownWidget Problem,
ProblemClarification,
ProblemTranslation,
Profile,
Solution,
)
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminSelect2MultipleWidget,
AdminSelect2Widget,
CheckboxSelectMultipleWithSelectAll,
HeavyPreviewAdminPageDownWidget,
HeavyPreviewPageDownWidget,
)
class ProblemForm(ModelForm): class ProblemForm(ModelForm):
change_message = forms.CharField(max_length=256, label='Edit reason', required=False) change_message = forms.CharField(
max_length=256, label="Edit reason", required=False
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProblemForm, self).__init__(*args, **kwargs) super(ProblemForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False self.fields["authors"].widget.can_add_related = False
self.fields['curators'].widget.can_add_related = False self.fields["curators"].widget.can_add_related = False
self.fields['testers'].widget.can_add_related = False self.fields["testers"].widget.can_add_related = False
self.fields['banned_users'].widget.can_add_related = False self.fields["banned_users"].widget.can_add_related = False
self.fields['change_message'].widget.attrs.update({ self.fields["change_message"].widget.attrs.update(
'placeholder': gettext('Describe the changes you made (optional)'), {
}) "placeholder": gettext("Describe the changes you made (optional)"),
}
)
class Meta: class Meta:
widgets = { widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "authors": AdminHeavySelect2MultipleWidget(
'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), data_view="profile_select2", attrs={"style": "width: 100%"}
'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), ),
'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2', "curators": AdminHeavySelect2MultipleWidget(
attrs={'style': 'width: 100%'}), data_view="profile_select2", attrs={"style": "width: 100%"}
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2', ),
attrs={'style': 'width: 100%'}), "testers": AdminHeavySelect2MultipleWidget(
'types': AdminSelect2MultipleWidget, data_view="profile_select2", attrs={"style": "width: 100%"}
'group': AdminSelect2Widget, ),
"banned_users": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"organizations": AdminHeavySelect2MultipleWidget(
data_view="organization_select2", attrs={"style": "width: 100%"}
),
"types": AdminSelect2MultipleWidget,
"group": AdminSelect2Widget,
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview')) widgets["description"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("problem_preview")
)
class ProblemCreatorListFilter(admin.SimpleListFilter): class ProblemCreatorListFilter(admin.SimpleListFilter):
title = parameter_name = 'creator' title = parameter_name = "creator"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
queryset = Profile.objects.exclude(authored_problems=None).values_list('user__username', flat=True) queryset = Profile.objects.exclude(authored_problems=None).values_list(
"user__username", flat=True
)
return [(name, name) for name in queryset] return [(name, name) for name in queryset]
def queryset(self, request, queryset): def queryset(self, request, queryset):
@ -63,24 +92,28 @@ class ProblemCreatorListFilter(admin.SimpleListFilter):
class LanguageLimitInlineForm(ModelForm): class LanguageLimitInlineForm(ModelForm):
class Meta: class Meta:
widgets = {'language': AdminSelect2Widget} widgets = {"language": AdminSelect2Widget}
class LanguageLimitInline(admin.TabularInline): class LanguageLimitInline(admin.TabularInline):
model = LanguageLimit model = LanguageLimit
fields = ('language', 'time_limit', 'memory_limit') fields = ("language", "time_limit", "memory_limit")
form = LanguageLimitInlineForm form = LanguageLimitInlineForm
class ProblemClarificationForm(ModelForm): class ProblemClarificationForm(ModelForm):
class Meta: class Meta:
if HeavyPreviewPageDownWidget is not None: if HeavyPreviewPageDownWidget is not None:
widgets = {'description': HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'))} widgets = {
"description": HeavyPreviewPageDownWidget(
preview=reverse_lazy("comment_preview")
)
}
class ProblemClarificationInline(admin.StackedInline): class ProblemClarificationInline(admin.StackedInline):
model = ProblemClarification model = ProblemClarification
fields = ('description',) fields = ("description",)
form = ProblemClarificationForm form = ProblemClarificationForm
extra = 0 extra = 0
@ -88,20 +121,24 @@ class ProblemClarificationInline(admin.StackedInline):
class ProblemSolutionForm(ModelForm): class ProblemSolutionForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProblemSolutionForm, self).__init__(*args, **kwargs) super(ProblemSolutionForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False self.fields["authors"].widget.can_add_related = False
class Meta: class Meta:
widgets = { widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "authors": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview')) widgets["content"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("solution_preview")
)
class ProblemSolutionInline(admin.StackedInline): class ProblemSolutionInline(admin.StackedInline):
model = Solution model = Solution
fields = ('is_public', 'publish_on', 'authors', 'content') fields = ("is_public", "publish_on", "authors", "content")
form = ProblemSolutionForm form = ProblemSolutionForm
extra = 0 extra = 0
@ -109,168 +146,250 @@ class ProblemSolutionInline(admin.StackedInline):
class ProblemTranslationForm(ModelForm): class ProblemTranslationForm(ModelForm):
class Meta: class Meta:
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets = {'description': HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('problem_preview'))} widgets = {
"description": HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("problem_preview")
)
}
class ProblemTranslationInline(admin.StackedInline): class ProblemTranslationInline(admin.StackedInline):
model = ProblemTranslation model = ProblemTranslation
fields = ('language', 'name', 'description') fields = ("language", "name", "description")
form = ProblemTranslationForm form = ProblemTranslationForm
extra = 0 extra = 0
class ProblemAdmin(CompareVersionAdmin): class ProblemAdmin(CompareVersionAdmin):
fieldsets = ( fieldsets = (
(None, { (
'fields': ( None,
'code', 'name', 'is_public', 'is_manually_managed', 'date', 'authors', 'curators', 'testers', {
'is_organization_private', 'organizations', 'description', 'license', "fields": (
), "code",
}), "name",
(_('Social Media'), {'classes': ('collapse',), 'fields': ('og_image', 'summary')}), "is_public",
(_('Taxonomy'), {'fields': ('types', 'group')}), "is_manually_managed",
(_('Points'), {'fields': (('points', 'partial'), 'short_circuit')}), "date",
(_('Limits'), {'fields': ('time_limit', 'memory_limit')}), "authors",
(_('Language'), {'fields': ('allowed_languages',)}), "curators",
(_('Justice'), {'fields': ('banned_users',)}), "testers",
(_('History'), {'fields': ('change_message',)}), "is_organization_private",
"organizations",
"description",
"license",
),
},
),
(
_("Social Media"),
{"classes": ("collapse",), "fields": ("og_image", "summary")},
),
(_("Taxonomy"), {"fields": ("types", "group")}),
(_("Points"), {"fields": (("points", "partial"), "short_circuit")}),
(_("Limits"), {"fields": ("time_limit", "memory_limit")}),
(_("Language"), {"fields": ("allowed_languages",)}),
(_("Justice"), {"fields": ("banned_users",)}),
(_("History"), {"fields": ("change_message",)}),
) )
list_display = ['code', 'name', 'show_authors', 'points', 'vote_cnt', 'vote_mean', 'vote_median', 'vote_std', 'is_public', 'show_public'] list_display = [
ordering = ['code'] "code",
search_fields = ('code', 'name', 'authors__user__username', 'curators__user__username') "name",
inlines = [LanguageLimitInline, ProblemClarificationInline, ProblemSolutionInline, ProblemTranslationInline] "show_authors",
"points",
"vote_cnt",
"vote_mean",
"vote_median",
"vote_std",
"is_public",
"show_public",
]
ordering = ["code"]
search_fields = (
"code",
"name",
"authors__user__username",
"curators__user__username",
)
inlines = [
LanguageLimitInline,
ProblemClarificationInline,
ProblemSolutionInline,
ProblemTranslationInline,
]
list_max_show_all = 1000 list_max_show_all = 1000
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
list_filter = ('is_public', ProblemCreatorListFilter) list_filter = ("is_public", ProblemCreatorListFilter)
form = ProblemForm form = ProblemForm
date_hierarchy = 'date' date_hierarchy = "date"
def get_actions(self, request): def get_actions(self, request):
actions = super(ProblemAdmin, self).get_actions(request) actions = super(ProblemAdmin, self).get_actions(request)
if request.user.has_perm('judge.change_public_visibility'): if request.user.has_perm("judge.change_public_visibility"):
func, name, desc = self.get_action('make_public') func, name, desc = self.get_action("make_public")
actions[name] = (func, name, desc) actions[name] = (func, name, desc)
func, name, desc = self.get_action('make_private') func, name, desc = self.get_action("make_private")
actions[name] = (func, name, desc) actions[name] = (func, name, desc)
return actions return actions
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields fields = self.readonly_fields
if not request.user.has_perm('judge.change_public_visibility'): if not request.user.has_perm("judge.change_public_visibility"):
fields += ('is_public',) fields += ("is_public",)
if not request.user.has_perm('judge.change_manually_managed'): if not request.user.has_perm("judge.change_manually_managed"):
fields += ('is_manually_managed',) fields += ("is_manually_managed",)
return fields return fields
def show_authors(self, obj): def show_authors(self, obj):
return ', '.join(map(attrgetter('user.username'), obj.authors.all())) return ", ".join(map(attrgetter("user.username"), obj.authors.all()))
show_authors.short_description = _('Authors') show_authors.short_description = _("Authors")
def show_public(self, obj): def show_public(self, obj):
return format_html('<a href="{1}">{0}</a>', gettext('View on site'), obj.get_absolute_url()) return format_html(
'<a href="{1}">{0}</a>', gettext("View on site"), obj.get_absolute_url()
)
show_public.short_description = '' show_public.short_description = ""
def _rescore(self, request, problem_id): def _rescore(self, request, problem_id):
from judge.tasks import rescore_problem from judge.tasks import rescore_problem
transaction.on_commit(rescore_problem.s(problem_id).delay) transaction.on_commit(rescore_problem.s(problem_id).delay)
def make_public(self, request, queryset): def make_public(self, request, queryset):
count = queryset.update(is_public=True) count = queryset.update(is_public=True)
for problem_id in queryset.values_list('id', flat=True): for problem_id in queryset.values_list("id", flat=True):
self._rescore(request, problem_id) self._rescore(request, problem_id)
self.message_user(request, ungettext('%d problem successfully marked as public.', self.message_user(
'%d problems successfully marked as public.', request,
count) % count) ungettext(
"%d problem successfully marked as public.",
"%d problems successfully marked as public.",
count,
)
% count,
)
make_public.short_description = _('Mark problems as public') make_public.short_description = _("Mark problems as public")
def make_private(self, request, queryset): def make_private(self, request, queryset):
count = queryset.update(is_public=False) count = queryset.update(is_public=False)
for problem_id in queryset.values_list('id', flat=True): for problem_id in queryset.values_list("id", flat=True):
self._rescore(request, problem_id) self._rescore(request, problem_id)
self.message_user(request, ungettext('%d problem successfully marked as private.', self.message_user(
'%d problems successfully marked as private.', request,
count) % count) ungettext(
"%d problem successfully marked as private.",
"%d problems successfully marked as private.",
count,
)
% count,
)
make_private.short_description = _('Mark problems as private') make_private.short_description = _("Mark problems as private")
def get_queryset(self, request): def get_queryset(self, request):
queryset = Problem.objects.prefetch_related('authors__user') queryset = Problem.objects.prefetch_related("authors__user")
queryset = queryset.annotate( queryset = queryset.annotate(
_vote_mean=Avg('problem_points_votes__points'), _vote_mean=Avg("problem_points_votes__points"),
_vote_std=StdDev('problem_points_votes__points'), _vote_std=StdDev("problem_points_votes__points"),
_vote_cnt=Count('problem_points_votes__points') _vote_cnt=Count("problem_points_votes__points"),
) )
if request.user.has_perm('judge.edit_all_problem'): if request.user.has_perm("judge.edit_all_problem"):
return queryset return queryset
access = Q() access = Q()
if request.user.has_perm('judge.edit_public_problem'): if request.user.has_perm("judge.edit_public_problem"):
access |= Q(is_public=True) access |= Q(is_public=True)
if request.user.has_perm('judge.edit_own_problem'): if request.user.has_perm("judge.edit_own_problem"):
access |= Q(authors__id=request.profile.id) | Q(curators__id=request.profile.id) access |= Q(authors__id=request.profile.id) | Q(
curators__id=request.profile.id
)
return queryset.filter(access).distinct() if access else queryset.none() return queryset.filter(access).distinct() if access else queryset.none()
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
if request.user.has_perm('judge.edit_all_problem') or obj is None: if request.user.has_perm("judge.edit_all_problem") or obj is None:
return True return True
if request.user.has_perm('judge.edit_public_problem') and obj.is_public: if request.user.has_perm("judge.edit_public_problem") and obj.is_public:
return True return True
if not request.user.has_perm('judge.edit_own_problem'): if not request.user.has_perm("judge.edit_own_problem"):
return False return False
return obj.is_editor(request.profile) return obj.is_editor(request.profile)
def formfield_for_manytomany(self, db_field, request=None, **kwargs): def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'allowed_languages': if db_field.name == "allowed_languages":
kwargs['widget'] = CheckboxSelectMultipleWithSelectAll() kwargs["widget"] = CheckboxSelectMultipleWithSelectAll()
return super(ProblemAdmin, self).formfield_for_manytomany(db_field, request, **kwargs) return super(ProblemAdmin, self).formfield_for_manytomany(
db_field, request, **kwargs
)
def get_form(self, *args, **kwargs): def get_form(self, *args, **kwargs):
form = super(ProblemAdmin, self).get_form(*args, **kwargs) form = super(ProblemAdmin, self).get_form(*args, **kwargs)
form.base_fields['authors'].queryset = Profile.objects.all() form.base_fields["authors"].queryset = Profile.objects.all()
return form return form
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
if form.changed_data and any(f in form.changed_data for f in ('is_public', 'points', 'partial')): if form.changed_data and any(
f in form.changed_data for f in ("is_public", "points", "partial")
):
self._rescore(request, obj.id) self._rescore(request, obj.id)
def construct_change_message(self, request, form, *args, **kwargs): def construct_change_message(self, request, form, *args, **kwargs):
if form.cleaned_data.get('change_message'): if form.cleaned_data.get("change_message"):
return form.cleaned_data['change_message'] return form.cleaned_data["change_message"]
return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs) return super(ProblemAdmin, self).construct_change_message(
request, form, *args, **kwargs
)
def vote_mean(self, obj): def vote_mean(self, obj):
return round(obj._vote_mean, 1) if obj._vote_mean is not None else None return round(obj._vote_mean, 1) if obj._vote_mean is not None else None
vote_mean.admin_order_field = '_vote_mean'
vote_mean.admin_order_field = "_vote_mean"
def vote_std(self, obj): def vote_std(self, obj):
return round(obj._vote_std, 1) if obj._vote_std is not None else None return round(obj._vote_std, 1) if obj._vote_std is not None else None
vote_std.admin_order_field = '_vote_std'
vote_std.admin_order_field = "_vote_std"
def vote_cnt(self, obj): def vote_cnt(self, obj):
return obj._vote_cnt return obj._vote_cnt
vote_cnt.admin_order_field = '_vote_cnt'
vote_cnt.admin_order_field = "_vote_cnt"
def vote_median(self, obj): def vote_median(self, obj):
votes = obj.problem_points_votes.values_list('points', flat=True) votes = obj.problem_points_votes.values_list("points", flat=True)
return statistics.median(votes) if votes else None return statistics.median(votes) if votes else None
class ProblemPointsVoteAdmin(admin.ModelAdmin): class ProblemPointsVoteAdmin(admin.ModelAdmin):
list_display = ('vote_points', 'voter', 'voter_rating', 'voter_point', 'problem_name', 'problem_code', 'problem_points') list_display = (
search_fields = ('voter__user__username', 'problem__code', 'problem__name') "vote_points",
readonly_fields = ('voter', 'problem', 'problem_code', 'problem_points', 'voter_rating', 'voter_point') "voter",
"voter_rating",
"voter_point",
"problem_name",
"problem_code",
"problem_points",
)
search_fields = ("voter__user__username", "problem__code", "problem__name")
readonly_fields = (
"voter",
"problem",
"problem_code",
"problem_points",
"voter_rating",
"voter_point",
)
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
if obj is None: if obj is None:
return request.user.has_perm('judge.edit_own_problem') return request.user.has_perm("judge.edit_own_problem")
return obj.problem.is_editable_by(request.user) return obj.problem.is_editable_by(request.user)
def lookup_allowed(self, key, value): def lookup_allowed(self, key, value):
@ -278,29 +397,35 @@ class ProblemPointsVoteAdmin(admin.ModelAdmin):
def problem_code(self, obj): def problem_code(self, obj):
return obj.problem.code return obj.problem.code
problem_code.short_description = _('Problem code')
problem_code.admin_order_field = 'problem__code' problem_code.short_description = _("Problem code")
problem_code.admin_order_field = "problem__code"
def problem_points(self, obj): def problem_points(self, obj):
return obj.problem.points return obj.problem.points
problem_points.short_description = _('Points')
problem_points.admin_order_field = 'problem__points' problem_points.short_description = _("Points")
problem_points.admin_order_field = "problem__points"
def problem_name(self, obj): def problem_name(self, obj):
return obj.problem.name return obj.problem.name
problem_name.short_description = _('Problem name')
problem_name.admin_order_field = 'problem__name' problem_name.short_description = _("Problem name")
problem_name.admin_order_field = "problem__name"
def voter_rating(self, obj): def voter_rating(self, obj):
return obj.voter.rating return obj.voter.rating
voter_rating.short_description = _('Voter rating')
voter_rating.admin_order_field = 'voter__rating' voter_rating.short_description = _("Voter rating")
voter_rating.admin_order_field = "voter__rating"
def voter_point(self, obj): def voter_point(self, obj):
return round(obj.voter.performance_points) return round(obj.voter.performance_points)
voter_point.short_description = _('Voter point')
voter_point.admin_order_field = 'voter__performance_points' voter_point.short_description = _("Voter point")
voter_point.admin_order_field = "voter__performance_points"
def vote_points(self, obj): def vote_points(self, obj):
return obj.points return obj.points
vote_points.short_description = _('Vote')
vote_points.short_description = _("Vote")

View file

@ -12,30 +12,40 @@ from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
class ProfileForm(ModelForm): class ProfileForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs) super(ProfileForm, self).__init__(*args, **kwargs)
if 'current_contest' in self.base_fields: if "current_contest" in self.base_fields:
# form.fields['current_contest'] does not exist when the user has only view permission on the model. # form.fields['current_contest'] does not exist when the user has only view permission on the model.
self.fields['current_contest'].queryset = self.instance.contest_history.select_related('contest') \ self.fields[
.only('contest__name', 'user_id', 'virtual') "current_contest"
self.fields['current_contest'].label_from_instance = \ ].queryset = self.instance.contest_history.select_related("contest").only(
lambda obj: '%s v%d' % (obj.contest.name, obj.virtual) if obj.virtual else obj.contest.name "contest__name", "user_id", "virtual"
)
self.fields["current_contest"].label_from_instance = (
lambda obj: "%s v%d" % (obj.contest.name, obj.virtual)
if obj.virtual
else obj.contest.name
)
class Meta: class Meta:
widgets = { widgets = {
'timezone': AdminSelect2Widget, "timezone": AdminSelect2Widget,
'language': AdminSelect2Widget, "language": AdminSelect2Widget,
'ace_theme': AdminSelect2Widget, "ace_theme": AdminSelect2Widget,
'current_contest': AdminSelect2Widget, "current_contest": AdminSelect2Widget,
} }
if AdminPagedownWidget is not None: if AdminPagedownWidget is not None:
widgets['about'] = AdminPagedownWidget widgets["about"] = AdminPagedownWidget
class TimezoneFilter(admin.SimpleListFilter): class TimezoneFilter(admin.SimpleListFilter):
title = _('timezone') title = _("timezone")
parameter_name = 'timezone' parameter_name = "timezone"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return Profile.objects.values_list('timezone', 'timezone').distinct().order_by('timezone') return (
Profile.objects.values_list("timezone", "timezone")
.distinct()
.order_by("timezone")
)
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() is None: if self.value() is None:
@ -44,75 +54,116 @@ class TimezoneFilter(admin.SimpleListFilter):
class ProfileAdmin(VersionAdmin): class ProfileAdmin(VersionAdmin):
fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme', fields = (
'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'is_banned_problem_voting', 'notes', 'is_totp_enabled', 'user_script', "user",
'current_contest') "display_rank",
readonly_fields = ('user',) "about",
list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full', "organizations",
'date_joined', 'last_access', 'ip', 'show_public') "timezone",
ordering = ('user__username',) "language",
search_fields = ('user__username', 'ip', 'user__email') "ace_theme",
list_filter = ('language', TimezoneFilter) "math_engine",
actions = ('recalculate_points',) "last_access",
"ip",
"mute",
"is_unlisted",
"is_banned_problem_voting",
"notes",
"is_totp_enabled",
"user_script",
"current_contest",
)
readonly_fields = ("user",)
list_display = (
"admin_user_admin",
"email",
"is_totp_enabled",
"timezone_full",
"date_joined",
"last_access",
"ip",
"show_public",
)
ordering = ("user__username",)
search_fields = ("user__username", "ip", "user__email")
list_filter = ("language", TimezoneFilter)
actions = ("recalculate_points",)
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
form = ProfileForm form = ProfileForm
def get_queryset(self, request): def get_queryset(self, request):
return super(ProfileAdmin, self).get_queryset(request).select_related('user') return super(ProfileAdmin, self).get_queryset(request).select_related("user")
def get_fields(self, request, obj=None): def get_fields(self, request, obj=None):
if request.user.has_perm('judge.totp'): if request.user.has_perm("judge.totp"):
fields = list(self.fields) fields = list(self.fields)
fields.insert(fields.index('is_totp_enabled') + 1, 'totp_key') fields.insert(fields.index("is_totp_enabled") + 1, "totp_key")
return tuple(fields) return tuple(fields)
else: else:
return self.fields return self.fields
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields fields = self.readonly_fields
if not request.user.has_perm('judge.totp'): if not request.user.has_perm("judge.totp"):
fields += ('is_totp_enabled',) fields += ("is_totp_enabled",)
return fields return fields
def show_public(self, obj): def show_public(self, obj):
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>', return format_html(
obj.get_absolute_url(), gettext('View on site')) '<a href="{0}" style="white-space:nowrap;">{1}</a>',
show_public.short_description = '' obj.get_absolute_url(),
gettext("View on site"),
)
show_public.short_description = ""
def admin_user_admin(self, obj): def admin_user_admin(self, obj):
return obj.username return obj.username
admin_user_admin.admin_order_field = 'user__username'
admin_user_admin.short_description = _('User') admin_user_admin.admin_order_field = "user__username"
admin_user_admin.short_description = _("User")
def email(self, obj): def email(self, obj):
return obj.user.email return obj.user.email
email.admin_order_field = 'user__email'
email.short_description = _('Email') email.admin_order_field = "user__email"
email.short_description = _("Email")
def timezone_full(self, obj): def timezone_full(self, obj):
return obj.timezone return obj.timezone
timezone_full.admin_order_field = 'timezone'
timezone_full.short_description = _('Timezone') timezone_full.admin_order_field = "timezone"
timezone_full.short_description = _("Timezone")
def date_joined(self, obj): def date_joined(self, obj):
return obj.user.date_joined return obj.user.date_joined
date_joined.admin_order_field = 'user__date_joined'
date_joined.short_description = _('date joined') date_joined.admin_order_field = "user__date_joined"
date_joined.short_description = _("date joined")
def recalculate_points(self, request, queryset): def recalculate_points(self, request, queryset):
count = 0 count = 0
for profile in queryset: for profile in queryset:
profile.calculate_points() profile.calculate_points()
count += 1 count += 1
self.message_user(request, ungettext('%d user have scores recalculated.', self.message_user(
'%d users have scores recalculated.', request,
count) % count) ungettext(
recalculate_points.short_description = _('Recalculate scores') "%d user have scores recalculated.",
"%d users have scores recalculated.",
count,
)
% count,
)
recalculate_points.short_description = _("Recalculate scores")
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
form = super(ProfileAdmin, self).get_form(request, obj, **kwargs) form = super(ProfileAdmin, self).get_form(request, obj, **kwargs)
if 'user_script' in form.base_fields: if "user_script" in form.base_fields:
# form.base_fields['user_script'] does not exist when the user has only view permission on the model. # form.base_fields['user_script'] does not exist when the user has only view permission on the model.
form.base_fields['user_script'].widget = AceWidget('javascript', request.profile.ace_theme) form.base_fields["user_script"].widget = AceWidget(
"javascript", request.profile.ace_theme
)
return form return form

View file

@ -16,41 +16,63 @@ from judge.widgets import AdminHeavySelect2MultipleWidget, AdminPagedownWidget
class LanguageForm(ModelForm): class LanguageForm(ModelForm):
problems = ModelMultipleChoiceField( problems = ModelMultipleChoiceField(
label=_('Disallowed problems'), label=_("Disallowed problems"),
queryset=Problem.objects.all(), queryset=Problem.objects.all(),
required=False, required=False,
help_text=_('These problems are NOT allowed to be submitted in this language'), help_text=_("These problems are NOT allowed to be submitted in this language"),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2')) widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class Meta: class Meta:
if AdminPagedownWidget is not None: if AdminPagedownWidget is not None:
widgets = {'description': AdminPagedownWidget} widgets = {"description": AdminPagedownWidget}
class LanguageAdmin(VersionAdmin): class LanguageAdmin(VersionAdmin):
fields = ('key', 'name', 'short_name', 'common_name', 'ace', 'pygments', 'info', 'description', fields = (
'template', 'problems') "key",
list_display = ('key', 'name', 'common_name', 'info') "name",
"short_name",
"common_name",
"ace",
"pygments",
"info",
"description",
"template",
"problems",
)
list_display = ("key", "name", "common_name", "info")
form = LanguageForm form = LanguageForm
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(LanguageAdmin, self).save_model(request, obj, form, change) super(LanguageAdmin, self).save_model(request, obj, form, change)
obj.problem_set.set(Problem.objects.exclude(id__in=form.cleaned_data['problems'].values('id'))) obj.problem_set.set(
Problem.objects.exclude(id__in=form.cleaned_data["problems"].values("id"))
)
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
self.form.base_fields['problems'].initial = \ self.form.base_fields["problems"].initial = (
Problem.objects.exclude(id__in=obj.problem_set.values('id')).values_list('pk', flat=True) if obj else [] Problem.objects.exclude(id__in=obj.problem_set.values("id")).values_list(
"pk", flat=True
)
if obj
else []
)
form = super(LanguageAdmin, self).get_form(request, obj, **kwargs) form = super(LanguageAdmin, self).get_form(request, obj, **kwargs)
if obj is not None: if obj is not None:
form.base_fields['template'].widget = AceWidget(obj.ace, request.profile.ace_theme) form.base_fields["template"].widget = AceWidget(
obj.ace, request.profile.ace_theme
)
return form return form
class GenerateKeyTextInput(TextInput): class GenerateKeyTextInput(TextInput):
def render(self, name, value, attrs=None, renderer=None): def render(self, name, value, attrs=None, renderer=None):
text = super(TextInput, self).render(name, value, attrs) text = super(TextInput, self).render(name, value, attrs)
return mark_safe(text + format_html( return mark_safe(
'''\ text
+ format_html(
"""\
<a href="#" onclick="return false;" class="button" id="id_{0}_regen">Regenerate</a> <a href="#" onclick="return false;" class="button" id="id_{0}_regen">Regenerate</a>
<script type="text/javascript"> <script type="text/javascript">
django.jQuery(document).ready(function ($) {{ django.jQuery(document).ready(function ($) {{
@ -65,37 +87,59 @@ django.jQuery(document).ready(function ($) {{
}}); }});
}}); }});
</script> </script>
''', name)) """,
name,
)
)
class JudgeAdminForm(ModelForm): class JudgeAdminForm(ModelForm):
class Meta: class Meta:
widgets = {'auth_key': GenerateKeyTextInput} widgets = {"auth_key": GenerateKeyTextInput}
if AdminPagedownWidget is not None: if AdminPagedownWidget is not None:
widgets['description'] = AdminPagedownWidget widgets["description"] = AdminPagedownWidget
class JudgeAdmin(VersionAdmin): class JudgeAdmin(VersionAdmin):
form = JudgeAdminForm form = JudgeAdminForm
readonly_fields = ('created', 'online', 'start_time', 'ping', 'load', 'last_ip', 'runtimes', 'problems') readonly_fields = (
fieldsets = ( "created",
(None, {'fields': ('name', 'auth_key', 'is_blocked')}), "online",
(_('Description'), {'fields': ('description',)}), "start_time",
(_('Information'), {'fields': ('created', 'online', 'last_ip', 'start_time', 'ping', 'load')}), "ping",
(_('Capabilities'), {'fields': ('runtimes', 'problems')}), "load",
"last_ip",
"runtimes",
"problems",
) )
list_display = ('name', 'online', 'start_time', 'ping', 'load', 'last_ip') fieldsets = (
ordering = ['-online', 'name'] (None, {"fields": ("name", "auth_key", "is_blocked")}),
(_("Description"), {"fields": ("description",)}),
(
_("Information"),
{"fields": ("created", "online", "last_ip", "start_time", "ping", "load")},
),
(_("Capabilities"), {"fields": ("runtimes", "problems")}),
)
list_display = ("name", "online", "start_time", "ping", "load", "last_ip")
ordering = ["-online", "name"]
def get_urls(self): def get_urls(self):
return ([url(r'^(\d+)/disconnect/$', self.disconnect_view, name='judge_judge_disconnect'), return [
url(r'^(\d+)/terminate/$', self.terminate_view, name='judge_judge_terminate')] + url(
super(JudgeAdmin, self).get_urls()) r"^(\d+)/disconnect/$",
self.disconnect_view,
name="judge_judge_disconnect",
),
url(
r"^(\d+)/terminate/$", self.terminate_view, name="judge_judge_terminate"
),
] + super(JudgeAdmin, self).get_urls()
def disconnect_judge(self, id, force=False): def disconnect_judge(self, id, force=False):
judge = get_object_or_404(Judge, id=id) judge = get_object_or_404(Judge, id=id)
judge.disconnect(force=force) judge.disconnect(force=force)
return HttpResponseRedirect(reverse('admin:judge_judge_changelist')) return HttpResponseRedirect(reverse("admin:judge_judge_changelist"))
def disconnect_view(self, request, id): def disconnect_view(self, request, id):
return self.disconnect_judge(id) return self.disconnect_judge(id)
@ -105,7 +149,7 @@ class JudgeAdmin(VersionAdmin):
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
if obj is not None and obj.online: if obj is not None and obj.online:
return self.readonly_fields + ('name',) return self.readonly_fields + ("name",)
return self.readonly_fields return self.readonly_fields
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
@ -116,5 +160,5 @@ class JudgeAdmin(VersionAdmin):
if AdminPagedownWidget is not None: if AdminPagedownWidget is not None:
formfield_overrides = { formfield_overrides = {
TextField: {'widget': AdminPagedownWidget}, TextField: {"widget": AdminPagedownWidget},
} }

View file

@ -13,239 +13,367 @@ from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _, pgettext, ungettext from django.utils.translation import gettext, gettext_lazy as _, pgettext, ungettext
from django_ace import AceWidget from django_ace import AceWidget
from judge.models import ContestParticipation, ContestProblem, ContestSubmission, Profile, Submission, \ from judge.models import (
SubmissionSource, SubmissionTestCase ContestParticipation,
ContestProblem,
ContestSubmission,
Profile,
Submission,
SubmissionSource,
SubmissionTestCase,
)
from judge.utils.raw_sql import use_straight_join from judge.utils.raw_sql import use_straight_join
class SubmissionStatusFilter(admin.SimpleListFilter): class SubmissionStatusFilter(admin.SimpleListFilter):
parameter_name = title = 'status' parameter_name = title = "status"
__lookups = (('None', _('None')), ('NotDone', _('Not done')), ('EX', _('Exceptional'))) + Submission.STATUS __lookups = (
("None", _("None")),
("NotDone", _("Not done")),
("EX", _("Exceptional")),
) + Submission.STATUS
__handles = set(map(itemgetter(0), Submission.STATUS)) __handles = set(map(itemgetter(0), Submission.STATUS))
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return self.__lookups return self.__lookups
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() == 'None': if self.value() == "None":
return queryset.filter(status=None) return queryset.filter(status=None)
elif self.value() == 'NotDone': elif self.value() == "NotDone":
return queryset.exclude(status__in=['D', 'IE', 'CE', 'AB']) return queryset.exclude(status__in=["D", "IE", "CE", "AB"])
elif self.value() == 'EX': elif self.value() == "EX":
return queryset.exclude(status__in=['D', 'CE', 'G', 'AB']) return queryset.exclude(status__in=["D", "CE", "G", "AB"])
elif self.value() in self.__handles: elif self.value() in self.__handles:
return queryset.filter(status=self.value()) return queryset.filter(status=self.value())
class SubmissionResultFilter(admin.SimpleListFilter): class SubmissionResultFilter(admin.SimpleListFilter):
parameter_name = title = 'result' parameter_name = title = "result"
__lookups = (('None', _('None')), ('BAD', _('Unaccepted'))) + Submission.RESULT __lookups = (("None", _("None")), ("BAD", _("Unaccepted"))) + Submission.RESULT
__handles = set(map(itemgetter(0), Submission.RESULT)) __handles = set(map(itemgetter(0), Submission.RESULT))
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return self.__lookups return self.__lookups
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() == 'None': if self.value() == "None":
return queryset.filter(result=None) return queryset.filter(result=None)
elif self.value() == 'BAD': elif self.value() == "BAD":
return queryset.exclude(result='AC') return queryset.exclude(result="AC")
elif self.value() in self.__handles: elif self.value() in self.__handles:
return queryset.filter(result=self.value()) return queryset.filter(result=self.value())
class SubmissionTestCaseInline(admin.TabularInline): class SubmissionTestCaseInline(admin.TabularInline):
fields = ('case', 'batch', 'status', 'time', 'memory', 'points', 'total') fields = ("case", "batch", "status", "time", "memory", "points", "total")
readonly_fields = ('case', 'batch', 'total') readonly_fields = ("case", "batch", "total")
model = SubmissionTestCase model = SubmissionTestCase
can_delete = False can_delete = False
max_num = 0 max_num = 0
class ContestSubmissionInline(admin.StackedInline): class ContestSubmissionInline(admin.StackedInline):
fields = ('problem', 'participation', 'points') fields = ("problem", "participation", "points")
model = ContestSubmission model = ContestSubmission
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj) kwargs["formfield_callback"] = partial(
self.formfield_for_dbfield, request=request, obj=obj
)
return super(ContestSubmissionInline, self).get_formset(request, obj, **kwargs) return super(ContestSubmissionInline, self).get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
submission = kwargs.pop('obj', None) submission = kwargs.pop("obj", None)
label = None label = None
if submission: if submission:
if db_field.name == 'participation': if db_field.name == "participation":
kwargs['queryset'] = ContestParticipation.objects.filter(user=submission.user, kwargs["queryset"] = ContestParticipation.objects.filter(
contest__problems=submission.problem) \ user=submission.user, contest__problems=submission.problem
.only('id', 'contest__name') ).only("id", "contest__name")
def label(obj): def label(obj):
return obj.contest.name return obj.contest.name
elif db_field.name == 'problem':
kwargs['queryset'] = ContestProblem.objects.filter(problem=submission.problem) \ elif db_field.name == "problem":
.only('id', 'problem__name', 'contest__name') kwargs["queryset"] = ContestProblem.objects.filter(
problem=submission.problem
).only("id", "problem__name", "contest__name")
def label(obj): def label(obj):
return pgettext('contest problem', '%(problem)s in %(contest)s') % { return pgettext("contest problem", "%(problem)s in %(contest)s") % {
'problem': obj.problem.name, 'contest': obj.contest.name, "problem": obj.problem.name,
"contest": obj.contest.name,
} }
field = super(ContestSubmissionInline, self).formfield_for_dbfield(db_field, **kwargs)
field = super(ContestSubmissionInline, self).formfield_for_dbfield(
db_field, **kwargs
)
if label is not None: if label is not None:
field.label_from_instance = label field.label_from_instance = label
return field return field
class SubmissionSourceInline(admin.StackedInline): class SubmissionSourceInline(admin.StackedInline):
fields = ('source',) fields = ("source",)
model = SubmissionSource model = SubmissionSource
can_delete = False can_delete = False
extra = 0 extra = 0
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
kwargs.setdefault('widgets', {})['source'] = AceWidget(mode=obj and obj.language.ace, kwargs.setdefault("widgets", {})["source"] = AceWidget(
theme=request.profile.ace_theme) mode=obj and obj.language.ace, theme=request.profile.ace_theme
)
return super().get_formset(request, obj, **kwargs) return super().get_formset(request, obj, **kwargs)
class SubmissionAdmin(admin.ModelAdmin): class SubmissionAdmin(admin.ModelAdmin):
readonly_fields = ('user', 'problem', 'date', 'judged_date') readonly_fields = ("user", "problem", "date", "judged_date")
fields = ('user', 'problem', 'date', 'judged_date', 'time', 'memory', 'points', 'language', 'status', 'result', fields = (
'case_points', 'case_total', 'judged_on', 'error') "user",
actions = ('judge', 'recalculate_score') "problem",
list_display = ('id', 'problem_code', 'problem_name', 'user_column', 'execution_time', 'pretty_memory', "date",
'points', 'language_column', 'status', 'result', 'judge_column') "judged_date",
list_filter = ('language', SubmissionStatusFilter, SubmissionResultFilter) "time",
search_fields = ('problem__code', 'problem__name', 'user__user__username') "memory",
"points",
"language",
"status",
"result",
"case_points",
"case_total",
"judged_on",
"error",
)
actions = ("judge", "recalculate_score")
list_display = (
"id",
"problem_code",
"problem_name",
"user_column",
"execution_time",
"pretty_memory",
"points",
"language_column",
"status",
"result",
"judge_column",
)
list_filter = ("language", SubmissionStatusFilter, SubmissionResultFilter)
search_fields = ("problem__code", "problem__name", "user__user__username")
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
inlines = [SubmissionSourceInline, SubmissionTestCaseInline, ContestSubmissionInline] inlines = [
SubmissionSourceInline,
SubmissionTestCaseInline,
ContestSubmissionInline,
]
def get_queryset(self, request): def get_queryset(self, request):
queryset = Submission.objects.select_related('problem', 'user__user', 'language').only( queryset = Submission.objects.select_related(
'problem__code', 'problem__name', 'user__user__username', 'language__name', "problem", "user__user", "language"
'time', 'memory', 'points', 'status', 'result', ).only(
"problem__code",
"problem__name",
"user__user__username",
"language__name",
"time",
"memory",
"points",
"status",
"result",
) )
use_straight_join(queryset) use_straight_join(queryset)
if not request.user.has_perm('judge.edit_all_problem'): if not request.user.has_perm("judge.edit_all_problem"):
id = request.profile.id id = request.profile.id
queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id)).distinct() queryset = queryset.filter(
Q(problem__authors__id=id) | Q(problem__curators__id=id)
).distinct()
return queryset return queryset
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
if not request.user.has_perm('judge.edit_own_problem'): if not request.user.has_perm("judge.edit_own_problem"):
return False return False
if request.user.has_perm('judge.edit_all_problem') or obj is None: if request.user.has_perm("judge.edit_all_problem") or obj is None:
return True return True
return obj.problem.is_editor(request.profile) return obj.problem.is_editor(request.profile)
def lookup_allowed(self, key, value): def lookup_allowed(self, key, value):
return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in ('problem__code',) return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in (
"problem__code",
)
def judge(self, request, queryset): def judge(self, request, queryset):
if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'): if not request.user.has_perm(
self.message_user(request, gettext('You do not have the permission to rejudge submissions.'), "judge.rejudge_submission"
level=messages.ERROR) ) or not request.user.has_perm("judge.edit_own_problem"):
self.message_user(
request,
gettext("You do not have the permission to rejudge submissions."),
level=messages.ERROR,
)
return return
queryset = queryset.order_by('id') queryset = queryset.order_by("id")
if not request.user.has_perm('judge.rejudge_submission_lot') and \ if (
queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT: not request.user.has_perm("judge.rejudge_submission_lot")
self.message_user(request, gettext('You do not have the permission to rejudge THAT many submissions.'), and queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT
level=messages.ERROR) ):
self.message_user(
request,
gettext(
"You do not have the permission to rejudge THAT many submissions."
),
level=messages.ERROR,
)
return return
if not request.user.has_perm('judge.edit_all_problem'): if not request.user.has_perm("judge.edit_all_problem"):
id = request.profile.id id = request.profile.id
queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id)) queryset = queryset.filter(
Q(problem__authors__id=id) | Q(problem__curators__id=id)
)
judged = len(queryset) judged = len(queryset)
for model in queryset: for model in queryset:
model.judge(rejudge=True, batch_rejudge=True) model.judge(rejudge=True, batch_rejudge=True)
self.message_user(request, ungettext('%d submission was successfully scheduled for rejudging.', self.message_user(
'%d submissions were successfully scheduled for rejudging.', request,
judged) % judged) ungettext(
judge.short_description = _('Rejudge the selected submissions') "%d submission was successfully scheduled for rejudging.",
"%d submissions were successfully scheduled for rejudging.",
judged,
)
% judged,
)
judge.short_description = _("Rejudge the selected submissions")
def recalculate_score(self, request, queryset): def recalculate_score(self, request, queryset):
if not request.user.has_perm('judge.rejudge_submission'): if not request.user.has_perm("judge.rejudge_submission"):
self.message_user(request, gettext('You do not have the permission to rejudge submissions.'), self.message_user(
level=messages.ERROR) request,
gettext("You do not have the permission to rejudge submissions."),
level=messages.ERROR,
)
return return
submissions = list(queryset.defer(None).select_related(None).select_related('problem') submissions = list(
.only('points', 'case_points', 'case_total', 'problem__partial', 'problem__points')) queryset.defer(None)
.select_related(None)
.select_related("problem")
.only(
"points",
"case_points",
"case_total",
"problem__partial",
"problem__points",
)
)
for submission in submissions: for submission in submissions:
submission.points = round(submission.case_points / submission.case_total * submission.problem.points submission.points = round(
if submission.case_total else 0, 1) submission.case_points
if not submission.problem.partial and submission.points < submission.problem.points: / submission.case_total
* submission.problem.points
if submission.case_total
else 0,
1,
)
if (
not submission.problem.partial
and submission.points < submission.problem.points
):
submission.points = 0 submission.points = 0
submission.save() submission.save()
submission.update_contest() submission.update_contest()
for profile in Profile.objects.filter(id__in=queryset.values_list('user_id', flat=True).distinct()): for profile in Profile.objects.filter(
id__in=queryset.values_list("user_id", flat=True).distinct()
):
profile.calculate_points() profile.calculate_points()
cache.delete('user_complete:%d' % profile.id) cache.delete("user_complete:%d" % profile.id)
cache.delete('user_attempted:%d' % profile.id) cache.delete("user_attempted:%d" % profile.id)
for participation in ContestParticipation.objects.filter( for participation in ContestParticipation.objects.filter(
id__in=queryset.values_list('contest__participation_id')).prefetch_related('contest'): id__in=queryset.values_list("contest__participation_id")
).prefetch_related("contest"):
participation.recompute_results() participation.recompute_results()
self.message_user(request, ungettext('%d submission were successfully rescored.', self.message_user(
'%d submissions were successfully rescored.', request,
len(submissions)) % len(submissions)) ungettext(
recalculate_score.short_description = _('Rescore the selected submissions') "%d submission were successfully rescored.",
"%d submissions were successfully rescored.",
len(submissions),
)
% len(submissions),
)
recalculate_score.short_description = _("Rescore the selected submissions")
def problem_code(self, obj): def problem_code(self, obj):
return obj.problem.code return obj.problem.code
problem_code.short_description = _('Problem code')
problem_code.admin_order_field = 'problem__code' problem_code.short_description = _("Problem code")
problem_code.admin_order_field = "problem__code"
def problem_name(self, obj): def problem_name(self, obj):
return obj.problem.name return obj.problem.name
problem_name.short_description = _('Problem name')
problem_name.admin_order_field = 'problem__name' problem_name.short_description = _("Problem name")
problem_name.admin_order_field = "problem__name"
def user_column(self, obj): def user_column(self, obj):
return obj.user.user.username return obj.user.user.username
user_column.admin_order_field = 'user__user__username'
user_column.short_description = _('User') user_column.admin_order_field = "user__user__username"
user_column.short_description = _("User")
def execution_time(self, obj): def execution_time(self, obj):
return round(obj.time, 2) if obj.time is not None else 'None' return round(obj.time, 2) if obj.time is not None else "None"
execution_time.short_description = _('Time')
execution_time.admin_order_field = 'time' execution_time.short_description = _("Time")
execution_time.admin_order_field = "time"
def pretty_memory(self, obj): def pretty_memory(self, obj):
memory = obj.memory memory = obj.memory
if memory is None: if memory is None:
return gettext('None') return gettext("None")
if memory < 1000: if memory < 1000:
return gettext('%d KB') % memory return gettext("%d KB") % memory
else: else:
return gettext('%.2f MB') % (memory / 1024) return gettext("%.2f MB") % (memory / 1024)
pretty_memory.admin_order_field = 'memory'
pretty_memory.short_description = _('Memory') pretty_memory.admin_order_field = "memory"
pretty_memory.short_description = _("Memory")
def language_column(self, obj): def language_column(self, obj):
return obj.language.name return obj.language.name
language_column.admin_order_field = 'language__name'
language_column.short_description = _('Language') language_column.admin_order_field = "language__name"
language_column.short_description = _("Language")
def judge_column(self, obj): def judge_column(self, obj):
return format_html('<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />', obj.id) return format_html(
judge_column.short_description = '' '<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />',
obj.id,
)
judge_column.short_description = ""
def get_urls(self): def get_urls(self):
return [ return [
url(r'^(\d+)/judge/$', self.judge_view, name='judge_submission_rejudge'), url(r"^(\d+)/judge/$", self.judge_view, name="judge_submission_rejudge"),
] + super(SubmissionAdmin, self).get_urls() ] + super(SubmissionAdmin, self).get_urls()
def judge_view(self, request, id): def judge_view(self, request, id):
if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'): if not request.user.has_perm(
"judge.rejudge_submission"
) or not request.user.has_perm("judge.edit_own_problem"):
raise PermissionDenied() raise PermissionDenied()
submission = get_object_or_404(Submission, id=id) submission = get_object_or_404(Submission, id=id)
if not request.user.has_perm('judge.edit_all_problem') and \ if not request.user.has_perm(
not submission.problem.is_editor(request.profile): "judge.edit_all_problem"
) and not submission.problem.is_editor(request.profile):
raise PermissionDenied() raise PermissionDenied()
submission.judge(rejudge=True) submission.judge(rejudge=True)
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))

View file

@ -8,45 +8,51 @@ from judge.widgets import AdminHeavySelect2MultipleWidget
class ProblemGroupForm(ModelForm): class ProblemGroupForm(ModelForm):
problems = ModelMultipleChoiceField( problems = ModelMultipleChoiceField(
label=_('Included problems'), label=_("Included problems"),
queryset=Problem.objects.all(), queryset=Problem.objects.all(),
required=False, required=False,
help_text=_('These problems are included in this group of problems'), help_text=_("These problems are included in this group of problems"),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2')) widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class ProblemGroupAdmin(admin.ModelAdmin): class ProblemGroupAdmin(admin.ModelAdmin):
fields = ('name', 'full_name', 'problems') fields = ("name", "full_name", "problems")
form = ProblemGroupForm form = ProblemGroupForm
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(ProblemGroupAdmin, self).save_model(request, obj, form, change) super(ProblemGroupAdmin, self).save_model(request, obj, form, change)
obj.problem_set.set(form.cleaned_data['problems']) obj.problem_set.set(form.cleaned_data["problems"])
obj.save() obj.save()
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else [] self.form.base_fields["problems"].initial = (
[o.pk for o in obj.problem_set.all()] if obj else []
)
return super(ProblemGroupAdmin, self).get_form(request, obj, **kwargs) return super(ProblemGroupAdmin, self).get_form(request, obj, **kwargs)
class ProblemTypeForm(ModelForm): class ProblemTypeForm(ModelForm):
problems = ModelMultipleChoiceField( problems = ModelMultipleChoiceField(
label=_('Included problems'), label=_("Included problems"),
queryset=Problem.objects.all(), queryset=Problem.objects.all(),
required=False, required=False,
help_text=_('These problems are included in this type of problems'), help_text=_("These problems are included in this type of problems"),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2')) widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class ProblemTypeAdmin(admin.ModelAdmin): class ProblemTypeAdmin(admin.ModelAdmin):
fields = ('name', 'full_name', 'problems') fields = ("name", "full_name", "problems")
form = ProblemTypeForm form = ProblemTypeForm
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super(ProblemTypeAdmin, self).save_model(request, obj, form, change) super(ProblemTypeAdmin, self).save_model(request, obj, form, change)
obj.problem_set.set(form.cleaned_data['problems']) obj.problem_set.set(form.cleaned_data["problems"])
obj.save() obj.save()
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else [] self.form.base_fields["problems"].initial = (
[o.pk for o in obj.problem_set.all()] if obj else []
)
return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs) return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs)

View file

@ -4,36 +4,56 @@ from django.forms import ModelForm
from django.urls import reverse_lazy from django.urls import reverse_lazy
from judge.models import TicketMessage from judge.models import TicketMessage
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class TicketMessageForm(ModelForm): class TicketMessageForm(ModelForm):
class Meta: class Meta:
widgets = { widgets = {
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "user": AdminHeavySelect2Widget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
} }
if HeavyPreviewAdminPageDownWidget is not None: if HeavyPreviewAdminPageDownWidget is not None:
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('ticket_preview')) widgets["body"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("ticket_preview")
)
class TicketMessageInline(StackedInline): class TicketMessageInline(StackedInline):
model = TicketMessage model = TicketMessage
form = TicketMessageForm form = TicketMessageForm
fields = ('user', 'body') fields = ("user", "body")
class TicketForm(ModelForm): class TicketForm(ModelForm):
class Meta: class Meta:
widgets = { widgets = {
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}), "user": AdminHeavySelect2Widget(
'assignees': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}), data_view="profile_select2", attrs={"style": "width: 100%"}
),
"assignees": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
} }
class TicketAdmin(ModelAdmin): class TicketAdmin(ModelAdmin):
fields = ('title', 'time', 'user', 'assignees', 'content_type', 'object_id', 'notes') fields = (
readonly_fields = ('time',) "title",
list_display = ('title', 'user', 'time', 'linked_item') "time",
"user",
"assignees",
"content_type",
"object_id",
"notes",
)
readonly_fields = ("time",)
list_display = ("title", "user", "time", "linked_item")
inlines = [TicketMessageInline] inlines = [TicketMessageInline]
form = TicketForm form = TicketForm
date_hierarchy = 'time' date_hierarchy = "time"

View file

@ -5,14 +5,30 @@ from django.utils.translation import gettext, gettext_lazy as _, ungettext
from judge.models import VolunteerProblemVote from judge.models import VolunteerProblemVote
class VolunteerProblemVoteAdmin(admin.ModelAdmin): class VolunteerProblemVoteAdmin(admin.ModelAdmin):
fields = ('voter', 'problem', 'time', 'thinking_points', 'knowledge_points', 'feedback') fields = (
readonly_fields = ('time', 'problem', 'voter') "voter",
list_display = ('voter', 'problem_link', 'time', 'thinking_points', 'knowledge_points', 'feedback') "problem",
date_hierarchy = 'time' "time",
"thinking_points",
"knowledge_points",
"feedback",
)
readonly_fields = ("time", "problem", "voter")
list_display = (
"voter",
"problem_link",
"time",
"thinking_points",
"knowledge_points",
"feedback",
)
date_hierarchy = "time"
def problem_link(self, obj): def problem_link(self, obj):
url = reverse('admin:judge_problem_change', args=(obj.problem.id,)) url = reverse("admin:judge_problem_change", args=(obj.problem.id,))
return format_html(f"<a href='{url}'>{obj.problem.code}</a>") return format_html(f"<a href='{url}'>{obj.problem.code}</a>")
problem_link.short_description = _('Problem')
problem_link.admin_order_field = 'problem__code' problem_link.short_description = _("Problem")
problem_link.admin_order_field = "problem__code"

View file

@ -4,8 +4,8 @@ from django.utils.translation import gettext_lazy
class JudgeAppConfig(AppConfig): class JudgeAppConfig(AppConfig):
name = 'judge' name = "judge"
verbose_name = gettext_lazy('Online Judge') verbose_name = gettext_lazy("Online Judge")
def ready(self): def ready(self):
# WARNING: AS THIS IS NOT A FUNCTIONAL PROGRAMMING LANGUAGE, # WARNING: AS THIS IS NOT A FUNCTIONAL PROGRAMMING LANGUAGE,

View file

@ -8,9 +8,9 @@ from netaddr import IPGlob, IPSet
from judge.utils.unicode import utf8text from judge.utils.unicode import utf8text
logger = logging.getLogger('judge.bridge') logger = logging.getLogger("judge.bridge")
size_pack = struct.Struct('!I') size_pack = struct.Struct("!I")
assert size_pack.size == 4 assert size_pack.size == 4
MAX_ALLOWED_PACKET_SIZE = 8 * 1024 * 1024 MAX_ALLOWED_PACKET_SIZE = 8 * 1024 * 1024
@ -20,7 +20,7 @@ def proxy_list(human_readable):
globs = [] globs = []
addrs = [] addrs = []
for item in human_readable: for item in human_readable:
if '*' in item or '-' in item: if "*" in item or "-" in item:
globs.append(IPGlob(item)) globs.append(IPGlob(item))
else: else:
addrs.append(item) addrs.append(item)
@ -43,7 +43,7 @@ class RequestHandlerMeta(type):
try: try:
handler.handle() handler.handle()
except BaseException: except BaseException:
logger.exception('Error in base packet handling') logger.exception("Error in base packet handling")
raise raise
finally: finally:
handler.on_disconnect() handler.on_disconnect()
@ -70,8 +70,12 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
def read_sized_packet(self, size, initial=None): def read_sized_packet(self, size, initial=None):
if size > MAX_ALLOWED_PACKET_SIZE: if size > MAX_ALLOWED_PACKET_SIZE:
logger.log(logging.WARNING if self._got_packet else logging.INFO, logger.log(
'Disconnecting client due to too-large message size (%d bytes): %s', size, self.client_address) logging.WARNING if self._got_packet else logging.INFO,
"Disconnecting client due to too-large message size (%d bytes): %s",
size,
self.client_address,
)
raise Disconnect() raise Disconnect()
buffer = [] buffer = []
@ -86,7 +90,7 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
data = self.request.recv(remainder) data = self.request.recv(remainder)
remainder -= len(data) remainder -= len(data)
buffer.append(data) buffer.append(data)
self._on_packet(b''.join(buffer)) self._on_packet(b"".join(buffer))
def parse_proxy_protocol(self, line): def parse_proxy_protocol(self, line):
words = line.split() words = line.split()
@ -94,18 +98,18 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
if len(words) < 2: if len(words) < 2:
raise Disconnect() raise Disconnect()
if words[1] == b'TCP4': if words[1] == b"TCP4":
if len(words) != 6: if len(words) != 6:
raise Disconnect() raise Disconnect()
self.client_address = (utf8text(words[2]), utf8text(words[4])) self.client_address = (utf8text(words[2]), utf8text(words[4]))
self.server_address = (utf8text(words[3]), utf8text(words[5])) self.server_address = (utf8text(words[3]), utf8text(words[5]))
elif words[1] == b'TCP6': elif words[1] == b"TCP6":
self.client_address = (utf8text(words[2]), utf8text(words[4]), 0, 0) self.client_address = (utf8text(words[2]), utf8text(words[4]), 0, 0)
self.server_address = (utf8text(words[3]), utf8text(words[5]), 0, 0) self.server_address = (utf8text(words[3]), utf8text(words[5]), 0, 0)
elif words[1] != b'UNKNOWN': elif words[1] != b"UNKNOWN":
raise Disconnect() raise Disconnect()
def read_size(self, buffer=b''): def read_size(self, buffer=b""):
while len(buffer) < size_pack.size: while len(buffer) < size_pack.size:
recv = self.request.recv(size_pack.size - len(buffer)) recv = self.request.recv(size_pack.size - len(buffer))
if not recv: if not recv:
@ -113,9 +117,9 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
buffer += recv buffer += recv
return size_pack.unpack(buffer)[0] return size_pack.unpack(buffer)[0]
def read_proxy_header(self, buffer=b''): def read_proxy_header(self, buffer=b""):
# Max line length for PROXY protocol is 107, and we received 4 already. # Max line length for PROXY protocol is 107, and we received 4 already.
while b'\r\n' not in buffer: while b"\r\n" not in buffer:
if len(buffer) > 107: if len(buffer) > 107:
raise Disconnect() raise Disconnect()
data = self.request.recv(107) data = self.request.recv(107)
@ -125,7 +129,7 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
return buffer return buffer
def _on_packet(self, data): def _on_packet(self, data):
decompressed = zlib.decompress(data).decode('utf-8') decompressed = zlib.decompress(data).decode("utf-8")
self._got_packet = True self._got_packet = True
self.on_packet(decompressed) self.on_packet(decompressed)
@ -145,8 +149,10 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
try: try:
tag = self.read_size() tag = self.read_size()
self._initial_tag = size_pack.pack(tag) self._initial_tag = size_pack.pack(tag)
if self.client_address[0] in self.proxies and self._initial_tag == b'PROX': if self.client_address[0] in self.proxies and self._initial_tag == b"PROX":
proxy, _, remainder = self.read_proxy_header(self._initial_tag).partition(b'\r\n') proxy, _, remainder = self.read_proxy_header(
self._initial_tag
).partition(b"\r\n")
self.parse_proxy_protocol(proxy) self.parse_proxy_protocol(proxy)
while remainder: while remainder:
@ -154,8 +160,8 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
self.read_sized_packet(self.read_size(remainder)) self.read_sized_packet(self.read_size(remainder))
break break
size = size_pack.unpack(remainder[:size_pack.size])[0] size = size_pack.unpack(remainder[: size_pack.size])[0]
remainder = remainder[size_pack.size:] remainder = remainder[size_pack.size :]
if len(remainder) <= size: if len(remainder) <= size:
self.read_sized_packet(size, remainder) self.read_sized_packet(size, remainder)
break break
@ -171,25 +177,36 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
return return
except zlib.error: except zlib.error:
if self._got_packet: if self._got_packet:
logger.warning('Encountered zlib error during packet handling, disconnecting client: %s', logger.warning(
self.client_address, exc_info=True) "Encountered zlib error during packet handling, disconnecting client: %s",
self.client_address,
exc_info=True,
)
else: else:
logger.info('Potentially wrong protocol (zlib error): %s: %r', self.client_address, self._initial_tag, logger.info(
exc_info=True) "Potentially wrong protocol (zlib error): %s: %r",
self.client_address,
self._initial_tag,
exc_info=True,
)
except socket.timeout: except socket.timeout:
if self._got_packet: if self._got_packet:
logger.info('Socket timed out: %s', self.client_address) logger.info("Socket timed out: %s", self.client_address)
self.on_timeout() self.on_timeout()
else: else:
logger.info('Potentially wrong protocol: %s: %r', self.client_address, self._initial_tag) logger.info(
"Potentially wrong protocol: %s: %r",
self.client_address,
self._initial_tag,
)
except socket.error as e: except socket.error as e:
# When a gevent socket is shutdown, gevent cancels all waits, causing recv to raise cancel_wait_ex. # When a gevent socket is shutdown, gevent cancels all waits, causing recv to raise cancel_wait_ex.
if e.__class__.__name__ == 'cancel_wait_ex': if e.__class__.__name__ == "cancel_wait_ex":
return return
raise raise
def send(self, data): def send(self, data):
compressed = zlib.compress(data.encode('utf-8')) compressed = zlib.compress(data.encode("utf-8"))
self.request.sendall(size_pack.pack(len(compressed)) + compressed) self.request.sendall(size_pack.pack(len(compressed)) + compressed)
def close(self): def close(self):

View file

@ -11,7 +11,7 @@ from judge.bridge.judge_list import JudgeList
from judge.bridge.server import Server from judge.bridge.server import Server
from judge.models import Judge, Submission from judge.models import Judge, Submission
logger = logging.getLogger('judge.bridge') logger = logging.getLogger("judge.bridge")
def reset_judges(): def reset_judges():
@ -20,12 +20,17 @@ def reset_judges():
def judge_daemon(): def judge_daemon():
reset_judges() reset_judges()
Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS) \ Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS).update(
.update(status='IE', result='IE', error=None) status="IE", result="IE", error=None
)
judges = JudgeList() judges = JudgeList()
judge_server = Server(settings.BRIDGED_JUDGE_ADDRESS, partial(JudgeHandler, judges=judges)) judge_server = Server(
django_server = Server(settings.BRIDGED_DJANGO_ADDRESS, partial(DjangoHandler, judges=judges)) settings.BRIDGED_JUDGE_ADDRESS, partial(JudgeHandler, judges=judges)
)
django_server = Server(
settings.BRIDGED_DJANGO_ADDRESS, partial(DjangoHandler, judges=judges)
)
threading.Thread(target=django_server.serve_forever).start() threading.Thread(target=django_server.serve_forever).start()
threading.Thread(target=judge_server.serve_forever).start() threading.Thread(target=judge_server.serve_forever).start()
@ -33,7 +38,7 @@ def judge_daemon():
stop = threading.Event() stop = threading.Event()
def signal_handler(signum, _): def signal_handler(signum, _):
logger.info('Exiting due to %s', signal.Signals(signum).name) logger.info("Exiting due to %s", signal.Signals(signum).name)
stop.set() stop.set()
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)

View file

@ -4,8 +4,8 @@ import struct
from judge.bridge.base_handler import Disconnect, ZlibPacketHandler from judge.bridge.base_handler import Disconnect, ZlibPacketHandler
logger = logging.getLogger('judge.bridge') logger = logging.getLogger("judge.bridge")
size_pack = struct.Struct('!I') size_pack = struct.Struct("!I")
class DjangoHandler(ZlibPacketHandler): class DjangoHandler(ZlibPacketHandler):
@ -13,47 +13,52 @@ class DjangoHandler(ZlibPacketHandler):
super().__init__(request, client_address, server) super().__init__(request, client_address, server)
self.handlers = { self.handlers = {
'submission-request': self.on_submission, "submission-request": self.on_submission,
'terminate-submission': self.on_termination, "terminate-submission": self.on_termination,
'disconnect-judge': self.on_disconnect_request, "disconnect-judge": self.on_disconnect_request,
} }
self.judges = judges self.judges = judges
def send(self, data): def send(self, data):
super().send(json.dumps(data, separators=(',', ':'))) super().send(json.dumps(data, separators=(",", ":")))
def on_packet(self, packet): def on_packet(self, packet):
packet = json.loads(packet) packet = json.loads(packet)
try: try:
result = self.handlers.get(packet.get('name', None), self.on_malformed)(packet) result = self.handlers.get(packet.get("name", None), self.on_malformed)(
packet
)
except Exception: except Exception:
logger.exception('Error in packet handling (Django-facing)') logger.exception("Error in packet handling (Django-facing)")
result = {'name': 'bad-request'} result = {"name": "bad-request"}
self.send(result) self.send(result)
raise Disconnect() raise Disconnect()
def on_submission(self, data): def on_submission(self, data):
id = data['submission-id'] id = data["submission-id"]
problem = data['problem-id'] problem = data["problem-id"]
language = data['language'] language = data["language"]
source = data['source'] source = data["source"]
judge_id = data['judge-id'] judge_id = data["judge-id"]
priority = data['priority'] priority = data["priority"]
if not self.judges.check_priority(priority): if not self.judges.check_priority(priority):
return {'name': 'bad-request'} return {"name": "bad-request"}
self.judges.judge(id, problem, language, source, judge_id, priority) self.judges.judge(id, problem, language, source, judge_id, priority)
return {'name': 'submission-received', 'submission-id': id} return {"name": "submission-received", "submission-id": id}
def on_termination(self, data): def on_termination(self, data):
return {'name': 'submission-received', 'judge-aborted': self.judges.abort(data['submission-id'])} return {
"name": "submission-received",
"judge-aborted": self.judges.abort(data["submission-id"]),
}
def on_disconnect_request(self, data): def on_disconnect_request(self, data):
judge_id = data['judge-id'] judge_id = data["judge-id"]
force = data['force'] force = data["force"]
self.judges.disconnect(judge_id, force=force) self.judges.disconnect(judge_id, force=force)
def on_malformed(self, packet): def on_malformed(self, packet):
logger.error('Malformed packet: %s', packet) logger.error("Malformed packet: %s", packet)
def on_close(self): def on_close(self):
self._to_kill = False self._to_kill = False

View file

@ -4,7 +4,7 @@ import struct
import time import time
import zlib import zlib
size_pack = struct.Struct('!I') size_pack = struct.Struct("!I")
def open_connection(): def open_connection():
@ -13,69 +13,70 @@ def open_connection():
def zlibify(data): def zlibify(data):
data = zlib.compress(data.encode('utf-8')) data = zlib.compress(data.encode("utf-8"))
return size_pack.pack(len(data)) + data return size_pack.pack(len(data)) + data
def dezlibify(data, skip_head=True): def dezlibify(data, skip_head=True):
if skip_head: if skip_head:
data = data[size_pack.size:] data = data[size_pack.size :]
return zlib.decompress(data).decode('utf-8') return zlib.decompress(data).decode("utf-8")
def main(): def main():
global host, port global host, port
import argparse import argparse
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-l', '--host', default='localhost') parser.add_argument("-l", "--host", default="localhost")
parser.add_argument('-p', '--port', default=9999, type=int) parser.add_argument("-p", "--port", default=9999, type=int)
args = parser.parse_args() args = parser.parse_args()
host, port = args.host, args.port host, port = args.host, args.port
print('Opening idle connection:', end=' ') print("Opening idle connection:", end=" ")
s1 = open_connection() s1 = open_connection()
print('Success') print("Success")
print('Opening hello world connection:', end=' ') print("Opening hello world connection:", end=" ")
s2 = open_connection() s2 = open_connection()
print('Success') print("Success")
print('Sending Hello, World!', end=' ') print("Sending Hello, World!", end=" ")
s2.sendall(zlibify('Hello, World!')) s2.sendall(zlibify("Hello, World!"))
print('Success') print("Success")
print('Testing blank connection:', end=' ') print("Testing blank connection:", end=" ")
s3 = open_connection() s3 = open_connection()
s3.close() s3.close()
print('Success') print("Success")
result = dezlibify(s2.recv(1024)) result = dezlibify(s2.recv(1024))
assert result == 'Hello, World!' assert result == "Hello, World!"
print(result) print(result)
s2.close() s2.close()
print('Large random data test:', end=' ') print("Large random data test:", end=" ")
s4 = open_connection() s4 = open_connection()
data = os.urandom(1000000).decode('iso-8859-1') data = os.urandom(1000000).decode("iso-8859-1")
print('Generated', end=' ') print("Generated", end=" ")
s4.sendall(zlibify(data)) s4.sendall(zlibify(data))
print('Sent', end=' ') print("Sent", end=" ")
result = b'' result = b""
while len(result) < size_pack.size: while len(result) < size_pack.size:
result += s4.recv(1024) result += s4.recv(1024)
size = size_pack.unpack(result[:size_pack.size])[0] size = size_pack.unpack(result[: size_pack.size])[0]
result = result[size_pack.size:] result = result[size_pack.size :]
while len(result) < size: while len(result) < size:
result += s4.recv(1024) result += s4.recv(1024)
print('Received', end=' ') print("Received", end=" ")
assert dezlibify(result, False) == data assert dezlibify(result, False) == data
print('Success') print("Success")
s4.close() s4.close()
print('Test malformed connection:', end=' ') print("Test malformed connection:", end=" ")
s5 = open_connection() s5 = open_connection()
s5.sendall(data[:100000].encode('utf-8')) s5.sendall(data[:100000].encode("utf-8"))
s5.close() s5.close()
print('Success') print("Success")
print('Waiting for timeout to close idle connection:', end=' ') print("Waiting for timeout to close idle connection:", end=" ")
time.sleep(6) time.sleep(6)
print('Done') print("Done")
s1.close() s1.close()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View file

@ -3,19 +3,22 @@ from judge.bridge.base_handler import ZlibPacketHandler
class EchoPacketHandler(ZlibPacketHandler): class EchoPacketHandler(ZlibPacketHandler):
def on_connect(self): def on_connect(self):
print('New client:', self.client_address) print("New client:", self.client_address)
self.timeout = 5 self.timeout = 5
def on_timeout(self): def on_timeout(self):
print('Inactive client:', self.client_address) print("Inactive client:", self.client_address)
def on_packet(self, data): def on_packet(self, data):
self.timeout = None self.timeout = None
print('Data from %s: %r' % (self.client_address, data[:30] if len(data) > 30 else data)) print(
"Data from %s: %r"
% (self.client_address, data[:30] if len(data) > 30 else data)
)
self.send(data) self.send(data)
def on_disconnect(self): def on_disconnect(self):
print('Closed client:', self.client_address) print("Closed client:", self.client_address)
def main(): def main():
@ -23,9 +26,9 @@ def main():
from judge.bridge.server import Server from judge.bridge.server import Server
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-l', '--host', action='append') parser.add_argument("-l", "--host", action="append")
parser.add_argument('-p', '--port', type=int, action='append') parser.add_argument("-p", "--port", type=int, action="append")
parser.add_argument('-P', '--proxy', action='append') parser.add_argument("-P", "--proxy", action="append")
args = parser.parse_args() args = parser.parse_args()
class Handler(EchoPacketHandler): class Handler(EchoPacketHandler):
@ -35,5 +38,5 @@ def main():
server.serve_forever() server.serve_forever()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ try:
except ImportError: except ImportError:
from pyllist import dllist from pyllist import dllist
logger = logging.getLogger('judge.bridge') logger = logging.getLogger("judge.bridge")
PriorityMarker = namedtuple('PriorityMarker', 'priority') PriorityMarker = namedtuple("PriorityMarker", "priority")
class JudgeList(object): class JudgeList(object):
@ -18,7 +18,9 @@ class JudgeList(object):
def __init__(self): def __init__(self):
self.queue = dllist() self.queue = dllist()
self.priority = [self.queue.append(PriorityMarker(i)) for i in range(self.priorities)] self.priority = [
self.queue.append(PriorityMarker(i)) for i in range(self.priorities)
]
self.judges = set() self.judges = set()
self.node_map = {} self.node_map = {}
self.submission_map = {} self.submission_map = {}
@ -32,11 +34,19 @@ class JudgeList(object):
id, problem, language, source, judge_id = node.value id, problem, language, source, judge_id = node.value
if judge.can_judge(problem, language, judge_id): if judge.can_judge(problem, language, judge_id):
self.submission_map[id] = judge self.submission_map[id] = judge
logger.info('Dispatched queued submission %d: %s', id, judge.name) logger.info(
"Dispatched queued submission %d: %s", id, judge.name
)
try: try:
judge.submit(id, problem, language, source) judge.submit(id, problem, language, source)
except Exception: except Exception:
logger.exception('Failed to dispatch %d (%s, %s) to %s', id, problem, language, judge.name) logger.exception(
"Failed to dispatch %d (%s, %s) to %s",
id,
problem,
language,
judge.name,
)
self.judges.remove(judge) self.judges.remove(judge)
return return
self.queue.remove(node) self.queue.remove(node)
@ -76,14 +86,14 @@ class JudgeList(object):
def on_judge_free(self, judge, submission): def on_judge_free(self, judge, submission):
with self.lock: with self.lock:
logger.info('Judge available after grading %d: %s', submission, judge.name) logger.info("Judge available after grading %d: %s", submission, judge.name)
del self.submission_map[submission] del self.submission_map[submission]
judge._working = False judge._working = False
self._handle_free_judge(judge) self._handle_free_judge(judge)
def abort(self, submission): def abort(self, submission):
with self.lock: with self.lock:
logger.info('Abort request: %d', submission) logger.info("Abort request: %d", submission)
try: try:
self.submission_map[submission].abort() self.submission_map[submission].abort()
return True return True
@ -108,21 +118,33 @@ class JudgeList(object):
return return
candidates = [ candidates = [
judge for judge in self.judges if not judge.working and judge.can_judge(problem, language, judge_id) judge
for judge in self.judges
if not judge.working and judge.can_judge(problem, language, judge_id)
] ]
if judge_id: if judge_id:
logger.info('Specified judge %s is%savailable', judge_id, ' ' if candidates else ' not ') logger.info(
"Specified judge %s is%savailable",
judge_id,
" " if candidates else " not ",
)
else: else:
logger.info('Free judges: %d', len(candidates)) logger.info("Free judges: %d", len(candidates))
if candidates: if candidates:
# Schedule the submission on the judge reporting least load. # Schedule the submission on the judge reporting least load.
judge = min(candidates, key=attrgetter('load')) judge = min(candidates, key=attrgetter("load"))
logger.info('Dispatched submission %d to: %s', id, judge.name) logger.info("Dispatched submission %d to: %s", id, judge.name)
self.submission_map[id] = judge self.submission_map[id] = judge
try: try:
judge.submit(id, problem, language, source) judge.submit(id, problem, language, source)
except Exception: except Exception:
logger.exception('Failed to dispatch %d (%s, %s) to %s', id, problem, language, judge.name) logger.exception(
"Failed to dispatch %d (%s, %s) to %s",
id,
problem,
language,
judge.name,
)
self.judges.discard(judge) self.judges.discard(judge)
return self.judge(id, problem, language, source, judge_id, priority) return self.judge(id, problem, language, source, judge_id, priority)
else: else:
@ -130,4 +152,4 @@ class JudgeList(object):
(id, problem, language, source, judge_id), (id, problem, language, source, judge_id),
self.priority[priority], self.priority[priority],
) )
logger.info('Queued submission: %d', id) logger.info("Queued submission: %d", id)

View file

@ -12,7 +12,9 @@ class Server:
self._shutdown = threading.Event() self._shutdown = threading.Event()
def serve_forever(self): def serve_forever(self):
threads = [threading.Thread(target=server.serve_forever) for server in self.servers] threads = [
threading.Thread(target=server.serve_forever) for server in self.servers
]
for thread in threads: for thread in threads:
thread.daemon = True thread.daemon = True
thread.start() thread.start()

View file

@ -2,9 +2,9 @@ from django.core.cache import cache
def finished_submission(sub): def finished_submission(sub):
keys = ['user_complete:%d' % sub.user_id, 'user_attempted:%s' % sub.user_id] keys = ["user_complete:%d" % sub.user_id, "user_attempted:%s" % sub.user_id]
if hasattr(sub, 'contest'): if hasattr(sub, "contest"):
participation = sub.contest.participation participation = sub.contest.participation
keys += ['contest_complete:%d' % participation.id] keys += ["contest_complete:%d" % participation.id]
keys += ['contest_attempted:%d' % participation.id] keys += ["contest_attempted:%d" % participation.id]
cache.delete_many(keys) cache.delete_many(keys)

View file

@ -7,7 +7,11 @@ from django.db.models import Count
from django.db.models.expressions import F, Value from django.db.models.expressions import F, Value
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.forms import ModelForm from django.forms import ModelForm
from django.http import HttpResponseForbidden, HttpResponseNotFound, HttpResponseRedirect from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseRedirect,
)
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -24,50 +28,55 @@ from judge.widgets import HeavyPreviewPageDownWidget
from judge.jinja2.reference import get_user_from_text from judge.jinja2.reference import get_user_from_text
def add_mention_notifications(comment): def add_mention_notifications(comment):
user_referred = get_user_from_text(comment.body).exclude(id=comment.author.id) user_referred = get_user_from_text(comment.body).exclude(id=comment.author.id)
for user in user_referred: for user in user_referred:
notification_ref = Notification(owner=user, notification_ref = Notification(owner=user, comment=comment, category="Mention")
comment=comment,
category='Mention')
notification_ref.save() notification_ref.save()
def del_mention_notifications(comment):
query = {
'comment': comment,
'category': 'Mention'
}
Notification.objects.filter(**query).delete()
def del_mention_notifications(comment):
query = {"comment": comment, "category": "Mention"}
Notification.objects.filter(**query).delete()
class CommentForm(ModelForm): class CommentForm(ModelForm):
class Meta: class Meta:
model = Comment model = Comment
fields = ['body', 'parent'] fields = ["body", "parent"]
widgets = { widgets = {
'parent': forms.HiddenInput(), "parent": forms.HiddenInput(),
} }
if HeavyPreviewPageDownWidget is not None: if HeavyPreviewPageDownWidget is not None:
widgets['body'] = HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'), widgets["body"] = HeavyPreviewPageDownWidget(
preview_timeout=1000, hide_preview_button=True) preview=reverse_lazy("comment_preview"),
preview_timeout=1000,
hide_preview_button=True,
)
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
self.request = request self.request = request
super(CommentForm, self).__init__(*args, **kwargs) super(CommentForm, self).__init__(*args, **kwargs)
self.fields['body'].widget.attrs.update({'placeholder': _('Comment body')}) self.fields["body"].widget.attrs.update({"placeholder": _("Comment body")})
def clean(self): def clean(self):
if self.request is not None and self.request.user.is_authenticated: if self.request is not None and self.request.user.is_authenticated:
profile = self.request.profile profile = self.request.profile
if profile.mute: if profile.mute:
raise ValidationError(_('Your part is silent, little toad.')) raise ValidationError(_("Your part is silent, little toad."))
elif (not self.request.user.is_staff and elif (
not profile.submission_set.filter(points=F('problem__points')).exists()): not self.request.user.is_staff
raise ValidationError(_('You need to have solved at least one problem ' and not profile.submission_set.filter(
'before your voice can be heard.')) points=F("problem__points")
).exists()
):
raise ValidationError(
_(
"You need to have solved at least one problem "
"before your voice can be heard."
)
)
return super(CommentForm, self).clean() return super(CommentForm, self).clean()
@ -80,10 +89,12 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
return self.comment_page return self.comment_page
def is_comment_locked(self): def is_comment_locked(self):
if self.request.user.has_perm('judge.override_comment_lock'): if self.request.user.has_perm("judge.override_comment_lock"):
return False return False
return (CommentLock.objects.filter(page=self.get_comment_page()).exists() return CommentLock.objects.filter(page=self.get_comment_page()).exists() or (
or (self.request.in_contest and self.request.participation.contest.use_clarifications)) self.request.in_contest
and self.request.participation.contest.use_clarifications
)
@method_decorator(login_required) @method_decorator(login_required)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -93,14 +104,16 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
if self.is_comment_locked(): if self.is_comment_locked():
return HttpResponseForbidden() return HttpResponseForbidden()
parent = request.POST.get('parent') parent = request.POST.get("parent")
if parent: if parent:
try: try:
parent = int(parent) parent = int(parent)
except ValueError: except ValueError:
return HttpResponseNotFound() return HttpResponseNotFound()
else: else:
if not Comment.objects.filter(hidden=False, id=parent, page=page).exists(): if not Comment.objects.filter(
hidden=False, id=parent, page=page
).exists():
return HttpResponseNotFound() return HttpResponseNotFound()
form = CommentForm(request, request.POST) form = CommentForm(request, request.POST)
@ -109,17 +122,18 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
comment.author = request.profile comment.author = request.profile
comment.page = page comment.page = page
with LockModel(
with LockModel(write=(Comment, Revision, Version), read=(ContentType,)), revisions.create_revision(): write=(Comment, Revision, Version), read=(ContentType,)
), revisions.create_revision():
revisions.set_user(request.user) revisions.set_user(request.user)
revisions.set_comment(_('Posted comment')) revisions.set_comment(_("Posted comment"))
comment.save() comment.save()
# add notification for reply # add notification for reply
if comment.parent and comment.parent.author != comment.author: if comment.parent and comment.parent.author != comment.author:
notification_rep = Notification(owner=comment.parent.author, notification_rep = Notification(
comment=comment, owner=comment.parent.author, comment=comment, category="Reply"
category='Reply') )
notification_rep.save() notification_rep.save()
add_mention_notifications(comment) add_mention_notifications(comment)
@ -131,25 +145,40 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
return self.render_to_response(self.get_context_data( return self.render_to_response(
object=self.object, self.get_context_data(
comment_form=CommentForm(request, initial={'page': self.get_comment_page(), 'parent': None}), object=self.object,
)) comment_form=CommentForm(
request, initial={"page": self.get_comment_page(), "parent": None}
),
)
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CommentedDetailView, self).get_context_data(**kwargs) context = super(CommentedDetailView, self).get_context_data(**kwargs)
queryset = Comment.objects.filter(hidden=False, page=self.get_comment_page()) queryset = Comment.objects.filter(hidden=False, page=self.get_comment_page())
context['has_comments'] = queryset.exists() context["has_comments"] = queryset.exists()
context['comment_lock'] = self.is_comment_locked() context["comment_lock"] = self.is_comment_locked()
queryset = queryset.select_related('author__user').defer('author__about').annotate(revisions=Count('versions')) queryset = (
queryset.select_related("author__user")
.defer("author__about")
.annotate(revisions=Count("versions"))
)
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
queryset = queryset.annotate(vote_score=Coalesce(RawSQLColumn(CommentVote, 'score'), Value(0))) queryset = queryset.annotate(
vote_score=Coalesce(RawSQLColumn(CommentVote, "score"), Value(0))
)
profile = self.request.profile profile = self.request.profile
unique_together_left_join(queryset, CommentVote, 'comment', 'voter', profile.id) unique_together_left_join(
context['is_new_user'] = (not self.request.user.is_staff and queryset, CommentVote, "comment", "voter", profile.id
not profile.submission_set.filter(points=F('problem__points')).exists()) )
context['comment_list'] = queryset context["is_new_user"] = (
context['vote_hide_threshold'] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD not self.request.user.is_staff
and not profile.submission_set.filter(
points=F("problem__points")
).exists()
)
context["comment_list"] = queryset
context["vote_hide_threshold"] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD
return context return context

View file

@ -14,14 +14,14 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr from judge.utils.timedelta import nice_repr
@register_contest_format('atcoder') @register_contest_format("atcoder")
class AtCoderContestFormat(DefaultContestFormat): class AtCoderContestFormat(DefaultContestFormat):
name = gettext_lazy('AtCoder') name = gettext_lazy("AtCoder")
config_defaults = {'penalty': 5} config_defaults = {"penalty": 5}
config_validators = {'penalty': lambda x: x >= 0} config_validators = {"penalty": lambda x: x >= 0}
''' """
penalty: Number of penalty minutes each incorrect submission adds. Defaults to 5. penalty: Number of penalty minutes each incorrect submission adds. Defaults to 5.
''' """
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
@ -29,7 +29,9 @@ class AtCoderContestFormat(DefaultContestFormat):
return return
if not isinstance(config, dict): if not isinstance(config, dict):
raise ValidationError('AtCoder-styled contest expects no config or dict as config') raise ValidationError(
"AtCoder-styled contest expects no config or dict as config"
)
for key, value in config.items(): for key, value in config.items():
if key not in cls.config_defaults: if key not in cls.config_defaults:
@ -37,7 +39,9 @@ class AtCoderContestFormat(DefaultContestFormat):
if not isinstance(value, type(cls.config_defaults[key])): if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key) raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value): if not cls.config_validators[key](value):
raise ValidationError('invalid value "%s" for config key "%s"' % (value, key)) raise ValidationError(
'invalid value "%s" for config key "%s"' % (value, key)
)
def __init__(self, contest, config): def __init__(self, contest, config):
self.config = self.config_defaults.copy() self.config = self.config_defaults.copy()
@ -51,7 +55,8 @@ class AtCoderContestFormat(DefaultContestFormat):
format_data = {} format_data = {}
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(''' cursor.execute(
"""
SELECT MAX(cs.points) as `score`, ( SELECT MAX(cs.points) as `score`, (
SELECT MIN(csub.date) SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN FROM judge_contestsubmission ccs LEFT OUTER JOIN
@ -62,21 +67,27 @@ class AtCoderContestFormat(DefaultContestFormat):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id) judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id GROUP BY cp.id
''', (participation.id, participation.id)) """,
(participation.id, participation.id),
)
for score, time, prob in cursor.fetchall(): for score, time, prob in cursor.fetchall():
time = from_database_time(time) time = from_database_time(time)
dt = (time - participation.start).total_seconds() dt = (time - participation.start).total_seconds()
# Compute penalty # Compute penalty
if self.config['penalty']: if self.config["penalty"]:
# An IE can have a submission result of `None` # An IE can have a submission result of `None`
subs = participation.submissions.exclude(submission__result__isnull=True) \ subs = (
.exclude(submission__result__in=['IE', 'CE']) \ participation.submissions.exclude(
.filter(problem_id=prob) submission__result__isnull=True
)
.exclude(submission__result__in=["IE", "CE"])
.filter(problem_id=prob)
)
if score: if score:
prev = subs.filter(submission__date__lte=time).count() - 1 prev = subs.filter(submission__date__lte=time).count() - 1
penalty += prev * self.config['penalty'] * 60 penalty += prev * self.config["penalty"] * 60
else: else:
# We should always display the penalty, even if the user has a score of 0 # We should always display the penalty, even if the user has a score of 0
prev = subs.count() prev = subs.count()
@ -86,7 +97,7 @@ class AtCoderContestFormat(DefaultContestFormat):
if score: if score:
cumtime = max(cumtime, dt) cumtime = max(cumtime, dt)
format_data[str(prob)] = {'time': dt, 'points': score, 'penalty': prev} format_data[str(prob)] = {"time": dt, "points": score, "penalty": prev}
points += score points += score
participation.cumtime = cumtime + penalty participation.cumtime = cumtime + penalty
@ -98,17 +109,38 @@ class AtCoderContestFormat(DefaultContestFormat):
def display_user_problem(self, participation, contest_problem): def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id)) format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data: if format_data:
penalty = format_html('<small style="color:red"> ({penalty})</small>', penalty = (
penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else '' format_html(
'<small style="color:red"> ({penalty})</small>',
penalty=floatformat(format_data["penalty"]),
)
if format_data["penalty"]
else ""
)
return format_html( return format_html(
'<td class="{state} problem-score-col"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>', '<td class="{state} problem-score-col"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') + state=(
self.best_solution_state(format_data['points'], contest_problem.points)), (
url=reverse('contest_user_submissions', "pretest-"
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]), if self.contest.run_pretests_only
points=floatformat(format_data['points']), and contest_problem.is_pretested
else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
),
url=reverse(
"contest_user_submissions",
args=[
self.contest.key,
participation.user.user.username,
contest_problem.problem.code,
],
),
points=floatformat(format_data["points"]),
penalty=penalty, penalty=penalty,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'), time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
) )
else: else:
return mark_safe('<td class="problem-score-col"></td>') return mark_safe('<td class="problem-score-col"></td>')

View file

@ -93,7 +93,7 @@ class BaseContestFormat(six.with_metaclass(ABCMeta)):
@classmethod @classmethod
def best_solution_state(cls, points, total): def best_solution_state(cls, points, total):
if not points: if not points:
return 'failed-score' return "failed-score"
if points == total: if points == total:
return 'full-score' return "full-score"
return 'partial-score' return "partial-score"

View file

@ -13,14 +13,16 @@ from judge.contest_format.registry import register_contest_format
from judge.utils.timedelta import nice_repr from judge.utils.timedelta import nice_repr
@register_contest_format('default') @register_contest_format("default")
class DefaultContestFormat(BaseContestFormat): class DefaultContestFormat(BaseContestFormat):
name = gettext_lazy('Default') name = gettext_lazy("Default")
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
if config is not None and (not isinstance(config, dict) or config): if config is not None and (not isinstance(config, dict) or config):
raise ValidationError('default contest expects no config or empty dict as config') raise ValidationError(
"default contest expects no config or empty dict as config"
)
def __init__(self, contest, config): def __init__(self, contest, config):
super(DefaultContestFormat, self).__init__(contest, config) super(DefaultContestFormat, self).__init__(contest, config)
@ -30,14 +32,18 @@ class DefaultContestFormat(BaseContestFormat):
points = 0 points = 0
format_data = {} format_data = {}
for result in participation.submissions.values('problem_id').annotate( for result in participation.submissions.values("problem_id").annotate(
time=Max('submission__date'), points=Max('points'), time=Max("submission__date"),
points=Max("points"),
): ):
dt = (result['time'] - participation.start).total_seconds() dt = (result["time"] - participation.start).total_seconds()
if result['points']: if result["points"]:
cumtime += dt cumtime += dt
format_data[str(result['problem_id'])] = {'time': dt, 'points': result['points']} format_data[str(result["problem_id"])] = {
points += result['points'] "time": dt,
"points": result["points"],
}
points += result["points"]
participation.cumtime = max(cumtime, 0) participation.cumtime = max(cumtime, 0)
participation.score = points participation.score = points
@ -49,30 +55,50 @@ class DefaultContestFormat(BaseContestFormat):
format_data = (participation.format_data or {}).get(str(contest_problem.id)) format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data: if format_data:
return format_html( return format_html(
u'<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>', '<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>',
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') + state=(
self.best_solution_state(format_data['points'], contest_problem.points)), (
url=reverse('contest_user_submissions', "pretest-"
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]), if self.contest.run_pretests_only
points=floatformat(format_data['points'], -self.contest.points_precision), and contest_problem.is_pretested
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'), else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
),
url=reverse(
"contest_user_submissions",
args=[
self.contest.key,
participation.user.user.username,
contest_problem.problem.code,
],
),
points=floatformat(
format_data["points"], -self.contest.points_precision
),
time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
) )
else: else:
return mark_safe('<td class="problem-score-col"></td>') return mark_safe('<td class="problem-score-col"></td>')
def display_participation_result(self, participation): def display_participation_result(self, participation):
return format_html( return format_html(
u'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>', '<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
points=floatformat(participation.score, -self.contest.points_precision), points=floatformat(participation.score, -self.contest.points_precision),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday'), cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday"),
) )
def get_problem_breakdown(self, participation, contest_problems): def get_problem_breakdown(self, participation, contest_problems):
return [(participation.format_data or {}).get(str(contest_problem.id)) for contest_problem in contest_problems] return [
(participation.format_data or {}).get(str(contest_problem.id))
for contest_problem in contest_problems
]
def get_contest_problem_label_script(self): def get_contest_problem_label_script(self):
return ''' return """
function(n) function(n)
return tostring(math.floor(n + 1)) return tostring(math.floor(n + 1))
end end
''' """

View file

@ -14,17 +14,21 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr from judge.utils.timedelta import nice_repr
@register_contest_format('ecoo') @register_contest_format("ecoo")
class ECOOContestFormat(DefaultContestFormat): class ECOOContestFormat(DefaultContestFormat):
name = gettext_lazy('ECOO') name = gettext_lazy("ECOO")
config_defaults = {'cumtime': False, 'first_ac_bonus': 10, 'time_bonus': 5} config_defaults = {"cumtime": False, "first_ac_bonus": 10, "time_bonus": 5}
config_validators = {'cumtime': lambda x: True, 'first_ac_bonus': lambda x: x >= 0, 'time_bonus': lambda x: x >= 0} config_validators = {
''' "cumtime": lambda x: True,
"first_ac_bonus": lambda x: x >= 0,
"time_bonus": lambda x: x >= 0,
}
"""
cumtime: Specify True if cumulative time is to be used in breaking ties. Defaults to False. cumtime: Specify True if cumulative time is to be used in breaking ties. Defaults to False.
first_ac_bonus: The number of points to award if a solution gets AC on its first non-IE/CE run. Defaults to 10. first_ac_bonus: The number of points to award if a solution gets AC on its first non-IE/CE run. Defaults to 10.
time_bonus: Number of minutes to award an extra point for submitting before the contest end. time_bonus: Number of minutes to award an extra point for submitting before the contest end.
Specify 0 to disable. Defaults to 5. Specify 0 to disable. Defaults to 5.
''' """
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
@ -32,7 +36,9 @@ class ECOOContestFormat(DefaultContestFormat):
return return
if not isinstance(config, dict): if not isinstance(config, dict):
raise ValidationError('ECOO-styled contest expects no config or dict as config') raise ValidationError(
"ECOO-styled contest expects no config or dict as config"
)
for key, value in config.items(): for key, value in config.items():
if key not in cls.config_defaults: if key not in cls.config_defaults:
@ -40,7 +46,9 @@ class ECOOContestFormat(DefaultContestFormat):
if not isinstance(value, type(cls.config_defaults[key])): if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key) raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value): if not cls.config_validators[key](value):
raise ValidationError('invalid value "%s" for config key "%s"' % (value, key)) raise ValidationError(
'invalid value "%s" for config key "%s"' % (value, key)
)
def __init__(self, contest, config): def __init__(self, contest, config):
self.config = self.config_defaults.copy() self.config = self.config_defaults.copy()
@ -53,7 +61,8 @@ class ECOOContestFormat(DefaultContestFormat):
format_data = {} format_data = {}
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(''' cursor.execute(
"""
SELECT ( SELECT (
SELECT MAX(ccs.points) SELECT MAX(ccs.points)
FROM judge_contestsubmission ccs LEFT OUTER JOIN FROM judge_contestsubmission ccs LEFT OUTER JOIN
@ -69,25 +78,31 @@ class ECOOContestFormat(DefaultContestFormat):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id) judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id GROUP BY cp.id
''', (participation.id, participation.id, participation.id)) """,
(participation.id, participation.id, participation.id),
)
for score, time, prob, subs, max_score in cursor.fetchall(): for score, time, prob, subs, max_score in cursor.fetchall():
time = from_database_time(time) time = from_database_time(time)
dt = (time - participation.start).total_seconds() dt = (time - participation.start).total_seconds()
if self.config['cumtime']: if self.config["cumtime"]:
cumtime += dt cumtime += dt
bonus = 0 bonus = 0
if score > 0: if score > 0:
# First AC bonus # First AC bonus
if subs == 1 and score == max_score: if subs == 1 and score == max_score:
bonus += self.config['first_ac_bonus'] bonus += self.config["first_ac_bonus"]
# Time bonus # Time bonus
if self.config['time_bonus']: if self.config["time_bonus"]:
bonus += (participation.end_time - time).total_seconds() // 60 // self.config['time_bonus'] bonus += (
(participation.end_time - time).total_seconds()
// 60
// self.config["time_bonus"]
)
points += bonus points += bonus
format_data[str(prob)] = {'time': dt, 'points': score, 'bonus': bonus} format_data[str(prob)] = {"time": dt, "points": score, "bonus": bonus}
points += score points += score
participation.cumtime = cumtime participation.cumtime = cumtime
@ -99,25 +114,47 @@ class ECOOContestFormat(DefaultContestFormat):
def display_user_problem(self, participation, contest_problem): def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id)) format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data: if format_data:
bonus = format_html('<small> +{bonus}</small>', bonus = (
bonus=floatformat(format_data['bonus'])) if format_data['bonus'] else '' format_html(
"<small> +{bonus}</small>", bonus=floatformat(format_data["bonus"])
)
if format_data["bonus"]
else ""
)
return format_html( return format_html(
'<td class="{state}"><a href="{url}">{points}{bonus}<div class="solving-time">{time}</div></a></td>', '<td class="{state}"><a href="{url}">{points}{bonus}<div class="solving-time">{time}</div></a></td>',
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') + state=(
self.best_solution_state(format_data['points'], contest_problem.points)), (
url=reverse('contest_user_submissions', "pretest-"
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]), if self.contest.run_pretests_only
points=floatformat(format_data['points']), and contest_problem.is_pretested
else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
),
url=reverse(
"contest_user_submissions",
args=[
self.contest.key,
participation.user.user.username,
contest_problem.problem.code,
],
),
points=floatformat(format_data["points"]),
bonus=bonus, bonus=bonus,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'), time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
) )
else: else:
return mark_safe('<td></td>') return mark_safe("<td></td>")
def display_participation_result(self, participation): def display_participation_result(self, participation):
return format_html( return format_html(
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>', '<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
points=floatformat(participation.score), points=floatformat(participation.score),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '', cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday")
if self.config["cumtime"]
else "",
) )

View file

@ -14,14 +14,14 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr from judge.utils.timedelta import nice_repr
@register_contest_format('icpc') @register_contest_format("icpc")
class ICPCContestFormat(DefaultContestFormat): class ICPCContestFormat(DefaultContestFormat):
name = gettext_lazy('ICPC') name = gettext_lazy("ICPC")
config_defaults = {'penalty': 20} config_defaults = {"penalty": 20}
config_validators = {'penalty': lambda x: x >= 0} config_validators = {"penalty": lambda x: x >= 0}
''' """
penalty: Number of penalty minutes each incorrect submission adds. Defaults to 20. penalty: Number of penalty minutes each incorrect submission adds. Defaults to 20.
''' """
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
@ -29,7 +29,9 @@ class ICPCContestFormat(DefaultContestFormat):
return return
if not isinstance(config, dict): if not isinstance(config, dict):
raise ValidationError('ICPC-styled contest expects no config or dict as config') raise ValidationError(
"ICPC-styled contest expects no config or dict as config"
)
for key, value in config.items(): for key, value in config.items():
if key not in cls.config_defaults: if key not in cls.config_defaults:
@ -37,7 +39,9 @@ class ICPCContestFormat(DefaultContestFormat):
if not isinstance(value, type(cls.config_defaults[key])): if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key) raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value): if not cls.config_validators[key](value):
raise ValidationError('invalid value "%s" for config key "%s"' % (value, key)) raise ValidationError(
'invalid value "%s" for config key "%s"' % (value, key)
)
def __init__(self, contest, config): def __init__(self, contest, config):
self.config = self.config_defaults.copy() self.config = self.config_defaults.copy()
@ -52,7 +56,8 @@ class ICPCContestFormat(DefaultContestFormat):
format_data = {} format_data = {}
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(''' cursor.execute(
"""
SELECT MAX(cs.points) as `points`, ( SELECT MAX(cs.points) as `points`, (
SELECT MIN(csub.date) SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN FROM judge_contestsubmission ccs LEFT OUTER JOIN
@ -63,21 +68,27 @@ class ICPCContestFormat(DefaultContestFormat):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id) judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id GROUP BY cp.id
''', (participation.id, participation.id)) """,
(participation.id, participation.id),
)
for points, time, prob in cursor.fetchall(): for points, time, prob in cursor.fetchall():
time = from_database_time(time) time = from_database_time(time)
dt = (time - participation.start).total_seconds() dt = (time - participation.start).total_seconds()
# Compute penalty # Compute penalty
if self.config['penalty']: if self.config["penalty"]:
# An IE can have a submission result of `None` # An IE can have a submission result of `None`
subs = participation.submissions.exclude(submission__result__isnull=True) \ subs = (
.exclude(submission__result__in=['IE', 'CE']) \ participation.submissions.exclude(
.filter(problem_id=prob) submission__result__isnull=True
)
.exclude(submission__result__in=["IE", "CE"])
.filter(problem_id=prob)
)
if points: if points:
prev = subs.filter(submission__date__lte=time).count() - 1 prev = subs.filter(submission__date__lte=time).count() - 1
penalty += prev * self.config['penalty'] * 60 penalty += prev * self.config["penalty"] * 60
else: else:
# We should always display the penalty, even if the user has a score of 0 # We should always display the penalty, even if the user has a score of 0
prev = subs.count() prev = subs.count()
@ -88,7 +99,7 @@ class ICPCContestFormat(DefaultContestFormat):
cumtime += dt cumtime += dt
last = max(last, dt) last = max(last, dt)
format_data[str(prob)] = {'time': dt, 'points': points, 'penalty': prev} format_data[str(prob)] = {"time": dt, "points": points, "penalty": prev}
score += points score += points
participation.cumtime = max(0, cumtime + penalty) participation.cumtime = max(0, cumtime + penalty)
@ -100,23 +111,44 @@ class ICPCContestFormat(DefaultContestFormat):
def display_user_problem(self, participation, contest_problem): def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id)) format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data: if format_data:
penalty = format_html('<small style="color:red"> ({penalty})</small>', penalty = (
penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else '' format_html(
'<small style="color:red"> ({penalty})</small>',
penalty=floatformat(format_data["penalty"]),
)
if format_data["penalty"]
else ""
)
return format_html( return format_html(
'<td class="{state}"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>', '<td class="{state}"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') + state=(
self.best_solution_state(format_data['points'], contest_problem.points)), (
url=reverse('contest_user_submissions', "pretest-"
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]), if self.contest.run_pretests_only
points=floatformat(format_data['points']), and contest_problem.is_pretested
else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
),
url=reverse(
"contest_user_submissions",
args=[
self.contest.key,
participation.user.user.username,
contest_problem.problem.code,
],
),
points=floatformat(format_data["points"]),
penalty=penalty, penalty=penalty,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'), time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
) )
else: else:
return mark_safe('<td></td>') return mark_safe("<td></td>")
def get_contest_problem_label_script(self): def get_contest_problem_label_script(self):
return ''' return """
function(n) function(n)
n = n + 1 n = n + 1
ret = "" ret = ""
@ -126,4 +158,4 @@ class ICPCContestFormat(DefaultContestFormat):
end end
return ret return ret
end end
''' """

View file

@ -14,13 +14,13 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr from judge.utils.timedelta import nice_repr
@register_contest_format('ioi') @register_contest_format("ioi")
class IOIContestFormat(DefaultContestFormat): class IOIContestFormat(DefaultContestFormat):
name = gettext_lazy('IOI') name = gettext_lazy("IOI")
config_defaults = {'cumtime': False} config_defaults = {"cumtime": False}
''' """
cumtime: Specify True if time penalties are to be computed. Defaults to False. cumtime: Specify True if time penalties are to be computed. Defaults to False.
''' """
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
@ -28,7 +28,9 @@ class IOIContestFormat(DefaultContestFormat):
return return
if not isinstance(config, dict): if not isinstance(config, dict):
raise ValidationError('IOI-styled contest expects no config or dict as config') raise ValidationError(
"IOI-styled contest expects no config or dict as config"
)
for key, value in config.items(): for key, value in config.items():
if key not in cls.config_defaults: if key not in cls.config_defaults:
@ -47,7 +49,8 @@ class IOIContestFormat(DefaultContestFormat):
format_data = {} format_data = {}
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(''' cursor.execute(
"""
SELECT MAX(cs.points) as `score`, ( SELECT MAX(cs.points) as `score`, (
SELECT MIN(csub.date) SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN FROM judge_contestsubmission ccs LEFT OUTER JOIN
@ -58,17 +61,21 @@ class IOIContestFormat(DefaultContestFormat):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id) judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id GROUP BY cp.id
''', (participation.id, participation.id)) """,
(participation.id, participation.id),
)
for score, time, prob in cursor.fetchall(): for score, time, prob in cursor.fetchall():
if self.config['cumtime']: if self.config["cumtime"]:
dt = (from_database_time(time) - participation.start).total_seconds() dt = (
from_database_time(time) - participation.start
).total_seconds()
if score: if score:
cumtime += dt cumtime += dt
else: else:
dt = 0 dt = 0
format_data[str(prob)] = {'time': dt, 'points': score} format_data[str(prob)] = {"time": dt, "points": score}
points += score points += score
participation.cumtime = max(cumtime, 0) participation.cumtime = max(cumtime, 0)
@ -82,12 +89,29 @@ class IOIContestFormat(DefaultContestFormat):
if format_data: if format_data:
return format_html( return format_html(
'<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>', '<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>',
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') + state=(
self.best_solution_state(format_data['points'], contest_problem.points)), (
url=reverse('contest_user_submissions', "pretest-"
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]), if self.contest.run_pretests_only
points=floatformat(format_data['points']), and contest_problem.is_pretested
time=nice_repr(timedelta(seconds=format_data['time']), 'noday') if self.config['cumtime'] else '', else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
),
url=reverse(
"contest_user_submissions",
args=[
self.contest.key,
participation.user.user.username,
contest_problem.problem.code,
],
),
points=floatformat(format_data["points"]),
time=nice_repr(timedelta(seconds=format_data["time"]), "noday")
if self.config["cumtime"]
else "",
) )
else: else:
return mark_safe('<td class="problem-score-col"></td>') return mark_safe('<td class="problem-score-col"></td>')
@ -96,5 +120,7 @@ class IOIContestFormat(DefaultContestFormat):
return format_html( return format_html(
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>', '<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
points=floatformat(participation.score), points=floatformat(participation.score),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '', cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday")
if self.config["cumtime"]
else "",
) )

View file

@ -5,19 +5,21 @@ from django.db import connection, transaction
class LockModel(object): class LockModel(object):
def __init__(self, write, read=()): def __init__(self, write, read=()):
self.tables = ', '.join(chain( self.tables = ", ".join(
('`%s` WRITE' % model._meta.db_table for model in write), chain(
('`%s` READ' % model._meta.db_table for model in read), ("`%s` WRITE" % model._meta.db_table for model in write),
)) ("`%s` READ" % model._meta.db_table for model in read),
)
)
self.cursor = connection.cursor() self.cursor = connection.cursor()
def __enter__(self): def __enter__(self):
self.cursor.execute('LOCK TABLES ' + self.tables) self.cursor.execute("LOCK TABLES " + self.tables)
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None: if exc_type is None:
transaction.commit() transaction.commit()
else: else:
transaction.rollback() transaction.rollback()
self.cursor.execute('UNLOCK TABLES') self.cursor.execute("UNLOCK TABLES")
self.cursor.close() self.cursor.close()

View file

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
__all__ = ['last', 'post'] __all__ = ["last", "post"]
if not settings.EVENT_DAEMON_USE: if not settings.EVENT_DAEMON_USE:
real = False real = False
@ -10,9 +10,12 @@ if not settings.EVENT_DAEMON_USE:
def last(): def last():
return 0 return 0
elif hasattr(settings, 'EVENT_DAEMON_AMQP'):
elif hasattr(settings, "EVENT_DAEMON_AMQP"):
from .event_poster_amqp import last, post from .event_poster_amqp import last, post
real = True real = True
else: else:
from .event_poster_ws import last, post from .event_poster_ws import last, post
real = True real = True

View file

@ -6,7 +6,7 @@ import pika
from django.conf import settings from django.conf import settings
from pika.exceptions import AMQPError from pika.exceptions import AMQPError
__all__ = ['EventPoster', 'post', 'last'] __all__ = ["EventPoster", "post", "last"]
class EventPoster(object): class EventPoster(object):
@ -15,14 +15,19 @@ class EventPoster(object):
self._exchange = settings.EVENT_DAEMON_AMQP_EXCHANGE self._exchange = settings.EVENT_DAEMON_AMQP_EXCHANGE
def _connect(self): def _connect(self):
self._conn = pika.BlockingConnection(pika.URLParameters(settings.EVENT_DAEMON_AMQP)) self._conn = pika.BlockingConnection(
pika.URLParameters(settings.EVENT_DAEMON_AMQP)
)
self._chan = self._conn.channel() self._chan = self._conn.channel()
def post(self, channel, message, tries=0): def post(self, channel, message, tries=0):
try: try:
id = int(time() * 1000000) id = int(time() * 1000000)
self._chan.basic_publish(self._exchange, '', self._chan.basic_publish(
json.dumps({'id': id, 'channel': channel, 'message': message})) self._exchange,
"",
json.dumps({"id": id, "channel": channel, "message": message}),
)
return id return id
except AMQPError: except AMQPError:
if tries > 10: if tries > 10:
@ -35,7 +40,7 @@ _local = threading.local()
def _get_poster(): def _get_poster():
if 'poster' not in _local.__dict__: if "poster" not in _local.__dict__:
_local.poster = EventPoster() _local.poster = EventPoster()
return _local.poster return _local.poster

View file

@ -5,7 +5,7 @@ import threading
from django.conf import settings from django.conf import settings
from websocket import WebSocketException, create_connection from websocket import WebSocketException, create_connection
__all__ = ['EventPostingError', 'EventPoster', 'post', 'last'] __all__ = ["EventPostingError", "EventPoster", "post", "last"]
_local = threading.local() _local = threading.local()
@ -20,19 +20,23 @@ class EventPoster(object):
def _connect(self): def _connect(self):
self._conn = create_connection(settings.EVENT_DAEMON_POST) self._conn = create_connection(settings.EVENT_DAEMON_POST)
if settings.EVENT_DAEMON_KEY is not None: if settings.EVENT_DAEMON_KEY is not None:
self._conn.send(json.dumps({'command': 'auth', 'key': settings.EVENT_DAEMON_KEY})) self._conn.send(
json.dumps({"command": "auth", "key": settings.EVENT_DAEMON_KEY})
)
resp = json.loads(self._conn.recv()) resp = json.loads(self._conn.recv())
if resp['status'] == 'error': if resp["status"] == "error":
raise EventPostingError(resp['code']) raise EventPostingError(resp["code"])
def post(self, channel, message, tries=0): def post(self, channel, message, tries=0):
try: try:
self._conn.send(json.dumps({'command': 'post', 'channel': channel, 'message': message})) self._conn.send(
json.dumps({"command": "post", "channel": channel, "message": message})
)
resp = json.loads(self._conn.recv()) resp = json.loads(self._conn.recv())
if resp['status'] == 'error': if resp["status"] == "error":
raise EventPostingError(resp['code']) raise EventPostingError(resp["code"])
else: else:
return resp['id'] return resp["id"]
except WebSocketException: except WebSocketException:
if tries > 10: if tries > 10:
raise raise
@ -43,10 +47,10 @@ class EventPoster(object):
try: try:
self._conn.send('{"command": "last-msg"}') self._conn.send('{"command": "last-msg"}')
resp = json.loads(self._conn.recv()) resp = json.loads(self._conn.recv())
if resp['status'] == 'error': if resp["status"] == "error":
raise EventPostingError(resp['code']) raise EventPostingError(resp["code"])
else: else:
return resp['id'] return resp["id"]
except WebSocketException: except WebSocketException:
if tries > 10: if tries > 10:
raise raise
@ -55,7 +59,7 @@ class EventPoster(object):
def _get_poster(): def _get_poster():
if 'poster' not in _local.__dict__: if "poster" not in _local.__dict__:
_local.poster = EventPoster() _local.poster = EventPoster()
return _local.poster return _local.poster

View file

@ -12,27 +12,35 @@ import re
# https://lsimons.wordpress.com/2011/03/17/stripping-illegal-characters-out-of-xml-in-python/ # https://lsimons.wordpress.com/2011/03/17/stripping-illegal-characters-out-of-xml-in-python/
def escape_xml_illegal_chars(val, replacement='?'): def escape_xml_illegal_chars(val, replacement="?"):
_illegal_xml_chars_RE = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]') _illegal_xml_chars_RE = re.compile(
"[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]"
)
return _illegal_xml_chars_RE.sub(replacement, val) return _illegal_xml_chars_RE.sub(replacement, val)
class ProblemFeed(Feed): class ProblemFeed(Feed):
title = 'Recently Added %s Problems' % settings.SITE_NAME title = "Recently Added %s Problems" % settings.SITE_NAME
link = '/' link = "/"
description = 'The latest problems added on the %s website' % settings.SITE_LONG_NAME description = (
"The latest problems added on the %s website" % settings.SITE_LONG_NAME
)
def items(self): def items(self):
return Problem.objects.filter(is_public=True, is_organization_private=False).defer('description')\ return (
.order_by('-date', '-id')[:25] Problem.objects.filter(is_public=True, is_organization_private=False)
.defer("description")
.order_by("-date", "-id")[:25]
)
def item_title(self, problem): def item_title(self, problem):
return problem.name return problem.name
def item_description(self, problem): def item_description(self, problem):
key = 'problem_feed:%d' % problem.id key = "problem_feed:%d" % problem.id
desc = cache.get(key) desc = cache.get(key)
if desc is None: if desc is None:
desc = str(markdown(problem.description, 'problem'))[:500] + '...' desc = str(markdown(problem.description, "problem"))[:500] + "..."
desc = escape_xml_illegal_chars(desc) desc = escape_xml_illegal_chars(desc)
cache.set(key, desc, 86400) cache.set(key, desc, 86400)
return desc return desc
@ -49,21 +57,21 @@ class AtomProblemFeed(ProblemFeed):
class CommentFeed(Feed): class CommentFeed(Feed):
title = 'Latest %s Comments' % settings.SITE_NAME title = "Latest %s Comments" % settings.SITE_NAME
link = '/' link = "/"
description = 'The latest comments on the %s website' % settings.SITE_LONG_NAME description = "The latest comments on the %s website" % settings.SITE_LONG_NAME
def items(self): def items(self):
return Comment.most_recent(AnonymousUser(), 25) return Comment.most_recent(AnonymousUser(), 25)
def item_title(self, comment): def item_title(self, comment):
return '%s -> %s' % (comment.author.user.username, comment.page_title) return "%s -> %s" % (comment.author.user.username, comment.page_title)
def item_description(self, comment): def item_description(self, comment):
key = 'comment_feed:%d' % comment.id key = "comment_feed:%d" % comment.id
desc = cache.get(key) desc = cache.get(key)
if desc is None: if desc is None:
desc = str(markdown(comment.body, 'comment')) desc = str(markdown(comment.body, "comment"))
desc = escape_xml_illegal_chars(desc) desc = escape_xml_illegal_chars(desc)
cache.set(key, desc, 86400) cache.set(key, desc, 86400)
return desc return desc
@ -80,21 +88,23 @@ class AtomCommentFeed(CommentFeed):
class BlogFeed(Feed): class BlogFeed(Feed):
title = 'Latest %s Blog Posts' % settings.SITE_NAME title = "Latest %s Blog Posts" % settings.SITE_NAME
link = '/' link = "/"
description = 'The latest blog posts from the %s' % settings.SITE_LONG_NAME description = "The latest blog posts from the %s" % settings.SITE_LONG_NAME
def items(self): def items(self):
return BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()).order_by('-sticky', '-publish_on') return BlogPost.objects.filter(
visible=True, publish_on__lte=timezone.now()
).order_by("-sticky", "-publish_on")
def item_title(self, post): def item_title(self, post):
return post.title return post.title
def item_description(self, post): def item_description(self, post):
key = 'blog_feed:%d' % post.id key = "blog_feed:%d" % post.id
summary = cache.get(key) summary = cache.get(key)
if summary is None: if summary is None:
summary = str(markdown(post.summary or post.content, 'blog')) summary = str(markdown(post.summary or post.content, "blog"))
summary = escape_xml_illegal_chars(summary) summary = escape_xml_illegal_chars(summary)
cache.set(key, summary, 86400) cache.set(key, summary, 86400)
return summary return summary

View file

@ -12,159 +12,211 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_ace import AceWidget from django_ace import AceWidget
from judge.models import Contest, Language, Organization, PrivateMessage, Problem, ProblemPointsVote, Profile, Submission from judge.models import (
Contest,
Language,
Organization,
PrivateMessage,
Problem,
ProblemPointsVote,
Profile,
Submission,
)
from judge.utils.subscription import newsletter_id from judge.utils.subscription import newsletter_id
from judge.widgets import HeavyPreviewPageDownWidget, MathJaxPagedownWidget, PagedownWidget, Select2MultipleWidget, \ from judge.widgets import (
Select2Widget HeavyPreviewPageDownWidget,
MathJaxPagedownWidget,
PagedownWidget,
Select2MultipleWidget,
Select2Widget,
)
def fix_unicode(string, unsafe=tuple('\u202a\u202b\u202d\u202e')): def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
return string + (sum(k in unsafe for k in string) - string.count('\u202c')) * '\u202c' return (
string + (sum(k in unsafe for k in string) - string.count("\u202c")) * "\u202c"
)
class ProfileForm(ModelForm): class ProfileForm(ModelForm):
if newsletter_id is not None: if newsletter_id is not None:
newsletter = forms.BooleanField(label=_('Subscribe to contest updates'), initial=False, required=False) newsletter = forms.BooleanField(
test_site = forms.BooleanField(label=_('Enable experimental features'), initial=False, required=False) label=_("Subscribe to contest updates"), initial=False, required=False
)
test_site = forms.BooleanField(
label=_("Enable experimental features"), initial=False, required=False
)
class Meta: class Meta:
model = Profile model = Profile
fields = ['about', 'organizations', 'timezone', 'language', 'ace_theme', 'user_script'] fields = [
"about",
"organizations",
"timezone",
"language",
"ace_theme",
"user_script",
]
widgets = { widgets = {
'user_script': AceWidget(theme='github'), "user_script": AceWidget(theme="github"),
'timezone': Select2Widget(attrs={'style': 'width:200px'}), "timezone": Select2Widget(attrs={"style": "width:200px"}),
'language': Select2Widget(attrs={'style': 'width:200px'}), "language": Select2Widget(attrs={"style": "width:200px"}),
'ace_theme': Select2Widget(attrs={'style': 'width:200px'}), "ace_theme": Select2Widget(attrs={"style": "width:200px"}),
} }
has_math_config = bool(settings.MATHOID_URL) has_math_config = bool(settings.MATHOID_URL)
if has_math_config: if has_math_config:
fields.append('math_engine') fields.append("math_engine")
widgets['math_engine'] = Select2Widget(attrs={'style': 'width:200px'}) widgets["math_engine"] = Select2Widget(attrs={"style": "width:200px"})
if HeavyPreviewPageDownWidget is not None: if HeavyPreviewPageDownWidget is not None:
widgets['about'] = HeavyPreviewPageDownWidget( widgets["about"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy('profile_preview'), preview=reverse_lazy("profile_preview"),
attrs={'style': 'max-width:700px;min-width:700px;width:700px'}, attrs={"style": "max-width:700px;min-width:700px;width:700px"},
) )
def clean(self): def clean(self):
organizations = self.cleaned_data.get('organizations') or [] organizations = self.cleaned_data.get("organizations") or []
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if sum(org.is_open for org in organizations) > max_orgs: if sum(org.is_open for org in organizations) > max_orgs:
raise ValidationError( raise ValidationError(
_('You may not be part of more than {count} public organizations.').format(count=max_orgs)) _(
"You may not be part of more than {count} public organizations."
).format(count=max_orgs)
)
return self.cleaned_data return self.cleaned_data
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None) user = kwargs.pop("user", None)
super(ProfileForm, self).__init__(*args, **kwargs) super(ProfileForm, self).__init__(*args, **kwargs)
if not user.has_perm('judge.edit_all_organization'): if not user.has_perm("judge.edit_all_organization"):
self.fields['organizations'].queryset = Organization.objects.filter( self.fields["organizations"].queryset = Organization.objects.filter(
Q(is_open=True) | Q(id__in=user.profile.organizations.all()), Q(is_open=True) | Q(id__in=user.profile.organizations.all()),
) )
class ProblemSubmitForm(ModelForm): class ProblemSubmitForm(ModelForm):
source = CharField(max_length=65536, widget=AceWidget(theme='twilight', no_ace_media=True)) source = CharField(
max_length=65536, widget=AceWidget(theme="twilight", no_ace_media=True)
)
judge = ChoiceField(choices=(), widget=forms.HiddenInput(), required=False) judge = ChoiceField(choices=(), widget=forms.HiddenInput(), required=False)
def __init__(self, *args, judge_choices=(), **kwargs): def __init__(self, *args, judge_choices=(), **kwargs):
super(ProblemSubmitForm, self).__init__(*args, **kwargs) super(ProblemSubmitForm, self).__init__(*args, **kwargs)
self.fields['language'].empty_label = None self.fields["language"].empty_label = None
self.fields['language'].label_from_instance = attrgetter('display_name') self.fields["language"].label_from_instance = attrgetter("display_name")
self.fields['language'].queryset = Language.objects.filter(judges__online=True).distinct() self.fields["language"].queryset = Language.objects.filter(
judges__online=True
).distinct()
if judge_choices: if judge_choices:
self.fields['judge'].widget = Select2Widget( self.fields["judge"].widget = Select2Widget(
attrs={'style': 'width: 150px', 'data-placeholder': _('Any judge')}, attrs={"style": "width: 150px", "data-placeholder": _("Any judge")},
) )
self.fields['judge'].choices = judge_choices self.fields["judge"].choices = judge_choices
class Meta: class Meta:
model = Submission model = Submission
fields = ['language'] fields = ["language"]
class EditOrganizationForm(ModelForm): class EditOrganizationForm(ModelForm):
class Meta: class Meta:
model = Organization model = Organization
fields = ['about', 'logo_override_image', 'admins'] fields = ["about", "logo_override_image", "admins"]
widgets = {'admins': Select2MultipleWidget()} widgets = {"admins": Select2MultipleWidget()}
if HeavyPreviewPageDownWidget is not None: if HeavyPreviewPageDownWidget is not None:
widgets['about'] = HeavyPreviewPageDownWidget(preview=reverse_lazy('organization_preview')) widgets["about"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("organization_preview")
)
class NewMessageForm(ModelForm): class NewMessageForm(ModelForm):
class Meta: class Meta:
model = PrivateMessage model = PrivateMessage
fields = ['title', 'content'] fields = ["title", "content"]
widgets = {} widgets = {}
if PagedownWidget is not None: if PagedownWidget is not None:
widgets['content'] = MathJaxPagedownWidget() widgets["content"] = MathJaxPagedownWidget()
class CustomAuthenticationForm(AuthenticationForm): class CustomAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CustomAuthenticationForm, self).__init__(*args, **kwargs) super(CustomAuthenticationForm, self).__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update({'placeholder': _('Username')}) self.fields["username"].widget.attrs.update({"placeholder": _("Username")})
self.fields['password'].widget.attrs.update({'placeholder': _('Password')}) self.fields["password"].widget.attrs.update({"placeholder": _("Password")})
self.has_google_auth = self._has_social_auth('GOOGLE_OAUTH2') self.has_google_auth = self._has_social_auth("GOOGLE_OAUTH2")
self.has_facebook_auth = self._has_social_auth('FACEBOOK') self.has_facebook_auth = self._has_social_auth("FACEBOOK")
self.has_github_auth = self._has_social_auth('GITHUB_SECURE') self.has_github_auth = self._has_social_auth("GITHUB_SECURE")
def _has_social_auth(self, key): def _has_social_auth(self, key):
return (getattr(settings, 'SOCIAL_AUTH_%s_KEY' % key, None) and return getattr(settings, "SOCIAL_AUTH_%s_KEY" % key, None) and getattr(
getattr(settings, 'SOCIAL_AUTH_%s_SECRET' % key, None)) settings, "SOCIAL_AUTH_%s_SECRET" % key, None
)
class NoAutoCompleteCharField(forms.CharField): class NoAutoCompleteCharField(forms.CharField):
def widget_attrs(self, widget): def widget_attrs(self, widget):
attrs = super(NoAutoCompleteCharField, self).widget_attrs(widget) attrs = super(NoAutoCompleteCharField, self).widget_attrs(widget)
attrs['autocomplete'] = 'off' attrs["autocomplete"] = "off"
return attrs return attrs
class TOTPForm(Form): class TOTPForm(Form):
TOLERANCE = settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES TOLERANCE = settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES
totp_token = NoAutoCompleteCharField(validators=[ totp_token = NoAutoCompleteCharField(
RegexValidator('^[0-9]{6}$', _('Two Factor Authentication tokens must be 6 decimal digits.')), validators=[
]) RegexValidator(
"^[0-9]{6}$",
_("Two Factor Authentication tokens must be 6 decimal digits."),
),
]
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.totp_key = kwargs.pop('totp_key') self.totp_key = kwargs.pop("totp_key")
super(TOTPForm, self).__init__(*args, **kwargs) super(TOTPForm, self).__init__(*args, **kwargs)
def clean_totp_token(self): def clean_totp_token(self):
if not pyotp.TOTP(self.totp_key).verify(self.cleaned_data['totp_token'], valid_window=self.TOLERANCE): if not pyotp.TOTP(self.totp_key).verify(
raise ValidationError(_('Invalid Two Factor Authentication token.')) self.cleaned_data["totp_token"], valid_window=self.TOLERANCE
):
raise ValidationError(_("Invalid Two Factor Authentication token."))
class ProblemCloneForm(Form): class ProblemCloneForm(Form):
code = CharField(max_length=20, validators=[RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))]) code = CharField(
max_length=20,
validators=[
RegexValidator("^[a-z0-9]+$", _("Problem code must be ^[a-z0-9]+$"))
],
)
def clean_code(self): def clean_code(self):
code = self.cleaned_data['code'] code = self.cleaned_data["code"]
if Problem.objects.filter(code=code).exists(): if Problem.objects.filter(code=code).exists():
raise ValidationError(_('Problem with code already exists.')) raise ValidationError(_("Problem with code already exists."))
return code return code
class ContestCloneForm(Form): class ContestCloneForm(Form):
key = CharField(max_length=20, validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))]) key = CharField(
max_length=20,
validators=[RegexValidator("^[a-z0-9]+$", _("Contest id must be ^[a-z0-9]+$"))],
)
def clean_key(self): def clean_key(self):
key = self.cleaned_data['key'] key = self.cleaned_data["key"]
if Contest.objects.filter(key=key).exists(): if Contest.objects.filter(key=key).exists():
raise ValidationError(_('Contest with key already exists.')) raise ValidationError(_("Contest with key already exists."))
return key return key
class ProblemPointsVoteForm(ModelForm): class ProblemPointsVoteForm(ModelForm):
class Meta: class Meta:
model = ProblemPointsVote model = ProblemPointsVote
fields = ['points'] fields = ["points"]

View file

@ -5,10 +5,10 @@ from django.db.models.query import QuerySet
class SearchQuerySet(QuerySet): class SearchQuerySet(QuerySet):
DEFAULT = '' DEFAULT = ""
BOOLEAN = ' IN BOOLEAN MODE' BOOLEAN = " IN BOOLEAN MODE"
NATURAL_LANGUAGE = ' IN NATURAL LANGUAGE MODE' NATURAL_LANGUAGE = " IN NATURAL LANGUAGE MODE"
QUERY_EXPANSION = ' WITH QUERY EXPANSION' QUERY_EXPANSION = " WITH QUERY EXPANSION"
def __init__(self, fields=None, **kwargs): def __init__(self, fields=None, **kwargs):
super(SearchQuerySet, self).__init__(**kwargs) super(SearchQuerySet, self).__init__(**kwargs)
@ -25,20 +25,26 @@ class SearchQuerySet(QuerySet):
# Get the table name and column names from the model # Get the table name and column names from the model
# in `table_name`.`column_name` style # in `table_name`.`column_name` style
columns = [meta.get_field(name).column for name in self._search_fields] columns = [meta.get_field(name).column for name in self._search_fields]
full_names = ['%s.%s' % full_names = [
(connection.ops.quote_name(meta.db_table), "%s.%s"
connection.ops.quote_name(column)) % (
for column in columns] connection.ops.quote_name(meta.db_table),
connection.ops.quote_name(column),
)
for column in columns
]
# Create the MATCH...AGAINST expressions # Create the MATCH...AGAINST expressions
fulltext_columns = ', '.join(full_names) fulltext_columns = ", ".join(full_names)
match_expr = ('MATCH(%s) AGAINST (%%s%s)' % (fulltext_columns, mode)) match_expr = "MATCH(%s) AGAINST (%%s%s)" % (fulltext_columns, mode)
# Add the extra SELECT and WHERE options # Add the extra SELECT and WHERE options
return self.extra(select={'relevance': match_expr}, return self.extra(
select_params=[query], select={"relevance": match_expr},
where=[match_expr], select_params=[query],
params=[query]) where=[match_expr],
params=[query],
)
class SearchManager(models.Manager): class SearchManager(models.Manager):

View file

@ -1,10 +1,10 @@
from django.utils.html import escape, mark_safe from django.utils.html import escape, mark_safe
__all__ = ['highlight_code'] __all__ = ["highlight_code"]
def _make_pre_code(code): def _make_pre_code(code):
return mark_safe('<pre>' + escape(code) + '</pre>') return mark_safe("<pre>" + escape(code) + "</pre>")
def _wrap_code(inner): def _wrap_code(inner):
@ -20,19 +20,28 @@ try:
import pygments.formatters.html import pygments.formatters.html
import pygments.util import pygments.util
except ImportError: except ImportError:
def highlight_code(code, language, cssclass=None): def highlight_code(code, language, cssclass=None):
return _make_pre_code(code) return _make_pre_code(code)
else: else:
class HtmlCodeFormatter(pygments.formatters.HtmlFormatter): class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
def wrap(self, source, outfile): def wrap(self, source, outfile):
return self._wrap_div(self._wrap_pre(_wrap_code(source))) return self._wrap_div(self._wrap_pre(_wrap_code(source)))
def highlight_code(code, language, cssclass='codehilite', linenos=True): def highlight_code(code, language, cssclass="codehilite", linenos=True):
try: try:
lexer = pygments.lexers.get_lexer_by_name(language) lexer = pygments.lexers.get_lexer_by_name(language)
except pygments.util.ClassNotFound: except pygments.util.ClassNotFound:
return _make_pre_code(code) return _make_pre_code(code)
if linenos: if linenos:
return mark_safe(pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass, linenos='table'))) return mark_safe(
return mark_safe(pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass))) pygments.highlight(
code, lexer, HtmlCodeFormatter(cssclass=cssclass, linenos="table")
)
)
return mark_safe(
pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass))
)

View file

@ -8,19 +8,33 @@ from statici18n.templatetags.statici18n import inlinei18n
from judge.highlight_code import highlight_code from judge.highlight_code import highlight_code
from judge.user_translations import gettext from judge.user_translations import gettext
from . import (camo, chat, datetime, filesize, gravatar, language, markdown, rating, reference, render, social, from . import (
spaceless, submission, timedelta) camo,
chat,
datetime,
filesize,
gravatar,
language,
markdown,
rating,
reference,
render,
social,
spaceless,
submission,
timedelta,
)
from . import registry from . import registry
registry.function('str', str) registry.function("str", str)
registry.filter('str', str) registry.filter("str", str)
registry.filter('json', json.dumps) registry.filter("json", json.dumps)
registry.filter('highlight', highlight_code) registry.filter("highlight", highlight_code)
registry.filter('urlquote', urlquote) registry.filter("urlquote", urlquote)
registry.filter('roundfloat', round) registry.filter("roundfloat", round)
registry.function('inlinei18n', inlinei18n) registry.function("inlinei18n", inlinei18n)
registry.function('mptt_tree', get_cached_trees) registry.function("mptt_tree", get_cached_trees)
registry.function('user_trans', gettext) registry.function("user_trans", gettext)
@registry.function @registry.function

View file

@ -1,6 +1,7 @@
from judge.utils.camo import client as camo_client from judge.utils.camo import client as camo_client
from . import registry from . import registry
@registry.filter @registry.filter
def camo(url): def camo(url):
if camo_client is None: if camo_client is None:

View file

@ -1,6 +1,7 @@
from . import registry from . import registry
from chat_box.utils import encrypt_url from chat_box.utils import encrypt_url
@registry.function @registry.function
def chat_param(request_profile, profile): def chat_param(request_profile, profile):
return encrypt_url(request_profile.id, profile.id) return encrypt_url(request_profile.id, profile.id)

View file

@ -10,7 +10,7 @@ from . import registry
def localtime_wrapper(func): def localtime_wrapper(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(datetime, *args, **kwargs): def wrapper(datetime, *args, **kwargs):
if getattr(datetime, 'convert_to_local_time', True): if getattr(datetime, "convert_to_local_time", True):
datetime = localtime(datetime) datetime = localtime(datetime)
return func(datetime, *args, **kwargs) return func(datetime, *args, **kwargs)
@ -22,6 +22,6 @@ registry.filter(localtime_wrapper(time))
@registry.function @registry.function
@registry.render_with('widgets/relative-time.html') @registry.render_with("widgets/relative-time.html")
def relative_time(time, format=_('N j, Y, g:i a'), rel=_('{time}'), abs=_('on {time}')): def relative_time(time, format=_("N j, Y, g:i a"), rel=_("{time}"), abs=_("on {time}")):
return {'time': time, 'format': format, 'rel_format': rel, 'abs_format': abs} return {"time": time, "format": format, "rel_format": rel, "abs_format": abs}

View file

@ -13,24 +13,28 @@ def _format_size(bytes, callback):
PB = 1 << 50 PB = 1 << 50
if bytes < KB: if bytes < KB:
return callback('', bytes) return callback("", bytes)
elif bytes < MB: elif bytes < MB:
return callback('K', bytes / KB) return callback("K", bytes / KB)
elif bytes < GB: elif bytes < GB:
return callback('M', bytes / MB) return callback("M", bytes / MB)
elif bytes < TB: elif bytes < TB:
return callback('G', bytes / GB) return callback("G", bytes / GB)
elif bytes < PB: elif bytes < PB:
return callback('T', bytes / TB) return callback("T", bytes / TB)
else: else:
return callback('P', bytes / PB) return callback("P", bytes / PB)
@registry.filter @registry.filter
def kbdetailformat(bytes): def kbdetailformat(bytes):
return avoid_wrapping(_format_size(bytes * 1024, lambda x, y: ['%d %sB', '%.2f %sB'][bool(x)] % (y, x))) return avoid_wrapping(
_format_size(
bytes * 1024, lambda x, y: ["%d %sB", "%.2f %sB"][bool(x)] % (y, x)
)
)
@registry.filter @registry.filter
def kbsimpleformat(kb): def kbsimpleformat(kb):
return _format_size(kb * 1024, lambda x, y: '%.0f%s' % (y, x or 'B')) return _format_size(kb * 1024, lambda x, y: "%.0f%s" % (y, x or "B"))

View file

@ -17,9 +17,13 @@ def gravatar(email, size=80, default=None):
elif isinstance(email, AbstractUser): elif isinstance(email, AbstractUser):
email = email.email email = email.email
gravatar_url = '//www.gravatar.com/avatar/' + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest() + '?' gravatar_url = (
args = {'d': 'identicon', 's': str(size)} "//www.gravatar.com/avatar/"
+ hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()
+ "?"
)
args = {"d": "identicon", "s": str(size)}
if default: if default:
args['f'] = 'y' args["f"] = "y"
gravatar_url += urlencode(args) gravatar_url += urlencode(args)
return gravatar_url return gravatar_url

View file

@ -3,7 +3,7 @@ from django.utils import translation
from . import registry from . import registry
@registry.function('language_info') @registry.function("language_info")
def get_language_info(language): def get_language_info(language):
# ``language`` is either a language code string or a sequence # ``language`` is either a language code string or a sequence
# with the language code as its first item # with the language code as its first item
@ -13,6 +13,6 @@ def get_language_info(language):
return translation.get_language_info(str(language)) return translation.get_language_info(str(language))
@registry.function('language_info_list') @registry.function("language_info_list")
def get_language_info_list(langs): def get_language_info_list(langs):
return [get_language_info(lang) for lang in langs] return [get_language_info(lang) for lang in langs]

View file

@ -12,22 +12,28 @@ from lxml.etree import ParserError, XMLSyntaxError
from judge.highlight_code import highlight_code from judge.highlight_code import highlight_code
from judge.jinja2.markdown.lazy_load import lazy_load as lazy_load_processor from judge.jinja2.markdown.lazy_load import lazy_load as lazy_load_processor
from judge.jinja2.markdown.math import MathInlineGrammar, MathInlineLexer, MathRenderer from judge.jinja2.markdown.math import MathInlineGrammar, MathInlineLexer, MathRenderer
from judge.jinja2.markdown.spoiler import SpoilerInlineGrammar, SpoilerInlineLexer, SpoilerRenderer from judge.jinja2.markdown.spoiler import (
SpoilerInlineGrammar,
SpoilerInlineLexer,
SpoilerRenderer,
)
from judge.utils.camo import client as camo_client from judge.utils.camo import client as camo_client
from judge.utils.texoid import TEXOID_ENABLED, TexoidRenderer from judge.utils.texoid import TEXOID_ENABLED, TexoidRenderer
from .. import registry from .. import registry
logger = logging.getLogger('judge.html') logger = logging.getLogger("judge.html")
NOFOLLOW_WHITELIST = settings.NOFOLLOW_EXCLUDED NOFOLLOW_WHITELIST = settings.NOFOLLOW_EXCLUDED
class CodeSafeInlineGrammar(mistune.InlineGrammar): class CodeSafeInlineGrammar(mistune.InlineGrammar):
double_emphasis = re.compile(r'^\*{2}([\s\S]+?)()\*{2}(?!\*)') # **word** double_emphasis = re.compile(r"^\*{2}([\s\S]+?)()\*{2}(?!\*)") # **word**
emphasis = re.compile(r'^\*((?:\*\*|[^\*])+?)()\*(?!\*)') # *word* emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)()\*(?!\*)") # *word*
class AwesomeInlineGrammar(MathInlineGrammar, SpoilerInlineGrammar, CodeSafeInlineGrammar): class AwesomeInlineGrammar(
MathInlineGrammar, SpoilerInlineGrammar, CodeSafeInlineGrammar
):
pass pass
@ -37,8 +43,8 @@ class AwesomeInlineLexer(MathInlineLexer, SpoilerInlineLexer, mistune.InlineLexe
class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer): class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.nofollow = kwargs.pop('nofollow', True) self.nofollow = kwargs.pop("nofollow", True)
self.texoid = TexoidRenderer() if kwargs.pop('texoid', False) else None self.texoid = TexoidRenderer() if kwargs.pop("texoid", False) else None
self.parser = HTMLParser() self.parser = HTMLParser()
super(AwesomeRenderer, self).__init__(*args, **kwargs) super(AwesomeRenderer, self).__init__(*args, **kwargs)
@ -51,18 +57,18 @@ class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer):
else: else:
if url.netloc and url.netloc not in NOFOLLOW_WHITELIST: if url.netloc and url.netloc not in NOFOLLOW_WHITELIST:
return ' rel="nofollow"' return ' rel="nofollow"'
return '' return ""
def autolink(self, link, is_email=False): def autolink(self, link, is_email=False):
text = link = mistune.escape(link) text = link = mistune.escape(link)
if is_email: if is_email:
link = 'mailto:%s' % link link = "mailto:%s" % link
return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text) return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text)
def table(self, header, body): def table(self, header, body):
return ( return (
'<table class="table">\n<thead>%s</thead>\n' '<table class="table">\n<thead>%s</thead>\n'
'<tbody>\n%s</tbody>\n</table>\n' "<tbody>\n%s</tbody>\n</table>\n"
) % (header, body) ) % (header, body)
def link(self, link, title, text): def link(self, link, title, text):
@ -70,40 +76,53 @@ class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer):
if not title: if not title:
return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text) return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text)
title = mistune.escape(title, quote=True) title = mistune.escape(title, quote=True)
return '<a href="%s" title="%s"%s>%s</a>' % (link, title, self._link_rel(link), text) return '<a href="%s" title="%s"%s>%s</a>' % (
link,
title,
self._link_rel(link),
text,
)
def block_code(self, code, lang=None): def block_code(self, code, lang=None):
if not lang: if not lang:
return '\n<pre><code>%s</code></pre>\n' % mistune.escape(code).rstrip() return "\n<pre><code>%s</code></pre>\n" % mistune.escape(code).rstrip()
return highlight_code(code, lang) return highlight_code(code, lang)
def block_html(self, html): def block_html(self, html):
if self.texoid and html.startswith('<latex'): if self.texoid and html.startswith("<latex"):
attr = html[6:html.index('>')] attr = html[6 : html.index(">")]
latex = html[html.index('>') + 1:html.rindex('<')] latex = html[html.index(">") + 1 : html.rindex("<")]
latex = self.parser.unescape(latex) latex = self.parser.unescape(latex)
result = self.texoid.get_result(latex) result = self.texoid.get_result(latex)
if not result: if not result:
return '<pre>%s</pre>' % mistune.escape(latex, smart_amp=False) return "<pre>%s</pre>" % mistune.escape(latex, smart_amp=False)
elif 'error' not in result: elif "error" not in result:
img = ('''<img src="%(svg)s" onerror="this.src='%(png)s';this.onerror=null"''' img = (
'width="%(width)s" height="%(height)s"%(tail)s>') % { '''<img src="%(svg)s" onerror="this.src='%(png)s';this.onerror=null"'''
'svg': result['svg'], 'png': result['png'], 'width="%(width)s" height="%(height)s"%(tail)s>'
'width': result['meta']['width'], 'height': result['meta']['height'], ) % {
'tail': ' /' if self.options.get('use_xhtml') else '', "svg": result["svg"],
"png": result["png"],
"width": result["meta"]["width"],
"height": result["meta"]["height"],
"tail": " /" if self.options.get("use_xhtml") else "",
} }
style = ['max-width: 100%', style = [
'height: %s' % result['meta']['height'], "max-width: 100%",
'max-height: %s' % result['meta']['height'], "height: %s" % result["meta"]["height"],
'width: %s' % result['meta']['height']] "max-height: %s" % result["meta"]["height"],
if 'inline' in attr: "width: %s" % result["meta"]["height"],
tag = 'span' ]
if "inline" in attr:
tag = "span"
else: else:
tag = 'div' tag = "div"
style += ['text-align: center'] style += ["text-align: center"]
return '<%s style="%s">%s</%s>' % (tag, ';'.join(style), img, tag) return '<%s style="%s">%s</%s>' % (tag, ";".join(style), img, tag)
else: else:
return '<pre>%s</pre>' % mistune.escape(result['error'], smart_amp=False) return "<pre>%s</pre>" % mistune.escape(
result["error"], smart_amp=False
)
return super(AwesomeRenderer, self).block_html(html) return super(AwesomeRenderer, self).block_html(html)
def header(self, text, level, *args, **kwargs): def header(self, text, level, *args, **kwargs):
@ -113,30 +132,41 @@ class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer):
@registry.filter @registry.filter
def markdown(value, style, math_engine=None, lazy_load=False): def markdown(value, style, math_engine=None, lazy_load=False):
styles = settings.MARKDOWN_STYLES.get(style, settings.MARKDOWN_DEFAULT_STYLE) styles = settings.MARKDOWN_STYLES.get(style, settings.MARKDOWN_DEFAULT_STYLE)
escape = styles.get('safe_mode', True) escape = styles.get("safe_mode", True)
nofollow = styles.get('nofollow', True) nofollow = styles.get("nofollow", True)
texoid = TEXOID_ENABLED and styles.get('texoid', False) texoid = TEXOID_ENABLED and styles.get("texoid", False)
math = hasattr(settings, 'MATHOID_URL') and styles.get('math', False) math = hasattr(settings, "MATHOID_URL") and styles.get("math", False)
post_processors = [] post_processors = []
if styles.get('use_camo', False) and camo_client is not None: if styles.get("use_camo", False) and camo_client is not None:
post_processors.append(camo_client.update_tree) post_processors.append(camo_client.update_tree)
if lazy_load: if lazy_load:
post_processors.append(lazy_load_processor) post_processors.append(lazy_load_processor)
renderer = AwesomeRenderer(escape=escape, nofollow=nofollow, texoid=texoid, renderer = AwesomeRenderer(
math=math and math_engine is not None, math_engine=math_engine) escape=escape,
markdown = mistune.Markdown(renderer=renderer, inline=AwesomeInlineLexer, nofollow=nofollow,
parse_block_html=1, parse_inline_html=1) texoid=texoid,
math=math and math_engine is not None,
math_engine=math_engine,
)
markdown = mistune.Markdown(
renderer=renderer,
inline=AwesomeInlineLexer,
parse_block_html=1,
parse_inline_html=1,
)
result = markdown(value) result = markdown(value)
if post_processors: if post_processors:
try: try:
tree = html.fromstring(result, parser=html.HTMLParser(recover=True)) tree = html.fromstring(result, parser=html.HTMLParser(recover=True))
except (XMLSyntaxError, ParserError) as e: except (XMLSyntaxError, ParserError) as e:
if result and (not isinstance(e, ParserError) or e.args[0] != 'Document is empty'): if result and (
logger.exception('Failed to parse HTML string') not isinstance(e, ParserError) or e.args[0] != "Document is empty"
tree = html.Element('div') ):
logger.exception("Failed to parse HTML string")
tree = html.Element("div")
for processor in post_processors: for processor in post_processors:
processor(tree) processor(tree)
result = html.tostring(tree, encoding='unicode') result = html.tostring(tree, encoding="unicode")
return Markup(result) return Markup(result)

View file

@ -5,16 +5,16 @@ from lxml import html
def lazy_load(tree): def lazy_load(tree):
blank = static('blank.gif') blank = static("blank.gif")
for img in tree.xpath('.//img'): for img in tree.xpath(".//img"):
src = img.get('src', '') src = img.get("src", "")
if src.startswith('data') or '-math' in img.get('class', ''): if src.startswith("data") or "-math" in img.get("class", ""):
continue continue
noscript = html.Element('noscript') noscript = html.Element("noscript")
copy = deepcopy(img) copy = deepcopy(img)
copy.tail = '' copy.tail = ""
noscript.append(copy) noscript.append(copy)
img.addprevious(noscript) img.addprevious(noscript)
img.set('data-src', src) img.set("data-src", src)
img.set('src', blank) img.set("src", blank)
img.set('class', img.get('class') + ' unveil' if img.get('class') else 'unveil') img.set("class", img.get("class") + " unveil" if img.get("class") else "unveil")

View file

@ -6,13 +6,13 @@ from django.conf import settings
from judge.utils.mathoid import MathoidMathParser from judge.utils.mathoid import MathoidMathParser
mistune._pre_tags.append('latex') mistune._pre_tags.append("latex")
class MathInlineGrammar(mistune.InlineGrammar): class MathInlineGrammar(mistune.InlineGrammar):
block_math = re.compile(r'^\$\$(.*?)\$\$|^\\\[(.*?)\\\]', re.DOTALL) block_math = re.compile(r"^\$\$(.*?)\$\$|^\\\[(.*?)\\\]", re.DOTALL)
math = re.compile(r'^~(.*?)~|^\\\((.*?)\\\)', re.DOTALL) math = re.compile(r"^~(.*?)~|^\\\((.*?)\\\)", re.DOTALL)
text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|\\[\[(]|https?://| {2,}\n|$)') text = re.compile(r"^[\s\S]+?(?=[\\<!\[_*`~$]|\\[\[(]|https?://| {2,}\n|$)")
class MathInlineLexer(mistune.InlineLexer): class MathInlineLexer(mistune.InlineLexer):
@ -21,8 +21,10 @@ class MathInlineLexer(mistune.InlineLexer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.default_rules = self.default_rules[:] self.default_rules = self.default_rules[:]
self.inline_html_rules = self.default_rules self.inline_html_rules = self.default_rules
self.default_rules.insert(self.default_rules.index('strikethrough') + 1, 'math') self.default_rules.insert(self.default_rules.index("strikethrough") + 1, "math")
self.default_rules.insert(self.default_rules.index('strikethrough') + 1, 'block_math') self.default_rules.insert(
self.default_rules.index("strikethrough") + 1, "block_math"
)
super(MathInlineLexer, self).__init__(*args, **kwargs) super(MathInlineLexer, self).__init__(*args, **kwargs)
def output_block_math(self, m): def output_block_math(self, m):
@ -35,14 +37,14 @@ class MathInlineLexer(mistune.InlineLexer):
tag = m.group(1) tag = m.group(1)
text = m.group(3) text = m.group(3)
if self._parse_inline_html and text: if self._parse_inline_html and text:
if tag == 'a': if tag == "a":
self._in_link = True self._in_link = True
text = self.output(text) text = self.output(text)
self._in_link = False self._in_link = False
else: else:
text = self.output(text) text = self.output(text)
extra = m.group(2) or '' extra = m.group(2) or ""
html = '<%s%s>%s</%s>' % (tag, extra, text, tag) html = "<%s%s>%s</%s>" % (tag, extra, text, tag)
else: else:
html = m.group(0) html = m.group(0)
return self.renderer.inline_html(html) return self.renderer.inline_html(html)
@ -50,18 +52,18 @@ class MathInlineLexer(mistune.InlineLexer):
class MathRenderer(mistune.Renderer): class MathRenderer(mistune.Renderer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if kwargs.pop('math', False) and settings.MATHOID_URL != False: if kwargs.pop("math", False) and settings.MATHOID_URL != False:
self.mathoid = MathoidMathParser(kwargs.pop('math_engine', None) or 'svg') self.mathoid = MathoidMathParser(kwargs.pop("math_engine", None) or "svg")
else: else:
self.mathoid = None self.mathoid = None
super(MathRenderer, self).__init__(*args, **kwargs) super(MathRenderer, self).__init__(*args, **kwargs)
def block_math(self, math): def block_math(self, math):
if self.mathoid is None or not math: if self.mathoid is None or not math:
return r'\[%s\]' % mistune.escape(str(math)) return r"\[%s\]" % mistune.escape(str(math))
return self.mathoid.display_math(math) return self.mathoid.display_math(math)
def math(self, math): def math(self, math):
if self.mathoid is None or not math: if self.mathoid is None or not math:
return r'\(%s\)' % mistune.escape(str(math)) return r"\(%s\)" % mistune.escape(str(math))
return self.mathoid.inline_math(math) return self.mathoid.inline_math(math)

View file

@ -3,14 +3,14 @@ import mistune
class SpoilerInlineGrammar(mistune.InlineGrammar): class SpoilerInlineGrammar(mistune.InlineGrammar):
spoiler = re.compile(r'^\|\|(.+?)\s+([\s\S]+?)\s*\|\|') spoiler = re.compile(r"^\|\|(.+?)\s+([\s\S]+?)\s*\|\|")
class SpoilerInlineLexer(mistune.InlineLexer): class SpoilerInlineLexer(mistune.InlineLexer):
grammar_class = SpoilerInlineGrammar grammar_class = SpoilerInlineGrammar
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.default_rules.insert(0, 'spoiler') self.default_rules.insert(0, "spoiler")
super(SpoilerInlineLexer, self).__init__(*args, **kwargs) super(SpoilerInlineLexer, self).__init__(*args, **kwargs)
def output_spoiler(self, m): def output_spoiler(self, m):
@ -19,9 +19,12 @@ class SpoilerInlineLexer(mistune.InlineLexer):
class SpoilerRenderer(mistune.Renderer): class SpoilerRenderer(mistune.Renderer):
def spoiler(self, summary, text): def spoiler(self, summary, text):
return '''<details> return """<details>
<summary style="color: brown"> <summary style="color: brown">
<span class="spoiler-summary">%s</span> <span class="spoiler-summary">%s</span>
</summary> </summary>
<div class="spoiler-text">%s</div> <div class="spoiler-text">%s</div>
</details>''' % (summary, text) </details>""" % (
summary,
text,
)

View file

@ -14,22 +14,22 @@ def _get_rating_value(func, obj):
return func(obj.rating) return func(obj.rating)
@registry.function('rating_class') @registry.function("rating_class")
def get_rating_class(obj): def get_rating_class(obj):
return _get_rating_value(rating_class, obj) or 'rate-none' return _get_rating_value(rating_class, obj) or "rate-none"
@registry.function(name='rating_name') @registry.function(name="rating_name")
def get_name(obj): def get_name(obj):
return _get_rating_value(rating_name, obj) or 'Unrated' return _get_rating_value(rating_name, obj) or "Unrated"
@registry.function(name='rating_progress') @registry.function(name="rating_progress")
def get_progress(obj): def get_progress(obj):
return _get_rating_value(rating_progress, obj) or 0.0 return _get_rating_value(rating_progress, obj) or 0.0
@registry.function @registry.function
@registry.render_with('user/rating.html') @registry.render_with("user/rating.html")
def rating_number(obj): def rating_number(obj):
return {'rating': obj} return {"rating": obj}

View file

@ -13,17 +13,17 @@ from judge.models import Contest, Problem, Profile
from judge.ratings import rating_class, rating_progress from judge.ratings import rating_class, rating_progress
from . import registry from . import registry
rereference = re.compile(r'\[(r?user):(\w+)\]') rereference = re.compile(r"\[(r?user):(\w+)\]")
def get_user(username, data): def get_user(username, data):
if not data: if not data:
element = Element('span') element = Element("span")
element.text = username element.text = username
return element return element
element = Element('span', {'class': Profile.get_user_css_class(*data)}) element = Element("span", {"class": Profile.get_user_css_class(*data)})
link = Element('a', {'href': reverse('user_page', args=[username])}) link = Element("a", {"href": reverse("user_page", args=[username])})
link.text = username link.text = username
element.append(link) element.append(link)
return element return element
@ -31,17 +31,21 @@ def get_user(username, data):
def get_user_rating(username, data): def get_user_rating(username, data):
if not data: if not data:
element = Element('span') element = Element("span")
element.text = username element.text = username
return element return element
rating = data[1] rating = data[1]
element = Element('a', {'class': 'rate-group', 'href': reverse('user_page', args=[username])}) element = Element(
"a", {"class": "rate-group", "href": reverse("user_page", args=[username])}
)
if rating: if rating:
rating_css = rating_class(rating) rating_css = rating_class(rating)
rate_box = Element('span', {'class': 'rate-box ' + rating_css}) rate_box = Element("span", {"class": "rate-box " + rating_css})
rate_box.append(Element('span', {'style': 'height: %3.fem' % rating_progress(rating)})) rate_box.append(
user = Element('span', {'class': 'rating ' + rating_css}) Element("span", {"style": "height: %3.fem" % rating_progress(rating)})
)
user = Element("span", {"class": "rating " + rating_css})
user.text = username user.text = username
element.append(rate_box) element.append(rate_box)
element.append(user) element.append(user)
@ -51,21 +55,24 @@ def get_user_rating(username, data):
def get_user_info(usernames): def get_user_info(usernames):
return {name: (rank, rating) for name, rank, rating in return {
Profile.objects.filter(user__username__in=usernames) name: (rank, rating)
.values_list('user__username', 'display_rank', 'rating')} for name, rank, rating in Profile.objects.filter(
user__username__in=usernames
).values_list("user__username", "display_rank", "rating")
}
def get_user_from_text(text): def get_user_from_text(text):
user_list = set() user_list = set()
for i in rereference.finditer(text): for i in rereference.finditer(text):
user_list.add(text[i.start() + 6: i.end() - 1]) user_list.add(text[i.start() + 6 : i.end() - 1])
return Profile.objects.filter(user__username__in=user_list) return Profile.objects.filter(user__username__in=user_list)
reference_map = { reference_map = {
'user': (get_user, get_user_info), "user": (get_user, get_user_info),
'ruser': (get_user_rating, get_user_info), "ruser": (get_user_rating, get_user_info),
} }
@ -77,9 +84,9 @@ def process_reference(text):
elements = [] elements = []
for piece in rereference.finditer(text): for piece in rereference.finditer(text):
if prev is None: if prev is None:
tail = text[last:piece.start()] tail = text[last : piece.start()]
else: else:
prev.append(text[last:piece.start()]) prev.append(text[last : piece.start()])
prev = list(piece.groups()) prev = list(piece.groups())
elements.append(prev) elements.append(prev)
last = piece.end() last = piece.end()
@ -143,52 +150,52 @@ def item_title(item):
return item.name return item.name
elif isinstance(item, Contest): elif isinstance(item, Contest):
return item.name return item.name
return '<Unknown>' return "<Unknown>"
@registry.function @registry.function
@registry.render_with('user/link.html') @registry.render_with("user/link.html")
def link_user(user): def link_user(user):
if isinstance(user, Profile): if isinstance(user, Profile):
user, profile = user.user, user user, profile = user.user, user
elif isinstance(user, AbstractUser): elif isinstance(user, AbstractUser):
profile = user.profile profile = user.profile
elif type(user).__name__ == 'ContestRankingProfile': elif type(user).__name__ == "ContestRankingProfile":
user, profile = user.user, user user, profile = user.user, user
else: else:
raise ValueError('Expected profile or user, got %s' % (type(user),)) raise ValueError("Expected profile or user, got %s" % (type(user),))
return {'user': user, 'profile': profile} return {"user": user, "profile": profile}
@registry.function @registry.function
@registry.render_with('user/link-list.html') @registry.render_with("user/link-list.html")
def link_users(users): def link_users(users):
return {'users': users} return {"users": users}
@registry.function @registry.function
@registry.render_with('runtime-version-fragment.html') @registry.render_with("runtime-version-fragment.html")
def runtime_versions(versions): def runtime_versions(versions):
return {'runtime_versions': versions} return {"runtime_versions": versions}
@registry.filter(name='absolutify') @registry.filter(name="absolutify")
def absolute_links(text, url): def absolute_links(text, url):
tree = lxml_tree.fromstring(text) tree = lxml_tree.fromstring(text)
for anchor in tree.xpath('.//a'): for anchor in tree.xpath(".//a"):
href = anchor.get('href') href = anchor.get("href")
if href: if href:
anchor.set('href', urljoin(url, href)) anchor.set("href", urljoin(url, href))
return tree return tree
@registry.function(name='urljoin') @registry.function(name="urljoin")
def join(first, second, *rest): def join(first, second, *rest):
if not rest: if not rest:
return urljoin(first, second) return urljoin(first, second)
return urljoin(urljoin(first, second), *rest) return urljoin(urljoin(first, second), *rest)
@registry.filter(name='ansi2html') @registry.filter(name="ansi2html")
def ansi2html(s): def ansi2html(s):
return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False)) return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False))

View file

@ -5,7 +5,7 @@ tests = {}
filters = {} filters = {}
extensions = [] extensions = []
__all__ = ['render_with', 'function', 'filter', 'test', 'extension'] __all__ = ["render_with", "function", "filter", "test", "extension"]
def _store_function(store, func, name=None): def _store_function(store, func, name=None):
@ -16,6 +16,7 @@ def _store_function(store, func, name=None):
def _register_function(store, name, func): def _register_function(store, name, func):
if name is None and func is None: if name is None and func is None:
def decorator(func): def decorator(func):
_store_function(store, func) _store_function(store, func)
return func return func
@ -26,6 +27,7 @@ def _register_function(store, name, func):
_store_function(store, name) _store_function(store, name)
return name return name
else: else:
def decorator(func): def decorator(func):
_store_function(store, func, name) _store_function(store, func, name)
return func return func

View file

@ -1,5 +1,9 @@
from django.template import (Context, Template as DjangoTemplate, TemplateSyntaxError as DjangoTemplateSyntaxError, from django.template import (
VariableDoesNotExist) Context,
Template as DjangoTemplate,
TemplateSyntaxError as DjangoTemplateSyntaxError,
VariableDoesNotExist,
)
from . import registry from . import registry
@ -24,4 +28,4 @@ def render_django(template, **context):
try: try:
return compile_template(template).render(Context(context)) return compile_template(template).render(Context(context))
except (VariableDoesNotExist, DjangoTemplateSyntaxError): except (VariableDoesNotExist, DjangoTemplateSyntaxError):
return 'Error rendering: %r' % template return "Error rendering: %r" % template

View file

@ -1,13 +1,29 @@
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django_social_share.templatetags.social_share import post_to_facebook_url, post_to_gplus_url, post_to_twitter_url from django_social_share.templatetags.social_share import (
post_to_facebook_url,
post_to_gplus_url,
post_to_twitter_url,
)
from . import registry from . import registry
SHARES = [ SHARES = [
('post_to_twitter', 'django_social_share/templatetags/post_to_twitter.html', post_to_twitter_url), (
('post_to_facebook', 'django_social_share/templatetags/post_to_facebook.html', post_to_facebook_url), "post_to_twitter",
('post_to_gplus', 'django_social_share/templatetags/post_to_gplus.html', post_to_gplus_url), "django_social_share/templatetags/post_to_twitter.html",
post_to_twitter_url,
),
(
"post_to_facebook",
"django_social_share/templatetags/post_to_facebook.html",
post_to_facebook_url,
),
(
"post_to_gplus",
"django_social_share/templatetags/post_to_gplus.html",
post_to_gplus_url,
),
# For future versions: # For future versions:
# ('post_to_linkedin', 'django_social_share/templatetags/post_to_linkedin.html', post_to_linkedin_url), # ('post_to_linkedin', 'django_social_share/templatetags/post_to_linkedin.html', post_to_linkedin_url),
# ('post_to_reddit', 'django_social_share/templatetags/post_to_reddit.html', post_to_reddit_url), # ('post_to_reddit', 'django_social_share/templatetags/post_to_reddit.html', post_to_reddit_url),
@ -17,7 +33,7 @@ SHARES = [
def make_func(name, template, url_func): def make_func(name, template, url_func):
def func(request, *args): def func(request, *args):
link_text = args[-1] link_text = args[-1]
context = {'request': request, 'link_text': mark_safe(link_text)} context = {"request": request, "link_text": mark_safe(link_text)}
context = url_func(context, *args[:-1]) context = url_func(context, *args[:-1])
return mark_safe(get_template(template).render(context)) return mark_safe(get_template(template).render(context))
@ -31,4 +47,6 @@ for name, template, url_func in SHARES:
@registry.function @registry.function
def recaptcha_init(language=None): def recaptcha_init(language=None):
return get_template('snowpenguin/recaptcha/recaptcha_init.html').render({'explicit': False, 'language': language}) return get_template("snowpenguin/recaptcha/recaptcha_init.html").render(
{"explicit": False, "language": language}
)

View file

@ -15,15 +15,17 @@ class SpacelessExtension(Extension):
https://stackoverflow.com/a/23741298/1090657 https://stackoverflow.com/a/23741298/1090657
""" """
tags = {'spaceless'} tags = {"spaceless"}
def parse(self, parser): def parse(self, parser):
lineno = next(parser.stream).lineno lineno = next(parser.stream).lineno
body = parser.parse_statements(['name:endspaceless'], drop_needle=True) body = parser.parse_statements(["name:endspaceless"], drop_needle=True)
return nodes.CallBlock( return nodes.CallBlock(
self.call_method('_strip_spaces', [], [], None, None), self.call_method("_strip_spaces", [], [], None, None),
[], [], body, [],
[],
body,
).set_lineno(lineno) ).set_lineno(lineno)
def _strip_spaces(self, caller=None): def _strip_spaces(self, caller=None):
return Markup(re.sub(r'>\s+<', '><', caller().unescape().strip())) return Markup(re.sub(r">\s+<", "><", caller().unescape().strip()))

View file

@ -2,7 +2,9 @@ from . import registry
@registry.function @registry.function
def submission_layout(submission, profile_id, user, editable_problem_ids, completed_problem_ids): def submission_layout(
submission, profile_id, user, editable_problem_ids, completed_problem_ids
):
problem_id = submission.problem_id problem_id = submission.problem_id
can_view = False can_view = False
@ -12,13 +14,15 @@ def submission_layout(submission, profile_id, user, editable_problem_ids, comple
if profile_id == submission.user_id: if profile_id == submission.user_id:
can_view = True can_view = True
if user.has_perm('judge.change_submission'): if user.has_perm("judge.change_submission"):
can_view = True can_view = True
if submission.problem_id in completed_problem_ids: if submission.problem_id in completed_problem_ids:
can_view |= submission.problem.is_public or profile_id in submission.problem.tester_ids can_view |= (
submission.problem.is_public or profile_id in submission.problem.tester_ids
)
if not can_view and hasattr(submission, 'contest'): if not can_view and hasattr(submission, "contest"):
contest = submission.contest.participation.contest contest = submission.contest.participation.contest
if contest.is_editable_by(user): if contest.is_editable_by(user):
can_view = True can_view = True

View file

@ -5,14 +5,14 @@ from . import registry
@registry.filter @registry.filter
def timedelta(value, display='long'): def timedelta(value, display="long"):
if value is None: if value is None:
return value return value
return nice_repr(value, display) return nice_repr(value, display)
@registry.filter @registry.filter
def timestampdelta(value, display='long'): def timestampdelta(value, display="long"):
value = datetime.timedelta(seconds=value) value = datetime.timedelta(seconds=value)
return timedelta(value, display) return timedelta(value, display)
@ -23,8 +23,8 @@ def seconds(timedelta):
@registry.filter @registry.filter
@registry.render_with('time-remaining-fragment.html') @registry.render_with("time-remaining-fragment.html")
def as_countdown(time): def as_countdown(time):
time_now = datetime.datetime.now(datetime.timezone.utc) time_now = datetime.datetime.now(datetime.timezone.utc)
initial = abs(time - time_now) initial = abs(time - time_now)
return {'countdown': time, 'initial': initial} return {"countdown": time, "initial": initial}

View file

@ -8,43 +8,51 @@ from django.conf import settings
from judge import event_poster as event from judge import event_poster as event
logger = logging.getLogger('judge.judgeapi') logger = logging.getLogger("judge.judgeapi")
size_pack = struct.Struct('!I') size_pack = struct.Struct("!I")
def _post_update_submission(submission, done=False): def _post_update_submission(submission, done=False):
if submission.problem.is_public: if submission.problem.is_public:
event.post('submissions', {'type': 'done-submission' if done else 'update-submission', event.post(
'id': submission.id, "submissions",
'contest': submission.contest_key, {
'user': submission.user_id, 'problem': submission.problem_id, "type": "done-submission" if done else "update-submission",
'status': submission.status, 'language': submission.language.key}) "id": submission.id,
"contest": submission.contest_key,
"user": submission.user_id,
"problem": submission.problem_id,
"status": submission.status,
"language": submission.language.key,
},
)
def judge_request(packet, reply=True): def judge_request(packet, reply=True):
sock = socket.create_connection(settings.BRIDGED_DJANGO_CONNECT or sock = socket.create_connection(
settings.BRIDGED_DJANGO_ADDRESS[0]) settings.BRIDGED_DJANGO_CONNECT or settings.BRIDGED_DJANGO_ADDRESS[0]
)
output = json.dumps(packet, separators=(',', ':')) output = json.dumps(packet, separators=(",", ":"))
output = zlib.compress(output.encode('utf-8')) output = zlib.compress(output.encode("utf-8"))
writer = sock.makefile('wb') writer = sock.makefile("wb")
writer.write(size_pack.pack(len(output))) writer.write(size_pack.pack(len(output)))
writer.write(output) writer.write(output)
writer.close() writer.close()
if reply: if reply:
reader = sock.makefile('rb', -1) reader = sock.makefile("rb", -1)
input = reader.read(size_pack.size) input = reader.read(size_pack.size)
if not input: if not input:
raise ValueError('Judge did not respond') raise ValueError("Judge did not respond")
length = size_pack.unpack(input)[0] length = size_pack.unpack(input)[0]
input = reader.read(length) input = reader.read(length)
if not input: if not input:
raise ValueError('Judge did not respond') raise ValueError("Judge did not respond")
reader.close() reader.close()
sock.close() sock.close()
result = json.loads(zlib.decompress(input).decode('utf-8')) result = json.loads(zlib.decompress(input).decode("utf-8"))
return result return result
@ -56,13 +64,23 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No
REJUDGE_PRIORITY = 2 REJUDGE_PRIORITY = 2
BATCH_REJUDGE_PRIORITY = 3 BATCH_REJUDGE_PRIORITY = 3
updates = {'time': None, 'memory': None, 'points': None, 'result': None, 'error': None, updates = {
'was_rejudged': rejudge, 'status': 'QU'} "time": None,
"memory": None,
"points": None,
"result": None,
"error": None,
"was_rejudged": rejudge,
"status": "QU",
}
try: try:
# This is set proactively; it might get unset in judgecallback's on_grading_begin if the problem doesn't # This is set proactively; it might get unset in judgecallback's on_grading_begin if the problem doesn't
# actually have pretests stored on the judge. # actually have pretests stored on the judge.
updates['is_pretested'] = all(ContestSubmission.objects.filter(submission=submission) updates["is_pretested"] = all(
.values_list('problem__contest__run_pretests_only', 'problem__is_pretested')[0]) ContestSubmission.objects.filter(submission=submission).values_list(
"problem__contest__run_pretests_only", "problem__is_pretested"
)[0]
)
except IndexError: except IndexError:
priority = DEFAULT_PRIORITY priority = DEFAULT_PRIORITY
else: else:
@ -76,43 +94,65 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No
# as that would prevent people from knowing a submission is being scheduled for rejudging. # as that would prevent people from knowing a submission is being scheduled for rejudging.
# It is worth noting that this mechanism does not prevent a new rejudge from being scheduled # It is worth noting that this mechanism does not prevent a new rejudge from being scheduled
# while already queued, but that does not lead to data corruption. # while already queued, but that does not lead to data corruption.
if not Submission.objects.filter(id=submission.id).exclude(status__in=('P', 'G')).update(**updates): if (
not Submission.objects.filter(id=submission.id)
.exclude(status__in=("P", "G"))
.update(**updates)
):
return False return False
SubmissionTestCase.objects.filter(submission_id=submission.id).delete() SubmissionTestCase.objects.filter(submission_id=submission.id).delete()
try: try:
response = judge_request({ response = judge_request(
'name': 'submission-request', {
'submission-id': submission.id, "name": "submission-request",
'problem-id': submission.problem.code, "submission-id": submission.id,
'language': submission.language.key, "problem-id": submission.problem.code,
'source': submission.source.source, "language": submission.language.key,
'judge-id': judge_id, "source": submission.source.source,
'priority': BATCH_REJUDGE_PRIORITY if batch_rejudge else REJUDGE_PRIORITY if rejudge else priority, "judge-id": judge_id,
}) "priority": BATCH_REJUDGE_PRIORITY
if batch_rejudge
else REJUDGE_PRIORITY
if rejudge
else priority,
}
)
except BaseException: except BaseException:
logger.exception('Failed to send request to judge') logger.exception("Failed to send request to judge")
Submission.objects.filter(id=submission.id).update(status='IE', result='IE') Submission.objects.filter(id=submission.id).update(status="IE", result="IE")
success = False success = False
else: else:
if response['name'] != 'submission-received' or response['submission-id'] != submission.id: if (
Submission.objects.filter(id=submission.id).update(status='IE', result='IE') response["name"] != "submission-received"
or response["submission-id"] != submission.id
):
Submission.objects.filter(id=submission.id).update(status="IE", result="IE")
_post_update_submission(submission) _post_update_submission(submission)
success = True success = True
return success return success
def disconnect_judge(judge, force=False): def disconnect_judge(judge, force=False):
judge_request({'name': 'disconnect-judge', 'judge-id': judge.name, 'force': force}, reply=False) judge_request(
{"name": "disconnect-judge", "judge-id": judge.name, "force": force},
reply=False,
)
def abort_submission(submission): def abort_submission(submission):
from .models import Submission from .models import Submission
response = judge_request({'name': 'terminate-submission', 'submission-id': submission.id})
response = judge_request(
{"name": "terminate-submission", "submission-id": submission.id}
)
# This defaults to true, so that in the case the JudgeList fails to remove the submission from the queue, # This defaults to true, so that in the case the JudgeList fails to remove the submission from the queue,
# and returns a bad-request, the submission is not falsely shown as "Aborted" when it will still be judged. # and returns a bad-request, the submission is not falsely shown as "Aborted" when it will still be judged.
if not response.get('judge-aborted', True): if not response.get("judge-aborted", True):
Submission.objects.filter(id=submission.id).update(status='AB', result='AB') Submission.objects.filter(id=submission.id).update(status="AB", result="AB")
event.post('sub_%s' % Submission.get_id_secret(submission.id), {'type': 'aborted-submission'}) event.post(
"sub_%s" % Submission.get_id_secret(submission.id),
{"type": "aborted-submission"},
)
_post_update_submission(submission, done=True) _post_update_submission(submission, done=True)

View file

@ -4,7 +4,7 @@ from django.utils.safestring import SafeData, mark_safe
from lxml import html from lxml import html
from lxml.etree import ParserError, XMLSyntaxError from lxml.etree import ParserError, XMLSyntaxError
logger = logging.getLogger('judge.html') logger = logging.getLogger("judge.html")
class HTMLTreeString(SafeData): class HTMLTreeString(SafeData):
@ -12,9 +12,11 @@ class HTMLTreeString(SafeData):
try: try:
self._tree = html.fromstring(str, parser=html.HTMLParser(recover=True)) self._tree = html.fromstring(str, parser=html.HTMLParser(recover=True))
except (XMLSyntaxError, ParserError) as e: except (XMLSyntaxError, ParserError) as e:
if str and (not isinstance(e, ParserError) or e.args[0] != 'Document is empty'): if str and (
logger.exception('Failed to parse HTML string') not isinstance(e, ParserError) or e.args[0] != "Document is empty"
self._tree = html.Element('div') ):
logger.exception("Failed to parse HTML string")
self._tree = html.Element("div")
def __getattr__(self, attr): def __getattr__(self, attr):
try: try:
@ -23,15 +25,15 @@ class HTMLTreeString(SafeData):
return getattr(str(self), attr) return getattr(str(self), attr)
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key[0] == '_': if key[0] == "_":
super(HTMLTreeString, self).__setattr__(key, value) super(HTMLTreeString, self).__setattr__(key, value)
setattr(self._tree, key, value) setattr(self._tree, key, value)
def __repr__(self): def __repr__(self):
return '<HTMLTreeString %r>' % str(self) return "<HTMLTreeString %r>" % str(self)
def __str__(self): def __str__(self):
return mark_safe(html.tostring(self._tree, encoding='unicode')) return mark_safe(html.tostring(self._tree, encoding="unicode"))
def __radd__(self, other): def __radd__(self, other):
return other + str(self) return other + str(self)

View file

@ -4,15 +4,14 @@ from judge.models import Judge
class Command(BaseCommand): class Command(BaseCommand):
help = 'create a judge' help = "create a judge"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('name', help='the name of the judge') parser.add_argument("name", help="the name of the judge")
parser.add_argument('auth_key', help='authentication key for the judge') parser.add_argument("auth_key", help="authentication key for the judge")
def handle(self, *args, **options): def handle(self, *args, **options):
judge = Judge() judge = Judge()
judge.name = options['name'] judge.name = options["name"]
judge.auth_key = options['auth_key'] judge.auth_key = options["auth_key"]
judge.save() judge.save()

View file

@ -6,27 +6,39 @@ from judge.models import Language, Profile
class Command(BaseCommand): class Command(BaseCommand):
help = 'creates a user' help = "creates a user"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('name', help='username') parser.add_argument("name", help="username")
parser.add_argument('email', help='email, not necessary to be resolvable') parser.add_argument("email", help="email, not necessary to be resolvable")
parser.add_argument('password', help='password for the user') parser.add_argument("password", help="password for the user")
parser.add_argument('language', nargs='?', default=settings.DEFAULT_USER_LANGUAGE, parser.add_argument(
help='default language ID for user') "language",
nargs="?",
default=settings.DEFAULT_USER_LANGUAGE,
help="default language ID for user",
)
parser.add_argument('--superuser', action='store_true', default=False, parser.add_argument(
help="if specified, creates user with superuser privileges") "--superuser",
parser.add_argument('--staff', action='store_true', default=False, action="store_true",
help="if specified, creates user with staff privileges") default=False,
help="if specified, creates user with superuser privileges",
)
parser.add_argument(
"--staff",
action="store_true",
default=False,
help="if specified, creates user with staff privileges",
)
def handle(self, *args, **options): def handle(self, *args, **options):
usr = User(username=options['name'], email=options['email'], is_active=True) usr = User(username=options["name"], email=options["email"], is_active=True)
usr.set_password(options['password']) usr.set_password(options["password"])
usr.is_superuser = options['superuser'] usr.is_superuser = options["superuser"]
usr.is_staff = options['staff'] usr.is_staff = options["staff"]
usr.save() usr.save()
profile = Profile(user=usr) profile = Profile(user=usr)
profile.language = Language.objects.get(key=options['language']) profile.language = Language.objects.get(key=options["language"])
profile.save() profile.save()

View file

@ -4,13 +4,13 @@ from judge.utils.camo import client as camo_client
class Command(BaseCommand): class Command(BaseCommand):
help = 'obtains the camo url for the specified url' help = "obtains the camo url for the specified url"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('url', help='url to use camo on') parser.add_argument("url", help="url to use camo on")
def handle(self, *args, **options): def handle(self, *args, **options):
if camo_client is None: if camo_client is None:
raise CommandError('Camo not available') raise CommandError("Camo not available")
print(camo_client.image_url(options['url'])) print(camo_client.image_url(options["url"]))

View file

@ -4,24 +4,30 @@ from judge.models import Language, LanguageLimit
class Command(BaseCommand): class Command(BaseCommand):
help = 'allows the problems that allow <source> to be submitted in <target>' help = "allows the problems that allow <source> to be submitted in <target>"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('source', help='language to copy from') parser.add_argument("source", help="language to copy from")
parser.add_argument('target', help='language to copy to') parser.add_argument("target", help="language to copy to")
def handle(self, *args, **options): def handle(self, *args, **options):
try: try:
source = Language.objects.get(key=options['source']) source = Language.objects.get(key=options["source"])
except Language.DoesNotExist: except Language.DoesNotExist:
raise CommandError('Invalid source language: %s' % options['source']) raise CommandError("Invalid source language: %s" % options["source"])
try: try:
target = Language.objects.get(key=options['target']) target = Language.objects.get(key=options["target"])
except Language.DoesNotExist: except Language.DoesNotExist:
raise CommandError('Invalid target language: %s' % options['target']) raise CommandError("Invalid target language: %s" % options["target"])
target.problem_set.set(source.problem_set.all()) target.problem_set.set(source.problem_set.all())
LanguageLimit.objects.bulk_create(LanguageLimit(problem=ll.problem, language=target, time_limit=ll.time_limit, LanguageLimit.objects.bulk_create(
memory_limit=ll.memory_limit) LanguageLimit(
for ll in LanguageLimit.objects.filter(language=source)) problem=ll.problem,
language=target,
time_limit=ll.time_limit,
memory_limit=ll.memory_limit,
)
for ll in LanguageLimit.objects.filter(language=source)
)

View file

@ -4,20 +4,20 @@ from judge.models import Problem, ProblemGroup, ProblemType
class Command(BaseCommand): class Command(BaseCommand):
help = 'create an empty problem' help = "create an empty problem"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('code', help='problem code') parser.add_argument("code", help="problem code")
parser.add_argument('name', help='problem title') parser.add_argument("name", help="problem title")
parser.add_argument('body', help='problem description') parser.add_argument("body", help="problem description")
parser.add_argument('type', help='problem type') parser.add_argument("type", help="problem type")
parser.add_argument('group', help='problem group') parser.add_argument("group", help="problem group")
def handle(self, *args, **options): def handle(self, *args, **options):
problem = Problem() problem = Problem()
problem.code = options['code'] problem.code = options["code"]
problem.name = options['name'] problem.name = options["name"]
problem.description = options['body'] problem.description = options["body"]
problem.group = ProblemGroup.objects.get(name=options['group']) problem.group = ProblemGroup.objects.get(name=options["group"])
problem.types = [ProblemType.objects.get(name=options['type'])] problem.types = [ProblemType.objects.get(name=options["type"])]
problem.save() problem.save()

View file

@ -7,41 +7,45 @@ from django.conf import settings
def gen_submissions(): def gen_submissions():
headers = ['uid', 'pid'] headers = ["uid", "pid"]
with open(os.path.join(settings.ML_DATA_PATH, 'submissions.csv'), 'w') as csvfile: with open(os.path.join(settings.ML_DATA_PATH, "submissions.csv"), "w") as csvfile:
f = csv.writer(csvfile) f = csv.writer(csvfile)
f.writerow(headers) f.writerow(headers)
last_pid = defaultdict(int) last_pid = defaultdict(int)
for u in Profile.objects.all(): for u in Profile.objects.all():
used = set() used = set()
print('Processing user', u.id) print("Processing user", u.id)
for s in Submission.objects.filter(user=u).order_by('-date'): for s in Submission.objects.filter(user=u).order_by("-date"):
if s.problem.id not in used: if s.problem.id not in used:
used.add(s.problem.id) used.add(s.problem.id)
f.writerow([u.id, s.problem.id]) f.writerow([u.id, s.problem.id])
def gen_users(): def gen_users():
headers = ['uid', 'username', 'rating', 'points'] headers = ["uid", "username", "rating", "points"]
with open(os.path.join(settings.ML_DATA_PATH, 'profiles.csv'), 'w') as csvfile: with open(os.path.join(settings.ML_DATA_PATH, "profiles.csv"), "w") as csvfile:
f = csv.writer(csvfile) f = csv.writer(csvfile)
f.writerow(headers) f.writerow(headers)
for u in Profile.objects.all(): for u in Profile.objects.all():
f.writerow([u.id, u.username, u.rating, u.performance_points]) f.writerow([u.id, u.username, u.rating, u.performance_points])
def gen_problems(): def gen_problems():
headers = ['pid', 'code', 'name', 'points', 'url'] headers = ["pid", "code", "name", "points", "url"]
with open(os.path.join(settings.ML_DATA_PATH, 'problems.csv'), 'w') as csvfile: with open(os.path.join(settings.ML_DATA_PATH, "problems.csv"), "w") as csvfile:
f = csv.writer(csvfile) f = csv.writer(csvfile)
f.writerow(headers) f.writerow(headers)
for p in Problem.objects.all(): for p in Problem.objects.all():
f.writerow([p.id, p.code, p.name, p.points, 'lqdoj.edu.vn/problem/' + p.code]) f.writerow(
[p.id, p.code, p.name, p.points, "lqdoj.edu.vn/problem/" + p.code]
)
class Command(BaseCommand): class Command(BaseCommand):
help = 'generate data for ML' help = "generate data for ML"
def handle(self, *args, **options): def handle(self, *args, **options):
gen_users() gen_users()

View file

@ -5,33 +5,69 @@ import sys
from django.conf import settings from django.conf import settings
from django.core.management import CommandError from django.core.management import CommandError
from django.core.management.commands.makemessages import Command as MakeMessagesCommand, check_programs from django.core.management.commands.makemessages import (
Command as MakeMessagesCommand,
check_programs,
)
from judge.models import NavigationBar, ProblemType from judge.models import NavigationBar, ProblemType
class Command(MakeMessagesCommand): class Command(MakeMessagesCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('--locale', '-l', default=[], dest='locale', action='append', parser.add_argument(
help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). ' "--locale",
'Can be used multiple times.') "-l",
parser.add_argument('--exclude', '-x', default=[], dest='exclude', action='append', default=[],
help='Locales to exclude. Default is none. Can be used multiple times.') dest="locale",
parser.add_argument('--all', '-a', action='store_true', dest='all', action="append",
default=False, help='Updates the message files for all existing locales.') help="Creates or updates the message files for the given locale(s) (e.g. pt_BR). "
parser.add_argument('--no-wrap', action='store_true', dest='no_wrap', "Can be used multiple times.",
default=False, help="Don't break long message lines into several lines.") )
parser.add_argument('--no-obsolete', action='store_true', dest='no_obsolete', parser.add_argument(
default=False, help="Remove obsolete message strings.") "--exclude",
parser.add_argument('--keep-pot', action='store_true', dest='keep_pot', "-x",
default=False, help="Keep .pot file after making messages. Useful when debugging.") default=[],
dest="exclude",
action="append",
help="Locales to exclude. Default is none. Can be used multiple times.",
)
parser.add_argument(
"--all",
"-a",
action="store_true",
dest="all",
default=False,
help="Updates the message files for all existing locales.",
)
parser.add_argument(
"--no-wrap",
action="store_true",
dest="no_wrap",
default=False,
help="Don't break long message lines into several lines.",
)
parser.add_argument(
"--no-obsolete",
action="store_true",
dest="no_obsolete",
default=False,
help="Remove obsolete message strings.",
)
parser.add_argument(
"--keep-pot",
action="store_true",
dest="keep_pot",
default=False,
help="Keep .pot file after making messages. Useful when debugging.",
)
def handle(self, *args, **options): def handle(self, *args, **options):
locale = options.get('locale') locale = options.get("locale")
exclude = options.get('exclude') exclude = options.get("exclude")
self.domain = 'dmoj-user' self.domain = "dmoj-user"
self.verbosity = options.get('verbosity') self.verbosity = options.get("verbosity")
process_all = options.get('all') process_all = options.get("all")
# Need to ensure that the i18n framework is enabled # Need to ensure that the i18n framework is enabled
if settings.configured: if settings.configured:
@ -40,43 +76,47 @@ class Command(MakeMessagesCommand):
settings.configure(USE_I18N=True) settings.configure(USE_I18N=True)
# Avoid messing with mutable class variables # Avoid messing with mutable class variables
if options.get('no_wrap'): if options.get("no_wrap"):
self.msgmerge_options = self.msgmerge_options[:] + ['--no-wrap'] self.msgmerge_options = self.msgmerge_options[:] + ["--no-wrap"]
self.msguniq_options = self.msguniq_options[:] + ['--no-wrap'] self.msguniq_options = self.msguniq_options[:] + ["--no-wrap"]
self.msgattrib_options = self.msgattrib_options[:] + ['--no-wrap'] self.msgattrib_options = self.msgattrib_options[:] + ["--no-wrap"]
self.xgettext_options = self.xgettext_options[:] + ['--no-wrap'] self.xgettext_options = self.xgettext_options[:] + ["--no-wrap"]
if options.get('no_location'): if options.get("no_location"):
self.msgmerge_options = self.msgmerge_options[:] + ['--no-location'] self.msgmerge_options = self.msgmerge_options[:] + ["--no-location"]
self.msguniq_options = self.msguniq_options[:] + ['--no-location'] self.msguniq_options = self.msguniq_options[:] + ["--no-location"]
self.msgattrib_options = self.msgattrib_options[:] + ['--no-location'] self.msgattrib_options = self.msgattrib_options[:] + ["--no-location"]
self.xgettext_options = self.xgettext_options[:] + ['--no-location'] self.xgettext_options = self.xgettext_options[:] + ["--no-location"]
self.no_obsolete = options.get('no_obsolete') self.no_obsolete = options.get("no_obsolete")
self.keep_pot = options.get('keep_pot') self.keep_pot = options.get("keep_pot")
if locale is None and not exclude and not process_all: if locale is None and not exclude and not process_all:
raise CommandError("Type '%s help %s' for usage information." % ( raise CommandError(
os.path.basename(sys.argv[0]), sys.argv[1])) "Type '%s help %s' for usage information."
% (os.path.basename(sys.argv[0]), sys.argv[1])
)
self.invoked_for_django = False self.invoked_for_django = False
self.locale_paths = [] self.locale_paths = []
self.default_locale_path = None self.default_locale_path = None
if os.path.isdir(os.path.join('conf', 'locale')): if os.path.isdir(os.path.join("conf", "locale")):
self.locale_paths = [os.path.abspath(os.path.join('conf', 'locale'))] self.locale_paths = [os.path.abspath(os.path.join("conf", "locale"))]
self.default_locale_path = self.locale_paths[0] self.default_locale_path = self.locale_paths[0]
self.invoked_for_django = True self.invoked_for_django = True
else: else:
self.locale_paths.extend(settings.LOCALE_PATHS) self.locale_paths.extend(settings.LOCALE_PATHS)
# Allow to run makemessages inside an app dir # Allow to run makemessages inside an app dir
if os.path.isdir('locale'): if os.path.isdir("locale"):
self.locale_paths.append(os.path.abspath('locale')) self.locale_paths.append(os.path.abspath("locale"))
if self.locale_paths: if self.locale_paths:
self.default_locale_path = self.locale_paths[0] self.default_locale_path = self.locale_paths[0]
if not os.path.exists(self.default_locale_path): if not os.path.exists(self.default_locale_path):
os.makedirs(self.default_locale_path) os.makedirs(self.default_locale_path)
# Build locale list # Build locale list
locale_dirs = list(filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path))) locale_dirs = list(
filter(os.path.isdir, glob.glob("%s/*" % self.default_locale_path))
)
all_locales = list(map(os.path.basename, locale_dirs)) all_locales = list(map(os.path.basename, locale_dirs))
# Account for excluded locales # Account for excluded locales
@ -87,9 +127,9 @@ class Command(MakeMessagesCommand):
locales = set(locales) - set(exclude) locales = set(locales) - set(exclude)
if locales: if locales:
check_programs('msguniq', 'msgmerge', 'msgattrib') check_programs("msguniq", "msgmerge", "msgattrib")
check_programs('xgettext') check_programs("xgettext")
try: try:
potfiles = self.build_potfiles() potfiles = self.build_potfiles()
@ -108,23 +148,33 @@ class Command(MakeMessagesCommand):
return [] return []
def _emit_message(self, potfile, string): def _emit_message(self, potfile, string):
potfile.write(''' potfile.write(
"""
msgid "%s" msgid "%s"
msgstr "" msgstr ""
''' % string.replace('\\', r'\\').replace('\t', '\\t').replace('\n', '\\n').replace('"', '\\"')) """
% string.replace("\\", r"\\")
.replace("\t", "\\t")
.replace("\n", "\\n")
.replace('"', '\\"')
)
def process_files(self, file_list): def process_files(self, file_list):
with io.open(os.path.join(self.default_locale_path, 'dmoj-user.pot'), 'w', encoding='utf-8') as potfile: with io.open(
os.path.join(self.default_locale_path, "dmoj-user.pot"),
"w",
encoding="utf-8",
) as potfile:
if self.verbosity > 1: if self.verbosity > 1:
self.stdout.write('processing navigation bar') self.stdout.write("processing navigation bar")
for label in NavigationBar.objects.values_list('label', flat=True): for label in NavigationBar.objects.values_list("label", flat=True):
if self.verbosity > 2: if self.verbosity > 2:
self.stdout.write('processing navigation item label "%s"\n' % label) self.stdout.write('processing navigation item label "%s"\n' % label)
self._emit_message(potfile, label) self._emit_message(potfile, label)
if self.verbosity > 1: if self.verbosity > 1:
self.stdout.write('processing problem types') self.stdout.write("processing problem types")
for name in ProblemType.objects.values_list('full_name', flat=True): for name in ProblemType.objects.values_list("full_name", flat=True):
if self.verbosity > 2: if self.verbosity > 2:
self.stdout.write('processing problem type name "%s"\n' % name) self.stdout.write('processing problem type name "%s"\n' % name)
self._emit_message(potfile, name) self._emit_message(potfile, name)

View file

@ -8,52 +8,98 @@ from django.template.loader import get_template
from django.utils import translation from django.utils import translation
from judge.models import Problem, ProblemTranslation from judge.models import Problem, ProblemTranslation
from judge.pdf_problems import DefaultPdfMaker, PhantomJSPdfMaker, PuppeteerPDFRender, SeleniumPDFRender, \ from judge.pdf_problems import (
SlimerJSPdfMaker DefaultPdfMaker,
PhantomJSPdfMaker,
PuppeteerPDFRender,
SeleniumPDFRender,
SlimerJSPdfMaker,
)
class Command(BaseCommand): class Command(BaseCommand):
help = 'renders a PDF file of a problem' help = "renders a PDF file of a problem"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('code', help='code of problem to render') parser.add_argument("code", help="code of problem to render")
parser.add_argument('directory', nargs='?', help='directory to store temporaries') parser.add_argument(
parser.add_argument('-l', '--language', default=settings.LANGUAGE_CODE, "directory", nargs="?", help="directory to store temporaries"
help='language to render PDF in') )
parser.add_argument('-p', '--phantomjs', action='store_const', const=PhantomJSPdfMaker, parser.add_argument(
default=DefaultPdfMaker, dest='engine') "-l",
parser.add_argument('-s', '--slimerjs', action='store_const', const=SlimerJSPdfMaker, dest='engine') "--language",
parser.add_argument('-c', '--chrome', '--puppeteer', action='store_const', default=settings.LANGUAGE_CODE,
const=PuppeteerPDFRender, dest='engine') help="language to render PDF in",
parser.add_argument('-S', '--selenium', action='store_const', const=SeleniumPDFRender, dest='engine') )
parser.add_argument(
"-p",
"--phantomjs",
action="store_const",
const=PhantomJSPdfMaker,
default=DefaultPdfMaker,
dest="engine",
)
parser.add_argument(
"-s",
"--slimerjs",
action="store_const",
const=SlimerJSPdfMaker,
dest="engine",
)
parser.add_argument(
"-c",
"--chrome",
"--puppeteer",
action="store_const",
const=PuppeteerPDFRender,
dest="engine",
)
parser.add_argument(
"-S",
"--selenium",
action="store_const",
const=SeleniumPDFRender,
dest="engine",
)
def handle(self, *args, **options): def handle(self, *args, **options):
try: try:
problem = Problem.objects.get(code=options['code']) problem = Problem.objects.get(code=options["code"])
except Problem.DoesNotExist: except Problem.DoesNotExist:
print('Bad problem code') print("Bad problem code")
return return
try: try:
trans = problem.translations.get(language=options['language']) trans = problem.translations.get(language=options["language"])
except ProblemTranslation.DoesNotExist: except ProblemTranslation.DoesNotExist:
trans = None trans = None
directory = options['directory'] directory = options["directory"]
with options['engine'](directory, clean_up=directory is None) as maker, \ with options["engine"](
translation.override(options['language']): directory, clean_up=directory is None
) as maker, translation.override(options["language"]):
problem_name = problem.name if trans is None else trans.name problem_name = problem.name if trans is None else trans.name
maker.html = get_template('problem/raw.html').render({ maker.html = (
'problem': problem, get_template("problem/raw.html")
'problem_name': problem_name, .render(
'description': problem.description if trans is None else trans.description, {
'url': '', "problem": problem,
'math_engine': maker.math_engine, "problem_name": problem_name,
}).replace('"//', '"https://').replace("'//", "'https://") "description": problem.description
if trans is None
else trans.description,
"url": "",
"math_engine": maker.math_engine,
}
)
.replace('"//', '"https://')
.replace("'//", "'https://")
)
maker.title = problem_name maker.title = problem_name
for file in ('style.css', 'pygment-github.css', 'mathjax_config.js'): for file in ("style.css", "pygment-github.css", "mathjax_config.js"):
maker.load(file, os.path.join(settings.DMOJ_RESOURCES, file)) maker.load(file, os.path.join(settings.DMOJ_RESOURCES, file))
maker.make(debug=True) maker.make(debug=True)
if not maker.success: if not maker.success:
print(maker.log, file=sys.stderr) print(maker.log, file=sys.stderr)
elif directory is None: elif directory is None:
shutil.move(maker.pdffile, problem.code + '.pdf') shutil.move(maker.pdffile, problem.code + ".pdf")

View file

@ -6,42 +6,50 @@ from judge.models import Contest, ContestParticipation, Submission
class Command(BaseCommand): class Command(BaseCommand):
help = 'Checks for duplicate code using MOSS' help = "Checks for duplicate code using MOSS"
LANG_MAPPING = { LANG_MAPPING = {
('C++', MOSS_LANG_CC), ("C++", MOSS_LANG_CC),
('C', MOSS_LANG_C), ("C", MOSS_LANG_C),
('Java', MOSS_LANG_JAVA), ("Java", MOSS_LANG_JAVA),
('Python', MOSS_LANG_PYTHON), ("Python", MOSS_LANG_PYTHON),
('Pascal', MOSS_LANG_PASCAL), ("Pascal", MOSS_LANG_PASCAL),
} }
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('contest', help='the id of the contest') parser.add_argument("contest", help="the id of the contest")
def handle(self, *args, **options): def handle(self, *args, **options):
moss_api_key = settings.MOSS_API_KEY moss_api_key = settings.MOSS_API_KEY
if moss_api_key is None: if moss_api_key is None:
print('No MOSS API Key supplied') print("No MOSS API Key supplied")
return return
contest = options['contest'] contest = options["contest"]
for problem in Contest.objects.get(key=contest).problems.order_by('code'): for problem in Contest.objects.get(key=contest).problems.order_by("code"):
print('========== %s / %s ==========' % (problem.code, problem.name)) print("========== %s / %s ==========" % (problem.code, problem.name))
for dmoj_lang, moss_lang in self.LANG_MAPPING: for dmoj_lang, moss_lang in self.LANG_MAPPING:
print("%s: " % dmoj_lang, end=' ') print("%s: " % dmoj_lang, end=" ")
subs = Submission.objects.filter( subs = Submission.objects.filter(
contest__participation__virtual__in=(ContestParticipation.LIVE, ContestParticipation.SPECTATE), contest__participation__virtual__in=(
ContestParticipation.LIVE,
ContestParticipation.SPECTATE,
),
contest__participation__contest__key=contest, contest__participation__contest__key=contest,
result='AC', problem__id=problem.id, result="AC",
problem__id=problem.id,
language__common_name=dmoj_lang, language__common_name=dmoj_lang,
).values_list('user__user__username', 'source__source') ).values_list("user__user__username", "source__source")
if not subs: if not subs:
print('<no submissions>') print("<no submissions>")
continue continue
moss_call = MOSS(moss_api_key, language=moss_lang, matching_file_limit=100, moss_call = MOSS(
comment='%s - %s' % (contest, problem.code)) moss_api_key,
language=moss_lang,
matching_file_limit=100,
comment="%s - %s" % (contest, problem.code),
)
users = set() users = set()
@ -49,6 +57,6 @@ class Command(BaseCommand):
if username in users: if username in users:
continue continue
users.add(username) users.add(username)
moss_call.add_file_from_memory(username, source.encode('utf-8')) moss_call.add_file_from_memory(username, source.encode("utf-8"))
print('(%d): %s' % (subs.count(), moss_call.process())) print("(%d): %s" % (subs.count(), moss_call.process()))

View file

@ -10,11 +10,13 @@ class ShortCircuitMiddleware:
def __call__(self, request): def __call__(self, request):
try: try:
callback, args, kwargs = resolve(request.path_info, getattr(request, 'urlconf', None)) callback, args, kwargs = resolve(
request.path_info, getattr(request, "urlconf", None)
)
except Resolver404: except Resolver404:
callback, args, kwargs = None, None, None callback, args, kwargs = None, None, None
if getattr(callback, 'short_circuit_middleware', False): if getattr(callback, "short_circuit_middleware", False):
return callback(request, *args, **kwargs) return callback(request, *args, **kwargs)
return self.get_response(request) return self.get_response(request)
@ -26,11 +28,16 @@ class DMOJLoginMiddleware(object):
def __call__(self, request): def __call__(self, request):
if request.user.is_authenticated: if request.user.is_authenticated:
profile = request.profile = request.user.profile profile = request.profile = request.user.profile
login_2fa_path = reverse('login_2fa') login_2fa_path = reverse("login_2fa")
if (profile.is_totp_enabled and not request.session.get('2fa_passed', False) and if (
request.path not in (login_2fa_path, reverse('auth_logout')) and profile.is_totp_enabled
not request.path.startswith(settings.STATIC_URL)): and not request.session.get("2fa_passed", False)
return HttpResponseRedirect(login_2fa_path + '?next=' + urlquote(request.get_full_path())) and request.path not in (login_2fa_path, reverse("auth_logout"))
and not request.path.startswith(settings.STATIC_URL)
):
return HttpResponseRedirect(
login_2fa_path + "?next=" + urlquote(request.get_full_path())
)
else: else:
request.profile = None request.profile = None
return self.get_response(request) return self.get_response(request)
@ -57,7 +64,7 @@ class ContestMiddleware(object):
profile.update_contest() profile.update_contest()
request.participation = profile.current_contest request.participation = profile.current_contest
request.in_contest = request.participation is not None request.in_contest = request.participation is not None
request.contest_mode = request.session.get('contest_mode', True) request.contest_mode = request.session.get("contest_mode", True)
else: else:
request.in_contest = False request.in_contest = False
request.participation = None request.participation = None

File diff suppressed because one or more lines are too long

View file

@ -7,33 +7,61 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0084_contest_formats'), ("judge", "0084_contest_formats"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='SubmissionSource', name="SubmissionSource",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('source', models.TextField(max_length=65536, verbose_name='source code')), "id",
('submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='link', to='judge.Submission', verbose_name='associated submission')), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"source",
models.TextField(max_length=65536, verbose_name="source code"),
),
(
"submission",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="link",
to="judge.Submission",
verbose_name="associated submission",
),
),
], ],
), ),
migrations.RunSQL( migrations.RunSQL(
['''INSERT INTO judge_submissionsource (source, submission_id) [
SELECT source, id AS 'submission_id' FROM judge_submission;'''], """INSERT INTO judge_submissionsource (source, submission_id)
['''UPDATE judge_submission sub SELECT source, id AS 'submission_id' FROM judge_submission;"""
],
[
"""UPDATE judge_submission sub
INNER JOIN judge_submissionsource src ON sub.id = src.submission_id INNER JOIN judge_submissionsource src ON sub.id = src.submission_id
SET sub.source = src.source;'''], SET sub.source = src.source;"""
],
elidable=True, elidable=True,
), ),
migrations.RemoveField( migrations.RemoveField(
model_name='submission', model_name="submission",
name='source', name="source",
), ),
migrations.AlterField( migrations.AlterField(
model_name='submissionsource', model_name="submissionsource",
name='submission', name="submission",
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='source', to='judge.Submission', verbose_name='associated submission'), field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="source",
to="judge.Submission",
verbose_name="associated submission",
),
), ),
] ]

View file

@ -6,18 +6,28 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0085_submission_source'), ("judge", "0085_submission_source"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='contest', model_name="contest",
name='rating_ceiling', name="rating_ceiling",
field=models.IntegerField(blank=True, help_text='Rating ceiling for contest', null=True, verbose_name='rating ceiling'), field=models.IntegerField(
blank=True,
help_text="Rating ceiling for contest",
null=True,
verbose_name="rating ceiling",
),
), ),
migrations.AddField( migrations.AddField(
model_name='contest', model_name="contest",
name='rating_floor', name="rating_floor",
field=models.IntegerField(blank=True, help_text='Rating floor for contest', null=True, verbose_name='rating floor'), field=models.IntegerField(
blank=True,
help_text="Rating floor for contest",
null=True,
verbose_name="rating floor",
),
), ),
] ]

View file

@ -7,18 +7,28 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0086_rating_ceiling'), ("judge", "0086_rating_ceiling"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='problem', model_name="problem",
name='memory_limit', name="memory_limit",
field=models.PositiveIntegerField(help_text='The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).', verbose_name='memory limit'), field=models.PositiveIntegerField(
help_text="The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).",
verbose_name="memory limit",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='problem', model_name="problem",
name='time_limit', name="time_limit",
field=models.FloatField(help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2000)], verbose_name='time limit'), field=models.FloatField(
help_text="The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.",
validators=[
django.core.validators.MinValueValidator(0),
django.core.validators.MaxValueValidator(2000),
],
verbose_name="time limit",
),
), ),
] ]

View file

@ -6,32 +6,53 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0087_problem_resource_limits'), ("judge", "0087_problem_resource_limits"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='contest', name="contest",
options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'}, options={
"permissions": (
("see_private_contest", "See private contests"),
("edit_own_contest", "Edit own contests"),
("edit_all_contest", "Edit all contests"),
("contest_rating", "Rate contests"),
("contest_access_code", "Contest access codes"),
("create_private_contest", "Create private contests"),
),
"verbose_name": "contest",
"verbose_name_plural": "contests",
},
), ),
migrations.RenameField( migrations.RenameField(
model_name='contest', model_name="contest",
old_name='is_public', old_name="is_public",
new_name='is_visible', new_name="is_visible",
), ),
migrations.AddField( migrations.AddField(
model_name='contest', model_name="contest",
name='is_organization_private', name="is_organization_private",
field=models.BooleanField(default=False, verbose_name='private to organizations'), field=models.BooleanField(
default=False, verbose_name="private to organizations"
),
), ),
migrations.AddField( migrations.AddField(
model_name='contest', model_name="contest",
name='private_contestants', name="private_contestants",
field=models.ManyToManyField(blank=True, help_text='If private, only these users may see the contest', related_name='_contest_private_contestants_+', to='judge.Profile', verbose_name='private contestants'), field=models.ManyToManyField(
blank=True,
help_text="If private, only these users may see the contest",
related_name="_contest_private_contestants_+",
to="judge.Profile",
verbose_name="private contestants",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='contest', model_name="contest",
name='is_private', name="is_private",
field=models.BooleanField(default=False, verbose_name='private to specific users'), field=models.BooleanField(
default=False, verbose_name="private to specific users"
),
), ),
] ]

View file

@ -7,21 +7,31 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0088_private_contests'), ("judge", "0088_private_contests"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='submission', model_name="submission",
name='contest_object', name="contest_object",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='judge.Contest', verbose_name='contest'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="judge.Contest",
verbose_name="contest",
),
), ),
migrations.RunSQL(''' migrations.RunSQL(
"""
UPDATE `judge_submission` UPDATE `judge_submission`
INNER JOIN `judge_contestsubmission` INNER JOIN `judge_contestsubmission`
ON (`judge_submission`.`id` = `judge_contestsubmission`.`submission_id`) ON (`judge_submission`.`id` = `judge_contestsubmission`.`submission_id`)
INNER JOIN `judge_contestparticipation` INNER JOIN `judge_contestparticipation`
ON (`judge_contestsubmission`.`participation_id` = `judge_contestparticipation`.`id`) ON (`judge_contestsubmission`.`participation_id` = `judge_contestparticipation`.`id`)
SET `judge_submission`.`contest_object_id` = `judge_contestparticipation`.`contest_id` SET `judge_submission`.`contest_object_id` = `judge_contestparticipation`.`contest_id`
''', migrations.RunSQL.noop), """,
migrations.RunSQL.noop,
),
] ]

View file

@ -4,16 +4,19 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0089_submission_to_contest'), ("judge", "0089_submission_to_contest"),
] ]
operations = [ operations = [
migrations.RunSQL(''' migrations.RunSQL(
"""
UPDATE `judge_contest` UPDATE `judge_contest`
SET `judge_contest`.`is_private` = 0, `judge_contest`.`is_organization_private` = 1 SET `judge_contest`.`is_private` = 0, `judge_contest`.`is_organization_private` = 1
WHERE `judge_contest`.`is_private` = 1 WHERE `judge_contest`.`is_private` = 1
''', ''' """,
"""
UPDATE `judge_contest` UPDATE `judge_contest`
SET `judge_contest`.`is_private` = `judge_contest`.`is_organization_private` SET `judge_contest`.`is_private` = `judge_contest`.`is_organization_private`
'''), """,
),
] ]

View file

@ -5,17 +5,17 @@ from lxml.html.clean import clean_html
def strip_error_html(apps, schema_editor): def strip_error_html(apps, schema_editor):
Submission = apps.get_model('judge', 'Submission') Submission = apps.get_model("judge", "Submission")
for sub in Submission.objects.filter(error__isnull=False).iterator(): for sub in Submission.objects.filter(error__isnull=False).iterator():
if sub.error: if sub.error:
sub.error = clean_html(lh.fromstring(sub.error)).text_content() sub.error = clean_html(lh.fromstring(sub.error)).text_content()
sub.save(update_fields=['error']) sub.save(update_fields=["error"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('judge', '0090_fix_contest_visibility'), ("judge", "0090_fix_contest_visibility"),
] ]
operations = [ operations = [

Some files were not shown because too many files have changed in this diff Show more