70、多方式登陆接口、自定义频率类 、基于APIView写分页 、全局异常处理 、auth 模块回顾 、局部钩子全局钩子源码、视图集回顾

67 阅读9分钟

练习

多方式登陆接口

需求:多方式登陆接口:用户名+密码、邮箱+密码、手机号+密码

serializer.py

from rest_framework import serializers
from .models import UserInfo
from rest_framework_jwt.views import ObtainJSONWebToken
import re
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

from rest_framework.exceptions import ValidationError


class LoginSerializer(serializers.ModelSerializer):

    class Meta:
        model=UserInfo
        fields = ['username','password'] # username 是unique唯一的,反序列化校验的时候,字段自己的规则,会去数据库查询有没有这个用户,如果有,直接报错了
    username = serializers.CharField() # 这个优先用这个,就不是映射过来的,就没有unique的限制了

    def validate(self, attrs):
        username  = attrs.get('username')
        password = attrs.get('password')
        if re.match('^1(3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8[0-9]|9[0-9])\d{8}$',username):

            user= UserInfo.objects.filter(phone=username).first()
            print(username)
        elif re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)",username):

            user = UserInfo.objects.filter(email=username).first()
        else:
            user =UserInfo.objects.filter(username=username).first()
        #根据密码查找
        if user.check_password(password) and user:
            # 签发token
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            self.user = user
            self.token = token
            return attrs
        else:
            raise ValidationError('用户名或密码错误')

views.py

from rest_framework.viewsets import ViewSet
from .serializer import LoginSerializer
from rest_framework.response import Response
class LoginView(ViewSet):
    def login(self,request):
        ser =LoginSerializer(data=request.data)
        if ser.is_valid():
            token = ser.token
            return Response({'code':100,'msg':'ok','token':token})
        else:
            return Response({'code': 100, 'msg':ser.errors})

urls.py

from django.contrib import admin
from django.urls import path
from app01.views import LoginView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',LoginView.as_view({'post':'login'})),
]

总结

1.在author扩展表中,username设置的字段参数是唯一的:unique=True,所以在序列化类的时候,如果使用ModelSerializer映射字段的时候,要重写username:username = serializers.CharField()
2.在反序列化全局钩子校验的时候(def validate(self, attrs)),其中attrs是一个字典,并且是经过字段自己校验和局部校验后的数据字典,且反序列全局钩子的返回值必须是一个字典attrs,
3.在视图里面,想要得到反序列化得到值:可以在序列化类中使用:self.属性=属性值,在视图类中在校验之后的数据中:ser.属性得到属性值
4.在视图里面,想要得到全局钩子的返回值,校验通过之后使用ser.validated_data得到
5.在视图函数里面,数据校验通过之后,也可以手动调用视图里面的函数

自定义频率类

需求:写出频率类,每个ip,一分钟只能访问3次

自定义的逻辑
	1.取出访问者ip:request.META.get('REMOTE_ADDR')
	2.判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
	3.循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
	4.判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
	5.当大于等于3,说明一分钟内访问超过三次,返回False验证失败
class MyThrottles():
    VISIT_RECORD = {}
    def __init__(self):
        self.history=None
    def allow_request(self,request, view):
        #(1)取出访问者ip
        # print(request.META)
        ip=request.META.get('REMOTE_ADDR')
        import time
        ctime=time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip]=[ctime,]
            return True
        self.history=self.VISIT_RECORD.get(ip)
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime-self.history[-1]>60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history)<3:
            self.history.insert(0,ctime)
            return True
        else:
            return False
    def wait(self):
        import time
        ctime=time.time()
        return 60-(ctime-self.history[-1])

需求:写出频率类,每个ip,一分钟只能访问5次

throttl.py

from rest_framework.throttling import SimpleRateThrottle
import time


class common_throttling:
    dic_ip = {}
    def __init__(self):
        self.time_list=None
    def allow_request(self, request, view):
        ip = request.META.get('REMOTE_ADDR')

        # 如果在字典中没有ip,就可以把这个ip,时间添加进去,并返回true
        ctime = time.time()
        if ip not in self.dic_ip:
            self.dic_ip[ip] = [ctime]
            return True

        # 如果ip在字典里的话,取出列表
        self.time_list= self.dic_ip[ip]

        # 判断如果当前时间减去列表中最后一个值的时间,如果大雨60就弹出
        if ctime-self.time_list[-1]>60:
            self.time_list.pop()

        # 如果此时现在列表长度大于=3,就返回false
        if len(self.time_list) >=5:
            return False
        else:
            self.time_list.insert(0,ctime)
            self.dic_ip[ip]=self.time_list

            return True
    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.time_list[-1])

views.py

from .throttl import common_throttling
class BookView(ViewSet):
    throttle_classes = [common_throttling]
    def get(self,request):
        return Response({'ok'})

urls.py

from django.contrib import admin
from django.urls import path
from app01.views import BookView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', BookView.as_view({'get':'get'})),
]

基于APIView写分页

views.py

from rest_framework.views import APIView

from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from .page import common_page
from .models import Book
from .serializer import BookSerializer

class BookView(APIView):

    def get(self,request):
        # page = self.paginate_queryset(queryset)
        obj_list = Book.objects.all()
        pagenation = common_page()
        page = pagenation.paginate_queryset(obj_list, request, self)
        qs = BookSerializer(page,many=True)
        print(qs)
        # return pagenation.get_paginated_response(qs.data)
        return Response({'code':100,
                         'mag':'查询成功',
                         'count':pagenation.page.paginator.count,
                         'next': pagenation.get_next_link(),
                         'previous':pagenation.get_previous_link(),
                         'results':qs.data})

page.py

from rest_framework.pagination import PageNumberPagination
class common_page(PageNumberPagination):
    page_size = 2
    page_query_param = 'page'
    page_size_query_param = "page_size"
    max_page_size = 5

全局异常处理

exception.py

from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
    # APIView对drf做了异常处理,没有对其他报错做处理
    res = exception_handler(exc, context)
    if res:
        # drf
        if isinstance(res.data,dict):
            detail = res.data.get('detail')
        else:
            detail = res.data
        return Response({'code': 102, 'msg': detail})

    else:
        print(type(exc))  # <class 'IndexError'>
        detail = str(exc)
        # return Response({'code': 999, 'msg': detail})
        return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})

settings.py

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}

auth扩展表补充

1.一开始没有使用auth的user表,做过迁移了
	1.后期再想扩展,其实就不行了
	2.如果真做了这个事,还要行
			1.把所有的迁移文件删除(自己的app,源码中的(admin,auth))的migrations
			2.删库
			3.重新迁移

auth 模块回顾

1.Auth模块:是Django自带的用户认证模块
		1.我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这是个麻烦的事情。
    2.Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,它默认使用 auth_user 表来存储用户数据。
    
2.超级用户创建:pthon manage.py createsuperuser
	

用户登陆

1.保存用户信息
  方式一:
  	request.session['username'] = 获取到用户对象.username
  方式二:
  	from django.contrib import auth
  	auth.login(request,用户对象)
    ps:如果你用了auth,就用一整套。只要你用了auth.login方法,后续就可以在任何方法里面通过:request.user拿到用户对象。eg:request.user.username --->user_obj.username

用户认证

1.判断用户登陆成功
	方式一:
  	request.session.get('username')
  方式二:
  	request.user.is_authenticated()   --->true  false
    
2.认证装饰器
		1.1 导模块:from django.contrib.auth.decorators import login_required
    1.2 装饰器:@login_required(),默认提交:/account/login/
    		1.局部添加:@login_required(login_url ='/xxx/')-->未登陆跳转/xxx/
        2.全局添加:@login_required()-->在配置文件中加:LOGIN_URL = '/login/',未登陆跳转

修改密码

1.验证老密码:request.user.check_password(旧密码)
2.修改密码:
	1.request.user.set_password(新密码)-->这一句没有真正的操作数据库(update)
  2.request.user.save()-->真正的操作数据库(update)
3.request.user-->返回的是用户对象;如果返回的是AnonymousUser,就是匿名用户,未登陆

注销登录

1.注销用户:
	1.导模块:from django.contrib import auth
	2.注销用户:auth.logout(request)
2.当调用该函数时,当前请求的session信息会全部清除

用户注册

1.导模块:from django.contrib.auth.models import User
2.注册用户名密码
	创建不加密:creat()-->User.objects.create(username=username,password=password)
  创建加密的:creat_user()-->User.objects.create_user(username=username,password=password)
  创建超级用户:create_superuser() --> User.objects.create_superuser(username=username,email='123@qq.com',password=password # 邮箱必须填

局部钩子全局钩子源码

局部钩子

全局钩子

many参数作用

1.如果传了many=True,序列化多条---->得到的对象不是BookSerializer对象,而是ListSerialzier的对象中套了一个个的BookSerializer对象
2.如果传了many=False,序列化单条---》得到的对象就是BookSerializer的对象
3.类实例化得到对象,是谁控制的? -->__new__()
# BaseSerializer有__new__()方法
def __new__(cls, *args, **kwargs):
  # We override this method in order to automatically create
  # `ListSerializer` classes instead when `many=True` is set.
  if kwargs.pop('many', False):
    return cls.many_init(*args, **kwargs)
  return super().__new__(cls, *args, **kwargs)

many_init---->BaseSerializer中的类


   	@classmethod
    def many_init(cls, *args, **kwargs):
				...
        meta = getattr(cls, 'Meta', None)
        list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)   
        return list_serializer_class(*args, **list_kwargs)

视图集回顾

1.ViewSetMixin:只要继承他,路由写法变了
2.ViewSet:ViewSetMixin, views.APIView
3.GenericViewSet:(ViewSetMixin, generics.GenericAPIView)
4.ModelViewSet:
        	 1、继承了ModelViewSet,没增加一条记录,就干某个事,重写perform_create
           2、序列化使用一个序列化类,反序列化使用配置的那个序列化类
           3、自定义了一个方法:login,使用了action装饰器,让它取到的qs对象和序列化类跟配置的都不一样
5.ReadOnlyModelViewSet(mixins.RetrieveModelMixin,mixins.ListModelMixin,GenericViewSet)
    
视图类 :self  内部有request对象,还有个action,就是视图类中方法的字符串名

练习

继承了ModelViewSet,每增加一条记录,就干某个事,重写perform_create

from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 每次添加数据的时候,打印hello
    def perform_create(self, serializer):
        print('hello')
        serializer.save()

序列化使用一个序列化类,反序列化使用配置的那个序列化类

from .models import Book
from .serializer import BookSerializer,PublishSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 序列化使用一个序列化类,反序列化使用配置的那个序列化类
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return PublishSerializer
        else:
            return self.serializer_class

自定义了一个方法:login,使用了action装饰器,让它取到的qs对象和序列化类跟配置的都不一样

from .models import Book,Publish
from .serializer import BookSerializer,PublishSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 序列化使用一个序列化类,反序列化使用配置的那个序列化类
    def get_queryset(self):
        if 'login' in self.request.path:
            return "单独的qs"
        else:
            return super().get_queryset()

    def get_serializer_class(self):
        if "login" in self.request.path:
            return '某个序列化类'
        return self.serializer_class

    @action(methods=['POST'])
    def login(self,request):
        self.get_queryset()
        self.get_serializer()
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 序列化使用一个序列化类,反序列化使用配置的那个序列化类
    def get_queryset(self):
        if self.action == 'login':
            return "单独的qs"
        else:
            return super().get_queryset()

    def get_serializer_class(self):
        if self.action == 'login':
            return '某个序列化类'
        return self.serializer_class

    @action(methods=['POST'])
    def login(self,request):
        self.get_queryset()
        self.get_serializer()

自定义中间件

1.django默认提供的有七个中间件,它还支持程序员自己定义中间件
  
2.自定中间件的步骤
	1.在项目名下或者应用名下新建一个任意名称的文件夹
  2.在这个文件夹下面新建一个py文件
  3.在这个py文件中,新建一个类,必须继承MiddlewareMixin
  4.在这个新建的类下面写两个方法:
  	1.process_reqeust
    2.process_response
  5.一定要在配置文件的中间件里面注册你的中间件路径
  
3.需求:创建自定义的中间件

Settings.py

MIDDLEWARE = [
    'app01.mymiddleware.mymiddleware1'
]

app01/mymiddleware/mymiddleware1

from django.utils.deprecation import MiddlewareMixin
import time
from django.shortcuts import HttpResponse

class mymiddleware1(MiddlewareMixin):
    def __init__(self, get_response):
        self.time_list = None
        self.get_response = get_response
        self.dic_ip = {}

    def process_request(self,request):
        ip = request.META.get('REMOTE_ADDR')

        # 如果在字典中没有ip,就可以把这个ip,时间添加进去,并返回true
        ctime = time.time()
        if ip not in self.dic_ip:
            self.dic_ip[ip] = [ctime]
            return

        # 如果ip在字典里的话,取出列表
        self.time_list = self.dic_ip[ip]

        # 判断如果当前时间减去列表中最后一个值的时间,如果大雨60就弹出
        if ctime - self.time_list[-1] > 60:
            self.time_list.pop()

        # 如果此时现在列表长度大于=3,就返回false
        if len(self.time_list) >= 5:
            return HttpResponse('限制了被', status=400)
        else:
            self.time_list.insert(0, ctime)
            self.dic_ip[ip] = self.time_list

            return

    def process_response(self, request, response):
        print('我是第一个中间件的process_response')
        return response  # 本质返回的也是HttpResponse

补充

1.魔法方法:类中使用__开头  __结尾的方法,它不需要主动调用,某种情况下会自动触发object类身上的