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):
name = 'chat_box'
name = "chat_box"

View file

@ -9,22 +9,43 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('judge', '0100_auto_20200127_0059'),
("judge", "0100_auto_20200127_0059"),
]
operations = [
migrations.CreateModel(
name='Message',
name="Message",
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')),
('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')),
(
"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"),
),
(
"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={
'verbose_name': 'message',
'verbose_name_plural': 'messages',
'ordering': ('-time',),
"verbose_name": "message",
"verbose_name_plural": "messages",
"ordering": ("-time",),
},
),
]

View file

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chat_box', '0001_initial'),
("chat_box", "0001_initial"),
]
operations = [
migrations.AddField(
model_name='message',
name='hidden',
field=models.BooleanField(default=False, verbose_name='is hidden'),
model_name="message",
name="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):
dependencies = [
('chat_box', '0002_message_hidden'),
("chat_box", "0002_message_hidden"),
]
operations = [
migrations.AlterField(
model_name='message',
name='hidden',
field=models.BooleanField(default=True, verbose_name='is hidden'),
model_name="message",
name="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):
dependencies = [
('chat_box', '0003_auto_20200505_2306'),
("chat_box", "0003_auto_20200505_2306"),
]
operations = [
migrations.AlterField(
model_name='message',
name='hidden',
field=models.BooleanField(default=False, verbose_name='is hidden'),
model_name="message",
name="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):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0004_auto_20200505_2336'),
("judge", "0116_auto_20211011_0645"),
("chat_box", "0004_auto_20200505_2336"),
]
operations = [
migrations.CreateModel(
name='Room',
name="Room",
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')),
('user_two', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_two', to='judge.Profile', verbose_name='user 2')),
(
"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",
),
),
(
"user_two",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_two",
to="judge.Profile",
verbose_name="user 2",
),
),
],
),
migrations.AddField(
model_name='message',
name='room',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id'),
model_name="message",
name="room",
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):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0005_auto_20211011_0714'),
("judge", "0116_auto_20211011_0645"),
("chat_box", "0005_auto_20211011_0714"),
]
operations = [
migrations.CreateModel(
name='UserRoom',
name="UserRoom",
fields=[
('id', models.AutoField(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')),
(
"id",
models.AutoField(
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):
dependencies = [
('chat_box', '0006_userroom'),
("chat_box", "0006_userroom"),
]
operations = [
migrations.AlterField(
model_name='userroom',
name='last_seen',
field=models.DateTimeField(auto_now_add=True, verbose_name='last seen'),
model_name="userroom",
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):
dependencies = [
('judge', '0116_auto_20211011_0645'),
('chat_box', '0007_auto_20211112_1255'),
("judge", "0116_auto_20211011_0645"),
("chat_box", "0007_auto_20211112_1255"),
]
operations = [
migrations.CreateModel(
name='Ignore',
name="Ignore",
fields=[
('id', 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')),
(
"id",
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
__all__ = ['Message']
__all__ = ["Message"]
class Room(models.Model):
user_one = models.ForeignKey(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)
user_one = models.ForeignKey(
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):
return self.user_one == profile or self.user_two == profile
def other_user(self, profile):
return self.user_one if profile == self.user_two else self.user_two
def users(self):
return [self.user_one, self.user_two]
class Message(models.Model):
author = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
hidden = models.BooleanField(verbose_name='is hidden', default=False)
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True)
author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
hidden = models.BooleanField(verbose_name="is hidden", default=False)
room = models.ForeignKey(
Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
)
def save(self, *args, **kwargs):
new_message = self.id
@ -32,26 +42,37 @@ class Message(models.Model):
super(Message, self).save(*args, **kwargs)
class Meta:
app_label = 'chat_box'
verbose_name = 'message'
verbose_name_plural = 'messages'
ordering = ('-time',)
app_label = "chat_box"
verbose_name = "message"
verbose_name_plural = "messages"
ordering = ("-time",)
class UserRoom(models.Model):
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)
last_seen = models.DateTimeField(verbose_name=_('last seen'), auto_now_add=True)
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
)
last_seen = models.DateTimeField(verbose_name=_("last seen"), auto_now_add=True)
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)
@classmethod
def is_ignored(self, current_user, new_friend):
try:
return current_user.ignored_chat_users.get().ignored_users \
.filter(id=new_friend.id).exists()
return (
current_user.ignored_chat_users.get()
.ignored_users.filter(id=new_friend.id)
.exists()
)
except:
return False
@ -64,21 +85,17 @@ class Ignore(models.Model):
@classmethod
def add_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create(
user = current_user
)
ignore, created = self.objects.get_or_create(user=current_user)
ignore.ignored_users.add(friend)
@classmethod
def remove_ignore(self, current_user, friend):
ignore, created = self.objects.get_or_create(
user = current_user
)
ignore, created = self.objects.get_or_create(user=current_user)
ignore.ignored_users.remove(friend)
@classmethod
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)
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
fernet = Fernet(secret_key)
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()
def decrypt_url(message_encrypted):
try:
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)
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.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.exceptions import PermissionDenied
from django.shortcuts import render
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.utils import timezone
from django.contrib.auth.decorators import login_required
@ -24,10 +40,10 @@ import json
class ChatView(ListView):
context_object_name = 'message'
template_name = 'chat/chat.html'
title = _('Chat Box')
context_object_name = "message"
template_name = "chat/chat.html"
title = _("Chat Box")
def __init__(self):
super().__init__()
self.room_id = None
@ -40,8 +56,8 @@ class ChatView(ListView):
return self.messages
def get(self, request, *args, **kwargs):
request_room = kwargs['room_id']
page = request.GET.get('page')
request_room = kwargs["room_id"]
page = request.GET.get("page")
if request_room:
try:
@ -64,109 +80,121 @@ class ChatView(ListView):
cur_page = self.paginator.get_page(page)
return render(request, 'chat/message_list.html', {
'object_list': cur_page.object_list,
'num_pages': self.paginator.num_pages
})
return render(
request,
"chat/message_list.html",
{
"object_list": cur_page.object_list,
"num_pages": self.paginator.num_pages,
},
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.title
context['last_msg'] = event.last()
context['status_sections'] = get_status_context(self.request)
context['room'] = self.room_id
context['unread_count_lobby'] = get_unread_count(None, self.request.profile)
context["title"] = self.title
context["last_msg"] = event.last()
context["status_sections"] = get_status_context(self.request)
context["room"] = self.room_id
context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
if self.room:
users_room = [self.room.user_one, self.room.user_two]
users_room.remove(self.request.profile)
context['other_user'] = users_room[0]
context['other_online'] = get_user_online_status(context['other_user'])
context['is_ignored'] = Ignore.is_ignored(self.request.profile, context['other_user'])
context["other_user"] = users_room[0]
context["other_online"] = get_user_online_status(context["other_user"])
context["is_ignored"] = Ignore.is_ignored(
self.request.profile, context["other_user"]
)
else:
context['online_count'] = get_online_count()
context['message_template'] = {
'author': self.request.profile,
'id': '$id',
'time': timezone.now(),
'body': '$body'
}
context["online_count"] = get_online_count()
context["message_template"] = {
"author": self.request.profile,
"id": "$id",
"time": timezone.now(),
"body": "$body",
}
return context
def delete_message(request):
ret = {'delete': 'done'}
if request.method == 'GET':
ret = {"delete": "done"}
if request.method == "GET":
return JsonResponse(ret)
if request.user.is_staff:
try:
messid = int(request.POST.get('message'))
messid = int(request.POST.get("message"))
mess = Message.objects.get(id=messid)
except:
return HttpResponseBadRequest()
mess.hidden = True
mess.save()
return JsonResponse(ret)
return JsonResponse(ret)
@login_required
def post_message(request):
ret = {'msg': 'posted'}
if request.method != 'POST':
ret = {"msg": "posted"}
if request.method != "POST":
return HttpResponseBadRequest()
if len(request.POST['body']) > 5000:
if len(request.POST["body"]) > 5000:
return HttpResponseBadRequest()
room = None
if request.POST['room']:
room = Room.objects.get(id=request.POST['room'])
if request.POST["room"]:
room = Room.objects.get(id=request.POST["room"])
if not can_access_room(request, room) or request.profile.mute:
return HttpResponseBadRequest()
new_message = Message(author=request.profile,
body=request.POST['body'],
room=room)
new_message = Message(author=request.profile, body=request.POST["body"], room=room)
new_message.save()
if not room:
event.post('chat_lobby', {
'type': 'lobby',
'author_id': request.profile.id,
'message': new_message.id,
'room': 'None',
'tmp_id': request.POST.get('tmp_id')
})
event.post(
"chat_lobby",
{
"type": "lobby",
"author_id": request.profile.id,
"message": new_message.id,
"room": "None",
"tmp_id": request.POST.get("tmp_id"),
},
)
else:
for user in room.users():
event.post('chat_' + str(user.id), {
'type': 'private',
'author_id': request.profile.id,
'message': new_message.id,
'room': room.id,
'tmp_id': request.POST.get('tmp_id')
})
event.post(
"chat_" + str(user.id),
{
"type": "private",
"author_id": request.profile.id,
"message": new_message.id,
"room": room.id,
"tmp_id": request.POST.get("tmp_id"),
},
)
return JsonResponse(ret)
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
def chat_message_ajax(request):
if request.method != 'GET':
if request.method != "GET":
return HttpResponseBadRequest()
try:
message_id = request.GET['message']
message_id = request.GET["message"]
except KeyError:
return HttpResponseBadRequest()
@ -174,22 +202,26 @@ def chat_message_ajax(request):
message = Message.objects.filter(hidden=False).get(id=message_id)
room = message.room
if room and not room.contain(request.profile):
return HttpResponse('Unauthorized', status=401)
return HttpResponse("Unauthorized", status=401)
except Message.DoesNotExist:
return HttpResponseBadRequest()
return render(request, 'chat/message.html', {
'message': message,
})
return render(
request,
"chat/message.html",
{
"message": message,
},
)
@login_required
def update_last_seen(request, **kwargs):
if 'room_id' in kwargs:
room_id = kwargs['room_id']
elif request.method == 'GET':
room_id = request.GET.get('room')
elif request.method == 'POST':
room_id = request.POST.get('room')
if "room_id" in kwargs:
room_id = kwargs["room_id"]
elif request.method == "GET":
room_id = request.GET.get("room")
elif request.method == "POST":
room_id = request.POST.get("room")
else:
return HttpResponseBadRequest()
@ -210,11 +242,11 @@ def update_last_seen(request, **kwargs):
user_room.last_seen = timezone.now()
user_room.save()
return JsonResponse({'msg': 'updated'})
return JsonResponse({"msg": "updated"})
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()
@ -225,10 +257,10 @@ def get_user_online_status(user):
def user_online_status_ajax(request):
if request.method != 'GET':
if request.method != "GET":
return HttpResponseBadRequest()
user_id = request.GET.get('user')
user_id = request.GET.get("user")
if user_id:
try:
@ -238,37 +270,45 @@ def user_online_status_ajax(request):
return HttpResponseBadRequest()
is_online = get_user_online_status(user)
return render(request, 'chat/user_online_status.html', {
'other_user': user,
'other_online': is_online,
'is_ignored': Ignore.is_ignored(request.profile, user)
})
return render(
request,
"chat/user_online_status.html",
{
"other_user": user,
"other_online": is_online,
"is_ignored": Ignore.is_ignored(request.profile, user),
},
)
else:
return render(request, 'chat/user_online_status.html', {
'online_count': get_online_count(),
})
return render(
request,
"chat/user_online_status.html",
{
"online_count": get_online_count(),
},
)
def get_online_status(request_user, queryset, rooms=None):
if not queryset:
return None
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
ret = []
if rooms:
unread_count = get_unread_count(rooms, request_user)
count = {}
for i in unread_count:
count[i['other_user']] = i['unread_count']
count[i["other_user"]] = i["unread_count"]
for user in queryset:
is_online = False
if (user.last_access >= last_two_minutes):
if user.last_access >= last_two_minutes:
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:
user_dict['unread_count'] = count[user.id]
user_dict['url'] = encrypt_url(request_user.id, user.id)
user_dict["unread_count"] = count[user.id]
user_dict["url"] = encrypt_url(request_user.id, user.id)
ret.append(user_dict)
return ret
@ -281,66 +321,82 @@ def get_status_context(request, include_ignored=False):
ignored_users = Ignore.get_ignored_users(request.profile)
queryset = Profile.objects.exclude(id__in=ignored_users)
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
recent_profile = Room.objects.filter(
Q(user_one=request.profile) | Q(user_two=request.profile)
).annotate(
last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
recent_profile = (
Room.objects.filter(Q(user_one=request.profile) | Q(user_two=request.profile))
.annotate(
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(
When(user_one=request.profile, then='user_two'),
default='user_one',
)
).filter(last_msg_time__isnull=False)\
.exclude(other_user__in=ignored_users)\
.order_by('-last_msg_time').values('other_user', 'id')[:20]
When(user_one=request.profile, then="user_two"),
default="user_one",
),
)
.filter(last_msg_time__isnull=False)
.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]
joined_id = ','.join(recent_profile_id)
recent_rooms = [int(i['id']) for i in recent_profile]
recent_profile_id = [str(i["other_user"]) for i in recent_profile]
joined_id = ",".join(recent_profile_id)
recent_rooms = [int(i["id"]) for i in recent_profile]
recent_list = None
if joined_id:
recent_list = Profile.objects.raw(
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)\
.order_by('-last_access')
admin_list = queryset.filter(display_rank='admin')\
.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)\
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)
.order_by("-last_access")
)
admin_list = (
queryset.filter(display_rank="admin")
.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]
)
return [
{
'title': 'Recent',
'user_list': get_online_status(request.profile, recent_list, recent_rooms),
"title": "Recent",
"user_list": get_online_status(request.profile, recent_list, recent_rooms),
},
{
'title': 'Following',
'user_list': get_online_status(request.profile, friend_list),
"title": "Following",
"user_list": get_online_status(request.profile, friend_list),
},
{
'title': 'Admin',
'user_list': get_online_status(request.profile, admin_list),
"title": "Admin",
"user_list": get_online_status(request.profile, admin_list),
},
{
'title': 'Other',
'user_list': get_online_status(request.profile, all_user_status),
"title": "Other",
"user_list": get_online_status(request.profile, all_user_status),
},
]
@login_required
def online_status_ajax(request):
return render(request, 'chat/online_status.html', {
'status_sections': get_status_context(request),
'unread_count_lobby': get_unread_count(None, request.profile),
})
return render(
request,
"chat/online_status.html",
{
"status_sections": get_status_context(request),
"unread_count_lobby": get_unread_count(None, request.profile),
},
)
@login_required
@ -353,10 +409,10 @@ def get_room(user_one, user_two):
@login_required
def get_or_create_room(request):
if request.method == 'GET':
decrypted_other_id = request.GET.get('other')
elif request.method == 'POST':
decrypted_other_id = request.POST.get('other')
if request.method == "GET":
decrypted_other_id = request.GET.get("other")
elif request.method == "POST":
decrypted_other_id = request.POST.get("other")
else:
return HttpResponseBadRequest()
@ -371,7 +427,7 @@ def get_or_create_room(request):
return HttpResponseBadRequest()
user = request.profile
if not other_user or not user:
return HttpResponseBadRequest()
# TODO: each user can only create <= 300 rooms
@ -382,47 +438,60 @@ def get_or_create_room(request):
user_room.last_seen = timezone.now()
user_room.save()
if request.method == 'GET':
return JsonResponse({'room': room.id, 'other_user_id': other_user.id})
return HttpResponseRedirect(reverse('chat', kwargs={'room_id': room.id}))
if request.method == "GET":
return JsonResponse({"room": room.id, "other_user_id": other_user.id})
return HttpResponseRedirect(reverse("chat", kwargs={"room_id": room.id}))
def get_unread_count(rooms, user):
if rooms:
mess = Message.objects.filter(room=OuterRef('room'),
time__gte=OuterRef('last_seen'))\
.exclude(author=user)\
.order_by().values('room')\
.annotate(unread_count=Count('pk')).values('unread_count')
mess = (
Message.objects.filter(
room=OuterRef("room"), time__gte=OuterRef("last_seen")
)
.exclude(author=user)
.order_by()
.values("room")
.annotate(unread_count=Count("pk"))
.values("unread_count")
)
return UserRoom.objects\
.filter(user=user, room__in=rooms)\
return (
UserRoom.objects.filter(user=user, room__in=rooms)
.annotate(
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
other_user=Case(
When(room__user_one=user, then='room__user_two'),
default='room__user_one',
)
).filter(unread_count__gte=1).values('other_user', 'unread_count')
else: # lobby
mess = 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')
When(room__user_one=user, then="room__user_two"),
default="room__user_one",
),
)
.filter(unread_count__gte=1)
.values("other_user", "unread_count")
)
else: # lobby
mess = (
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\
.filter(user=user, room__isnull=True)\
res = (
UserRoom.objects.filter(user=user, room__isnull=True)
.annotate(
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
@login_required
def toggle_ignore(request, **kwargs):
user_id = kwargs['user_id']
user_id = kwargs["user_id"]
if not user_id:
return HttpResponseBadRequest()
try:
@ -431,28 +500,34 @@ def toggle_ignore(request, **kwargs):
return HttpResponseBadRequest()
Ignore.toggle_ignore(request.profile, other_user)
next_url = request.GET.get('next', '/')
next_url = request.GET.get("next", "/")
return HttpResponseRedirect(next_url)
@login_required
def get_unread_boxes(request):
if (request.method != 'GET'):
if request.method != "GET":
return HttpResponseBadRequest()
ignored_users = Ignore.get_ignored_users(request.profile)
mess = Message.objects.filter(room=OuterRef('room'),
time__gte=OuterRef('last_seen'))\
.exclude(author=request.profile)\
.exclude(author__in=ignored_users)\
.order_by().values('room')\
.annotate(unread_count=Count('pk')).values('unread_count')
mess = (
Message.objects.filter(room=OuterRef("room"), time__gte=OuterRef("last_seen"))
.exclude(author=request.profile)
.exclude(author__in=ignored_users)
.order_by()
.values("room")
.annotate(unread_count=Count("pk"))
.values("unread_count")
)
unread_boxes = UserRoom.objects\
.filter(user=request.profile, room__isnull=False)\
unread_boxes = (
UserRoom.objects.filter(user=request.profile, room__isnull=False)
.annotate(
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.
# See MySQLdb/cursors.py in the source distribution.
# 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

View file

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

View file

@ -4,24 +4,30 @@ import socket
from celery import Celery
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
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
if hasattr(settings, 'CELERY_RESULT_BACKEND_SECRET'):
if hasattr(settings, "CELERY_RESULT_BACKEND_SECRET"):
app.conf.result_backend = settings.CELERY_RESULT_BACKEND_SECRET
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
# Logger to enable errors be reported.
logger = logging.getLogger('judge.celery')
logger = logging.getLogger("judge.celery")
@task_failure.connect()
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
exc_info=(type(exception), exception, traceback))
logger.error(
"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
def timeit(method):
def timed(*args, **kw):
ts = time.time()
result = method(*args, **kw)
te = time.time()
if 'log_time' in kw:
name = kw.get('log_name', method.__name__.upper())
kw['log_time'][name] = int((te - ts) * 1000)
if "log_time" in kw:
name = kw.get("log_name", method.__name__.upper())
kw["log_time"][name] = int((te - ts) * 1000)
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/
# 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!
DEBUG = True
@ -30,8 +30,8 @@ DEBUG = True
ALLOWED_HOSTS = []
SITE_ID = 1
SITE_NAME = 'LQDOJ'
SITE_LONG_NAME = 'LQDOJ: Le Quy Don Online Judge'
SITE_NAME = "LQDOJ"
SITE_LONG_NAME = "LQDOJ: Le Quy Don Online Judge"
SITE_ADMIN_EMAIL = False
DMOJ_REQUIRE_STAFF_2FA = True
@ -44,13 +44,15 @@ DMOJ_SSL = 0
# Refer to dmoj.ca/post/103-point-system-rework
DMOJ_PP_STEP = 0.95
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'
EXIFTOOL = '/usr/bin/exiftool'
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'
DEFAULT_SELECT2_CSS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
NODEJS = "/usr/bin/node"
EXIFTOOL = "/usr/bin/exiftool"
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"
DEFAULT_SELECT2_CSS = (
"//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css"
)
DMOJ_CAMO_URL = None
DMOJ_CAMO_KEY = None
@ -74,14 +76,14 @@ DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT = 7
DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1
DMOJ_USER_MAX_ORGANIZATION_COUNT = 10
DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5
DMOJ_PDF_PROBLEM_CACHE = ''
DMOJ_PDF_PROBLEM_CACHE = ""
DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir()
DMOJ_STATS_SUBMISSION_RESULT_COLORS = {
'TLE': '#a3bcbd',
'AC': '#00a92a',
'WA': '#ed4420',
'CE': '#42586d',
'ERR': '#ffa71c',
"TLE": "#a3bcbd",
"AC": "#00a92a",
"WA": "#ed4420",
"CE": "#42586d",
"ERR": "#ffa71c",
}
MARKDOWN_STYLES = {}
@ -90,14 +92,14 @@ MARKDOWN_DEFAULT_STYLE = {}
MATHOID_URL = False
MATHOID_GZIP = False
MATHOID_MML_CACHE = None
MATHOID_CSS_CACHE = 'default'
MATHOID_DEFAULT_TYPE = 'auto'
MATHOID_CSS_CACHE = "default"
MATHOID_DEFAULT_TYPE = "auto"
MATHOID_MML_CACHE_TTL = 86400
MATHOID_CACHE_ROOT = tempfile.gettempdir() + '/mathoidCache'
MATHOID_CACHE_ROOT = tempfile.gettempdir() + "/mathoidCache"
MATHOID_CACHE_URL = False
TEXOID_GZIP = False
TEXOID_META_CACHE = 'default'
TEXOID_META_CACHE = "default"
TEXOID_META_CACHE_TTL = 86400
DMOJ_NEWSLETTER_ID_ON_REGISTER = 1
@ -110,31 +112,33 @@ TIMEZONE_MAP = None
TIMEZONE_DETECT_BACKEND = None
TERMS_OF_SERVICE_URL = None
DEFAULT_USER_LANGUAGE = 'PY3'
DEFAULT_USER_LANGUAGE = "PY3"
PHANTOMJS = ''
PHANTOMJS = ""
PHANTOMJS_PDF_ZOOM = 0.75
PHANTOMJS_PDF_TIMEOUT = 5.0
PHANTOMJS_PAPER_SIZE = 'Letter'
PHANTOMJS_PAPER_SIZE = "Letter"
SLIMERJS = ''
SLIMERJS = ""
SLIMERJS_PDF_ZOOM = 0.75
SLIMERJS_FIREFOX_PATH = ''
SLIMERJS_PAPER_SIZE = 'Letter'
SLIMERJS_FIREFOX_PATH = ""
SLIMERJS_PAPER_SIZE = "Letter"
PUPPETEER_MODULE = '/usr/lib/node_modules/puppeteer'
PUPPETEER_PAPER_SIZE = 'Letter'
PUPPETEER_MODULE = "/usr/lib/node_modules/puppeteer"
PUPPETEER_PAPER_SIZE = "Letter"
USE_SELENIUM = False
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_FONTAWESOME = True
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'
DMOJ_CANONICAL = ''
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"
)
DMOJ_CANONICAL = ""
# Application definition
@ -146,130 +150,130 @@ except ImportError:
pass
else:
del wpadmin
INSTALLED_APPS += ('wpadmin',)
INSTALLED_APPS += ("wpadmin",)
WPADMIN = {
'admin': {
'title': 'LQDOJ Admin',
'menu': {
'top': 'wpadmin.menu.menus.BasicTopMenu',
'left': 'wpadmin.menu.custom.CustomModelLeftMenuWithDashboard',
"admin": {
"title": "LQDOJ Admin",
"menu": {
"top": "wpadmin.menu.menus.BasicTopMenu",
"left": "wpadmin.menu.custom.CustomModelLeftMenuWithDashboard",
},
'custom_menu': [
"custom_menu": [
{
'model': 'judge.Problem',
'icon': 'fa-question-circle',
'children': [
'judge.ProblemGroup',
'judge.ProblemType',
'judge.ProblemPointsVote',
"model": "judge.Problem",
"icon": "fa-question-circle",
"children": [
"judge.ProblemGroup",
"judge.ProblemType",
"judge.ProblemPointsVote",
],
},
{
'model': 'judge.Submission',
'icon': 'fa-check-square-o',
'children': [
'judge.Language',
'judge.Judge',
"model": "judge.Submission",
"icon": "fa-check-square-o",
"children": [
"judge.Language",
"judge.Judge",
],
},
{
'model': 'judge.Contest',
'icon': 'fa-bar-chart',
'children': [
'judge.ContestParticipation',
'judge.ContestTag',
"model": "judge.Contest",
"icon": "fa-bar-chart",
"children": [
"judge.ContestParticipation",
"judge.ContestTag",
],
},
{
'model': 'auth.User',
'icon': 'fa-user',
'children': [
'auth.Group',
'registration.RegistrationProfile',
"model": "auth.User",
"icon": "fa-user",
"children": [
"auth.Group",
"registration.RegistrationProfile",
],
},
{
'model': 'judge.Profile',
'icon': 'fa-user-plus',
'children': [
'judge.Organization',
'judge.OrganizationRequest',
"model": "judge.Profile",
"icon": "fa-user-plus",
"children": [
"judge.Organization",
"judge.OrganizationRequest",
],
},
{
'model': 'judge.NavigationBar',
'icon': 'fa-bars',
'children': [
'judge.MiscConfig',
'judge.License',
'sites.Site',
'redirects.Redirect',
"model": "judge.NavigationBar",
"icon": "fa-bars",
"children": [
"judge.MiscConfig",
"judge.License",
"sites.Site",
"redirects.Redirect",
],
},
('judge.BlogPost', 'fa-rss-square'),
('judge.Comment', 'fa-comment-o'),
('judge.Ticket', 'fa-exclamation-circle'),
('flatpages.FlatPage', 'fa-file-text-o'),
('judge.Solution', 'fa-pencil'),
("judge.BlogPost", "fa-rss-square"),
("judge.Comment", "fa-comment-o"),
("judge.Ticket", "fa-exclamation-circle"),
("flatpages.FlatPage", "fa-file-text-o"),
("judge.Solution", "fa-pencil"),
],
'dashboard': {
'breadcrumbs': True,
"dashboard": {
"breadcrumbs": True,
},
},
}
INSTALLED_APPS += (
'django.contrib.admin',
'judge',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.flatpages',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.redirects',
'django.contrib.staticfiles',
'django.contrib.sites',
'django.contrib.sitemaps',
'registration',
'mptt',
'reversion',
'reversion_compare',
'django_social_share',
'social_django',
'compressor',
'django_ace',
'pagedown',
'sortedm2m',
'statici18n',
'impersonate',
'django_jinja',
'chat_box',
'newsletter',
'django.forms',
"django.contrib.admin",
"judge",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.flatpages",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.redirects",
"django.contrib.staticfiles",
"django.contrib.sites",
"django.contrib.sitemaps",
"registration",
"mptt",
"reversion",
"reversion_compare",
"django_social_share",
"social_django",
"compressor",
"django_ace",
"pagedown",
"sortedm2m",
"statici18n",
"impersonate",
"django_jinja",
"chat_box",
"newsletter",
"django.forms",
)
MIDDLEWARE = (
'judge.middleware.ShortCircuitMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'judge.middleware.DMOJLoginMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'judge.user_log.LogUserAccessMiddleware',
'judge.timezone.TimezoneMiddleware',
'impersonate.middleware.ImpersonateMiddleware',
'judge.middleware.DMOJImpersonationMiddleware',
'judge.middleware.ContestMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'judge.social_auth.SocialAuthExceptionMiddleware',
'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
"judge.middleware.ShortCircuitMiddleware",
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"judge.middleware.DMOJLoginMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"judge.user_log.LogUserAccessMiddleware",
"judge.timezone.TimezoneMiddleware",
"impersonate.middleware.ImpersonateMiddleware",
"judge.middleware.DMOJImpersonationMiddleware",
"judge.middleware.ContestMiddleware",
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
"judge.social_auth.SocialAuthExceptionMiddleware",
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
)
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
IMPERSONATE_REQUIRE_SUPERUSER = True
IMPERSONATE_DISABLE_LOGGING = True
@ -278,226 +282,229 @@ ACCOUNT_ACTIVATION_DAYS = 7
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'
LOGIN_REDIRECT_URL = '/user'
WSGI_APPLICATION = 'dmoj.wsgi.application'
ROOT_URLCONF = "dmoj.urls"
LOGIN_REDIRECT_URL = "/user"
WSGI_APPLICATION = "dmoj.wsgi.application"
TEMPLATES = [
{
'BACKEND': 'django_jinja.backend.Jinja2',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
"BACKEND": "django_jinja.backend.Jinja2",
"DIRS": [
os.path.join(BASE_DIR, "templates"),
],
'APP_DIRS': False,
'OPTIONS': {
'match_extension': ('.html', '.txt'),
'match_regex': '^(?!admin/)',
'context_processors': [
'django.template.context_processors.media',
'django.template.context_processors.tz',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'judge.template_context.comet_location',
'judge.template_context.get_resource',
'judge.template_context.general_info',
'judge.template_context.site',
'judge.template_context.site_name',
'judge.template_context.misc_config',
'judge.template_context.math_setting',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
"APP_DIRS": False,
"OPTIONS": {
"match_extension": (".html", ".txt"),
"match_regex": "^(?!admin/)",
"context_processors": [
"django.template.context_processors.media",
"django.template.context_processors.tz",
"django.template.context_processors.i18n",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
"judge.template_context.comet_location",
"judge.template_context.get_resource",
"judge.template_context.general_info",
"judge.template_context.site",
"judge.template_context.site_name",
"judge.template_context.misc_config",
"judge.template_context.math_setting",
"social_django.context_processors.backends",
"social_django.context_processors.login_redirect",
],
'autoescape': select_autoescape(['html', 'xml']),
'trim_blocks': True,
'lstrip_blocks': True,
'extensions': DEFAULT_EXTENSIONS + [
'compressor.contrib.jinja2ext.CompressorExtension',
'judge.jinja2.DMOJExtension',
'judge.jinja2.spaceless.SpacelessExtension',
"autoescape": select_autoescape(["html", "xml"]),
"trim_blocks": True,
"lstrip_blocks": True,
"extensions": DEFAULT_EXTENSIONS
+ [
"compressor.contrib.jinja2ext.CompressorExtension",
"judge.jinja2.DMOJExtension",
"judge.jinja2.spaceless.SpacelessExtension",
],
},
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
"DIRS": [
os.path.join(BASE_DIR, "templates"),
],
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.media',
'django.template.context_processors.tz',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.media",
"django.template.context_processors.tz",
"django.template.context_processors.i18n",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
},
},
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
os.path.join(BASE_DIR, "locale"),
]
LANGUAGES = [
('de', _('German')),
('en', _('English')),
('es', _('Spanish')),
('fr', _('French')),
('hr', _('Croatian')),
('hu', _('Hungarian')),
('ja', _('Japanese')),
('ko', _('Korean')),
('pt', _('Brazilian Portuguese')),
('ro', _('Romanian')),
('ru', _('Russian')),
('sr-latn', _('Serbian (Latin)')),
('tr', _('Turkish')),
('vi', _('Vietnamese')),
('zh-hans', _('Simplified Chinese')),
('zh-hant', _('Traditional Chinese')),
("de", _("German")),
("en", _("English")),
("es", _("Spanish")),
("fr", _("French")),
("hr", _("Croatian")),
("hu", _("Hungarian")),
("ja", _("Japanese")),
("ko", _("Korean")),
("pt", _("Brazilian Portuguese")),
("ro", _("Romanian")),
("ru", _("Russian")),
("sr-latn", _("Serbian (Latin)")),
("tr", _("Turkish")),
("vi", _("Vietnamese")),
("zh-hans", _("Simplified Chinese")),
("zh-hant", _("Traditional Chinese")),
]
MARKDOWN_ADMIN_EDITABLE_STYLE = {
'safe_mode': False,
'use_camo': True,
'texoid': True,
'math': True,
"safe_mode": False,
"use_camo": True,
"texoid": True,
"math": True,
}
MARKDOWN_DEFAULT_STYLE = {
'safe_mode': True,
'nofollow': True,
'use_camo': True,
'math': True,
"safe_mode": True,
"nofollow": True,
"use_camo": True,
"math": True,
}
MARKDOWN_USER_LARGE_STYLE = {
'safe_mode': True,
'nofollow': True,
'use_camo': True,
'math': True,
"safe_mode": True,
"nofollow": True,
"use_camo": True,
"math": True,
}
MARKDOWN_STYLES = {
'comment': MARKDOWN_DEFAULT_STYLE,
'self-description': MARKDOWN_USER_LARGE_STYLE,
'problem': MARKDOWN_ADMIN_EDITABLE_STYLE,
'contest': MARKDOWN_ADMIN_EDITABLE_STYLE,
'language': MARKDOWN_ADMIN_EDITABLE_STYLE,
'license': MARKDOWN_ADMIN_EDITABLE_STYLE,
'judge': MARKDOWN_ADMIN_EDITABLE_STYLE,
'blog': MARKDOWN_ADMIN_EDITABLE_STYLE,
'solution': MARKDOWN_ADMIN_EDITABLE_STYLE,
'contest_tag': MARKDOWN_ADMIN_EDITABLE_STYLE,
'organization-about': MARKDOWN_USER_LARGE_STYLE,
'ticket': MARKDOWN_USER_LARGE_STYLE,
"comment": MARKDOWN_DEFAULT_STYLE,
"self-description": MARKDOWN_USER_LARGE_STYLE,
"problem": MARKDOWN_ADMIN_EDITABLE_STYLE,
"contest": MARKDOWN_ADMIN_EDITABLE_STYLE,
"language": MARKDOWN_ADMIN_EDITABLE_STYLE,
"license": MARKDOWN_ADMIN_EDITABLE_STYLE,
"judge": MARKDOWN_ADMIN_EDITABLE_STYLE,
"blog": MARKDOWN_ADMIN_EDITABLE_STYLE,
"solution": MARKDOWN_ADMIN_EDITABLE_STYLE,
"contest_tag": MARKDOWN_ADMIN_EDITABLE_STYLE,
"organization-about": MARKDOWN_USER_LARGE_STYLE,
"ticket": MARKDOWN_USER_LARGE_STYLE,
}
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
},
}
ENABLE_FTS = False
# Bridged configuration
BRIDGED_JUDGE_ADDRESS = [('localhost', 9999)]
BRIDGED_JUDGE_ADDRESS = [("localhost", 9999)]
BRIDGED_JUDGE_PROXIES = None
BRIDGED_DJANGO_ADDRESS = [('localhost', 9998)]
BRIDGED_DJANGO_ADDRESS = [("localhost", 9998)]
BRIDGED_DJANGO_CONNECT = None
# Event Server configuration
EVENT_DAEMON_USE = False
EVENT_DAEMON_POST = 'ws://localhost:9997/'
EVENT_DAEMON_GET = 'ws://localhost:9996/'
EVENT_DAEMON_POLL = '/channels/'
EVENT_DAEMON_POST = "ws://localhost:9997/"
EVENT_DAEMON_GET = "ws://localhost:9996/"
EVENT_DAEMON_POLL = "/channels/"
EVENT_DAEMON_KEY = None
EVENT_DAEMON_AMQP_EXCHANGE = 'dmoj-events'
EVENT_DAEMON_SUBMISSION_KEY = '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww'
EVENT_DAEMON_AMQP_EXCHANGE = "dmoj-events"
EVENT_DAEMON_SUBMISSION_KEY = (
"6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww"
)
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
# Whatever you do, this better be one of the entries in `LANGUAGES`.
LANGUAGE_CODE = 'vi'
TIME_ZONE = 'Asia/Ho_Chi_Minh'
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
LANGUAGE_CODE = "vi"
TIME_ZONE = "Asia/Ho_Chi_Minh"
DEFAULT_USER_TIME_ZONE = "Asia/Ho_Chi_Minh"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Cookies
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# Static files (CSS, JavaScript, Images)
# 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 = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'resources'),
os.path.join(BASE_DIR, "resources"),
]
STATIC_URL = '/static/'
STATIC_URL = "/static/"
# Define a cache
CACHES = {}
# Authentication
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.facebook.FacebookOAuth2',
'judge.social_auth.GitHubSecureEmailOAuth2',
'django.contrib.auth.backends.ModelBackend',
"social_core.backends.google.GoogleOAuth2",
"social_core.backends.facebook.FacebookOAuth2",
"judge.social_auth.GitHubSecureEmailOAuth2",
"django.contrib.auth.backends.ModelBackend",
)
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'judge.social_auth.verify_email',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'judge.social_auth.choose_username',
'social_core.pipeline.user.create_user',
'judge.social_auth.make_profile',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
"social_core.pipeline.social_auth.social_details",
"social_core.pipeline.social_auth.social_uid",
"social_core.pipeline.social_auth.auth_allowed",
"judge.social_auth.verify_email",
"social_core.pipeline.social_auth.social_user",
"social_core.pipeline.user.get_username",
"social_core.pipeline.social_auth.associate_by_email",
"judge.social_auth.choose_username",
"social_core.pipeline.user.create_user",
"judge.social_auth.make_profile",
"social_core.pipeline.social_auth.associate_user",
"social_core.pipeline.social_auth.load_extra_data",
"social_core.pipeline.user.user_details",
)
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['first_name', 'last_name']
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ['email', 'username']
SOCIAL_AUTH_GITHUB_SECURE_SCOPE = ['user:email']
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ["first_name", "last_name"]
SOCIAL_AUTH_GOOGLE_OAUTH2_USER_FIELDS = ["email", "username"]
SOCIAL_AUTH_GITHUB_SECURE_SCOPE = ["user:email"]
SOCIAL_AUTH_FACEBOOK_SCOPE = ["email"]
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
@ -529,7 +536,7 @@ NEWSLETTER_BATCH_SIZE = 100
REGISTER_NAME_URL = None
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())
except IOError:
pass

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
try:
import MySQLdb # noqa: F401, imported for side effect
@ -8,5 +9,8 @@ except ImportError:
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()

View file

@ -2,13 +2,17 @@ import os
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()
# noinspection PyUnresolvedReferences
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
import django_2_2_pymysql_patch # noqa: I100, F401, I202, imported for side effect
application = get_wsgi_application()

View file

@ -2,19 +2,22 @@ import os
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()
# noinspection PyUnresolvedReferences
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
django.setup()
# noinspection PyUnresolvedReferences
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()

View file

@ -6,7 +6,7 @@ except ImportError:
import dmoj_install_pymysql # noqa: F401, imported for side effect
# 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
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.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.problem import ProblemAdmin, ProblemPointsVoteAdmin
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.ticket import TicketAdmin
from judge.admin.volunteer import VolunteerProblemVoteAdmin
from judge.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \
ContestTag, Judge, Language, License, MiscConfig, NavigationBar, Organization, \
OrganizationRequest, Problem, ProblemGroup, ProblemPointsVote, ProblemType, Profile, Submission, Ticket, \
VolunteerProblemVote
from judge.models import (
BlogPost,
Comment,
CommentLock,
Contest,
ContestParticipation,
ContestTag,
Judge,
Language,
License,
MiscConfig,
NavigationBar,
Organization,
OrganizationRequest,
Problem,
ProblemGroup,
ProblemPointsVote,
ProblemType,
Profile,
Submission,
Ticket,
VolunteerProblemVote,
)
admin.site.register(BlogPost, BlogPostAdmin)
@ -39,4 +63,4 @@ admin.site.register(ProblemType, ProblemTypeAdmin)
admin.site.register(Profile, ProfileAdmin)
admin.site.register(Submission, SubmissionAdmin)
admin.site.register(Ticket, TicketAdmin)
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)

View file

@ -11,53 +11,70 @@ from judge.widgets import AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidg
class CommentForm(ModelForm):
class Meta:
widgets = {
'author': AdminHeavySelect2Widget(data_view='profile_select2'),
'parent': AdminHeavySelect2Widget(data_view='comment_select2'),
"author": AdminHeavySelect2Widget(data_view="profile_select2"),
"parent": AdminHeavySelect2Widget(data_view="comment_select2"),
}
if HeavyPreviewAdminPageDownWidget is not None:
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('comment_preview'))
widgets["body"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("comment_preview")
)
class CommentAdmin(VersionAdmin):
fieldsets = (
(None, {'fields': ('author', 'page', 'parent', 'score', 'hidden')}),
('Content', {'fields': ('body',)}),
(None, {"fields": ("author", "page", "parent", "score", "hidden")}),
("Content", {"fields": ("body",)}),
)
list_display = ['author', 'linked_page', 'time']
search_fields = ['author__user__username', 'page', 'body']
readonly_fields = ['score']
actions = ['hide_comment', 'unhide_comment']
list_filter = ['hidden']
list_display = ["author", "linked_page", "time"]
search_fields = ["author__user__username", "page", "body"]
readonly_fields = ["score"]
actions = ["hide_comment", "unhide_comment"]
list_filter = ["hidden"]
actions_on_top = True
actions_on_bottom = True
form = CommentForm
date_hierarchy = 'time'
date_hierarchy = "time"
def get_queryset(self, request):
return Comment.objects.order_by('-time')
return Comment.objects.order_by("-time")
def hide_comment(self, request, queryset):
count = queryset.update(hidden=True)
self.message_user(request, ungettext('%d comment successfully hidden.',
'%d comments successfully hidden.',
count) % count)
hide_comment.short_description = _('Hide comments')
self.message_user(
request,
ungettext(
"%d comment successfully hidden.",
"%d comments successfully hidden.",
count,
)
% count,
)
hide_comment.short_description = _("Hide comments")
def unhide_comment(self, request, queryset):
count = queryset.update(hidden=False)
self.message_user(request, ungettext('%d comment successfully unhidden.',
'%d comments successfully unhidden.',
count) % count)
unhide_comment.short_description = _('Unhide comments')
self.message_user(
request,
ungettext(
"%d comment successfully unhidden.",
"%d comments successfully unhidden.",
count,
)
% count,
)
unhide_comment.short_description = _("Unhide comments")
def linked_page(self, obj):
link = obj.link
if link is not None:
return format_html('<a href="{0}">{1}</a>', link, obj.page)
else:
return format_html('{0}', obj.page)
linked_page.short_description = _('Associated page')
linked_page.admin_order_field = 'page'
return format_html("{0}", obj.page)
linked_page.short_description = _("Associated page")
linked_page.admin_order_field = "page"
def save_model(self, 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 judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating
from judge.ratings import rate_contest
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminPagedownWidget, \
AdminSelect2MultipleWidget, AdminSelect2Widget, HeavyPreviewAdminPageDownWidget
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
AdminPagedownWidget,
AdminSelect2MultipleWidget,
AdminSelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
@ -28,159 +34,252 @@ class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
class ContestTagForm(ModelForm):
contests = ModelMultipleChoiceField(
label=_('Included contests'),
label=_("Included contests"),
queryset=Contest.objects.all(),
required=False,
widget=AdminHeavySelect2MultipleWidget(data_view='contest_select2'))
widget=AdminHeavySelect2MultipleWidget(data_view="contest_select2"),
)
class ContestTagAdmin(admin.ModelAdmin):
fields = ('name', 'color', 'description', 'contests')
list_display = ('name', 'color')
fields = ("name", "color", "description", "contests")
list_display = ("name", "color")
actions_on_top = True
actions_on_bottom = True
form = ContestTagForm
if AdminPagedownWidget is not None:
formfield_overrides = {
TextField: {'widget': AdminPagedownWidget},
TextField: {"widget": AdminPagedownWidget},
}
def save_model(self, 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):
form = super(ContestTagAdmin, self).get_form(request, obj, **kwargs)
if obj is not None:
form.base_fields['contests'].initial = obj.contests.all()
form.base_fields["contests"].initial = obj.contests.all()
return form
class ContestProblemInlineForm(ModelForm):
class Meta:
widgets = {'problem': AdminHeavySelect2Widget(data_view='problem_select2')}
widgets = {"problem": AdminHeavySelect2Widget(data_view="problem_select2")}
class ContestProblemInline(admin.TabularInline):
model = ContestProblem
verbose_name = _('Problem')
verbose_name_plural = 'Problems'
fields = ('problem', 'points', 'partial', 'is_pretested', 'max_submissions', 'output_prefix_override', 'order',
'rejudge_column')
readonly_fields = ('rejudge_column',)
verbose_name = _("Problem")
verbose_name_plural = "Problems"
fields = (
"problem",
"points",
"partial",
"is_pretested",
"max_submissions",
"output_prefix_override",
"order",
"rejudge_column",
)
readonly_fields = ("rejudge_column",)
form = ContestProblemInlineForm
def rejudge_column(self, obj):
if obj.id is None:
return ''
return format_html('<a class="button rejudge-link" href="{}">Rejudge</a>',
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)))
rejudge_column.short_description = ''
return ""
return format_html(
'<a class="button rejudge-link" href="{}">Rejudge</a>',
reverse("admin:judge_contest_rejudge", args=(obj.contest.id, obj.id)),
)
rejudge_column.short_description = ""
class ContestForm(ModelForm):
def __init__(self, *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:
self.fields['rate_exclude'].queryset = \
Profile.objects.filter(contest_history__contest=self.instance).distinct()
self.fields["rate_exclude"].queryset = Profile.objects.filter(
contest_history__contest=self.instance
).distinct()
else:
self.fields['rate_exclude'].queryset = Profile.objects.none()
self.fields['banned_users'].widget.can_add_related = False
self.fields['view_contest_scoreboard'].widget.can_add_related = False
self.fields["rate_exclude"].queryset = Profile.objects.none()
self.fields["banned_users"].widget.can_add_related = False
self.fields["view_contest_scoreboard"].widget.can_add_related = False
def clean(self):
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:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'private_contestants': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
attrs={'style': 'width: 100%'}),
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2'),
'tags': AdminSelect2MultipleWidget,
'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
attrs={'style': 'width: 100%'}),
'view_contest_scoreboard': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
attrs={'style': 'width: 100%'}),
"authors": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
"curators": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
"testers": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
"private_contestants": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"organizations": AdminHeavySelect2MultipleWidget(
data_view="organization_select2"
),
"tags": AdminSelect2MultipleWidget,
"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:
widgets['description'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('contest_preview'))
widgets["description"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("contest_preview")
)
class ContestAdmin(CompareVersionAdmin):
fieldsets = (
(None, {'fields': ('key', 'name', 'authors', 'curators', 'testers')}),
(_('Settings'), {'fields': ('is_visible', 'use_clarifications', 'hide_problem_tags', 'scoreboard_visibility',
'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',)}),
(None, {"fields": ("key", "name", "authors", "curators", "testers")}),
(
_("Settings"),
{
"fields": (
"is_visible",
"use_clarifications",
"hide_problem_tags",
"scoreboard_visibility",
"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')
search_fields = ('key', 'name')
list_display = (
"key",
"name",
"is_visible",
"is_rated",
"start_time",
"end_time",
"time_limit",
"user_count",
)
search_fields = ("key", "name")
inlines = [ContestProblemInline]
actions_on_top = True
actions_on_bottom = True
form = ContestForm
change_list_template = 'admin/judge/contest/change_list.html'
filter_horizontal = ['rate_exclude']
date_hierarchy = 'start_time'
change_list_template = "admin/judge/contest/change_list.html"
filter_horizontal = ["rate_exclude"]
date_hierarchy = "start_time"
def get_actions(self, request):
actions = super(ContestAdmin, self).get_actions(request)
if request.user.has_perm('judge.change_contest_visibility') or \
request.user.has_perm('judge.create_private_contest'):
for action in ('make_visible', 'make_hidden'):
if request.user.has_perm(
"judge.change_contest_visibility"
) or request.user.has_perm("judge.create_private_contest"):
for action in ("make_visible", "make_hidden"):
actions[action] = self.get_action(action)
return actions
def get_queryset(self, request):
queryset = Contest.objects.all()
if request.user.has_perm('judge.edit_all_contest'):
if request.user.has_perm("judge.edit_all_contest"):
return queryset
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):
readonly = []
if not request.user.has_perm('judge.contest_rating'):
readonly += ['is_rated', 'rate_all', 'rate_exclude']
if not request.user.has_perm('judge.contest_access_code'):
readonly += ['access_code']
if not request.user.has_perm('judge.create_private_contest'):
readonly += ['is_private', 'private_contestants', 'is_organization_private', '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']
if not request.user.has_perm("judge.contest_rating"):
readonly += ["is_rated", "rate_all", "rate_exclude"]
if not request.user.has_perm("judge.contest_access_code"):
readonly += ["access_code"]
if not request.user.has_perm("judge.create_private_contest"):
readonly += [
"is_private",
"private_contestants",
"is_organization_private",
"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
def save_model(self, request, obj, form, change):
# `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 not form.cleaned_data['is_private'] and not form.cleaned_data['is_organization_private']:
if form.cleaned_data.get("is_visible") and not request.user.has_perm(
"judge.change_contest_visibility"
):
if (
not form.cleaned_data["is_private"]
and not form.cleaned_data["is_organization_private"]
):
raise PermissionDenied
if not request.user.has_perm('judge.create_private_contest'):
if not request.user.has_perm("judge.create_private_contest"):
raise PermissionDenied
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
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._rescored = True
@ -188,10 +287,10 @@ class ContestAdmin(CompareVersionAdmin):
super().save_related(request, form, formsets, change)
# 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):
self._rescore(form.cleaned_data['key'])
self._rescore(form.cleaned_data["key"])
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
if obj is None:
return True
@ -199,76 +298,113 @@ class ContestAdmin(CompareVersionAdmin):
def _rescore(self, contest_key):
from judge.tasks import rescore_contest
transaction.on_commit(rescore_contest.s(contest_key).delay)
def make_visible(self, request, queryset):
if not request.user.has_perm('judge.change_contest_visibility'):
queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True))
if not request.user.has_perm("judge.change_contest_visibility"):
queryset = queryset.filter(
Q(is_private=True) | Q(is_organization_private=True)
)
count = queryset.update(is_visible=True)
self.message_user(request, ungettext('%d contest successfully marked as visible.',
'%d contests successfully marked as visible.',
count) % count)
make_visible.short_description = _('Mark contests as visible')
self.message_user(
request,
ungettext(
"%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):
if not request.user.has_perm('judge.change_contest_visibility'):
queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True))
if not request.user.has_perm("judge.change_contest_visibility"):
queryset = queryset.filter(
Q(is_private=True) | Q(is_organization_private=True)
)
count = queryset.update(is_visible=True)
self.message_user(request, ungettext('%d contest successfully marked as hidden.',
'%d contests successfully marked as hidden.',
count) % count)
make_hidden.short_description = _('Mark contests as hidden')
self.message_user(
request,
ungettext(
"%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):
return [
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+)/judge/(\d+)/$', self.rejudge_view, name='judge_contest_rejudge'),
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+)/judge/(\d+)/$", self.rejudge_view, name="judge_contest_rejudge"
),
] + super(ContestAdmin, self).get_urls()
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:
model.submission.judge(rejudge=True)
self.message_user(request, ungettext('%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,)))
self.message_user(
request,
ungettext(
"%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):
if not request.user.has_perm('judge.contest_rating'):
if not request.user.has_perm("judge.contest_rating"):
raise PermissionDenied()
with transaction.atomic():
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)
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)
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))
return HttpResponseRedirect(reverse("admin:judge_contest_changelist"))
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()
contest = get_object_or_404(Contest, id=id)
if not contest.is_rated or not contest.ended:
raise Http404()
with transaction.atomic():
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):
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
# on the model.
form.base_fields['problem_label_script'].widget = AceWidget('lua', request.profile.ace_theme)
perms = ('edit_own_contest', 'edit_all_contest')
form.base_fields['curators'].queryset = Profile.objects.filter(
Q(user__is_superuser=True) |
Q(user__groups__permissions__codename__in=perms) |
Q(user__user_permissions__codename__in=perms),
form.base_fields["problem_label_script"].widget = AceWidget(
"lua", request.profile.ace_theme
)
perms = ("edit_own_contest", "edit_all_contest")
form.base_fields["curators"].queryset = Profile.objects.filter(
Q(user__is_superuser=True)
| Q(user__groups__permissions__codename__in=perms)
| Q(user__user_permissions__codename__in=perms),
).distinct()
return form
@ -276,29 +412,48 @@ class ContestAdmin(CompareVersionAdmin):
class ContestParticipationForm(ModelForm):
class Meta:
widgets = {
'contest': AdminSelect2Widget(),
'user': AdminHeavySelect2Widget(data_view='profile_select2'),
"contest": AdminSelect2Widget(),
"user": AdminHeavySelect2Widget(data_view="profile_select2"),
}
class ContestParticipationAdmin(admin.ModelAdmin):
fields = ('contest', 'user', 'real_start', 'virtual', 'is_disqualified')
list_display = ('contest', 'username', 'show_virtual', 'real_start', 'score', 'cumtime', 'tiebreaker')
actions = ['recalculate_results']
fields = ("contest", "user", "real_start", "virtual", "is_disqualified")
list_display = (
"contest",
"username",
"show_virtual",
"real_start",
"score",
"cumtime",
"tiebreaker",
)
actions = ["recalculate_results"]
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
date_hierarchy = 'real_start'
date_hierarchy = "real_start"
def get_queryset(self, request):
return super(ContestParticipationAdmin, self).get_queryset(request).only(
'contest__name', 'contest__format_name', 'contest__format_config',
'user__user__username', 'real_start', 'score', 'cumtime', 'tiebreaker', 'virtual',
return (
super(ContestParticipationAdmin, self)
.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):
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)
def recalculate_results(self, request, queryset):
@ -306,17 +461,26 @@ class ContestParticipationAdmin(admin.ModelAdmin):
for participation in queryset:
participation.recompute_results()
count += 1
self.message_user(request, ungettext('%d participation recalculated.',
'%d participations recalculated.',
count) % count)
recalculate_results.short_description = _('Recalculate results')
self.message_user(
request,
ungettext(
"%d participation recalculated.",
"%d participations recalculated.",
count,
)
% count,
)
recalculate_results.short_description = _("Recalculate results")
def username(self, obj):
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):
return obj.virtual or '-'
show_virtual.short_description = _('virtual')
show_virtual.admin_order_field = 'virtual'
return obj.virtual or "-"
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.models import NavigationBar
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class NavigationBarAdmin(DraggableMPTTAdmin):
list_display = DraggableMPTTAdmin.list_display + ('key', 'linked_path')
fields = ('key', 'label', 'path', 'order', 'regex', 'parent')
list_display = DraggableMPTTAdmin.list_display + ("key", "linked_path")
fields = ("key", "label", "path", "order", "regex", "parent")
list_editable = () # Bug in SortableModelAdmin: 500 without list_editable being set
mptt_level_indent = 20
sortable = 'order'
sortable = "order"
def __init__(self, *args, **kwargs):
super(NavigationBarAdmin, self).__init__(*args, **kwargs)
self.__save_model_calls = 0
def linked_path(self, obj):
return format_html(u'<a href="{0}" target="_blank">{0}</a>', obj.path)
linked_path.short_description = _('link path')
return format_html('<a href="{0}" target="_blank">{0}</a>', obj.path)
linked_path.short_description = _("link path")
def save_model(self, request, obj, form, change):
self.__save_model_calls += 1
@ -36,7 +41,9 @@ class NavigationBarAdmin(DraggableMPTTAdmin):
def changelist_view(self, request, extra_context=None):
self.__save_model_calls = 0
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:
with LockModel(write=(NavigationBar,)):
NavigationBar.objects.rebuild()
@ -46,74 +53,105 @@ class NavigationBarAdmin(DraggableMPTTAdmin):
class BlogPostForm(ModelForm):
def __init__(self, *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:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2',
attrs={'style': 'width: 100%'}),
"authors": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"organizations": AdminHeavySelect2MultipleWidget(
data_view="organization_select2", attrs={"style": "width: 100%"}
),
}
if HeavyPreviewAdminPageDownWidget is not None:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview'))
widgets['summary'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('blog_preview'))
widgets["content"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("blog_preview")
)
widgets["summary"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("blog_preview")
)
class BlogPostAdmin(CompareVersionAdmin):
fieldsets = (
(None, {'fields': ('title', 'slug', 'authors', 'visible', 'sticky', 'publish_on',
'is_organization_private', 'organizations')}),
(_('Content'), {'fields': ('content', 'og_image')}),
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}),
(
None,
{
"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',)}
list_display = ('id', 'title', 'visible', 'sticky', 'publish_on')
list_display_links = ('id', 'title')
ordering = ('-publish_on',)
prepopulated_fields = {"slug": ("title",)}
list_display = ("id", "title", "visible", "sticky", "publish_on")
list_display_links = ("id", "title")
ordering = ("-publish_on",)
form = BlogPostForm
date_hierarchy = 'publish_on'
date_hierarchy = "publish_on"
def has_change_permission(self, request, obj=None):
return (request.user.has_perm('judge.edit_all_post') or
request.user.has_perm('judge.change_blogpost') and (
obj is None or
obj.authors.filter(id=request.profile.id).exists()))
return (
request.user.has_perm("judge.edit_all_post")
or request.user.has_perm("judge.change_blogpost")
and (obj is None or obj.authors.filter(id=request.profile.id).exists())
)
class SolutionForm(ModelForm):
def __init__(self, *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:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'problem': AdminHeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 250px'}),
"authors": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"problem": AdminHeavySelect2Widget(
data_view="problem_select2", attrs={"style": "width: 250px"}
),
}
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 Meta:
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):
fields = ('key', 'link', 'name', 'display', 'icon', 'text')
list_display = ('name', 'key')
fields = ("key", "link", "name", "display", "icon", "text")
list_display = ("name", "key")
form = LicenseForm
class UserListFilter(admin.SimpleListFilter):
title = _('user')
parameter_name = 'user'
title = _("user")
parameter_name = "user"
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):
if self.value():
@ -122,10 +160,24 @@ class UserListFilter(admin.SimpleListFilter):
class LogEntryAdmin(admin.ModelAdmin):
readonly_fields = ('user', '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')
readonly_fields = (
"user",
"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
actions = None
@ -144,25 +196,35 @@ class LogEntryAdmin(admin.ModelAdmin):
else:
ct = obj.content_type
try:
link = format_html('<a href="{1}">{0}</a>', obj.object_repr,
reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(obj.object_id,)))
link = format_html(
'<a href="{1}">{0}</a>',
obj.object_repr,
reverse(
"admin:%s_%s_change" % (ct.app_label, ct.model),
args=(obj.object_id,),
),
)
except NoReverseMatch:
link = obj.object_repr
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):
if obj.is_deletion():
return None
ct = obj.content_type
try:
url = reverse('admin:%s_%s_history' % (ct.app_label, ct.model), args=(obj.object_id,))
link = format_html('<a href="{1}">{0}</a>', _('Diff'), url)
url = reverse(
"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:
link = None
return link
diff_link.short_description = _('diff')
diff_link.short_description = _("diff")
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 judge.models import Organization
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class OrganizationForm(ModelForm):
class Meta:
widgets = {
'admins': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'registrant': AdminHeavySelect2Widget(data_view='profile_select2'),
"admins": AdminHeavySelect2MultipleWidget(data_view="profile_select2"),
"registrant": AdminHeavySelect2Widget(data_view="profile_select2"),
}
if HeavyPreviewAdminPageDownWidget is not None:
widgets['about'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('organization_preview'))
widgets["about"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("organization_preview")
)
class OrganizationAdmin(VersionAdmin):
readonly_fields = ('creation_date',)
fields = ('name', 'slug', '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',)}
readonly_fields = ("creation_date",)
fields = (
"name",
"slug",
"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_bottom = True
form = OrganizationForm
def show_public(self, obj):
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>',
obj.get_absolute_url(), gettext('View on site'))
return format_html(
'<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):
fields = self.readonly_fields
if not request.user.has_perm('judge.organization_admin'):
return fields + ('registrant', 'admins', 'is_open', 'slots')
if not request.user.has_perm("judge.organization_admin"):
return fields + ("registrant", "admins", "is_open", "slots")
return fields
def get_queryset(self, request):
queryset = Organization.objects.all()
if request.user.has_perm('judge.edit_all_organization'):
if request.user.has_perm("judge.edit_all_organization"):
return queryset
else:
return queryset.filter(admins=request.profile.id)
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
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 obj.admins.filter(id=request.profile.id).exists()
class OrganizationRequestAdmin(admin.ModelAdmin):
list_display = ('username', 'organization', 'state', 'time')
readonly_fields = ('user', 'organization')
list_display = ("username", "organization", "state", "time")
readonly_fields = ("user", "organization")
def username(self, obj):
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 judge.models import LanguageLimit, Problem, ProblemClarification, ProblemTranslation, Profile, Solution
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminSelect2MultipleWidget, AdminSelect2Widget, \
CheckboxSelectMultipleWithSelectAll, HeavyPreviewAdminPageDownWidget, HeavyPreviewPageDownWidget
from judge.models import (
LanguageLimit,
Problem,
ProblemClarification,
ProblemTranslation,
Profile,
Solution,
)
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminSelect2MultipleWidget,
AdminSelect2Widget,
CheckboxSelectMultipleWithSelectAll,
HeavyPreviewAdminPageDownWidget,
HeavyPreviewPageDownWidget,
)
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):
super(ProblemForm, self).__init__(*args, **kwargs)
self.fields['authors'].widget.can_add_related = False
self.fields['curators'].widget.can_add_related = False
self.fields['testers'].widget.can_add_related = False
self.fields['banned_users'].widget.can_add_related = False
self.fields['change_message'].widget.attrs.update({
'placeholder': gettext('Describe the changes you made (optional)'),
})
self.fields["authors"].widget.can_add_related = False
self.fields["curators"].widget.can_add_related = False
self.fields["testers"].widget.can_add_related = False
self.fields["banned_users"].widget.can_add_related = False
self.fields["change_message"].widget.attrs.update(
{
"placeholder": gettext("Describe the changes you made (optional)"),
}
)
class Meta:
widgets = {
'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'curators': AdminHeavySelect2MultipleWidget(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',
attrs={'style': 'width: 100%'}),
'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2',
attrs={'style': 'width: 100%'}),
'types': AdminSelect2MultipleWidget,
'group': AdminSelect2Widget,
"authors": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"curators": AdminHeavySelect2MultipleWidget(
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", attrs={"style": "width: 100%"}
),
"organizations": AdminHeavySelect2MultipleWidget(
data_view="organization_select2", attrs={"style": "width: 100%"}
),
"types": AdminSelect2MultipleWidget,
"group": AdminSelect2Widget,
}
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):
title = parameter_name = 'creator'
title = parameter_name = "creator"
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]
def queryset(self, request, queryset):
@ -63,24 +92,28 @@ class ProblemCreatorListFilter(admin.SimpleListFilter):
class LanguageLimitInlineForm(ModelForm):
class Meta:
widgets = {'language': AdminSelect2Widget}
widgets = {"language": AdminSelect2Widget}
class LanguageLimitInline(admin.TabularInline):
model = LanguageLimit
fields = ('language', 'time_limit', 'memory_limit')
fields = ("language", "time_limit", "memory_limit")
form = LanguageLimitInlineForm
class ProblemClarificationForm(ModelForm):
class Meta:
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):
model = ProblemClarification
fields = ('description',)
fields = ("description",)
form = ProblemClarificationForm
extra = 0
@ -88,20 +121,24 @@ class ProblemClarificationInline(admin.StackedInline):
class ProblemSolutionForm(ModelForm):
def __init__(self, *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:
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:
widgets['content'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('solution_preview'))
widgets["content"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("solution_preview")
)
class ProblemSolutionInline(admin.StackedInline):
model = Solution
fields = ('is_public', 'publish_on', 'authors', 'content')
fields = ("is_public", "publish_on", "authors", "content")
form = ProblemSolutionForm
extra = 0
@ -109,168 +146,250 @@ class ProblemSolutionInline(admin.StackedInline):
class ProblemTranslationForm(ModelForm):
class Meta:
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):
model = ProblemTranslation
fields = ('language', 'name', 'description')
fields = ("language", "name", "description")
form = ProblemTranslationForm
extra = 0
class ProblemAdmin(CompareVersionAdmin):
fieldsets = (
(None, {
'fields': (
'code', 'name', 'is_public', 'is_manually_managed', 'date', 'authors', 'curators', 'testers',
'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',)}),
(
None,
{
"fields": (
"code",
"name",
"is_public",
"is_manually_managed",
"date",
"authors",
"curators",
"testers",
"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']
ordering = ['code']
search_fields = ('code', 'name', 'authors__user__username', 'curators__user__username')
inlines = [LanguageLimitInline, ProblemClarificationInline, ProblemSolutionInline, ProblemTranslationInline]
list_display = [
"code",
"name",
"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
actions_on_top = True
actions_on_bottom = True
list_filter = ('is_public', ProblemCreatorListFilter)
list_filter = ("is_public", ProblemCreatorListFilter)
form = ProblemForm
date_hierarchy = 'date'
date_hierarchy = "date"
def get_actions(self, request):
actions = super(ProblemAdmin, self).get_actions(request)
if request.user.has_perm('judge.change_public_visibility'):
func, name, desc = self.get_action('make_public')
if request.user.has_perm("judge.change_public_visibility"):
func, name, desc = self.get_action("make_public")
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)
return actions
def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
if not request.user.has_perm('judge.change_public_visibility'):
fields += ('is_public',)
if not request.user.has_perm('judge.change_manually_managed'):
fields += ('is_manually_managed',)
if not request.user.has_perm("judge.change_public_visibility"):
fields += ("is_public",)
if not request.user.has_perm("judge.change_manually_managed"):
fields += ("is_manually_managed",)
return fields
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):
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):
from judge.tasks import rescore_problem
transaction.on_commit(rescore_problem.s(problem_id).delay)
def make_public(self, request, queryset):
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.message_user(request, ungettext('%d problem successfully marked as public.',
'%d problems successfully marked as public.',
count) % count)
self.message_user(
request,
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):
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.message_user(request, ungettext('%d problem successfully marked as private.',
'%d problems successfully marked as private.',
count) % count)
self.message_user(
request,
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):
queryset = Problem.objects.prefetch_related('authors__user')
queryset = Problem.objects.prefetch_related("authors__user")
queryset = queryset.annotate(
_vote_mean=Avg('problem_points_votes__points'),
_vote_std=StdDev('problem_points_votes__points'),
_vote_cnt=Count('problem_points_votes__points')
_vote_mean=Avg("problem_points_votes__points"),
_vote_std=StdDev("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
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)
if request.user.has_perm('judge.edit_own_problem'):
access |= Q(authors__id=request.profile.id) | Q(curators__id=request.profile.id)
if request.user.has_perm("judge.edit_own_problem"):
access |= Q(authors__id=request.profile.id) | Q(
curators__id=request.profile.id
)
return queryset.filter(access).distinct() if access else queryset.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
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
if not request.user.has_perm('judge.edit_own_problem'):
if not request.user.has_perm("judge.edit_own_problem"):
return False
return obj.is_editor(request.profile)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'allowed_languages':
kwargs['widget'] = CheckboxSelectMultipleWithSelectAll()
return super(ProblemAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
if db_field.name == "allowed_languages":
kwargs["widget"] = CheckboxSelectMultipleWithSelectAll()
return super(ProblemAdmin, self).formfield_for_manytomany(
db_field, request, **kwargs
)
def get_form(self, *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
def save_model(self, 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)
def construct_change_message(self, request, form, *args, **kwargs):
if form.cleaned_data.get('change_message'):
return form.cleaned_data['change_message']
return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs)
if form.cleaned_data.get("change_message"):
return form.cleaned_data["change_message"]
return super(ProblemAdmin, self).construct_change_message(
request, form, *args, **kwargs
)
def vote_mean(self, obj):
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):
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):
return obj._vote_cnt
vote_cnt.admin_order_field = '_vote_cnt'
vote_cnt.admin_order_field = "_vote_cnt"
def vote_median(self, obj):
votes = obj.problem_points_votes.values_list('points', flat=True)
return statistics.median(votes) if votes else None
votes = obj.problem_points_votes.values_list("points", flat=True)
return statistics.median(votes) if votes else None
class ProblemPointsVoteAdmin(admin.ModelAdmin):
list_display = ('vote_points', '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')
list_display = (
"vote_points",
"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):
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)
def lookup_allowed(self, key, value):
@ -278,29 +397,35 @@ class ProblemPointsVoteAdmin(admin.ModelAdmin):
def problem_code(self, obj):
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):
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):
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):
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):
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):
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):
def __init__(self, *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.
self.fields['current_contest'].queryset = self.instance.contest_history.select_related('contest') \
.only('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
self.fields[
"current_contest"
].queryset = self.instance.contest_history.select_related("contest").only(
"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:
widgets = {
'timezone': AdminSelect2Widget,
'language': AdminSelect2Widget,
'ace_theme': AdminSelect2Widget,
'current_contest': AdminSelect2Widget,
"timezone": AdminSelect2Widget,
"language": AdminSelect2Widget,
"ace_theme": AdminSelect2Widget,
"current_contest": AdminSelect2Widget,
}
if AdminPagedownWidget is not None:
widgets['about'] = AdminPagedownWidget
widgets["about"] = AdminPagedownWidget
class TimezoneFilter(admin.SimpleListFilter):
title = _('timezone')
parameter_name = 'timezone'
title = _("timezone")
parameter_name = "timezone"
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):
if self.value() is None:
@ -44,75 +54,116 @@ class TimezoneFilter(admin.SimpleListFilter):
class ProfileAdmin(VersionAdmin):
fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme',
'math_engine', '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',)
fields = (
"user",
"display_rank",
"about",
"organizations",
"timezone",
"language",
"ace_theme",
"math_engine",
"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_bottom = True
form = ProfileForm
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):
if request.user.has_perm('judge.totp'):
if request.user.has_perm("judge.totp"):
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)
else:
return self.fields
def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
if not request.user.has_perm('judge.totp'):
fields += ('is_totp_enabled',)
if not request.user.has_perm("judge.totp"):
fields += ("is_totp_enabled",)
return fields
def show_public(self, obj):
return format_html('<a href="{0}" style="white-space:nowrap;">{1}</a>',
obj.get_absolute_url(), gettext('View on site'))
show_public.short_description = ''
return format_html(
'<a href="{0}" style="white-space:nowrap;">{1}</a>',
obj.get_absolute_url(),
gettext("View on site"),
)
show_public.short_description = ""
def admin_user_admin(self, obj):
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):
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):
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):
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):
count = 0
for profile in queryset:
profile.calculate_points()
count += 1
self.message_user(request, ungettext('%d user have scores recalculated.',
'%d users have scores recalculated.',
count) % count)
recalculate_points.short_description = _('Recalculate scores')
self.message_user(
request,
ungettext(
"%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):
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'].widget = AceWidget('javascript', request.profile.ace_theme)
form.base_fields["user_script"].widget = AceWidget(
"javascript", request.profile.ace_theme
)
return form

View file

@ -16,41 +16,63 @@ from judge.widgets import AdminHeavySelect2MultipleWidget, AdminPagedownWidget
class LanguageForm(ModelForm):
problems = ModelMultipleChoiceField(
label=_('Disallowed problems'),
label=_("Disallowed problems"),
queryset=Problem.objects.all(),
required=False,
help_text=_('These problems are NOT allowed to be submitted in this language'),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
help_text=_("These problems are NOT allowed to be submitted in this language"),
widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class Meta:
if AdminPagedownWidget is not None:
widgets = {'description': AdminPagedownWidget}
widgets = {"description": AdminPagedownWidget}
class LanguageAdmin(VersionAdmin):
fields = ('key', 'name', 'short_name', 'common_name', 'ace', 'pygments', 'info', 'description',
'template', 'problems')
list_display = ('key', 'name', 'common_name', 'info')
fields = (
"key",
"name",
"short_name",
"common_name",
"ace",
"pygments",
"info",
"description",
"template",
"problems",
)
list_display = ("key", "name", "common_name", "info")
form = LanguageForm
def save_model(self, 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):
self.form.base_fields['problems'].initial = \
Problem.objects.exclude(id__in=obj.problem_set.values('id')).values_list('pk', flat=True) if obj else []
self.form.base_fields["problems"].initial = (
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)
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
class GenerateKeyTextInput(TextInput):
def render(self, name, value, attrs=None, renderer=None):
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>
<script type="text/javascript">
django.jQuery(document).ready(function ($) {{
@ -65,37 +87,59 @@ django.jQuery(document).ready(function ($) {{
}});
}});
</script>
''', name))
""",
name,
)
)
class JudgeAdminForm(ModelForm):
class Meta:
widgets = {'auth_key': GenerateKeyTextInput}
widgets = {"auth_key": GenerateKeyTextInput}
if AdminPagedownWidget is not None:
widgets['description'] = AdminPagedownWidget
widgets["description"] = AdminPagedownWidget
class JudgeAdmin(VersionAdmin):
form = JudgeAdminForm
readonly_fields = ('created', 'online', 'start_time', 'ping', 'load', 'last_ip', 'runtimes', 'problems')
fieldsets = (
(None, {'fields': ('name', 'auth_key', 'is_blocked')}),
(_('Description'), {'fields': ('description',)}),
(_('Information'), {'fields': ('created', 'online', 'last_ip', 'start_time', 'ping', 'load')}),
(_('Capabilities'), {'fields': ('runtimes', 'problems')}),
readonly_fields = (
"created",
"online",
"start_time",
"ping",
"load",
"last_ip",
"runtimes",
"problems",
)
list_display = ('name', 'online', 'start_time', 'ping', 'load', 'last_ip')
ordering = ['-online', 'name']
fieldsets = (
(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):
return ([url(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())
return [
url(
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):
judge = get_object_or_404(Judge, id=id)
judge.disconnect(force=force)
return HttpResponseRedirect(reverse('admin:judge_judge_changelist'))
return HttpResponseRedirect(reverse("admin:judge_judge_changelist"))
def disconnect_view(self, request, id):
return self.disconnect_judge(id)
@ -105,7 +149,7 @@ class JudgeAdmin(VersionAdmin):
def get_readonly_fields(self, request, obj=None):
if obj is not None and obj.online:
return self.readonly_fields + ('name',)
return self.readonly_fields + ("name",)
return self.readonly_fields
def has_delete_permission(self, request, obj=None):
@ -116,5 +160,5 @@ class JudgeAdmin(VersionAdmin):
if AdminPagedownWidget is not None:
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_ace import AceWidget
from judge.models import ContestParticipation, ContestProblem, ContestSubmission, Profile, Submission, \
SubmissionSource, SubmissionTestCase
from judge.models import (
ContestParticipation,
ContestProblem,
ContestSubmission,
Profile,
Submission,
SubmissionSource,
SubmissionTestCase,
)
from judge.utils.raw_sql import use_straight_join
class SubmissionStatusFilter(admin.SimpleListFilter):
parameter_name = title = 'status'
__lookups = (('None', _('None')), ('NotDone', _('Not done')), ('EX', _('Exceptional'))) + Submission.STATUS
parameter_name = title = "status"
__lookups = (
("None", _("None")),
("NotDone", _("Not done")),
("EX", _("Exceptional")),
) + Submission.STATUS
__handles = set(map(itemgetter(0), Submission.STATUS))
def lookups(self, request, model_admin):
return self.__lookups
def queryset(self, request, queryset):
if self.value() == 'None':
if self.value() == "None":
return queryset.filter(status=None)
elif self.value() == 'NotDone':
return queryset.exclude(status__in=['D', 'IE', 'CE', 'AB'])
elif self.value() == 'EX':
return queryset.exclude(status__in=['D', 'CE', 'G', 'AB'])
elif self.value() == "NotDone":
return queryset.exclude(status__in=["D", "IE", "CE", "AB"])
elif self.value() == "EX":
return queryset.exclude(status__in=["D", "CE", "G", "AB"])
elif self.value() in self.__handles:
return queryset.filter(status=self.value())
class SubmissionResultFilter(admin.SimpleListFilter):
parameter_name = title = 'result'
__lookups = (('None', _('None')), ('BAD', _('Unaccepted'))) + Submission.RESULT
parameter_name = title = "result"
__lookups = (("None", _("None")), ("BAD", _("Unaccepted"))) + Submission.RESULT
__handles = set(map(itemgetter(0), Submission.RESULT))
def lookups(self, request, model_admin):
return self.__lookups
def queryset(self, request, queryset):
if self.value() == 'None':
if self.value() == "None":
return queryset.filter(result=None)
elif self.value() == 'BAD':
return queryset.exclude(result='AC')
elif self.value() == "BAD":
return queryset.exclude(result="AC")
elif self.value() in self.__handles:
return queryset.filter(result=self.value())
class SubmissionTestCaseInline(admin.TabularInline):
fields = ('case', 'batch', 'status', 'time', 'memory', 'points', 'total')
readonly_fields = ('case', 'batch', 'total')
fields = ("case", "batch", "status", "time", "memory", "points", "total")
readonly_fields = ("case", "batch", "total")
model = SubmissionTestCase
can_delete = False
max_num = 0
class ContestSubmissionInline(admin.StackedInline):
fields = ('problem', 'participation', 'points')
fields = ("problem", "participation", "points")
model = ContestSubmission
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)
def formfield_for_dbfield(self, db_field, **kwargs):
submission = kwargs.pop('obj', None)
submission = kwargs.pop("obj", None)
label = None
if submission:
if db_field.name == 'participation':
kwargs['queryset'] = ContestParticipation.objects.filter(user=submission.user,
contest__problems=submission.problem) \
.only('id', 'contest__name')
if db_field.name == "participation":
kwargs["queryset"] = ContestParticipation.objects.filter(
user=submission.user, contest__problems=submission.problem
).only("id", "contest__name")
def label(obj):
return obj.contest.name
elif db_field.name == 'problem':
kwargs['queryset'] = ContestProblem.objects.filter(problem=submission.problem) \
.only('id', 'problem__name', 'contest__name')
elif db_field.name == "problem":
kwargs["queryset"] = ContestProblem.objects.filter(
problem=submission.problem
).only("id", "problem__name", "contest__name")
def label(obj):
return pgettext('contest problem', '%(problem)s in %(contest)s') % {
'problem': obj.problem.name, 'contest': obj.contest.name,
return pgettext("contest problem", "%(problem)s in %(contest)s") % {
"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:
field.label_from_instance = label
return field
class SubmissionSourceInline(admin.StackedInline):
fields = ('source',)
fields = ("source",)
model = SubmissionSource
can_delete = False
extra = 0
def get_formset(self, request, obj=None, **kwargs):
kwargs.setdefault('widgets', {})['source'] = AceWidget(mode=obj and obj.language.ace,
theme=request.profile.ace_theme)
kwargs.setdefault("widgets", {})["source"] = AceWidget(
mode=obj and obj.language.ace, theme=request.profile.ace_theme
)
return super().get_formset(request, obj, **kwargs)
class SubmissionAdmin(admin.ModelAdmin):
readonly_fields = ('user', 'problem', 'date', 'judged_date')
fields = ('user', 'problem', 'date', 'judged_date', 'time', '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')
readonly_fields = ("user", "problem", "date", "judged_date")
fields = (
"user",
"problem",
"date",
"judged_date",
"time",
"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_bottom = True
inlines = [SubmissionSourceInline, SubmissionTestCaseInline, ContestSubmissionInline]
inlines = [
SubmissionSourceInline,
SubmissionTestCaseInline,
ContestSubmissionInline,
]
def get_queryset(self, request):
queryset = Submission.objects.select_related('problem', 'user__user', 'language').only(
'problem__code', 'problem__name', 'user__user__username', 'language__name',
'time', 'memory', 'points', 'status', 'result',
queryset = Submission.objects.select_related(
"problem", "user__user", "language"
).only(
"problem__code",
"problem__name",
"user__user__username",
"language__name",
"time",
"memory",
"points",
"status",
"result",
)
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
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
def has_add_permission(self, request):
return False
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
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 obj.problem.is_editor(request.profile)
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):
if not request.user.has_perm('judge.rejudge_submission') 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)
if not request.user.has_perm(
"judge.rejudge_submission"
) 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
queryset = queryset.order_by('id')
if not request.user.has_perm('judge.rejudge_submission_lot') and \
queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT:
self.message_user(request, gettext('You do not have the permission to rejudge THAT many submissions.'),
level=messages.ERROR)
queryset = queryset.order_by("id")
if (
not request.user.has_perm("judge.rejudge_submission_lot")
and queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT
):
self.message_user(
request,
gettext(
"You do not have the permission to rejudge THAT many submissions."
),
level=messages.ERROR,
)
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
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)
for model in queryset:
model.judge(rejudge=True, batch_rejudge=True)
self.message_user(request, ungettext('%d submission was successfully scheduled for rejudging.',
'%d submissions were successfully scheduled for rejudging.',
judged) % judged)
judge.short_description = _('Rejudge the selected submissions')
self.message_user(
request,
ungettext(
"%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):
if not request.user.has_perm('judge.rejudge_submission'):
self.message_user(request, gettext('You do not have the permission to rejudge submissions.'),
level=messages.ERROR)
if not request.user.has_perm("judge.rejudge_submission"):
self.message_user(
request,
gettext("You do not have the permission to rejudge submissions."),
level=messages.ERROR,
)
return
submissions = list(queryset.defer(None).select_related(None).select_related('problem')
.only('points', 'case_points', 'case_total', 'problem__partial', 'problem__points'))
submissions = list(
queryset.defer(None)
.select_related(None)
.select_related("problem")
.only(
"points",
"case_points",
"case_total",
"problem__partial",
"problem__points",
)
)
for submission in submissions:
submission.points = round(submission.case_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 = round(
submission.case_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.save()
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()
cache.delete('user_complete:%d' % profile.id)
cache.delete('user_attempted:%d' % profile.id)
cache.delete("user_complete:%d" % profile.id)
cache.delete("user_attempted:%d" % profile.id)
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()
self.message_user(request, ungettext('%d submission were successfully rescored.',
'%d submissions were successfully rescored.',
len(submissions)) % len(submissions))
recalculate_score.short_description = _('Rescore the selected submissions')
self.message_user(
request,
ungettext(
"%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):
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):
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):
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):
return round(obj.time, 2) if obj.time is not None else 'None'
execution_time.short_description = _('Time')
execution_time.admin_order_field = 'time'
return round(obj.time, 2) if obj.time is not None else "None"
execution_time.short_description = _("Time")
execution_time.admin_order_field = "time"
def pretty_memory(self, obj):
memory = obj.memory
if memory is None:
return gettext('None')
return gettext("None")
if memory < 1000:
return gettext('%d KB') % memory
return gettext("%d KB") % memory
else:
return gettext('%.2f MB') % (memory / 1024)
pretty_memory.admin_order_field = 'memory'
pretty_memory.short_description = _('Memory')
return gettext("%.2f MB") % (memory / 1024)
pretty_memory.admin_order_field = "memory"
pretty_memory.short_description = _("Memory")
def language_column(self, obj):
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):
return format_html('<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />', obj.id)
judge_column.short_description = ''
return format_html(
'<input type="button" value="Rejudge" onclick="location.href=\'{}/judge/\'" />',
obj.id,
)
judge_column.short_description = ""
def get_urls(self):
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()
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()
submission = get_object_or_404(Submission, id=id)
if not request.user.has_perm('judge.edit_all_problem') and \
not submission.problem.is_editor(request.profile):
if not request.user.has_perm(
"judge.edit_all_problem"
) and not submission.problem.is_editor(request.profile):
raise PermissionDenied()
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):
problems = ModelMultipleChoiceField(
label=_('Included problems'),
label=_("Included problems"),
queryset=Problem.objects.all(),
required=False,
help_text=_('These problems are included in this group of problems'),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
help_text=_("These problems are included in this group of problems"),
widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class ProblemGroupAdmin(admin.ModelAdmin):
fields = ('name', 'full_name', 'problems')
fields = ("name", "full_name", "problems")
form = ProblemGroupForm
def save_model(self, 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()
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)
class ProblemTypeForm(ModelForm):
problems = ModelMultipleChoiceField(
label=_('Included problems'),
label=_("Included problems"),
queryset=Problem.objects.all(),
required=False,
help_text=_('These problems are included in this type of problems'),
widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
help_text=_("These problems are included in this type of problems"),
widget=AdminHeavySelect2MultipleWidget(data_view="problem_select2"),
)
class ProblemTypeAdmin(admin.ModelAdmin):
fields = ('name', 'full_name', 'problems')
fields = ("name", "full_name", "problems")
form = ProblemTypeForm
def save_model(self, 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()
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)

View file

@ -4,36 +4,56 @@ from django.forms import ModelForm
from django.urls import reverse_lazy
from judge.models import TicketMessage
from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, HeavyPreviewAdminPageDownWidget
from judge.widgets import (
AdminHeavySelect2MultipleWidget,
AdminHeavySelect2Widget,
HeavyPreviewAdminPageDownWidget,
)
class TicketMessageForm(ModelForm):
class Meta:
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:
widgets['body'] = HeavyPreviewAdminPageDownWidget(preview=reverse_lazy('ticket_preview'))
widgets["body"] = HeavyPreviewAdminPageDownWidget(
preview=reverse_lazy("ticket_preview")
)
class TicketMessageInline(StackedInline):
model = TicketMessage
form = TicketMessageForm
fields = ('user', 'body')
fields = ("user", "body")
class TicketForm(ModelForm):
class Meta:
widgets = {
'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
'assignees': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
"user": AdminHeavySelect2Widget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
"assignees": AdminHeavySelect2MultipleWidget(
data_view="profile_select2", attrs={"style": "width: 100%"}
),
}
class TicketAdmin(ModelAdmin):
fields = ('title', 'time', 'user', 'assignees', 'content_type', 'object_id', 'notes')
readonly_fields = ('time',)
list_display = ('title', 'user', 'time', 'linked_item')
fields = (
"title",
"time",
"user",
"assignees",
"content_type",
"object_id",
"notes",
)
readonly_fields = ("time",)
list_display = ("title", "user", "time", "linked_item")
inlines = [TicketMessageInline]
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
class VolunteerProblemVoteAdmin(admin.ModelAdmin):
fields = ('voter', 'problem', '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'
fields = (
"voter",
"problem",
"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):
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>")
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):
name = 'judge'
verbose_name = gettext_lazy('Online Judge')
name = "judge"
verbose_name = gettext_lazy("Online Judge")
def ready(self):
# 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
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
MAX_ALLOWED_PACKET_SIZE = 8 * 1024 * 1024
@ -20,7 +20,7 @@ def proxy_list(human_readable):
globs = []
addrs = []
for item in human_readable:
if '*' in item or '-' in item:
if "*" in item or "-" in item:
globs.append(IPGlob(item))
else:
addrs.append(item)
@ -43,7 +43,7 @@ class RequestHandlerMeta(type):
try:
handler.handle()
except BaseException:
logger.exception('Error in base packet handling')
logger.exception("Error in base packet handling")
raise
finally:
handler.on_disconnect()
@ -70,8 +70,12 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
def read_sized_packet(self, size, initial=None):
if size > MAX_ALLOWED_PACKET_SIZE:
logger.log(logging.WARNING if self._got_packet else logging.INFO,
'Disconnecting client due to too-large message size (%d bytes): %s', size, self.client_address)
logger.log(
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()
buffer = []
@ -86,7 +90,7 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
data = self.request.recv(remainder)
remainder -= len(data)
buffer.append(data)
self._on_packet(b''.join(buffer))
self._on_packet(b"".join(buffer))
def parse_proxy_protocol(self, line):
words = line.split()
@ -94,18 +98,18 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
if len(words) < 2:
raise Disconnect()
if words[1] == b'TCP4':
if words[1] == b"TCP4":
if len(words) != 6:
raise Disconnect()
self.client_address = (utf8text(words[2]), utf8text(words[4]))
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.server_address = (utf8text(words[3]), utf8text(words[5]), 0, 0)
elif words[1] != b'UNKNOWN':
elif words[1] != b"UNKNOWN":
raise Disconnect()
def read_size(self, buffer=b''):
def read_size(self, buffer=b""):
while len(buffer) < size_pack.size:
recv = self.request.recv(size_pack.size - len(buffer))
if not recv:
@ -113,9 +117,9 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
buffer += recv
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.
while b'\r\n' not in buffer:
while b"\r\n" not in buffer:
if len(buffer) > 107:
raise Disconnect()
data = self.request.recv(107)
@ -125,7 +129,7 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
return buffer
def _on_packet(self, data):
decompressed = zlib.decompress(data).decode('utf-8')
decompressed = zlib.decompress(data).decode("utf-8")
self._got_packet = True
self.on_packet(decompressed)
@ -145,8 +149,10 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
try:
tag = self.read_size()
self._initial_tag = size_pack.pack(tag)
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')
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")
self.parse_proxy_protocol(proxy)
while remainder:
@ -154,8 +160,8 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
self.read_sized_packet(self.read_size(remainder))
break
size = size_pack.unpack(remainder[:size_pack.size])[0]
remainder = remainder[size_pack.size:]
size = size_pack.unpack(remainder[: size_pack.size])[0]
remainder = remainder[size_pack.size :]
if len(remainder) <= size:
self.read_sized_packet(size, remainder)
break
@ -171,25 +177,36 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
return
except zlib.error:
if self._got_packet:
logger.warning('Encountered zlib error during packet handling, disconnecting client: %s',
self.client_address, exc_info=True)
logger.warning(
"Encountered zlib error during packet handling, disconnecting client: %s",
self.client_address,
exc_info=True,
)
else:
logger.info('Potentially wrong protocol (zlib error): %s: %r', self.client_address, self._initial_tag,
exc_info=True)
logger.info(
"Potentially wrong protocol (zlib error): %s: %r",
self.client_address,
self._initial_tag,
exc_info=True,
)
except socket.timeout:
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()
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:
# 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
raise
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)
def close(self):

View file

@ -11,7 +11,7 @@ from judge.bridge.judge_list import JudgeList
from judge.bridge.server import Server
from judge.models import Judge, Submission
logger = logging.getLogger('judge.bridge')
logger = logging.getLogger("judge.bridge")
def reset_judges():
@ -20,12 +20,17 @@ def reset_judges():
def judge_daemon():
reset_judges()
Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS) \
.update(status='IE', result='IE', error=None)
Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS).update(
status="IE", result="IE", error=None
)
judges = JudgeList()
judge_server = Server(settings.BRIDGED_JUDGE_ADDRESS, partial(JudgeHandler, judges=judges))
django_server = Server(settings.BRIDGED_DJANGO_ADDRESS, partial(DjangoHandler, judges=judges))
judge_server = Server(
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=judge_server.serve_forever).start()
@ -33,7 +38,7 @@ def judge_daemon():
stop = threading.Event()
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()
signal.signal(signal.SIGINT, signal_handler)

View file

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

View file

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

View file

@ -3,19 +3,22 @@ from judge.bridge.base_handler import ZlibPacketHandler
class EchoPacketHandler(ZlibPacketHandler):
def on_connect(self):
print('New client:', self.client_address)
print("New client:", self.client_address)
self.timeout = 5
def on_timeout(self):
print('Inactive client:', self.client_address)
print("Inactive client:", self.client_address)
def on_packet(self, data):
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)
def on_disconnect(self):
print('Closed client:', self.client_address)
print("Closed client:", self.client_address)
def main():
@ -23,9 +26,9 @@ def main():
from judge.bridge.server import Server
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--host', action='append')
parser.add_argument('-p', '--port', type=int, action='append')
parser.add_argument('-P', '--proxy', action='append')
parser.add_argument("-l", "--host", action="append")
parser.add_argument("-p", "--port", type=int, action="append")
parser.add_argument("-P", "--proxy", action="append")
args = parser.parse_args()
class Handler(EchoPacketHandler):
@ -35,5 +38,5 @@ def main():
server.serve_forever()
if __name__ == '__main__':
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ try:
except ImportError:
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):
@ -18,7 +18,9 @@ class JudgeList(object):
def __init__(self):
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.node_map = {}
self.submission_map = {}
@ -32,11 +34,19 @@ class JudgeList(object):
id, problem, language, source, judge_id = node.value
if judge.can_judge(problem, language, judge_id):
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:
judge.submit(id, problem, language, source)
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)
return
self.queue.remove(node)
@ -76,14 +86,14 @@ class JudgeList(object):
def on_judge_free(self, judge, submission):
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]
judge._working = False
self._handle_free_judge(judge)
def abort(self, submission):
with self.lock:
logger.info('Abort request: %d', submission)
logger.info("Abort request: %d", submission)
try:
self.submission_map[submission].abort()
return True
@ -108,21 +118,33 @@ class JudgeList(object):
return
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:
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:
logger.info('Free judges: %d', len(candidates))
logger.info("Free judges: %d", len(candidates))
if candidates:
# Schedule the submission on the judge reporting least load.
judge = min(candidates, key=attrgetter('load'))
logger.info('Dispatched submission %d to: %s', id, judge.name)
judge = min(candidates, key=attrgetter("load"))
logger.info("Dispatched submission %d to: %s", id, judge.name)
self.submission_map[id] = judge
try:
judge.submit(id, problem, language, source)
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)
return self.judge(id, problem, language, source, judge_id, priority)
else:
@ -130,4 +152,4 @@ class JudgeList(object):
(id, problem, language, source, judge_id),
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()
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:
thread.daemon = True
thread.start()

View file

@ -2,9 +2,9 @@ from django.core.cache import cache
def finished_submission(sub):
keys = ['user_complete:%d' % sub.user_id, 'user_attempted:%s' % sub.user_id]
if hasattr(sub, 'contest'):
keys = ["user_complete:%d" % sub.user_id, "user_attempted:%s" % sub.user_id]
if hasattr(sub, "contest"):
participation = sub.contest.participation
keys += ['contest_complete:%d' % participation.id]
keys += ['contest_attempted:%d' % participation.id]
keys += ["contest_complete:%d" % participation.id]
keys += ["contest_attempted:%d" % participation.id]
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.functions import Coalesce
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.utils.decorators import method_decorator
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
def add_mention_notifications(comment):
user_referred = get_user_from_text(comment.body).exclude(id=comment.author.id)
for user in user_referred:
notification_ref = Notification(owner=user,
comment=comment,
category='Mention')
notification_ref = Notification(owner=user, comment=comment, category="Mention")
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 Meta:
model = Comment
fields = ['body', 'parent']
fields = ["body", "parent"]
widgets = {
'parent': forms.HiddenInput(),
"parent": forms.HiddenInput(),
}
if HeavyPreviewPageDownWidget is not None:
widgets['body'] = HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'),
preview_timeout=1000, hide_preview_button=True)
widgets["body"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("comment_preview"),
preview_timeout=1000,
hide_preview_button=True,
)
def __init__(self, request, *args, **kwargs):
self.request = request
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):
if self.request is not None and self.request.user.is_authenticated:
profile = self.request.profile
if profile.mute:
raise ValidationError(_('Your part is silent, little toad.'))
elif (not self.request.user.is_staff and
not profile.submission_set.filter(points=F('problem__points')).exists()):
raise ValidationError(_('You need to have solved at least one problem '
'before your voice can be heard.'))
raise ValidationError(_("Your part is silent, little toad."))
elif (
not self.request.user.is_staff
and not profile.submission_set.filter(
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()
@ -80,10 +89,12 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
return self.comment_page
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 (CommentLock.objects.filter(page=self.get_comment_page()).exists()
or (self.request.in_contest and self.request.participation.contest.use_clarifications))
return CommentLock.objects.filter(page=self.get_comment_page()).exists() or (
self.request.in_contest
and self.request.participation.contest.use_clarifications
)
@method_decorator(login_required)
def post(self, request, *args, **kwargs):
@ -93,14 +104,16 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
if self.is_comment_locked():
return HttpResponseForbidden()
parent = request.POST.get('parent')
parent = request.POST.get("parent")
if parent:
try:
parent = int(parent)
except ValueError:
return HttpResponseNotFound()
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()
form = CommentForm(request, request.POST)
@ -109,21 +122,22 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
comment.author = request.profile
comment.page = page
with LockModel(write=(Comment, Revision, Version), read=(ContentType,)), revisions.create_revision():
with LockModel(
write=(Comment, Revision, Version), read=(ContentType,)
), revisions.create_revision():
revisions.set_user(request.user)
revisions.set_comment(_('Posted comment'))
revisions.set_comment(_("Posted comment"))
comment.save()
# add notification for reply
if comment.parent and comment.parent.author != comment.author:
notification_rep = Notification(owner=comment.parent.author,
comment=comment,
category='Reply')
notification_rep = Notification(
owner=comment.parent.author, comment=comment, category="Reply"
)
notification_rep.save()
add_mention_notifications(comment)
return HttpResponseRedirect(request.path)
context = self.get_context_data(object=self.object, comment_form=form)
@ -131,25 +145,40 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return self.render_to_response(self.get_context_data(
object=self.object,
comment_form=CommentForm(request, initial={'page': self.get_comment_page(), 'parent': None}),
))
return self.render_to_response(
self.get_context_data(
object=self.object,
comment_form=CommentForm(
request, initial={"page": self.get_comment_page(), "parent": None}
),
)
)
def get_context_data(self, **kwargs):
context = super(CommentedDetailView, self).get_context_data(**kwargs)
queryset = Comment.objects.filter(hidden=False, page=self.get_comment_page())
context['has_comments'] = queryset.exists()
context['comment_lock'] = self.is_comment_locked()
queryset = queryset.select_related('author__user').defer('author__about').annotate(revisions=Count('versions'))
context["has_comments"] = queryset.exists()
context["comment_lock"] = self.is_comment_locked()
queryset = (
queryset.select_related("author__user")
.defer("author__about")
.annotate(revisions=Count("versions"))
)
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
unique_together_left_join(queryset, CommentVote, 'comment', 'voter', profile.id)
context['is_new_user'] = (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
unique_together_left_join(
queryset, CommentVote, "comment", "voter", profile.id
)
context["is_new_user"] = (
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

View file

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

View file

@ -93,7 +93,7 @@ class BaseContestFormat(six.with_metaclass(ABCMeta)):
@classmethod
def best_solution_state(cls, points, total):
if not points:
return 'failed-score'
return "failed-score"
if points == total:
return 'full-score'
return 'partial-score'
return "full-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
@register_contest_format('default')
@register_contest_format("default")
class DefaultContestFormat(BaseContestFormat):
name = gettext_lazy('Default')
name = gettext_lazy("Default")
@classmethod
def validate(cls, 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):
super(DefaultContestFormat, self).__init__(contest, config)
@ -30,14 +32,18 @@ class DefaultContestFormat(BaseContestFormat):
points = 0
format_data = {}
for result in participation.submissions.values('problem_id').annotate(
time=Max('submission__date'), points=Max('points'),
for result in participation.submissions.values("problem_id").annotate(
time=Max("submission__date"),
points=Max("points"),
):
dt = (result['time'] - participation.start).total_seconds()
if result['points']:
dt = (result["time"] - participation.start).total_seconds()
if result["points"]:
cumtime += dt
format_data[str(result['problem_id'])] = {'time': dt, 'points': result['points']}
points += result['points']
format_data[str(result["problem_id"])] = {
"time": dt,
"points": result["points"],
}
points += result["points"]
participation.cumtime = max(cumtime, 0)
participation.score = points
@ -49,30 +55,50 @@ class DefaultContestFormat(BaseContestFormat):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
return format_html(
u'<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 '') +
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'),
'<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 ""
)
+ 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:
return mark_safe('<td class="problem-score-col"></td>')
def display_participation_result(self, participation):
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),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday'),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday"),
)
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):
return '''
return """
function(n)
return tostring(math.floor(n + 1))
end
'''
"""

View file

@ -14,17 +14,21 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr
@register_contest_format('ecoo')
@register_contest_format("ecoo")
class ECOOContestFormat(DefaultContestFormat):
name = gettext_lazy('ECOO')
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}
'''
name = gettext_lazy("ECOO")
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,
}
"""
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.
time_bonus: Number of minutes to award an extra point for submitting before the contest end.
Specify 0 to disable. Defaults to 5.
'''
"""
@classmethod
def validate(cls, config):
@ -32,7 +36,9 @@ class ECOOContestFormat(DefaultContestFormat):
return
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():
if key not in cls.config_defaults:
@ -40,7 +46,9 @@ class ECOOContestFormat(DefaultContestFormat):
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
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):
self.config = self.config_defaults.copy()
@ -53,7 +61,8 @@ class ECOOContestFormat(DefaultContestFormat):
format_data = {}
with connection.cursor() as cursor:
cursor.execute('''
cursor.execute(
"""
SELECT (
SELECT MAX(ccs.points)
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_submission sub ON (sub.id = cs.submission_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():
time = from_database_time(time)
dt = (time - participation.start).total_seconds()
if self.config['cumtime']:
if self.config["cumtime"]:
cumtime += dt
bonus = 0
if score > 0:
# First AC bonus
if subs == 1 and score == max_score:
bonus += self.config['first_ac_bonus']
bonus += self.config["first_ac_bonus"]
# Time bonus
if self.config['time_bonus']:
bonus += (participation.end_time - time).total_seconds() // 60 // self.config['time_bonus']
if self.config["time_bonus"]:
bonus += (
(participation.end_time - time).total_seconds()
// 60
// self.config["time_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
participation.cumtime = cumtime
@ -99,25 +114,47 @@ class ECOOContestFormat(DefaultContestFormat):
def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
bonus = format_html('<small> +{bonus}</small>',
bonus=floatformat(format_data['bonus'])) if format_data['bonus'] else ''
bonus = (
format_html(
"<small> +{bonus}</small>", bonus=floatformat(format_data["bonus"])
)
if format_data["bonus"]
else ""
)
return format_html(
'<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 '') +
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']),
state=(
(
"pretest-"
if self.contest.run_pretests_only
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,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
)
else:
return mark_safe('<td></td>')
return mark_safe("<td></td>")
def display_participation_result(self, participation):
return format_html(
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
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
@register_contest_format('icpc')
@register_contest_format("icpc")
class ICPCContestFormat(DefaultContestFormat):
name = gettext_lazy('ICPC')
config_defaults = {'penalty': 20}
config_validators = {'penalty': lambda x: x >= 0}
'''
name = gettext_lazy("ICPC")
config_defaults = {"penalty": 20}
config_validators = {"penalty": lambda x: x >= 0}
"""
penalty: Number of penalty minutes each incorrect submission adds. Defaults to 20.
'''
"""
@classmethod
def validate(cls, config):
@ -29,7 +29,9 @@ class ICPCContestFormat(DefaultContestFormat):
return
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():
if key not in cls.config_defaults:
@ -37,7 +39,9 @@ class ICPCContestFormat(DefaultContestFormat):
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
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):
self.config = self.config_defaults.copy()
@ -52,7 +56,8 @@ class ICPCContestFormat(DefaultContestFormat):
format_data = {}
with connection.cursor() as cursor:
cursor.execute('''
cursor.execute(
"""
SELECT MAX(cs.points) as `points`, (
SELECT MIN(csub.date)
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_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id
''', (participation.id, participation.id))
""",
(participation.id, participation.id),
)
for points, time, prob in cursor.fetchall():
time = from_database_time(time)
dt = (time - participation.start).total_seconds()
# Compute penalty
if self.config['penalty']:
if self.config["penalty"]:
# An IE can have a submission result of `None`
subs = participation.submissions.exclude(submission__result__isnull=True) \
.exclude(submission__result__in=['IE', 'CE']) \
.filter(problem_id=prob)
subs = (
participation.submissions.exclude(
submission__result__isnull=True
)
.exclude(submission__result__in=["IE", "CE"])
.filter(problem_id=prob)
)
if points:
prev = subs.filter(submission__date__lte=time).count() - 1
penalty += prev * self.config['penalty'] * 60
penalty += prev * self.config["penalty"] * 60
else:
# We should always display the penalty, even if the user has a score of 0
prev = subs.count()
@ -88,7 +99,7 @@ class ICPCContestFormat(DefaultContestFormat):
cumtime += 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
participation.cumtime = max(0, cumtime + penalty)
@ -100,23 +111,44 @@ class ICPCContestFormat(DefaultContestFormat):
def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
penalty = format_html('<small style="color:red"> ({penalty})</small>',
penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else ''
penalty = (
format_html(
'<small style="color:red"> ({penalty})</small>',
penalty=floatformat(format_data["penalty"]),
)
if format_data["penalty"]
else ""
)
return format_html(
'<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 '') +
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']),
state=(
(
"pretest-"
if self.contest.run_pretests_only
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,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
)
else:
return mark_safe('<td></td>')
return mark_safe("<td></td>")
def get_contest_problem_label_script(self):
return '''
return """
function(n)
n = n + 1
ret = ""
@ -126,4 +158,4 @@ class ICPCContestFormat(DefaultContestFormat):
end
return ret
end
'''
"""

View file

@ -14,13 +14,13 @@ from judge.timezone import from_database_time
from judge.utils.timedelta import nice_repr
@register_contest_format('ioi')
@register_contest_format("ioi")
class IOIContestFormat(DefaultContestFormat):
name = gettext_lazy('IOI')
config_defaults = {'cumtime': False}
'''
name = gettext_lazy("IOI")
config_defaults = {"cumtime": False}
"""
cumtime: Specify True if time penalties are to be computed. Defaults to False.
'''
"""
@classmethod
def validate(cls, config):
@ -28,7 +28,9 @@ class IOIContestFormat(DefaultContestFormat):
return
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():
if key not in cls.config_defaults:
@ -47,7 +49,8 @@ class IOIContestFormat(DefaultContestFormat):
format_data = {}
with connection.cursor() as cursor:
cursor.execute('''
cursor.execute(
"""
SELECT MAX(cs.points) as `score`, (
SELECT MIN(csub.date)
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_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id
''', (participation.id, participation.id))
""",
(participation.id, participation.id),
)
for score, time, prob in cursor.fetchall():
if self.config['cumtime']:
dt = (from_database_time(time) - participation.start).total_seconds()
if self.config["cumtime"]:
dt = (
from_database_time(time) - participation.start
).total_seconds()
if score:
cumtime += dt
else:
dt = 0
format_data[str(prob)] = {'time': dt, 'points': score}
format_data[str(prob)] = {"time": dt, "points": score}
points += score
participation.cumtime = max(cumtime, 0)
@ -82,12 +89,29 @@ class IOIContestFormat(DefaultContestFormat):
if format_data:
return format_html(
'<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 '') +
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 '',
state=(
(
"pretest-"
if self.contest.run_pretests_only
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"]),
time=nice_repr(timedelta(seconds=format_data["time"]), "noday")
if self.config["cumtime"]
else "",
)
else:
return mark_safe('<td class="problem-score-col"></td>')
@ -96,5 +120,7 @@ class IOIContestFormat(DefaultContestFormat):
return format_html(
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
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):
def __init__(self, write, read=()):
self.tables = ', '.join(chain(
('`%s` WRITE' % model._meta.db_table for model in write),
('`%s` READ' % model._meta.db_table for model in read),
))
self.tables = ", ".join(
chain(
("`%s` WRITE" % model._meta.db_table for model in write),
("`%s` READ" % model._meta.db_table for model in read),
)
)
self.cursor = connection.cursor()
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):
if exc_type is None:
transaction.commit()
else:
transaction.rollback()
self.cursor.execute('UNLOCK TABLES')
self.cursor.execute("UNLOCK TABLES")
self.cursor.close()

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import threading
from django.conf import settings
from websocket import WebSocketException, create_connection
__all__ = ['EventPostingError', 'EventPoster', 'post', 'last']
__all__ = ["EventPostingError", "EventPoster", "post", "last"]
_local = threading.local()
@ -20,19 +20,23 @@ class EventPoster(object):
def _connect(self):
self._conn = create_connection(settings.EVENT_DAEMON_POST)
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())
if resp['status'] == 'error':
raise EventPostingError(resp['code'])
if resp["status"] == "error":
raise EventPostingError(resp["code"])
def post(self, channel, message, tries=0):
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())
if resp['status'] == 'error':
raise EventPostingError(resp['code'])
if resp["status"] == "error":
raise EventPostingError(resp["code"])
else:
return resp['id']
return resp["id"]
except WebSocketException:
if tries > 10:
raise
@ -43,10 +47,10 @@ class EventPoster(object):
try:
self._conn.send('{"command": "last-msg"}')
resp = json.loads(self._conn.recv())
if resp['status'] == 'error':
raise EventPostingError(resp['code'])
if resp["status"] == "error":
raise EventPostingError(resp["code"])
else:
return resp['id']
return resp["id"]
except WebSocketException:
if tries > 10:
raise
@ -55,7 +59,7 @@ class EventPoster(object):
def _get_poster():
if 'poster' not in _local.__dict__:
if "poster" not in _local.__dict__:
_local.poster = EventPoster()
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/
def escape_xml_illegal_chars(val, replacement='?'):
_illegal_xml_chars_RE = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')
def escape_xml_illegal_chars(val, replacement="?"):
_illegal_xml_chars_RE = re.compile(
"[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]"
)
return _illegal_xml_chars_RE.sub(replacement, val)
class ProblemFeed(Feed):
title = 'Recently Added %s Problems' % settings.SITE_NAME
link = '/'
description = 'The latest problems added on the %s website' % settings.SITE_LONG_NAME
title = "Recently Added %s Problems" % settings.SITE_NAME
link = "/"
description = (
"The latest problems added on the %s website" % settings.SITE_LONG_NAME
)
def items(self):
return Problem.objects.filter(is_public=True, is_organization_private=False).defer('description')\
.order_by('-date', '-id')[:25]
return (
Problem.objects.filter(is_public=True, is_organization_private=False)
.defer("description")
.order_by("-date", "-id")[:25]
)
def item_title(self, problem):
return problem.name
def item_description(self, problem):
key = 'problem_feed:%d' % problem.id
key = "problem_feed:%d" % problem.id
desc = cache.get(key)
if desc is None:
desc = str(markdown(problem.description, 'problem'))[:500] + '...'
desc = str(markdown(problem.description, "problem"))[:500] + "..."
desc = escape_xml_illegal_chars(desc)
cache.set(key, desc, 86400)
return desc
@ -49,21 +57,21 @@ class AtomProblemFeed(ProblemFeed):
class CommentFeed(Feed):
title = 'Latest %s Comments' % settings.SITE_NAME
link = '/'
description = 'The latest comments on the %s website' % settings.SITE_LONG_NAME
title = "Latest %s Comments" % settings.SITE_NAME
link = "/"
description = "The latest comments on the %s website" % settings.SITE_LONG_NAME
def items(self):
return Comment.most_recent(AnonymousUser(), 25)
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):
key = 'comment_feed:%d' % comment.id
key = "comment_feed:%d" % comment.id
desc = cache.get(key)
if desc is None:
desc = str(markdown(comment.body, 'comment'))
desc = str(markdown(comment.body, "comment"))
desc = escape_xml_illegal_chars(desc)
cache.set(key, desc, 86400)
return desc
@ -80,21 +88,23 @@ class AtomCommentFeed(CommentFeed):
class BlogFeed(Feed):
title = 'Latest %s Blog Posts' % settings.SITE_NAME
link = '/'
description = 'The latest blog posts from the %s' % settings.SITE_LONG_NAME
title = "Latest %s Blog Posts" % settings.SITE_NAME
link = "/"
description = "The latest blog posts from the %s" % settings.SITE_LONG_NAME
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):
return post.title
def item_description(self, post):
key = 'blog_feed:%d' % post.id
key = "blog_feed:%d" % post.id
summary = cache.get(key)
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)
cache.set(key, summary, 86400)
return summary

View file

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

View file

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

View file

@ -1,10 +1,10 @@
from django.utils.html import escape, mark_safe
__all__ = ['highlight_code']
__all__ = ["highlight_code"]
def _make_pre_code(code):
return mark_safe('<pre>' + escape(code) + '</pre>')
return mark_safe("<pre>" + escape(code) + "</pre>")
def _wrap_code(inner):
@ -20,19 +20,28 @@ try:
import pygments.formatters.html
import pygments.util
except ImportError:
def highlight_code(code, language, cssclass=None):
return _make_pre_code(code)
else:
class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
def wrap(self, source, outfile):
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:
lexer = pygments.lexers.get_lexer_by_name(language)
except pygments.util.ClassNotFound:
return _make_pre_code(code)
if linenos:
return mark_safe(pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass, linenos='table')))
return mark_safe(pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass)))
return mark_safe(
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.user_translations import gettext
from . import (camo, chat, datetime, filesize, gravatar, language, markdown, rating, reference, render, social,
spaceless, submission, timedelta)
from . import (
camo,
chat,
datetime,
filesize,
gravatar,
language,
markdown,
rating,
reference,
render,
social,
spaceless,
submission,
timedelta,
)
from . import registry
registry.function('str', str)
registry.filter('str', str)
registry.filter('json', json.dumps)
registry.filter('highlight', highlight_code)
registry.filter('urlquote', urlquote)
registry.filter('roundfloat', round)
registry.function('inlinei18n', inlinei18n)
registry.function('mptt_tree', get_cached_trees)
registry.function('user_trans', gettext)
registry.function("str", str)
registry.filter("str", str)
registry.filter("json", json.dumps)
registry.filter("highlight", highlight_code)
registry.filter("urlquote", urlquote)
registry.filter("roundfloat", round)
registry.function("inlinei18n", inlinei18n)
registry.function("mptt_tree", get_cached_trees)
registry.function("user_trans", gettext)
@registry.function

View file

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

View file

@ -1,6 +1,7 @@
from . import registry
from chat_box.utils import encrypt_url
@registry.function
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):
@functools.wraps(func)
def wrapper(datetime, *args, **kwargs):
if getattr(datetime, 'convert_to_local_time', True):
if getattr(datetime, "convert_to_local_time", True):
datetime = localtime(datetime)
return func(datetime, *args, **kwargs)
@ -22,6 +22,6 @@ registry.filter(localtime_wrapper(time))
@registry.function
@registry.render_with('widgets/relative-time.html')
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}
@registry.render_with("widgets/relative-time.html")
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}

View file

@ -13,24 +13,28 @@ def _format_size(bytes, callback):
PB = 1 << 50
if bytes < KB:
return callback('', bytes)
return callback("", bytes)
elif bytes < MB:
return callback('K', bytes / KB)
return callback("K", bytes / KB)
elif bytes < GB:
return callback('M', bytes / MB)
return callback("M", bytes / MB)
elif bytes < TB:
return callback('G', bytes / GB)
return callback("G", bytes / GB)
elif bytes < PB:
return callback('T', bytes / TB)
return callback("T", bytes / TB)
else:
return callback('P', bytes / PB)
return callback("P", bytes / PB)
@registry.filter
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
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):
email = email.email
gravatar_url = '//www.gravatar.com/avatar/' + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest() + '?'
args = {'d': 'identicon', 's': str(size)}
gravatar_url = (
"//www.gravatar.com/avatar/"
+ hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()
+ "?"
)
args = {"d": "identicon", "s": str(size)}
if default:
args['f'] = 'y'
args["f"] = "y"
gravatar_url += urlencode(args)
return gravatar_url

View file

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

View file

@ -5,16 +5,16 @@ from lxml import html
def lazy_load(tree):
blank = static('blank.gif')
for img in tree.xpath('.//img'):
src = img.get('src', '')
if src.startswith('data') or '-math' in img.get('class', ''):
blank = static("blank.gif")
for img in tree.xpath(".//img"):
src = img.get("src", "")
if src.startswith("data") or "-math" in img.get("class", ""):
continue
noscript = html.Element('noscript')
noscript = html.Element("noscript")
copy = deepcopy(img)
copy.tail = ''
copy.tail = ""
noscript.append(copy)
img.addprevious(noscript)
img.set('data-src', src)
img.set('src', blank)
img.set('class', img.get('class') + ' unveil' if img.get('class') else 'unveil')
img.set("data-src", src)
img.set("src", blank)
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
mistune._pre_tags.append('latex')
mistune._pre_tags.append("latex")
class MathInlineGrammar(mistune.InlineGrammar):
block_math = re.compile(r'^\$\$(.*?)\$\$|^\\\[(.*?)\\\]', re.DOTALL)
math = re.compile(r'^~(.*?)~|^\\\((.*?)\\\)', re.DOTALL)
text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|\\[\[(]|https?://| {2,}\n|$)')
block_math = re.compile(r"^\$\$(.*?)\$\$|^\\\[(.*?)\\\]", re.DOTALL)
math = re.compile(r"^~(.*?)~|^\\\((.*?)\\\)", re.DOTALL)
text = re.compile(r"^[\s\S]+?(?=[\\<!\[_*`~$]|\\[\[(]|https?://| {2,}\n|$)")
class MathInlineLexer(mistune.InlineLexer):
@ -21,8 +21,10 @@ class MathInlineLexer(mistune.InlineLexer):
def __init__(self, *args, **kwargs):
self.default_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, 'block_math')
self.default_rules.insert(self.default_rules.index("strikethrough") + 1, "math")
self.default_rules.insert(
self.default_rules.index("strikethrough") + 1, "block_math"
)
super(MathInlineLexer, self).__init__(*args, **kwargs)
def output_block_math(self, m):
@ -35,14 +37,14 @@ class MathInlineLexer(mistune.InlineLexer):
tag = m.group(1)
text = m.group(3)
if self._parse_inline_html and text:
if tag == 'a':
if tag == "a":
self._in_link = True
text = self.output(text)
self._in_link = False
else:
text = self.output(text)
extra = m.group(2) or ''
html = '<%s%s>%s</%s>' % (tag, extra, text, tag)
extra = m.group(2) or ""
html = "<%s%s>%s</%s>" % (tag, extra, text, tag)
else:
html = m.group(0)
return self.renderer.inline_html(html)
@ -50,18 +52,18 @@ class MathInlineLexer(mistune.InlineLexer):
class MathRenderer(mistune.Renderer):
def __init__(self, *args, **kwargs):
if kwargs.pop('math', False) and settings.MATHOID_URL != False:
self.mathoid = MathoidMathParser(kwargs.pop('math_engine', None) or 'svg')
if kwargs.pop("math", False) and settings.MATHOID_URL != False:
self.mathoid = MathoidMathParser(kwargs.pop("math_engine", None) or "svg")
else:
self.mathoid = None
super(MathRenderer, self).__init__(*args, **kwargs)
def block_math(self, 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)
def math(self, math):
if self.mathoid is None or not math:
return r'\(%s\)' % mistune.escape(str(math))
return self.mathoid.inline_math(math)
return r"\(%s\)" % mistune.escape(str(math))
return self.mathoid.inline_math(math)

View file

@ -3,25 +3,28 @@ import mistune
class SpoilerInlineGrammar(mistune.InlineGrammar):
spoiler = re.compile(r'^\|\|(.+?)\s+([\s\S]+?)\s*\|\|')
spoiler = re.compile(r"^\|\|(.+?)\s+([\s\S]+?)\s*\|\|")
class SpoilerInlineLexer(mistune.InlineLexer):
grammar_class = SpoilerInlineGrammar
def __init__(self, *args, **kwargs):
self.default_rules.insert(0, 'spoiler')
self.default_rules.insert(0, "spoiler")
super(SpoilerInlineLexer, self).__init__(*args, **kwargs)
def output_spoiler(self, m):
return self.renderer.spoiler(m.group(1), m.group(2))
class SpoilerRenderer(mistune.Renderer):
def spoiler(self, summary, text):
return '''<details>
return """<details>
<summary style="color: brown">
<span class="spoiler-summary">%s</span>
</summary>
<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)
@registry.function('rating_class')
@registry.function("rating_class")
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):
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):
return _get_rating_value(rating_progress, obj) or 0.0
@registry.function
@registry.render_with('user/rating.html')
@registry.render_with("user/rating.html")
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 . import registry
rereference = re.compile(r'\[(r?user):(\w+)\]')
rereference = re.compile(r"\[(r?user):(\w+)\]")
def get_user(username, data):
if not data:
element = Element('span')
element = Element("span")
element.text = username
return element
element = Element('span', {'class': Profile.get_user_css_class(*data)})
link = Element('a', {'href': reverse('user_page', args=[username])})
element = Element("span", {"class": Profile.get_user_css_class(*data)})
link = Element("a", {"href": reverse("user_page", args=[username])})
link.text = username
element.append(link)
return element
@ -31,17 +31,21 @@ def get_user(username, data):
def get_user_rating(username, data):
if not data:
element = Element('span')
element = Element("span")
element.text = username
return element
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:
rating_css = rating_class(rating)
rate_box = Element('span', {'class': 'rate-box ' + rating_css})
rate_box.append(Element('span', {'style': 'height: %3.fem' % rating_progress(rating)}))
user = Element('span', {'class': 'rating ' + rating_css})
rate_box = Element("span", {"class": "rate-box " + rating_css})
rate_box.append(
Element("span", {"style": "height: %3.fem" % rating_progress(rating)})
)
user = Element("span", {"class": "rating " + rating_css})
user.text = username
element.append(rate_box)
element.append(user)
@ -51,21 +55,24 @@ def get_user_rating(username, data):
def get_user_info(usernames):
return {name: (rank, rating) for name, rank, rating in
Profile.objects.filter(user__username__in=usernames)
.values_list('user__username', 'display_rank', 'rating')}
return {
name: (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):
user_list = set()
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)
reference_map = {
'user': (get_user, get_user_info),
'ruser': (get_user_rating, get_user_info),
"user": (get_user, get_user_info),
"ruser": (get_user_rating, get_user_info),
}
@ -77,9 +84,9 @@ def process_reference(text):
elements = []
for piece in rereference.finditer(text):
if prev is None:
tail = text[last:piece.start()]
tail = text[last : piece.start()]
else:
prev.append(text[last:piece.start()])
prev.append(text[last : piece.start()])
prev = list(piece.groups())
elements.append(prev)
last = piece.end()
@ -143,52 +150,52 @@ def item_title(item):
return item.name
elif isinstance(item, Contest):
return item.name
return '<Unknown>'
return "<Unknown>"
@registry.function
@registry.render_with('user/link.html')
@registry.render_with("user/link.html")
def link_user(user):
if isinstance(user, Profile):
user, profile = user.user, user
elif isinstance(user, AbstractUser):
profile = user.profile
elif type(user).__name__ == 'ContestRankingProfile':
elif type(user).__name__ == "ContestRankingProfile":
user, profile = user.user, user
else:
raise ValueError('Expected profile or user, got %s' % (type(user),))
return {'user': user, 'profile': profile}
raise ValueError("Expected profile or user, got %s" % (type(user),))
return {"user": user, "profile": profile}
@registry.function
@registry.render_with('user/link-list.html')
@registry.render_with("user/link-list.html")
def link_users(users):
return {'users': users}
return {"users": users}
@registry.function
@registry.render_with('runtime-version-fragment.html')
@registry.render_with("runtime-version-fragment.html")
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):
tree = lxml_tree.fromstring(text)
for anchor in tree.xpath('.//a'):
href = anchor.get('href')
for anchor in tree.xpath(".//a"):
href = anchor.get("href")
if href:
anchor.set('href', urljoin(url, href))
anchor.set("href", urljoin(url, href))
return tree
@registry.function(name='urljoin')
@registry.function(name="urljoin")
def join(first, second, *rest):
if not rest:
return urljoin(first, second)
return urljoin(urljoin(first, second), *rest)
@registry.filter(name='ansi2html')
@registry.filter(name="ansi2html")
def ansi2html(s):
return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False))

View file

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

View file

@ -1,5 +1,9 @@
from django.template import (Context, Template as DjangoTemplate, TemplateSyntaxError as DjangoTemplateSyntaxError,
VariableDoesNotExist)
from django.template import (
Context,
Template as DjangoTemplate,
TemplateSyntaxError as DjangoTemplateSyntaxError,
VariableDoesNotExist,
)
from . import registry
@ -24,4 +28,4 @@ def render_django(template, **context):
try:
return compile_template(template).render(Context(context))
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.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
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_gplus', 'django_social_share/templatetags/post_to_gplus.html', post_to_gplus_url),
(
"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_gplus",
"django_social_share/templatetags/post_to_gplus.html",
post_to_gplus_url,
),
# For future versions:
# ('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),
@ -17,7 +33,7 @@ SHARES = [
def make_func(name, template, url_func):
def func(request, *args):
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])
return mark_safe(get_template(template).render(context))
@ -31,4 +47,6 @@ for name, template, url_func in SHARES:
@registry.function
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
"""
tags = {'spaceless'}
tags = {"spaceless"}
def parse(self, parser):
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(
self.call_method('_strip_spaces', [], [], None, None),
[], [], body,
self.call_method("_strip_spaces", [], [], None, None),
[],
[],
body,
).set_lineno(lineno)
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
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
can_view = False
@ -12,13 +14,15 @@ def submission_layout(submission, profile_id, user, editable_problem_ids, comple
if profile_id == submission.user_id:
can_view = True
if user.has_perm('judge.change_submission'):
if user.has_perm("judge.change_submission"):
can_view = True
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
if contest.is_editable_by(user):
can_view = True

View file

@ -5,14 +5,14 @@ from . import registry
@registry.filter
def timedelta(value, display='long'):
def timedelta(value, display="long"):
if value is None:
return value
return nice_repr(value, display)
@registry.filter
def timestampdelta(value, display='long'):
def timestampdelta(value, display="long"):
value = datetime.timedelta(seconds=value)
return timedelta(value, display)
@ -23,8 +23,8 @@ def seconds(timedelta):
@registry.filter
@registry.render_with('time-remaining-fragment.html')
@registry.render_with("time-remaining-fragment.html")
def as_countdown(time):
time_now = datetime.datetime.now(datetime.timezone.utc)
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
logger = logging.getLogger('judge.judgeapi')
size_pack = struct.Struct('!I')
logger = logging.getLogger("judge.judgeapi")
size_pack = struct.Struct("!I")
def _post_update_submission(submission, done=False):
if submission.problem.is_public:
event.post('submissions', {'type': 'done-submission' if done else 'update-submission',
'id': submission.id,
'contest': submission.contest_key,
'user': submission.user_id, 'problem': submission.problem_id,
'status': submission.status, 'language': submission.language.key})
event.post(
"submissions",
{
"type": "done-submission" if done else "update-submission",
"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):
sock = socket.create_connection(settings.BRIDGED_DJANGO_CONNECT or
settings.BRIDGED_DJANGO_ADDRESS[0])
sock = socket.create_connection(
settings.BRIDGED_DJANGO_CONNECT or settings.BRIDGED_DJANGO_ADDRESS[0]
)
output = json.dumps(packet, separators=(',', ':'))
output = zlib.compress(output.encode('utf-8'))
writer = sock.makefile('wb')
output = json.dumps(packet, separators=(",", ":"))
output = zlib.compress(output.encode("utf-8"))
writer = sock.makefile("wb")
writer.write(size_pack.pack(len(output)))
writer.write(output)
writer.close()
if reply:
reader = sock.makefile('rb', -1)
reader = sock.makefile("rb", -1)
input = reader.read(size_pack.size)
if not input:
raise ValueError('Judge did not respond')
raise ValueError("Judge did not respond")
length = size_pack.unpack(input)[0]
input = reader.read(length)
if not input:
raise ValueError('Judge did not respond')
raise ValueError("Judge did not respond")
reader.close()
sock.close()
result = json.loads(zlib.decompress(input).decode('utf-8'))
result = json.loads(zlib.decompress(input).decode("utf-8"))
return result
@ -56,13 +64,23 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No
REJUDGE_PRIORITY = 2
BATCH_REJUDGE_PRIORITY = 3
updates = {'time': None, 'memory': None, 'points': None, 'result': None, 'error': None,
'was_rejudged': rejudge, 'status': 'QU'}
updates = {
"time": None,
"memory": None,
"points": None,
"result": None,
"error": None,
"was_rejudged": rejudge,
"status": "QU",
}
try:
# 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.
updates['is_pretested'] = all(ContestSubmission.objects.filter(submission=submission)
.values_list('problem__contest__run_pretests_only', 'problem__is_pretested')[0])
updates["is_pretested"] = all(
ContestSubmission.objects.filter(submission=submission).values_list(
"problem__contest__run_pretests_only", "problem__is_pretested"
)[0]
)
except IndexError:
priority = DEFAULT_PRIORITY
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.
# 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.
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
SubmissionTestCase.objects.filter(submission_id=submission.id).delete()
try:
response = judge_request({
'name': 'submission-request',
'submission-id': submission.id,
'problem-id': submission.problem.code,
'language': submission.language.key,
'source': submission.source.source,
'judge-id': judge_id,
'priority': BATCH_REJUDGE_PRIORITY if batch_rejudge else REJUDGE_PRIORITY if rejudge else priority,
})
response = judge_request(
{
"name": "submission-request",
"submission-id": submission.id,
"problem-id": submission.problem.code,
"language": submission.language.key,
"source": submission.source.source,
"judge-id": judge_id,
"priority": BATCH_REJUDGE_PRIORITY
if batch_rejudge
else REJUDGE_PRIORITY
if rejudge
else priority,
}
)
except BaseException:
logger.exception('Failed to send request to judge')
Submission.objects.filter(id=submission.id).update(status='IE', result='IE')
logger.exception("Failed to send request to judge")
Submission.objects.filter(id=submission.id).update(status="IE", result="IE")
success = False
else:
if response['name'] != 'submission-received' or response['submission-id'] != submission.id:
Submission.objects.filter(id=submission.id).update(status='IE', result='IE')
if (
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)
success = True
return success
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):
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,
# 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):
Submission.objects.filter(id=submission.id).update(status='AB', result='AB')
event.post('sub_%s' % Submission.get_id_secret(submission.id), {'type': 'aborted-submission'})
_post_update_submission(submission, done=True)
if not response.get("judge-aborted", True):
Submission.objects.filter(id=submission.id).update(status="AB", result="AB")
event.post(
"sub_%s" % Submission.get_id_secret(submission.id),
{"type": "aborted-submission"},
)
_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.etree import ParserError, XMLSyntaxError
logger = logging.getLogger('judge.html')
logger = logging.getLogger("judge.html")
class HTMLTreeString(SafeData):
@ -12,9 +12,11 @@ class HTMLTreeString(SafeData):
try:
self._tree = html.fromstring(str, parser=html.HTMLParser(recover=True))
except (XMLSyntaxError, ParserError) as e:
if str and (not isinstance(e, ParserError) or e.args[0] != 'Document is empty'):
logger.exception('Failed to parse HTML string')
self._tree = html.Element('div')
if str and (
not isinstance(e, ParserError) or e.args[0] != "Document is empty"
):
logger.exception("Failed to parse HTML string")
self._tree = html.Element("div")
def __getattr__(self, attr):
try:
@ -23,15 +25,15 @@ class HTMLTreeString(SafeData):
return getattr(str(self), attr)
def __setattr__(self, key, value):
if key[0] == '_':
if key[0] == "_":
super(HTMLTreeString, self).__setattr__(key, value)
setattr(self._tree, key, value)
def __repr__(self):
return '<HTMLTreeString %r>' % str(self)
return "<HTMLTreeString %r>" % 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):
return other + str(self)

View file

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

View file

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

View file

@ -4,13 +4,13 @@ from judge.utils.camo import client as camo_client
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):
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):
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):
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):
parser.add_argument('source', help='language to copy from')
parser.add_argument('target', help='language to copy to')
parser.add_argument("source", help="language to copy from")
parser.add_argument("target", help="language to copy to")
def handle(self, *args, **options):
try:
source = Language.objects.get(key=options['source'])
source = Language.objects.get(key=options["source"])
except Language.DoesNotExist:
raise CommandError('Invalid source language: %s' % options['source'])
raise CommandError("Invalid source language: %s" % options["source"])
try:
target = Language.objects.get(key=options['target'])
target = Language.objects.get(key=options["target"])
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())
LanguageLimit.objects.bulk_create(LanguageLimit(problem=ll.problem, language=target, time_limit=ll.time_limit,
memory_limit=ll.memory_limit)
for ll in LanguageLimit.objects.filter(language=source))
LanguageLimit.objects.bulk_create(
LanguageLimit(
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):
help = 'create an empty problem'
help = "create an empty problem"
def add_arguments(self, parser):
parser.add_argument('code', help='problem code')
parser.add_argument('name', help='problem title')
parser.add_argument('body', help='problem description')
parser.add_argument('type', help='problem type')
parser.add_argument('group', help='problem group')
parser.add_argument("code", help="problem code")
parser.add_argument("name", help="problem title")
parser.add_argument("body", help="problem description")
parser.add_argument("type", help="problem type")
parser.add_argument("group", help="problem group")
def handle(self, *args, **options):
problem = Problem()
problem.code = options['code']
problem.name = options['name']
problem.description = options['body']
problem.group = ProblemGroup.objects.get(name=options['group'])
problem.types = [ProblemType.objects.get(name=options['type'])]
problem.code = options["code"]
problem.name = options["name"]
problem.description = options["body"]
problem.group = ProblemGroup.objects.get(name=options["group"])
problem.types = [ProblemType.objects.get(name=options["type"])]
problem.save()

View file

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

View file

@ -5,33 +5,69 @@ import sys
from django.conf import settings
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
class Command(MakeMessagesCommand):
def add_arguments(self, parser):
parser.add_argument('--locale', '-l', default=[], dest='locale', action='append',
help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). '
'Can be used multiple times.')
parser.add_argument('--exclude', '-x', 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.")
parser.add_argument(
"--locale",
"-l",
default=[],
dest="locale",
action="append",
help="Creates or updates the message files for the given locale(s) (e.g. pt_BR). "
"Can be used multiple times.",
)
parser.add_argument(
"--exclude",
"-x",
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):
locale = options.get('locale')
exclude = options.get('exclude')
self.domain = 'dmoj-user'
self.verbosity = options.get('verbosity')
process_all = options.get('all')
locale = options.get("locale")
exclude = options.get("exclude")
self.domain = "dmoj-user"
self.verbosity = options.get("verbosity")
process_all = options.get("all")
# Need to ensure that the i18n framework is enabled
if settings.configured:
@ -40,43 +76,47 @@ class Command(MakeMessagesCommand):
settings.configure(USE_I18N=True)
# Avoid messing with mutable class variables
if options.get('no_wrap'):
self.msgmerge_options = self.msgmerge_options[:] + ['--no-wrap']
self.msguniq_options = self.msguniq_options[:] + ['--no-wrap']
self.msgattrib_options = self.msgattrib_options[:] + ['--no-wrap']
self.xgettext_options = self.xgettext_options[:] + ['--no-wrap']
if options.get('no_location'):
self.msgmerge_options = self.msgmerge_options[:] + ['--no-location']
self.msguniq_options = self.msguniq_options[:] + ['--no-location']
self.msgattrib_options = self.msgattrib_options[:] + ['--no-location']
self.xgettext_options = self.xgettext_options[:] + ['--no-location']
if options.get("no_wrap"):
self.msgmerge_options = self.msgmerge_options[:] + ["--no-wrap"]
self.msguniq_options = self.msguniq_options[:] + ["--no-wrap"]
self.msgattrib_options = self.msgattrib_options[:] + ["--no-wrap"]
self.xgettext_options = self.xgettext_options[:] + ["--no-wrap"]
if options.get("no_location"):
self.msgmerge_options = self.msgmerge_options[:] + ["--no-location"]
self.msguniq_options = self.msguniq_options[:] + ["--no-location"]
self.msgattrib_options = self.msgattrib_options[:] + ["--no-location"]
self.xgettext_options = self.xgettext_options[:] + ["--no-location"]
self.no_obsolete = options.get('no_obsolete')
self.keep_pot = options.get('keep_pot')
self.no_obsolete = options.get("no_obsolete")
self.keep_pot = options.get("keep_pot")
if locale is None and not exclude and not process_all:
raise CommandError("Type '%s help %s' for usage information." % (
os.path.basename(sys.argv[0]), sys.argv[1]))
raise CommandError(
"Type '%s help %s' for usage information."
% (os.path.basename(sys.argv[0]), sys.argv[1])
)
self.invoked_for_django = False
self.locale_paths = []
self.default_locale_path = None
if os.path.isdir(os.path.join('conf', 'locale')):
self.locale_paths = [os.path.abspath(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.default_locale_path = self.locale_paths[0]
self.invoked_for_django = True
else:
self.locale_paths.extend(settings.LOCALE_PATHS)
# Allow to run makemessages inside an app dir
if os.path.isdir('locale'):
self.locale_paths.append(os.path.abspath('locale'))
if os.path.isdir("locale"):
self.locale_paths.append(os.path.abspath("locale"))
if self.locale_paths:
self.default_locale_path = self.locale_paths[0]
if not os.path.exists(self.default_locale_path):
os.makedirs(self.default_locale_path)
# 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))
# Account for excluded locales
@ -87,9 +127,9 @@ class Command(MakeMessagesCommand):
locales = set(locales) - set(exclude)
if locales:
check_programs('msguniq', 'msgmerge', 'msgattrib')
check_programs("msguniq", "msgmerge", "msgattrib")
check_programs('xgettext')
check_programs("xgettext")
try:
potfiles = self.build_potfiles()
@ -108,23 +148,33 @@ class Command(MakeMessagesCommand):
return []
def _emit_message(self, potfile, string):
potfile.write('''
potfile.write(
"""
msgid "%s"
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):
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:
self.stdout.write('processing navigation bar')
for label in NavigationBar.objects.values_list('label', flat=True):
self.stdout.write("processing navigation bar")
for label in NavigationBar.objects.values_list("label", flat=True):
if self.verbosity > 2:
self.stdout.write('processing navigation item label "%s"\n' % label)
self._emit_message(potfile, label)
if self.verbosity > 1:
self.stdout.write('processing problem types')
for name in ProblemType.objects.values_list('full_name', flat=True):
self.stdout.write("processing problem types")
for name in ProblemType.objects.values_list("full_name", flat=True):
if self.verbosity > 2:
self.stdout.write('processing problem type name "%s"\n' % 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 judge.models import Problem, ProblemTranslation
from judge.pdf_problems import DefaultPdfMaker, PhantomJSPdfMaker, PuppeteerPDFRender, SeleniumPDFRender, \
SlimerJSPdfMaker
from judge.pdf_problems import (
DefaultPdfMaker,
PhantomJSPdfMaker,
PuppeteerPDFRender,
SeleniumPDFRender,
SlimerJSPdfMaker,
)
class Command(BaseCommand):
help = 'renders a PDF file of a problem'
help = "renders a PDF file of a problem"
def add_arguments(self, parser):
parser.add_argument('code', help='code of problem to render')
parser.add_argument('directory', nargs='?', help='directory to store temporaries')
parser.add_argument('-l', '--language', default=settings.LANGUAGE_CODE,
help='language to render PDF in')
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')
parser.add_argument("code", help="code of problem to render")
parser.add_argument(
"directory", nargs="?", help="directory to store temporaries"
)
parser.add_argument(
"-l",
"--language",
default=settings.LANGUAGE_CODE,
help="language to render PDF in",
)
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):
try:
problem = Problem.objects.get(code=options['code'])
problem = Problem.objects.get(code=options["code"])
except Problem.DoesNotExist:
print('Bad problem code')
print("Bad problem code")
return
try:
trans = problem.translations.get(language=options['language'])
trans = problem.translations.get(language=options["language"])
except ProblemTranslation.DoesNotExist:
trans = None
directory = options['directory']
with options['engine'](directory, clean_up=directory is None) as maker, \
translation.override(options['language']):
directory = options["directory"]
with options["engine"](
directory, clean_up=directory is None
) as maker, translation.override(options["language"]):
problem_name = problem.name if trans is None else trans.name
maker.html = get_template('problem/raw.html').render({
'problem': problem,
'problem_name': problem_name,
'description': problem.description if trans is None else trans.description,
'url': '',
'math_engine': maker.math_engine,
}).replace('"//', '"https://').replace("'//", "'https://")
maker.html = (
get_template("problem/raw.html")
.render(
{
"problem": problem,
"problem_name": problem_name,
"description": problem.description
if trans is None
else trans.description,
"url": "",
"math_engine": maker.math_engine,
}
)
.replace('"//', '"https://')
.replace("'//", "'https://")
)
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.make(debug=True)
if not maker.success:
print(maker.log, file=sys.stderr)
elif directory is None:
shutil.move(maker.pdffile, problem.code + '.pdf')
shutil.move(maker.pdffile, problem.code + ".pdf")

View file

@ -5,4 +5,4 @@ from judge.bridge.daemon import judge_daemon
class Command(BaseCommand):
def handle(self, *args, **options):
judge_daemon()
judge_daemon()

View file

@ -6,42 +6,50 @@ from judge.models import Contest, ContestParticipation, Submission
class Command(BaseCommand):
help = 'Checks for duplicate code using MOSS'
help = "Checks for duplicate code using MOSS"
LANG_MAPPING = {
('C++', MOSS_LANG_CC),
('C', MOSS_LANG_C),
('Java', MOSS_LANG_JAVA),
('Python', MOSS_LANG_PYTHON),
('Pascal', MOSS_LANG_PASCAL),
("C++", MOSS_LANG_CC),
("C", MOSS_LANG_C),
("Java", MOSS_LANG_JAVA),
("Python", MOSS_LANG_PYTHON),
("Pascal", MOSS_LANG_PASCAL),
}
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):
moss_api_key = settings.MOSS_API_KEY
if moss_api_key is None:
print('No MOSS API Key supplied')
print("No MOSS API Key supplied")
return
contest = options['contest']
contest = options["contest"]
for problem in Contest.objects.get(key=contest).problems.order_by('code'):
print('========== %s / %s ==========' % (problem.code, problem.name))
for problem in Contest.objects.get(key=contest).problems.order_by("code"):
print("========== %s / %s ==========" % (problem.code, problem.name))
for dmoj_lang, moss_lang in self.LANG_MAPPING:
print("%s: " % dmoj_lang, end=' ')
print("%s: " % dmoj_lang, end=" ")
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,
result='AC', problem__id=problem.id,
result="AC",
problem__id=problem.id,
language__common_name=dmoj_lang,
).values_list('user__user__username', 'source__source')
).values_list("user__user__username", "source__source")
if not subs:
print('<no submissions>')
print("<no submissions>")
continue
moss_call = MOSS(moss_api_key, language=moss_lang, matching_file_limit=100,
comment='%s - %s' % (contest, problem.code))
moss_call = MOSS(
moss_api_key,
language=moss_lang,
matching_file_limit=100,
comment="%s - %s" % (contest, problem.code),
)
users = set()
@ -49,6 +57,6 @@ class Command(BaseCommand):
if username in users:
continue
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):
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:
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 self.get_response(request)
@ -26,11 +28,16 @@ class DMOJLoginMiddleware(object):
def __call__(self, request):
if request.user.is_authenticated:
profile = request.profile = request.user.profile
login_2fa_path = reverse('login_2fa')
if (profile.is_totp_enabled and not request.session.get('2fa_passed', False) 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()))
login_2fa_path = reverse("login_2fa")
if (
profile.is_totp_enabled
and not request.session.get("2fa_passed", False)
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:
request.profile = None
return self.get_response(request)
@ -57,7 +64,7 @@ class ContestMiddleware(object):
profile.update_contest()
request.participation = profile.current_contest
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:
request.in_contest = False
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):
dependencies = [
('judge', '0084_contest_formats'),
("judge", "0084_contest_formats"),
]
operations = [
migrations.CreateModel(
name='SubmissionSource',
name="SubmissionSource",
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')),
('submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='link', to='judge.Submission', verbose_name='associated submission')),
(
"id",
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(
['''INSERT INTO judge_submissionsource (source, submission_id)
SELECT source, id AS 'submission_id' FROM judge_submission;'''],
['''UPDATE judge_submission sub
[
"""INSERT INTO judge_submissionsource (source, submission_id)
SELECT source, id AS 'submission_id' FROM judge_submission;"""
],
[
"""UPDATE judge_submission sub
INNER JOIN judge_submissionsource src ON sub.id = src.submission_id
SET sub.source = src.source;'''],
SET sub.source = src.source;"""
],
elidable=True,
),
migrations.RemoveField(
model_name='submission',
name='source',
model_name="submission",
name="source",
),
migrations.AlterField(
model_name='submissionsource',
name='submission',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='source', to='judge.Submission', verbose_name='associated submission'),
model_name="submissionsource",
name="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):
dependencies = [
('judge', '0085_submission_source'),
("judge", "0085_submission_source"),
]
operations = [
migrations.AddField(
model_name='contest',
name='rating_ceiling',
field=models.IntegerField(blank=True, help_text='Rating ceiling for contest', null=True, verbose_name='rating ceiling'),
model_name="contest",
name="rating_ceiling",
field=models.IntegerField(
blank=True,
help_text="Rating ceiling for contest",
null=True,
verbose_name="rating ceiling",
),
),
migrations.AddField(
model_name='contest',
name='rating_floor',
field=models.IntegerField(blank=True, help_text='Rating floor for contest', null=True, verbose_name='rating floor'),
model_name="contest",
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):
dependencies = [
('judge', '0086_rating_ceiling'),
("judge", "0086_rating_ceiling"),
]
operations = [
migrations.AlterField(
model_name='problem',
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'),
model_name="problem",
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(
model_name='problem',
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'),
model_name="problem",
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):
dependencies = [
('judge', '0087_problem_resource_limits'),
("judge", "0087_problem_resource_limits"),
]
operations = [
migrations.AlterModelOptions(
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'},
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",
},
),
migrations.RenameField(
model_name='contest',
old_name='is_public',
new_name='is_visible',
model_name="contest",
old_name="is_public",
new_name="is_visible",
),
migrations.AddField(
model_name='contest',
name='is_organization_private',
field=models.BooleanField(default=False, verbose_name='private to organizations'),
model_name="contest",
name="is_organization_private",
field=models.BooleanField(
default=False, verbose_name="private to organizations"
),
),
migrations.AddField(
model_name='contest',
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'),
model_name="contest",
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(
model_name='contest',
name='is_private',
field=models.BooleanField(default=False, verbose_name='private to specific users'),
model_name="contest",
name="is_private",
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):
dependencies = [
('judge', '0088_private_contests'),
("judge", "0088_private_contests"),
]
operations = [
migrations.AddField(
model_name='submission',
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'),
model_name="submission",
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",
),
),
migrations.RunSQL('''
migrations.RunSQL(
"""
UPDATE `judge_submission`
INNER JOIN `judge_contestsubmission`
ON (`judge_submission`.`id` = `judge_contestsubmission`.`submission_id`)
INNER JOIN `judge_contestparticipation`
ON (`judge_contestsubmission`.`participation_id` = `judge_contestparticipation`.`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):
dependencies = [
('judge', '0089_submission_to_contest'),
("judge", "0089_submission_to_contest"),
]
operations = [
migrations.RunSQL('''
migrations.RunSQL(
"""
UPDATE `judge_contest`
SET `judge_contest`.`is_private` = 0, `judge_contest`.`is_organization_private` = 1
WHERE `judge_contest`.`is_private` = 1
''', '''
""",
"""
UPDATE `judge_contest`
SET `judge_contest`.`is_private` = `judge_contest`.`is_organization_private`
'''),
""",
),
]

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