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:
zema1
2017-09-12 11:45:17 +08:00
parent 1e4ede6d1a
commit f55a242ec0
15 changed files with 221 additions and 113 deletions

View 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),
),
]

View File

@@ -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)

View File

@@ -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):

View File

@@ -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()