7.寻光集后台管理系统-用户管理(登录视图)

在上一章中已经完成了注册的接口了,现在需要完成登录

因为登录采用了JWT方式进行校验,所以需要继承rest_framework_simplejwt.views中的视图

登录

代码如下

from rest_framework_simplejwt.views import TokenObtainPairView

class LoginView(TokenObtainPairView):
    """
    登录视图
    """
    serializer_class = MyTokenObtainPairSerializer

测试

使用postman进行测试

请求地址:http://127.0.0.1:8000/users/login/

请求方式:POST

请求参数:

{
    "username": "zhongxin",
    "password": "123456"
}

请求结果

{
    "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2MDk2NDg1OCwiaWF0IjoxNjYwODc4NDU4LCJqdGkiOiI0Njg3MzAyZGE3ZjM0NzlkODE0NmUxNzU4ZTA0M2E0ZCIsInVzZXJfaWQiOjF9.V2R9h1NDXu33vmiM5rcGOv5xpODK-37sD-_GcWmxn8Q",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjYwOTY0ODU4LCJpYXQiOjE2NjA4Nzg0NTgsImp0aSI6IjZjYjZhYzc0NDA4ZjRiYzdhMzlmM2Q0ODBhNGQyYmUyIiwidXNlcl9pZCI6MX0.dg2mKJXq1pWV-80RIWzriaFmnf2AgsaTpl0bgkVpb3g",
    "userInfo": {
        "userId": 1,
        "userName": "测试游记",
        "dashboard": "0",
        "is_superuser": false,
        "role": []
    }
}
8b6f1b35d78fd6cc7c946bac77b90ea4.jpeg

分析

其中serializer_class指定为之前写的序列化器

继承的TokenObtainPairView需要分析一下

TokenViewBase

查看一下它的代码

Takes a set of user credentials and returns an access and refresh JSON web  
token pair to prove the authentication of those credentials.

获取一组用户凭据并返回访问和刷新json web令牌对,以证明这些凭据的身份验证。

class TokenObtainPairView(TokenViewBase):
    """
    Takes a set of user credentials and returns an access and refresh JSON web
    token pair to prove the authentication of those credentials.
    """

    _serializer_class = api_settings.TOKEN_OBTAIN_SERIALIZER

它也有一个_serializer_class

"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",

查看它的序列化器rest_framework_simplejwt.serializers.TokenObtainPairSerializer

class TokenObtainPairSerializer(TokenObtainSerializer):
    token_class = RefreshToken

    def validate(self, attrs):
        data = super().validate(attrs)

        refresh = self.get_token(self.user)

        data["refresh"] = str(refresh)
        data["access"] = str(refresh.access_token)

        if api_settings.UPDATE_LAST_LOGIN:
            update_last_login(None, self.user)

        return data

它的继承关系图

f86d79ec6daf91939ae5846b27aab17b.jpeg

这里比较复杂,所以只先关注get_tokenupdate_last_login

get_token

这个方法来自父类

@classmethod
def get_token(cls, user):
    return cls.token_class.for_user(user)

其中token_class在父类是None,在子类为RefreshToken

Token中的for_user
@classmethod
def for_user(cls, user):
    """
    Returns an authorization token for the given user that will be provided
    after authenticating the user's credentials.
    """
    user_id = getattr(user, api_settings.USER_ID_FIELD)
    if not isinstance(user_id, int):
        user_id = str(user_id)

    token = cls()
    token[api_settings.USER_ID_CLAIM] = user_id

    return token

api_settings

"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",

没有特殊修改的话就是

token["user_id"] = user.id

另外的字段在token = cls()中生成

rest_framework_simplejwt.tokens.Token.__init__

里面不细看了,总之会返回这样一个token

a1d590f897767101821d4b6f82392685.jpeg

update_last_login

如果配置了UPDATE_LAST_LOGIN则会触发update_last_login操作

def update_last_login(sender, user, **kwargs):
    """
    A signal receiver which updates the last_login date for
    the user logging in.
    """
    user.last_login = timezone.now()
    user.save(update_fields=['last_login'])

它就是获取了当前时间,并把该时间记录为最后登录的时间

要让它生效的话,修改下backend/LightSeeking/settings.py中的SIMPLE_JWT

# JWT配置
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=1),  # token过期时间1天
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    "UPDATE_LAST_LOGIN": True,  # 记录最后登录时间
}

发起请求后查看数据库的last_login字段,发现时间变为了当前时间(时区为0)

378dc7a244ff3a5ccf23126b82922700.jpeg

异常处理

之前在backend/LightSeeking/settings.pyREST_FRAMEWORK写了

# DRF的配置
REST_FRAMEWORK = {
    ...
    # 异常处理
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler'
}

说明我们需要自定义DRF的异常处理方法

原来的异常处理方法可以见DRF的settings.py:venv/lib/python3.9/site-packages/rest_framework/settings.py

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
def exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.

    By default we handle the REST framework `APIException`, and also
    Django's built-in `Http404` and `PermissionDenied` exceptions.

    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    return None

我们仿照一下在backend/utils/exception.py

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status


def exception_handler(exc, context):
    """
    自定义处理异常函数
    :param exc:
    :param context:
    :return:
    """
    response = drf_exception_handler(exc, context)
    if response is None:  # 处理之后为空,再进行自定义的二次处理
        # print(exc)      # 错误原因   还可以做更详细的原因,通过判断exc信息类型
        # print(context)  # 错误信息
        print('%s - %s - %s' % (context['view'], context['request'].method, exc))
        return Response({
            'detail': '服务器错误'
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
    return response  # 处理之后有值,就直接返回结果

这样我们就可以对异常进行自定义的处理了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值