用户注册登录功能
表结构
UserProfile
class UserProfile(AbstractUser):
name = models.CharField(max_length=30,null=True,blank=True,verbose_name="姓名")
birthday = models.DateField(null=True,blank=True,verbose_name="出生年月")
mobile = models.CharField(blank=True,null=True,max_length=11,verbose_name="手机号")
gender = models.CharField(max_length=6,choices=(("male","男"),("female","女")),default="male")
email = models.EmailField(max_length=100,null=True,blank=True,verbose_name="邮箱")
class Meta:
verbose_name = "用户"
verbose_name_plural=verbose_name
def __str__(self):
return self.username
这个表是继承了Django的auth认证的user表
具体实现方式
-
导包
-
from django.contrib.auth.models import AbstractUser
-
-
让自己的UserProfile类继承AbstractUser,并添加自己的扩展字段
-
在settings里配置:
AUTH_USER_MODEL = "users.UserProfile"
VerifyCode
class VerifyCode(models.Model):
code = models.CharField(max_length=10,verbose_name="验证码")
mobile = models.CharField(max_length=11, verbose_name="手机号")
add_time = models.DateTimeField(default=datetime.now,verbose_name="添加时间")
class Meta:
verbose_name="短信验证码"
verbose_name_plural=verbose_name
def __str__(self):
return self.code
主要存的是code,mobil,用来存验证码的
准备工作
Django有一个自带的登录认证方式,默认是验证username和password是否正确,但是我们是也要通过手机号来注册的
所以我们要重新配置
在 users/views
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
# 自定义用户验证,需要继承ModelBackend,并重新写authenticate方法
class CustomBackend(ModelBackend):
"""
自定义用户验证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
# 在这里我把获取user的验证改了,用Q函数,可以通过username=username或mobile=username的方式获取user
user = UserModel.objects.get(Q(username=username) | Q(mobile=username))
print(user)
#user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
在settings配置
# 自定义用户验证认证类
AUTHENTICATION_BACKENDS = ('users.views.CustomBackend',) # 元组,里面是类的路径(是一个元组,后边的逗号一定要加,不然报错)
接口
/code/?mobile=手机号--->get
serializers
class CodeSerializers(serializers.Serializer):
mobile = serializers.CharField(max_length=11,required=True,error_messages={
"max_length":"手机号不合法",
"required":"请输入手机号"
})
def validate_mobile(self, data):
RE = '^[1]([3-9])[0-9]{9}$'
# 验证手机是否注册
if UserModel.objects.filter(mobile=data).count():
raise serializers.ValidationError("手机号码已经注册" ,code="Phone aready exsit")
flag = re.match(RE,data)
if not flag:
raise serializers.ValidationError("手机号码输入不合法",code="No valid")
one_min = datetime.datetime.now() - datetime.timedelta(hours=0,minutes=1,seconds=0)
# 限制频率
if VerifyCode.objects.filter(add_time__gt=one_min,mobile=data):
raise serializers.ValidationError(detail="一分钟之内不能重复发送..",code="No repeat send")
return data
views
# 验证码
class CodeView(APIView):
def seed_random_code(self):
seeds = list('0123456789')
random_code = ""
for i in range(4):
seed = random.choice(seeds)
random_code = random_code+seed
return random_code
def get(self,request):
# 获取mobile
mobile = request.query_params.get("mobile",None)
# 获取序列化器
ser = CodeSerializers(data={"mobile":mobile})
# 验证
ser.is_valid(raise_exception=True)
# 发送验证码
code = self.seed_random_code()
print(f"----{mobile}----{code}")
CodeCls = alicode.AliCode(mobile,code=code)
res = CodeCls.send_code()
print(res.body.message)
# 入库
if res.body.code=='OK':
instance = VerifyCode.objects.create(code=code,mobile=mobile)
instance.save()
return Response(data=res.body.message,status=status.HTTP_200_OK)
简单的描述: 前端携带mobile参数请求,后端验证,最后存入数据库
/reg/
post
参数:
-
unsername
-
password
-
code
后端逻辑: 拿到这三个参数,但是你要知道,这里的username存的是手机号,所以我们后端要在序列化器哪里,将username存入mobile字段里
同时username字段也是手机号,并把password加密,然后在存入数据库
serializers
class UserRegSerializers(serializers.ModelSerializer):
code = serializers.CharField(max_length=4,min_length=4,required=True,write_only=True)
mobile = serializers.CharField(max_length=11, allow_blank=True,allow_null=True,required=False)
password = serializers.CharField(
style={"input_type":'password'}, # input密码框的type=password
label="密码",
write_only=True
)
def validate_code(self, code):
# 访问数据库
recodeCode = VerifyCode.objects.filter(code=code,mobile=self.initial_data['username']).order_by('-add_time')
print(recodeCode)
if recodeCode:
# 取出最后一次验证码
last_code = recodeCode[0]
one_five_min_ago = datetime.datetime.now()- datetime.timedelta(hours=0, minutes=5, seconds=0)
# 如果 验证码超过5分钟
# 这个是没有办法比的,应为两个时间的格式不一样,详情看后面
if last_code.add_time < one_five_min_ago:
raise serializers.ValidationError(detail="验证码超时",code="code timeout")
else:
# 比对验证码是否正确
if last_code.code!=code:
raise serializers.ValidationError(detail="验证码错误",code="code not right")
return code
else:
raise serializers.ValidationError(detail="验证失败")
def validate(self, attrs):
print("validataed")
attrs['mobile'] = attrs['username']
del attrs['code']
print(attrs)
return attrs
def create(self, validated_data):
users = super().create(validated_data=validated_data)
users.set_password(validated_data['password'])
users.save()
return users
class Meta:
model = UserProfile
fields = ['username','mobile','code','password']
views
# User用户注册
class UserRegVIPViews(CreateModelMixin,GenericViewSet):
queryset = UserProfile
serializer_class = UserRegSerializers
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
# 携带用户名和taken
res_dict = serializer.data
payload = jwt_payload_handler(user)
res_dict['token'] = jwt_encode_handler(payload)
res_dict['name'] = user.name if user.name else user.username
# 上面的几行代码很重要,是想前端返回token和name的,要存入浏览器的
headers = self.get_success_headers(serializer.data)
return Response(res_dict, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
views的导包:
views导包
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework.viewsets import GenericViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_jwt.serializers import jwt_encode_handler,jwt_payload_handler
from apps.users.models import VerifyCode,UserProfile
from apps.users.serializers import CodeSerializers,UserRegSerializers
from apps.users.utils import alicode
import random
关于序列化器code时间不能超过5分钟,否则失效,我们可以时间比较大小,但是这里有坑:问题解决参考网址
问题描述
>>> import datetime
>>> from django.utils import timezone
>>> naive = datetime.datetime.utcnow()
>>> aware = timezone.now()
>>> naive == aware
Traceback (most recent call last):
...
TypeError: can't compare offset-naive and offset-aware datetimes
解决问题
当遇到这种问题,首先要了解一下 offset-naive 和 offset-aware 是如何生成的
-
由Django生成的datetime,例如,从model的字段中读取的值,当Django开启时区支持USE_TZ = True,该值就是
aware -
由Python原生生成的datetime,就是
naive
因此,问题的解决就是改变一下datetime即可,aware=>naive 或者 naive=>aware
创建一个aware datetime
>>> import pytz
>>> aware = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
这样就生成了一个UTC时区的创建一个 aware datetime
另外一个方法
在Django中一个配置USE_TZ,该配置会影响django.utils.timezone.now()函数的返回,当 USE_TZ = False 返回 naive datetime,当 USE_TZ = True 返回 aware datetime
因此, 配置 USE_TZ = False 后即可与 datetime.timedelta 等进行计算比较等操作
这里用的是阿里云的短信发送测试功能,我把它封装成一个类
uers/utils/alicode
from typing import List
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
class Sample:
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> Dysmsapi20170525Client:
"""
使用AK&SK初始化账号Client
@param access_key_id:
@param access_key_secret:
@return: Client
@throws Exception
"""
config = open_api_models.Config(
# 您的AccessKey ID,
access_key_id=access_key_id,
# 您的AccessKey Secret,
access_key_secret=access_key_secret
)
# 访问的域名
config.endpoint = f'dysmsapi.aliyuncs.com'
return Dysmsapi20170525Client(config)
class AliCode:
def __init__(self,moble,code):
self.moble = moble
code_dict = {"code":code}
self.code = code_dict
def send_code(self):
# accessKeyId, accessKeySecret需要自己去阿里云获取
client = Sample.create_client('accessKeyId', 'accessKeySecret')
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
sign_name='阿里云短信测试',
template_code='SMS_154950909',
phone_numbers=self.moble,
template_param=f'{self.code}'
)
# 发送code
res = client.send_sms(send_sms_request)
return res
浙公网安备 33010602011771号