mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2025-11-04 14:49:58 +08:00
Accept Merge Request #331 dev -> master : (dev -> master)
Merge Request: dev -> master Created By: @virusdefender Accepted By: @virusdefender URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/331
This commit is contained in:
commit
d8bb845693
20
account/migrations/0015_userprofile_student_id.py
Normal file
20
account/migrations/0015_userprofile_student_id.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-08 06:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0014_auto_20151110_1037'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='student_id',
|
||||
field=models.CharField(blank=True, max_length=15, null=True),
|
||||
),
|
||||
]
|
||||
@ -70,7 +70,7 @@ class UserProfile(models.Model):
|
||||
problems_status = JSONField(default={})
|
||||
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
||||
school = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
student_id = models.CharField(max_length=15, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "user_profile"
|
||||
|
||||
@ -25,6 +25,7 @@ class UserRegisterSerializer(serializers.Serializer):
|
||||
password = serializers.CharField(max_length=30, min_length=6)
|
||||
email = serializers.EmailField(max_length=254)
|
||||
captcha = serializers.CharField(max_length=4, min_length=4)
|
||||
student_id = serializers.CharField(max_length=15, required=False, default=None)
|
||||
|
||||
|
||||
class UserChangePasswordSerializer(serializers.Serializer):
|
||||
@ -74,6 +75,7 @@ class EditUserProfileSerializer(serializers.Serializer):
|
||||
codeforces_username = serializers.CharField(max_length=30, required=False, allow_blank=True, default='')
|
||||
school = serializers.CharField(max_length=200, required=False, allow_blank=True, default='')
|
||||
phone_number = serializers.CharField(max_length=15, required=False, allow_blank=True, default='')
|
||||
student_id = serializers.CharField(max_length=15, required=False, default="")
|
||||
|
||||
|
||||
class UserProfileSerializer(serializers.ModelSerializer):
|
||||
@ -81,4 +83,4 @@ class UserProfileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ["avatar", "blog", "mood", "hduoj_username", "bestcoder_username", "codeforces_username",
|
||||
"rank", "accepted_number", "submissions_number", "problems_status", "phone_number", "school"]
|
||||
"rank", "accepted_number", "submissions_number", "problems_status", "phone_number", "school", "student_id"]
|
||||
|
||||
@ -14,7 +14,7 @@ from rest_framework.response import Response
|
||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, error_page, paginate, rand_str)
|
||||
from utils.captcha import Captcha
|
||||
from mail.tasks import send_email
|
||||
from utils.mail import send_email
|
||||
|
||||
from .decorators import login_required
|
||||
from .models import User, UserProfile
|
||||
@ -97,7 +97,7 @@ class UserRegisterAPIView(APIView):
|
||||
email=data["email"])
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
UserProfile.objects.create(user=user, school=data["school"])
|
||||
UserProfile.objects.create(user=user, school=data["school"], student_id=data["student_id"])
|
||||
return success_response(u"注册成功!")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
@ -262,6 +262,7 @@ class UserProfileAPIView(APIView):
|
||||
user_profile.codeforces_username = data["codeforces_username"]
|
||||
user_profile.blog = data["blog"]
|
||||
user_profile.school = data["school"]
|
||||
user_profile.student_id = data["student_id"]
|
||||
user_profile.phone_number = data["phone_number"]
|
||||
user_profile.save()
|
||||
return success_response(u"修改成功")
|
||||
|
||||
@ -8,8 +8,8 @@ from django.core.urlresolvers import reverse
|
||||
|
||||
from utils.shortcuts import error_response, error_page
|
||||
|
||||
from account.models import SUPER_ADMIN
|
||||
from .models import (Contest, PASSWORD_PROTECTED_CONTEST, PUBLIC_CONTEST, GROUP_CONTEST,
|
||||
from account.models import SUPER_ADMIN, ADMIN
|
||||
from .models import (Contest, PASSWORD_PROTECTED_CONTEST, PASSWORD_PROTECTED_GROUP_CONTEST, PUBLIC_CONTEST, GROUP_CONTEST,
|
||||
CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY)
|
||||
|
||||
|
||||
@ -57,7 +57,10 @@ def check_user_contest_permission(func):
|
||||
|
||||
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if request.user.admin_type == ADMIN:
|
||||
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
|
||||
if contest in contest_set:
|
||||
return func(*args, **kwargs)
|
||||
# 管理员可见隐藏的比赛,已经先判断了身份
|
||||
if not contest.visible:
|
||||
if request.is_ajax():
|
||||
@ -83,6 +86,15 @@ def check_user_contest_permission(func):
|
||||
else:
|
||||
return render(request, "oj/contest/no_contest_permission.html",
|
||||
{"reason": "group_limited", "show_tab": False, "contest": contest})
|
||||
|
||||
if contest.contest_type == PASSWORD_PROTECTED_GROUP_CONTEST:
|
||||
if not contest.groups.filter(id__in=request.user.group_set.all()).exists():
|
||||
if contest.id not in request.session.get("contests", []):
|
||||
if request.is_ajax():
|
||||
return error_response(u"请先输入密码")
|
||||
else:
|
||||
return render(request, "oj/contest/no_contest_permission.html",
|
||||
{"reason": "password_protect", "show_tab": False, "contest": contest})
|
||||
|
||||
# 比赛没有开始
|
||||
if contest.status == CONTEST_NOT_START:
|
||||
|
||||
@ -7,12 +7,13 @@ from problem.models import AbstractProblem
|
||||
from group.models import Group
|
||||
from utils.models import RichTextField
|
||||
from jsonfield import JSONField
|
||||
from judge.judger.result import result
|
||||
from judge.result import result
|
||||
|
||||
|
||||
GROUP_CONTEST = 0
|
||||
PUBLIC_CONTEST = 1
|
||||
PASSWORD_PROTECTED_CONTEST = 2
|
||||
PASSWORD_PROTECTED_GROUP_CONTEST = 3
|
||||
|
||||
CONTEST_NOT_START = 1
|
||||
CONTEST_ENDED = -1
|
||||
|
||||
@ -17,13 +17,13 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||
success_response, paginate, error_page, paginate_data)
|
||||
from account.models import SUPER_ADMIN, User
|
||||
from account.decorators import login_required, super_admin_required
|
||||
from group.models import Group
|
||||
from group.models import Group, AdminGroupRelation, UserGroupRelation
|
||||
from utils.cache import get_cache_redis
|
||||
from submission.models import Submission
|
||||
from problem.models import Problem
|
||||
from .models import (Contest, ContestProblem, CONTEST_ENDED,
|
||||
CONTEST_NOT_START, CONTEST_UNDERWAY, ContestRank)
|
||||
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST
|
||||
from .models import GROUP_CONTEST, PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST, PASSWORD_PROTECTED_GROUP_CONTEST
|
||||
from .decorators import check_user_contest_permission
|
||||
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
||||
CreateContestProblemSerializer, ContestProblemSerializer,
|
||||
@ -50,11 +50,11 @@ class ContestAdminAPIView(APIView):
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
return error_response(u"只有超级管理员才可创建公开赛")
|
||||
|
||||
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||
if data["contest_type"] in [PASSWORD_PROTECTED_CONTEST, PASSWORD_PROTECTED_GROUP_CONTEST]:
|
||||
if not data["password"]:
|
||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||
return error_response(u"此比赛为有密码的比赛,密码不可为空")
|
||||
# 没有密码的公开赛 没有密码的小组赛
|
||||
elif data["contest_type"] == GROUP_CONTEST:
|
||||
if data["contest_type"] == GROUP_CONTEST or data["contest_type"] == PASSWORD_PROTECTED_GROUP_CONTEST:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
groups = Group.objects.filter(id__in=data["groups"])
|
||||
else:
|
||||
@ -91,8 +91,10 @@ class ContestAdminAPIView(APIView):
|
||||
try:
|
||||
# 超级管理员可以编辑所有的
|
||||
contest = Contest.objects.get(id=data["id"])
|
||||
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
||||
return error_response(u"无权访问!")
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
|
||||
if contest not in contest_set:
|
||||
return error_response(u"无权访问!")
|
||||
except Contest.DoesNotExist:
|
||||
return error_response(u"该比赛不存在!")
|
||||
try:
|
||||
@ -107,7 +109,7 @@ class ContestAdminAPIView(APIView):
|
||||
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||
if not data["password"]:
|
||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||
elif data["contest_type"] == GROUP_CONTEST:
|
||||
elif data["contest_type"] in [GROUP_CONTEST, PASSWORD_PROTECTED_GROUP_CONTEST]:
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
groups = Group.objects.filter(id__in=data["groups"])
|
||||
else:
|
||||
@ -151,16 +153,18 @@ class ContestAdminAPIView(APIView):
|
||||
# 普通管理员只能获取自己创建的题目
|
||||
# 超级管理员可以获取全部的题目
|
||||
contest = Contest.objects.get(id=contest_id)
|
||||
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
||||
return error_response(u"题目不存在")
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
|
||||
if contest not in contest_set:
|
||||
return error_response(u"比赛不存在")
|
||||
return success_response(ContestSerializer(contest).data)
|
||||
except Contest.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
return error_response(u"比赛不存在")
|
||||
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
contest = Contest.objects.all().order_by("-create_time")
|
||||
else:
|
||||
contest = Contest.objects.filter(created_by=request.user).order_by("-create_time")
|
||||
contest = Contest.objects.filter(groups__in=request.user.managed_groups.all()).distinct().order_by("-create_time")
|
||||
visible = request.GET.get("visible", None)
|
||||
if visible:
|
||||
contest = contest.filter(visible=(visible == "true"))
|
||||
@ -184,8 +188,10 @@ class ContestProblemAdminAPIView(APIView):
|
||||
data = serializer.data
|
||||
try:
|
||||
contest = Contest.objects.get(id=data["contest_id"])
|
||||
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
||||
return error_response(u"比赛不存在")
|
||||
if request.user.admin_type != SUPER_ADMIN:
|
||||
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
|
||||
if contest not in contest_set:
|
||||
return error_response(u"比赛不存在")
|
||||
except Contest.DoesNotExist:
|
||||
return error_response(u"比赛不存在")
|
||||
contest_problem = ContestProblem.objects.create(title=data["title"],
|
||||
@ -362,7 +368,10 @@ def contest_problem_page(request, contest_id, contest_problem_id):
|
||||
request.user.admin_type == SUPER_ADMIN or \
|
||||
request.user == contest.created_by:
|
||||
show_submit_code_area = True
|
||||
|
||||
else:
|
||||
contest_set = Contest.objects.filter(groups__in=request.user.managed_groups.all())
|
||||
if contest in contest_set:
|
||||
show_submit_code_area = True
|
||||
return render(request, "oj/problem/contest_problem.html", {"problem": problem,
|
||||
"contest": contest,
|
||||
"samples": json.loads(problem.samples),
|
||||
|
||||
@ -19,4 +19,5 @@ RUN cd lrun && make install
|
||||
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
|
||||
RUN chmod -R 777 /var/judger/run/
|
||||
COPY policy /var/judger/run/
|
||||
WORKDIR /var/judger/code/
|
||||
WORKDIR /var/judger/code/judge/
|
||||
CMD python server.py
|
||||
@ -4,11 +4,11 @@ redis
|
||||
django-redis-sessions
|
||||
djangorestframework
|
||||
django-rest-swagger
|
||||
celery
|
||||
gunicorn
|
||||
coverage
|
||||
django-extensions
|
||||
supervisor
|
||||
pillow
|
||||
jsonfield
|
||||
Envelopes
|
||||
Envelopes
|
||||
huey
|
||||
@ -23,4 +23,4 @@ serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.
|
||||
|
||||
[include]
|
||||
|
||||
files=gunicorn.conf mq.conf
|
||||
files=gunicorn.conf task_queue.conf
|
||||
@ -1,12 +1,12 @@
|
||||
[program:mq]
|
||||
|
||||
command=python manage.py runscript mq
|
||||
command=python manage.py run_huey
|
||||
|
||||
directory=/code/
|
||||
user=root
|
||||
numprocs=1
|
||||
stdout_logfile=/code/log/mq.log
|
||||
stderr_logfile=/code/log/mq.log
|
||||
stdout_logfile=/code/log/task_queue.log
|
||||
stderr_logfile=/code/log/task_queue.log
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startsecs=5
|
||||
20
group/migrations/0006_auto_20151209_1834.py
Normal file
20
group/migrations/0006_auto_20151209_1834.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-09 10:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('group', '0005_joingrouprequest_accepted'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='group',
|
||||
old_name='admin',
|
||||
new_name='created_by',
|
||||
),
|
||||
]
|
||||
38
group/migrations/0007_auto_20151209_1836.py
Normal file
38
group/migrations/0007_auto_20151209_1836.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-09 10:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('group', '0006_auto_20151209_1834'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminGroupRelation',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='group.Group')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'admin_group_relation',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='admin',
|
||||
field=models.ManyToManyField(related_name='managed_groups', through='group.AdminGroupRelation', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='admingrouprelation',
|
||||
unique_together=set([('user', 'group')]),
|
||||
),
|
||||
]
|
||||
@ -8,10 +8,11 @@ class Group(models.Model):
|
||||
name = models.CharField(max_length=30, unique=True)
|
||||
description = models.TextField()
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
admin = models.ForeignKey(User, related_name="my_groups")
|
||||
created_by = models.ForeignKey(User, related_name="my_groups")
|
||||
# 0是公开 1是需要申请后加入 2是不允许任何人加入
|
||||
join_group_setting = models.IntegerField(default=1)
|
||||
members = models.ManyToManyField(User, through="UserGroupRelation")
|
||||
admin = models.ManyToManyField(User, through="AdminGroupRelation", related_name="managed_groups")
|
||||
# 解散小组后,这一项改为False
|
||||
visible = models.BooleanField(default=True)
|
||||
|
||||
@ -29,6 +30,16 @@ class UserGroupRelation(models.Model):
|
||||
unique_together = ("group", "user")
|
||||
|
||||
|
||||
|
||||
class AdminGroupRelation(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
group = models.ForeignKey(Group)
|
||||
|
||||
class Meta:
|
||||
db_table = "admin_group_relation"
|
||||
unique_together = ("user", "group")
|
||||
|
||||
|
||||
class JoinGroupRequest(models.Model):
|
||||
group = models.ForeignKey(Group)
|
||||
user = models.ForeignKey(User, related_name="my_join_group_requests")
|
||||
|
||||
@ -17,6 +17,7 @@ class EditGroupSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=20)
|
||||
description = serializers.CharField(max_length=300)
|
||||
join_group_setting = serializers.IntegerField()
|
||||
visible = serializers.BooleanField()
|
||||
|
||||
|
||||
class CreateJoinGroupRequestSerializer(serializers.Serializer):
|
||||
@ -73,4 +74,8 @@ class EditGroupMemberSerializer(serializers.Serializer):
|
||||
|
||||
class PutJoinGroupRequestSerializer(serializers.Serializer):
|
||||
request_id = serializers.IntegerField()
|
||||
status = serializers.BooleanField()
|
||||
status = serializers.BooleanField()
|
||||
|
||||
class GroupPromoteAdminSerializer(serializers.Serializer):
|
||||
user_id = serializers.IntegerField()
|
||||
group_id = serializers.IntegerField()
|
||||
|
||||
@ -5,14 +5,14 @@ from django.db import IntegrityError
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate, error_page
|
||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
|
||||
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN, User
|
||||
from account.decorators import login_required
|
||||
|
||||
from .models import Group, JoinGroupRequest, UserGroupRelation
|
||||
from .models import Group, JoinGroupRequest, UserGroupRelation, AdminGroupRelation
|
||||
from .serializers import (CreateGroupSerializer, EditGroupSerializer,
|
||||
CreateJoinGroupRequestSerializer, GroupSerializer,
|
||||
GroupMemberSerializer, EditGroupMemberSerializer,
|
||||
JoinGroupRequestSerializer, PutJoinGroupRequestSerializer)
|
||||
JoinGroupRequestSerializer, PutJoinGroupRequestSerializer, GroupPromoteAdminSerializer)
|
||||
from announcement.models import Announcement
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q
|
||||
@ -25,7 +25,7 @@ class GroupAPIViewBase(object):
|
||||
管理员可以查询所有的小组,其他用户查询自己创建的自傲组
|
||||
"""
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
group = Group.objects.get(id=group_id, visible=True)
|
||||
group = Group.objects.get(id=group_id)
|
||||
else:
|
||||
group = Group.objects.get(id=group_id, visible=True, admin=request.user)
|
||||
return group
|
||||
@ -36,7 +36,7 @@ class GroupAPIViewBase(object):
|
||||
如果是管理员,就返回他创建的全部小组
|
||||
"""
|
||||
if request.user.admin_type == SUPER_ADMIN:
|
||||
groups = Group.objects.filter(visible=True)
|
||||
groups = Group.objects.filter()
|
||||
else:
|
||||
groups = Group.objects.filter(admin=request.user, visible=True)
|
||||
return groups
|
||||
@ -57,9 +57,10 @@ class GroupAdminAPIView(APIView, GroupAPIViewBase):
|
||||
group = Group.objects.create(name=data["name"],
|
||||
description=data["description"],
|
||||
join_group_setting=data["join_group_setting"],
|
||||
admin=request.user)
|
||||
created_by=request.user)
|
||||
except IntegrityError:
|
||||
return error_response(u"小组名已经存在")
|
||||
AdminGroupRelation.objects.create(group=group, user=request.user)
|
||||
return success_response(GroupSerializer(group).data)
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
@ -82,6 +83,7 @@ class GroupAdminAPIView(APIView, GroupAPIViewBase):
|
||||
group.name = data["name"]
|
||||
group.description = data["description"]
|
||||
group.join_group_setting = data["join_group_setting"]
|
||||
group.visible = data["visible"]
|
||||
group.save()
|
||||
except IntegrityError:
|
||||
return error_response(u"小组名已经存在")
|
||||
@ -132,8 +134,13 @@ class GroupMemberAdminAPIView(APIView, GroupAPIViewBase):
|
||||
group = self.get_group(request, group_id)
|
||||
except Group.DoesNotExist:
|
||||
return error_response(u"小组不存在")
|
||||
|
||||
return paginate(request, UserGroupRelation.objects.filter(group=group), GroupMemberSerializer)
|
||||
admin_only = request.GET.get("admin_only", None)
|
||||
if admin_only:
|
||||
members = AdminGroupRelation.objects.filter(group=group)
|
||||
else:
|
||||
members = UserGroupRelation.objects.filter(group=group)
|
||||
|
||||
return paginate(request, members, GroupMemberSerializer)
|
||||
|
||||
def put(self, request):
|
||||
"""
|
||||
@ -217,7 +224,7 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
|
||||
---
|
||||
response_serializer: JoinGroupRequestSerializer
|
||||
"""
|
||||
requests = JoinGroupRequest.objects.filter(group=Group.objects.filter(admin=request.user, visible=True),
|
||||
requests = JoinGroupRequest.objects.filter(group__in=Group.objects.filter(admin=request.user, visible=True),
|
||||
status=False)
|
||||
return paginate(request, requests, JoinGroupRequestSerializer)
|
||||
|
||||
@ -292,7 +299,12 @@ def group_page(request, group_id):
|
||||
group = Group.objects.get(id=group_id, visible=True)
|
||||
except Group.DoesNotExist:
|
||||
return error_page(request, u"小组不存在")
|
||||
return render(request, "oj/group/group.html", {"group": group})
|
||||
joined = True
|
||||
try:
|
||||
UserGroupRelation.objects.get(user=request.user, group=group)
|
||||
except UserGroupRelation.DoesNotExist:
|
||||
joined = False
|
||||
return render(request, "oj/group/group.html", {"group": group, "joined": joined})
|
||||
|
||||
|
||||
@login_required
|
||||
@ -314,3 +326,30 @@ def application_page(request, request_id):
|
||||
return error_page(request, u"申请不存在")
|
||||
return render(request, "oj/group/my_application.html",
|
||||
{"application": application})
|
||||
|
||||
|
||||
class GroupPrometAdminAPIView(APIView):
|
||||
def post(self, request):
|
||||
"""
|
||||
创建小组管理员的api
|
||||
---
|
||||
request_serializer: GroupPromoteAdminSerializer
|
||||
"""
|
||||
serializer = GroupPromoteAdminSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
try:
|
||||
group = Group.objects.get(id=data["group_id"])
|
||||
except Group.DoesNotExist:
|
||||
return error_response(u"小组不存在")
|
||||
try:
|
||||
user = User.objects.get(id=data["user_id"])
|
||||
except User.DoesNotExist:
|
||||
return error_response(u"用户不存在")
|
||||
try:
|
||||
AdminGroupRelation.objects.create(user=user, group=group)
|
||||
except IntegrityError:
|
||||
return error_response(u"该用户已经是管理员了")
|
||||
return success_response(u"操作成功")
|
||||
else:
|
||||
return serializer_invalid_response(serializer)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
@ -1,94 +0,0 @@
|
||||
# coding=utf-8
|
||||
import sys
|
||||
import json
|
||||
import MySQLdb
|
||||
|
||||
from client import JudgeClient
|
||||
from language import languages
|
||||
from compiler import compile_
|
||||
from result import result
|
||||
from settings import judger_workspace, submission_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
# 简单的解析命令行参数
|
||||
# 参数有 -solution_id -time_limit -memory_limit -test_case_id
|
||||
# 获取到的值是['xxx.py', '-solution_id', '1111', '-time_limit', '1000', '-memory_limit', '100', '-test_case_id', 'aaaa']
|
||||
args = sys.argv
|
||||
submission_id = args[2]
|
||||
time_limit = args[4]
|
||||
memory_limit = args[6]
|
||||
test_case_id = args[8]
|
||||
|
||||
|
||||
def db_conn():
|
||||
return MySQLdb.connect(db=submission_db["db"],
|
||||
user=submission_db["user"],
|
||||
passwd=submission_db["password"],
|
||||
host=submission_db["host"],
|
||||
port=submission_db["port"], charset="utf8")
|
||||
|
||||
|
||||
conn = db_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("select language, code from submission where id = %s", (submission_id,))
|
||||
data = cur.fetchall()
|
||||
if not data:
|
||||
exit()
|
||||
language_code = data[0][0]
|
||||
code = data[0][1]
|
||||
|
||||
conn.close()
|
||||
|
||||
# 将代码写入文件
|
||||
language = languages[language_code]
|
||||
src_path = judger_workspace + "run/" + language["src_name"]
|
||||
f = open(src_path, "w")
|
||||
f.write(code.encode("utf8"))
|
||||
f.close()
|
||||
|
||||
# 编译
|
||||
try:
|
||||
exe_path = compile_(language, src_path, judger_workspace + "run/")
|
||||
except Exception as e:
|
||||
print e
|
||||
conn = db_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("update submission set result=%s, info=%s where id=%s",
|
||||
(result["compile_error"], str(e), submission_id))
|
||||
conn.commit()
|
||||
exit()
|
||||
|
||||
# 运行
|
||||
try:
|
||||
client = JudgeClient(language_code=language_code,
|
||||
exe_path=exe_path,
|
||||
max_cpu_time=int(time_limit),
|
||||
max_real_time=int(time_limit) * 2,
|
||||
max_memory=int(memory_limit),
|
||||
test_case_dir=judger_workspace + "test_case/" + test_case_id + "/")
|
||||
judge_result = {"result": result["accepted"], "info": client.run(), "accepted_answer_time": None}
|
||||
|
||||
for item in judge_result["info"]:
|
||||
if item["result"]:
|
||||
judge_result["result"] = item["result"]
|
||||
break
|
||||
else:
|
||||
l = sorted(judge_result["info"], key=lambda k: k["cpu_time"])
|
||||
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
conn = db_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
|
||||
conn.commit()
|
||||
exit()
|
||||
|
||||
conn = db_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",
|
||||
(judge_result["result"], json.dumps(judge_result["info"]), judge_result["accepted_answer_time"],
|
||||
submission_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@ -1 +0,0 @@
|
||||
celery -A judge.controller worker -l DEBUG
|
||||
@ -1,9 +0,0 @@
|
||||
# coding=utf-8
|
||||
from __future__ import absolute_import
|
||||
from celery import Celery, platforms
|
||||
from .settings import redis_config
|
||||
|
||||
app = Celery("judge", broker='redis://%s:%s/%s' % (redis_config["host"], redis_config["port"], redis_config["db"]),
|
||||
include=["judge.judger_controller.tasks"])
|
||||
|
||||
platforms.C_FORCE_ROOT =True
|
||||
@ -1,39 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""
|
||||
注意:
|
||||
此文件包含 celery 的部分配置,但是 celery 并不是运行在docker 中的,所以本配置文件中的 redis和 MySQL 的地址就应该是
|
||||
运行 redis 和 MySQL 的 docker 容器的地址了。怎么获取这个地址见帮助文档。测试用例的路径和源代码路径同理。
|
||||
"""
|
||||
import os
|
||||
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
|
||||
redis_config = {
|
||||
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR"),
|
||||
"port": 6379,
|
||||
"db": 0
|
||||
}
|
||||
|
||||
|
||||
# 判题的 docker 容器的配置参数
|
||||
docker_config = {
|
||||
"image_name": "judger",
|
||||
"docker_path": "docker",
|
||||
"shell": True
|
||||
}
|
||||
|
||||
|
||||
# 测试用例的路径,是主机上的实际路径
|
||||
test_case_dir = "/root/test_case/"
|
||||
# 源代码路径,也就是 manage.py 所在的实际路径
|
||||
source_code_dir = "/root/qduoj/"
|
||||
# 日志文件夹路径
|
||||
log_dir = "/root/log/"
|
||||
|
||||
|
||||
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
|
||||
submission_db = {
|
||||
"host": os.environ.get("submission_db_host"),
|
||||
"port": 3306,
|
||||
"db": "oj_submission",
|
||||
"user": "root",
|
||||
"password": "root"
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
import redis
|
||||
import MySQLdb
|
||||
import subprocess
|
||||
from ..judger.result import result
|
||||
from ..judger_controller.celery import app
|
||||
from settings import docker_config, source_code_dir, test_case_dir, log_dir, submission_db, redis_config
|
||||
|
||||
|
||||
@app.task
|
||||
def judge(submission_id, time_limit, memory_limit, test_case_id):
|
||||
try:
|
||||
command = "%s run --privileged --rm " \
|
||||
"--link mysql " \
|
||||
"-v %s:/var/judger/test_case/:ro " \
|
||||
"-v %s:/var/judger/code/:ro " \
|
||||
"-v %s:/var/judger/code/log/ " \
|
||||
"--device /dev/null:/dev/null " \
|
||||
"%s " \
|
||||
"python judge/judger/run.py " \
|
||||
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
|
||||
(docker_config["docker_path"],
|
||||
test_case_dir,
|
||||
source_code_dir,
|
||||
log_dir,
|
||||
docker_config["image_name"],
|
||||
submission_id, str(time_limit), str(memory_limit), test_case_id)
|
||||
subprocess.call(command, shell=docker_config["shell"])
|
||||
except Exception as e:
|
||||
conn = MySQLdb.connect(db=submission_db["db"],
|
||||
user=submission_db["user"],
|
||||
passwd=submission_db["password"],
|
||||
host=submission_db["host"],
|
||||
port=submission_db["port"],
|
||||
charset="utf8")
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute("update submission set result=%s, info=%s where id=%s",
|
||||
(result["system_error"], str(e), submission_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||
r.decr("judge_queue_length")
|
||||
r.lpush("queue", submission_id)
|
||||
@ -7,16 +7,16 @@ languages = {
|
||||
"src_name": "main.c",
|
||||
"code": 1,
|
||||
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
|
||||
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}main",
|
||||
"execute_command": "{exe_path}main"
|
||||
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}/main",
|
||||
"execute_command": "{exe_path}/main"
|
||||
},
|
||||
2: {
|
||||
"name": "cpp",
|
||||
"src_name": "main.cpp",
|
||||
"code": 2,
|
||||
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
|
||||
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}main",
|
||||
"execute_command": "{exe_path}main"
|
||||
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}/main",
|
||||
"execute_command": "{exe_path}/main"
|
||||
},
|
||||
3: {
|
||||
"name": "java",
|
||||
65
judge/runner.py
Normal file
65
judge/runner.py
Normal file
@ -0,0 +1,65 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
import socket
|
||||
import shutil
|
||||
|
||||
from client import JudgeClient
|
||||
from language import languages
|
||||
from compiler import compile_
|
||||
from result import result
|
||||
from settings import judger_workspace
|
||||
|
||||
|
||||
class JudgeInstanceRunner(object):
|
||||
|
||||
def run(self, token, submission_id, language_code, code, time_limit, memory_limit, test_case_id):
|
||||
language = languages[language_code]
|
||||
host_name = socket.gethostname()
|
||||
judge_base_path = os.path.join(judger_workspace, "run", submission_id)
|
||||
|
||||
if not token or token != os.environ.get("rpc_token"):
|
||||
return {"code": 2, "data": {"error": "Invalid token", "server": host_name}}
|
||||
|
||||
try:
|
||||
os.mkdir(judge_base_path)
|
||||
os.chmod(judge_base_path, 0777)
|
||||
|
||||
# 将代码写入文件
|
||||
src_path = os.path.join(judge_base_path, language["src_name"])
|
||||
f = open(src_path, "w")
|
||||
f.write(code.encode("utf8"))
|
||||
f.close()
|
||||
except Exception as e:
|
||||
shutil.rmtree(judge_base_path, ignore_errors=True)
|
||||
return {"code": 2, "data": {"error": str(e), "server": host_name}}
|
||||
|
||||
# 编译
|
||||
try:
|
||||
exe_path = compile_(language, src_path, judge_base_path)
|
||||
except Exception as e:
|
||||
shutil.rmtree(judge_base_path, ignore_errors=True)
|
||||
return {"code": 1, "data": {"error": str(e), "server": host_name}}
|
||||
|
||||
# 运行
|
||||
try:
|
||||
client = JudgeClient(language_code=language_code,
|
||||
exe_path=exe_path,
|
||||
max_cpu_time=int(time_limit),
|
||||
max_real_time=int(time_limit) * 2,
|
||||
max_memory=int(memory_limit),
|
||||
test_case_dir=judger_workspace + "test_case/" + test_case_id + "/")
|
||||
judge_result = {"result": result["accepted"], "info": client.run(),
|
||||
"accepted_answer_time": None, "server": host_name}
|
||||
|
||||
for item in judge_result["info"]:
|
||||
if item["result"]:
|
||||
judge_result["result"] = item["result"]
|
||||
break
|
||||
else:
|
||||
l = sorted(judge_result["info"], key=lambda k: k["cpu_time"])
|
||||
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
|
||||
return {"code": 0, "data": judge_result}
|
||||
except Exception as e:
|
||||
return {"code": 2, "data": {"error": str(e), "server": host_name}}
|
||||
finally:
|
||||
shutil.rmtree(judge_base_path, ignore_errors=True)
|
||||
13
judge/server.py
Normal file
13
judge/server.py
Normal file
@ -0,0 +1,13 @@
|
||||
# coding=utf-8
|
||||
import SocketServer
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
|
||||
from runner import JudgeInstanceRunner
|
||||
|
||||
|
||||
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
|
||||
pass
|
||||
|
||||
|
||||
server = AsyncXMLRPCServer(('0.0.0.0', 8080), SimpleXMLRPCRequestHandler, allow_none=True)
|
||||
server.register_instance(JudgeInstanceRunner())
|
||||
server.serve_forever()
|
||||
41
judge_dispatcher/migrations/0001_initial.py
Normal file
41
judge_dispatcher/migrations/0001_initial.py
Normal file
@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='JudgeServer',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('ip', models.GenericIPAddressField()),
|
||||
('port', models.IntegerField()),
|
||||
('max_instance_number', models.IntegerField()),
|
||||
('left_instance_number', models.IntegerField()),
|
||||
('workload', models.IntegerField(default=0)),
|
||||
('token', models.CharField(max_length=30)),
|
||||
('lock', models.BooleanField(default=False)),
|
||||
('status', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'judge_server',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JudgeWaitingQueue',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('submission_id', models.CharField(max_length=40)),
|
||||
('create_time', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'judge_waiting_queue',
|
||||
},
|
||||
),
|
||||
]
|
||||
32
judge_dispatcher/migrations/0002_auto_20151207_2310.py
Normal file
32
judge_dispatcher/migrations/0002_auto_20151207_2310.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('judge_dispatcher', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='judgewaitingqueue',
|
||||
name='memory_limit',
|
||||
field=models.IntegerField(default=1),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='judgewaitingqueue',
|
||||
name='test_case_id',
|
||||
field=models.CharField(default=1, max_length=40),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='judgewaitingqueue',
|
||||
name='time_limit',
|
||||
field=models.IntegerField(default=1),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
0
judge_dispatcher/migrations/__init__.py
Normal file
0
judge_dispatcher/migrations/__init__.py
Normal file
44
judge_dispatcher/models.py
Normal file
44
judge_dispatcher/models.py
Normal file
@ -0,0 +1,44 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
|
||||
|
||||
class JudgeServer(models.Model):
|
||||
ip = models.GenericIPAddressField()
|
||||
port = models.IntegerField()
|
||||
# 这个服务器最大可能运行的判题实例数量
|
||||
max_instance_number = models.IntegerField()
|
||||
left_instance_number = models.IntegerField()
|
||||
workload = models.IntegerField(default=0)
|
||||
token = models.CharField(max_length=30)
|
||||
# 进行测试用例同步的时候加锁
|
||||
lock = models.BooleanField(default=False)
|
||||
# status 为 false 的时候代表不使用这个服务器
|
||||
status = models.BooleanField(default=True)
|
||||
|
||||
def use_judge_instance(self):
|
||||
# 因为use 和 release 中间是判题时间,可能这个 model 的数据已经被修改了,所以不能直接使用self.xxx,否则取到的是旧数据
|
||||
server = JudgeServer.objects.select_for_update().get(id=self.id)
|
||||
server.left_instance_number -= 1
|
||||
server.workload = 100 - int(float(server.left_instance_number) / server.max_instance_number * 100)
|
||||
server.save()
|
||||
|
||||
def release_judge_instance(self):
|
||||
# 使用原子操作
|
||||
server = JudgeServer.objects.select_for_update().get(id=self.id)
|
||||
server.left_instance_number += 1
|
||||
server.workload = 100 - int(float(server.left_instance_number) / server.max_instance_number * 100)
|
||||
server.save()
|
||||
|
||||
class Meta:
|
||||
db_table = "judge_server"
|
||||
|
||||
|
||||
class JudgeWaitingQueue(models.Model):
|
||||
submission_id = models.CharField(max_length=40)
|
||||
time_limit = models.IntegerField()
|
||||
memory_limit = models.IntegerField()
|
||||
test_case_id = models.CharField(max_length=40)
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "judge_waiting_queue"
|
||||
24
judge_dispatcher/rpc_client.py
Normal file
24
judge_dispatcher/rpc_client.py
Normal file
@ -0,0 +1,24 @@
|
||||
# coding=utf-8
|
||||
import xmlrpclib
|
||||
import httplib
|
||||
|
||||
|
||||
class TimeoutHTTPConnection(httplib.HTTPConnection):
|
||||
def __init__(self, host, timeout=10):
|
||||
httplib.HTTPConnection.__init__(self, host, timeout=timeout)
|
||||
|
||||
|
||||
class TimeoutTransport(xmlrpclib.Transport):
|
||||
def __init__(self, timeout=10, *args, **kwargs):
|
||||
xmlrpclib.Transport.__init__(self, *args, **kwargs)
|
||||
self.timeout = timeout
|
||||
|
||||
def make_connection(self, host):
|
||||
conn = TimeoutHTTPConnection(host, self.timeout)
|
||||
return conn
|
||||
|
||||
|
||||
class TimeoutServerProxy(xmlrpclib.ServerProxy):
|
||||
def __init__(self, uri, timeout=10, *args, **kwargs):
|
||||
kwargs['transport'] = TimeoutTransport(timeout=timeout, use_datetime=kwargs.get('use_datetime', 0))
|
||||
xmlrpclib.ServerProxy.__init__(self, uri, *args, **kwargs)
|
||||
149
judge_dispatcher/tasks.py
Normal file
149
judge_dispatcher/tasks.py
Normal file
@ -0,0 +1,149 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from rpc_client import TimeoutServerProxy
|
||||
|
||||
from judge.result import result
|
||||
from contest.models import ContestProblem, ContestRank, Contest, CONTEST_UNDERWAY
|
||||
from problem.models import Problem
|
||||
from submission.models import Submission
|
||||
from account.models import User
|
||||
from utils.cache import get_cache_redis
|
||||
|
||||
from .models import JudgeServer, JudgeWaitingQueue
|
||||
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
class JudgeDispatcher(object):
|
||||
def __init__(self, submission, time_limit, memory_limit, test_case_id):
|
||||
self.submission = submission
|
||||
self.time_limit = time_limit
|
||||
self.memory_limit = memory_limit
|
||||
self.test_case_id = test_case_id
|
||||
self.user = User.objects.get(id=submission.user_id)
|
||||
|
||||
def choose_judge_server(self):
|
||||
servers = JudgeServer.objects.filter(workload__lt=100, lock=False, status=True).order_by("-workload")
|
||||
if servers.exists():
|
||||
return servers.first()
|
||||
|
||||
def judge(self, is_waiting_task=False):
|
||||
self.submission.judge_start_time = int(time.time() * 1000)
|
||||
|
||||
with transaction.atomic():
|
||||
judge_server = self.choose_judge_server()
|
||||
|
||||
# 如果没有合适的判题服务器,就放入等待队列中等待判题
|
||||
if not judge_server:
|
||||
JudgeWaitingQueue.objects.create(submission_id=self.submission.id, time_limit=self.time_limit,
|
||||
memory_limit=self.memory_limit, test_case_id=self.test_case_id)
|
||||
return
|
||||
|
||||
judge_server.use_judge_instance()
|
||||
|
||||
try:
|
||||
s = TimeoutServerProxy("http://" + judge_server.ip + ":" + str(judge_server.port), timeout=20)
|
||||
|
||||
data = s.run(judge_server.token, self.submission.id, self.submission.language,
|
||||
self.submission.code, self.time_limit, self.memory_limit, self.test_case_id)
|
||||
# 编译错误
|
||||
if data["code"] == 1:
|
||||
self.submission.result = result["compile_error"]
|
||||
self.submission.info = data["data"]["error"]
|
||||
# system error
|
||||
elif data["code"] == 2:
|
||||
self.submission.result = result["system_error"]
|
||||
self.submission.info = data["data"]["error"]
|
||||
elif data["code"] == 0:
|
||||
self.submission.result = data["data"]["result"]
|
||||
self.submission.info = json.dumps(data["data"]["info"])
|
||||
self.submission.accepted_answer_time = data["data"]["accepted_answer_time"]
|
||||
except Exception as e:
|
||||
self.submission.result = result["system_error"]
|
||||
self.submission.info = str(e)
|
||||
finally:
|
||||
with transaction.atomic():
|
||||
judge_server.release_judge_instance()
|
||||
|
||||
self.submission.judge_end_time = int(time.time() * 1000)
|
||||
self.submission.save()
|
||||
|
||||
if self.submission.contest_id:
|
||||
self.update_contest_problem_status()
|
||||
else:
|
||||
self.update_problem_status()
|
||||
|
||||
with transaction.atomic():
|
||||
waiting_submissions = JudgeWaitingQueue.objects.select_for_update().all()
|
||||
if waiting_submissions.exists():
|
||||
# 防止循环依赖
|
||||
from submission.tasks import _judge
|
||||
|
||||
waiting_submission = waiting_submissions.first()
|
||||
|
||||
submission = Submission.objects.get(id=waiting_submission.submission_id)
|
||||
waiting_submission.delete()
|
||||
|
||||
_judge(submission, time_limit=waiting_submission.time_limit,
|
||||
memory_limit=waiting_submission.memory_limit, test_case_id=waiting_submission.test_case_id,
|
||||
is_waiting_task=True)
|
||||
|
||||
def update_problem_status(self):
|
||||
problem = Problem.objects.get(id=self.submission.problem_id)
|
||||
|
||||
# 更新普通题目的计数器
|
||||
problem.add_submission_number()
|
||||
|
||||
# 更新用户做题状态
|
||||
problems_status = self.user.problems_status
|
||||
if "problems" not in problems_status:
|
||||
problems_status["problems"] = {}
|
||||
if self.submission.result == result["accepted"]:
|
||||
problem.add_ac_number()
|
||||
problems_status["problems"][str(problem.id)] = 1
|
||||
else:
|
||||
problems_status["problems"][str(problem.id)] = 2
|
||||
self.user.problems_status = problems_status
|
||||
self.user.save()
|
||||
# 普通题目的话,到这里就结束了
|
||||
|
||||
def update_contest_problem_status(self):
|
||||
# 能运行到这里的都是比赛题目
|
||||
contest = Contest.objects.get(id=self.submission.contest_id)
|
||||
if contest.status != CONTEST_UNDERWAY:
|
||||
logger.info("Contest debug mode, id: " + str(contest.id) + ", submission id: " + self.submission.id)
|
||||
return
|
||||
with transaction.atomic():
|
||||
contest_problem = ContestProblem.objects.select_for_update().get(contest=contest, id=self.submission.problem_id)
|
||||
|
||||
contest_problem.add_submission_number()
|
||||
|
||||
# todo 事务
|
||||
problems_status = self.user.problems_status
|
||||
if "contest_problems" not in problems_status:
|
||||
problems_status["contest_problems"] = {}
|
||||
if self.submission.result == result["accepted"]:
|
||||
contest_problem.add_ac_number()
|
||||
problems_status["contest_problems"][str(contest_problem.id)] = 1
|
||||
else:
|
||||
problems_status["contest_problems"][str(contest_problem.id)] = 0
|
||||
self.user.problems_status = problems_status
|
||||
self.user.save()
|
||||
|
||||
self.update_contest_rank(contest)
|
||||
|
||||
def update_contest_rank(self, contest):
|
||||
if contest.real_time_rank:
|
||||
get_cache_redis().delete(str(contest.id) + "_rank_cache")
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
contest_rank = ContestRank.objects.select_for_update().get(contest=contest, user=self.user)
|
||||
contest_rank.update_rank(self.submission)
|
||||
except ContestRank.DoesNotExist:
|
||||
ContestRank.objects.create(contest=contest, user=self.user).update_rank(self.submission)
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
@ -1,19 +0,0 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
from envelopes import Envelope
|
||||
|
||||
SMTP_CONFIG = {"smtp_server": "smtp.mxhichina.com",
|
||||
"email": "noreply@qduoj.com",
|
||||
"password": os.environ.get("smtp_password", "111111"),
|
||||
"tls": False}
|
||||
|
||||
|
||||
def send_email(from_name, to_email, to_name, subject, content):
|
||||
envelope = Envelope(from_addr=(SMTP_CONFIG["email"], from_name),
|
||||
to_addr=(to_email, to_name),
|
||||
subject=subject,
|
||||
html_body=content)
|
||||
envelope.send(SMTP_CONFIG["smtp_server"],
|
||||
login=SMTP_CONFIG["email"],
|
||||
password=SMTP_CONFIG["password"],
|
||||
tls=SMTP_CONFIG["tls"])
|
||||
@ -2,15 +2,15 @@
|
||||
import redis
|
||||
import datetime
|
||||
from rest_framework.views import APIView
|
||||
from judge.judger.result import result
|
||||
from judge.judger_controller.settings import redis_config
|
||||
from judge.result import result
|
||||
from django.conf import settings
|
||||
from utils.shortcuts import success_response
|
||||
from submission.models import Submission
|
||||
|
||||
|
||||
class QueueLengthMonitorAPIView(APIView):
|
||||
def get(self, request):
|
||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||
r = redis.Redis(host=settings.redis_config["host"], port=settings.redis_config["port"], db=settings.redis_config["db"])
|
||||
waiting_number = r.get("judge_queue_length")
|
||||
if waiting_number is None:
|
||||
waiting_number = 0
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
@ -1 +0,0 @@
|
||||
# coding=utf-8
|
||||
104
mq/scripts/mq.py
104
mq/scripts/mq.py
@ -1,104 +0,0 @@
|
||||
# coding=utf-8
|
||||
import logging
|
||||
|
||||
import redis
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from judge.judger_controller.settings import redis_config
|
||||
from judge.judger.result import result
|
||||
from submission.models import Submission
|
||||
from problem.models import Problem
|
||||
from utils.cache import get_cache_redis
|
||||
from contest.models import ContestProblem, Contest, CONTEST_UNDERWAY, ContestRank
|
||||
from account.models import User
|
||||
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
class MessageQueue(object):
|
||||
def __init__(self):
|
||||
self.conn = redis.StrictRedis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||
self.queue = 'queue'
|
||||
|
||||
def listen_task(self):
|
||||
while True:
|
||||
submission_id = self.conn.blpop(self.queue, 0)[1]
|
||||
logger.debug("receive submission_id: " + submission_id)
|
||||
|
||||
try:
|
||||
submission = Submission.objects.get(id=submission_id)
|
||||
except Submission.DoesNotExist:
|
||||
logger.warning("Submission does not exist, submission_id: " + submission_id)
|
||||
continue
|
||||
|
||||
# 更新该用户的解题状态用
|
||||
try:
|
||||
user = User.objects.get(pk=submission.user_id)
|
||||
except User.DoesNotExist:
|
||||
logger.warning("Submission user does not exist, submission_id: " + submission_id)
|
||||
continue
|
||||
|
||||
if not submission.contest_id:
|
||||
try:
|
||||
problem = Problem.objects.get(id=submission.problem_id)
|
||||
except Problem.DoesNotExist:
|
||||
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
||||
continue
|
||||
|
||||
problems_status = user.problems_status
|
||||
|
||||
# 更新普通题目的计数器
|
||||
problem.add_submission_number()
|
||||
if "problems" not in problems_status:
|
||||
problems_status["problems"] = {}
|
||||
if submission.result == result["accepted"]:
|
||||
problem.add_ac_number()
|
||||
problems_status["problems"][str(problem.id)] = 1
|
||||
else:
|
||||
problems_status["problems"][str(problem.id)] = 2
|
||||
user.problems_status = problems_status
|
||||
user.save()
|
||||
# 普通题目的话,到这里就结束了
|
||||
continue
|
||||
|
||||
# 能运行到这里的都是比赛题目
|
||||
try:
|
||||
contest = Contest.objects.get(id=submission.contest_id)
|
||||
if contest.status != CONTEST_UNDERWAY:
|
||||
logger.info("Contest debug mode, id: " + str(contest.id) + ", submission id: " + submission_id)
|
||||
continue
|
||||
contest_problem = ContestProblem.objects.get(contest=contest, id=submission.problem_id)
|
||||
except Contest.DoesNotExist:
|
||||
logger.warning("Submission contest does not exist, submission_id: " + submission_id)
|
||||
continue
|
||||
except ContestProblem.DoesNotExist:
|
||||
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
||||
continue
|
||||
|
||||
# 如果比赛现在不是封榜状态,删除比赛的排名缓存
|
||||
if contest.real_time_rank:
|
||||
get_cache_redis().delete(str(contest.id) + "_rank_cache")
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
contest_rank = ContestRank.objects.get(contest=contest, user=user)
|
||||
contest_rank.update_rank(submission)
|
||||
except ContestRank.DoesNotExist:
|
||||
ContestRank.objects.create(contest=contest, user=user).update_rank(submission)
|
||||
|
||||
problems_status = user.problems_status
|
||||
|
||||
contest_problem.add_submission_number()
|
||||
if "contest_problems" not in problems_status:
|
||||
problems_status["contest_problems"] = {}
|
||||
if submission.result == result["accepted"]:
|
||||
contest_problem.add_ac_number()
|
||||
problems_status["contest_problems"][str(contest_problem.id)] = 1
|
||||
else:
|
||||
problems_status["contest_problems"][str(contest_problem.id)] = 0
|
||||
user.problems_status = problems_status
|
||||
user.save()
|
||||
|
||||
logger.debug("Start message queue")
|
||||
MessageQueue().listen_task()
|
||||
@ -6,4 +6,4 @@
|
||||
\___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_|
|
||||
|___/ |___/ |_|
|
||||
https://github.com/QingdaoU/OnlineJudge
|
||||
"""
|
||||
"""
|
||||
|
||||
@ -22,6 +22,12 @@ REDIS_CACHE = {
|
||||
"db": 1
|
||||
}
|
||||
|
||||
REDIS_QUEUE = {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379,
|
||||
"db": 2
|
||||
}
|
||||
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
@ -33,4 +39,4 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/src/"), BASE_DIR]
|
||||
# 模板文件夹
|
||||
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/src/')]
|
||||
|
||||
SSO = {"callback": "http://localhost:8765/login"}
|
||||
SSO = {"callback": "http://localhost:8765/login"}
|
||||
|
||||
@ -11,7 +11,7 @@ DATABASES = {
|
||||
'CONN_MAX_AGE': 0.1,
|
||||
'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
|
||||
'PORT': 3306,
|
||||
'USER': 'root',
|
||||
'USER': os.environ.get("MYSQL_ENV_MYSQL_USER", "root"),
|
||||
'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
|
||||
},
|
||||
'submission': {
|
||||
@ -20,7 +20,7 @@ DATABASES = {
|
||||
'CONN_MAX_AGE': 0.1,
|
||||
'HOST': os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
|
||||
'PORT': 3306,
|
||||
'USER': 'root',
|
||||
'USER': os.environ.get("MYSQL_ENV_MYSQL_USER", "root"),
|
||||
'PASSWORD': os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,12 @@ REDIS_CACHE = {
|
||||
"db": 1
|
||||
}
|
||||
|
||||
REDIS_QUEUE = {
|
||||
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR", "127.0.0.1"),
|
||||
"port": 6379,
|
||||
"db": 2
|
||||
}
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
@ -48,12 +48,12 @@ INSTALLED_APPS = (
|
||||
'problem',
|
||||
'admin',
|
||||
'submission',
|
||||
'mq',
|
||||
'contest',
|
||||
'mail',
|
||||
'judge',
|
||||
'judge_dispatcher',
|
||||
|
||||
'django_extensions',
|
||||
'rest_framework',
|
||||
'huey.djhuey',
|
||||
)
|
||||
|
||||
if DEBUG:
|
||||
@ -98,11 +98,11 @@ WSGI_APPLICATION = 'oj.wsgi.application'
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'zh-cn'
|
||||
LANGUAGE_CODE = 'zh-hans'
|
||||
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
|
||||
USE_I18N = False
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
@ -118,7 +118,6 @@ AUTH_USER_MODEL = 'account.User'
|
||||
|
||||
LOG_PATH = "log/"
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
@ -186,3 +185,17 @@ IMAGE_UPLOAD_DIR = os.path.join(BASE_DIR, 'upload/')
|
||||
WEBSITE_INFO = {"website_name": "qduoj",
|
||||
"website_footer": u"青岛大学信息工程学院 创新实验室 <a href=\"http://www.miibeian.gov.cn/\">京ICP备15062075号-1</a>",
|
||||
"url": "https://qduoj.com"}
|
||||
|
||||
HUEY = {
|
||||
'backend': 'huey.backends.redis_backend',
|
||||
'name': 'task_queue',
|
||||
'connection': {'host': REDIS_QUEUE["host"], 'port': REDIS_QUEUE["port"], 'db': REDIS_QUEUE["db"]},
|
||||
'always_eager': False, # Defaults to False when running via manage.py run_huey
|
||||
# Options to pass into the consumer when running ``manage.py run_huey``
|
||||
'consumer_options': {'workers': 50},
|
||||
}
|
||||
|
||||
SMTP_CONFIG = {"smtp_server": "smtp.mxhichina.com",
|
||||
"email": "noreply@qduoj.com",
|
||||
"password": os.environ.get("smtp_password", "111111"),
|
||||
"tls": False}
|
||||
|
||||
@ -15,7 +15,7 @@ from contest.views import (ContestAdminAPIView, ContestProblemAdminAPIView,
|
||||
MakeContestProblemPublicAPIView)
|
||||
|
||||
from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView,
|
||||
JoinGroupAPIView, JoinGroupRequestAdminAPIView)
|
||||
JoinGroupAPIView, JoinGroupRequestAdminAPIView, GroupPrometAdminAPIView)
|
||||
|
||||
from admin.views import AdminTemplateView
|
||||
|
||||
@ -55,6 +55,7 @@ urlpatterns = [
|
||||
url(r'^api/contest/submission/$', ContestSubmissionAPIView.as_view(), name="contest_submission_api"),
|
||||
url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
|
||||
url(r'^api/group_join/$', JoinGroupAPIView.as_view(), name="group_join_api"),
|
||||
|
||||
|
||||
url(r'^api/admin/upload_image/$', SimditorImageUploadAPIView.as_view(), name="simditor_upload_image"),
|
||||
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
|
||||
@ -62,6 +63,8 @@ urlpatterns = [
|
||||
url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"),
|
||||
url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
|
||||
url(r'^api/admin/group_member/$', GroupMemberAdminAPIView.as_view(), name="group_member_admin_api"),
|
||||
url(r'^api/admin/group/promot_as_admin/$', GroupPrometAdminAPIView.as_view(), name="group_promote_admin_api"),
|
||||
|
||||
|
||||
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"),
|
||||
url(r'^api/admin/contest_problem/$', ContestProblemAdminAPIView.as_view(), name="contest_problem_admin_api"),
|
||||
|
||||
@ -1 +0,0 @@
|
||||
nohup celery -A judge.judger_controller worker -l DEBUG &
|
||||
@ -103,14 +103,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "bootstrap"], function ($,
|
||||
}
|
||||
});
|
||||
|
||||
vm.$watch("showGroupDetailPage", function (groupId) {
|
||||
vm.groupId = groupId;
|
||||
vm.template_url = "template/group/group_detail.html";
|
||||
});
|
||||
|
||||
vm.$watch("showGroupListPage", function () {
|
||||
vm.template_url = "template/group/group.html";
|
||||
});
|
||||
|
||||
|
||||
avalon.scan();
|
||||
|
||||
|
||||
@ -22,6 +22,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
||||
selectedGroups.push(vm.allGroups[i].id);
|
||||
}
|
||||
}
|
||||
if (vm.password) {
|
||||
ajaxData.password = vm.password;
|
||||
ajaxData.contest_type = 3;
|
||||
}
|
||||
ajaxData.groups = selectedGroups;
|
||||
}
|
||||
else {
|
||||
|
||||
@ -23,6 +23,10 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
||||
selectedGroups.push(vm.allGroups[i].id);
|
||||
}
|
||||
}
|
||||
if (vm.password) {
|
||||
ajaxData.password = vm.password;
|
||||
ajaxData.contest_type = 3;
|
||||
}
|
||||
ajaxData.groups = selectedGroups;
|
||||
}
|
||||
else {
|
||||
@ -131,7 +135,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
|
||||
vm.startTime = contest.start_time.substring(0, 16).replace("T", " ");
|
||||
vm.endTime = contest.end_time.substring(0, 16).replace("T", " ");
|
||||
vm.password = contest.password;
|
||||
if (contest.contest_type == 0) { //contest_type == 0, 小组内比赛
|
||||
if (contest.contest_type == 0 || contest.contest_type == 3) { //contest_type == 0, 小组内比赛
|
||||
vm.isGlobal = false;
|
||||
for (var i = 0; i < vm.allGroups.length; i++) {
|
||||
vm.allGroups[i].isSelected = false;
|
||||
|
||||
@ -1,7 +1,30 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
|
||||
avalon.ready(function () {
|
||||
//avalon.vmodels.group = null;
|
||||
$('#add-group-form').validator().on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var name = vm.name;
|
||||
var description = vm.description;
|
||||
var join_group_setting = vm.group_type;
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/group/",
|
||||
method: "post",
|
||||
data: {name: name, description: description, join_group_setting: join_group_setting},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
getPageData(1);
|
||||
bsAlert("添加成功");
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
})
|
||||
if (avalon.vmodels.group) {
|
||||
var vm = avalon.vmodels.group;
|
||||
}
|
||||
@ -16,7 +39,9 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
page: 1, // 当前页数
|
||||
totalPage: 1, // 总页数
|
||||
keyword: "",
|
||||
|
||||
name: "",
|
||||
description: "",
|
||||
group_type: 0,
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
@ -42,8 +67,10 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
|
||||
getGroupSettingString: function (setting) {
|
||||
return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting]
|
||||
},
|
||||
|
||||
showGroupDetailPage: function (groupId) {
|
||||
vm.$fire("up!showGroupDetailPage", groupId);
|
||||
avalon.vmodels.admin.groupId = groupId;
|
||||
avalon.vmodels.admin.template_url = "template/group/group_detail.html";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($, avalon, csrfTokenHeader, bsAlert) {
|
||||
|
||||
|
||||
// avalon:定义模式 group_list
|
||||
avalon.ready(function () {
|
||||
|
||||
@ -19,7 +18,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
|
||||
name: "",
|
||||
description: "",
|
||||
checkedSetting: "0",
|
||||
|
||||
visible: true,
|
||||
getNext: function () {
|
||||
if (!vm.nextPage)
|
||||
return;
|
||||
@ -53,7 +52,20 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
|
||||
})
|
||||
},
|
||||
showGroupListPage: function () {
|
||||
avalon.vmodels.admin.template_url = "template/group/group.html";
|
||||
vm.$fire("up!showGroupListPage");
|
||||
},
|
||||
promotAsAdmin: function (relation) {
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/admin/group/promot_as_admin/",
|
||||
method: "post",
|
||||
data: JSON.stringify({group_id: relation.group, user_id: relation.user.id}),
|
||||
contentType: "application/json;charset=UTF-8",
|
||||
success: function (data) {
|
||||
bsAlert(data.data);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -91,6 +103,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
|
||||
vm.name = data.data.name;
|
||||
vm.description = data.data.description;
|
||||
vm.checkedSetting = data.data.join_group_setting.toString();
|
||||
vm.visible = data.data.visible;
|
||||
}
|
||||
else {
|
||||
bsAlert(data.data);
|
||||
@ -111,7 +124,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
|
||||
url: "/api/admin/group/",
|
||||
method: "put",
|
||||
data: {group_id: group_id, name: name, description: description,
|
||||
join_group_setting: join_group_setting},
|
||||
join_group_setting: join_group_setting, visible:vm.visible},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (!data.code) {
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
|
||||
$("#stu_id").hide();
|
||||
$('form').validator().on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var username = $("#username").val();
|
||||
var realName = $("#real_name").val();
|
||||
var school = $('#school').val();
|
||||
var student_id = $('#student_id').val();
|
||||
var password = $("#password").val();
|
||||
var email = $("#email").val();
|
||||
var captcha = $("#captcha").val();
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/register/",
|
||||
data: {username: username, school: school, real_name: realName, password: password, email: email, captcha:captcha},
|
||||
data: {username: username, school: school, student_id: student_id, real_name: realName, password: password, email: email, captcha: captcha},
|
||||
dataType: "json",
|
||||
method: "post",
|
||||
success: function (data) {
|
||||
@ -29,6 +31,14 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$("#school").blur(function () {
|
||||
var school = $("#school").val().trim(school).toLowerCase();
|
||||
if (school == "青岛大学" || school == "qdu" || school == "青大") {
|
||||
$("#stu_id").show();
|
||||
$("#school").val("青岛大学");
|
||||
}
|
||||
});
|
||||
function refresh_captcha() {
|
||||
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
|
||||
$("#captcha")[0].value = "";
|
||||
|
||||
@ -2,8 +2,8 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
||||
var applied_captcha = false;
|
||||
$('form').validator().on('submit', function (e) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
var index = location.href.indexOf("/t/");
|
||||
var token = location.href.substr(36+3, 32);
|
||||
var loca = location.href.split("/");
|
||||
var token = loca[loca.length-2];
|
||||
var captcha = $("#captcha").val();
|
||||
var password = $("#new_password").val();
|
||||
$.ajax({
|
||||
|
||||
@ -8,7 +8,7 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
||||
var blog = $("#blog").val();
|
||||
var mood = $("#mood").val();
|
||||
var school = $("#school").val();
|
||||
|
||||
var student_id = $("#student_id").val();
|
||||
$.ajax({
|
||||
beforeSend: csrfTokenHeader,
|
||||
url: "/api/account/userprofile/",
|
||||
@ -19,7 +19,8 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c
|
||||
codeforces_username: codeforces_username,
|
||||
blog: blog,
|
||||
mood: mood,
|
||||
school: school
|
||||
school: school,
|
||||
student_id: student_id
|
||||
},
|
||||
dataType: "json",
|
||||
method: "put",
|
||||
|
||||
@ -3,9 +3,10 @@ require(["jquery", "csrfToken", "bsAlert"], function ($, csrfTokenHeader, bsAler
|
||||
var message;
|
||||
if ($("#applyMessage").length) {
|
||||
message = $("#applyMessage").val();
|
||||
if (!message)
|
||||
if (!message) {
|
||||
bsAlert("提交失败,请填写申请信息!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var groupId = window.location.pathname.split("/")[2];
|
||||
|
||||
@ -57,25 +57,28 @@
|
||||
problem_2_pack: "app/oj/problem/problem",
|
||||
submissionList_3_pack: "app/admin/problem/submissionList",
|
||||
contestCountdown_4_pack: "app/oj/contest/contestCountdown",
|
||||
addProblem_5_pack: "app/admin/problem/addProblem",
|
||||
problem_6_pack: "app/admin/problem/problem",
|
||||
contestList_7_pack: "app/admin/contest/contestList",
|
||||
admin_8_pack: "app/admin/admin",
|
||||
login_9_pack: "app/oj/account/login",
|
||||
addContest_10_pack: "app/admin/contest/addContest",
|
||||
contestPassword_11_pack: "app/oj/contest/contestPassword",
|
||||
changePassword_12_pack: "app/oj/account/changePassword",
|
||||
monitor_13_pack: "app/admin/monitor/monitor",
|
||||
editProblem_14_pack: "app/admin/contest/editProblem",
|
||||
joinGroupRequestList_15_pack: "app/admin/group/joinGroupRequestList",
|
||||
group_16_pack: "app/oj/group/group",
|
||||
contestProblemList_17_pack: "app/admin/contest/contestProblemList",
|
||||
editProblem_18_pack: "app/admin/problem/editProblem",
|
||||
register_19_pack: "app/oj/account/register",
|
||||
groupDetail_20_pack: "app/admin/group/groupDetail",
|
||||
editContest_21_pack: "app/admin/contest/editContest",
|
||||
group_22_pack: "app/admin/group/group",
|
||||
settings_23_pack: "app/oj/account/settings"
|
||||
avatar_5_pack: "app/oj/account/avatar",
|
||||
addProblem_6_pack: "app/admin/problem/addProblem",
|
||||
problem_7_pack: "app/admin/problem/problem",
|
||||
contestList_8_pack: "app/admin/contest/contestList",
|
||||
admin_9_pack: "app/admin/admin",
|
||||
login_10_pack: "app/oj/account/login",
|
||||
applyResetPassword_11_pack: "app/oj/account/applyResetPassword",
|
||||
addContest_12_pack: "app/admin/contest/addContest",
|
||||
contestPassword_13_pack: "app/oj/contest/contestPassword",
|
||||
changePassword_14_pack: "app/oj/account/changePassword",
|
||||
monitor_15_pack: "app/admin/monitor/monitor",
|
||||
editProblem_16_pack: "app/admin/contest/editProblem",
|
||||
joinGroupRequestList_17_pack: "app/admin/group/joinGroupRequestList",
|
||||
group_18_pack: "app/oj/group/group",
|
||||
contestProblemList_19_pack: "app/admin/contest/contestProblemList",
|
||||
editProblem_20_pack: "app/admin/problem/editProblem",
|
||||
register_21_pack: "app/oj/account/register",
|
||||
groupDetail_22_pack: "app/admin/group/groupDetail",
|
||||
editContest_23_pack: "app/admin/contest/editContest",
|
||||
resetPassword_24_pack: "app/oj/account/resetPassword",
|
||||
group_25_pack: "app/admin/group/group",
|
||||
settings_26_pack: "app/oj/account/settings"
|
||||
},
|
||||
shim: {
|
||||
avalon: {
|
||||
@ -86,12 +89,6 @@
|
||||
appDir: "../",
|
||||
dir: "../../release/",
|
||||
modules: [
|
||||
{
|
||||
name: "bootstrap",
|
||||
},
|
||||
{
|
||||
name: "codeMirror"
|
||||
},
|
||||
{
|
||||
name: "announcement_0_pack"
|
||||
},
|
||||
@ -108,62 +105,71 @@
|
||||
name: "contestCountdown_4_pack"
|
||||
},
|
||||
{
|
||||
name: "addProblem_5_pack"
|
||||
name: "avatar_5_pack"
|
||||
},
|
||||
{
|
||||
name: "problem_6_pack"
|
||||
name: "addProblem_6_pack"
|
||||
},
|
||||
{
|
||||
name: "contestList_7_pack"
|
||||
name: "problem_7_pack"
|
||||
},
|
||||
{
|
||||
name: "admin_8_pack"
|
||||
name: "contestList_8_pack"
|
||||
},
|
||||
{
|
||||
name: "login_9_pack"
|
||||
name: "admin_9_pack"
|
||||
},
|
||||
{
|
||||
name: "addContest_10_pack"
|
||||
name: "login_10_pack"
|
||||
},
|
||||
{
|
||||
name: "contestPassword_11_pack"
|
||||
name: "applyResetPassword_11_pack"
|
||||
},
|
||||
{
|
||||
name: "changePassword_12_pack"
|
||||
name: "addContest_12_pack"
|
||||
},
|
||||
{
|
||||
name: "monitor_13_pack"
|
||||
name: "contestPassword_13_pack"
|
||||
},
|
||||
{
|
||||
name: "editProblem_14_pack"
|
||||
name: "changePassword_14_pack"
|
||||
},
|
||||
{
|
||||
name: "joinGroupRequestList_15_pack"
|
||||
name: "monitor_15_pack"
|
||||
},
|
||||
{
|
||||
name: "group_16_pack"
|
||||
name: "editProblem_16_pack"
|
||||
},
|
||||
{
|
||||
name: "contestProblemList_17_pack"
|
||||
name: "joinGroupRequestList_17_pack"
|
||||
},
|
||||
{
|
||||
name: "editProblem_18_pack"
|
||||
name: "group_18_pack"
|
||||
},
|
||||
{
|
||||
name: "register_19_pack"
|
||||
name: "contestProblemList_19_pack"
|
||||
},
|
||||
{
|
||||
name: "groupDetail_20_pack"
|
||||
name: "editProblem_20_pack"
|
||||
},
|
||||
{
|
||||
name: "editContest_21_pack"
|
||||
name: "register_21_pack"
|
||||
},
|
||||
{
|
||||
name: "group_22_pack"
|
||||
name: "groupDetail_22_pack"
|
||||
},
|
||||
{
|
||||
name: "settings_23_pack"
|
||||
name: "editContest_23_pack"
|
||||
},
|
||||
{
|
||||
name: "resetPassword_24_pack"
|
||||
},
|
||||
{
|
||||
name: "group_25_pack"
|
||||
},
|
||||
{
|
||||
name: "settings_26_pack"
|
||||
}
|
||||
],
|
||||
optimizeCss: "standard",
|
||||
})
|
||||
@ -59,25 +59,28 @@ var require = {
|
||||
problem_2_pack: "app/oj/problem/problem",
|
||||
submissionList_3_pack: "app/admin/problem/submissionList",
|
||||
contestCountdown_4_pack: "app/oj/contest/contestCountdown",
|
||||
addProblem_5_pack: "app/admin/problem/addProblem",
|
||||
problem_6_pack: "app/admin/problem/problem",
|
||||
contestList_7_pack: "app/admin/contest/contestList",
|
||||
admin_8_pack: "app/admin/admin",
|
||||
login_9_pack: "app/oj/account/login",
|
||||
addContest_10_pack: "app/admin/contest/addContest",
|
||||
contestPassword_11_pack: "app/oj/contest/contestPassword",
|
||||
changePassword_12_pack: "app/oj/account/changePassword",
|
||||
monitor_13_pack: "app/admin/monitor/monitor",
|
||||
editProblem_14_pack: "app/admin/contest/editProblem",
|
||||
joinGroupRequestList_15_pack: "app/admin/group/joinGroupRequestList",
|
||||
group_16_pack: "app/oj/group/group",
|
||||
contestProblemList_17_pack: "app/admin/contest/contestProblemList",
|
||||
editProblem_18_pack: "app/admin/problem/editProblem",
|
||||
register_19_pack: "app/oj/account/register",
|
||||
groupDetail_20_pack: "app/admin/group/groupDetail",
|
||||
editContest_21_pack: "app/admin/contest/editContest",
|
||||
group_22_pack: "app/admin/group/group",
|
||||
settings_23_pack: "app/oj/account/settings"
|
||||
avatar_5_pack: "app/oj/account/avatar",
|
||||
addProblem_6_pack: "app/admin/problem/addProblem",
|
||||
problem_7_pack: "app/admin/problem/problem",
|
||||
contestList_8_pack: "app/admin/contest/contestList",
|
||||
admin_9_pack: "app/admin/admin",
|
||||
login_10_pack: "app/oj/account/login",
|
||||
applyResetPassword_11_pack: "app/oj/account/applyResetPassword",
|
||||
addContest_12_pack: "app/admin/contest/addContest",
|
||||
contestPassword_13_pack: "app/oj/contest/contestPassword",
|
||||
changePassword_14_pack: "app/oj/account/changePassword",
|
||||
monitor_15_pack: "app/admin/monitor/monitor",
|
||||
editProblem_16_pack: "app/admin/contest/editProblem",
|
||||
joinGroupRequestList_17_pack: "app/admin/group/joinGroupRequestList",
|
||||
group_18_pack: "app/oj/group/group",
|
||||
contestProblemList_19_pack: "app/admin/contest/contestProblemList",
|
||||
editProblem_20_pack: "app/admin/problem/editProblem",
|
||||
register_21_pack: "app/oj/account/register",
|
||||
groupDetail_22_pack: "app/admin/group/groupDetail",
|
||||
editContest_23_pack: "app/admin/contest/editContest",
|
||||
resetPassword_24_pack: "app/oj/account/resetPassword",
|
||||
group_25_pack: "app/admin/group/group",
|
||||
settings_26_pack: "app/oj/account/settings",
|
||||
},
|
||||
shim: {
|
||||
avalon: {
|
||||
|
||||
24
submission/migrations/0007_auto_20151207_1645.py
Normal file
24
submission/migrations/0007_auto_20151207_1645.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submission', '0006_submission_shared'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='submission',
|
||||
name='judge_end_time',
|
||||
field=models.IntegerField(null=True, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='submission',
|
||||
name='judge_start_time',
|
||||
field=models.IntegerField(null=True, blank=True),
|
||||
),
|
||||
]
|
||||
25
submission/migrations/0008_auto_20151208_2106.py
Normal file
25
submission/migrations/0008_auto_20151208_2106.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2015-12-08 13:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submission', '0007_auto_20151207_1645'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='submission',
|
||||
name='judge_end_time',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='submission',
|
||||
name='judge_start_time',
|
||||
field=models.BigIntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@ -1,13 +1,17 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
from utils.shortcuts import rand_str
|
||||
from judge.judger.result import result
|
||||
from judge.result import result
|
||||
|
||||
|
||||
class Submission(models.Model):
|
||||
id = models.CharField(max_length=32, default=rand_str, primary_key=True, db_index=True)
|
||||
user_id = models.IntegerField(db_index=True)
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
# 判题开始时间
|
||||
judge_start_time = models.BigIntegerField(blank=True, null=True)
|
||||
# 判题结束时间
|
||||
judge_end_time = models.BigIntegerField(blank=True, null=True)
|
||||
result = models.IntegerField(default=result["waiting"])
|
||||
language = models.IntegerField()
|
||||
code = models.TextField()
|
||||
@ -24,3 +28,6 @@ class Submission(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = "submission"
|
||||
|
||||
def __unicode__(self):
|
||||
return self.id
|
||||
|
||||
9
submission/tasks.py
Normal file
9
submission/tasks.py
Normal file
@ -0,0 +1,9 @@
|
||||
# coding=utf-8
|
||||
from huey.djhuey import task
|
||||
|
||||
from judge_dispatcher.tasks import JudgeDispatcher
|
||||
|
||||
|
||||
@task()
|
||||
def _judge(submission, time_limit, memory_limit, test_case_id, is_waiting_task=False):
|
||||
JudgeDispatcher(submission, time_limit, memory_limit, test_case_id).judge(is_waiting_task)
|
||||
@ -7,14 +7,13 @@ from django.shortcuts import render
|
||||
from django.core.paginator import Paginator
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from judge.judger_controller.tasks import judge
|
||||
from account.decorators import login_required, super_admin_required
|
||||
from account.models import SUPER_ADMIN, User
|
||||
from problem.models import Problem
|
||||
from contest.models import ContestProblem, Contest
|
||||
from contest.decorators import check_user_contest_permission
|
||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
|
||||
from utils.cache import get_cache_redis
|
||||
from .tasks import _judge
|
||||
from .models import Submission
|
||||
from .serializers import (CreateSubmissionSerializer, SubmissionSerializer,
|
||||
SubmissionhareSerializer, SubmissionRejudgeSerializer,
|
||||
@ -23,11 +22,6 @@ from .serializers import (CreateSubmissionSerializer, SubmissionSerializer,
|
||||
logger = logging.getLogger("app_info")
|
||||
|
||||
|
||||
def _judge(submission_id, time_limit, memory_limit, test_case_id):
|
||||
judge.delay(submission_id, time_limit, memory_limit, test_case_id)
|
||||
get_cache_redis().incr("judge_queue_length")
|
||||
|
||||
|
||||
class SubmissionAPIView(APIView):
|
||||
@login_required
|
||||
def post(self, request):
|
||||
@ -49,7 +43,7 @@ class SubmissionAPIView(APIView):
|
||||
problem_id=problem.id)
|
||||
|
||||
try:
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
_judge(submission, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
@ -94,7 +88,7 @@ class ContestSubmissionAPIView(APIView):
|
||||
code=data["code"],
|
||||
problem_id=problem.id)
|
||||
try:
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
_judge(submission, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
@ -279,7 +273,7 @@ class SubmissionRejudgeAdminAPIView(APIView):
|
||||
except Problem.DoesNotExist:
|
||||
return error_response(u"题目不存在")
|
||||
try:
|
||||
_judge(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
_judge(submission, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return error_response(u"提交判题任务失败")
|
||||
|
||||
@ -46,13 +46,13 @@
|
||||
<div>
|
||||
<span ms-if="showGlobalViewRadio">
|
||||
<label>
|
||||
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
|
||||
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">公开赛
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<label>
|
||||
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
|
||||
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组赛
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
@ -60,10 +60,18 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" ms-visible="isGlobal">
|
||||
<label>密码保护</label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||
<div class="col-md-6" >
|
||||
<div ms-visible="isGlobal">
|
||||
<label>密码保护</label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||
</div>
|
||||
</div>
|
||||
<div ms-visible="!isGlobal">
|
||||
<label>邀请密码</label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空则只有小组内可以参加" ms-duplex="password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12" ms-visible="!isGlobal">
|
||||
|
||||
@ -55,13 +55,13 @@
|
||||
<div>
|
||||
<span ms-if="showGlobalViewRadio">
|
||||
<label>
|
||||
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
|
||||
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">公开赛
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<label>
|
||||
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
|
||||
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组赛
|
||||
</small>
|
||||
</label>
|
||||
</span>
|
||||
@ -69,11 +69,19 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" ms-visible="isGlobal">
|
||||
<label>密码保护</label>
|
||||
<div class="col-md-6">
|
||||
<div ms-visible="isGlobal">
|
||||
<label>密码保护</label>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空就是公开赛" ms-duplex="password">
|
||||
</div>
|
||||
</div>
|
||||
<div ms-visible="!isGlobal">
|
||||
<label>邀请密码</label>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="password" placeholder="留空则只有小组内可以参加" ms-duplex="password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12" ms-visible="!isGlobal">
|
||||
|
||||
@ -38,6 +38,35 @@
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
</div>
|
||||
<h2>创建小组</h2>
|
||||
<form id="add-group-form">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group"><label>小组名</label>
|
||||
<input type="text" name="name" class="form-control" ms-duplex="name"
|
||||
data-error="请填写小组名(名称不能超过20字)" maxlength="20" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group"><label>描述</label>
|
||||
<textarea rows="3" name="description" class="form-control" ms-duplex="description"
|
||||
data-error="请填写描述" maxlength="300" required>
|
||||
</textarea>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label>加入小组设置</label>
|
||||
<input type="radio" name="join_group_setting" value="0" ms-duplex-string="group_type">允许任何人加入
|
||||
<input type="radio" name="join_group_setting" value="1" ms-duplex-string="group_type">提交请求后管理员审核
|
||||
<input type="radio" name="join_group_setting" value="2" ms-duplex-string="group_type">不允许任何人加入
|
||||
|
||||
</div>
|
||||
<input type="submit" class="btn btn-success btn-lg" value="创建小组" id="submitBtn">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<script src="/static/js/app/admin/group/group.js"></script>
|
||||
@ -5,7 +5,7 @@
|
||||
aria-hidden="true">←</span> 返回</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h1>小组成员管理</h1>
|
||||
<h2>小组成员管理</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
@ -19,19 +19,19 @@
|
||||
<td>{{ el.user.username }}</td>
|
||||
<td>{{ el.user.real_name }}</td>
|
||||
<td>{{ el.join_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
|
||||
|
||||
|
||||
<td>
|
||||
<button class="btn-sm btn-info" ms-click="promotAsAdmin(el)">设为管理员</button>
|
||||
<button class="btn-sm btn-danger" ms-click="removeMember(el)">移除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="text-right">
|
||||
页数:{{ page }}/{{ totalPage }}
|
||||
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
|
||||
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
|
||||
</div>
|
||||
<h1>修改小组信息</h1>
|
||||
<h2>修改小组信息</h2>
|
||||
|
||||
<form id="edit_group_form">
|
||||
<div class="col-md-12">
|
||||
@ -49,18 +49,24 @@
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-9">
|
||||
<div class="form-group">
|
||||
|
||||
<label>加入小组设置</label>
|
||||
<input type="radio" name="join_group_setting" value="0" ms-duplex-string="checkedSetting">允许任何人加入
|
||||
<input type="radio" name="join_group_setting" value="1" ms-duplex-string="checkedSetting">提交请求后管理员审核
|
||||
<input type="radio" name="join_group_setting" value="2" ms-duplex-string="checkedSetting">不允许任何人加入
|
||||
|
||||
<label>加入小组设置</label><br>
|
||||
<label><input type="radio" name="join_group_setting" value="0" ms-duplex-string="checkedSetting"><small>允许任何人加入</small></label>
|
||||
<label><input type="radio" name="join_group_setting" value="1" ms-duplex-string="checkedSetting"><small>提交请求后管理员审核</small></label>
|
||||
<label><input type="radio" name="join_group_setting" value="2" ms-duplex-string="checkedSetting"><small>不允许任何人加入</small></label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label>前台是否可见</label><br>
|
||||
<label><input type="checkbox" ms-duplex-checked="visible">
|
||||
<small>可见</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary" type="submit">提交</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<script src="/static/js/app/admin/group/groupDetail.js"></script>
|
||||
|
||||
@ -19,10 +19,14 @@
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="real_name">学校</label>
|
||||
<label for="school">学校</label>
|
||||
<input type="text" class="form-control input-lg" id="school" name="school" placeholder="学校" data-error="请填写学校" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group" id="stu_id">
|
||||
<label for="student_id">学号</label>
|
||||
<input type="number" class="form-control input-lg" id="student_id" name="student_id" placeholder="非必填,如果你需要使用课程功能,请填写该字段">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱地址</label>
|
||||
<input type="email" class="form-control input-lg" id="email" name="email" placeholder="邮箱地址" data-remote="/api/email_check/" data-remote-error="该邮箱已被注册!" data-error="请填写正确的邮箱地址" required>
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
value="{{ request.user.email }}" readonly>
|
||||
</div>
|
||||
<div class="form-group col-md-6"><label>手机</label>
|
||||
<input name="phone" type="text" maxlength="11" id="phone"
|
||||
<input name="phone_number" type="text" maxlength="11" id="phone"
|
||||
class="form-control"
|
||||
value="{% if request.user.userprofile.phone_number %}{{ request.user.userprofile.phone_number }}{% endif %}">
|
||||
</div>
|
||||
@ -50,28 +50,35 @@
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label>hduoj 用户名</label>
|
||||
<input name="hduoj" type="text" class="form-control" id="hduoj_username"
|
||||
value="{{ request.user.userprofile.hduoj_username }}">
|
||||
<input name="hduoj_username" type="text" class="form-control" id="hduoj_username"
|
||||
value="{% if request.user.userprofile.hduoj_username %}{{ request.user.userprofile.hduoj_username }}{% endif %}">
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label>BestCoder 用户名</label>
|
||||
<input name="bestcoder" type="text" class="form-control" id="bestcoder_username"
|
||||
value="{{ request.user.userprofile.bestcoder_username }}">
|
||||
<input name="bestcoder_username" type="text" class="form-control" id="bestcoder_username"
|
||||
value="{% if request.user.userprofile.bestcoder_username %}{{ request.user.userprofile.bestcoder_username }}{% endif %}">
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label>Codeforces 用户名</label>
|
||||
<input name="codeforces" type="text" class="form-control" id="codeforce_username"
|
||||
value="{{ request.user.userprofile.bestcoder_username }}">
|
||||
<input name="codeforces_username" type="text" class="form-control" id="codeforces_username"
|
||||
value="{% if request.user.userprofile.codeforce_username %}{{ request.user.userprofile.codeforces_username }}{% endif %}">
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
<div class="form-group col-md-12"><label>学号</label>
|
||||
<input name="student_id" type="number" class="form-control" id="student_id"
|
||||
value="{% if request.user.userprofile.student_id %}{{ request.user.userprofile.student_id }}{% endif %}">
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
|
||||
</div>
|
||||
<div class="form-group col-md-12"><label>blog</label>
|
||||
<input name="blog" type="url" class="form-control" id="blog"
|
||||
value="{{ request.user.userprofile.blog }}">
|
||||
value="{% if request.user.userprofile.blog %}{{ request.user.userprofile.blog }}{% endif %}">
|
||||
|
||||
<div class="help-block with-errors"></div>
|
||||
|
||||
|
||||
@ -22,11 +22,16 @@
|
||||
{% if user.userprofile.mood %}
|
||||
<p id="user-mood">{{ user.userprofile.mood }}</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="list-group col-lg-9">
|
||||
|
||||
{% if user.userprofile.school %}
|
||||
<p class="list-group-item"> <span class="glyphicon glyphicon-education"></span>
|
||||
{{ user.userprofile.school }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if user.userprofile.blog %}
|
||||
<p class="list-group-item"><span class="glyphicon glyphicon-link"></span>
|
||||
<p class="list-group-item"> <span class="glyphicon glyphicon-link"></span>
|
||||
<a href="{{ user.userprofile.blog }}" target="_blank">{{ blog_link }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
@ -57,7 +62,7 @@
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<p class="list-group-item">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
{{ user.create_time }}
|
||||
@ -68,7 +73,7 @@
|
||||
<strong id="user-data-number">{{ user.userprofile.rank }}</strong>
|
||||
<span id="user-data-text">Rank</span>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="col-lg-6 text-center">
|
||||
<strong id="user-data-number">{{ user.userprofile.accepted_number }}</strong>
|
||||
<span id="user-data-text">AC</span>
|
||||
@ -77,6 +82,7 @@
|
||||
<strong id="user-data-number">{{ user.userprofile.submissions_number }}</strong>
|
||||
<span id="user-data-text">Submissions</span>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<td>{{ contest.end_time }}</td>
|
||||
<td>{{ contest|contest_status }}</td>
|
||||
{% ifequal contest.contest_type 0 %}
|
||||
<td>小组赛</td>
|
||||
<td>私有小组赛</td>
|
||||
{% endifequal %}
|
||||
{% ifequal contest.contest_type 1 %}
|
||||
<td>公开赛</td>
|
||||
@ -29,6 +29,9 @@
|
||||
{% ifequal contest.contest_type 2 %}
|
||||
<td>公开赛(密码保护)</td>
|
||||
{% endifequal %}
|
||||
{% ifequal contest.contest_type 3 %}
|
||||
<td>小组邀请赛</td>
|
||||
{% endifequal %}
|
||||
|
||||
<td>{{ contest.created_by.username }}</td>
|
||||
</tr>
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
<td>{{ item.start_time }}</td>
|
||||
|
||||
{% ifequal item.contest_type 0 %}
|
||||
<td>小组赛</td>
|
||||
<td>私有小组赛</td>
|
||||
{% endifequal %}
|
||||
{% ifequal item.contest_type 1 %}
|
||||
<td>公开赛</td>
|
||||
@ -45,6 +45,9 @@
|
||||
{% ifequal item.contest_type 2 %}
|
||||
<td>公开赛(密码保护)</td>
|
||||
{% endifequal %}
|
||||
{% ifequal item.contest_type 3 %}
|
||||
<td>小组邀请赛</td>
|
||||
{% endifequal %}
|
||||
|
||||
<td class="{{ item|contest_status_color }}">{{ item|contest_status }}</td>
|
||||
</tr>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<h2 class="text-center">{{ group.name }}</h2>
|
||||
|
||||
<p class="text-muted text-center">发布时间 : {{ group.create_time }}
|
||||
创建者 : {{ group.admin }}
|
||||
创建者 : {{ group.created_by }}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
@ -25,23 +25,23 @@
|
||||
<p class="group-detail">{{ group.description|safe }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% if not joined %}
|
||||
<hr>
|
||||
<div>
|
||||
{% if group.join_group_setting %}
|
||||
{% ifequal group.join_group_setting 1 %}
|
||||
<div class="form-group">
|
||||
<input id="groupId" value="{{ group.id }" type="hidden">
|
||||
<label>申请信息</label>
|
||||
<textarea class="form-control" id="applyMessage" rows="10"></textarea>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary" id="sendApplication">
|
||||
{% if group.join_group_setting %}
|
||||
申请
|
||||
{% endif %}
|
||||
加入</button>
|
||||
<button class="btn btn-primary" id="sendApplication">{% if group.join_group_setting %}申请{% endif %}加入</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
|
||||
@ -34,13 +34,17 @@
|
||||
<th scope="row"><a href="/group/{{ item.id }}/" target="_blank">{{ item.id }}</a></th>
|
||||
<td><a href="/group/{{ item.id }}/" target="_blank">{{ item.name }}</a></td>
|
||||
<td>
|
||||
{% if item.join_group_setting %}
|
||||
{% ifequal item.join_group_setting 1 %}
|
||||
需要申请
|
||||
{% else %}
|
||||
{% endifequal %}
|
||||
{% ifequal item.join_group_setting 0 %}
|
||||
无需申请
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% ifequal item.join_group_setting 2 %}
|
||||
不允许加入
|
||||
{% endifequal %}
|
||||
</td>
|
||||
<td>{{ item.admin }}</td>
|
||||
<td>{{ item.created_by }}</td>
|
||||
<td>{{ item.create_time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@ -97,7 +97,7 @@
|
||||
{% block js_block %}{% endblock %}
|
||||
<!-- footer begin -->
|
||||
<div class="footer">
|
||||
<p class="text-muted text-center">Copyright © 2015 {% show_website_info "website_footer" %}</p>
|
||||
<p class="text-muted text-center">Copyright © 2015 {% autoescape off %}{% show_website_info "website_footer" %}{% endautoescape %}</p>
|
||||
</div>
|
||||
<!-- footer end -->
|
||||
</body>
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
[program:celery]
|
||||
command=celery worker -A judge.judger_controller --loglevel=DEBUG
|
||||
|
||||
directory=/root/qduoj/
|
||||
user=root
|
||||
numprocs=1
|
||||
stdout_logfile=/root/log/celery_worker.log
|
||||
stderr_logfile=/root/log/celery_worker.log
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startsecs=5
|
||||
|
||||
stopwaitsecs = 6
|
||||
|
||||
killasgroup=true
|
||||
@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
python manage.py runserver
|
||||
cls
|
||||
cd..
|
||||
python manage.py runserver
|
||||
@ -1,12 +0,0 @@
|
||||
@echo off
|
||||
coverage run --source='.' manage.py test
|
||||
coverage html
|
||||
cd htmlcov
|
||||
index.html
|
||||
cls
|
||||
cd..
|
||||
coverage run --source='.' manage.py test
|
||||
coverage html
|
||||
cd htmlcov
|
||||
index.html
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
|
||||
[supervisord]
|
||||
logfile=/root/log/supervisord.log ; supervisord log file
|
||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
||||
logfile_backups=10 ; number of backed up logfiles
|
||||
loglevel=info ; info, debug, warn, trace
|
||||
pidfile=/root/log/supervisord.pid ; pidfile location
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
user=root ; default user
|
||||
childlogdir=/root/log/ ; where child log files will live
|
||||
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.
|
||||
|
||||
|
||||
[include]
|
||||
|
||||
files=*.conf
|
||||
15
utils/mail.py
Normal file
15
utils/mail.py
Normal file
@ -0,0 +1,15 @@
|
||||
# coding=utf-8
|
||||
from envelopes import Envelope
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def send_email(from_name, to_email, to_name, subject, content):
|
||||
envelope = Envelope(from_addr=(settings.SMTP_CONFIG["email"], from_name),
|
||||
to_addr=(to_email, to_name),
|
||||
subject=subject,
|
||||
html_body=content)
|
||||
envelope.send(settings.SMTP_CONFIG["smtp_server"],
|
||||
login=settings.SMTP_CONFIG["email"],
|
||||
password=settings.SMTP_CONFIG["password"],
|
||||
tls=settings.SMTP_CONFIG["tls"])
|
||||
Loading…
Reference in New Issue
Block a user