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:
virusdefender 2015-12-10 19:32:00 +08:00
commit d8bb845693
87 changed files with 977 additions and 584 deletions

View 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),
),
]

View File

@ -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"

View File

@ -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"]

View File

@ -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"修改成功")

View File

@ -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:

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -4,11 +4,11 @@ redis
django-redis-sessions
djangorestframework
django-rest-swagger
celery
gunicorn
coverage
django-extensions
supervisor
pillow
jsonfield
Envelopes
Envelopes
huey

View File

@ -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

View File

@ -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

View 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',
),
]

View 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')]),
),
]

View File

@ -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")

View File

@ -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()

View File

@ -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)

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -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()

View File

@ -1 +0,0 @@
celery -A judge.controller worker -l DEBUG

View File

@ -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

View File

@ -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"
}

View File

@ -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)

View File

@ -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
View 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
View 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()

View 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',
},
),
]

View 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,
),
]

View File

View 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"

View 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
View 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)

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -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"])

View File

@ -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

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -1 +0,0 @@
# coding=utf-8

View File

@ -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()

View File

@ -6,4 +6,4 @@
\___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_|
|___/ |___/ |_|
https://github.com/QingdaoU/OnlineJudge
"""
"""

View File

@ -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"}

View File

@ -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 = ['*']

View File

@ -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}

View File

@ -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"),

View File

@ -1 +0,0 @@
nohup celery -A judge.judger_controller worker -l DEBUG &

View File

@ -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();

View File

@ -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 {

View File

@ -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;

View File

@ -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";
}
});
}

View File

@ -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) {

View File

@ -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 = "";

View File

@ -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({

View File

@ -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",

View File

@ -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];

View File

@ -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",
})

View File

@ -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: {

View 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),
),
]

View 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),
),
]

View File

@ -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
View 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)

View File

@ -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"提交判题任务失败")

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -5,7 +5,7 @@
aria-hidden="true">&larr;</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 }}&nbsp;&nbsp;
<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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -15,7 +15,7 @@
<h2 class="text-center">{{ group.name }}</h2>
<p class="text-muted text-center">发布时间 : {{ group.create_time }}&nbsp;&nbsp;
创建者 : {{ 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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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

View File

@ -1,5 +0,0 @@
@echo off
python manage.py runserver
cls
cd..
python manage.py runserver

View File

@ -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

View File

@ -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
View 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"])