整理代码

This commit is contained in:
zemal 2017-08-20 08:35:59 +08:00
parent 0647312124
commit 3b1f02c356
10 changed files with 200 additions and 268 deletions

View File

@ -15,6 +15,7 @@ class UsernameOrEmailCheckSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30, required=False) username = serializers.CharField(max_length=30, required=False)
email = serializers.EmailField(max_length=30, required=False) email = serializers.EmailField(max_length=30, required=False)
class UserRegisterSerializer(serializers.Serializer): class UserRegisterSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30) username = serializers.CharField(max_length=30)
password = serializers.CharField(max_length=30, min_length=6) password = serializers.CharField(max_length=30, min_length=6)
@ -46,7 +47,6 @@ class UserProfileSerializer(serializers.ModelSerializer):
class UserInfoSerializer(serializers.ModelSerializer): class UserInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = UserProfile model = UserProfile

View File

@ -2,7 +2,8 @@ from django.conf.urls import url
from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI, from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
UserChangePasswordAPI, UserRegisterAPI, UserChangePasswordAPI, UserRegisterAPI,
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck) UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI)
from utils.captcha.views import CaptchaAPIView from utils.captcha.views import CaptchaAPIView
@ -14,5 +15,9 @@ urlpatterns = [
url(r"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"), url(r"^apply_reset_password/?$", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"),
url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api"), url(r"^reset_password/?$", ResetPasswordAPI.as_view(), name="apply_reset_password_api"),
url(r"^captcha/?$", CaptchaAPIView.as_view(), name="show_captcha"), url(r"^captcha/?$", CaptchaAPIView.as_view(), name="show_captcha"),
url(r"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email") url(r"^check_username_or_email", UsernameOrEmailCheck.as_view(), name="check_username_or_email"),
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api")
] ]

View File

@ -1,12 +0,0 @@
from django.conf.urls import url
from ..views.user import (SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI,
UserProfileAPI)
urlpatterns = [
# url(r"^username/?$", UserNameAPI.as_view(), name="user_name_api"),
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
url(r"^sso/?$", SSOAPI.as_view(), name="sso_api"),
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api")
]

View File

@ -1,13 +1,18 @@
import os
import qrcode
from io import BytesIO
from datetime import timedelta from datetime import timedelta
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.core.exceptions import MultipleObjectsReturned
from django.utils.timezone import now from django.utils.timezone import now
from otpauth import OtpAuth from django.http import HttpResponse
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
from conf.models import WebsiteConfig from conf.models import WebsiteConfig
from utils.api import APIView, validate_serializer 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
@ -17,9 +22,153 @@ from ..serializers import (ApplyResetPasswordSerializer,
ResetPasswordSerializer, ResetPasswordSerializer,
UserChangePasswordSerializer, UserLoginSerializer, UserChangePasswordSerializer, UserLoginSerializer,
UserRegisterSerializer, UsernameOrEmailCheckSerializer) UserRegisterSerializer, UsernameOrEmailCheckSerializer)
from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer,
UserProfileSerializer,
EditUserProfileSerializer, AvatarUploadForm)
from ..tasks import send_email_async 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:
user = User.objects.get(username=username, is_disabled=False)
else:
user = request.user
except User.DoesNotExist:
return self.error("User does not exist")
profile = UserProfile.objects.get(user=user)
return self.success(UserProfileSerializer(profile).data)
@validate_serializer(EditUserProfileSerializer)
@login_required
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 暂时不加
user_profile.save()
return self.success("Succeeded")
class AvatarUploadAPI(CSRFExemptAPIView):
request_parsers = ()
def post(self, request):
form = AvatarUploadForm(request.POST, request.FILES)
if form.is_valid():
avatar = form.cleaned_data["file"]
else:
return self.error("Upload failed")
if avatar.size > 1024 * 1024:
return self.error("Picture too large")
if os.path.splitext(avatar.name)[-1].lower() not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
return self.error("Unsupported file format")
name = "avatar_" + rand_str(5) + os.path.splitext(avatar.name)[-1]
with open(os.path.join(settings.IMAGE_UPLOAD_DIR, name), "wb") as img:
for chunk in avatar:
img.write(chunk)
print(os.path.join(settings.IMAGE_UPLOAD_DIR, name))
return self.success({"path": "/static/upload/" + name})
class SSOAPI(APIView):
@login_required
def get(self, request):
callback = request.GET.get("callback", None)
if not callback:
return self.error("Parameter Error")
token = rand_str()
request.user.auth_token = token
request.user.save()
return self.success({"redirect_url": callback + "?token=" + token,
"callback": callback})
@validate_serializer(SSOSerializer)
def post(self, request):
data = request.data
try:
User.objects.get(open_api_appkey=data["appkey"])
except User.DoesNotExist:
return self.error("Invalid appkey")
try:
user = User.objects.get(auth_token=data["token"])
user.auth_token = None
user.save()
return self.success({"username": user.username,
"id": user.id,
"admin_type": user.admin_type,
"avatar": user.userprofile.avatar})
except User.DoesNotExist:
return self.error("User does not exist")
class TwoFactorAuthAPI(APIView):
@login_required
def get(self, request):
"""
Get QR code
"""
user = request.user
if user.two_factor_auth:
return self.error("Already open 2FA")
token = rand_str()
user.tfa_token = token
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")
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def post(self, request):
"""
Open 2FA
"""
code = request.data["code"]
user = request.user
if OtpAuth(user.tfa_token).valid_totp(code):
user.two_factor_auth = True
user.save()
return self.success("Succeeded")
else:
return self.error("Invalid captcha")
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def put(self, request):
code = request.data["code"]
user = request.user
if OtpAuth(user.tfa_token).valid_totp(code):
user.two_factor_auth = False
user.save()
else:
return self.error("Invalid captcha")
class UserLoginAPI(APIView): class UserLoginAPI(APIView):
@validate_serializer(UserLoginSerializer) @validate_serializer(UserLoginSerializer)
def post(self, request): def post(self, request):
@ -89,22 +238,15 @@ class UserRegisterAPI(APIView):
captcha = Captcha(request) captcha = Captcha(request)
if not captcha.validate(data["captcha"]): if not captcha.validate(data["captcha"]):
return self.error("Invalid captcha") return self.error("Invalid captcha")
try: if User.objects.filter(username=data["username"]).exists():
User.objects.get(username=data["username"])
return self.error("Username already exists") return self.error("Username already exists")
except User.DoesNotExist: if User.objects.filter(email=data["email"]).exists():
pass
try:
User.objects.get(email=data["email"])
return self.error("Email already exists") return self.error("Email already exists")
# Some old data has duplicate email
except MultipleObjectsReturned:
return self.error("Email already exists")
except User.DoesNotExist:
user = User.objects.create(username=data["username"], email=data["email"]) user = User.objects.create(username=data["username"], email=data["email"])
user.set_password(data["password"]) user.set_password(data["password"])
user.save() user.save()
UserProfile.objects.create(user=user) UserProfile.objects.create(user=user, time_zone=settings.USER_DEFAULT_TZ)
return self.success("Succeeded") return self.success("Succeeded")
@ -117,7 +259,7 @@ class UserChangePasswordAPI(APIView):
""" """
data = request.data data = request.data
captcha = Captcha(request) captcha = Captcha(request)
if not captcha.check(data["captcha"]): if not captcha.validate(data["captcha"]):
return self.error("Invalid 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"])

View File

@ -1,179 +0,0 @@
import os
from io import BytesIO
import qrcode
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
from otpauth import OtpAuth
from conf.models import WebsiteConfig
from utils.api import APIView, validate_serializer, CSRFExemptAPIView
from utils.shortcuts import rand_str
from ..decorators import login_required
from ..models import User, UserProfile
from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer,
UserProfileSerializer,
EditUserProfileSerializer, AvatarUploadForm)
class UserNameAPI(APIView):
@method_decorator(ensure_csrf_cookie)
def get(self, request):
"""
Return Username to valid login status
"""
try:
user = User.objects.get(id=request.user.id)
except User.DoesNotExist:
return self.success({
"username": "User does not exist",
"isLogin": False
})
return self.success({
"username": user.username,
"isLogin": True
})
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:
user = User.objects.get(username=username, is_disabled=False)
else:
user = request.user
except User.DoesNotExist:
return self.error("User does not exist")
profile = UserProfile.objects.get(user=user)
return self.success(UserProfileSerializer(profile).data)
@validate_serializer(EditUserProfileSerializer)
@login_required
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 暂时不加
user_profile.save()
return self.success("Succeeded")
class AvatarUploadAPI(CSRFExemptAPIView):
request_parsers = ()
def post(self, request):
form = AvatarUploadForm(request.POST, request.FILES)
if form.is_valid():
avatar = form.cleaned_data["file"]
else:
return self.error("Upload failed")
if avatar.size > 1024 * 1024:
return self.error("Picture too large")
if os.path.splitext(avatar.name)[-1].lower() not in [".gif", ".jpg", ".jpeg", ".bmp", ".png"]:
return self.error("Unsupported file format")
name = "avatar_" + rand_str(5) + os.path.splitext(avatar.name)[-1]
with open(os.path.join(settings.IMAGE_UPLOAD_DIR, name), "wb") as img:
for chunk in avatar:
img.write(chunk)
print(os.path.join(settings.IMAGE_UPLOAD_DIR, name))
return self.success({"path": "/static/upload/" + name})
class SSOAPI(APIView):
@login_required
def get(self, request):
callback = request.GET.get("callback", None)
if not callback:
return self.error("Parameter Error")
token = rand_str()
request.user.auth_token = token
request.user.save()
return self.success({"redirect_url": callback + "?token=" + token,
"callback": callback})
@validate_serializer(SSOSerializer)
def post(self, request):
data = request.data
try:
User.objects.get(open_api_appkey=data["appkey"])
except User.DoesNotExist:
return self.error("Invalid appkey")
try:
user = User.objects.get(auth_token=data["token"])
user.auth_token = None
user.save()
return self.success({"username": user.username,
"id": user.id,
"admin_type": user.admin_type,
"avatar": user.userprofile.avatar})
except User.DoesNotExist:
return self.error("User does not exist")
class TwoFactorAuthAPI(APIView):
@login_required
def get(self, request):
"""
Get QR code
"""
user = request.user
if user.two_factor_auth:
return self.error("Already open 2FA")
token = rand_str()
user.tfa_token = token
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")
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def post(self, request):
"""
Open 2FA
"""
code = request.data["code"]
user = request.user
if OtpAuth(user.tfa_token).valid_totp(code):
user.two_factor_auth = True
user.save()
return self.success("Succeeded")
else:
return self.error("Invalid captcha")
@login_required
@validate_serializer(TwoFactorAuthCodeSerializer)
def put(self, request):
code = request.data["code"]
user = request.user
if OtpAuth(user.tfa_token).valid_totp(code):
user.two_factor_auth = False
user.save()
else:
return self.error("Invalid captcha")

View File

@ -10,29 +10,6 @@ DATABASES = {
} }
} }
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"JudgeQueue": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"Throttling": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/3",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# For celery # For celery
REDIS_QUEUE = { REDIS_QUEUE = {

View File

@ -97,6 +97,8 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# in user's profile
USER_DEFAULT_TZ = 'Asia/Shanghai'
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/ # https://docs.djangoproject.com/en/1.8/howto/static-files/

View File

@ -3,7 +3,6 @@ from django.conf.urls import include, url
urlpatterns = [ urlpatterns = [
url(r"^api/", include("account.urls.oj")), url(r"^api/", include("account.urls.oj")),
url(r"^api/admin/", include("account.urls.admin")), url(r"^api/admin/", include("account.urls.admin")),
url(r"^api/account/", include("account.urls.user")),
url(r"^api/admin/", include("announcement.urls.admin")), url(r"^api/admin/", include("announcement.urls.admin")),
url(r"^api/", include("conf.urls.oj")), url(r"^api/", include("conf.urls.oj")),
url(r"^api/admin/", include("conf.urls.admin")), url(r"^api/admin/", include("conf.urls.admin")),

View File

@ -17,30 +17,30 @@ from math import ceil
from six import BytesIO from six import BytesIO
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
__version__ = '0.3.3' __version__ = "0.3.3"
current_path = os.path.normpath(os.path.dirname(__file__)) current_path = os.path.normpath(os.path.dirname(__file__))
class Captcha(object):
class Captcha(object):
def __init__(self, request): def __init__(self, request):
""" something init """ something init
""" """
self.django_request = request self.django_request = request
self.session_key = '_django_captcha_key' self.session_key = "_django_captcha_key"
self.words = ["hello", "word"] self.words = ["hello", "word"]
# image size (pix) # image size (pix)
self.img_width = 150 self.img_width = 150
self.img_height = 30 self.img_height = 30
self.type = 'number' self.type = "number"
self.mode = 'number' self.mode = "number"
def _get_font_size(self): def _get_font_size(self):
s1 = int(self.img_height * 0.8) s1 = int(self.img_height * 0.8)
s2 = int(self.img_width/len(self.code)) s2 = int(self.img_width / len(self.code))
return int(min((s1, s2)) + max((s1, s2))*0.05) return int(min((s1, s2)) + max((s1, s2)) * 0.05)
def _get_words(self): def _get_words(self):
""" words list """ words list
@ -51,7 +51,6 @@ class Captcha(object):
if self.words: if self.words:
return set(self.words) return set(self.words)
def _set_answer(self, answer): def _set_answer(self, answer):
self.django_request.session[self.session_key] = str(answer) self.django_request.session[self.session_key] = str(answer)
@ -86,16 +85,16 @@ class Captcha(object):
""" """
# font color # font color
self.font_color = ['black', 'darkblue', 'darkred'] self.font_color = ["black", "darkblue", "darkred"]
# background color # background color
self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255)) self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255))
# font path # font path
self.font_path = os.path.join(current_path, 'timesbi.ttf') # or Menlo.ttc self.font_path = os.path.join(current_path, "timesbi.ttf") # or Menlo.ttc
self.django_request.session[self.session_key] = '' self.django_request.session[self.session_key] = ""
im = Image.new('RGB', (self.img_width, self.img_height), self.background) im = Image.new("RGB", (self.img_width, self.img_height), self.background)
self.code = self._generate() self.code = self._generate()
# set font size automaticly # set font size automaticly
@ -105,20 +104,20 @@ class Captcha(object):
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
# draw noisy point/line # draw noisy point/line
if self.mode == 'word': if self.mode == "word":
c = int(8/len(self.code)*3) or 3 c = int(8 / len(self.code) * 3) or 3
elif self.mode == 'number': elif self.mode == "number":
c = 4 c = 4
for i in range(random.randrange(c-2, c)): for i in range(random.randrange(c - 2, c)):
line_color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255)) line_color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255))
xy = (random.randrange(0, int(self.img_width*0.2)), random.randrange(0, self.img_height), xy = (random.randrange(0, int(self.img_width * 0.2)), random.randrange(0, self.img_height),
random.randrange(int(3*self.img_width/4), self.img_width), random.randrange(0, self.img_height)) random.randrange(int(3 * self.img_width / 4), self.img_width), random.randrange(0, self.img_height))
draw.line(xy, fill=line_color, width=int(self.font_size*0.1)) draw.line(xy, fill=line_color, width=int(self.font_size * 0.1))
# main part # main part
j = int(self.font_size*0.3) j = int(self.font_size * 0.3)
k = int(self.font_size*0.5) k = int(self.font_size * 0.5)
x = random.randrange(j, k) x = random.randrange(j, k)
for i in self.code: for i in self.code:
@ -126,21 +125,21 @@ class Captcha(object):
m = int(len(self.code)) m = int(len(self.code))
y = random.randrange(1, 3) y = random.randrange(1, 3)
if i in ('+', '=', '?'): if i in ("+", "=", "?"):
# 对计算符号等特殊字符放大处理 # 对计算符号等特殊字符放大处理
m = ceil(self.font_size*0.8) m = ceil(self.font_size * 0.8)
else: else:
# 字体大小变化量,字数越少,字体大小变化越多 # 字体大小变化量,字数越少,字体大小变化越多
m = random.randrange(0, int(45 / self.font_size) + int(self.font_size/5)) m = random.randrange(0, int(45 / self.font_size) + int(self.font_size / 5))
self.font = ImageFont.truetype(self.font_path.replace('\\', '/'), self.font_size + int(ceil(m))) self.font = ImageFont.truetype(self.font_path.replace("\\", "/"), self.font_size + int(ceil(m)))
draw.text((x, y), i, font=self.font, fill=random.choice(self.font_color)) draw.text((x, y), i, font=self.font, fill=random.choice(self.font_color))
x += self.font_size*0.9 x += self.font_size * 0.9
del x del x
del draw del draw
with BytesIO() as buf: with BytesIO() as buf:
im.save(buf, 'gif') im.save(buf, "gif")
buf_str = buf.getvalue() buf_str = buf.getvalue()
return buf_str return buf_str
@ -152,7 +151,6 @@ class Captcha(object):
return False return False
code = code.strip() code = code.strip()
_code = self.django_request.session.get(self.session_key) or '' _code = self.django_request.session.get(self.session_key) or ""
self.django_request.session[self.session_key] = '' self.django_request.session[self.session_key] = ""
return _code.lower() == str(code).lower() return _code.lower() == str(code).lower()

View File

@ -13,7 +13,7 @@ class Command(BaseCommand):
"would you like to reset it's password?\n" "would you like to reset it's password?\n"
"Input yes to confirm: ")) "Input yes to confirm: "))
if input() == "yes": if input() == "yes":
# for dev # todo remove this in product env
# rand_password = rand_str(length=6) # rand_password = rand_str(length=6)
rand_password = "rootroot" rand_password = "rootroot"
admin.save() admin.save()