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:
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):
|
||||
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)
|
||||
create_time = models.DateTimeField(auto_now_add=True, null=True)
|
||||
# One of UserType
|
||||
@@ -69,17 +68,19 @@ def _random_avatar():
|
||||
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(User)
|
||||
# Store user problem solution status with json string format, Only for problems not contest_problems
|
||||
# ACM: {1: {status: JudgeStatus.ACCEPTED}}
|
||||
# OI: {1: {score: 33}}
|
||||
problems_status = JSONField(default={})
|
||||
# Store user problem solution status with json string format
|
||||
# {problems: {1: JudgeStatus.ACCEPTED}, contest_problems: {1: JudgeStatus.ACCEPTED}}, record problem_id and status
|
||||
acm_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)
|
||||
blog = models.URLField(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)
|
||||
school = 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)
|
||||
# for ACM
|
||||
accepted_number = models.IntegerField(default=0)
|
||||
|
||||
@@ -35,18 +35,23 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
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"]
|
||||
|
||||
|
||||
class UserProfileSerializer(serializers.ModelSerializer):
|
||||
user = UserSerializer()
|
||||
acm_problems_status = serializers.JSONField()
|
||||
oi_problems_status = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
|
||||
|
||||
class UserInfoSerializer(serializers.ModelSerializer):
|
||||
acm_problems_status = serializers.JSONField()
|
||||
oi_problems_status = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
|
||||
@@ -54,7 +59,6 @@ class UserInfoSerializer(serializers.ModelSerializer):
|
||||
class EditUserSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
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)
|
||||
email = serializers.EmailField(max_length=254)
|
||||
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):
|
||||
real_name = serializers.CharField(max_length=30)
|
||||
avatar = serializers.CharField(max_length=100, allow_null=True, required=False)
|
||||
blog = serializers.URLField(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, )
|
||||
school = 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):
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import os
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
from datetime import timedelta
|
||||
from otpauth import OtpAuth
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.utils.timezone import now
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from conf.models import WebsiteConfig
|
||||
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
|
||||
from utils.captcha import Captcha
|
||||
from utils.shortcuts import rand_str
|
||||
from utils.shortcuts import rand_str, img2base64
|
||||
|
||||
from ..decorators import login_required
|
||||
from ..models import User, UserProfile
|
||||
@@ -29,16 +28,14 @@ from ..tasks import send_email_async
|
||||
|
||||
|
||||
class UserProfileAPI(APIView):
|
||||
"""
|
||||
判断是否登录, 若登录返回用户信息
|
||||
"""
|
||||
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def get(self, request, **kwargs):
|
||||
"""
|
||||
判断是否登录, 若登录返回用户信息
|
||||
"""
|
||||
user = request.user
|
||||
if not user.is_authenticated():
|
||||
return self.success(0)
|
||||
|
||||
username = request.GET.get("username")
|
||||
try:
|
||||
if username:
|
||||
@@ -55,19 +52,10 @@ class UserProfileAPI(APIView):
|
||||
def put(self, request):
|
||||
data = request.data
|
||||
user_profile = request.user.userprofile
|
||||
print(data)
|
||||
if data.get("avatar"):
|
||||
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 暂时不加
|
||||
for k, v in data.items():
|
||||
setattr(user_profile, k, v)
|
||||
user_profile.save()
|
||||
return self.success("Succeeded")
|
||||
return self.success(UserProfileSerializer(user_profile).data)
|
||||
|
||||
|
||||
class AvatarUploadAPI(CSRFExemptAPIView):
|
||||
@@ -137,11 +125,9 @@ class TwoFactorAuthAPI(APIView):
|
||||
user.save()
|
||||
|
||||
config = WebsiteConfig.objects.first()
|
||||
image = qrcode.make(OtpAuth(token).to_uri("totp", config.base_url, config.name))
|
||||
buf = BytesIO()
|
||||
image.save(buf, "gif")
|
||||
|
||||
return HttpResponse(buf.getvalue(), "image/gif")
|
||||
label = f"{config.name_shortcut}:{user.username}@{config.base_url}"
|
||||
image = qrcode.make(OtpAuth(token).to_uri("totp", label, config.name))
|
||||
return self.success(img2base64(image))
|
||||
|
||||
@login_required
|
||||
@validate_serializer(TwoFactorAuthCodeSerializer)
|
||||
@@ -215,17 +201,17 @@ class UsernameOrEmailCheck(APIView):
|
||||
check username or email is duplicate
|
||||
"""
|
||||
data = request.data
|
||||
# True means OK.
|
||||
# True means already exist.
|
||||
result = {
|
||||
"username": True,
|
||||
"email": True
|
||||
"username": False,
|
||||
"email": False
|
||||
}
|
||||
if data.get("username"):
|
||||
if User.objects.filter(username=data["username"]).exists():
|
||||
result["username"] = False
|
||||
result["username"] = True
|
||||
if data.get("email"):
|
||||
if User.objects.filter(email=data["email"]).exists():
|
||||
result["email"] = False
|
||||
result["email"] = True
|
||||
return self.success(result)
|
||||
|
||||
|
||||
@@ -259,9 +245,6 @@ class UserChangePasswordAPI(APIView):
|
||||
User change password api
|
||||
"""
|
||||
data = request.data
|
||||
captcha = Captcha(request)
|
||||
if not captcha.check(data["captcha"]):
|
||||
return self.error("Invalid captcha")
|
||||
username = request.user.username
|
||||
user = auth.authenticate(username=username, password=data["old_password"])
|
||||
if user:
|
||||
@@ -284,24 +267,23 @@ class ApplyResetPasswordAPI(APIView):
|
||||
user = User.objects.get(email=data["email"])
|
||||
except User.DoesNotExist:
|
||||
return self.error("User does not exist")
|
||||
if user.reset_password_token_expire_time and 0 < (
|
||||
user.reset_password_token_expire_time - now()).total_seconds() < 20 * 60:
|
||||
if user.reset_password_token_expire_time and \
|
||||
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")
|
||||
user.reset_password_token = rand_str()
|
||||
|
||||
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
||||
user.save()
|
||||
email_template = open("reset_password_email.html", "w",
|
||||
encoding="utf-8").read()
|
||||
email_template = email_template.replace("{{ username }}", user.username). \
|
||||
replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]). \
|
||||
replace("{{ link }}", settings.WEBSITE_INFO["url"] + "/reset_password/t/" +
|
||||
user.reset_password_token)
|
||||
render_data = {
|
||||
"username": user.username,
|
||||
"website_name": config.name,
|
||||
"link": f"{config.base_url}/reset-password/{user.reset_password_token}"
|
||||
}
|
||||
email_html = render_to_string('reset_password_email.html', render_data)
|
||||
send_email_async.delay(config.name,
|
||||
user.email,
|
||||
user.username,
|
||||
config.name + " 登录信息找回邮件",
|
||||
email_template)
|
||||
email_html)
|
||||
return self.success("Succeeded")
|
||||
|
||||
|
||||
@@ -316,8 +298,8 @@ class ResetPasswordAPI(APIView):
|
||||
user = User.objects.get(reset_password_token=data["token"])
|
||||
except User.DoesNotExist:
|
||||
return self.error("Token dose not exist")
|
||||
if 0 < (user.reset_password_token_expire_time - now()).total_seconds() < 30 * 60:
|
||||
return self.error("Token expired")
|
||||
if int((user.reset_password_token_expire_time - now()).total_seconds()) < 0:
|
||||
return self.error("Token have expired")
|
||||
user.reset_password_token = None
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
|
||||
Reference in New Issue
Block a user