• restful(3):认证、权限、频率 & 解析器、路由控制、分页、渲染器、版本


    models.py中:

    class UserInfo(models.Model):
        name = models.CharField(max_length=32)
        psw = models.CharField(max_length=32)
        user_type_choices = ((1,"普通"),(2,"VIP"),(3,"SVIP"))
        user_type = models.SmallIntegerField(choices=user_type_choices,default=1)  # 新添加一个标识用户权限级别的字段
    
    class Token(models.Model):  # Token类用于 认证
        user = 
    models.OneToOneField(to="UserInfo",on_delete=models.CASCADE)
        token = models.CharField(max_length=128)

    认证、权限和频率

     1 # 认证类中一定要有一个 authenticate() 的方法
     2 # 权限类中一定要有一个 has_permission() 的方法 (认证组件执行时会 request.user = 当前登陆用户)
     3 # 频率类中一定要有一个 allow_request() 的方法
     4 
     5 
     6 # 执行组件:认证、权限、频率
     7 # 认证:request.user
     8 self.initial(request,*args,**kwargs):
     9     ==== # 认证组件
    10            self.perform_authentication(request)
    11          # 权限组件
    12            self.check_permissions(request)
    13          # 频率组件
    14            self.check_throttles(request)
    15          ### 这三个组件也是在dispatch()执行的时候执行(有访问请求的时候)
    16          
    17 request.META.get("REMOTE_ADDR")  # 客户端的IP地址

    认证组件:

    局部视图认证:

    在app01.service.auth.py:

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    
    class Authentication(BaseAuthentication):
    
        def authenticate(self,request):  # authenticate()  这个方法名是固定的
            
            # http:www...../?token=soihtfn7a9sdfvb987... # url中应该带有token
            # token=request._request.GET.get("token")
            token = request.query_params.get("token")  # request.query_params  # 获取到 GET请求数据(/? 后面的数据,和请求方式无关;POST请求时,也能 request.GET来获取 /? 后面的数据);request是后来封装好的request
            token_obj=UserToken.objects.filter(token=token).first()
            if not token_obj:  # 检查token是否存在
                raise AuthenticationFailed("验证失败!")  # 认证失败时的固定语法
            return (token_obj.user,token_obj)  # 认证成功后需要返回一个元组:第一个是用户有关的信息,第二个参数是token对象    

    在views.py:

    def get_random_str(user):
        import hashlib,time
        ctime=str(time.time())
    
        md5=hashlib.md5(bytes(user,encoding="utf8"))  # user是为了“加盐”处理
        md5.update(bytes(ctime,encoding="utf8"))
    
        return md5.hexdigest()
    
    
    from app01.service.auth import *
    
    from django.http import JsonResponse
    class LoginViewSet(APIView):
        # authentication_classes = [Authentication,]  # authentication_classes 是固定写法;需要认证的类都写在后面的列表中
        def post(self,request,*args,**kwargs):
            res={"code":1000,"msg":None}
            try:
                user=request._request.POST.get("user")  
                pwd=request._request.POST.get("pwd")
                user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first()
                print(user,pwd,user_obj)
                if not user_obj:
                    res["code"]=1001
                    res["msg"]="用户名或者密码错误"
                else:
                    token=get_random_str(user) 
                    UserToken.objects.update_or_create(user=user_obj,defaults={"token":token})  # 表中没有就创建,有就更新;# defaults表示:除了defaults 中的字段外,其它的字段联合比较是否已经存在,存在则更新,不存在则创建 # 返回一个元组:第一个是对象,第二个是布尔值(表示create还是update)
                    res["token"]=token
    
            except Exception as e:
                res["code"]=1002
                res["msg"]=e
    
            return JsonResponse(res,json_dumps_params={"ensure_ascii":False})  # {"ensure_ascii":False} 作用:显示中文

    全局视图认证组件:

    settings.py配置如下:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]  # 写上 认证类的路径
    }

    认证源码:

    self表示封装之后的request,所以,认证完成之后,request.user 和 request.auth 就是你赋给它们的值

    自定义用户认证的类:

    # 自定义用户验证的类(如手机或邮箱配合密码登陆;因为默认是 用户名和密码验证)需要继承 ModelBackend;并且需要在 settings 中设置 AUTHENTICATION_BACKENDS = ("路径.自定义用户验证的类",)
    from django.contrib.auth.backends import ModelBackend
    from django.db.models import Q
    class CustomBackend(ModelBackend):
        def authenticate(self,username=None,password=None,**kwargs):
            try:
                user = models.User.objects.get(Q(username=username)|Q(email=username)|Q(mobile=username))
                if user.check_password(password):  # 前端传过来的密码是明文的,Django中保存的是密文,check_password() 会把明文转化为密文
                    return user
            except Exception as e:
                return None

    权限组件

    局部视图权限

    在app01.service.permissions.py中:

    from rest_framework.permissions import BasePermission
    class SVIPPermission(BasePermission):
        message="SVIP才能访问!"  # 没有权限时返回的错误信息
        def has_permission(self, request, view):
            if request.user.user_type==3:
                return True
            return False  
    
    # return True就是通过权限认证, return False 即没有权限

    在views.py:

    from app01.service.permissions import *
    
    class BookViewSet(generics.ListCreateAPIView):
        permission_classes = [SVIPPermission,]  # permission_classes 是固定写法;需要校验的权限类都写在后面的列表中(这是局部权限校验)
        queryset = Book.objects.all()
        serializer_class = BookSerializers

    全局视图权限:

    settings.py配置如下:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]  # 写上权限认证类的路径
    }

    登陆权限的类:IsAuthenticated

    from rest_framework.permissions import IsAuthenticated
    
    class xxxViewSet():
        permission_classes = (IsAuthenticated,)  # 该视图只有登陆后才能访问

    只有对象的拥有者才能有权限操作(如:只能删除自己的收藏):

    # 只有对象的拥有者才能有权限操作(如:只能删除自己的收藏)
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        Object-level permission to only allow owners of an object to edit it.
        Assumes the model instance has an `owner` attribute.
        """
    
        def has_object_permission(self, request, view, obj):  # obj 表示被操作的对象,如一条收藏记录
            # Read permissions are allowed to any request,
            # so we'll always allow GET, HEAD or OPTIONS requests.
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # Instance must have an attribute named `owner`.
            return obj.owner == request.user
            

    只能查看自己的内容(GET请求;)

    class UserFavViewSet(mixin.CreateModelMixin,mixin.ListModelMixin,mixin.RetrieveMixin,generics.GenericAPIView):
        authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication)  # ?? 此处有疑问 
        permission_classes = (IsAuthenticated,IsOwnerReadOnly)
        serializer_class = UserFavSerializer
        lookup_field = "goods_id"  # 用于执行各个model实例的对象查找的model字段,默认是 "pk"; 程序先 执行的 get_queryset() ,然后才走的 这一步
        
        def get_queryset(self):  # 有了 get_queryset() 这个方法时, 上面就不再需要写 queryset
            return UserFav.objects.filter(user=self.request.user)  # 筛选出 user 为当前登陆用户的 记录 (即 只能查看自己的)

    throttle(访问频率)组件

    局部视图throttle

    在app01.service.throttles.py中:

    from rest_framework.throttling import BaseThrottle
    
    VISIT_RECORD={}
    class VisitThrottle(BaseThrottle):
    
        def __init__(self):
            self.history=None
    
        def allow_request(self,request,view):  # allow_request()是固定的方法名
    
            # 以下为业务逻辑(rest 只处理数据,不处理逻辑)
            remote_addr = request.META.get('REMOTE_ADDR')  # 客户端的IP地址
            print(remote_addr)
            import time
            ctime=time.time()
    
            if remote_addr not in VISIT_RECORD:
                VISIT_RECORD[remote_addr]=[ctime,]
                return True
    
            history=VISIT_RECORD.get(remote_addr)
            self.history=history
    
            while history and history[-1]<ctime-60:
                history.pop()
    
            if len(history)<3:
                history.insert(0,ctime)
                return True # return True 表示通过验证
            else:
                return False  # return False 表示没通过验证
    
        def wait(self):
            import time
            ctime=time.time()
            return 60-(ctime-self.history[-1])

    在views.py中:

    from app01.service.throttles import *
    
    class BookViewSet(generics.ListCreateAPIView):
        throttle_classes = [VisitThrottle,]  # throttle_classes 是固定写法;
        queryset = Book.objects.all()
        serializer_class = BookSerializers

    全局视图throttle

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
    }

    内置throttle类

    在app01.service.throttles.py修改为:

    class VisitThrottle(SimpleRateThrottle):
    
        scope="visit_rate"
        def get_cache_key(self, request, view):
    
            return self.get_ident(request)

    settings.py设置:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
        "DEFAULT_THROTTLE_RATES":{
            "visit_rate":"5/m",
        }
    }

     

    使用默认的Throttling:

    1. 配置settings

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': (
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ),
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/day',
            'user': '1000/day'
        }
    }

    2. 在相关视图中添加 throttle_classes 的类,如:

    from rest_framework.throttling import AnonRateThrottle,UserRateThrottle
    
    class GoodsViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,GenericViewSet):
        """
        商品列表页,分页,过滤,搜索,排序
        list:
            所有商品列表
        retrieve:
            查看单个商品
        """
        queryset =  Goods.objects.all().order_by("pk")
        serializer_class = GoodsSerializer
        pagination_class = GoodsPagination
        throttle_classes = (AnonRateThrottle,UserRateThrottle)  # 控制频率的类
        filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter)
        filter_class = GoodsFilter  # 过滤
        search_fields = ("name","goods_brief","goods_details")  # 搜索
        ordering_fields = ("sold_num","shop_price")  # 排序
    
        # 修改点击数
        def retrieve(self, request, *args, **kwargs):
            instance = self.get_object()  # instance 是一个 Goods() 的对象
            instance.click_num += 1  # 点击数 +1
            instance.save()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)

    解析器

    局部视图

    from rest_framework.parsers import JSONParser,FormParser
    class PublishViewSet(generics.ListCreateAPIView):
        parser_classes = [FormParser,JSONParser]  # parser_classes 是固定写法;解析器名放在后面的列表中
        queryset = Publish.objects.all()
        serializer_class = PublshSerializers
        def post(self, request, *args, **kwargs):
            print("request.data",request.data)
            return self.create(request, *args, **kwargs)

    全局视图

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
        "DEFAULT_THROTTLE_RATES":{
            "visit_rate":"5/m",
        },
        "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',]
    }

    路由控制

    路由控制针对的只是以下这种情况:

    # urls.py部分:
    re_path(r"^books/$",views.BookModelView.as_view({"get":"list","post":"create"})),
    re_path(r"^books/(?P<pk>d+)/$",views.BookModelView.as_view({"get":"retrieve","put":"update","delete":"destroy"})),
    
    # views.py部分:
    class BookModelView(viewsets.ModelViewSet):
        queryset = models.Book.objects.all()  # queryset 表示要处理的数据;queryset这个变量名是固定的
        serializer_class = serializer.BookSerializers  # serializer_class 表示 所要用到的 序列化的类;serializer_class 是固定写法

    上面的两条 url 可以利用 路由控制 组件来简化:

    # urls.py 中
    
    from rest_framework import routers
    from django.urls import path,re_path,include
    from app01 import views
    
    
    routers = routers.DefaultRouter()
    routers.register("books",views.BookModelView)  # 第一个参数是路径的前缀,第二参数是 视图类 名称  # 这两行代码执行后,会生成四条 以 books/ 为前缀的 url
    
    urlpatterns = [
        re_path(r'^', include(routers.urls)),  # 把上面 register() 的路径在 urlpatterns 中 include 一下
    ]

    分页

    普通分页

    from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
    
    class PNPagination(PageNumberPagination):
            page_size = 1  # 后端设置的每页条数
            page_query_param = 'page'  # 前端查询页码的参数
            page_size_query_param = "size"  # 前端临时修改每页条数的参数
            max_page_size = 5  # 前端能修改的每页条数 的最大值
    
    class BookViewSet(viewsets.ModelViewSet):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializers
    
        # 继承 ModelViewSet 时 需要修改其 list() 方法
        def list(self,request,*args,**kwargs):
    
            book_list=Book.objects.all()
            pp=PNPagination()
            pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self)  # 分页函数
            print(pager_books)
            bs=BookSerializers(pager_books,many=True)
    
            return Response(bs.data)
            # return pp.get_paginated_response(bs.data)

    偏移分页

    from rest_framework.pagination import LimitOffsetPagination

     

    响应器:

    from rest_framework.response import Response

    # Response 内部会自动做序列化

    渲染器:

    渲染器作用:规定页面显示的效果(无用)

    局部渲染:

    from rest_framework.renderers import JSONRenderer
    
    class TestView(APIView):
        renderer_classes = [JSONRenderer, ]  # renderer_classes 渲染器固定写法; 通常用 都用 JSONRenderer--- 只渲染为 Json字符串
    
        def get(self, request, *args, **kwargs):
            user_list = models.UserInfo.objects.all()
            ser = TestSerializer(instance=user_list, many=True)
            return Response(ser.data)

    全局渲染配置:

    REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES':['rest_framework.renderers.JSONRenderer',]
    }

    版本

    a.  基于url的get传参方式: 如:/users?version=v1

    settings 中的配置:

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(),name='test'),
    ]
    
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning
    
    
    class TestView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
    
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py

    b. 基于url的正则方式:

    如:/v1/users/

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
    ]
    
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import URLPathVersioning
    
    
    class TestView(APIView):
        versioning_class = URLPathVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本: request.version
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    # views.py

    全局使用:

    REST_FRAMEWORK = {
        'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
        'DEFAULT_VERSION': 'v1',
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        'VERSION_PARAM': 'version' 
    }
    
    # settings.py
  • 相关阅读:
    c++11-17 模板核心知识(七)—— 模板参数 按值传递 vs 按引用传递
    c++11-17 模板核心知识(六)—— 理解auto推导规则
    c++11-17 模板核心知识(五)—— 理解模板参数推导规则
    c++11-17 模板核心知识(四)—— 可变参数模板 Variadic Template
    c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters
    c++11-17 模板核心知识(二)—— 类模板
    c++11-17 模板核心知识(一)—— 函数模板
    Docker修改Devicemapper存储驱动为Direct-lvm模式
    Linux配置/etc/resolv.conf详解
    深入浅出容器学习--Docker网络
  • 原文地址:https://www.cnblogs.com/neozheng/p/9589175.html
Copyright © 2020-2023  润新知