mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2025-11-04 14:49:58 +08:00
Move real_name to UserProfile;
Delete student_id field; Mark the problems that have submission; Alter dispatcher to adapt the changes.
This commit is contained in:
parent
1e4ede6d1a
commit
f55a242ec0
39
account/migrations/0005_auto_20170830_1154.py
Normal file
39
account/migrations/0005_auto_20170830_1154.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.4 on 2017-08-30 11:54
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import jsonfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0004_remove_userprofile_time_zone'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='userprofile',
|
||||||
|
old_name='problems_status',
|
||||||
|
new_name='acm_problems_status',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='oi_problems_status',
|
||||||
|
field=jsonfield.fields.JSONField(default={}),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='user',
|
||||||
|
name='real_name',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='student_id',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='real_name',
|
||||||
|
field=models.CharField(max_length=30, blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -24,7 +24,6 @@ class UserManager(models.Manager):
|
|||||||
|
|
||||||
class User(AbstractBaseUser):
|
class User(AbstractBaseUser):
|
||||||
username = models.CharField(max_length=30, unique=True)
|
username = models.CharField(max_length=30, unique=True)
|
||||||
real_name = models.CharField(max_length=30, null=True)
|
|
||||||
email = models.EmailField(max_length=254, null=True)
|
email = models.EmailField(max_length=254, null=True)
|
||||||
create_time = models.DateTimeField(auto_now_add=True, null=True)
|
create_time = models.DateTimeField(auto_now_add=True, null=True)
|
||||||
# One of UserType
|
# One of UserType
|
||||||
@ -69,17 +68,19 @@ def _random_avatar():
|
|||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
user = models.OneToOneField(User)
|
user = models.OneToOneField(User)
|
||||||
# Store user problem solution status with json string format, Only for problems not contest_problems
|
# Store user problem solution status with json string format
|
||||||
# ACM: {1: {status: JudgeStatus.ACCEPTED}}
|
# {problems: {1: JudgeStatus.ACCEPTED}, contest_problems: {1: JudgeStatus.ACCEPTED}}, record problem_id and status
|
||||||
# OI: {1: {score: 33}}
|
acm_problems_status = JSONField(default={})
|
||||||
problems_status = JSONField(default={})
|
# {problems: {1: 33}, contest_problems: {1: 44}, record problem_id and score
|
||||||
|
oi_problems_status = JSONField(default={})
|
||||||
|
|
||||||
|
real_name = models.CharField(max_length=30, blank=True, null=True)
|
||||||
avatar = models.CharField(max_length=50, default=_random_avatar)
|
avatar = models.CharField(max_length=50, default=_random_avatar)
|
||||||
blog = models.URLField(blank=True, null=True)
|
blog = models.URLField(blank=True, null=True)
|
||||||
mood = models.CharField(max_length=200, blank=True, null=True)
|
mood = models.CharField(max_length=200, blank=True, null=True)
|
||||||
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
phone_number = models.CharField(max_length=15, blank=True, null=True)
|
||||||
school = models.CharField(max_length=200, blank=True, null=True)
|
school = models.CharField(max_length=200, blank=True, null=True)
|
||||||
major = models.CharField(max_length=200, blank=True, null=True)
|
major = models.CharField(max_length=200, blank=True, null=True)
|
||||||
student_id = models.CharField(max_length=15, blank=True, null=True)
|
|
||||||
language = models.CharField(max_length=32, blank=True, null=True)
|
language = models.CharField(max_length=32, blank=True, null=True)
|
||||||
# for ACM
|
# for ACM
|
||||||
accepted_number = models.IntegerField(default=0)
|
accepted_number = models.IntegerField(default=0)
|
||||||
|
|||||||
@ -35,18 +35,23 @@ class UserSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ["id", "username", "real_name", "email", "admin_type", "problem_permission",
|
fields = ["id", "username", "email", "admin_type", "problem_permission",
|
||||||
"create_time", "last_login", "two_factor_auth", "open_api", "is_disabled"]
|
"create_time", "last_login", "two_factor_auth", "open_api", "is_disabled"]
|
||||||
|
|
||||||
|
|
||||||
class UserProfileSerializer(serializers.ModelSerializer):
|
class UserProfileSerializer(serializers.ModelSerializer):
|
||||||
user = UserSerializer()
|
user = UserSerializer()
|
||||||
|
acm_problems_status = serializers.JSONField()
|
||||||
|
oi_problems_status = serializers.JSONField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserProfile
|
model = UserProfile
|
||||||
|
|
||||||
|
|
||||||
class UserInfoSerializer(serializers.ModelSerializer):
|
class UserInfoSerializer(serializers.ModelSerializer):
|
||||||
|
acm_problems_status = serializers.JSONField()
|
||||||
|
oi_problems_status = serializers.JSONField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserProfile
|
model = UserProfile
|
||||||
|
|
||||||
@ -54,7 +59,6 @@ class UserInfoSerializer(serializers.ModelSerializer):
|
|||||||
class EditUserSerializer(serializers.Serializer):
|
class EditUserSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
username = serializers.CharField(max_length=30)
|
username = serializers.CharField(max_length=30)
|
||||||
real_name = serializers.CharField(max_length=30)
|
|
||||||
password = serializers.CharField(max_length=30, min_length=6, allow_blank=True, required=False, default=None)
|
password = serializers.CharField(max_length=30, min_length=6, allow_blank=True, required=False, default=None)
|
||||||
email = serializers.EmailField(max_length=254)
|
email = serializers.EmailField(max_length=254)
|
||||||
admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN))
|
admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN))
|
||||||
@ -66,13 +70,13 @@ class EditUserSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
|
|
||||||
class EditUserProfileSerializer(serializers.Serializer):
|
class EditUserProfileSerializer(serializers.Serializer):
|
||||||
|
real_name = serializers.CharField(max_length=30)
|
||||||
avatar = serializers.CharField(max_length=100, allow_null=True, required=False)
|
avatar = serializers.CharField(max_length=100, allow_null=True, required=False)
|
||||||
blog = serializers.URLField(allow_null=True, required=False)
|
blog = serializers.URLField(allow_null=True, required=False)
|
||||||
mood = serializers.CharField(max_length=200, allow_null=True, required=False)
|
mood = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||||
phone_number = serializers.CharField(max_length=15, allow_null=True, required=False, )
|
phone_number = serializers.CharField(max_length=15, allow_null=True, required=False, )
|
||||||
school = serializers.CharField(max_length=200, allow_null=True, required=False)
|
school = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||||
major = serializers.CharField(max_length=200, allow_null=True, required=False)
|
major = serializers.CharField(max_length=200, allow_null=True, required=False)
|
||||||
student_id = serializers.CharField(max_length=15, allow_null=True, required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyResetPasswordSerializer(serializers.Serializer):
|
class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||||
|
|||||||
@ -1,20 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
import qrcode
|
import qrcode
|
||||||
from io import BytesIO
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from otpauth import OtpAuth
|
from otpauth import OtpAuth
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from conf.models import WebsiteConfig
|
from conf.models import WebsiteConfig
|
||||||
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
|
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
|
||||||
from utils.captcha import Captcha
|
from utils.captcha import Captcha
|
||||||
from utils.shortcuts import rand_str
|
from utils.shortcuts import rand_str, img2base64
|
||||||
|
|
||||||
from ..decorators import login_required
|
from ..decorators import login_required
|
||||||
from ..models import User, UserProfile
|
from ..models import User, UserProfile
|
||||||
@ -29,16 +28,14 @@ from ..tasks import send_email_async
|
|||||||
|
|
||||||
|
|
||||||
class UserProfileAPI(APIView):
|
class UserProfileAPI(APIView):
|
||||||
"""
|
|
||||||
判断是否登录, 若登录返回用户信息
|
|
||||||
"""
|
|
||||||
|
|
||||||
@method_decorator(ensure_csrf_cookie)
|
@method_decorator(ensure_csrf_cookie)
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
|
"""
|
||||||
|
判断是否登录, 若登录返回用户信息
|
||||||
|
"""
|
||||||
user = request.user
|
user = request.user
|
||||||
if not user.is_authenticated():
|
if not user.is_authenticated():
|
||||||
return self.success(0)
|
return self.success(0)
|
||||||
|
|
||||||
username = request.GET.get("username")
|
username = request.GET.get("username")
|
||||||
try:
|
try:
|
||||||
if username:
|
if username:
|
||||||
@ -55,19 +52,10 @@ class UserProfileAPI(APIView):
|
|||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
user_profile = request.user.userprofile
|
user_profile = request.user.userprofile
|
||||||
print(data)
|
for k, v in data.items():
|
||||||
if data.get("avatar"):
|
setattr(user_profile, k, v)
|
||||||
user_profile.avatar = data["avatar"]
|
|
||||||
else:
|
|
||||||
user_profile.mood = data["mood"]
|
|
||||||
user_profile.blog = data["blog"]
|
|
||||||
user_profile.school = data["school"]
|
|
||||||
user_profile.student_id = data["student_id"]
|
|
||||||
user_profile.phone_number = data["phone_number"]
|
|
||||||
user_profile.major = data["major"]
|
|
||||||
# Timezone & language 暂时不加
|
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
return self.success("Succeeded")
|
return self.success(UserProfileSerializer(user_profile).data)
|
||||||
|
|
||||||
|
|
||||||
class AvatarUploadAPI(CSRFExemptAPIView):
|
class AvatarUploadAPI(CSRFExemptAPIView):
|
||||||
@ -137,11 +125,9 @@ class TwoFactorAuthAPI(APIView):
|
|||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
config = WebsiteConfig.objects.first()
|
config = WebsiteConfig.objects.first()
|
||||||
image = qrcode.make(OtpAuth(token).to_uri("totp", config.base_url, config.name))
|
label = f"{config.name_shortcut}:{user.username}@{config.base_url}"
|
||||||
buf = BytesIO()
|
image = qrcode.make(OtpAuth(token).to_uri("totp", label, config.name))
|
||||||
image.save(buf, "gif")
|
return self.success(img2base64(image))
|
||||||
|
|
||||||
return HttpResponse(buf.getvalue(), "image/gif")
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@validate_serializer(TwoFactorAuthCodeSerializer)
|
@validate_serializer(TwoFactorAuthCodeSerializer)
|
||||||
@ -215,17 +201,17 @@ class UsernameOrEmailCheck(APIView):
|
|||||||
check username or email is duplicate
|
check username or email is duplicate
|
||||||
"""
|
"""
|
||||||
data = request.data
|
data = request.data
|
||||||
# True means OK.
|
# True means already exist.
|
||||||
result = {
|
result = {
|
||||||
"username": True,
|
"username": False,
|
||||||
"email": True
|
"email": False
|
||||||
}
|
}
|
||||||
if data.get("username"):
|
if data.get("username"):
|
||||||
if User.objects.filter(username=data["username"]).exists():
|
if User.objects.filter(username=data["username"]).exists():
|
||||||
result["username"] = False
|
result["username"] = True
|
||||||
if data.get("email"):
|
if data.get("email"):
|
||||||
if User.objects.filter(email=data["email"]).exists():
|
if User.objects.filter(email=data["email"]).exists():
|
||||||
result["email"] = False
|
result["email"] = True
|
||||||
return self.success(result)
|
return self.success(result)
|
||||||
|
|
||||||
|
|
||||||
@ -259,9 +245,6 @@ class UserChangePasswordAPI(APIView):
|
|||||||
User change password api
|
User change password api
|
||||||
"""
|
"""
|
||||||
data = request.data
|
data = request.data
|
||||||
captcha = Captcha(request)
|
|
||||||
if not captcha.check(data["captcha"]):
|
|
||||||
return self.error("Invalid captcha")
|
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
user = auth.authenticate(username=username, password=data["old_password"])
|
user = auth.authenticate(username=username, password=data["old_password"])
|
||||||
if user:
|
if user:
|
||||||
@ -284,24 +267,23 @@ class ApplyResetPasswordAPI(APIView):
|
|||||||
user = User.objects.get(email=data["email"])
|
user = User.objects.get(email=data["email"])
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return self.error("User does not exist")
|
return self.error("User does not exist")
|
||||||
if user.reset_password_token_expire_time and 0 < (
|
if user.reset_password_token_expire_time and \
|
||||||
user.reset_password_token_expire_time - now()).total_seconds() < 20 * 60:
|
0 < int((user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60:
|
||||||
return self.error("You can only reset password once per 20 minutes")
|
return self.error("You can only reset password once per 20 minutes")
|
||||||
user.reset_password_token = rand_str()
|
user.reset_password_token = rand_str()
|
||||||
|
|
||||||
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
||||||
user.save()
|
user.save()
|
||||||
email_template = open("reset_password_email.html", "w",
|
render_data = {
|
||||||
encoding="utf-8").read()
|
"username": user.username,
|
||||||
email_template = email_template.replace("{{ username }}", user.username). \
|
"website_name": config.name,
|
||||||
replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]). \
|
"link": f"{config.base_url}/reset-password/{user.reset_password_token}"
|
||||||
replace("{{ link }}", settings.WEBSITE_INFO["url"] + "/reset_password/t/" +
|
}
|
||||||
user.reset_password_token)
|
email_html = render_to_string('reset_password_email.html', render_data)
|
||||||
send_email_async.delay(config.name,
|
send_email_async.delay(config.name,
|
||||||
user.email,
|
user.email,
|
||||||
user.username,
|
user.username,
|
||||||
config.name + " 登录信息找回邮件",
|
config.name + " 登录信息找回邮件",
|
||||||
email_template)
|
email_html)
|
||||||
return self.success("Succeeded")
|
return self.success("Succeeded")
|
||||||
|
|
||||||
|
|
||||||
@ -316,8 +298,8 @@ class ResetPasswordAPI(APIView):
|
|||||||
user = User.objects.get(reset_password_token=data["token"])
|
user = User.objects.get(reset_password_token=data["token"])
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return self.error("Token dose not exist")
|
return self.error("Token dose not exist")
|
||||||
if 0 < (user.reset_password_token_expire_time - now()).total_seconds() < 30 * 60:
|
if int((user.reset_password_token_expire_time - now()).total_seconds()) < 0:
|
||||||
return self.error("Token expired")
|
return self.error("Token have expired")
|
||||||
user.reset_password_token = None
|
user.reset_password_token = None
|
||||||
user.set_password(data["password"])
|
user.set_password(data["password"])
|
||||||
user.save()
|
user.save()
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class JudgeDispatcher(object):
|
|||||||
self.redis_conn = judge_cache
|
self.redis_conn = judge_cache
|
||||||
self.submission = Submission.objects.get(pk=submission_id)
|
self.submission = Submission.objects.get(pk=submission_id)
|
||||||
if self.submission.contest_id:
|
if self.submission.contest_id:
|
||||||
self.problem = ContestProblem.objects.select_related("contest")\
|
self.problem = ContestProblem.objects.select_related("contest") \
|
||||||
.get(_id=problem_id, contest_id=self.submission.contest_id)
|
.get(_id=problem_id, contest_id=self.submission.contest_id)
|
||||||
self.contest = self.problem.contest
|
self.contest = self.problem.contest
|
||||||
else:
|
else:
|
||||||
@ -114,7 +114,8 @@ class JudgeDispatcher(object):
|
|||||||
# todo OI statistic_info["score"]
|
# todo OI statistic_info["score"]
|
||||||
|
|
||||||
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
|
error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"]))
|
||||||
# 多个测试点全部正确则AC,否则 ACM模式下取第一个错误的测试点的状态, OI模式若全部错误则取第一个错误测试点状态,否则为部分正确
|
# ACM模式下,多个测试点全部正确则AC,否则取第一个错误的测试点的状态
|
||||||
|
# OI模式下, 若多个测试点全部正确则AC, 若全部错误则取第一个错误测试点状态,否则为部分正确
|
||||||
if not error_test_case:
|
if not error_test_case:
|
||||||
self.submission.result = JudgeStatus.ACCEPTED
|
self.submission.result = JudgeStatus.ACCEPTED
|
||||||
elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
|
elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]):
|
||||||
@ -125,11 +126,9 @@ class JudgeDispatcher(object):
|
|||||||
self.release_judge_res(server.id)
|
self.release_judge_res(server.id)
|
||||||
|
|
||||||
self.update_problem_status()
|
self.update_problem_status()
|
||||||
|
|
||||||
if self.submission.contest_id:
|
if self.submission.contest_id:
|
||||||
self.update_contest_rank()
|
self.update_contest_rank()
|
||||||
else:
|
|
||||||
self.update_user_profile()
|
|
||||||
# 至此判题结束,尝试处理任务队列中剩余的任务
|
# 至此判题结束,尝试处理任务队列中剩余的任务
|
||||||
process_pending_task()
|
process_pending_task()
|
||||||
|
|
||||||
@ -140,53 +139,71 @@ class JudgeDispatcher(object):
|
|||||||
return self._request(urljoin(service_url, "compile_spj"), data=data)
|
return self._request(urljoin(service_url, "compile_spj"), data=data)
|
||||||
|
|
||||||
def update_problem_status(self):
|
def update_problem_status(self):
|
||||||
self.problem.add_submission_number()
|
|
||||||
if self.submission.result == JudgeStatus.ACCEPTED:
|
|
||||||
self.problem.add_ac_number()
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
# prepare problem and user_profile
|
||||||
if self.submission.contest_id:
|
if self.submission.contest_id:
|
||||||
problem = ContestProblem.objects.select_for_update().get(_id=self.problem._id, contest_id=self.contest.id)
|
problem = ContestProblem.objects.select_for_update().get(contest_id=self.contest.id,
|
||||||
|
_id=self.problem._id)
|
||||||
else:
|
else:
|
||||||
problem = Problem.objects.select_related().get(_id=self.problem._id)
|
problem = Problem.objects.select_for_update().get(_id=self.problem._id)
|
||||||
info = problem.statistic_info
|
problem_info = problem.statistic_info
|
||||||
result = str(self.submission.result)
|
user = User.objects.select_for_update().select_for_update("userprofile").get(id=self.submission.user_id)
|
||||||
info[result] = info.get(result, 0) + 1
|
|
||||||
problem.statistic_info = info
|
|
||||||
problem.save(update_fields=["statistic_info"])
|
|
||||||
|
|
||||||
def update_user_profile(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
user = User.objects.select_for_update().get(id=self.submission.user_id)
|
|
||||||
user_profile = user.userprofile
|
user_profile = user.userprofile
|
||||||
user_profile.add_submission_number()
|
if self.submission.contest_id:
|
||||||
problems_status = user_profile.problems_status
|
key = "contest_problems"
|
||||||
|
else:
|
||||||
|
key = "problems"
|
||||||
|
acm_problems_status = user_profile.acm_problems_status.get(key, {})
|
||||||
|
oi_problems_status = user_profile.oi_problems_status.get(key, {})
|
||||||
|
|
||||||
|
# update submission and accepted number counter
|
||||||
|
# only when submission is not in contest, we update user profile,
|
||||||
|
# in other words, users' submission in a contest will not be counted in user profile
|
||||||
|
if not self.submission.contest_id:
|
||||||
|
user_profile.submission_number += 1
|
||||||
|
if self.submission.result == JudgeStatus.ACCEPTED:
|
||||||
|
user_profile.accepted_number += 1
|
||||||
|
problem.submission_number += 1
|
||||||
|
if self.submission.result == JudgeStatus.ACCEPTED:
|
||||||
|
problem.accepted_number += 1
|
||||||
|
|
||||||
problem_id = str(self.problem._id)
|
problem_id = str(self.problem._id)
|
||||||
if self.problem.rule_type == ProblemRuleType.ACM:
|
if self.problem.rule_type == ProblemRuleType.ACM:
|
||||||
if problem_id not in problems_status:
|
# update acm problem info
|
||||||
problems_status[problem_id] = {"status": self.submission.result}
|
result = str(self.submission.result)
|
||||||
|
problem_info[result] = problem_info.get(result, 0) + 1
|
||||||
|
problem.statistic_info = problem_info
|
||||||
|
|
||||||
|
# update user_profile
|
||||||
|
if problem_id not in acm_problems_status:
|
||||||
|
acm_problems_status[problem_id] = self.submission.result
|
||||||
|
# skip if the problem has been accepted
|
||||||
|
elif acm_problems_status[problem_id] != JudgeStatus.ACCEPTED:
|
||||||
if self.submission.result == JudgeStatus.ACCEPTED:
|
if self.submission.result == JudgeStatus.ACCEPTED:
|
||||||
user_profile.add_accepted_problem_number()
|
acm_problems_status[problem_id] = JudgeStatus.ACCEPTED
|
||||||
# 以前提交过, ac了直接略过
|
|
||||||
elif problems_status[problem_id]["status"] != JudgeStatus.ACCEPTED:
|
|
||||||
if self.submission.result == JudgeStatus.ACCEPTED:
|
|
||||||
user_profile.add_accepted_problem_number()
|
|
||||||
problems_status[problem_id]["status"] = JudgeStatus.ACCEPTED
|
|
||||||
else:
|
else:
|
||||||
problems_status[problem_id]["status"] = self.submission.result
|
acm_problems_status[problem_id] = self.submission.result
|
||||||
|
user_profile.acm_problems_status[key] = acm_problems_status
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# update oi problem info
|
||||||
score = self.submission.statistic_info["score"]
|
score = self.submission.statistic_info["score"]
|
||||||
if problem_id not in problems_status:
|
problem_info[score] = problem_info.get(score, 0) + 1
|
||||||
user_profile.add_score(score)
|
problem.statistic_info = problem_info
|
||||||
problems_status[problem_id] = {"score": score}
|
|
||||||
else:
|
|
||||||
# 加上本次 减掉上次的score
|
|
||||||
user_profile.add_score(score, problems_status[problem_id]["score"])
|
|
||||||
problems_status[problem_id] = {"score": score}
|
|
||||||
|
|
||||||
user_profile.problems_status = problems_status
|
# update user_profile
|
||||||
user_profile.save(update_fields=["problems_status"])
|
if problem_id not in oi_problems_status:
|
||||||
|
user_profile.add_score(score)
|
||||||
|
oi_problems_status[problem_id] = score
|
||||||
|
else:
|
||||||
|
# minus last time score, add this time score
|
||||||
|
user_profile.add_score(score, oi_problems_status[problem_id])
|
||||||
|
oi_problems_status[problem_id] = score
|
||||||
|
user_profile.oi_problems_status[key] = oi_problems_status
|
||||||
|
|
||||||
|
problem.save(update_fields=["submission_number", "accepted_number", "statistic_info"])
|
||||||
|
user_profile.save(
|
||||||
|
update_fields=["submission_number", "accepted_number", "acm_problems_status", "oi_problems_status"])
|
||||||
|
|
||||||
def update_contest_rank(self):
|
def update_contest_rank(self):
|
||||||
if self.contest.real_time_rank:
|
if self.contest.real_time_rank:
|
||||||
|
|||||||
@ -23,7 +23,6 @@ if ENV == "local":
|
|||||||
elif ENV == "server":
|
elif ENV == "server":
|
||||||
from .server_settings import *
|
from .server_settings import *
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
@ -80,9 +79,26 @@ TEMPLATES = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'oj.wsgi.application'
|
WSGI_APPLICATION = 'oj.wsgi.application'
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
@ -105,7 +121,7 @@ AUTH_USER_MODEL = 'account.User'
|
|||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': False,
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'standard': {
|
'standard': {
|
||||||
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'}
|
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'}
|
||||||
|
|||||||
@ -57,8 +57,7 @@ class AbstractProblem(models.Model):
|
|||||||
source = models.CharField(max_length=200, blank=True, null=True)
|
source = models.CharField(max_length=200, blank=True, null=True)
|
||||||
submission_number = models.BigIntegerField(default=0)
|
submission_number = models.BigIntegerField(default=0)
|
||||||
accepted_number = models.BigIntegerField(default=0)
|
accepted_number = models.BigIntegerField(default=0)
|
||||||
# {0: 0, 1: 0, 2: 0, 3: 0 ...}
|
# ACM rule_type: {JudgeStatus.ACCEPTED: 3, JudgeStaus.WRONG_ANSWER: 11}, the number means count
|
||||||
# the first number means JudgeStatus, the second number present count
|
|
||||||
statistic_info = JSONField(default={})
|
statistic_info = JSONField(default={})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from utils.api import APIView
|
from utils.api import APIView
|
||||||
from account.decorators import check_contest_permission
|
from account.decorators import check_contest_permission
|
||||||
from ..models import ProblemTag, Problem, ContestProblem
|
from ..models import ProblemTag, Problem, ContestProblem, ProblemRuleType
|
||||||
from ..serializers import ProblemSerializer, TagSerializer
|
from ..serializers import ProblemSerializer, TagSerializer
|
||||||
from ..serializers import ContestProblemSerializer
|
from ..serializers import ContestProblemSerializer
|
||||||
|
from contest.models import ContestRuleType
|
||||||
|
|
||||||
class ProblemTagAPI(APIView):
|
class ProblemTagAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
@ -41,8 +41,18 @@ class ProblemAPI(APIView):
|
|||||||
difficulty_rank = request.GET.get("difficulty")
|
difficulty_rank = request.GET.get("difficulty")
|
||||||
if difficulty_rank:
|
if difficulty_rank:
|
||||||
problems = problems.filter(difficulty=difficulty_rank)
|
problems = problems.filter(difficulty=difficulty_rank)
|
||||||
|
# 根据profile 为做过的题目添加标记
|
||||||
return self.success(self.paginate_data(request, problems, ProblemSerializer))
|
data = self.paginate_data(request, problems, ProblemSerializer)
|
||||||
|
if request.user.id:
|
||||||
|
profile = request.user.userprofile
|
||||||
|
acm_problems_status = profile.acm_problems_status.get("problems", {})
|
||||||
|
oi_problems_status = profile.oi_problems_status.get("problems", {})
|
||||||
|
for problem in data["results"]:
|
||||||
|
if problem["rule_type"] == ProblemRuleType.ACM:
|
||||||
|
problem["my_status"] = acm_problems_status.get(problem["_id"], None)
|
||||||
|
else:
|
||||||
|
problem["my_status"] = oi_problems_status.get(problem["_id"], None)
|
||||||
|
return self.success(data)
|
||||||
|
|
||||||
|
|
||||||
class ContestProblemAPI(APIView):
|
class ContestProblemAPI(APIView):
|
||||||
@ -57,4 +67,14 @@ class ContestProblemAPI(APIView):
|
|||||||
return self.success(ContestProblemSerializer(problem).data)
|
return self.success(ContestProblemSerializer(problem).data)
|
||||||
|
|
||||||
contest_problems = ContestProblem.objects.select_related("created_by").filter(contest=self.contest, visible=True)
|
contest_problems = ContestProblem.objects.select_related("created_by").filter(contest=self.contest, visible=True)
|
||||||
|
# 根据profile, 为做过的题目添加标记
|
||||||
|
data = ContestProblemSerializer(contest_problems, many=True).data
|
||||||
|
if request.user.id:
|
||||||
|
profile = request.user.userprofile
|
||||||
|
if self.contest.rule_type == ContestRuleType.ACM:
|
||||||
|
problems_status = profile.acm_problems_status.get("contest_problems", {})
|
||||||
|
else:
|
||||||
|
problems_status = profile.oi_problems_status.get("contest_problems", {})
|
||||||
|
for problem in data:
|
||||||
|
problem["my_status"] = problems_status.get(problem["_id"], None)
|
||||||
return self.success(ContestProblemSerializer(contest_problems, many=True).data)
|
return self.success(ContestProblemSerializer(contest_problems, many=True).data)
|
||||||
|
|||||||
0
reset_password_email.html
Normal file
0
reset_password_email.html
Normal file
20
submission/migrations/0006_auto_20170830_1154.py
Normal file
20
submission/migrations/0006_auto_20170830_1154.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.4 on 2017-08-30 11:54
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('submission', '0005_submission_username'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='submission',
|
||||||
|
name='result',
|
||||||
|
field=models.IntegerField(db_index=True, default=6),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -27,7 +27,7 @@ class Submission(models.Model):
|
|||||||
user_id = models.IntegerField(db_index=True)
|
user_id = models.IntegerField(db_index=True)
|
||||||
username = models.CharField(max_length=30)
|
username = models.CharField(max_length=30)
|
||||||
code = models.TextField()
|
code = models.TextField()
|
||||||
result = models.IntegerField(default=JudgeStatus.PENDING)
|
result = models.IntegerField(db_index=True, default=JudgeStatus.PENDING)
|
||||||
# 判题结果的详细信息
|
# 判题结果的详细信息
|
||||||
info = JSONField(default={})
|
info = JSONField(default={})
|
||||||
language = models.CharField(max_length=20)
|
language = models.CharField(max_length=20)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
from account.decorators import login_required, check_contest_permission
|
from account.decorators import login_required, check_contest_permission
|
||||||
from judge.tasks import judge_task
|
from judge.tasks import judge_task
|
||||||
# from judge.dispatcher import JudgeDispatcher
|
from judge.dispatcher import JudgeDispatcher
|
||||||
from problem.models import Problem, ProblemRuleType, ContestProblem
|
from problem.models import Problem, ProblemRuleType, ContestProblem
|
||||||
from contest.models import Contest, ContestStatus
|
from contest.models import Contest, ContestStatus
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
@ -104,11 +104,14 @@ class SubmissionListAPI(APIView):
|
|||||||
|
|
||||||
def process_submissions(self, request, submissions):
|
def process_submissions(self, request, submissions):
|
||||||
problem_id = request.GET.get("problem_id")
|
problem_id = request.GET.get("problem_id")
|
||||||
|
myself = request.GET.get("myself")
|
||||||
|
result = request.GET.get("result")
|
||||||
if problem_id:
|
if problem_id:
|
||||||
submissions = submissions.filter(problem_id=problem_id)
|
submissions = submissions.filter(problem_id=problem_id)
|
||||||
|
if myself and myself == "1":
|
||||||
if request.GET.get("myself") and request.GET["myself"] == "1":
|
|
||||||
submissions = submissions.filter(user_id=request.user.id)
|
submissions = submissions.filter(user_id=request.user.id)
|
||||||
|
if result:
|
||||||
|
submissions = submissions.filter(result=result)
|
||||||
data = self.paginate_data(request, submissions)
|
data = self.paginate_data(request, submissions)
|
||||||
data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data
|
data["results"] = SubmissionListSerializer(data["results"], many=True, user=request.user).data
|
||||||
return self.success(data)
|
return self.success(data)
|
||||||
|
|||||||
@ -82,10 +82,7 @@ class Captcha(object):
|
|||||||
x += font_size * random.randrange(6, 8) / 10
|
x += font_size * random.randrange(6, 8) / 10
|
||||||
|
|
||||||
self.django_request.session[self.session_key] = "".join(code)
|
self.django_request.session[self.session_key] = "".join(code)
|
||||||
with BytesIO() as buf:
|
return image
|
||||||
image.save(buf, "gif")
|
|
||||||
buf_str = buf.getvalue()
|
|
||||||
return buf_str
|
|
||||||
|
|
||||||
def check(self, code):
|
def check(self, code):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -2,10 +2,9 @@ from base64 import b64encode
|
|||||||
|
|
||||||
from . import Captcha
|
from . import Captcha
|
||||||
from ..api import APIView
|
from ..api import APIView
|
||||||
|
from ..shortcuts import img2base64
|
||||||
|
|
||||||
|
|
||||||
class CaptchaAPIView(APIView):
|
class CaptchaAPIView(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
img_prefix = "data:image/png;base64,"
|
return self.success(img2base64(Captcha(request).get()))
|
||||||
img = img_prefix + b64encode(Captcha(request).get()).decode("utf-8")
|
|
||||||
return self.success(img)
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from io import BytesIO
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from envelopes import Envelope
|
from envelopes import Envelope
|
||||||
@ -58,3 +60,12 @@ def build_query_string(kv_data, ignore_none=True):
|
|||||||
query_string = "?"
|
query_string = "?"
|
||||||
query_string += (k + "=" + str(v))
|
query_string += (k + "=" + str(v))
|
||||||
return query_string
|
return query_string
|
||||||
|
|
||||||
|
|
||||||
|
def img2base64(img):
|
||||||
|
with BytesIO() as buf:
|
||||||
|
img.save(buf, "gif")
|
||||||
|
buf_str = buf.getvalue()
|
||||||
|
img_prefix = "data:image/png;base64,"
|
||||||
|
b64_str = img_prefix + b64encode(buf_str).decode("utf-8")
|
||||||
|
return b64_str
|
||||||
Loading…
Reference in New Issue
Block a user