mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 16:33:22 +00:00
Merge branch 'dev'
* dev: (22 commits) 修改模板名称 使用新的生成比赛排名的方式 JSONField 会自动把字典的 key转换为数字,判断一下,防止重复创建 fix typo 晕了,没提交代码就在服务器上调试。。。 修复用户提交题目的时候,problems_status 中没有对应的 key 导致的错误 更新用户提交 ac 标志存储方法 修复 jsonField 中的问题 增加 model 修改的 migrations 修改用户题目 ac 状态的存储方式 更新 ContestRank的生成方法 不是本人的提交就增加作者用户名 模板中判断能否分享 fix typo fix typo 修复 None 导致的 xss filter 错误 修复None 导致的 xss filter 工作异常 修复分享代码时候的逻辑错误 add oj text logo 增加富文本的 xss 过滤 ...
This commit is contained in:
commit
4c7f03776b
@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
文档:https://www.zybuluo.com/virusdefender/note/171932
|
文档:https://www.zybuluo.com/virusdefender/note/171932
|
||||||
|
|
||||||
|
demo: https://qduoj.com
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
- 完善文档,目前还差很多
|
- 完善文档,目前还差很多
|
||||||
- 完善测试
|
- 完善测试
|
||||||
- 搭建 demo 站点
|
|
||||||
|
|
||||||
![oj_previewindex.png][1]
|
![oj_previewindex.png][1]
|
||||||
|
|
||||||
|
20
account/migrations/0006_auto_20150924_1530.py
Normal file
20
account/migrations/0006_auto_20150924_1530.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0005_user_problems_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='problems_status',
|
||||||
|
field=utils.models.JsonField(default={}),
|
||||||
|
),
|
||||||
|
]
|
@ -2,6 +2,8 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractBaseUser
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
|
|
||||||
|
from jsonfield import JSONField
|
||||||
|
|
||||||
|
|
||||||
class AdminGroup(models.Model):
|
class AdminGroup(models.Model):
|
||||||
pass
|
pass
|
||||||
@ -31,7 +33,9 @@ class User(AbstractBaseUser):
|
|||||||
# 0代表不是管理员 1是普通管理员 2是超级管理员
|
# 0代表不是管理员 1是普通管理员 2是超级管理员
|
||||||
admin_type = models.IntegerField(default=0)
|
admin_type = models.IntegerField(default=0)
|
||||||
# JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行
|
# JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行
|
||||||
problems_status = models.TextField(default="{}")
|
problems_status = JSONField(default={})
|
||||||
|
# 找回密码用的token
|
||||||
|
# reset_password_token = models.CharField(max_length=40, blank=True, null=True)
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
@ -46,3 +46,9 @@ class EditUserSerializer(serializers.Serializer):
|
|||||||
password = serializers.CharField(max_length=30, min_length=6, required=False, default=None)
|
password = serializers.CharField(max_length=30, min_length=6, required=False, default=None)
|
||||||
email = serializers.EmailField(max_length=254)
|
email = serializers.EmailField(max_length=254)
|
||||||
admin_type = serializers.IntegerField(default=0)
|
admin_type = serializers.IntegerField(default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(max_length=30)
|
||||||
|
email = serializers.EmailField()
|
||||||
|
captcha = serializers.CharField(max_length=4, min_length=4)
|
@ -3,17 +3,20 @@ from django import http
|
|||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate
|
from utils.shortcuts import serializer_invalid_response, error_response, success_response, paginate, rand_str
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
|
|
||||||
from .decorators import login_required
|
from .decorators import login_required
|
||||||
from .models import User
|
from .models import User
|
||||||
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
|
from .serializers import (UserLoginSerializer, UsernameCheckSerializer,
|
||||||
UserRegisterSerializer, UserChangePasswordSerializer,
|
UserRegisterSerializer, UserChangePasswordSerializer,
|
||||||
EmailCheckSerializer, UserSerializer, EditUserSerializer)
|
EmailCheckSerializer, UserSerializer, EditUserSerializer,
|
||||||
|
ApplyResetPasswordSerializer)
|
||||||
|
from .decorators import super_admin_required
|
||||||
|
|
||||||
|
|
||||||
class UserLoginAPIView(APIView):
|
class UserLoginAPIView(APIView):
|
||||||
@ -150,6 +153,7 @@ class EmailCheckAPIView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class UserAdminAPIView(APIView):
|
class UserAdminAPIView(APIView):
|
||||||
|
@super_admin_required
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
"""
|
"""
|
||||||
用户编辑json api接口
|
用户编辑json api接口
|
||||||
@ -181,6 +185,7 @@ class UserAdminAPIView(APIView):
|
|||||||
else:
|
else:
|
||||||
return serializer_invalid_response(serializer)
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
@super_admin_required
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
用户分页json api接口
|
用户分页json api接口
|
||||||
@ -222,8 +227,39 @@ class AccountSecurityAPIView(APIView):
|
|||||||
username = request.GET.get("username", None)
|
username = request.GET.get("username", None)
|
||||||
if username:
|
if username:
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=username, admin_type__gt=0)
|
user = User.objects.get(username=username)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return success_response({"applied_captcha": False})
|
return success_response({"applied_captcha": True})
|
||||||
|
if user.admin_type > 0:
|
||||||
return success_response({"applied_captcha": True})
|
return success_response({"applied_captcha": True})
|
||||||
return success_response({"applied_captcha": False})
|
return success_response({"applied_captcha": False})
|
||||||
|
|
||||||
|
|
||||||
|
class ApplyResetPasswordAPIView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
serializer = ApplyResetPasswordSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
data = serializer.data
|
||||||
|
captcha = Captcha(request)
|
||||||
|
if not captcha.check(data["captcha"]):
|
||||||
|
return error_response(u"验证码错误")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=data["username"], email=data["email"])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return error_response(u"用户不存在")
|
||||||
|
user.reset_password_token = rand_str()
|
||||||
|
user.save()
|
||||||
|
# todo
|
||||||
|
email_template = open(settings.TEMPLATES[0]["DIRS"][0] + "utils/reset_password_email.html", "r").read()
|
||||||
|
email_template.replace("{{ username }}", user.username).replace("{{ link }}", "/reset_password/?token=" + user.reset_password_token)
|
||||||
|
return success_response(u"邮件发生成功")
|
||||||
|
else:
|
||||||
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPasswordAPIView(APIView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def user_index_page(request, username):
|
||||||
|
return render(request, "oj/account/user_index.html")
|
||||||
|
20
announcement/migrations/0003_auto_20150922_1703.py
Normal file
20
announcement/migrations/0003_auto_20150922_1703.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('announcement', '0002_auto_20150818_1445'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='announcement',
|
||||||
|
name='content',
|
||||||
|
field=utils.models.RichTextField(),
|
||||||
|
),
|
||||||
|
]
|
@ -3,13 +3,14 @@ from django.db import models
|
|||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
|
from utils.models import RichTextField
|
||||||
|
|
||||||
|
|
||||||
class Announcement(models.Model):
|
class Announcement(models.Model):
|
||||||
# 标题
|
# 标题
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
# 公告的内容 HTML 格式
|
# 公告的内容 HTML 格式
|
||||||
content = models.TextField()
|
content = RichTextField()
|
||||||
# 创建时间
|
# 创建时间
|
||||||
create_time = models.DateTimeField(auto_now_add=True)
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
# 这个公告是谁创建的
|
# 这个公告是谁创建的
|
||||||
|
25
contest/migrations/0010_auto_20150922_1703.py
Normal file
25
contest/migrations/0010_auto_20150922_1703.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contest', '0009_contestsubmission_first_achieved'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contest',
|
||||||
|
name='description',
|
||||||
|
field=utils.models.RichTextField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contestproblem',
|
||||||
|
name='description',
|
||||||
|
field=utils.models.RichTextField(),
|
||||||
|
),
|
||||||
|
]
|
29
contest/migrations/0011_contestrank.py
Normal file
29
contest/migrations/0011_contestrank.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('contest', '0010_auto_20150922_1703'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContestRank',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('total_submission_number', models.IntegerField(default=0)),
|
||||||
|
('total_ac_number', models.IntegerField(default=0)),
|
||||||
|
('total_time', models.IntegerField(default=0)),
|
||||||
|
('submission_info', utils.models.JsonField(default={})),
|
||||||
|
('contest', models.ForeignKey(to='contest.Contest')),
|
||||||
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -5,6 +5,10 @@ from django.utils.timezone import now
|
|||||||
from account.models import User
|
from account.models import User
|
||||||
from problem.models import AbstractProblem
|
from problem.models import AbstractProblem
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
|
from utils.models import RichTextField
|
||||||
|
from jsonfield import JSONField
|
||||||
|
from judge.judger.result import result
|
||||||
|
|
||||||
|
|
||||||
GROUP_CONTEST = 0
|
GROUP_CONTEST = 0
|
||||||
PUBLIC_CONTEST = 1
|
PUBLIC_CONTEST = 1
|
||||||
@ -17,7 +21,7 @@ CONTEST_UNDERWAY = 0
|
|||||||
|
|
||||||
class Contest(models.Model):
|
class Contest(models.Model):
|
||||||
title = models.CharField(max_length=40, unique=True)
|
title = models.CharField(max_length=40, unique=True)
|
||||||
description = models.TextField()
|
description = RichTextField()
|
||||||
# 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式
|
# 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式
|
||||||
mode = models.IntegerField()
|
mode = models.IntegerField()
|
||||||
# 是否显示实时排名结果
|
# 是否显示实时排名结果
|
||||||
@ -102,3 +106,66 @@ class ContestSubmission(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "contest_submission"
|
db_table = "contest_submission"
|
||||||
|
|
||||||
|
|
||||||
|
class ContestRank(models.Model):
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
contest = models.ForeignKey(Contest)
|
||||||
|
total_submission_number = models.IntegerField(default=0)
|
||||||
|
total_ac_number = models.IntegerField(default=0)
|
||||||
|
# ac 的题目才要加到这个字段里面 = ac 时间 + 错误次数 * 20 * 60
|
||||||
|
# 没有 ac 的题目不计算罚时 单位是秒
|
||||||
|
total_time = models.IntegerField(default=0)
|
||||||
|
# 数据结构{23: {"is_ac": True, "ac_time": 8999, "error_number": 2, "is_first_ac": True}}
|
||||||
|
# key 是比赛题目的id
|
||||||
|
submission_info = JSONField(default={})
|
||||||
|
|
||||||
|
def update_rank(self, submission):
|
||||||
|
if not submission.contest_id or submission.contest_id != self.contest_id:
|
||||||
|
raise ValueError("Error submission type")
|
||||||
|
|
||||||
|
# 这道题以前提交过
|
||||||
|
if str(submission.problem_id) in self.submission_info:
|
||||||
|
info = self.submission_info[str(submission.problem_id)]
|
||||||
|
# 如果这道题目已经 ac 了就跳过
|
||||||
|
if info["is_ac"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.total_submission_number += 1
|
||||||
|
|
||||||
|
if submission.result == result["accepted"]:
|
||||||
|
|
||||||
|
self.total_ac_number += 1
|
||||||
|
|
||||||
|
info["is_ac"] = True
|
||||||
|
info["ac_time"] = (submission.create_time - self.contest.start_time).total_seconds()
|
||||||
|
|
||||||
|
# 之前已经提交过,但是是错误的,这次提交是正确的。错误的题目不计入罚时
|
||||||
|
self.total_time += (info["ac_time"] + info["error_number"] * 20 * 60)
|
||||||
|
problem = ContestProblem.objects.get(id=submission.problem_id)
|
||||||
|
if problem.total_accepted_number == 0:
|
||||||
|
info["is_first_ac"] = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
info["error_number"] += 1
|
||||||
|
info["is_ac"] = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 第一次提交这道题目
|
||||||
|
self.total_submission_number += 1
|
||||||
|
info = {"is_ac": False, "ac_time": 0, "error_number": 0, "is_first_ac": False}
|
||||||
|
if submission.result == result["accepted"]:
|
||||||
|
self.total_ac_number += 1
|
||||||
|
info["is_ac"] = True
|
||||||
|
info["ac_time"] = (submission.create_time - self.contest.start_time).total_seconds()
|
||||||
|
self.total_time += info["ac_time"]
|
||||||
|
problem = ContestProblem.objects.get(id=submission.problem_id)
|
||||||
|
|
||||||
|
if problem.total_accepted_number == 0:
|
||||||
|
info["is_first_ac"] = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
info["is_ac"] = False
|
||||||
|
info["error_number"] = 1
|
||||||
|
self.submission_info[str(submission.problem_id)] = info
|
||||||
|
self.save()
|
||||||
|
@ -16,7 +16,8 @@ from utils.shortcuts import (serializer_invalid_response, error_response,
|
|||||||
from account.models import SUPER_ADMIN, User
|
from account.models import SUPER_ADMIN, User
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required
|
||||||
from group.models import Group
|
from group.models import Group
|
||||||
from .models import Contest, ContestProblem, ContestSubmission, CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY
|
from .models import (Contest, ContestProblem, ContestSubmission, 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
|
||||||
from .decorators import check_user_contest_permission
|
from .decorators import check_user_contest_permission
|
||||||
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer,
|
||||||
@ -334,18 +335,6 @@ def contest_problems_list_page(request, contest_id):
|
|||||||
"""
|
"""
|
||||||
contest = Contest.objects.get(id=contest_id)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
||||||
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
|
|
||||||
state = {}
|
|
||||||
for item in submissions:
|
|
||||||
state[item.problem_id] = item.ac
|
|
||||||
for item in contest_problems:
|
|
||||||
if item.id in state:
|
|
||||||
if state[item.id]:
|
|
||||||
item.state = 1
|
|
||||||
else:
|
|
||||||
item.state = 2
|
|
||||||
else:
|
|
||||||
item.state = 0
|
|
||||||
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
|
return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems,
|
||||||
"contest": {"id": contest_id}})
|
"contest": {"id": contest_id}})
|
||||||
|
|
||||||
@ -391,81 +380,16 @@ def contest_list_page(request, page=1):
|
|||||||
"keyword": keyword, "join": join})
|
"keyword": keyword, "join": join})
|
||||||
|
|
||||||
|
|
||||||
def _cmp(x, y):
|
|
||||||
if x["total_ac"] > y["total_ac"]:
|
|
||||||
return 1
|
|
||||||
elif x["total_ac"] < y["total_ac"]:
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
if x["total_time"] < y["total_time"]:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
|
|
||||||
def get_the_formatted_time(seconds):
|
|
||||||
if not seconds:
|
|
||||||
return ""
|
|
||||||
result = str(seconds % 60)
|
|
||||||
if seconds % 60 < 10:
|
|
||||||
result = "0" + result
|
|
||||||
result = str((seconds % 3600) / 60) + ":" + result
|
|
||||||
if (seconds % 3600) / 60 < 10:
|
|
||||||
result = "0" + result
|
|
||||||
result = str(seconds / 3600) + ":" + result
|
|
||||||
if seconds / 3600 < 10:
|
|
||||||
result = "0" + result
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@check_user_contest_permission
|
@check_user_contest_permission
|
||||||
def contest_rank_page(request, contest_id):
|
def contest_rank_page(request, contest_id):
|
||||||
contest = Contest.objects.get(id=contest_id)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index")
|
||||||
r = redis.Redis(host=REDIS_CACHE["host"], port=REDIS_CACHE["port"], db=REDIS_CACHE["db"])
|
rank = ContestRank.objects.filter(contest_id=contest_id).order_by("-total_ac_number", "total_time")
|
||||||
if contest.real_time_rank:
|
|
||||||
# 更新rank
|
|
||||||
result = ContestSubmission.objects.filter(contest=contest).values("user_id"). \
|
|
||||||
annotate(total_submit=Sum("total_submission_number"))
|
|
||||||
for i in range(0, len(result)):
|
|
||||||
# 这个人所有的提交
|
|
||||||
submissions = ContestSubmission.objects.filter(user_id=result[i]["user_id"], contest_id=contest_id)
|
|
||||||
result[i]["submissions"] = {}
|
|
||||||
result[i]["problems"] = []
|
|
||||||
for problem in contest_problems:
|
|
||||||
try:
|
|
||||||
status = submissions.get(problem=problem)
|
|
||||||
result[i]["problems"].append({
|
|
||||||
"first_achieved": status.first_achieved,
|
|
||||||
"ac": status.ac,
|
|
||||||
"failed_number": status.total_submission_number,
|
|
||||||
"ac_time": get_the_formatted_time(status.ac_time)})
|
|
||||||
if status.ac:
|
|
||||||
result[i]["problems"][-1]["failed_number"] -= 1
|
|
||||||
except ContestSubmission.DoesNotExist:
|
|
||||||
result[i]["problems"].append({})
|
|
||||||
result[i]["total_ac"] = submissions.filter(ac=True).count()
|
|
||||||
user= User.objects.get(id=result[i]["user_id"])
|
|
||||||
result[i]["username"] = user.username
|
|
||||||
result[i]["real_name"] = user.real_name
|
|
||||||
result[i]["total_time"] = get_the_formatted_time(submissions.filter(ac=True).aggregate(total_time=Sum("total_time"))["total_time"])
|
|
||||||
|
|
||||||
result = sorted(result, cmp=_cmp, reverse=True)
|
|
||||||
r.set("contest_rank_" + contest_id, json.dumps(list(result)))
|
|
||||||
else:
|
|
||||||
# 从缓存读取排名信息
|
|
||||||
result = r.get("contest_rank_" + contest_id)
|
|
||||||
if result:
|
|
||||||
result = json.loads(result)
|
|
||||||
else:
|
|
||||||
result = []
|
|
||||||
|
|
||||||
return render(request, "oj/contest/contest_rank.html",
|
return render(request, "oj/contest/contest_rank.html",
|
||||||
{"contest": contest, "contest_problems": contest_problems,
|
{"rank": rank, "contest": contest,
|
||||||
"result": result,
|
"contest_problems": contest_problems,
|
||||||
"auto_refresh": request.GET.get("auto_refresh", None) == "true",
|
"auto_refresh": request.GET.get("auto_refresh", None) == "true",
|
||||||
"show_real_name": request.GET.get("show_real_name", None) == "true",
|
"show_real_name": request.GET.get("show_real_name", None) == "true",})
|
||||||
"real_time_rank": contest.real_time_rank})
|
|
||||||
|
|
||||||
|
|
||||||
class ContestTimeAPIView(APIView):
|
class ContestTimeAPIView(APIView):
|
||||||
@ -481,4 +405,3 @@ class ContestTimeAPIView(APIView):
|
|||||||
return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
|
return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000),
|
||||||
"end": int((contest.end_time - now()).total_seconds() * 1000),
|
"end": int((contest.end_time - now()).total_seconds() * 1000),
|
||||||
"status": contest.status})
|
"status": contest.status})
|
||||||
|
|
||||||
|
@ -49,6 +49,13 @@ class ContestSubmissionAPIView(APIView):
|
|||||||
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
judge.delay(submission.id, problem.time_limit, problem.memory_limit, problem.test_case_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
return error_response(u"提交判题任务失败")
|
return error_response(u"提交判题任务失败")
|
||||||
|
# 修改用户解题状态
|
||||||
|
problems_status = request.user.problems_status
|
||||||
|
if "contest_problems" not in problems_status:
|
||||||
|
problems_status["contest_problems"] = {}
|
||||||
|
problems_status["contest_problems"][str(data["problem_id"])] = 2
|
||||||
|
request.user.problems_status = problems_status
|
||||||
|
request.user.save()
|
||||||
# 增加redis 中判题队列长度的计数器
|
# 增加redis 中判题队列长度的计数器
|
||||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||||
r.incr("judge_queue_length")
|
r.incr("judge_queue_length")
|
||||||
|
@ -10,3 +10,4 @@ coverage
|
|||||||
django-extensions
|
django-extensions
|
||||||
supervisor
|
supervisor
|
||||||
pillow
|
pillow
|
||||||
|
jsonfield
|
@ -10,7 +10,7 @@ from judge.judger_controller.settings import redis_config
|
|||||||
from judge.judger.result import result
|
from judge.judger.result import result
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import ContestProblem, Contest, ContestSubmission
|
from contest.models import ContestProblem, Contest, ContestSubmission, CONTEST_UNDERWAY, ContestRank
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
|
||||||
logger = logging.getLogger("app_info")
|
logger = logging.getLogger("app_info")
|
||||||
@ -25,12 +25,20 @@ class MessageQueue(object):
|
|||||||
while True:
|
while True:
|
||||||
submission_id = self.conn.blpop(self.queue, 0)[1]
|
submission_id = self.conn.blpop(self.queue, 0)[1]
|
||||||
logger.debug("receive submission_id: " + submission_id)
|
logger.debug("receive submission_id: " + submission_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
submission = Submission.objects.get(id=submission_id)
|
submission = Submission.objects.get(id=submission_id)
|
||||||
except Submission.DoesNotExist:
|
except Submission.DoesNotExist:
|
||||||
logger.warning("Submission does not exist, submission_id: " + submission_id)
|
logger.warning("Submission does not exist, submission_id: " + submission_id)
|
||||||
continue
|
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 submission.result == result["accepted"] and not submission.contest_id:
|
if submission.result == result["accepted"] and not submission.contest_id:
|
||||||
# 更新普通题目的 ac 计数器
|
# 更新普通题目的 ac 计数器
|
||||||
try:
|
try:
|
||||||
@ -40,15 +48,10 @@ class MessageQueue(object):
|
|||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
||||||
continue
|
continue
|
||||||
# 更新该用户的解题状态
|
|
||||||
try:
|
problems_status = user.problems_status
|
||||||
user = User.objects.get(pk=submission.user_id)
|
problems_status["problems"][str(problem.id)] = 1
|
||||||
except User.DoesNotExist:
|
user.problems_status = problems_status
|
||||||
logger.warning("Submission user does not exist, submission_id: " + submission_id)
|
|
||||||
continue
|
|
||||||
problems_status = json.loads(user.problems_status)
|
|
||||||
problems_status[str(problem.id)] = 1
|
|
||||||
user.problems_status = json.dumps(problems_status)
|
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
# 普通题目的话,到这里就结束了
|
# 普通题目的话,到这里就结束了
|
||||||
@ -57,7 +60,7 @@ class MessageQueue(object):
|
|||||||
# 能运行到这里的都是比赛题目
|
# 能运行到这里的都是比赛题目
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=submission.contest_id)
|
contest = Contest.objects.get(id=submission.contest_id)
|
||||||
if contest.status != 0:
|
if contest.status != CONTEST_UNDERWAY:
|
||||||
logger.info("Contest debug mode, id: " + str(contest.id) + ", submission id: " + submission_id)
|
logger.info("Contest debug mode, id: " + str(contest.id) + ", submission id: " + submission_id)
|
||||||
continue
|
continue
|
||||||
contest_problem = ContestProblem.objects.get(contest=contest, id=submission.problem_id)
|
contest_problem = ContestProblem.objects.get(contest=contest, id=submission.problem_id)
|
||||||
@ -68,50 +71,21 @@ class MessageQueue(object):
|
|||||||
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
logger.warning("Submission problem does not exist, submission_id: " + submission_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
try:
|
try:
|
||||||
contest_submission = ContestSubmission.objects.get(user_id=submission.user_id, contest=contest,
|
contest_rank = ContestRank.objects.get(contest=contest, user=user)
|
||||||
problem_id=contest_problem.id)
|
contest_rank.update_rank(submission)
|
||||||
# 提交次数加1
|
except ContestRank.DoesNotExist:
|
||||||
with transaction.atomic():
|
ContestRank.objects.create(contest=contest, user=user).update_rank(submission)
|
||||||
|
|
||||||
if submission.result == result["accepted"]:
|
if submission.result == result["accepted"]:
|
||||||
# 避免这道题已经 ac 了,但是又重新提交了一遍
|
|
||||||
if not contest_submission.ac:
|
|
||||||
# 这种情况是这个题目前处于错误状态,就使用已经存储了的罚时加上这道题的实际用时
|
|
||||||
contest_submission.ac_time = int((submission.create_time - contest.start_time).total_seconds())
|
|
||||||
contest_submission.total_time += contest_submission.ac_time
|
|
||||||
contest_submission.total_submission_number += 1
|
|
||||||
# 标记为已经通过
|
|
||||||
if contest_problem.total_accepted_number == 0:
|
|
||||||
contest_submission.first_achieved = True
|
|
||||||
contest_submission.ac = True
|
|
||||||
# contest problem ac 计数器加1
|
|
||||||
contest_problem.total_accepted_number += 1
|
|
||||||
else:
|
|
||||||
# 如果这个提交是错误的,就罚时20分钟
|
|
||||||
contest_submission.total_time += 1200
|
|
||||||
contest_submission.total_submission_number += 1
|
|
||||||
contest_submission.save()
|
|
||||||
contest_problem.save()
|
|
||||||
except ContestSubmission.DoesNotExist:
|
|
||||||
# 第一次提交
|
|
||||||
with transaction.atomic():
|
|
||||||
is_ac = submission.result == result["accepted"]
|
|
||||||
first_achieved = False
|
|
||||||
ac_time = 0
|
|
||||||
if is_ac:
|
|
||||||
ac_time = int((submission.create_time - contest.start_time).total_seconds())
|
|
||||||
total_time = int((submission.create_time - contest.start_time).total_seconds())
|
|
||||||
# 增加题目总的ac数计数器
|
|
||||||
if contest_problem.total_accepted_number == 0:
|
|
||||||
first_achieved = True
|
|
||||||
contest_problem.total_accepted_number += 1
|
contest_problem.total_accepted_number += 1
|
||||||
contest_problem.save()
|
contest_problem.save()
|
||||||
else:
|
|
||||||
# 没过罚时20分钟
|
problems_status = user.problems_status
|
||||||
total_time = 1200
|
problems_status["contest_problems"][str(contest_problem.id)] = 1
|
||||||
ContestSubmission.objects.create(user_id=submission.user_id, contest=contest, problem=contest_problem,
|
user.problems_status = problems_status
|
||||||
ac=is_ac, total_time=total_time, first_achieved=first_achieved,
|
user.save()
|
||||||
ac_time=ac_time)
|
|
||||||
|
|
||||||
logger.debug("Start message queue")
|
logger.debug("Start message queue")
|
||||||
MessageQueue().listen_task()
|
MessageQueue().listen_task()
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
___ _ _ __ _ _ _
|
||||||
|
/___\ _ __ | |(_) _ __ ___ \ \ _ _ __| | __ _ ___ | |__ _ _ __ _ __| | _ _
|
||||||
|
// //| '_ \ | || || '_ \ / _ \ \ \| | | | / _` | / _` | / _ \ | '_ \ | | | | / _` | / _` || | | |
|
||||||
|
/ \_// | | | || || || | | || __/ /\_/ /| |_| || (_| || (_| || __/ | |_) || |_| | | (_| || (_| || |_| |
|
||||||
|
\___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_|
|
||||||
|
|___/ |___/ |_|
|
||||||
|
https://github.com/QingdaoU/OnlineJudge
|
||||||
|
"""
|
20
problem/migrations/0008_auto_20150922_1702.py
Normal file
20
problem/migrations/0008_auto_20150922_1702.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('problem', '0007_remove_problem_last_update_time'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='problem',
|
||||||
|
name='description',
|
||||||
|
field=utils.models.RichTextField(),
|
||||||
|
),
|
||||||
|
]
|
@ -2,6 +2,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
|
from utils.models import RichTextField
|
||||||
|
|
||||||
|
|
||||||
class ProblemTag(models.Model):
|
class ProblemTag(models.Model):
|
||||||
@ -15,7 +16,7 @@ class AbstractProblem(models.Model):
|
|||||||
# 标题
|
# 标题
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
# 问题描述 HTML 格式
|
# 问题描述 HTML 格式
|
||||||
description = models.TextField()
|
description = RichTextField()
|
||||||
# 输入描述
|
# 输入描述
|
||||||
input_description = models.CharField(max_length=10000)
|
input_description = models.CharField(max_length=10000)
|
||||||
# 输出描述
|
# 输出描述
|
||||||
|
@ -305,15 +305,11 @@ def problem_list_page(request, page=1):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if request.user.is_authenticated():
|
|
||||||
problems_status = json.loads(request.user.problems_status)
|
|
||||||
else:
|
|
||||||
problems_status = {}
|
|
||||||
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
|
# 右侧标签列表 按照关联的题目的数量排序 排除题目数量为0的
|
||||||
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
|
tags = ProblemTag.objects.annotate(problem_number=Count("problem")).filter(problem_number__gt=0).order_by("-problem_number")
|
||||||
|
|
||||||
return render(request, "oj/problem/problem_list.html",
|
return render(request, "oj/problem/problem_list.html",
|
||||||
{"problems": current_page, "page": int(page),
|
{"problems": current_page, "page": int(page),
|
||||||
"previous_page": previous_page, "next_page": next_page,
|
"previous_page": previous_page, "next_page": next_page,
|
||||||
"keyword": keyword, "tag": tag_text,"problems_status": problems_status,
|
"keyword": keyword, "tag": tag_text,
|
||||||
"tags": tags, "difficulty_order": difficulty_order})
|
"tags": tags, "difficulty_order": difficulty_order})
|
||||||
|
@ -218,7 +218,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
description: problem.description,
|
description: problem.description,
|
||||||
time_limit: problem.time_limit,
|
time_limit: problem.time_limit,
|
||||||
memory_limit: problem.memory_limit,
|
memory_limit: problem.memory_limit,
|
||||||
samples: problem.samples,
|
samples: [],
|
||||||
test_case_id: problem.test_case_id,
|
test_case_id: problem.test_case_id,
|
||||||
hint: problem.hint,
|
hint: problem.hint,
|
||||||
source: problem.contest.title,
|
source: problem.contest.title,
|
||||||
@ -228,6 +228,9 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
|
|||||||
output_description: problem.output_description,
|
output_description: problem.output_description,
|
||||||
difficulty: 0
|
difficulty: 0
|
||||||
};
|
};
|
||||||
|
for (var i = 0; i < problem.samples.length; i++) {
|
||||||
|
ajaxData.samples.push({input: problem.samples[i].input, output: problem.samples[i].output})
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
beforeSend: csrfTokenHeader,
|
beforeSend: csrfTokenHeader,
|
||||||
url: "/api/admin/problem/",
|
url: "/api/admin/problem/",
|
||||||
|
@ -77,20 +77,20 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
|
|||||||
});
|
});
|
||||||
if (avalon.vmodels.editProblem) {
|
if (avalon.vmodels.editProblem) {
|
||||||
var vm = avalon.vmodels.editProblem;
|
var vm = avalon.vmodels.editProblem;
|
||||||
title: "",
|
vm.title= "",
|
||||||
description= "";
|
vm.description= "";
|
||||||
timeLimit= -1;
|
vm.timeLimit= -1;
|
||||||
memoryLimit= -1;
|
vm.memoryLimit= -1;
|
||||||
samples= [];
|
vm.samples= [];
|
||||||
hint= "";
|
vm.hint= "";
|
||||||
visible= true;
|
vm.visible= true;
|
||||||
difficulty= 0;
|
vm.difficulty= 0;
|
||||||
inputDescription= "";
|
vm.inputDescription= "";
|
||||||
outputDescription= "";
|
vm.outputDescription= "";
|
||||||
testCaseIdd= "";
|
vm.testCaseIdd= "";
|
||||||
uploadSuccess= false;
|
vm.uploadSuccess= false;
|
||||||
source= "";
|
vm.source= "";
|
||||||
testCaseList= [];
|
vm.testCaseList= [];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
var vm = avalon.define({
|
var vm = avalon.define({
|
||||||
|
@ -10,8 +10,8 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from judge.judger_controller.tasks import judge
|
from judge.judger_controller.tasks import judge
|
||||||
from judge.judger_controller.settings import redis_config
|
from judge.judger_controller.settings import redis_config
|
||||||
from account.decorators import login_required
|
from account.decorators import login_required, super_admin_required
|
||||||
from account.models import SUPER_ADMIN, User
|
from account.models import SUPER_ADMIN, User, REGULAR_USER
|
||||||
|
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
from contest.models import ContestProblem, Contest
|
from contest.models import ContestProblem, Contest
|
||||||
@ -53,9 +53,11 @@ class SubmissionAPIView(APIView):
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
return error_response(u"提交判题任务失败")
|
return error_response(u"提交判题任务失败")
|
||||||
# 修改用户解题状态
|
# 修改用户解题状态
|
||||||
problems_status = json.loads(request.user.problems_status)
|
problems_status = request.user.problems_status
|
||||||
problems_status[str(data["problem_id"])] = 2
|
if "problems" not in problems_status:
|
||||||
request.user.problems_status = json.dumps(problems_status)
|
problems_status["problems"] = {}
|
||||||
|
problems_status["problems"][str(data["problem_id"])] = 2
|
||||||
|
request.user.problems_status = problems_status
|
||||||
request.user.save()
|
request.user.save()
|
||||||
# 增加redis 中判题队列长度的计数器
|
# 增加redis 中判题队列长度的计数器
|
||||||
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"])
|
||||||
@ -104,13 +106,16 @@ def _get_submission(submission_id, user):
|
|||||||
"""
|
"""
|
||||||
submission = Submission.objects.get(id=submission_id)
|
submission = Submission.objects.get(id=submission_id)
|
||||||
# 超级管理员或者提交者自己或者是一个分享的提交
|
# 超级管理员或者提交者自己或者是一个分享的提交
|
||||||
if user.admin_type == SUPER_ADMIN or submission.user_id == user.id or submission.shared:
|
if user.admin_type == SUPER_ADMIN or submission.user_id == user.id:
|
||||||
return submission
|
return {"submission": submission, "can_share": True}
|
||||||
if submission.contest_id:
|
if submission.contest_id:
|
||||||
contest = Contest.objects.get(id=submission.contest_id)
|
contest = Contest.objects.get(id=submission.contest_id)
|
||||||
# 比赛提交的话,比赛创建者也可见
|
# 比赛提交的话,比赛创建者也可见
|
||||||
if contest.created_by == user:
|
if contest.created_by == user:
|
||||||
return submission
|
return {"submission": submission, "can_share": True}
|
||||||
|
if submission.shared:
|
||||||
|
return {"submission": submission, "can_share": False}
|
||||||
|
else:
|
||||||
raise Submission.DoesNotExist
|
raise Submission.DoesNotExist
|
||||||
|
|
||||||
|
|
||||||
@ -120,7 +125,8 @@ def my_submission(request, submission_id):
|
|||||||
单个题目的提交详情页
|
单个题目的提交详情页
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
submission = _get_submission(submission_id, request.user)
|
result = _get_submission(submission_id, request.user)
|
||||||
|
submission = result["submission"]
|
||||||
except Submission.DoesNotExist:
|
except Submission.DoesNotExist:
|
||||||
return error_page(request, u"提交不存在")
|
return error_page(request, u"提交不存在")
|
||||||
|
|
||||||
@ -143,8 +149,10 @@ def my_submission(request, submission_id):
|
|||||||
info = submission.info
|
info = submission.info
|
||||||
else:
|
else:
|
||||||
info = None
|
info = None
|
||||||
|
user = User.objects.get(id=submission.user_id)
|
||||||
return render(request, "oj/problem/my_submission.html",
|
return render(request, "oj/problem/my_submission.html",
|
||||||
{"submission": submission, "problem": problem, "info": info})
|
{"submission": submission, "problem": problem, "info": info,
|
||||||
|
"user": user, "can_share": result["can_share"]})
|
||||||
|
|
||||||
|
|
||||||
class SubmissionAdminAPIView(APIView):
|
class SubmissionAdminAPIView(APIView):
|
||||||
@ -222,9 +230,12 @@ class SubmissionShareAPIView(APIView):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
submission_id = serializer.data["submission_id"]
|
submission_id = serializer.data["submission_id"]
|
||||||
try:
|
try:
|
||||||
submission = _get_submission(submission_id, request.user)
|
result = _get_submission(submission_id, request.user)
|
||||||
except Submission.DoesNotExist:
|
except Submission.DoesNotExist:
|
||||||
return error_response(u"提交不存在")
|
return error_response(u"提交不存在")
|
||||||
|
if not result["can_share"]:
|
||||||
|
return error_page(request, u"提交不存在")
|
||||||
|
submission = result["submission"]
|
||||||
submission.shared = not submission.shared
|
submission.shared = not submission.shared
|
||||||
submission.save()
|
submission.save()
|
||||||
return success_response(submission.shared)
|
return success_response(submission.shared)
|
||||||
@ -233,6 +244,7 @@ class SubmissionShareAPIView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class SubmissionRejudgeAdminAPIView(APIView):
|
class SubmissionRejudgeAdminAPIView(APIView):
|
||||||
|
@super_admin_required
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = SubmissionRejudgeSerializer(data=request.data)
|
serializer = SubmissionRejudgeSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
|
@ -44,20 +44,7 @@
|
|||||||
{% for item in contest_problems %}
|
{% for item in contest_problems %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
<span class="{% get_problem_status request.user.problems_status.contest_problems item.id %}"></span>
|
||||||
<span class="glyphicon
|
|
||||||
{% if item.state %}
|
|
||||||
{% ifequal item.state 1%}
|
|
||||||
glyphicon-ok ac-flag
|
|
||||||
{% else %}
|
|
||||||
glyphicon-minus dealing-flag
|
|
||||||
{% endifequal %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"></span>
|
|
||||||
|
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
|
<a href="/contest/{{ item.contest.id }}/problem/{{ item.id }}/">{{ item.sort_index }}</a>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
比赛排名
|
比赛排名
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% load contest %}
|
||||||
<div class="container main">
|
<div class="container main">
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-google contest-tab">
|
<ul class="nav nav-tabs nav-tabs-google contest-tab">
|
||||||
@ -23,13 +24,13 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h2 class="text-center">排名(
|
<h2 class="text-center">排名(
|
||||||
{% if real_time_rank %}
|
{% if contest.real_time_rank %}
|
||||||
实时
|
实时
|
||||||
{% else %}
|
{% else %}
|
||||||
已封榜
|
已封榜
|
||||||
{% endif %})
|
{% endif %})
|
||||||
</h2>
|
</h2>
|
||||||
{% if result %}
|
{% if rank %}
|
||||||
<table class="table table-bordered text-center">
|
<table class="table table-bordered text-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -45,24 +46,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="rank">
|
<tbody class="rank">
|
||||||
{% for item in result %}
|
{% for item in rank %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ forloop.counter }}</th>
|
<th scope="row">{{ forloop.counter }}</th>
|
||||||
<td>
|
<td>
|
||||||
{{ item.username }}
|
{{ item.user.username }}
|
||||||
{% if show_real_name %}
|
{% if show_real_name %}
|
||||||
({{ item.real_name }})
|
({{ item.real_name }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.total_ac }} / {{ item.total_submit }}</td>
|
<td>{{ item.total_ac_number }} / {{ item.total_submission_number }}</td>
|
||||||
<td>{% if item.total_time %}{{ item.total_time }}{% else %}--{% endif %}</td>
|
<td>{% if item.total_time %}{{ item.total_time|format_seconds }}{% else %}--{% endif %}</td>
|
||||||
{% for problem in item.problems %}
|
{% for problem in contest_problems %}
|
||||||
<td class="
|
<td class="{% get_submission_class item problem %}">
|
||||||
{% if problem %}{% if problem.ac %}{% if problem.first_achieved %}first-achieved{% else %}alert-success{% endif %}{% else %}alert-danger{% endif %}{% endif %}">
|
{% get_submission_content item problem %}
|
||||||
{% if problem.ac %}{{ problem.ac_time }}{% endif %}
|
|
||||||
{% if problem.failed_number %}
|
|
||||||
(-{{ problem.failed_number }})
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
{{ submission.result|translate_result }}
|
{{ submission.result|translate_result }}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
{% ifnotequal request.user.id submission.user_id %}
|
||||||
|
<p>作者:{{ user.username }}</p>
|
||||||
|
{% endifnotequal %}
|
||||||
{% ifequal submission.result 0 %}
|
{% ifequal submission.result 0 %}
|
||||||
<p>时间 : {{ submission.accepted_answer_time }}ms 语言 :
|
<p>时间 : {{ submission.accepted_answer_time }}ms 语言 :
|
||||||
{{ submission.language|translate_language }}
|
{{ submission.language|translate_language }}
|
||||||
@ -65,7 +68,7 @@
|
|||||||
<div id="code-field">
|
<div id="code-field">
|
||||||
<textarea id="code-editor">{{ submission.code }}</textarea>
|
<textarea id="code-editor">{{ submission.code }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
{% if can_share %}
|
||||||
<div id="share-code" class="col-lg-6 col-md-6">
|
<div id="share-code" class="col-lg-6 col-md-6">
|
||||||
{% if submission.shared %}
|
{% if submission.shared %}
|
||||||
<button class="btn btn-warning" id="share-code-btn">取消分享</button>
|
<button class="btn btn-warning" id="share-code-btn">取消分享</button>
|
||||||
@ -76,6 +79,7 @@
|
|||||||
{% if not submission.shared %}style="display: none" {% endif %}>{{ problem.title }}
|
{% if not submission.shared %}style="display: none" {% endif %}>{{ problem.title }}
|
||||||
{{ request.build_absolute_uri }}</textarea>
|
{{ request.build_absolute_uri }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for item in problems %}
|
{% for item in problems %}
|
||||||
<tr>
|
<tr>
|
||||||
<th><span class="{% get_problem_status problems_status item.id %}"></span></th>
|
<th><span class="{% get_problem_status request.user.problems_status.problems item.id %}"></span></th>
|
||||||
<th scope="row"><a href="/problem/{{ item.id }}/">{{ item.id }}</a></th>
|
<th scope="row"><a href="/problem/{{ item.id }}/">{{ item.id }}</a></th>
|
||||||
<td><a href="/problem/{{ item.id }}/">{{ item.title }}</a></td>
|
<td><a href="/problem/{{ item.id }}/">{{ item.title }}</a></td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -23,6 +23,7 @@ inspect_redis = json.loads(os.popen("docker inspect redis").read())
|
|||||||
if not inspect_redis:
|
if not inspect_redis:
|
||||||
print "Error when inspect redis ip"
|
print "Error when inspect redis ip"
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
redis_ip = inspect_redis[0]["NetworkSettings"]["IPAddress"]
|
redis_ip = inspect_redis[0]["NetworkSettings"]["IPAddress"]
|
||||||
print "redis ip ", redis_ip
|
print "redis ip ", redis_ip
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ inspect_mysql = json.loads(os.popen("docker inspect mysql").read())
|
|||||||
if not inspect_mysql:
|
if not inspect_mysql:
|
||||||
print "Error when inspect mysql ip"
|
print "Error when inspect mysql ip"
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
mysql_ip = inspect_mysql[0]["NetworkSettings"]["IPAddress"]
|
mysql_ip = inspect_mysql[0]["NetworkSettings"]["IPAddress"]
|
||||||
print "mysql ip ", mysql_ip
|
print "mysql ip ", mysql_ip
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ for line in f.readlines():
|
|||||||
content += ("\nexport submission_db_host=" + mysql_ip + "\n")
|
content += ("\nexport submission_db_host=" + mysql_ip + "\n")
|
||||||
else:
|
else:
|
||||||
content += line
|
content += line
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
21
utils/models.py
Normal file
21
utils/models.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import json
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from utils.xss_filter import XssHtml
|
||||||
|
|
||||||
|
|
||||||
|
class RichTextField(models.TextField):
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
|
||||||
|
def get_prep_value(self, value):
|
||||||
|
if not value:
|
||||||
|
value = ""
|
||||||
|
parser = XssHtml()
|
||||||
|
parser.feed(value)
|
||||||
|
parser.close()
|
||||||
|
return parser.getHtml()
|
||||||
|
|
||||||
|
|
||||||
|
class JsonField(models.TextField):
|
||||||
|
pass
|
@ -100,5 +100,5 @@ def paginate(request, query_set, object_serializer):
|
|||||||
|
|
||||||
|
|
||||||
def rand_str(length=32):
|
def rand_str(length=32):
|
||||||
string = hashlib.md5(str(time.time()) + str(random.randrange(1, 9999999900))).hexdigest()
|
string = hashlib.md5(str(time.time()) + str(random.randrange(1, 987654321234567))).hexdigest()
|
||||||
return string[0:length]
|
return string[0:length]
|
@ -23,9 +23,50 @@ def get_contest_status_color(contest):
|
|||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
|
|
||||||
|
def get_the_formatted_time(seconds):
|
||||||
|
if not seconds:
|
||||||
|
return ""
|
||||||
|
seconds = int(seconds)
|
||||||
|
hour = seconds / (60 * 60)
|
||||||
|
minute = (seconds - hour * 60 * 60) / 60
|
||||||
|
second = seconds - hour * 60 * 60 - minute * 60
|
||||||
|
return str(hour) + ":" + str(minute) + ":" + str(second)
|
||||||
|
|
||||||
|
|
||||||
|
def get_submission_class(rank, problem):
|
||||||
|
if str(problem.id) not in rank.submission_info:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
submission = rank.submission_info[str(problem.id)]
|
||||||
|
if submission["is_ac"]:
|
||||||
|
_class = "alert-success"
|
||||||
|
if submission["is_first_ac"]:
|
||||||
|
_class += " first-achieved"
|
||||||
|
else:
|
||||||
|
_class = "alert-danger"
|
||||||
|
return _class
|
||||||
|
|
||||||
|
|
||||||
|
def get_submission_content(rank, problem):
|
||||||
|
if str(problem.id) not in rank.submission_info:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
submission = rank.submission_info[str(problem.id)]
|
||||||
|
if submission["is_ac"]:
|
||||||
|
r = get_the_formatted_time(submission["ac_time"])
|
||||||
|
if submission["error_number"]:
|
||||||
|
r += "(-" + str(submission["error_number"]) + ")"
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
return "(-" + str(submission["error_number"]) + ")"
|
||||||
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
register.filter("contest_status", get_contest_status)
|
register.filter("contest_status", get_contest_status)
|
||||||
register.filter("contest_status_color", get_contest_status_color)
|
register.filter("contest_status_color", get_contest_status_color)
|
||||||
|
register.filter("format_seconds", get_the_formatted_time)
|
||||||
|
register.simple_tag(get_submission_class, name="get_submission_class")
|
||||||
|
register.simple_tag(get_submission_content, name="get_submission_content")
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ def get_problem_accepted_radio(problem):
|
|||||||
|
|
||||||
|
|
||||||
def get_problem_status(problems_status, problem_id):
|
def get_problem_status(problems_status, problem_id):
|
||||||
|
# 用户没登陆 或者 user.problem_status 中没有这个字段都会到导致这里的problem_status 为 ""
|
||||||
|
if not problems_status:
|
||||||
|
return ""
|
||||||
|
|
||||||
if str(problem_id) in problems_status:
|
if str(problem_id) in problems_status:
|
||||||
if problems_status[str(problem_id)] == 1:
|
if problems_status[str(problem_id)] == 1:
|
||||||
@ -16,6 +19,7 @@ def get_problem_status(problems_status, problem_id):
|
|||||||
return "glyphicon glyphicon-minus dealing-flag"
|
return "glyphicon glyphicon-minus dealing-flag"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
202
utils/xss_filter.py
Normal file
202
utils/xss_filter.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Python 富文本XSS过滤类
|
||||||
|
@package XssHtml
|
||||||
|
@version 0.1
|
||||||
|
@link http://phith0n.github.io/python-xss-filter
|
||||||
|
@since 20150407
|
||||||
|
@copyright (c) Phithon All Rights Reserved
|
||||||
|
|
||||||
|
Based on native Python module HTMLParser purifier of HTML, To Clear all javascript in html
|
||||||
|
You can use it in all python web framework
|
||||||
|
Written by Phithon <root@leavesongs.com> in 2015 and placed in the public domain.
|
||||||
|
phithon <root@leavesongs.com> 编写于20150407
|
||||||
|
From: XDSEC <www.xdsec.org> & 离别歌 <www.leavesongs.com>
|
||||||
|
GitHub Pages: https://github.com/phith0n/python-xss-filter
|
||||||
|
Usage:
|
||||||
|
parser = XssHtml()
|
||||||
|
parser.feed('<html code>')
|
||||||
|
parser.close()
|
||||||
|
html = parser.getHtml()
|
||||||
|
print html
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
Python 2.6+ or 3.2+
|
||||||
|
Cannot defense xss in browser which is belowed IE7
|
||||||
|
浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
except:
|
||||||
|
from HTMLParser import HTMLParser
|
||||||
|
|
||||||
|
|
||||||
|
class XssHtml(HTMLParser):
|
||||||
|
allow_tags = ['a', 'img', 'br', 'strong', 'b', 'code', 'pre',
|
||||||
|
'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4',
|
||||||
|
'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td',
|
||||||
|
'hr', 'li', 'u', 'embed', 's', 'table', 'thead', 'tbody',
|
||||||
|
'caption', 'small', 'q', 'sup', 'sub']
|
||||||
|
common_attrs = ["style", "class", "name"]
|
||||||
|
nonend_tags = ["img", "hr", "br", "embed"]
|
||||||
|
tags_own_attrs = {
|
||||||
|
"img": ["src", "width", "height", "alt", "align"],
|
||||||
|
"a": ["href", "target", "rel", "title"],
|
||||||
|
"embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"],
|
||||||
|
"table": ["border", "cellpadding", "cellspacing"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, allows=[]):
|
||||||
|
HTMLParser.__init__(self)
|
||||||
|
self.allow_tags = allows if allows else self.allow_tags
|
||||||
|
self.result = []
|
||||||
|
self.start = []
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def getHtml(self):
|
||||||
|
"""
|
||||||
|
Get the safe html code
|
||||||
|
"""
|
||||||
|
for i in range(0, len(self.result)):
|
||||||
|
tmp = self.result[i].rstrip('\n')
|
||||||
|
tmp = tmp.lstrip('\n')
|
||||||
|
if tmp:
|
||||||
|
self.data.append(tmp)
|
||||||
|
return ''.join(self.data)
|
||||||
|
|
||||||
|
def handle_startendtag(self, tag, attrs):
|
||||||
|
self.handle_starttag(tag, attrs)
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
if tag not in self.allow_tags:
|
||||||
|
return
|
||||||
|
end_diagonal = ' /' if tag in self.nonend_tags else ''
|
||||||
|
if not end_diagonal:
|
||||||
|
self.start.append(tag)
|
||||||
|
attdict = {}
|
||||||
|
for attr in attrs:
|
||||||
|
attdict[attr[0]] = attr[1]
|
||||||
|
|
||||||
|
attdict = self._wash_attr(attdict, tag)
|
||||||
|
if hasattr(self, "node_%s" % tag):
|
||||||
|
attdict = getattr(self, "node_%s" % tag)(attdict)
|
||||||
|
else:
|
||||||
|
attdict = self.node_default(attdict)
|
||||||
|
|
||||||
|
attrs = []
|
||||||
|
for (key, value) in attdict.items():
|
||||||
|
attrs.append('%s="%s"' % (key, self._htmlspecialchars(value)))
|
||||||
|
attrs = (' ' + ' '.join(attrs)) if attrs else ''
|
||||||
|
self.result.append('<' + tag + attrs + end_diagonal + '>')
|
||||||
|
|
||||||
|
def handle_endtag(self, tag):
|
||||||
|
if self.start and tag == self.start[len(self.start) - 1]:
|
||||||
|
self.result.append('</' + tag + '>')
|
||||||
|
self.start.pop()
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
self.result.append(self._htmlspecialchars(data))
|
||||||
|
|
||||||
|
def handle_entityref(self, name):
|
||||||
|
if name.isalpha():
|
||||||
|
self.result.append("&%s;" % name)
|
||||||
|
|
||||||
|
def handle_charref(self, name):
|
||||||
|
if name.isdigit():
|
||||||
|
self.result.append("&#%s;" % name)
|
||||||
|
|
||||||
|
def node_default(self, attrs):
|
||||||
|
attrs = self._common_attr(attrs)
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def node_a(self, attrs):
|
||||||
|
attrs = self._common_attr(attrs)
|
||||||
|
attrs = self._get_link(attrs, "href")
|
||||||
|
attrs = self._set_attr_default(attrs, "target", "_blank")
|
||||||
|
attrs = self._limit_attr(attrs, {
|
||||||
|
"target": ["_blank", "_self"]
|
||||||
|
})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def node_embed(self, attrs):
|
||||||
|
attrs = self._common_attr(attrs)
|
||||||
|
attrs = self._get_link(attrs, "src")
|
||||||
|
attrs = self._limit_attr(attrs, {
|
||||||
|
"type": ["application/x-shockwave-flash"],
|
||||||
|
"wmode": ["transparent", "window", "opaque"],
|
||||||
|
"play": ["true", "false"],
|
||||||
|
"loop": ["true", "false"],
|
||||||
|
"menu": ["true", "false"],
|
||||||
|
"allowfullscreen": ["true", "false"]
|
||||||
|
})
|
||||||
|
attrs["allowscriptaccess"] = "never"
|
||||||
|
attrs["allownetworking"] = "none"
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _true_url(self, url):
|
||||||
|
prog = re.compile(r"^(http|https|ftp)://.+", re.I | re.S)
|
||||||
|
if prog.match(url):
|
||||||
|
return url
|
||||||
|
else:
|
||||||
|
return "http://%s" % url
|
||||||
|
|
||||||
|
def _true_style(self, style):
|
||||||
|
if style:
|
||||||
|
style = re.sub(r"(\\|&#|/\*|\*/)", "_", style)
|
||||||
|
style = re.sub(r"e.*x.*p.*r.*e.*s.*s.*i.*o.*n", "_", style)
|
||||||
|
return style
|
||||||
|
|
||||||
|
def _get_style(self, attrs):
|
||||||
|
if "style" in attrs:
|
||||||
|
attrs["style"] = self._true_style(attrs.get("style"))
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _get_link(self, attrs, name):
|
||||||
|
if name in attrs:
|
||||||
|
attrs[name] = self._true_url(attrs[name])
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _wash_attr(self, attrs, tag):
|
||||||
|
if tag in self.tags_own_attrs:
|
||||||
|
other = self.tags_own_attrs.get(tag)
|
||||||
|
else:
|
||||||
|
other = []
|
||||||
|
if attrs:
|
||||||
|
for (key, value) in attrs.items():
|
||||||
|
if key not in self.common_attrs + other:
|
||||||
|
del attrs[key]
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _common_attr(self, attrs):
|
||||||
|
attrs = self._get_style(attrs)
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _set_attr_default(self, attrs, name, default=''):
|
||||||
|
if name not in attrs:
|
||||||
|
attrs[name] = default
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _limit_attr(self, attrs, limit={}):
|
||||||
|
for (key, value) in limit.items():
|
||||||
|
if key in attrs and attrs[key] not in value:
|
||||||
|
del attrs[key]
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def _htmlspecialchars(self, html):
|
||||||
|
return html.replace("<", "<") \
|
||||||
|
.replace(">", ">") \
|
||||||
|
.replace('"', """) \
|
||||||
|
.replace("'", "'")
|
||||||
|
|
||||||
|
|
||||||
|
if "__main__" == __name__:
|
||||||
|
parser = XssHtml()
|
||||||
|
parser.feed("""<p><img src=1 onerror=alert(/xss/)></p><div class="left">
|
||||||
|
<a href='javascript:prompt(1)'><br />hehe</a></div>
|
||||||
|
<p id="test" onmouseover="alert(1)">>M<svg>
|
||||||
|
<a href="https://www.baidu.com" target="self">MM</a></p>
|
||||||
|
<embed src='javascript:alert(/hehe/)' allowscriptaccess=always />""")
|
||||||
|
parser.close()
|
||||||
|
print(parser.getHtml())
|
Loading…
Reference in New Issue
Block a user