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
原文地址:https://www.cnblogs.com/neozheng/p/9589175.html