drf续集之 - 多方式登录、批量操作

DRF - 多方式登录

用户可以以用户名、邮箱、手机号登录

之前写的逻辑处理一般都是在视图类中,现在把逻辑拿到了序列化类的validate全局钩子中,在视图函数中 就无需写逻辑了,执行到.is_valid就会执行validate方法

  • 重点:视图类和序列化类之间交互数据的桥梁:context={}(是1个字典)

自定义用户表,登录手动签发token,自定义的认证类

认证通过,正常情况下查询得到当前登录用户

效果

用户名登录
在这里插入图片描述
邮箱登录
在这里插入图片描述
手机号登录
在这里插入图片描述

代码

models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=16, verbose_name='用户名')
    password = models.CharField(max_length=16, verbose_name='密码')
    phone = models.CharField(max_length=11, verbose_name='手机号')
    email = models.EmailField(verbose_name='邮箱')

    class Meta:
        verbose_name_plural = '用户信息表'

    def __str__(self):
        return self.username

serializer.py

import re

from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework.exceptions import ValidationError
from rest_framework import serializers

from multiple_login.models import UserInfo


class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        # username可能是邮箱,手机号,用户名
        username = attrs.get('username')
        password = attrs.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.check_password(password):
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # token是要在视图类中使用,现在我们在序列化类中
            # self.context.get('request')
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs

        else:
            raise ValidationError('用户名或密码错误')

utils.py

from rest_framework.views import exception_handler
from rest_framework.response import Response


class APIResponse(Response):
    def __init__(self, code=100, msg='成功', data=None, status=None,
                 headers=None, content_type=None, **kwargs):
        dic = {'code': code, 'msg': msg}
        if data:
            dic['data'] = data
        dic.update(kwargs)
        super().__init__(data=dic, status=status, headers=headers, content_type=content_type)


def common_exception(exc, context):
    # 先调用REST framework默认的异常处理方法获得标准错误响应对象
    response = exception_handler(exc, context)
    # 在此处补充自定义的异常处理
    if response is None:
        response = Response(data={'code': 999, 'msg': str(exc)})

    return response

auth.py

import jwt

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler


class JwtAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_Authorization'.upper())
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('过期了')
        except jwt.DecodeError:
            raise AuthenticationFailed('解码错误')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('不合法的token')
        # 得到的user对象,应该是自己user表的user对象
        # user=MyUser.objects.get(id=payload['user_id'])
        user = payload

        return (user, token)

views.py

import re

from rest_framework_jwt.settings import api_settings
from rest_framework.views import APIView

from multiple_login.utils import APIResponse
from multiple_login.models import UserInfo

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.password == password:
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误')

urls.py

from django.contrib import admin
from django.urls import path

from multiple_login import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.MyLoginView.as_view()),
]

settings.py

REST_FRAMEWORK = {
    # 配置全局异常
    'EXCEPTION_HANDLER': 'multiple_login.utils.common_exception'
}

DRF - 批量操作

功能:群增、群删、群改、群查

代码

models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=16, verbose_name='用户名')
    password = models.CharField(max_length=16, verbose_name='密码')
    phone = models.CharField(max_length=11, verbose_name='手机号')
    email = models.EmailField(verbose_name='邮箱')

    class Meta:
        verbose_name_plural = '用户信息表'

    def __str__(self):
        return self.username


# 抽象出一个基表(不在数据库生成,abstract=True),只用来继承(类似于Mixin的混入思想)
class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    create_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        # 基表必须设置abstract = True,基表就是给普通Model类继承使用的,设置了abstract 就不会完成数据库迁移 完成建表
        abstract = True


class Book(BaseModel):
    name = models.CharField(max_length=16, verbose_name='书名')
    price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='图书价格')
    publisher = models.ForeignKey(to='Publisher', db_constraint=False, on_delete=models.DO_NOTHING,
                                  verbose_name='图书关联的出版社')
    # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
    # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False, verbose_name='图书的作者们')

    @property
    def publisher_name(self):
        return self.publisher.name

    @property
    def author_list(self):
        # ll=[]
        # for author in self.authors.all():
        #     ll.append({'name':author.name,'sex':author.get_gender_display()})
        return [{'name': author.name, 'sex': author.get_gender_display()} for author in self.authors.all()]

    class Meta:
        verbose_name_plural = '图书表'

    def __str__(self):
        return self.name


class Publisher(BaseModel):
    name = models.CharField(max_length=16, verbose_name='出版社名')
    address = models.CharField(max_length=64, verbose_name='出版社地址')

    class Meta:
        verbose_name_plural = '出版社表'

    def __str__(self):
        return self.name


class Author(BaseModel):
    name = models.CharField(max_length=16, verbose_name='作者姓名')
    gender = models.IntegerField(choices=[(0, '保密'), (1, '男'), (1, '女')], default=0, verbose_name='作者性别')

    class Meta:
        verbose_name_plural = '作者表'

    def __str__(self):
        return self.name


class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)
    # 有作者可以没有详情,删除作者,详情一定会被级联删除
    # 外键字段为正向查询字段,related_name是反向查询字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE,
                                  verbose_name='关联的作者')

    class Meta:
        verbose_name_plural = '作者详情表'

serializer.py

import re

from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework.exceptions import ValidationError
from rest_framework import serializers

from app01 import models


class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()

    class Meta:
        model = models.UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        # username可能是邮箱,手机号,用户名
        username = attrs.get('username')
        password = attrs.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = models.UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = models.UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = models.UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.check_password(password):
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # token是要在视图类中使用,现在我们在序列化类中
            # self.context.get('request')
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs

        else:
            raise ValidationError('用户名或密码错误')


# 图书表序列化类
class ListBookSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # print(instance)  # book_list:是一堆图书对象
        # print(validated_data)  # 列表套字典,是要修改的数据
        return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        list_serializer_class = ListBookSerializer  # 指定many=True的时候,生成的ListBookSerializer的对象了
        fields = ['name', 'price', 'publisher', 'authors', 'publisher_name', 'author_list']
        extra_kwargs = {
            'publisher': {'write_only': True},
            'authors': {'write_only': True},
            'publisher_name': {'read_only': True},
            'author_list': {'read_only': True},
        }

utils.py

from rest_framework.views import exception_handler
from rest_framework.response import Response


class APIResponse(Response):
    def __init__(self, code=100, msg='成功', data=None, status=None,
                 headers=None, content_type=None, **kwargs):
        dic = {'code': code, 'msg': msg}
        if data:
            dic['data'] = data
        dic.update(kwargs)
        super().__init__(data=dic, status=status, headers=headers, content_type=content_type)


def common_exception(exc, context):
    # 先调用REST framework默认的异常处理方法获得标准错误响应对象
    response = exception_handler(exc, context)
    # 在此处补充自定义的异常处理
    if response is None:
        response = Response(data={'code': 999, 'msg': str(exc)})

    return response

auth.py

import jwt

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler


class JwtAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_Authorization'.upper())
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('过期了')
        except jwt.DecodeError:
            raise AuthenticationFailed('解码错误')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('不合法的token')
        # 得到的user对象,应该是自己user表的user对象
        print(payload)
        # user=MyUser.objects.get(id=payload['user_id'])
        user = payload

        return (user, token)

views.py

import re

from rest_framework_jwt.settings import api_settings
from rest_framework.views import APIView

from app01 import models, serializer
from app01.utils import APIResponse
from app01.models import UserInfo

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.password == password:
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误')


class BookView(APIView):
    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 增一条
            ser = serializer.BookSerializer(data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(data=ser.data)
        elif isinstance(request.data, list):
            # 增多条
            ser = serializer.BookSerializer(data=request.data, many=True)
            # 内部如何实现的?
            # many=True,ser不是BookSerializer对象,而是ListSerializer对象,套了一个个的BookSerializer
            # print(type(ser))
            ser.is_valid(raise_exception=True)
            ser.save()  # ListSerializer的save
            return APIResponse(msg=f'增加{ser}条成功')

    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 单查
            # 方式一
            # book=models.Book.objects.filter(id=pk).filter(is_delete=False).first()
            # if not book:
            #     raise Exception('要查询的不存在')
            # 方式二
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book)

        else:
            # 查所有
            book_list = models.Book.objects.all().filter(is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, many=True)
        return APIResponse(data=ser.data)

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        pks = []
        if pk:
            # 单条删除
            # res=models.Book.objects.filter(id=pk).update(is_delete=True)
            # print(res)
            # return APIResponse(msg='删除成功')
            pks.append(pk)
        else:
            pks = request.data

        res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
        if res >= 1:
            return APIResponse(msg=f'删除{res}条成功')
        else:
            return APIResponse(code=999, msg='没有要删除的数据')

    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 单条修改
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book, data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改成功')
        else:
            # 分析:ListSerializer的update方法没有写,需要我们自己写
            # pks=[item['id'] for item in request.data]
            # 如果不重写ListSerializer的update方法,这是存不进去的
            pks = []
            for item in request.data:
                pks.append(item['id'])
                item.pop('id')
            book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg=f'修改{len(book_list)}条成功')

urls.py

from django.contrib import admin
from django.urls import path, re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.MyLoginView.as_view()),
    path('books/', views.BookView.as_view()),
    re_path('books/(?P<pk>\d+)', views.BookView.as_view()),
]

settings.py

REST_FRAMEWORK = {
    # 配置全局异常
    'EXCEPTION_HANDLER': 'app01.utils.common_exception'
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值