mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 16:33:22 +00:00
Merge branch 'debug' of https://git.coding.net/virusdefender/qduoj into debug
# Via virusdefender (2) and sxw (1) * 'debug' of https://git.coding.net/virusdefender/qduoj: add oj text logo 增加富文本的 xss 过滤 增加 demo 网址 修补部分没有判断比赛权限的问题;增加比赛调试模式的页面提示 增加部分注释,修补部分没有判断权限的问题 增加判断管理员权限的 decorator 只有管理员才能查看所有人的提交
This commit is contained in:
commit
2957d7f716
@ -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]
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from django.http import HttpResponse, HttpResponseRedirect
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from utils.shortcuts import error_response, error_page
|
from utils.shortcuts import error_response, error_page
|
||||||
from .models import User
|
from .models import User, SUPER_ADMIN
|
||||||
|
|
||||||
|
|
||||||
def login_required(func):
|
def login_required(func):
|
||||||
@ -12,7 +12,10 @@ def login_required(func):
|
|||||||
def check(*args, **kwargs):
|
def check(*args, **kwargs):
|
||||||
# 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
|
# 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
|
||||||
# 在function based views 里面,args 只有request 一个参数
|
# 在function based views 里面,args 只有request 一个参数
|
||||||
|
if len(args) == 2:
|
||||||
request = args[-1]
|
request = args[-1]
|
||||||
|
else:
|
||||||
|
request = args[0]
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
if request.is_ajax():
|
if request.is_ajax():
|
||||||
@ -25,7 +28,10 @@ def login_required(func):
|
|||||||
def admin_required(func):
|
def admin_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def check(*args, **kwargs):
|
def check(*args, **kwargs):
|
||||||
|
if len(args) == 2:
|
||||||
request = args[-1]
|
request = args[-1]
|
||||||
|
else:
|
||||||
|
request = args[0]
|
||||||
if request.user.is_authenticated() and request.user.admin_type:
|
if request.user.is_authenticated() and request.user.admin_type:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
if request.is_ajax():
|
if request.is_ajax():
|
||||||
@ -33,3 +39,20 @@ def admin_required(func):
|
|||||||
else:
|
else:
|
||||||
return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
|
return error_page(request, u"需要管理员权限,如果没有登录,请先登录")
|
||||||
return check
|
return check
|
||||||
|
|
||||||
|
|
||||||
|
def super_admin_required(func):
|
||||||
|
@wraps(func)
|
||||||
|
def check(*args, **kwargs):
|
||||||
|
if len(args) == 2:
|
||||||
|
request = args[-1]
|
||||||
|
else:
|
||||||
|
request = args[0]
|
||||||
|
if request.user.is_authenticated() and request.user.admin_type == SUPER_ADMIN:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
if request.is_ajax():
|
||||||
|
return error_response(u"需要超级管理员权限")
|
||||||
|
else:
|
||||||
|
return error_page(request, u"需要超级管理员权限")
|
||||||
|
|
||||||
|
return check
|
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(),
|
||||||
|
),
|
||||||
|
]
|
@ -5,6 +5,8 @@ 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
|
||||||
|
|
||||||
|
|
||||||
GROUP_CONTEST = 0
|
GROUP_CONTEST = 0
|
||||||
PUBLIC_CONTEST = 1
|
PUBLIC_CONTEST = 1
|
||||||
@ -17,7 +19,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()
|
||||||
# 是否显示实时排名结果
|
# 是否显示实时排名结果
|
||||||
|
@ -16,7 +16,7 @@ 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
|
from .models import Contest, ContestProblem, ContestSubmission, CONTEST_ENDED, CONTEST_NOT_START, CONTEST_UNDERWAY
|
||||||
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,
|
||||||
@ -45,10 +45,10 @@ class ContestAdminAPIView(APIView):
|
|||||||
if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]:
|
if data["contest_type"] in [PUBLIC_CONTEST, PASSWORD_PROTECTED_CONTEST]:
|
||||||
if request.user.admin_type != SUPER_ADMIN:
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
return error_response(u"只有超级管理员才可创建公开赛")
|
return error_response(u"只有超级管理员才可创建公开赛")
|
||||||
|
|
||||||
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
if data["contest_type"] == PASSWORD_PROTECTED_CONTEST:
|
||||||
if not data["password"]:
|
if not data["password"]:
|
||||||
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
return error_response(u"此比赛为有密码的公开赛,密码不可为空")
|
||||||
|
|
||||||
# 没有密码的公开赛 没有密码的小组赛
|
# 没有密码的公开赛 没有密码的小组赛
|
||||||
elif data["contest_type"] == GROUP_CONTEST:
|
elif data["contest_type"] == GROUP_CONTEST:
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
if request.user.admin_type == SUPER_ADMIN:
|
||||||
@ -58,7 +58,7 @@ class ContestAdminAPIView(APIView):
|
|||||||
if not groups.count():
|
if not groups.count():
|
||||||
return error_response(u"请至少选择一个小组")
|
return error_response(u"请至少选择一个小组")
|
||||||
if data["start_time"] >= data["end_time"]:
|
if data["start_time"] >= data["end_time"]:
|
||||||
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间")
|
return error_response(u"比赛的开始时间必须早于比赛结束的时间")
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.create(title=data["title"], description=data["description"],
|
contest = Contest.objects.create(title=data["title"], description=data["description"],
|
||||||
mode=data["mode"], contest_type=data["contest_type"],
|
mode=data["mode"], contest_type=data["contest_type"],
|
||||||
@ -86,7 +86,10 @@ class ContestAdminAPIView(APIView):
|
|||||||
data = serializer.data
|
data = serializer.data
|
||||||
groups = []
|
groups = []
|
||||||
try:
|
try:
|
||||||
|
# 超级管理员可以编辑所有的
|
||||||
contest = Contest.objects.get(id=data["id"])
|
contest = Contest.objects.get(id=data["id"])
|
||||||
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
|
contest = contest.get(created_by=request.user)
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return error_response(u"该比赛不存在!")
|
return error_response(u"该比赛不存在!")
|
||||||
try:
|
try:
|
||||||
@ -109,9 +112,8 @@ class ContestAdminAPIView(APIView):
|
|||||||
if not groups.count():
|
if not groups.count():
|
||||||
return error_response(u"请至少选择一个小组")
|
return error_response(u"请至少选择一个小组")
|
||||||
if data["start_time"] >= data["end_time"]:
|
if data["start_time"] >= data["end_time"]:
|
||||||
return error_response(u"比赛的开始时间不能晚于或等于比赛结束的时间")
|
return error_response(u"比赛的开始时间必须早于比赛结束的时间")
|
||||||
if request.user.admin_type != SUPER_ADMIN and request.user != contest.created_by:
|
|
||||||
return error_response(u"你无权修改该比赛!")
|
|
||||||
contest.title = data["title"]
|
contest.title = data["title"]
|
||||||
contest.description = data["description"]
|
contest.description = data["description"]
|
||||||
contest.mode = data["mode"]
|
contest.mode = data["mode"]
|
||||||
@ -163,6 +165,8 @@ class ContestProblemAdminAPIView(APIView):
|
|||||||
data = serializer.data
|
data = serializer.data
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=data["contest_id"])
|
contest = Contest.objects.get(id=data["contest_id"])
|
||||||
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
|
contest = contest.get(created_by=request.user)
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return error_response(u"比赛不存在")
|
return error_response(u"比赛不存在")
|
||||||
contest_problem = ContestProblem.objects.create(title=data["title"],
|
contest_problem = ContestProblem.objects.create(title=data["title"],
|
||||||
@ -199,7 +203,7 @@ class ContestProblemAdminAPIView(APIView):
|
|||||||
return error_response(u"该比赛题目不存在!")
|
return error_response(u"该比赛题目不存在!")
|
||||||
contest = Contest.objects.get(id=contest_problem.contest_id)
|
contest = Contest.objects.get(id=contest_problem.contest_id)
|
||||||
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
|
||||||
return error_response(u"你无权修改该题目!")
|
return error_response(u"比赛不存在")
|
||||||
contest_problem.title = data["title"]
|
contest_problem.title = data["title"]
|
||||||
contest_problem.description = data["description"]
|
contest_problem.description = data["description"]
|
||||||
contest_problem.input_description = data["input_description"]
|
contest_problem.input_description = data["input_description"]
|
||||||
@ -227,29 +231,27 @@ class ContestProblemAdminAPIView(APIView):
|
|||||||
if contest_problem_id:
|
if contest_problem_id:
|
||||||
try:
|
try:
|
||||||
contest_problem = ContestProblem.objects.get(id=contest_problem_id)
|
contest_problem = ContestProblem.objects.get(id=contest_problem_id)
|
||||||
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
|
contest_problem = contest_problem.get(created_by=request.user)
|
||||||
return success_response(ContestProblemSerializer(contest_problem).data)
|
return success_response(ContestProblemSerializer(contest_problem).data)
|
||||||
except ContestProblem.DoesNotExist:
|
except ContestProblem.DoesNotExist:
|
||||||
return error_response(u"比赛题目不存在")
|
return error_response(u"比赛题目不存在")
|
||||||
if request.user.admin_type == SUPER_ADMIN:
|
|
||||||
contest_problem = ContestProblem.objects.all().order_by("sort_index")
|
contest_problems = ContestProblem.objects.all().order_by("sort_index")
|
||||||
else:
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
contest_problem = ContestProblem.objects.filter(created_by=request.user).order_by("sort_index")
|
contest_problems = contest_problems.filter(created_by=request.user).order_by("sort_index")
|
||||||
visible = request.GET.get("visible", None)
|
visible = request.GET.get("visible", None)
|
||||||
if visible:
|
if visible:
|
||||||
contest_problem = contest_problem.filter(visible=(visible == "true"))
|
contest_problems = contest_problems.filter(visible=(visible == "true"))
|
||||||
keyword = request.GET.get("keyword", None)
|
keyword = request.GET.get("keyword", None)
|
||||||
if keyword:
|
if keyword:
|
||||||
contest_problem = contest_problem.filter(Q(title__contains=keyword) |
|
contest_problems = contest_problems.filter(Q(title__contains=keyword) |
|
||||||
Q(description__contains=keyword))
|
Q(description__contains=keyword))
|
||||||
contest_id = request.GET.get("contest_id", None)
|
contest_id = request.GET.get("contest_id", None)
|
||||||
if contest_id:
|
if contest_id:
|
||||||
try:
|
contest_problem = contest_problems.filter(contest__id=contest_id).order_by("sort_index")
|
||||||
contest = Contest.objects.get(id=contest_id)
|
|
||||||
except Contest.DoesNotExist:
|
|
||||||
return error_response(u"该比赛不存在!")
|
|
||||||
contest_problem = contest_problem.filter(contest=contest).order_by("sort_index")
|
|
||||||
|
|
||||||
return paginate(request, contest_problem, ContestProblemSerializer)
|
return paginate(request, contest_problems, ContestProblemSerializer)
|
||||||
|
|
||||||
|
|
||||||
class ContestPasswordVerifyAPIView(APIView):
|
class ContestPasswordVerifyAPIView(APIView):
|
||||||
@ -306,12 +308,23 @@ def contest_problem_page(request, contest_id, contest_problem_id):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# 已经结束
|
# 已经结束
|
||||||
if contest.status == -1:
|
if contest.status == CONTEST_ENDED:
|
||||||
show_warning = True
|
show_warning = True
|
||||||
warning = u"比赛已经结束"
|
warning = u"比赛已经结束"
|
||||||
|
elif contest.status == CONTEST_NOT_START:
|
||||||
|
show_warning = True
|
||||||
|
warning = u"比赛没有开始,您是管理员,可以提交和测试题目,但是目前的提交不会计入排名。"
|
||||||
|
|
||||||
|
show_submit_code_area = False
|
||||||
|
if contest.status == CONTEST_UNDERWAY:
|
||||||
|
show_submit_code_area = True
|
||||||
|
if request.user.admin_type == SUPER_ADMIN or request.user == contest.created_by:
|
||||||
|
show_submit_code_area = True
|
||||||
|
|
||||||
return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest,
|
return render(request, "oj/contest/contest_problem.html", {"contest_problem": contest_problem, "contest": contest,
|
||||||
"samples": json.loads(contest_problem.samples),
|
"samples": json.loads(contest_problem.samples),
|
||||||
"show_warning": show_warning, "warning": warning})
|
"show_warning": show_warning, "warning": warning,
|
||||||
|
"show_submit_code_area": show_submit_code_area})
|
||||||
|
|
||||||
|
|
||||||
@check_user_contest_permission
|
@check_user_contest_permission
|
||||||
@ -319,10 +332,7 @@ def contest_problems_list_page(request, contest_id):
|
|||||||
"""
|
"""
|
||||||
比赛所有题目的列表页
|
比赛所有题目的列表页
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
contest = Contest.objects.get(id=contest_id)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
except Contest.DoesNotExist:
|
|
||||||
return error_page(request, u"比赛不存在")
|
|
||||||
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)
|
submissions = ContestSubmission.objects.filter(user=request.user, contest=contest)
|
||||||
state = {}
|
state = {}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
___ _ _ __ _ _ _
|
||||||
|
/___\ _ __ | |(_) _ __ ___ \ \ _ _ __| | __ _ ___ | |__ _ _ __ _ __| | _ _
|
||||||
|
// //| '_ \ | || || '_ \ / _ \ \ \| | | | / _` | / _` | / _ \ | '_ \ | | | | / _` | / _` || | | |
|
||||||
|
/ \_// | | | || || || | | || __/ /\_/ /| |_| || (_| || (_| || __/ | |_) || |_| | | (_| || (_| || |_| |
|
||||||
|
\___/ |_| |_||_||_||_| |_| \___| \___/ \__,_| \__,_| \__, | \___| |_.__/ \__, | \__, | \__,_| \__,_|
|
||||||
|
|___/ |___/ |_|
|
||||||
|
https://github.com/QingdaoU/OnlineJudge
|
||||||
|
"""
|
28
problem/decorators.py
Normal file
28
problem/decorators.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from account.models import SUPER_ADMIN
|
||||||
|
from utils.shortcuts import error_response
|
||||||
|
from .models import Problem
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_problem_permission(func):
|
||||||
|
@wraps(func)
|
||||||
|
def check(*args, **kwargs):
|
||||||
|
# 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
|
||||||
|
# 在function based views 里面,args 只有request 一个参数
|
||||||
|
if len(args) == 2:
|
||||||
|
request = args[-1]
|
||||||
|
else:
|
||||||
|
request = args[0]
|
||||||
|
|
||||||
|
# 这是在后台使用的url middleware 已经确保用户是登录状态的了
|
||||||
|
if request.user.admin_type == SUPER_ADMIN:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
Problem.objects.get(id=request.data.get("problem_id", -1), created_by=request.user)
|
||||||
|
except Problem.DoesNotExist:
|
||||||
|
return error_response(u"问题不存在")
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return check
|
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)
|
||||||
# 输出描述
|
# 输出描述
|
||||||
|
@ -4,6 +4,7 @@ import re
|
|||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count
|
||||||
@ -13,19 +14,23 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from account.models import SUPER_ADMIN
|
||||||
from announcement.models import Announcement
|
from account.decorators import super_admin_required
|
||||||
from utils.shortcuts import (serializer_invalid_response, error_response,
|
from utils.shortcuts import (serializer_invalid_response, error_response,
|
||||||
success_response, paginate, rand_str, error_page)
|
success_response, paginate, rand_str, error_page)
|
||||||
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
from .serizalizers import (CreateProblemSerializer, EditProblemSerializer, ProblemSerializer,
|
||||||
ProblemTagSerializer, CreateProblemTagSerializer)
|
ProblemTagSerializer, CreateProblemTagSerializer)
|
||||||
from .models import Problem, ProblemTag
|
from .models import Problem, ProblemTag
|
||||||
import logging
|
from .decorators import check_user_problem_permission
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("app_info")
|
logger = logging.getLogger("app_info")
|
||||||
|
|
||||||
|
|
||||||
def problem_page(request, problem_id):
|
def problem_page(request, problem_id):
|
||||||
|
"""
|
||||||
|
前台题目详情页
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id, visible=True)
|
problem = Problem.objects.get(id=problem_id, visible=True)
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
@ -34,11 +39,15 @@ def problem_page(request, problem_id):
|
|||||||
|
|
||||||
|
|
||||||
class ProblemTagAdminAPIView(APIView):
|
class ProblemTagAdminAPIView(APIView):
|
||||||
|
"""
|
||||||
|
获取所有标签的列表
|
||||||
|
"""
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data)
|
return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data)
|
||||||
|
|
||||||
|
|
||||||
class ProblemAdminAPIView(APIView):
|
class ProblemAdminAPIView(APIView):
|
||||||
|
@super_admin_required
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
题目发布json api接口
|
题目发布json api接口
|
||||||
@ -72,6 +81,7 @@ class ProblemAdminAPIView(APIView):
|
|||||||
else:
|
else:
|
||||||
return serializer_invalid_response(serializer)
|
return serializer_invalid_response(serializer)
|
||||||
|
|
||||||
|
@check_user_problem_permission
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
"""
|
"""
|
||||||
题目编辑json api接口
|
题目编辑json api接口
|
||||||
@ -82,11 +92,7 @@ class ProblemAdminAPIView(APIView):
|
|||||||
serializer = EditProblemSerializer(data=request.data)
|
serializer = EditProblemSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
try:
|
|
||||||
problem = Problem.objects.get(id=data["id"])
|
problem = Problem.objects.get(id=data["id"])
|
||||||
except Problem.DoesNotExist:
|
|
||||||
return error_response(u"该题目不存在!")
|
|
||||||
|
|
||||||
problem.title = data["title"]
|
problem.title = data["title"]
|
||||||
problem.description = data["description"]
|
problem.description = data["description"]
|
||||||
problem.input_description = data["input_description"]
|
problem.input_description = data["input_description"]
|
||||||
@ -123,23 +129,36 @@ class ProblemAdminAPIView(APIView):
|
|||||||
problem_id = request.GET.get("problem_id", None)
|
problem_id = request.GET.get("problem_id", None)
|
||||||
if problem_id:
|
if problem_id:
|
||||||
try:
|
try:
|
||||||
|
# 普通管理员只能获取自己创建的题目
|
||||||
|
# 超级管理员可以获取全部的题目
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
|
problem = problem.get(created_by=request.user)
|
||||||
return success_response(ProblemSerializer(problem).data)
|
return success_response(ProblemSerializer(problem).data)
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return error_response(u"题目不存在")
|
return error_response(u"题目不存在")
|
||||||
problem = Problem.objects.all().order_by("-create_time")
|
|
||||||
|
# 获取问题列表
|
||||||
|
problems = Problem.objects.all().order_by("-create_time")
|
||||||
|
|
||||||
|
if request.user.admin_type != SUPER_ADMIN:
|
||||||
|
problems = problems.filter(created_by=request.user)
|
||||||
|
|
||||||
visible = request.GET.get("visible", None)
|
visible = request.GET.get("visible", None)
|
||||||
if visible:
|
if visible:
|
||||||
problem = problem.filter(visible=(visible == "true"))
|
problems = problems.filter(visible=(visible == "true"))
|
||||||
keyword = request.GET.get("keyword", None)
|
keyword = request.GET.get("keyword", None)
|
||||||
if keyword:
|
if keyword:
|
||||||
problem = problem.filter(Q(title__contains=keyword) |
|
problems = problems.filter(Q(title__contains=keyword) |
|
||||||
Q(description__contains=keyword))
|
Q(description__contains=keyword))
|
||||||
|
|
||||||
return paginate(request, problem, ProblemSerializer)
|
return paginate(request, problems, ProblemSerializer)
|
||||||
|
|
||||||
|
|
||||||
class TestCaseUploadAPIView(APIView):
|
class TestCaseUploadAPIView(APIView):
|
||||||
|
"""
|
||||||
|
上传题目的测试用例
|
||||||
|
"""
|
||||||
def _is_legal_test_case_file_name(self, file_name):
|
def _is_legal_test_case_file_name(self, file_name):
|
||||||
# 正整数开头的 .in 或者.out 结尾的
|
# 正整数开头的 .in 或者.out 结尾的
|
||||||
regex = r"^[1-9]\d*\.(in|out)$"
|
regex = r"^[1-9]\d*\.(in|out)$"
|
||||||
@ -237,6 +256,9 @@ class TestCaseUploadAPIView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
def problem_list_page(request, page=1):
|
def problem_list_page(request, page=1):
|
||||||
|
"""
|
||||||
|
前台的问题列表
|
||||||
|
"""
|
||||||
# 正常情况
|
# 正常情况
|
||||||
problems = Problem.objects.filter(visible=True)
|
problems = Problem.objects.filter(visible=True)
|
||||||
|
|
||||||
|
@ -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({
|
||||||
|
@ -162,7 +162,7 @@ def my_submission_list_page(request, page=1):
|
|||||||
我的所有提交的列表页
|
我的所有提交的列表页
|
||||||
"""
|
"""
|
||||||
# 显示所有人的提交 这是管理员的调试功能
|
# 显示所有人的提交 这是管理员的调试功能
|
||||||
show_all = request.GET.get("show_all", False) == "true"
|
show_all = request.GET.get("show_all", False) == "true" and request.user.admin_type == SUPER_ADMIN
|
||||||
if show_all:
|
if show_all:
|
||||||
submissions = Submission.objects.filter(contest_id__isnull=True)
|
submissions = Submission.objects.filter(contest_id__isnull=True)
|
||||||
else:
|
else:
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<div class="problem-detail">{{ contest_problem.hint|safe }}</div>
|
<div class="problem-detail">{{ contest_problem.hint|safe }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% ifequal contest.status 0 %}
|
{% if show_submit_code_area %}
|
||||||
<div>
|
<div>
|
||||||
<label>选择语言</label>
|
<label>选择语言</label>
|
||||||
<div>
|
<div>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<img src="/static/img/loading.gif" id="loading-gif">
|
<img src="/static/img/loading.gif" id="loading-gif">
|
||||||
</div>
|
</div>
|
||||||
{% endifequal %}
|
{% endif %}
|
||||||
{% if show_warning %}
|
{% if show_warning %}
|
||||||
<div class="alert alert-success" role="alert">{{ warning }}</div>
|
<div class="alert alert-success" role="alert">{{ warning }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
14
utils/models.py
Normal file
14
utils/models.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from utils.xss_filter import XssHtml
|
||||||
|
|
||||||
|
|
||||||
|
class RichTextField(models.TextField):
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
|
||||||
|
def get_prep_value(self, value):
|
||||||
|
parser = XssHtml()
|
||||||
|
parser.feed(value)
|
||||||
|
parser.close()
|
||||||
|
return parser.getHtml()
|
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