更新 ContestRank的生成方法

This commit is contained in:
virusdefender 2015-09-24 14:55:20 +08:00
parent 190221f2a5
commit 7eea999277
7 changed files with 155 additions and 66 deletions

View File

@ -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 utils.models 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为ac2为正在进行 # JSON字典用来表示该用户的问题的解决状态 1为ac2为正在进行
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 = []

View File

@ -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})
return success_response({"applied_captcha": True}) if user.admin_type > 0:
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")

View File

@ -5,7 +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 from utils.models import RichTextField, JsonField
from judge.judger.result import result
GROUP_CONTEST = 0 GROUP_CONTEST = 0
@ -104,3 +105,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 submission.problem_id in self.problem_info:
info = self.submission_info[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_time"] * 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[submission.problem_id] = info
self.save()

View File

@ -49,6 +49,11 @@ 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
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")

View File

@ -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,16 @@ 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
try: with transaction.atomic():
contest_submission = ContestSubmission.objects.get(user_id=submission.user_id, contest=contest, try:
problem_id=contest_problem.id) contest_rank = ContestRank.objects.get(contest=contest, user=user)
# 提交次数加1 contest_rank.update_rank(submission)
with transaction.atomic(): except ContestRank.DoesNotExist:
if submission.result == result["accepted"]: ContestRank.objects.create(contest=contest, user=user).update_rank(submission)
# 避免这道题已经 ac 了,但是又重新提交了一遍
if not contest_submission.ac: if submission.result == result["accepted"]:
# 这种情况是这个题目前处于错误状态,就使用已经存储了的罚时加上这道题的实际用时 contest_problem.total_accepted_number += 1
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() 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.save()
else:
# 没过罚时20分钟
total_time = 1200
ContestSubmission.objects.create(user_id=submission.user_id, contest=contest, problem=contest_problem,
ac=is_ac, total_time=total_time, first_achieved=first_achieved,
ac_time=ac_time)
logger.debug("Start message queue") logger.debug("Start message queue")
MessageQueue().listen_task() MessageQueue().listen_task()

View File

@ -53,9 +53,9 @@ 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 problems_status["problems"][str(data["problem_id"])] = 2
request.user.problems_status = json.dumps(problems_status) 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"])

View File

@ -1,4 +1,5 @@
# coding=utf-8 # coding=utf-8
import json
from django.db import models from django.db import models
from utils.xss_filter import XssHtml from utils.xss_filter import XssHtml
@ -9,8 +10,18 @@ class RichTextField(models.TextField):
def get_prep_value(self, value): def get_prep_value(self, value):
if not value: if not value:
return value value = ""
parser = XssHtml() parser = XssHtml()
parser.feed(value) parser.feed(value)
parser.close() parser.close()
return parser.getHtml() return parser.getHtml()
class JsonField(models.TextField):
__metaclass__ = models.SubfieldBase
def get_prep_value(self, value):
return json.dumps(value)
def to_python(self, value):
return json.loads(value)