diff --git a/account/migrations/0015_userprofile_student_id.py b/account/migrations/0015_userprofile_student_id.py new file mode 100644 index 000000000..cb8991bf1 --- /dev/null +++ b/account/migrations/0015_userprofile_student_id.py @@ -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), + ), + ] diff --git a/account/models.py b/account/models.py index e08ff6c8e..b99c69175 100644 --- a/account/models.py +++ b/account/models.py @@ -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" diff --git a/account/serializers.py b/account/serializers.py index 0aac07aed..0d92ae7ed 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -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"] diff --git a/account/views.py b/account/views.py index 2ba5739c6..5ca7612a0 100644 --- a/account/views.py +++ b/account/views.py @@ -14,7 +14,7 @@ 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 @@ def post(self, request): 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 @@ def put(self, request): 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"修改成功") diff --git a/contest/decorators.py b/contest/decorators.py index 01297f675..663f8fbe6 100644 --- a/contest/decorators.py +++ b/contest/decorators.py @@ -8,8 +8,8 @@ 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(*args, **kwargs): 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(*args, **kwargs): 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: diff --git a/contest/models.py b/contest/models.py index 2213f43ea..591b54afb 100644 --- a/contest/models.py +++ b/contest/models.py @@ -7,12 +7,13 @@ 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 diff --git a/contest/views.py b/contest/views.py index 87378db5f..cfc2049bb 100644 --- a/contest/views.py +++ b/contest/views.py @@ -17,13 +17,13 @@ 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 @@ def post(self, request): 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 @@ def put(self, request): 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 @@ def put(self, request): 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 @@ def get(self, request): # 普通管理员只能获取自己创建的题目 # 超级管理员可以获取全部的题目 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 @@ def post(self, request): 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), diff --git a/judge/judger/__init__.py b/db1.sqlite3 similarity index 100% rename from judge/judger/__init__.py rename to db1.sqlite3 diff --git a/dockerfiles/judger/Dockerfile b/dockerfiles/judger/Dockerfile index 6090728e8..f13a9a5dd 100644 --- a/dockerfiles/judger/Dockerfile +++ b/dockerfiles/judger/Dockerfile @@ -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/ \ No newline at end of file +WORKDIR /var/judger/code/judge/ +CMD python server.py \ No newline at end of file diff --git a/dockerfiles/oj_web_server/requirements.txt b/dockerfiles/oj_web_server/requirements.txt index 7ad8718e1..56676a92d 100644 --- a/dockerfiles/oj_web_server/requirements.txt +++ b/dockerfiles/oj_web_server/requirements.txt @@ -4,11 +4,11 @@ redis django-redis-sessions djangorestframework django-rest-swagger -celery gunicorn coverage django-extensions supervisor pillow jsonfield -Envelopes \ No newline at end of file +Envelopes +huey \ No newline at end of file diff --git a/dockerfiles/oj_web_server/supervisord.conf b/dockerfiles/oj_web_server/supervisord.conf index 420b65f4b..dec4bf49d 100644 --- a/dockerfiles/oj_web_server/supervisord.conf +++ b/dockerfiles/oj_web_server/supervisord.conf @@ -23,4 +23,4 @@ serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets. [include] -files=gunicorn.conf mq.conf \ No newline at end of file +files=gunicorn.conf task_queue.conf \ No newline at end of file diff --git a/dockerfiles/oj_web_server/mq.conf b/dockerfiles/oj_web_server/task_queue.conf similarity index 53% rename from dockerfiles/oj_web_server/mq.conf rename to dockerfiles/oj_web_server/task_queue.conf index ae1797c67..39f837e03 100644 --- a/dockerfiles/oj_web_server/mq.conf +++ b/dockerfiles/oj_web_server/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 diff --git a/group/migrations/0006_auto_20151209_1834.py b/group/migrations/0006_auto_20151209_1834.py new file mode 100644 index 000000000..3e5a6e955 --- /dev/null +++ b/group/migrations/0006_auto_20151209_1834.py @@ -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', + ), + ] diff --git a/group/migrations/0007_auto_20151209_1836.py b/group/migrations/0007_auto_20151209_1836.py new file mode 100644 index 000000000..937d6f071 --- /dev/null +++ b/group/migrations/0007_auto_20151209_1836.py @@ -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')]), + ), + ] diff --git a/group/models.py b/group/models.py index a2db074a9..26059f7e5 100644 --- a/group/models.py +++ b/group/models.py @@ -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 Meta: 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") diff --git a/group/serializers.py b/group/serializers.py index 402ea5b94..377d56d6d 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -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() \ No newline at end of file + status = serializers.BooleanField() + +class GroupPromoteAdminSerializer(serializers.Serializer): + user_id = serializers.IntegerField() + group_id = serializers.IntegerField() diff --git a/group/views.py b/group/views.py index a4a31236e..5c2797b16 100644 --- a/group/views.py +++ b/group/views.py @@ -5,14 +5,14 @@ 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 @@ def get_group(self, request, group_id): 管理员可以查询所有的小组,其他用户查询自己创建的自傲组 """ 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 @@ def get_groups(self, request): 如果是管理员,就返回他创建的全部小组 """ 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 @@ def post(self, request): 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 @@ def put(self, request): 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 @@ def get(self, request): 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 @@ def get(self, request): --- 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) diff --git a/judge/__init__.py b/judge/__init__.py index 9bad5790a..e69de29bb 100644 --- a/judge/__init__.py +++ b/judge/__init__.py @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/judge/judger/client.py b/judge/client.py similarity index 100% rename from judge/judger/client.py rename to judge/client.py diff --git a/judge/judger/compiler.py b/judge/compiler.py similarity index 100% rename from judge/judger/compiler.py rename to judge/compiler.py diff --git a/judge/judger/judge_exceptions.py b/judge/judge_exceptions.py similarity index 100% rename from judge/judger/judge_exceptions.py rename to judge/judge_exceptions.py diff --git a/judge/judger/run.py b/judge/judger/run.py deleted file mode 100644 index 8aeb54767..000000000 --- a/judge/judger/run.py +++ /dev/null @@ -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() diff --git a/judge/judger_controller/README.md b/judge/judger_controller/README.md deleted file mode 100644 index 9dd560320..000000000 --- a/judge/judger_controller/README.md +++ /dev/null @@ -1 +0,0 @@ -celery -A judge.controller worker -l DEBUG \ No newline at end of file diff --git a/judge/judger_controller/celery.py b/judge/judger_controller/celery.py deleted file mode 100644 index 4c64ab042..000000000 --- a/judge/judger_controller/celery.py +++ /dev/null @@ -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 diff --git a/judge/judger_controller/settings.py b/judge/judger_controller/settings.py deleted file mode 100644 index 4d48340ea..000000000 --- a/judge/judger_controller/settings.py +++ /dev/null @@ -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" -} diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py deleted file mode 100644 index 2219d96eb..000000000 --- a/judge/judger_controller/tasks.py +++ /dev/null @@ -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) diff --git a/judge/judger/language.py b/judge/language.py similarity index 87% rename from judge/judger/language.py rename to judge/language.py index c7a14eb48..e6406566c 100644 --- a/judge/judger/language.py +++ b/judge/language.py @@ -7,16 +7,16 @@ "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", diff --git a/judge/judger/logger.py b/judge/logger.py similarity index 100% rename from judge/judger/logger.py rename to judge/logger.py diff --git a/judge/judger/result.py b/judge/result.py similarity index 100% rename from judge/judger/result.py rename to judge/result.py diff --git a/judge/runner.py b/judge/runner.py new file mode 100644 index 000000000..5ca5c2056 --- /dev/null +++ b/judge/runner.py @@ -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) \ No newline at end of file diff --git a/judge/server.py b/judge/server.py new file mode 100644 index 000000000..477cc1e4e --- /dev/null +++ b/judge/server.py @@ -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() \ No newline at end of file diff --git a/judge/judger/settings.py b/judge/settings.py similarity index 100% rename from judge/judger/settings.py rename to judge/settings.py diff --git a/judge/judger/utils.py b/judge/utils.py similarity index 100% rename from judge/judger/utils.py rename to judge/utils.py diff --git a/judge/judger_controller/__init__.py b/judge_dispatcher/__init__.py similarity index 100% rename from judge/judger_controller/__init__.py rename to judge_dispatcher/__init__.py diff --git a/judge_dispatcher/migrations/0001_initial.py b/judge_dispatcher/migrations/0001_initial.py new file mode 100644 index 000000000..e85330ed7 --- /dev/null +++ b/judge_dispatcher/migrations/0001_initial.py @@ -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', + }, + ), + ] diff --git a/judge_dispatcher/migrations/0002_auto_20151207_2310.py b/judge_dispatcher/migrations/0002_auto_20151207_2310.py new file mode 100644 index 000000000..1597919ce --- /dev/null +++ b/judge_dispatcher/migrations/0002_auto_20151207_2310.py @@ -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, + ), + ] diff --git a/judge_dispatcher/migrations/__init__.py b/judge_dispatcher/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/judge_dispatcher/models.py b/judge_dispatcher/models.py new file mode 100644 index 000000000..7ec5af8f5 --- /dev/null +++ b/judge_dispatcher/models.py @@ -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" diff --git a/judge_dispatcher/rpc_client.py b/judge_dispatcher/rpc_client.py new file mode 100644 index 000000000..c095cb5e4 --- /dev/null +++ b/judge_dispatcher/rpc_client.py @@ -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) diff --git a/judge_dispatcher/tasks.py b/judge_dispatcher/tasks.py new file mode 100644 index 000000000..244c9074b --- /dev/null +++ b/judge_dispatcher/tasks.py @@ -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) diff --git a/mail/__init__.py b/mail/__init__.py deleted file mode 100644 index 9bad5790a..000000000 --- a/mail/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/mail/celery.py b/mail/celery.py deleted file mode 100644 index 9bad5790a..000000000 --- a/mail/celery.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/mail/tasks.py b/mail/tasks.py deleted file mode 100644 index 490472baf..000000000 --- a/mail/tasks.py +++ /dev/null @@ -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"]) diff --git a/monitor/views.py b/monitor/views.py index 15c62b4c7..f502579fa 100644 --- a/monitor/views.py +++ b/monitor/views.py @@ -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 diff --git a/mq/__init__.py b/mq/__init__.py deleted file mode 100644 index 9bad5790a..000000000 --- a/mq/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/mq/models.py b/mq/models.py deleted file mode 100644 index 9bad5790a..000000000 --- a/mq/models.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/mq/scripts/__init__.py b/mq/scripts/__init__.py deleted file mode 100644 index 9bad5790a..000000000 --- a/mq/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# coding=utf-8 diff --git a/mq/scripts/mq.py b/mq/scripts/mq.py deleted file mode 100644 index a3b15e7ed..000000000 --- a/mq/scripts/mq.py +++ /dev/null @@ -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() diff --git a/oj/__init__.py b/oj/__init__.py index c19c07943..f4206374b 100644 --- a/oj/__init__.py +++ b/oj/__init__.py @@ -6,4 +6,4 @@ \___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_| |___/ |___/ |_| https://github.com/QingdaoU/OnlineJudge -""" \ No newline at end of file +""" diff --git a/oj/local_settings.py b/oj/local_settings.py index a08ae7e2d..794a7b633 100644 --- a/oj/local_settings.py +++ b/oj/local_settings.py @@ -22,6 +22,12 @@ "db": 1 } +REDIS_QUEUE = { + "host": "127.0.0.1", + "port": 6379, + "db": 2 +} + DEBUG = True ALLOWED_HOSTS = [] @@ -33,4 +39,4 @@ # 模板文件夹 TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/src/')] -SSO = {"callback": "http://localhost:8765/login"} \ No newline at end of file +SSO = {"callback": "http://localhost:8765/login"} diff --git a/oj/server_settings.py b/oj/server_settings.py index bba27c6f7..afec6d194 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -11,7 +11,7 @@ '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 @@ '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 @@ "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 = ['*'] diff --git a/oj/settings.py b/oj/settings.py index ceed0fe53..f58570338 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -48,12 +48,12 @@ 'problem', 'admin', 'submission', - 'mq', 'contest', - 'mail', + 'judge', + 'judge_dispatcher', - 'django_extensions', 'rest_framework', + 'huey.djhuey', ) if DEBUG: @@ -98,11 +98,11 @@ # 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 @@ LOG_PATH = "log/" - LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -186,3 +185,17 @@ WEBSITE_INFO = {"website_name": "qduoj", "website_footer": u"青岛大学信息工程学院 创新实验室 京ICP备15062075号-1", "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} diff --git a/oj/urls.py b/oj/urls.py index d2beba16d..77a92b666 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -15,7 +15,7 @@ MakeContestProblemPublicAPIView) from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, - JoinGroupAPIView, JoinGroupRequestAdminAPIView) + JoinGroupAPIView, JoinGroupRequestAdminAPIView, GroupPrometAdminAPIView) from admin.views import AdminTemplateView @@ -55,6 +55,7 @@ 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 @@ 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"), diff --git a/runJudge.sh b/runJudge.sh deleted file mode 100755 index ba25da83a..000000000 --- a/runJudge.sh +++ /dev/null @@ -1 +0,0 @@ -nohup celery -A judge.judger_controller worker -l DEBUG & diff --git a/static/src/js/app/admin/admin.js b/static/src/js/app/admin/admin.js index 72c8fd6b1..2c527de7f 100644 --- a/static/src/js/app/admin/admin.js +++ b/static/src/js/app/admin/admin.js @@ -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(); diff --git a/static/src/js/app/admin/contest/addContest.js b/static/src/js/app/admin/contest/addContest.js index e84f6b08d..0fd6f9ff1 100644 --- a/static/src/js/app/admin/contest/addContest.js +++ b/static/src/js/app/admin/contest/addContest.js @@ -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 { diff --git a/static/src/js/app/admin/contest/editContest.js b/static/src/js/app/admin/contest/editContest.js index 67a027c35..a49781776 100644 --- a/static/src/js/app/admin/contest/editContest.js +++ b/static/src/js/app/admin/contest/editContest.js @@ -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; diff --git a/static/src/js/app/admin/group/group.js b/static/src/js/app/admin/group/group.js index 425e4bbf6..f97e809c2 100644 --- a/static/src/js/app/admin/group/group.js +++ b/static/src/js/app/admin/group/group.js @@ -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"; } }); } diff --git a/static/src/js/app/admin/group/groupDetail.js b/static/src/js/app/admin/group/groupDetail.js index de4a0ff57..6df05afaf 100644 --- a/static/src/js/app/admin/group/groupDetail.js +++ b/static/src/js/app/admin/group/groupDetail.js @@ -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) { diff --git a/static/src/js/app/oj/account/register.js b/static/src/js/app/oj/account/register.js index ef23a38c3..9c2109e33 100644 --- a/static/src/js/app/oj/account/register.js +++ b/static/src/js/app/oj/account/register.js @@ -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 = ""; diff --git a/static/src/js/app/oj/account/resetPassword.js b/static/src/js/app/oj/account/resetPassword.js index 0f0bb4bc5..ef1128d0e 100644 --- a/static/src/js/app/oj/account/resetPassword.js +++ b/static/src/js/app/oj/account/resetPassword.js @@ -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({ diff --git a/static/src/js/app/oj/account/settings.js b/static/src/js/app/oj/account/settings.js index b4538ee54..2c485300b 100644 --- a/static/src/js/app/oj/account/settings.js +++ b/static/src/js/app/oj/account/settings.js @@ -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", diff --git a/static/src/js/app/oj/group/group.js b/static/src/js/app/oj/group/group.js index d9371427e..66bb73357 100644 --- a/static/src/js/app/oj/group/group.js +++ b/static/src/js/app/oj/group/group.js @@ -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]; diff --git a/static/src/js/build.js b/static/src/js/build.js index beb891e0a..8c8de627d 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -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", }) \ No newline at end of file diff --git a/static/src/js/config.js b/static/src/js/config.js index e2856f654..ee6f52b25 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -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: { diff --git a/submission/migrations/0007_auto_20151207_1645.py b/submission/migrations/0007_auto_20151207_1645.py new file mode 100644 index 000000000..a053d98c7 --- /dev/null +++ b/submission/migrations/0007_auto_20151207_1645.py @@ -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), + ), + ] diff --git a/submission/migrations/0008_auto_20151208_2106.py b/submission/migrations/0008_auto_20151208_2106.py new file mode 100644 index 000000000..b3a85763c --- /dev/null +++ b/submission/migrations/0008_auto_20151208_2106.py @@ -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), + ), + ] diff --git a/submission/models.py b/submission/models.py index b28bd5ab6..fc1a0708a 100644 --- a/submission/models.py +++ b/submission/models.py @@ -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 diff --git a/submission/tasks.py b/submission/tasks.py new file mode 100644 index 000000000..25476e36e --- /dev/null +++ b/submission/tasks.py @@ -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) \ No newline at end of file diff --git a/submission/views.py b/submission/views.py index 1f2acb164..ccda8acb2 100644 --- a/submission/views.py +++ b/submission/views.py @@ -7,14 +7,13 @@ 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 @@ 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 @@ def post(self, request): 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 @@ def post(self, request): 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 @@ def post(self, request): 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"提交判题任务失败") diff --git a/template/src/admin/contest/add_contest.html b/template/src/admin/contest/add_contest.html index 010b73ebc..e97331612 100644 --- a/template/src/admin/contest/add_contest.html +++ b/template/src/admin/contest/add_contest.html @@ -46,13 +46,13 @@
@@ -60,10 +60,18 @@
-
- -
- +
+
+ +
+ +
+
+
+ +
+ +
diff --git a/template/src/admin/contest/edit_contest.html b/template/src/admin/contest/edit_contest.html index 08f7c09dd..3e5060362 100644 --- a/template/src/admin/contest/edit_contest.html +++ b/template/src/admin/contest/edit_contest.html @@ -55,13 +55,13 @@
@@ -69,11 +69,19 @@
-
- +
+
+ -
- +
+ +
+
+
+ +
+ +
diff --git a/template/src/admin/group/group.html b/template/src/admin/group/group.html index 84614c9bc..3dcb7eaab 100644 --- a/template/src/admin/group/group.html +++ b/template/src/admin/group/group.html @@ -38,6 +38,35 @@

小组管理

+

创建小组

+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ + 允许任何人加入 + 提交请求后管理员审核 + 不允许任何人加入 + +
+ +
+
\ No newline at end of file diff --git a/template/src/admin/group/group_detail.html b/template/src/admin/group/group_detail.html index b75f0efae..71e54b5cb 100644 --- a/template/src/admin/group/group_detail.html +++ b/template/src/admin/group/group_detail.html @@ -5,7 +5,7 @@ aria-hidden="true">← 返回 -

小组成员管理

+

小组成员管理

@@ -19,19 +19,19 @@

小组成员管理

- +
ID {{ el.user.username }} {{ el.user.real_name }} {{ el.join_time|date("yyyy-MM-dd HH:mm:ss")}} +
-
页数:{{ page }}/{{ totalPage }}  
-

修改小组信息

+

修改小组信息

@@ -49,18 +49,24 @@

修改小组信息

-
+
- - - 允许任何人加入 - 提交请求后管理员审核 - 不允许任何人加入 - +
+ + +
+ +
+
+
+ +
+
-
diff --git a/template/src/oj/account/register.html b/template/src/oj/account/register.html index f3b990b30..504361111 100644 --- a/template/src/oj/account/register.html +++ b/template/src/oj/account/register.html @@ -19,10 +19,14 @@

用户注册

- +
+
+ + +
diff --git a/template/src/oj/account/settings.html b/template/src/oj/account/settings.html index ebfee385a..53560240f 100644 --- a/template/src/oj/account/settings.html +++ b/template/src/oj/account/settings.html @@ -34,7 +34,7 @@ value="{{ request.user.email }}" readonly>
-
@@ -50,28 +50,35 @@
- +
- +
- +
+
+ + +
+ +
+ value="{% if request.user.userprofile.blog %}{{ request.user.userprofile.blog }}{% endif %}">
diff --git a/template/src/oj/account/user_index.html b/template/src/oj/account/user_index.html index a829a25d9..6d34593d5 100644 --- a/template/src/oj/account/user_index.html +++ b/template/src/oj/account/user_index.html @@ -22,11 +22,16 @@

{% if user.userprofile.mood %}

{{ user.userprofile.mood }}

{% endif %} +

- + {% if user.userprofile.school %} +

+ {{ user.userprofile.school }} +

+ {% endif %} {% if user.userprofile.blog %} -

+

{{ blog_link }}

{% endif %} @@ -57,7 +62,7 @@

{% endif %} - +

{{ user.create_time }} @@ -68,7 +73,7 @@

{{ user.userprofile.rank }} Rank

- --> +
{{ user.userprofile.accepted_number }} AC @@ -77,6 +82,7 @@

{{ user.userprofile.submissions_number }} Submissions

+ --> diff --git a/template/src/oj/contest/_contest_header.html b/template/src/oj/contest/_contest_header.html index 3fcac4470..2e1fdf3c7 100644 --- a/template/src/oj/contest/_contest_header.html +++ b/template/src/oj/contest/_contest_header.html @@ -21,7 +21,7 @@

{{ contest.title }}

{{ contest.end_time }} {{ contest|contest_status }} {% ifequal contest.contest_type 0 %} - 小组赛 + 私有小组赛 {% endifequal %} {% ifequal contest.contest_type 1 %} 公开赛 @@ -29,6 +29,9 @@

{{ contest.title }}

{% ifequal contest.contest_type 2 %} 公开赛(密码保护) {% endifequal %} + {% ifequal contest.contest_type 3 %} + 小组邀请赛 + {% endifequal %} {{ contest.created_by.username }} diff --git a/template/src/oj/contest/contest_list.html b/template/src/oj/contest/contest_list.html index 92a59d87f..00fde88bb 100644 --- a/template/src/oj/contest/contest_list.html +++ b/template/src/oj/contest/contest_list.html @@ -37,7 +37,7 @@ {{ item.start_time }} {% ifequal item.contest_type 0 %} - 小组赛 + 私有小组赛 {% endifequal %} {% ifequal item.contest_type 1 %} 公开赛 @@ -45,6 +45,9 @@ {% ifequal item.contest_type 2 %} 公开赛(密码保护) {% endifequal %} + {% ifequal item.contest_type 3 %} + 小组邀请赛 + {% endifequal %} {{ item|contest_status }} diff --git a/template/src/oj/group/group.html b/template/src/oj/group/group.html index c860fdf89..bbf6c32ba 100644 --- a/template/src/oj/group/group.html +++ b/template/src/oj/group/group.html @@ -15,7 +15,7 @@

{{ group.name }}

发布时间 : {{ group.create_time }}   - 创建者 : {{ group.admin }} + 创建者 : {{ group.created_by }}

@@ -25,23 +25,23 @@

{{ group.name }}

{{ group.description|safe }}

+ {% if not joined %}
- {% if group.join_group_setting %} + {% ifequal group.join_group_setting 1 %}
- {% endif %} + {% endifequal %} +
- +
+
+ {% endif %} {% endblock %} {% block js_block %} diff --git a/template/src/oj/group/group_list.html b/template/src/oj/group/group_list.html index 500257a02..575102bda 100644 --- a/template/src/oj/group/group_list.html +++ b/template/src/oj/group/group_list.html @@ -34,13 +34,17 @@ {{ item.id }} {{ item.name }} - {% 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 %} - {{ item.admin }} + {{ item.created_by }} {{ item.create_time }} {% endfor %} diff --git a/template/src/oj_base.html b/template/src/oj_base.html index 692bce1f2..725461e98 100644 --- a/template/src/oj_base.html +++ b/template/src/oj_base.html @@ -97,7 +97,7 @@ {% block js_block %}{% endblock %} diff --git a/tools/celeryd.conf b/tools/celeryd.conf deleted file mode 100644 index 60e3fc215..000000000 --- a/tools/celeryd.conf +++ /dev/null @@ -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 diff --git a/tools/runserver.cmd b/tools/runserver.cmd deleted file mode 100644 index b458e559f..000000000 --- a/tools/runserver.cmd +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -python manage.py runserver -cls -cd.. -python manage.py runserver \ No newline at end of file diff --git a/tools/runtest.cmd b/tools/runtest.cmd deleted file mode 100644 index e6d9ad7b6..000000000 --- a/tools/runtest.cmd +++ /dev/null @@ -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 - diff --git a/tools/supervisord.conf b/tools/supervisord.conf deleted file mode 100644 index 1494d7cd4..000000000 --- a/tools/supervisord.conf +++ /dev/null @@ -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 \ No newline at end of file diff --git a/utils/mail.py b/utils/mail.py new file mode 100644 index 000000000..5d4e1c3ce --- /dev/null +++ b/utils/mail.py @@ -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"])