DRF 商城項目 - 用戶( 登陸, 註冊,登出,我的中心 ) 邏輯梳理

用戶登陸

自定義用戶登陸字段處理

用戶的登陸時經過 手機號也能夠進行登陸html

須要重寫登陸驗證邏輯前端

from django.contrib.auth.backends import ModelBackend

class CustomBackend(ModelBackend):

    def authenticate(self, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username) | Q(mobile=username))
            # 前端的用戶傳遞過來的密碼和數據庫的保存密碼是不一致的, 所以須要使用 check_password 的方式進行比對
            if user.check_password(password):
                return user
        except Exception as e:
            return None

登陸邏輯

經過 login 接口進入驗證, 調用默認重寫後的驗證邏輯進行處理python

 url(r'^login/', obtain_jwt_token)

驗證成功後會返回 token數據庫

用戶註冊

用戶註冊基於 手機號註冊django

驗證碼發送基於 雲片網 提供的技術支持json

驗證碼邏輯

驗證碼API 接口

# 配置手機驗證碼發送 的 url
router.register(r'codes', SmsCodeViewset, base_name="codes")

驗證碼序列化組件

選取序列化方式的時候覺得不是所有的字段都須要用上, 所以不需用到 ModelSerializer後端

須要對前端拿到的  mobile 字段進行相關的驗證api

是否註冊, 是否合法, 以及頻率限制服務器

# 手機驗證序列化組件
# 不使用 ModelSerializer, 並不須要全部的字段, 會有麻煩
class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    # 驗證手機號碼
    # validate_ + 字段名 的格式命名
    def validate_mobile(self, mobile):

        # 手機是否註冊
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("用戶已經存在")

        # 驗證手機號碼是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("手機號碼非法")

        # 驗證碼發送頻率
        # 當前時間減去一分鐘( 倒退一分鐘 ), 而後發送時間要大於這個時間, 表示還在一分鐘內
        one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
            raise serializers.ValidationError("距離上一次發送未超過60s")
        return mobile

驗證碼視圖

視圖主要處理 驗證碼生成發送相關邏輯cookie

具體的雲片網接口對接處理詳情官網查閱

# 發送短信驗證碼
class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
    serializer_class = SmsSerializer

    # 生成四位數字的驗證碼
    def generate_code(self):

        seeds = "1234567890"
        random_str = []
        for i in range(4):
            random_str.append(choice(seeds))
        return "".join(random_str)

    # 重寫 create 方法
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 驗證後便可取出數據
        mobile = serializer.validated_data["mobile"]
        yun_pian = YunPian(APIKEY)
        code = self.generate_code()
        sms_status = yun_pian.send_sms(code=code, mobile=mobile)

        if sms_status["code"] != 0:
            return Response({
                "mobile": sms_status["msg"]
            }, status=status.HTTP_400_BAD_REQUEST)
        else:
            # 確認無誤後須要保存數據庫中
            code_record = VerifyCode(code=code, mobile=mobile)
            code_record.save()
            return Response({
                "mobile": mobile
            }, status=status.HTTP_201_CREATED)

雲片驗證碼工具文件

# _*_ coding:utf-8 _*_
from YtShop.settings import APIKEY

__author__ = "yangtuo"
__date__ = "2019/4/15 20:25"
import requests
import json


# 雲片網短信發送功能類
class YunPian(object):

    def __init__(self, api_key):
        self.api_key = api_key
        self.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json"

    def send_sms(self, code, mobile):
        parmas = {
            "apikey": self.api_key,
            "mobile": mobile,
            "text": "您的驗證碼是{code}。如非本人操做,請忽略本短信".format(code=code)
        }

        response = requests.post(self.single_send_url, data=parmas)
        re_dict = json.loads(response.text)
        return re_dict


if __name__ == "__main__":
    yun_pian = YunPian(APIKEY)
    yun_pian.send_sms("2019", "")  # 參數爲 code 以及 mobile

配置文件

須要用到兩個配置添加

# 手機號碼的驗證正則式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"

# 雲片網的 APIKEY 設置
APIKEY = "2480f562xxxxxxxxxxxxxcb7673f8"

註冊邏輯

註冊 API 接口

# 配置用戶註冊的 url
router.register(r'users', UserViewset, base_name="users")

註冊序列化組件

用戶註冊須要的字段較多

每一個字段都有些獨有的特殊裁定

用戶名  要進行重複判斷

驗證碼  要進行有效期, 正確性判斷

密碼  設置 輸入框爲密碼格式

在最後回傳的時候 code 是不須要的, 所以能夠刪除掉

# 用戶註冊
class UserRegSerializer(serializers.ModelSerializer):
    """
    max_length      最大長度
    min_length      最小長度
    label           顯示名字
    help_text       幫助提示信息
    error_messages  錯誤類型映射提示
        blank         空字段提示
        required      必填字段提示
        max_length    超長度提示
        min_length    太短提示
    write_only      只讀, 序列化的時候忽略字段, 再也不返回給前端頁面, 用於去除關鍵信息(密碼等)或者某些沒必要要字段(驗證碼)
    style           更改輸入標籤顯示類型
    validators      能夠指明一些默認的約束類
        UniqueValidator             約束惟一
        UniqueTogetherValidator     聯合約束惟一
        UniqueForMonthValidator
        UniqueForDateValidator
        UniqueForYearValidator
        ....
    """
    code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4, label="驗證碼",
                                 error_messages={
                                     "blank": "請輸入驗證碼",
                                     "required": "請輸入驗證碼",
                                     "max_length": "驗證碼格式錯誤",
                                     "min_length": "驗證碼格式錯誤"
                                 },
                                 help_text="驗證碼")

    # validators 能夠指明一些默認的約束類, 此處的 UniqueValidator 表示惟一約束限制不能重名
    username = serializers.CharField(label="用戶名", help_text="用戶名", required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")])

    # style 能夠設置爲密文狀態
    password = serializers.CharField(
        style={'input_type': 'password'}, help_text="密碼", label="密碼", write_only=True,
    )

    # 用戶表中的 password 是須要加密後再保存的, 次數須要重寫一次 create 方法
    # 固然也能夠不這樣作, 這裏的操做利用 django 的信號來處理, 詳情見 signals.py
    # def create(self, validated_data):
    #     user = super(UserRegSerializer, self).create(validated_data=validated_data)
    #     user.set_password(validated_data["password"])
    #     user.save()
    #     return user

    # 對驗證碼的驗證處理
    # validate_ + 字段對個別字段進行單一處理
    def validate_code(self, code):

        # 若是使用 get 方式須要處理兩個異常, 分別是查找到多個信息的狀況以及查詢到0信息的狀況的異常
        # 可是使用 filter 方式查到多個就以列表方式返回, 若是查詢不到數據就會返回空值, 各方面都很方便
        # try:
        #     verify_records = VerifyCode.objects.get(mobile=self.initial_data["username"], code=code)
        # except VerifyCode.DoesNotExist as e:
        #     pass
        # except VerifyCode.MultipleObjectsReturned as e:
        #     pass

        # 前端傳過來的全部的數據都在, initial_data 字典裏面, 若是是驗證經過的數據則保存在 validated_data 字典中
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_record = verify_records[0]  # 時間倒敘排序後的的第一條就是最新的一條
            # 當前時間回退5分鐘
            five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            # 最後一條短信記錄的發出時間小於5分鐘前, 表示是5分鐘前發送的, 表示過時
            if five_mintes_ago > last_record.add_time:
                raise serializers.ValidationError("驗證碼過時")
            # 根據記錄的 驗證碼 比對判斷
            if last_record.code != code:
                raise serializers.ValidationError("驗證碼錯誤")
            # return code  # 不必保存驗證碼記錄, 僅僅是用做驗證
        else:
            raise serializers.ValidationError("驗證碼錯誤")

    # 對全部的字段進行限制
    def validate(self, attrs):
        attrs["mobile"] = attrs["username"]  # 重命名一下
        del attrs["code"]  # 刪除無用字段
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile", "password")

註冊視圖

class UserViewset(CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    serializer_class = UserRegSerializer
    queryset = User.objects.all()

    # 重寫 create 函數來完成註冊後自動登陸功能
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
        
        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        # token 的添加只能用此方法, 此方法經過源碼閱讀查找到位置爲
        re_dict["token"] = jwt_encode_handler(payload)
        # 自定義一個字段加入進去
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

    def get_object(self):
        return self.request.user

    def perform_create(self, serializer):
        return serializer.save()

信號量處理工具文件

註冊後的信息回傳給數據庫保存的時候 密碼是按照是未加密狀態保存

此處須要進行加密後才能夠, 所以這裏能夠用信號量來處理, post_save 觸發

在此觸發流程中完成加密後保存數據庫

# _*_ coding:utf-8 _*_
__author__ = "yangtuo"
__date__ = "2019/4/15 20:25"

from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model

User = get_user_model()


@receiver(post_save, sender=User)  # post_save 信號類型, sender 能觸發信號的模型
def create_user(sender, instance=None, created=False, **kwargs):    # created 是否新建( update 就不會被識別 )
    # instance 表示保存對象, 在這裏是被保存的 user 對象
    if created:
        password = instance.password
        instance.set_password(password)
        instance.save()
        # Token.objects.create(user=instance)
        # user 對象的保存通常是要伴隨着 token 的, 這裏已經使用 JWT 方式了, 所以就不須要這種 token 了.

註冊後自動登陸邏輯

目標預期

用戶註冊後自動跳轉到主頁

同時要實現註冊用戶已登陸狀態

需求分析

用戶註冊相關的操做本質是從前端拿到數據傳送到後端經過 相關的 view 進行操做

本質是 底層的 create 方法, 默認的方法只能實現用戶建立沒法實現其餘附加

( DRF 的視圖 功能嵌套 層次詳情點擊 這裏查看  )

所以咱們須要重寫 create 方法 

定位重寫 create 方法

可見只有序列化類的更新和推送, 無其餘功能

默認的 create 方法

若是想實現自動登陸, 首先本質就是加入用戶登陸的狀態, 即 token 的生成和保存

本次項目使用的是 JWT 做爲 token 方案, 所以 須要考究在 JWT 的源碼中 token 如何生成

定位 token 生成源碼查閱

JWT 的源碼入口 ( URL 對接視圖 )

往上找到視圖類

這裏是作了一層很簡單的封裝, 以及能夠看到熟悉的 as_view()

不過咱們目前不關心這個, 這裏一樣基於 DRF 視圖中相似

視圖類中找到序列化處理

 這個 serializer_class 就是對應着序列化類的處理

 

序列化處理中對 token 的處理

其實咱們已經知道了JWT 的方式是不會基於數據庫的, 所以他們的序列化類中的是沒有任何的字段

經過各類方法來實現字段的計算和生成

如下是所有的 相關邏輯

class JSONWebTokenSerializer(Serializer):
    """
    Serializer class used to validate a username and password.

    'username' is identified by the custom UserModel.USERNAME_FIELD.

    Returns a JSON Web Token that can be used to authenticate later calls.
    """
    def __init__(self, *args, **kwargs):
        """
        Dynamically add the USERNAME_FIELD to self.fields.
        """
        super(JSONWebTokenSerializer, self).__init__(*args, **kwargs)

        self.fields[self.username_field] = serializers.CharField()
        self.fields['password'] = PasswordField(write_only=True)

    @property
    def username_field(self):
        return get_username_field()

    def validate(self, attrs):
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            user = authenticate(**credentials)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                payload = jwt_payload_handler(user)

                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
            else:
                msg = _('Unable to log in with provided credentials.')
                raise serializers.ValidationError(msg)
        else:
            msg = _('Must include "{username_field}" and "password".')
            msg = msg.format(username_field=self.username_field)
            raise serializers.ValidationError(msg)

定位到 token 的生成代碼

可見 須要使用到 jwt_payload_handler 方法以及 jwt_encode_handler 方法

所以生成 token 就是在這裏了, 爲了生成 token 咱們須要用到這兩個方法, 使用方法就徹底模仿源碼便可

完成 create 重寫

from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler

class UserViewset(CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    serializer_class = UserRegSerializer
    queryset = User.objects.all()

    # 重寫 create 函數來完成註冊後自動登陸功能
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
   
       # 此處爲自定義的 token 的生成 
        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        re_dict["token"] = jwt_encode_handler(payload)
        # 順便把 用戶名一併傳過去
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

    def get_object(self):
        return self.request.user

    def perform_create(self, serializer):
        return serializer.save()

用戶退出

不須要再寫一個 logout 接口 

JWT 不須要服務器這邊進行相關的操做

只須要前端進行一個 cookie 的清空而後跳轉便可

跳轉到 登陸頁面或者主頁皆可

    loginOut(){
        cookie.delCookie('token');
        cookie.delCookie('name');
        //從新觸發store
        //更新store數據
        this.$store.dispatch('setInfo');
        //跳轉到登陸
        this.$router.push({name: 'login'})
      },

用戶我的中心 

retrieve 方式添加

用戶中心的數據來源是對單一用戶的詳細數據請求, 所以須要在原有基礎上加上對  retrieve 的處理

 mixins.RetrieveModelMixin

用戶 id 傳遞

同時由於對單一用戶的請求須要指明用戶id, 有兩種方式能夠傳遞

第一種 直接在數據裏面提供當前用戶 id

第二種 重寫 get_object 獲取當前用戶

# 由於要涉及到 我的中心的操做須要傳遞過去 用戶的 id, 重寫 get_object 來實現
    def get_object(self):
        return self.request.user

權限分離

用戶中心必須指定當前用戶只能訪問本身, 所以須要對是否登陸進行驗證

可是當前視圖的其餘類型請求好比 create 的註冊則不須要進行驗證, 所以  permission_classes 沒法知足需求

源碼剖析

在繼承了  ViewSetMixin 以後內部的 initialize_request 方面裏面的 提供了 .action 在 request 中能夠對請求類型進行分離

同時 APIView 內部的  get_permissions  方法負責提取認證類型, 所以重寫此方法便可完成

 

此爲 源碼, 可見是直接使用一個列表表達式來獲取當前視圖的 permission_classes 裏面的全部認證方式

實現重寫

基於咱們本身的需求進行重寫, 利用 action 進行分流

注意其餘未設置的最後必定要返回空

    # permission_classes = (permissions.IsAuthenticated, )  # 由於根據類型的不一樣權限的認證也不一樣, 不能再統一設置了
    def get_permissions(self):
        if self.action == "retrieve":
            return [permissions.IsAuthenticated()]
        elif self.action == "create":
            return []
        return []

序列化組件分離

建立組件

以前設置的序列化組件是爲了註冊用的, 只採集了註冊相關的字段, 沒法知足用戶中心的其餘字段處理

所以須要從新設置一個用戶詳情的 序列化組件

# 用戶詳情信息序列化類
class UserDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("name", "gender", "birthday", "email", "mobile")

源碼剖析

一樣是基於對 action 的方法進行分流, 對於 action 的位置在 權限分流的部分有圖,

在   GenericAPIView 中存在 get_serializer_class  方法, 用於獲取當前視圖中的 序列化組件

實現重寫

基於 action 進行分流, 而後進行對 get_serializer_class 進行重寫

實現方式相似於 權限的分流

   def get_serializer_class(self):
        if self.action == "retrieve":
            return UserDetailSerializer
        elif self.action == "create":
            return UserRegSerializer
        return UserDetailSerializer

完整代碼

用戶視圖代碼

# 用戶視圖
class UserViewset(mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    serializer_class = UserRegSerializer
    queryset = User.objects.all()

    authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication)

    # 用戶中心的我的詳情數據不能再基於統一設置的 UserRegSerializer 了
    # 用戶註冊和 用戶詳情分爲了兩個序列化組件
    # self.action 必需要繼承了 ViewSetMixin 纔有此功能
    # get_serializer_class 的源碼位置在 GenericAPIView 中
    def get_serializer_class(self):
        if self.action == "retrieve":
            return UserDetailSerializer
        elif self.action == "create":
            return UserRegSerializer
        return UserDetailSerializer

    # permission_classes = (permissions.IsAuthenticated, )  # 由於根據類型的不一樣權限的認證也不一樣, 不能再統一設置了
    # get_permissions 的源碼在 APIview 中
    def get_permissions(self):
        if self.action == "retrieve":
            return [permissions.IsAuthenticated()]
        elif self.action == "create":
            return []
        return []

    # 重寫 create 函數來完成註冊後自動登陸功能
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)

        """
        此處重寫的源碼分析以及 相關的邏輯
        詳情點擊此博客 
        https://www.cnblogs.com/shijieli/p/10726194.html
        """
        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        # token 的添加只能用此方法, 此方法經過源碼閱讀查找到位置爲
        re_dict["token"] = jwt_encode_handler(payload)
        # 自定義一個字段加入進去
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

    # 由於要涉及到 我的中心的操做須要傳遞過去 用戶的 id, 重寫 get_object 來實現
    def get_object(self):
        return self.request.user

    def perform_create(self, serializer):
        return serializer.save()

用戶相關序列化組件

# _*_ coding:utf-8 _*_
__author__ = "yangtuo"
__date__ = "2019/4/15 20:25"

import re
from rest_framework import serializers
from django.contrib.auth import get_user_model
from datetime import datetime
from datetime import timedelta
from rest_framework.validators import UniqueValidator

from .models import VerifyCode
from YtShop.settings import REGEX_MOBILE

User = get_user_model()


# 手機驗證序列化組件
# 不使用 ModelSerializer, 並不須要全部的字段, 會有麻煩
class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    # 驗證手機號碼
    # validate_ + 字段名 的格式命名
    def validate_mobile(self, mobile):

        # 手機是否註冊
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("用戶已經存在")

        # 驗證手機號碼是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("手機號碼非法")

        # 驗證碼發送頻率
        # 當前時間減去一分鐘( 倒退一分鐘 ), 而後發送時間要大於這個時間, 表示還在一分鐘內
        one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
            raise serializers.ValidationError("距離上一次發送未超過60s")
        return mobile


# 用戶詳情信息序列化類
class UserDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("name", "gender", "birthday", "email", "mobile")


# 用戶註冊
class UserRegSerializer(serializers.ModelSerializer):
    """
    max_length      最大長度
    min_length      最小長度
    label           顯示名字
    help_text       幫助提示信息
    error_messages  錯誤類型映射提示
        blank         空字段提示
        required      必填字段提示
        max_length    超長度提示
        min_length    太短提示
    write_only      只讀, 序列化的時候忽略字段, 再也不返回給前端頁面, 用於去除關鍵信息(密碼等)或者某些沒必要要字段(驗證碼)
    style           更改輸入標籤顯示類型
    validators      能夠指明一些默認的約束類
        UniqueValidator             約束惟一
        UniqueTogetherValidator     聯合約束惟一
        UniqueForMonthValidator
        UniqueForDateValidator
        UniqueForYearValidator
        ....
    """
    code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4, label="驗證碼",
                                 error_messages={
                                     "blank": "請輸入驗證碼",
                                     "required": "請輸入驗證碼",
                                     "max_length": "驗證碼格式錯誤",
                                     "min_length": "驗證碼格式錯誤"
                                 },
                                 help_text="驗證碼")

    # validators 能夠指明一些默認的約束類, 此處的 UniqueValidator 表示惟一約束限制不能重名
    username = serializers.CharField(label="用戶名", help_text="用戶名", required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")])

    # style 能夠設置爲密文狀態
    password = serializers.CharField(
        style={'input_type': 'password'}, help_text="密碼", label="密碼", write_only=True,
    )

    # 用戶表中的 password 是須要加密後再保存的, 次數須要重寫一次 create 方法
    # 固然也能夠不這樣作, 這裏的操做利用 django 的信號來處理, 詳情見 signals.py
    # def create(self, validated_data):
    #     user = super(UserRegSerializer, self).create(validated_data=validated_data)
    #     user.set_password(validated_data["password"])
    #     user.save()
    #     return user

    # 對驗證碼的驗證處理
    # validate_ + 字段對個別字段進行單一處理
    def validate_code(self, code):

        # 若是使用 get 方式須要處理兩個異常, 分別是查找到多個信息的狀況以及查詢到0信息的狀況的異常
        # 可是使用 filter 方式查到多個就以列表方式返回, 若是查詢不到數據就會返回空值, 各方面都很方便
        # try:
        #     verify_records = VerifyCode.objects.get(mobile=self.initial_data["username"], code=code)
        # except VerifyCode.DoesNotExist as e:
        #     pass
        # except VerifyCode.MultipleObjectsReturned as e:
        #     pass

        # 前端傳過來的全部的數據都在, initial_data 字典裏面 ,
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_record = verify_records[0]  # 時間倒敘排序後的的第一條就是最新的一條
            # 當前時間回退5分鐘
            five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            # 最後一條短信記錄的發出時間小於5分鐘前, 表示是5分鐘前發送的, 表示過時
            if five_mintes_ago > last_record.add_time:
                raise serializers.ValidationError("驗證碼過時")
            # 根據記錄的 驗證碼 比對判斷
            if last_record.code != code:
                raise serializers.ValidationError("驗證碼錯誤")
            # return code  # 不必保存驗證碼記錄, 僅僅是用做驗證
        else:
            raise serializers.ValidationError("驗證碼錯誤")

    # 對全部的字段進行限制
    def validate(self, attrs):
        attrs["mobile"] = attrs["username"]  # 重命名一下
        del attrs["code"]  # 刪除無用字段
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile", "password")
相關文章
相關標籤/搜索