Django-RestFramework中文学习笔记

Django-RestFramework中文学习笔记

1、Django-RestFramework的基本概念和使用场景。

Django-RestFramework是一个用于构建Web API的框架,可以快速、简单地创建API。它建立在Django框架之上,提供了许多用于处理序列化、认证、权限控制等功能的工具和库。
Django-RestFramework的几个基本概念和使用场景:

序列化(Serialization):Django-RestFramework提供了一个序列化器(Serializer)类,用于将模型实例转化为JSON或其他格式的数据,以便于API的输入和输出。序列化器可以自动处理模型之间的关系,并提供字段的验证和转换功能。

视图(View):Django-RestFramework的视图类提供了一种定义API端点行为的方式。视图类可以通过继承和定制来实现各种不同的功能,例如创建、更新、删除和检索资源。

路由(Routing):Django-RestFramework提供了一种简单的路由系统,用于将URL映射到相应的视图。路由系统可以自动生成URL模式,并支持嵌套路由和动态路由。

认证(Authentication):Django-RestFramework提供了多种认证方式,包括基于Token的认证、OAuth2认证、Session认证等。这些认证方式可以用于保护API端点,仅允许经过认证的用户访问。

权限控制(Permissions):Django-RestFramework提供了一套灵活的权限系统,用于控制API端点的访问权限。权限可以基于用户身份、请求方法、对象状态等进行控制。

过滤(Filtering):Django-RestFramework提供了用于过滤查询结果的功能。通过配置过滤器类,可以根据请求参数来筛选和排序查询结果。

Django-RestFramework适用于构建各种类型的Web API,包括单页面应用(SPA)、移动应用程序后端、微服务和RESTful API等。它提供了丰富的功能和灵活的配置选项,可以满足不同场景下的需求。

2、前后端不分离和分离的区别

前后端不分离是指前端和后端的开发在同一个项目中进行,前后端的代码是混合在一起的。前端负责展示页面、用户交互等,后端负责处理数据、业务逻辑等。
优点:开发速度较快,因为前后端代码在同一个项目中进行开发,可以直接调用后端代码,不需要通过接口进行通信。
前后端不分离

前后端分离是指前端和后端的开发分别在不同的项目中进行,前端和后端通过接口进行通信。前端只负责展示页面和用户交互,后端负责处理数据、业务逻辑,并提供接口给前端调用。
优点是代码结构清晰,维护性较高。前端和后端分别独立开发,可以根据具体需求选择合适的技术栈,提升开发效率和质量。前后端通过接口进行通信,前端可以独立进行开发和调试,后端可以根据需求进行灵活的部署和扩展。
前后端不分离适用于一些小型项目或者对开发速度要求较高的项目,前后端分离适用于大型项目或者对代码结构要求较高的项目。
前后端分离

3、Restful 规范

REST全称是Representational State Transfer
Restful是一种定义web api接口的设计风格
这种风格的理念认为后端开发数据就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的url路径就表示这种要操作的数据资源

1️⃣.数据的安全保障

url链接一般都采用https协议进行传输
采用https协议,可以提供数据交互过程中的安全性

2️⃣.接口特征表现

用api关键字标识接口url
看到api,就代表该请求url链接是完成前后端数据交互
例如: https://www.baidu.com/api or https://api.baidu.com

3️⃣.多数据版本共存

在url链接中标名数据版本
例如: https://api.baidu.com/v1 or https://api.baidu.com/v2
链接中的v1、v2就是不同数据版本的体现,可以更好兼容新老版本客户端

4️⃣.数据即资源,均使用名词(可负数)

接口一般都是完成前后端端数据交互,交互的数据我们称之为资源
一般提倡使用资源的复数形式,可以让访问信息更直观,尽量不要出现操作资源的动词
例如:https://api.baidu.com/book or https://api.baidu.com/books
#错误示范
例如:hppts://api.baidu.com/get_book or https://api.baidu.com/delete-user
#特殊接口
特殊的接口可以出现动词,因为这些接口一般没有一个明确资源,或者动词就是接口的核心含义
例如:https://api.baidu.com/login or https://api.baidu.com/place/search

5️⃣.资源操作由请求方式决定(method)

操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书

6️⃣.过滤-通过在url上传参的形式传递搜索条件

https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件

7️⃣.响应状态码-通过状态码来判断响应

1.正常响应-状态码2xx
200:常规请求
201:创建成功
2.重定向响应-状态码3xx
301:永久重定向
302:临时重定向
3.客户端异常-状态码4xx
403:请求无权限
404:请求路径不存在
405:请求方法不存在
4.服务器异常-状态码5xx
500:服务器异常

8️⃣.错误处理

应返回错误信息,error当做key
{‘error’:‘参数错误’}

9️⃣.返回结果,针对不同的操作,服务器向用户返回的结果应该符合以下规范

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

🔟.需要url请求的资源需要带请求链接

例如:{
‘info’:user,
‘img’:‘https://img.png’
}

4、Django-restframwork的使用

1️⃣. 在Djano 中安装和注册

pip install djangorestframework # 安装

2️⃣.如果要使用 restframework 需要在 django 项目 settings 中注册才可以使用

#settings.py 
 INSTALLED_APPS = [
		... # 其他注册的组件
		'rest-framework', # 注册 rest-framewor
]

5、序列化器

1️⃣. 什么是序列化

序列化:序列化器会把模型对象转换成字典,经过response认证后变成json字符串
反序列化:把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典变成模型

2️⃣.序列化器在CURD中如何执行

:校验请求数据 -> 执行反序列化过程 ->保存数据库 - > 将保存的对象序列化并返回
:判断要执行的数据是否存在- >执行数据库删除
:判断要修改的数据是否存在 - >校验请求的数据 - >执行反序列化过程 ->保存数据库 - > 将保存的对象序列化并返回
: 查询数据库 - > 将数据序列化并返回

3️⃣.序列化器的实现

在项目目录中创建序列化器seralizers.py
eg:

# models.py
from django.db import models
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, verbose_name='书名', unique=True)
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格', blank=True, null=True, unique=True)
        class Meta:
        db_table = 'books'
        verbose_name = '图书表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title

定义book 的序列化类,继承自 serializers.Serializer

# seralizers.py
from rest_framework import serializers
from .models import Book
# 继承Serializer
class BookSerializer(serializers.Serializer):
    """图书序列化器"""
    id = serializers.IntegerField(label='id', read_only=True)
    title = serializers.CharField(label='书名', max_length=50)
    price = serializers.DecimalField(label='价格', max_digits=10, decimal_places=2)

    def create(self, validated_data):
        """
        创建数据
        :param validated_data: 验证过的数据
        :return:
        """
        return Book.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        
        :param instance: 要跟新的实列
        :param validated_data: 跟新的内容
        :return: 跟新后的实例
        """
        instance.title = validated_data.get('title', instance.title)  # 获取要跟新的值
        instance.price = validated_data.get('price', instance)
        instance.save()
        return instance

定义book 的序列化类,继承自 serializers.Serializer

class BookSerializer(serializers.ModelSerializer):
#  直接写元类
    class Meta:
        model = models.Book # 使用的模型类
        # 需要序列化哪些字段,可以是元组或列表,有默认实现的 create 和 update
        fields = ('id', 'title', 'price') 
        

4️⃣、关系和超链接

如果要把字段转化为可以跳转对应 id 的 url 链接,需要使用serializers.HyperlinkedModelSerializer
HyperlinkedModelSerializer类是drf用于实现超链接模型序列化器的父类
HyperlinkedModelSerializer与ModelSerializer的区别:
ModelSerializer默认情况下不包括 ID 字段
HyperlinkedModelSerializer自带一个 url 字段,使用HyperlinkedRelatedField
关联关系使用HyperlinkedRelatedField

#serializer.py
class BookSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    # serializers.HyperlinkedIdentityField:超链接的标识字段
    # view_name 用哪个视图去处理
    highlighted = serializers.HyperlinkedRelatedField(view_name='book-ighlighted', format='html')

    class Meta:
        model = models.Book
        fields = ('url', 'id', 'title', 'price', 'highlighted')


class UserSerializer(serializers.HyperlinkedModelSerializer):
    books = serializers.HyperlinkedRelatedField(many=True, view_name='book-detail', read_only=True)

    class Meta:
        model = User
        fields = ('url', 'id', 'username', 'books')

#urls.py
#路由中使用name字段定义名称,与序列化类中要使用的视图名称一致匹配
path('books/<int:pk>/', views.BookDetail.as_view(),name='book-highlight'),
path('users/<int:pk>', views.UserDetail.as_view(),name='book-detail'),

# 接口内容
# 可以看到url自动生成的跳转地址
# 以及 生成的字典highlighted生成的超链接地址
[
    {
        "url": "http://127.0.0.1:8000/users/1",
        "id": 1,
        "title": "python",
        "price": 88.26,
        "owner": "liuwei",
        "highlighted": "http://127.0.0.1:8000/books/1/"
    },
    {
        "url": "http://127.0.0.1:8000/users/2",
        "id": 2,
        "title": "美丽的一天",
        "price":22.56
        "owner": "liuwei",
        "highlighted": "http://127.0.0.1:8000/books/2/"
    }
]

6、视图

rest框架提供了三种可用于编写API视图的包装饰器(wrappers)

1️⃣.基于函数视图的@api_view装饰器

@api_view的使用
from rest_framework import status  # rest_framework 提供的状态吗
from rest_framework.decorators import api_view  # api_view 装饰器
from rest_framework.response import Response  # def response
from .models import Book
from .seralizer import BookSerializer


@api_view(['GET', 'POST'])  # 装饰器列表参数,声明该视图处理那种请求类型
def book_list(request):
    if request.method == 'GET':
        books = Book.objects.all()
        ser = BookSerializer(books, many=True)
        return Response(ser.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        # request.data 已用默认的解析器讲数据解析成 json 格式
        ser = BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data, status=status.HTTP_201_CREATED)
        return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)

2️⃣基于类视图

APIView类系列
class BookList(APIView):
class BookList(APIView):
    def get(self, request, format=None):
        """
        # 内部定义对应的请求方法,会根据前端的请求类型做不同的操作
        :param request:  rest_framework提供的 request
        :param format: 
        :return: 
        """
        books = Book.objects.all()
        ser = BookSerializer(books, many=True)
        return Response(ser.data, status=status.HTTP_200_OK)

    def post(self, request, format=None):
        ser = BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data, status=status.HTTP_201_CREATED)
        return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)


class BookDetail(APIView):
    def get_object(self, pk):
        try:
            book = Book.objects.get(pk=pk)
            return book
        except Book.DoesNotExist:
            raise status.HTTP_404_NOT_FOUND

    def get(self, request, pk, format=None):
        book = self.get_object(pk)
        ser = BookSerializer(book)
        return Response(ser.data, status=status.HTTP_200_OK)
    
    def put(self, request, pk, format=None):
        book = self.get_object(pk)
        ser = BookSerializer(book, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data, status=status.HTTP_201_CREATED)
        return Response(ser.errors, status=status.HTTP_404_NOT_FOUND)
    
    def delete(self, request, pk, format=None):
        book = self.get_object(pk)
        book.delete()
        return Response(status=status.HTTP_200_OK)

# urls.py
urlpatterns = [
    path('books/',views.BookList.as_view()),
    path('books/<int:pk>/',views.BookDetail.as_view()),
]

请求 books

在这里插入图片描述
在这里插入图片描述

混合类(mixins)

使用基于混合类的视图,最大的优势是创建可服用的代码,通过抽象出相似代码放入 mixin 类中,然后作为父类提供

mixins作用请求方法
ListModelMixin定义 list 方法,返回一个 quesrset 列表Get
CreateModelMixin定义 create 方法,创建一个实列Post
RetrieveModelMixin定义 retrieve 方法,返回一个 具体的实例Get
UpdateModelMixin定义 update 方法, 对某个实列进行跟新Put/Patch
DestroyModelMixin定义 delete 方法, 删除某个实列Delete

使用 mixins 示例

class BookList(mixins.ListModelMixin, mixins.CreateModelMixin, mixins, generics.GenericAPIView):
    queryset = Book.objects.all()  # 指定查询集
    serializer_class = BookSerializer  # 指定序列化器

    # queryset 和 serializer_class 是继承的generics.GenericAPIView的类属性
    # 定义 get 方法
    def get(self, request, *args, **kwargs):
        # 使用 ListModelMin 的 list 方法,讲数据自动转化格式处理返回
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class BookDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)

通过使用 minxin 类,可以减少代码量重写视图,基于此,rest_framework 也提供了一组已经混合好的通用视图类
eg:

class BookList(generics.ListCreateAPIView):
    """
    generics.ListCreateAPIView 源码继承了mixins.ListModelMixin,mixins.CreateModelMixin,
    GenericAPIView,封装了get和post方法
    """
    queryset = Book.objects.all()
    serializer_class = BookSerializer


class BookDetail(generics.RetrieveUpdateDestroyAPIView):
    """
    generics.RetrieveUpdateDestroyAPIView 源码继承了mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,mixins.DestroyModelMixin,GenericAPIView,
    封装了 get、put、delete、patch方法
    """
    queryset = Book.objects.all()
    serializer_class = BookSerializer

3️⃣.基于viewset视图集的类系列

ViewSet和 Router

RESTFRAMEWORK框架为我们提高了一个更加抽象的ViewSet视图集,ViewSet提供一套自动的urlconf路由
ViewSet与View类几乎相同,不同之处在于它们提供诸如read或update之类的操作,而不是get、put等方法处理程序
ViewSet通常使用Router类来处理URL conf
我们使用 ReadOnlyModelViewSet 类来自动提供默认的“只读”操作。仍然像使用常规视图那样设置 queryset 和 serializer_class 属性,但我们不再需要向两个不同的类提供相同的信息

#views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework import renderers

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    这个视图集会自动提供`list`和 `detail`操作
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

class BookViewSet(viewsets.ModelViewSet):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
    '''
    父类里面没有highlight方法,我们使用action装饰器
    detail:是否查看详细
    renderers_class:指定使用的解析器
    '''
    @action(detail=True,renderers_class=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        book = self.get_object()
        return Response(book.highlighted)

#url.py 显式地将ViewSets绑定到URL路由上
from .views import BookViewSet,UserViewSet,api_root

# 字典参数映射,例如使用get方法 就是 对应list操作
book_list = BookViewSet.as_view({
    'get':'list',
    'post':'create'
})

book_detail = BookViewSet.as_view({
    'get': 'list',
    'put': 'update',
    'patch':'partial_update',
    'delete':'destroy'

})

urlpatterns = [
    path('books/',books_list),
    path('book/',book_detail)
]

#url.py 使用 drf 提供的路由 Router
from rest_framework.routers import DefaultRouter
router =DefaultRouter()
# 通过router注册了两类url,books 和 users,而不是两条url
# router可以自动实现根路由
router.register(r'books', views.BookViewSet),
router.register(r'users', views.UserViewSet),
urplatterns += router.urls

7、权限

没有设置权限的 api,任何人都可以编辑或删除,通常为了确保
每个代码都关联一个创建者
只有通过身份认证的用户才可以创建
只有创建者可以跟新或删除
未经身份验证的请求只具有只读的访问权限
,可以对模型类进行修改,添加字段用于标识创建代码片段用户,如在模型中可以添加.save()方法,会覆盖父类 Seven()方法,通过添加逻辑,使保存每一个对象到数据库的时候执行相关逻辑

from django.db import models
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, verbose_name='书名', unique=True)
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格', blank=True, null=True, unique=True)
	# 定义一个外键,指向django.contrib.auth.models.User模型
    owner = models.ForeignKey('auth.User',related_name='books',on_delete=models.CASCADE)
	def save(self,*args,**kwargs):
			# ....相关代码快
			super(Book,self).save(*args, **kwargs)
    class Meta:
        db_table = 'books'
        verbose_name = '图书表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title

# 导入user模型
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):

    # PrimaryKeyRelatedField 将对应的模型序列化成一个列表,存放对应的的主键的数值
    # 添加一个snippets序列化字段,值是 对象模型的序列化列表
    snippets = serializers.PrimaryKeyRelatedField(many=True,queryset=Book.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username','books')

# 用户视图
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

#urls.py
path('users/',views.UserList.as_view()),
path('users/<int:pk>',views.UserDetail.as_view())

在 Book 模型类中定义的 owner 外键是必传项,直接创建数据会报错

# perform_create 是django给我们提供的一个钩子方法,调用序列化的save()
# 在视图里面重写perform_create方法,为owner提供参数
def perform_create(self, serializer):
    # self.request是drf把Django原有request封装
    # owner为 Book模型类型定义的 关联User外键
    # self.request.user,默认后台的会话会往request对象写入一个user,就是服务器当前登录的用户
    serializer.save(owner=self.request.user)

# 在urls中分发路由到admin后台url,在页面登录后才可以创建数据
path('api-auth/',include('rest_framework.urls'))

#默认 owner字段当前关联的是 当前登录用户的ID
#在BookSerizlizer里定义字段 可以展示为当前登录的用户名
# source表示这个字段的值来自哪
# ReadOnlyField / 只读
owner = serializers.ReadOnlyField(source='owner.username')

在 Django-RestFramework 中专门提供了登录和权限认证
如果希望只有通过认证的用户才可以进行相关操作,就需要使用 rest_reamework 提供的 permissions 来设置相关权限
eg:

# 1 、在视图类方法里直接定义权限
 # permission_classes 为当前视图应该遵守的权限,是一个元组 
 # IsAuthenticatedOrReadOnly:如果是已经登录的用户,就允许所做创建功能,否则只读
 permission_classes = (IsAuthenticatedOrReadOnly,)
#2、如果权限要细分,只针对当前用户可以进行操作
#在项目下创建一个permissions.py 权限类
#定义一个权限类,继承permissions.BasePermission 基类
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        """
        如果权限被授予返回 True,否则返回 False
        :param request:
        :param view:
        :param obj: 当前对线的实列
        :return:
        """
        if request.method in permissions.SAFE_METHODS:
            # 当强求方法是安全时,不会造成破坏性操作,返回 True
            return True
        # 判断当前的对象用户 和 当前请求中登录的用户是否一致,一致返回 True 不一致返回 False
        return obj.owner == request.user

#view.py
from .permissions import IsOwnerOrReadOnly
#在视图类里面 ,操作的视图类权限设定元组追加自定义的权限类
permission_classes = (IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly)

8、drf 视图系统组成及继承关系

REST framework提供了一个 APIView 类,它是Django的 View 类的子类。
APIView 类和Django原生的类视图的 View 类有以下不同:
传入的请求对象不是Django原生的 HttpRequest 类的实例,而是REST framework的Request 类的实例。
视图返回的是REST framework的 Response 响应,而不是Django的 HttpResponse 。
视图会管理内容协商的结果,给响应设置正确的渲染器
任何 APIException 异常都会被捕获,并且传递给合适的响应。
请求对象会经过合法性检验,权限验证,或者阈值检查后,再被派发到相应的视图。
使用 APIView 类和使用一般的 View 类非常相似,通常,进入的请求会被分发到合适的处理
Api 视图的属性

.renderer_classes  #渲染器
.parser_classes # 解析器
.authentication_classes #认证器
.throttle_classes #限流器
.permission_classes #权限
.content_negotiation_class  #内容协商

API实例化方法属性

.get_renderers(self)
.get_parsers(self)
.get_authenticators(self)
.get_throttles(self)
.get_permissions(self)
.get_content_negotiator(self)
.get_exception_handler(self)

API实现方法

下面这些方法会在请求被分发到具体的处理方法之前调用
.check_permissions(self, request)
.check_throttles(self, request)
.perform_content_negotiation(self, request, force=False)

分发dispatch()

下面这些方法会被视图的 .dispatch() 方法直接调用。它们在调用 .get , .post() , put() , patch() 和 delete() 之类的请求处理方法之前或者之后执行任何需要执行的操作。
.initial(self, request, *args, **kwargs)
在处理方法调用之前进行任何需要的动作。 这个方法用于执行权限认证和限制,并且执行内容
协商,通常不需要重写此方法。

.handle_exception(self, exc)
任何被处理请求的方法抛出的异常都会被传递给这个方法,这个方法既不返回 Response 的实例,也不重新抛出异常。
默会处理 rest_framework.expceptions.APIException 的子类异常,以及Django的Http404 和 
PermissionDenied 异常,并且返回一个适当的错误响应。
如果你需要在自己的API中自定义返回的错误响应,你可以重写这个方法。

.initialize_request(self, request, *args, **kwargs)
这个方法确保传递给视图的请求对象是 Request 的实例,而不是原生的
Django HttpResquest 的实例。通常不需要重写这个方法。

.finalize_response(self, request, response, *args, **kwargs)
确保任何从处理请求的方法返回的 Response 对象被渲染到由内容协商决定的正确内容类型。
通常不需要重写这个方法。

@api_view()装饰器

签名: @api_view(http_method_names=['GET'], exclude_from_schema=False)
api_view 装饰器的主要参数是响应的HTTP方法的列表。 比如,你可以像这样写一个返回一
些数据的非常简单的视图。
这个视图会使用settings中指定的默认的渲染器,解析器,认证类等等。
默认的情况下,只有 GET 请求会被接受。其他的请求方法会得到一个"405 Method Not
Allowed"响应。

API 访问策略装饰器

REST framework提供了一组可以加到视图上的装饰器来重写一些默认设置。这些装饰器必须放
在 @api_view 装饰器的后 ()面。比如,要创建一个使用限制器确保特定用户每天只能调用
一次的视图,可以用 @throttle_classes 装饰器并给它传递一个限制器类的列表。
from rest_framework.decorators import api_view
@api_view()
def hello_world(request):
return Response({"message": "Hello, world!"})

@api_view(['GET', 'POST'])
def hello_world(request):
if request.method == 'POST':
return Response({"message": "Got some data!", "data": request.data})
return Response({"message": "Hello, world!"})

这些装饰器和前文中的 APIView 的子类中设置的属性相对应。
可用的装饰器有:

@renderer_classes(...)
@parser_classes(...)
@authentication_classes(...)
@throttle_classes(...)
@permission_classes(...)
这些装饰器都只接受一个参数,这个参数必须是类的列表或元组。

视图模式装饰器

要重写默认的基于函数的视图生成的模式,你需要使用 @schema 装饰器。它必须放在
@api_view 装饰器后面,例如:
如果给装饰器传递一个 None 参数值,那么会将函数排除在模式生成之外。
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.throttling import UserRateThrottle
class OncePerDayUserThrottle(UserRateThrottle):
rate = '1/day'
@api_view(['GET'])
@throttle_classes([OncePerDayUserThrottle])
def view(request):
return Response({"message": "Hello for today! See you tomorrow!"})

from rest_framework.decorators import api_view, schema
from rest_framework.schemas import AutoSchema
class CustomAutoSchema(AutoSchema):
def get_link(self, path, method, base_url):
# override view introspection here...
@api_view(['GET'])
@schema(CustomAutoSchema())
def view(request):
return Response({"message": "Hello for today! See you tomorrow!"})

@api_view(['GET'])
@schema(None)
def view(request):
return Response({"message": "Will not appear in schema!"})

通用视图GenericAPIView、属性和方法

generics.ListCreateAPIView
源码

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

使用代码

from rest_framework import generics
from .models.models import Book # 模型类
from ..serializers import bookSerializer # 序列化器

class BookList(generics.ListCreateAPIView):
    # 指定查询集
    queryset = Book.objects.all()
    # 指定序列化类
    serializer_class = bookSerializer

从源码可以看出,generics.ListCreateAPIView继承了mixins.ListModelMixin, mixins.CreateModelMixin,GenericAPIView,里面写好了get和post方法使用了混合类的list和create方法,所以我们使用的时候,只需要定义queryset和serializer_calss,然后直接发送get、post请求就可以完成请求,如果需要功能扩展,可以重写get、post方法

GenericAPIView
GenericAPIView继承APIView,将APIView的属性方法重 写扩展,并提供了一些新的属性

 queryset
"""
必须指定,用于从视图返回对象的查询结果集,通常,你必须设置此属性或者重写get_queryset()方法,
如果你重写了一个视图的方法,你应该调用get_queryset()而不是直接访问该属性,因为queryset将被计算一次
这些结果将为后续请求存储起来
"""
class BookList(generics.ListCreateAPIView):
   # 指定查询集
   queryset = Book.objects.all()
   # 指定序列化类
   serializer_class = bookSerializer
   用户验证和反序列化输入以及用户序列号输出的serializer类,通常,必须设置此属性或者重写get_serializer_class()方法
   # 如果有需求重写
   def get_serializer_class(self):
       if self.request.user.is_staff:
           return postSerializer
       return gettSerializer
   # 如果需要,可以重写queryset过滤一遍
   def get_queryset(self):
       ...
   def list(self, request):
       queryset = self.get_queryset() # 获queryset查询结果集
       serializer = self.get_serializer(queryset,many=True) # 获取serializer序列化器
#url
"""
用于执行各个model实例的对下查找的model字段,默认为pk
注意:在使用超链接API时,如果需要使用自定义的值,需要确保在API和序列化类中都设置查找字段
"""
path('book/<int:pk>/', BookListDetail.as_view()),

#视图
class BookListDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
   queryset = Book
   serializer_class = bookSerializer
#不指定lookup_field ,默认就是pk,传值时只能是pk
   def get(self, request, pk):
       #APIView 不能使用get_object()方法获取参数值
       #继承APIView的其他扩展View,可以使用get_objcet()方法获取参数值,也可以直接传值
#指定lookup_field ,传值就是指定的值
lookup_field = 'test'
def get(self, request, test):

lookup_url_kwarg = 'pk' # 如果url内不包含lookup_url_kwarg的值,则会报错 

get_queryset()
"""
返回list视图中使用的查询集,该查询集还用做detail视图中的查找基础,默认返回由queryset属性指定的查询集
平时我们应该多使用这个方法,而不是直接访问self.queryset,因为self.queryset只会被提交一次
然后这些结果将为后续请求缓存起来,该方法可能会被重写以提供动态行为
"""
get_object()
"""
返回用于detail视图的对象实例,默认使用look_up_field参数过滤基本的查询集
该方法可以被重写以提供更复杂的行为,例如基于多个url参数的对象查找
"""
filter_queryset
"""
给定一个queryset,使用任何过滤器后端进行过滤,返回一个新的queryset
"""

mixin类源码解析

mixin类用于提供视图的基本操作行为,注意mixin类提供动作方法,而不是直接定义处理程序方法
例如.get() .post(),这允许更灵活的定义,mixin从rest_framework.mixins导入
mixin只是提供了基本操作行为,并没有queryset、get_object()等属性方法,所以我们使用mixin的时候,
要同时继承GenericAPIView配合使用
CreateModelMixin

#源码
"""
提供 .create(request, *args, **kwargs) 方法,实现创建和保存一个新model实例的功能。
如果创建了一个对象,这将返回一个 201 Created 响应,将该对象的序列化表示作为响应的
主体。如果序列化的表示中包含名为 url 的键,则响应的 Location 头将填充该值。
如果为创建对象提供的请求数据无效,将返回 400 Bad Request ,其中错误详细信息作为响应的正文。
"""
class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        #序列化数据
        serializer = self.get_serializer(data=request.data)
        #is_valid判断序列化是否数据是否有效,raise_exception=Teue有问题自动抛出错误
        serializer.is_valid(raise_exception=True)
        # 调用下方方法保存数据
        self.perform_create(serializer)
        # 成功的headers
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
    
    def perform_create(self, serializer):
        """
        单独写一个函数调用.save,这样写的意义是后续保存前需要做其他事情,
        直接重写该方法就可以
        """
        serializer.save()
    
    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

ListModelMixin

#源码
提供一个 .list(request, *args, **kwargs) 方法,返回查询结果的列表。
如果查询集被填充了数据,则返回 200 OK 响应,将查询集的序列化表示作为响应的主体。
相应数据可以任意分页。
class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        """
        self.get_queryset() 获取查询结果集
        self.filter_queryset() 留了一个钩子,如果有需要,可以过滤结果集
        """
        queryset = self.filter_queryset(self.get_queryset())
        # 设置分页
        page = self.paginate_queryset(queryset)
        # 如果页码不为空
        if page is not None
            # 获取指定页面的序列化内容
            serializer = self.get_serializer(page, many=True)
            # 返回指定页面的数据
            return self.get_paginated_response(serializer.data)
        # 如果页码为空,则返回全部数据
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

RetrieveModelMixin

#源码
"""
提供一个 .retrieve(request, *args, **kwargs) 方法,返回响应中现有模型的实例。
如果可以检索对象,则返回 200 OK 响应,将该对象的序列化表示作为响应的主体。否则将
返回 404 Not Found 。
"""
class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        # 获取指定look_up_field查询的内容
        instance = self.get_object()
        # 序列化数据
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

UpdateModelMixin

#源码
"""
提供 .update(request, *args, **kwargs) 方法,实现更新和保存现有模型实例的功能
同时还提供了一个 .partial_update(request, *args, **kwargs) 方法,这个方法
和 update 方法类似,但更新的所有字段都是可选的。这允许支持 HTTP PATCH 请求。
如果一个对象被更新,这将返回一个 200 OK 响应,并将对象的序列化表示作为响应的主体。
如果为更新对象提供的请求数据无效,将返回一个 400 Bad Request 响应,错误详细信息作为响应的正文。
"""
class UpdateModelMixin:
    """
    Update a model instance.
    """
    
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        # 查询指定的lookup_filed结果
        instance = self.get_object()
        
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_ex ception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)
    
    def perform_update(self, serializer):
        #修改数据,如果操作前需要其他操作,重写该方法
        serializer.save()
    # 局部修改
    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

DestroyModelMixin

# 源码
"""
提供一个 .destroy(request, *args, **kwargs) 方法,实现删除现有模型实例的功能。
如果成功删除对象,则返回 204 No Content 响应,否则返回 404 Not Found 。
DRF对mixin类的设计是让它们可以尽量的组合使用,不是一次只能继承一个mixin,可以同时继承多个mixin
"""
class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        # 查询指定的lookup_filed结果
        instance = self.get_object()
        # 删除指定数据
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        # 删除前需要其他操作的话,可以重写该方法
        instance.delete()

通用类视图

以下类是具体的通用视图,也是我们平时真正使用的类,除非你需要深度定制,否则不要直接使用mixin父类。
这些视图类可以从 rest_framework.generics 导入
CreateAPIView

#源码
"""
仅用于创建功能的视图。提供 post 方法。
"""

class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    """
    Concrete view for creating a model instance.
    """
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
"""
其实就是先继承了CreateModelMixin,然后继承GenericAPIView,最后留个post方法的坑。
后面的类的构造,基本也是这个套路。

ListAPIView

"""
#源码
"""
以只读的方式列出某些查询对象的集合。提供 get 方法。
"""
class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

RetrieveAPIView

#源码
"""
以只读的形式获取某个对象。提供 get 方法。
"""
class RetrieveAPIView(mixins.RetrieveModelMixin,
                      GenericAPIView):
    """
    Concrete view for retrieving a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

DestroyAPIView

#源码
"""
删除单个模型实例。提供 delete 方法。
"""
class DestroyAPIView(mixins.DestroyModelMixin,
                     GenericAPIView):
    """
    Concrete view for deleting a model instance.
    """
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

UpdateAPIView

#源码
"""
更新单个模型实例。提供 put 和 patch 方法
"""
class UpdateAPIView(mixins.UpdateModelMixin,
                    GenericAPIView):
    """
    Concrete view for updating a model instance.
    """
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

ListCreateAPIView

#源码
"""
创建或者列出模型实例的集合。提供 get 和 post 方法。
同时继承了ListModelMixin、CreateModelMixin两个mixin类,以及基类 GenericAPIView。
"""
class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

RetrieveUpdateAPIView

#源码
"""
读取或更新单个模型实例。提供 get , put 和 patch 方法的占坑。
"""
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
                            mixins.UpdateModelMixin,
                            GenericAPIView):
    """
    Concrete view for retrieving, updating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

RetrieveDestroyAPIView

#源码
"""
读取或删除单个模型实例。提供 get 和 delete 方法。
"""
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
                             mixins.DestroyModelMixin,
                             GenericAPIView):
    """
    Concrete view for retrieving or deleting a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

RetrieveUpdateDestroyAPIView

#源码
"""
读写删除单个模型实例。提供 get , put , patch 和 delete 方法
"""
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    """
    Concrete view for retrieving, updating or deleting a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

自定义 mixin 和基类

很多时候drf提供的 mixin 并不能满足开发需求,就需要自定义 mixin

# get_object源码中字段查询源代码
    filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
    obj = get_object_or_404(queryset, **filter_kwargs)

自定义多字段查询 mixin

class MultipleFieldLookupMixin(object):
    # 重写GenericAPIView里面的 get_object
    def get_object(self):
        queryset = self.get_queryset()
        queryset = self.filter_queryset(queryset)
        filter = {} # 重写字段部分代码,原来是只有一个字段,使用循环处理返回多个字段
        for field in self.lookup_fields:
            if self.kwargs[field]:
                filter[field] = self.kwargs[field]

        obj = get_object_or_404(queryset, **filter)
        self.check_object_permissions(self.request, obj)
        return obj

# 继承自定义多字段查询mixin class RetrievePostView(MultipleFieldLookupMixin,generics.RetrieveAPIView): queryset = Post.objects.all() serizlizer_calss = PostSerializer lookup_fields = ('pk','title') # 多字段

# 继承自定义多字段查询mixin
class RetrieveBookView(MultipleFieldLookupMixin,generics.RetrieveAPIView):
    queryset = Book.objects.all()
    serizlizer_calss = BookSerializer
    lookup_fields = ('pk','title') # 多字段
#url视图匹配多个字段
path('posts/<int:pk>/<str:title>', RetrieveBookView.as_view()),

ViewSet和 Action

dispatch过程中,下列属性可用于 ViewSet
basename - 根 url 路径
action - 当前动作类型(如 list,create)
detail - 用于当前动作是对于一个列表还是一个对线 detail 的布尔指示器
suffix. - vieset类型的前缀
name - viewset的名字
description - 描述信息
eg:

def get_func(self):
    # 通过action判断动作类型
    if self.action == 'list':
        '''get请求操作'''
    else:
        '''其他请求操作'''

VeiwSet视图集中,如果我们需要被路由用的额外方法,可以使用@action装饰器进行标记

from rest_framework.decorators import action

@action()装饰器参数 
methods: 该action支持的请求方式,列表传递
detail: 表示是action中要处理的是否是视图资源的对象(即是否通过url路径获取主键)
        True 表示使用通过URL获取的字段对应的数据对象
        False 表示不使用URL获取字段
# post请求  url路由: <int:pk>/set_password
from rest_framework.decorators import action
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'])  
    def set_password(self, request, pk=None):  # 定义pk=None
        '''
        detail=True,从路由中获取pk字段。假设pk=test
        那么操作路径为 .../test/set_password
        '''
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
            
# put和post请求 url路由:.../set_password
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=False, methods=['post','put'])  
    def set_password(self, request):  
        '''
        detail=False,不从url中获取字段
        '''
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# action装饰器中也可以加其他额外参数,例如权限、限流等等

# 示例 增加权限验证
@action(detail=False, methods=['post','put'],permission_classes = [isAdminOrIsSelf])  
def set_password(self, request):  

ViewSet继承自 views.APIView 。可以使用任何父类属性,
如 permission_classes , authentication_classes 以便控制视图集上的 API 策略。
ViewSet 类不提供任何操作的实现。使用 ViewSet 类,需要重写该类并显式地定义动作实现。
GenericViewSet类继承 GenericAPIView
并提供 get_object ,get_queryset 方法和其他通用视图基本行为的默认配置,但默认情况不包括任何操作

ModelViewSet 又继承了 GenericAPIView ,但实现了基本的HTTP请求方法。
它提供 .list() , .retrieve() , .create() , .update() , .partial_update()和 .destroy() 操作。
这是我们真正使用的类,使用时至少需要提供queryset 和 serializer_class 属性的值。
可以使用父类GenericAPIView所有的方法

ReadOnlyModelViewSet 也继承 GenericAPIView 。与 ModelViewSet 相同的是,它也包
括一些动作的实现。不同的是但是只提供只读的 .list() 和 .retrieve() 动作

9、路由器 Router

rest_framework为django添加了以一种简单、快速、一致的方式自动生成 urls 的功能,但routers必须配合viewset使用
DRF的routers模块主要包含以下几个类:
BaseRouter:路由的基类
SimpleRouter:继承了BaseRouter 常用类之一
DefaultRouter:继承了BaseRouter 常用类之一
DynamicDetailRoute
DynamicListRoute
RenameRoutermethods
APIRootView
我们主要使用的就是SimpleRouter、DefaultRouter
SimpleRouter

'''
对于register()方法,有两个必填参数
   1.prefix:用户视图集的url路径前缀
   2.vewset:对应的视图集类
还有一个可选,但是又特别重要的参数
   basename:URL的name属性的基础部分
   如果没有显示的指定这个参数,那么将以视图集的queryset属性的值自动生成
   如果视图集没有queryset属性,那么你必须在register方法里设置 
'''
router = routers.SimpleRouter()
# 使用register方法注册路由模式
router.register(r'update/', TestUpdateView)
router.register(r'read/', TestReadView)
# 将router的urls属性赋值给django的基本路由变量urlpatterns
urlpatterns = router.urls

# 自定义生成路由
class TestUpdateView(viewsets.ModelViewSet):


    @action(
    methods=['get'],detail=False,
    url_path='get-info', #在action自定义设置path
    url_name='get_info' #action中自定义指定name
    # 这样路由会使用自定义的规则,而不会使用自动生成的规则
    )
    def getInfo(self,request):
        ...
  --------------------------------------------------------------- 
将ViewSets绑定到URL路由上
'''
SnippetViewSet继承ModelViewSet,默认有ViewSet提供的 list、update等方法
自己定义get请求方法getInfo
'''

urlpatterns = [
    path('admin/', admin.site.urls),
    path('sinpple/',views.SnippetViewSet.as_view(
         'get': 'list',  #get请求 sinpple,使用ModelViewSet提供的list方法
         'put': 'update'  #put请求 sinpple,使用ModelViewSet提供的update方法
    ))
    
    
    # 由于一个视图集中我们有多个get请求都要使用,上面使用get访问list方法,所有再定义get我们需求生成新的路由,
    # 不然全部是一个url路径,两个get请求不知道访问哪个方法
    path('sinppleInfo/',views.SnippetViewSet.as_view({'get':'getInfo'})
]

'''
此方法是自己把viewset方法绑定到url上,并没有使用Router.register去注册,所以不会自动去生成路由
需要自动生成路由的话,可以使用对应方法去注册
'''

路由合并

urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls

路由分发

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)), # 转发到DRF的router
]
-------------------------
提供一个app的命名空间参数
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include((router.urls, 'app_name'))),
    ]
------------------------------
app和实例的命名空间
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include((router.urls, 'app_name'),namespace='instance_name')),
    ]
-----------------------------------

27.路由器Routers
像一些reils这样的web框架提供自动生成urls的功能,但是Django没有
rest framework为django添加了这一功能,以一种简单、快速、一致的方式
routers必须配合viewset使用
#导包
from rest_framework import routers

‘’’
DRF的routers模块主要包含以下几个类
BaseRouter:路由的基类
SimpleRouter:继承了BaseRouter 常用类之一
DefaultRouter:继承了BaseRouter 常用类之一
DynamicDetailRoute
DynamicListRoute
RenameRoutermethods
APIRootView
我们主要使用的就是SimpleRouter、DefaultRouter
‘’’
基本用法示例-继承SimpleRouter

实例化一个SimpleRouter对象router

router = routers.SimpleRouter()

使用register方法注册路由模式

router.register(r’update/‘, TestUpdateView)
router.register(r’read/’, TestReadView)

‘’’
对于register()方法,有两个必填参数
1.prefix:用户视图集的url路径前缀
2.vewset:对应的视图集类
还有一个可选,但是又特别重要的参数
basename:URL的name属性的基础部分
如果没有显示的指定这个参数,那么将以视图集的queryset属性的值自动生成
如果视图集没有queryset属性,那么你必须在register方法里设置
‘’’

将router的urls属性赋值给django的基本路由变量urlpatterns

urlpatterns = router.urls

自定义生成路由
class TestUpdateView(viewsets.ModelViewSet):

@action(
methods=['get'],detail=False,
url_path='get-info', #在action自定义设置path
url_name='get_info' #action中自定义指定name
# 这样路由会使用自定义的规则,而不会使用自动生成的规则
)
def getInfo(self,request):
    ...

将ViewSets绑定到URL路由上

‘’’
SnippetViewSet继承ModelViewSet,默认有ViewSet提供的 list、update等方法
自己定义get请求方法getInfo
‘’’

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘sinpple/’,views.SnippetViewSet.as_view(
‘get’: ‘list’, #get请求 sinpple,使用ModelViewSet提供的list方法
‘put’: ‘update’ #put请求 sinpple,使用ModelViewSet提供的update方法
))

# 由于一个视图集中我们有多个get请求都要使用,上面使用get访问list方法,所有再定义get我们需求生成新的路由,
# 不然全部是一个url路径,两个get请求不知道访问哪个方法
path('sinppleInfo/',views.SnippetViewSet.as_view({'get':'getInfo'})

]

‘’’
此方法是自己把viewset方法绑定到url上,并没有使用Router.register去注册,所以不会自动去生成路由
需要自动生成路由的话,可以使用对应方法去注册
‘’’
路由器其他语法
路由合并
DRF路由器对象的urls属性,本质上是一个Django标准的URL模式对象,同样可以使用标准的Django路由语法和功能。
可以将 router.urls 追加到现有的urlpatterns里,也就是列表+列表的合并操作

urlpatterns = [
path(‘admin/’, admin.site.urls),
]
urlpatterns += router.urls

路由分发
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘’, include(router.urls)), # 转发到DRF的router
]

提供一个app的命名空间参数
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/’, include((router.urls, ‘app_name’))),
]

app和实例的命名空间

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/’, include((router.urls, ‘app_name’),namespace=‘instance_name’)),
]

SimpleRouterDefaultRouter的区别

url 类型http 方法方法
{prefix}/GETList
{prefix}/POSTcreate
{prefix}/{url_path}/GET@action(detail=False) descorated method
{prefix}/{lookup}GETretrieve
{prefix}/{lookup}PUTUpdate
{prefix}/{lookup}PatchPartial_update
{prefix}/{lookup}DELETEDestroy
{prefix}/{lookup}/{url_path}GET@action(detail=False) descorated method

DefaultRouter类和 SimpleRouter 基本类似
不同之处在于它还会自动生成默认的API根视图的url路径。另外,它还可以为 .json 类型的请求生成对应的url

10、Serializers模块源码解析

rest_framework序列化类的继承关系
field类:
序列化基类的基类

BaseSerializer:
继承field
派生ListSerializer序列化类

Serializer:
继承SerializerMetaClass
继承BaseSerializer

ModelSerializer:
继承Serializer

HyperlinkedModelSerializer:
继承ModelSerializer
反序列化流程
生成序列化对象serializer([instance],data=request.data)
数据验证:is_valid()
生成:validated_data
调用save方法
根据是否有instance,分别调用create或者update
返回serializer.data
除了ListSerializer类不太一样
BaseSerializer和Serializer、ModelSerializer三个类的is_valid和save则是完全一样
create和update只有ModelSerializer有直接实现
HyperLinkedModelSerializer是继承ModelSerializer基础上增加了url字段和嵌套
create()源码

BaseSerializer中的create

def create(self, validated_data):
raise NotImplementedError(‘create() must be implemented.’)

在Baseserliazer序列化基类的时候,创建了create方法

但是并没有具体去实现他,只是抛出未实现的异常

‘’’
Serializer继承BaseSerializer,没有直接提供写好的create方法
所以我们继承Serializer实现序列化的时候,需要自己去写create
ModelSerializer源码中实现了create,可以直接使用
‘’’

ModelSerializer源码中的create

def create(self, validated_data):

raise_errors_on_nested_writes('create', self, validated_data)

ModelClass = self.Meta.model  # 获取模型类

info = model_meta.get_field_info(ModelClass) # 获取字段信息赋值给info
many_to_many = {} #多对多空字典
for field_name, relation_info in info.relations.items(): # 遍历info中的字段信息
    # 如果是多对多字段全部拿出去后面单独处理
    if relation_info.to_many and (field_name in validated_data):
        many_to_many[field_name] = validated_data.pop(field_name)

try: 
    # 生成模型的实例对象,赋值给instance
    instance = ModelClass._default_manager.create(**validated_data)
except TypeError: # 异常捕获类型错误
    tb = traceback.format_exc()
    msg = (
        'Got a `TypeError` when calling `%s.%s.create()`. '
        'This may be because you have a writable field on the '
        'serializer class that is not a valid argument to '
        '`%s.%s.create()`. You may need to make the field '
        'read-only, or override the %s.create() method to handle '
        'this correctly.\nOriginal exception was:\n %s' %
        (
            ModelClass.__name__,
            ModelClass._default_manager.name,
            ModelClass.__name__,
            ModelClass._default_manager.name,
            self.__class__.__name__,
            tb
        )
    )
    raise TypeError(msg)

# Save many-to-many relationships after the instance is created.
if many_to_many: # 如果多对多有值字典有值
    for field_name, value in many_to_many.items(): # 遍历多对多信息字典
        field = getattr(instance, field_name) #通过getattr生成对应实例
        field.set(value) #将value保存

return instance

‘’’
整体逻辑就是先将每个字段的值拿出来,先用非多对多字段的值创建模型实例
最后遍历多对多字段值保持到实例,最后返回
‘’’
update()源码
‘’’
update同样也是在BaseSerializer中留下了对应方法的位置待后续实现
Serializer中没有做具体实现,直接继承Serializer类需要自己实现update
下面是ModelSerializer中实现的update源码
‘’’

def update(self, instance, validated_data):
raise_errors_on_nested_writes(‘update’, self, validated_data)
info = model_meta.get_field_info(instance)
m2m_fields = []
for attr, value in validated_data.items(): #直接遍历所有已验证的数据,不区分多对多关系

    # 如果是多对多字段, 
    if attr in info.relations and info.relations[attr].to_many:
        m2m_fields.append((attr, value))  #追加到m2m
    else:
        setattr(instance, attr, value) # 更新字段数据

instance.save()

for attr, value in m2m_fields: #遍历m2m字段
    field = getattr(instance, attr) # 更新数据
    field.set(value)

return instance

is_valid源码
‘’’
is_valid在BaseSerializer中有直接实现
‘’’
def is_valid(self, raise_exception=False):
assert hasattr(self, ‘initial_data’), (
'Cannot call .is_valid() as no data= keyword argument was ’
‘passed when instantiating the serializer instance.’
)

if not hasattr(self, '_validated_data'): # 如果没有self._validated_data
    try: #使用self.run_validation验证数据,如果没有问题赋值给self._validated_data 
        self._validated_data = self.run_validation(self.initial_data)
    except ValidationError as exc:
        self._validated_data = {} #如果有问题将_validated_data赋值为一个空字典
        self._errors = exc.detail # errors信息
    else:
        self._errors = {} 

if self._errors and raise_exception: # 如果有erroes信息和raise_exception设置为True
    raise ValidationError(self.errors) #抛出ValidationError异常信息

return not bool(self._errors)  #如果self._errors没有值就是一个空字典,bool就是False,not bool就是True

save()
def save(self, **kwargs):
# 断言否包含error
assert hasattr(self, ‘_errors’), (
‘You must call .is_valid() before calling .save().’
)

# 断言 self.errors是否有错误信息
assert not self.errors, (
    'You cannot call `.save()` on a serializer with invalid data.'
)

# 检查参数是否包含commit字段
assert 'commit' not in kwargs, (
    "'commit' is not a valid keyword argument to the 'save()' method. "
    "If you need to access data before committing to the database then "
    "inspect 'serializer.validated_data' instead. "
    "You can also pass additional keyword arguments to 'save()' if you "
    "need to set extra attributes on the saved model instance. "
    "For example: 'serializer.save(owner=request.user)'.'"
)
# 检查是否是使用self._data后调用.save()
assert not hasattr(self, '_data'), (
    "You cannot call `.save()` after accessing `serializer.data`."
    "If you need to access data before committing to the database then "
    "inspect 'serializer.validated_data' instead. "
)
# 经过合格验证的数据,self.validated_data的每一项和kwargs的每一项
validated_data = {**self.validated_data, **kwargs}

# 通过判断instance是否有值来决定是更新操作还是创建操作
if self.instance is not None: #如果有值
    # 调用update并且返回对应结果
    self.instance = self.update(self.instance, validated_data)
    # 断言更新后的结果来判断是否更新成功
    assert self.instance is not None, (
        '`update()` did not return an object instance.'
    )
else:#如果没有instance,就执行create方法
    self.instance = self.create(validated_data)
    #断言创建结果
    assert self.instance is not None, ( 
        '`create()` did not return an object instance.'
    )

return self.instance 

传递附加属性到save方法
‘’’
一般来说我们调用save是不传值
下方代码调用save的时候,传递了owner=self.request.user
owner模型类的一个字段
‘’’
def perfortm_create(self,serializer):
serializer.save(owner=self.request.user)

‘’’
save源码中的validated_data = {**self.validated_data, **kwargs}
除了self.validated_data以外,还有kwargs
所以validated_data是 self.validated_data验证过的数据 和 附加数据 kwargs组合起来的一个dict
‘’’

重写save方法
‘’’
一般来说 save方法是去找create或者update方法来做数据更新、创建
‘’’

class EmailSerializer(serializer.Serializer):
email = serializers.EmailField()
message = serializer.CharField()

def save(self):
    email = self.validated_data['email'] #拿到经过验证的email字段
    message = self.validated_data['message'] #拿到经过验证的message的字段
    send_meail(from=email,message=message) #发送邮件

‘’’
上面的代码直接重写了save方法,覆盖源码的save,不再具有更新、创建的功能
is_valid\save\create\update是反序列化的最重要的几个逻辑方法
在序列化中,我们可以完完全全重写几个方法源码的逻辑根据我们的业务逻辑来定制我们的场景
‘’’

11、自定义验证器

反序列化过程中
如果使用is_valid 验证字段通过,我们会获得一个validated_data数据字典
如果验证不通过,会获得一个errors数据字典,is_valid参数如果raise_exception设为True,会弹出ValidationError错误
除了DRF本身的验证,有时候我可以会做一些自定义的验证

自定义字段级别验证器
如果我们要在序列化类中添加自定义的字段验证
定义的函数名字必须是 validate_ 开头,下划线后面跟要验证的字段名称
定义的函数参数:value,value是表示原初始的值
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers

class UserSerializer(ModelSerializer):
class Meta:
model = UserModel
fields = (‘id’, ‘title’, ‘name’)

# 我们要对name字段进行自定义验证,函数名以validate_开头,跟name字段
def validate_name(self, value):
    # value代表原始的值
    # 判断字段Y 如果不在name中
    if 'Y' not in value.lower():
        # 抛出Serializer的ValidationError异常
        raise serializers.ValidationError('名字必须包含字母Y')

    # 如果验证通过,不对数据进行处理,直接返回数据
    return value

自定义对象级别验证器
对象级别的验证器
一个序列化类中只能有一个,且函数名字必须是validate
参数:data, 表示完整的传入进来的序列化类中的所有数据dict
涉及到两个及以上字段验证的可以用对象级别验证器
from rest_framework import serializers

class UserSerializer(serializers.Serializer):
desc = serializers.CharField(max_length=300)
start_time = serializers.DateTimeField
end_time = serializers.DateTimeField

def validate(self, data):
    
    # 检查 starttime是否在endtime之前
    if data['start_time'] > data['end_time']:
        return serializers.ValidationError('time error')
    return data

自定义字段参数验证器
字段参数验证器的名字没有要求,参数是value
定义好规则之后,需要使用该验证器的序列化字段里 使用validators=[]来指定验证器
一个字段可以同时使用多个验证器
from rest_framework import serializers
#验证器函数写在序列化内外都可以

class UserSerializer(serializers.Serializer):

# 定义验证器 参数的值 需要<=1000
def number(self, value):
    if value > 1000:
        raise serializers.ValidationError('number error')
    

# 定义验证器 参数的值 需要>30
def flower(self, value):
    if value < 30:
        raise serializers.ValidationError('flower error')
  

desc = serializers.CharField(max_length=300)
start_time = serializers.DateTimeField()
end_time = serializers.DateTimeField()
score = serializers.IntegerField(validators=[number]) # 指定number验证器
flower = serializers.IntegerField(validators=[number,flower]) #指定number、flower两个验证器

to_internal_value和to_representation
DRF所有序列化器类都继承了BaseSerializer 类
通过重写该类的 to_representation() 和to_internal_value()方法可以改变序列化和反序列化的行为
比如给序列化后的数据进行处理修改等,或者对请求携带的数据进行反序列化处理以及用来自定义序列化器字段
序列化过程:queryset -> serializer类 -> to_representation
反序列化过程:前端提交数据 -> request -> to_internal_value ->field
to_representation() , 转json(序列化)的最后一个步骤,允许我们改变序列化的输出
to_internal_value() ,反序列化的第一个步骤,允许改变我们反序列化的输出
#示例

def to_internal_value(self, data):
# 反序列化时,将configs类型修改为字符串类型
data[‘configs’] = str(data[‘configs’])
# 调用父类to_internal_value方法传入修改后的data
info = super().to_internal_value(data)
return info #返回修改后的数据

def to_representation(self, instance):
data = super().to_representation(instance)
# 序列化时,将字符串类型的configs 转换为字典类型的configs
data[‘configs’] = eval(data[‘configs’])
return data

‘’’
to_internal_value用于重新构造validated_data并返回
to_representation用于重新构造serializer.data的值并返回
‘’’

12、关系字段的序列化

很多时候我们的模型设计并不只是单一的字段数据,还有多对多、1对多、关联等
序列化与单一的字段数据有所不同

模型类
‘’’
如下三个模型类
Text分别与auth和category外键关联和多对多关联
‘’’

## 作者
class Auth(models.Model):
    username = models.CharField(max_length=130)
## 分类
class Category(models.Model):
    name = models.CharField(max_length=130)

## 文章
class Text(models.Model):
    title = models.CharField(max_length=130)
    content = models.TextField()
        '''
    定义外键关联和多对多
    '''

    auth = models.ForeignKey(Auth, on_delete=models.CASCADE)
    category = models.ManyToManyField(Category, blank=True)

    class Meta:
        ordering = ['created']

序列化器
from rest_framework import serializers
from .models import Auth, Category, Text


## 作者
class AuthSerializer(serializers.ModelSerializer):
    class Meta:
        model = Auth
        fields = ('id', 'username',)


## 分类
class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('id', 'username',)
## 文本
class TextSerializer(serializers.ModelSerializer):
    class Meta:
        model = Text
        fields = ('id', 'title', 'content', 'auth', 'category')

视图

from .models import Auth, Text, Category
from .serializer import AuthSerializer, TextSerializer, CategorySerializer
from rest_framework.viewsets import ModelViewSet


class AuthViewSet(ModelViewSet):
    queryset = Auth.objects.all()
    serializer_class = AuthSerializer


class CategoryViewSet(ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class TextViewSet(ModelViewSet):
    queryset = Text.objects.all()
    serializer_class = TextSerializer

路由

from . import views

from rest_framework.routers import DefaultRouter

router = DefaultRouter()

router.register(r'auth', views.AuthViewSet,basename='auth')
router.register(r'category', views.Category,basename='category')
router.register(r'text', views.Text,'text')

urlpatterns = router.urls

字段处理
遵循restful规范,一个名词url只处理一个资源,就像上面代码
不在嵌套的关系字段中同时创建对象
比如创建auth,我们用auth去处理,而不是创建text对象同时创建可auth
前后端协商接口的时候,就要将逻辑分开

## 如果非要在Text中去对auth、category去做处理,这种设计不主张,但是也可以实现
class TextSerializer(serializers.ModelSerializer):
    # 在text序列化类中声明auth的序列化类
    # 有auth的处理,会使用AuthSerializer进行
    # 在序列化中的实例,本身也是一种字段
    # 如果不声明直接就使用text对auth进行操作,会请求出错
    auth = AuthSerializer() 
    
    class Meta:
        model = Text
        fields = ('id', 'title', 'content', 'auth', 'category','created')
        
    # 声明atuh序列化类之后直接请求会抛出non_field_error
    # 在反向序列化的时候,源码默认的create方法不支持嵌套的字段
    # 所以需要我们重写create方法
    '''
    请求的validated_data 数据:
    {'title': 'python', 'content': 'test', 'auth': OrderedDict([('username', '嵌套用户请求')])}
    可以看出auth是的数据是嵌套在对应层级内
    '''
    def create(self, validated_data):
        auth_data = validated_data.pop('auth')  # 通过pop拿出auth对应的值
    
        # 我们要把对应内容创建给Auth,需要调用对应模型的创建
        # 有就获取用户,如果没有就创建用户,get_or_create除了返回结果还会返回对应状态 True /False
        auth,flag = Auth.objects.get_or_create(username=auth_data['username'])
        # 上面我们通过pop把auth拿出来单独传入,其他的字典数据解包传入
        text = Text.objects.create(auth=auth, **validated_data)
        return text
            
        
    ''' 
    上面create只处理了关联的user外键,
    如果处理category的多对多字段,还需要单独处理
    '''        
    def create(self, validated_data):
        # 在text处理关联的多对多字段是跟外键一样的嵌套关系
        category_data = [] # 定义一个多对多空列表
        if validated_data['category']: # 如果有传入多对多字段数据
            category_data = validated_data.pop('category') # 将对应数据拿出
    
        auth_data = validated_data.pop('auth')
        auth = Auth.objects.get_or_create(username=auth_data['username'])
        text = Text.objects.create(auth=auth, **validated_data)
    
        # 待其他数据处理完毕之后判断是否有传入多对多字段
        if category_data:
            # text关联的category使用add方法进行字段追加
            # add 就是orm针对多对多处理的语法
            text.category.add(*category_data)
    
        return text
        
        
    # 更新数据重写update方法
    
    def update(self, instance, validated_data):
        category_data = validated_data.pop('category') 
        auth_data = validated_data.pop('auth')
        # 如果多对多字段 category有值
        if category_data:
            # 更新对应的值
            instance.category.set(category_data)
    
        # 先更新没有关联外键、多对多关联的数据
        instance.title = validated_data.get('title', instance.title)
        instance.title = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        instance.id = validated_data.get('id', instance.id)
        instance.content = validated_data.get('content', instance.content)
        # auth 外键更新
        auth = instance.auth  # 获取到auth 模型
        auth.username = auth_data.get('username', auth.username)
        auth.save()
        return instance


13、ModelSerializer详解

ModelSerializer特点
根据Model模型的定义,自动生成字段
自动生成相应的验证器
实现create和update
自动默认将关系字段映射成PrimaryKeyRelatedField主键关系字段
ModelSerializer指定序列化字段三种方法

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        # 1.序列化指定字段
        fields = ('id', 'name',)
        # 2.序列化全部所有的字段
        fields = '__all__'
        # 3. 序列化除了user以外的所有字段
        exclude = ('user',)
        
'''
三种方法只能使用一种
第二种和第三种方法有暴露数据的风险
例如数据库有password字段,第二种会直接序列化出来,第三种,如果没有排除的话,也会序列化出来
'''

depth
depth用于指定序列化字段时嵌套的深度
如下其中的category分类返回了对应的两个分类id
不查看category数据的话
我们并不知道对应id的name是什么
在这里插入图片描述

class TextSerializer(serializers.ModelSerializer):
    auth = AuthSerializer()

    class Meta:
        model = Text
        fields = ('id', 'title', 'content', 'auth', 'category', 'created')
        # 修改Text序列化器,指定depth深度为1
        depth = 1

可以看出指定深度后,返回的内容 除了id还有对应的name

在这里插入图片描述

read_only_field
如果我们使用Serializer类写序列化的时候,有字段希望只读不被修改的话,会通过read_only字段设定,例如
name = serializer.Charfield(read_only=True)

在ModelSerializer中,如果要设定的只读字段的话使用read_only_field批量指定元组即可
不需要像serializer中一个一个字段设置指定

class Meta:
    model = Text
    fields = ('id', 'title', 'content', 'auth', 'category', 'created')
    depth = 1
    read_only_field = ('created','auth',)

extra_kwargs额外的关键字参数

class Meta:
    model = Text
    fields = ('id', 'title', 'content', 'auth', 'category', 'created')
    depth = 1
    read_only_field = ('created','auth',)
    # 使用extra_kwargs指定字典
    extra_kwargs = {
        # key就是字段名,value就是要设置的参数
        'title':{'write_only':True,'require':True},
    }

14、 序列化器通用字段参数和字段类型

drf在Django字段类型的基础上派生了自己的字段类型以及字段参数
序列化器的字段类型用于处理原始值和内部数据类型直接的转换
还可以用于验证输入、以及父对象检索和设置值
通用字段参数

read_only
    该参数默认为false,设置为True的话则将字段变为只读
    被设置成只读的字段可以包含在api输出中,但是创建或者更新期间不应该包含在输入中
    即使你再序列化的时候为一个read_only字段提供值,也会被忽略
    也就是说validated_data中不会包含发送过来的设置为只读字段的数据
    
write_only
    默认为False
    设置为True则表示只写不读 

required
    默认为True,表示该字段必填
    设置为False表示该字段的内容可以空缺
    
default
    为字段设置默认值
    在字段中同时设置default和required关键字参数是无效的,并且会引发错误
    
allow_null
    该参数的默认值为False,表示该字段不允许为空
    如果设置为True,表示字段的值可以为空,或者是none
    类型Django原生自动参数中的blank和null
    
source
     指定字段的值的来源如何获取
     如果指定了该参数,那么该字段就不需要从请求中获取
     可以序列化的时候知道获取对应的值
     参数的值:
         只接受self参数的方法URLField(source='get_absolute_url')
         是使用点分表示来遍历属性EmailField(source='user.email'
         source='*' 具有特殊含义,用于指示整个对象应该传递到该字段
         这对于创建嵌套表示或者需要访问整个对象以确定输出表示的字段非常有用
     
     值示例:
          user_role = serializers.CharField(source='user_type') # 获取model中user_type字段的内容
          url = serializers.URLField(source='get_absolute_url') # 获取完整url地址 
          group_id = serializers.CharField(source='group.id') # 获取外键关联的组的id

validators
    为字段指定专门的验证器
    
error_messages
    错误消息的错误代码字典
    可以自定义错误信息

label
    一个简短的文本字符串
    可以用作html表单或其他描述性元素中生成的字段的标签
    
help_text
    一个文本字符串
    可以用作html表单或其他描述性元素中生成的字段的描述文字
    
initial
    用于预先填充html表单字段的值
    也就是给字段一个初始化值
    也可以将一个callable可调用方法传递给他
    
style
    该参数的值必须是字典
    可用于控制渲染器如何渲染字段
    也就是为字段添加额外的css或者html控制

布尔类型字段

BooleanField
    普通的布尔字段。也就是值只能为True或者False
    # 示例
    status = serializer.BooleanField()
    
NullBooleanField
    接受 None 作为有效值的布尔字段,也就是说这个字段也可以为空
     # 示例
    status = serializer.NullBooleanField()   

字符串类型字段

CharField
    最基本的文本类型。常用的参数是验证文本是否短于 max_length 和长于 min_length
    不同于Django原生,这两个参数非必须
    参数:
        max_length - 字段最大长度
        min_length - 字段最小长度
        allow_blank - 字段是否可以为空,默认为 False 
        trim_whitespace - 如果设置为 True 则会自动修剪字符串的前导和尾随的空格
        相当于自动应用了一个字符串的strip方法。默认为 True 
        
        
EmailField
    文本格式,验证为有效的电子邮件地址
    #示例:
    EmailField(max_length=None, min_length=None, allow_blank=False)
    
RegexField
    文本格式,字段的值必须与regex参数指定的正则表达式相匹配
    #示例:
    RegexField(regex, max_length=None, min_length=None,allow_blank=False)
    必填的第一位置参数 regex 可以是字符串,也可以是编译过的python正则表达式对象
    
SlugField
     RegexField 字段,根据 [a-zA-Z0-9_-]+ 正则模式验证输入。也就是定死了正则表达式
       
URLField
    RegexField 类型字段,规定字段必须是个合法的URL类型的字符串
    
UUIDField
    确保输入的字段是有效的UUID字符串
    to_internal_value 方法将返回一个 uuid.UUID 实例,在输出时,该字段将以规范的以短横线连接的格式返回一个字符串
    #示例:
    UUIDField(format='hex_verbose')
    format指定uuid值的表示格式:
        'hex_verbose' - 十六进制表示,包括连字符
        'hex' - UUID的紧凑十六进制表示形式,不包括连字符
        'int' - UUID的128位整数表示
        'urn' - UUID的RFC 4122 URN表示
        
FilePathField
    文件路径类型字段,其选择仅限于文件系统上某个目录中的文件名
    #示例:
    FilePathField(path, match=None, recursive=False, allow_files=True,allow_folders=False, required=None, **kwargs)    
    path - 此FilePathField应从中选择的目录的绝对文件系统路径
    match - 作为字符串的正则表达式,FilePathField用它过滤文件名
    recursive - 指定是否应递归搜索路径的所有子目录。默认是 False 
    allow_files - 指定是否应包括指定位置的文件。默认是 True 
    allow_folders - 指定是否应包括指定位置的文件夹。默认是 False 
    
IPAddressField
    确保输入是有效的IPv4或IPv6的字符串
    #示例:
    IPAddressField(protocol='both', unpack_ipv4=False, **options)
    protocol :接受协议的类型。可接受的值是“both”(默认)、“IPv4”或“IPv6”。匹配不区分大小写。
    unpack_ipv4 :解包IPv4映射地址,如:: ww:192.0.2.1
    如果启用此选项,则该地址将解压缩到192.0.2.1。默认为禁用。只能在协议设置为“both”时使用

数字类型字段

IntegerField
    整数
    IntegerField(max_value=None, min_value=None)
    max_value 允许的最大值
    min_value 允许的最小值
    
FloatField
    FloatField(max_value=None, min_value=None)
    max_value 验证提供的数字是否不大于此值
    min_value 验证提供的数字是否不低于此值
    
    
DecimalField
    科学十进制表示,Python的 Decimal 实例
    #示例:
    DecimalField(max_digits, decimal_places, coerce_to_string=None,max_value=None, min_value=None)
    max_digits 数字中允许的最大位数。它必须是 None 或大于或等于 decimal_places的整数。
    decimal_places 与数字一起存储的小数位数。
    max_value :验证提供的数字是否不大于此值。
    min_value : 验证提供的数字是否不低于此值。
    localize :默认为 False 。设置为 True ,则根据当前区域设置启用输入和输出的本地化。这也将迫使 coerce_to_string 设置为 True 。如果已在设置文件中进行了 USE_L10N=True 设置,则会启用数据格式设置。
    rounding :设置量化到配置精度时使用的舍入模式。默认为 None

日期和时间类型字段

DateTimeField
    日期和时间表示
    #示例:
    DateTimeField(format=api_settings.DATETIME_FORMAT,input_formats=None, default_timezone=None)
    format - 时间字符串的格式。如果未指定,则默认为settings中的 DATETIME_FORMAT 设置。
    input_formats - 用于解析日期的输入格式的字符串列表。如果未指定,
    DATETIME_INPUT_FORMATS 将使用默认设置 ['iso-8601'] 。
    default_timezone - 默认时区
    auto_now 和 auto_now_add 模型字段
    使用 ModelSerializer 或 HyperlinkedModelSerializer 序列化器时,,带有auto_now=True 或 auto_now_add=True 的模型字段将自动设置 read_only=True 参数
    如果要覆盖此行为,则需要在序列化类中显式声明一个 DateTimeField 字段
    
    
    
    
DateField
    日期类型字段
    DateField(format=api_settings.DATE_FORMAT, input_formats=None)

TimeField
    时间类型字段
    TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
    
    
DurationField
    持续时间长度类型的字段
    你定义了这种类型的字段,那么在 validated_data 变量中将包含一个datetime.timedelta 实例
    max_value 验证提供的持续时间不大于此值
    min_value 验证提供的持续时间不小于此值

选择类型字段

ChoiceField
    接受有限选择集中的值的字段
    如果相应的模型字段包含 choices=… 参数,则 ModelSerializer 会自动生成一个对应的字段
    #示例:
    ChoiceField(choices)
    choices - 有效值列表或 (key, display_name) 元组列表。
    allow_blank - 如果设置为 True 则应将空字符串视为有效值。如果设置为 False 则空字符串被视为无效并将引发验证错误。默认为 False 。
    html_cutoff - 如果设置,这将是HTML下拉列表标签将显示的最大选择数。
    html_cutoff_text - 如果设置,如果在HTML选择下拉列表中截断了最大项目数,则将显示文本指示符。默认为 "More than {count} items…"
    allow_blank 与 allow_null 两个参数都可以用于 ChoiceField 字段
    但建议只使用一个,而不是同时用两个
     allow_blank 应是文本选择的首选 allow_null 对应数字或其他非文本选择
    
MultipleChoiceField
    可多选的类型字段
    可以接受零个、一个或多个值的字段。 to_internal_value 方法可以返回包含所选值的集合
    #示例:
    MultipleChoiceField(choices)
    choices - 有效值列表或 (key, display_name) 元组列表,必填参数。
    allow_blank - 如果设置为 True 则应将空字符串视为有效值。如果设置为 False 则空字符串被视为无效并将引发验证错误。默认为 False 。
    html_cutoff - 同上
    html_cutoff_text - 同上 

文件和图片字段

FileField 和 ImageField 字段仅适用于使用 MultiPartParser 或 FileUploadParser解析器的情况
大多数解析器(例如JSON)不支持文件上载
Django原生的FILE_UPLOAD_HANDLERS用于上传文件的处理

FileField
    文件字段。执行Django标准的FileField验证
    max_length - 指定文件名的最大长度。
    allow_empty_file - 指定是否允许空文件。
    use_url - 如果设置为 True ,则URL字符串将用于输出表示。如果设置为 False 
    则文件名字符串值将用于输出表示。默认为settings中 UPLOADED_FILES_USE_URL 设置的值,除非另有设置


ImageField
    图像字段。将上载的文件内容验证为与已知图像格式匹配
    max_length - 指定文件名的最大长度。
    allow_empty_file - 指定是否允许空文件。
    use_url - 同上
    使用此字段需要提前安装 Pillow 包

复合类型字段

ListField
    列表的字段类型
    #示例:
    ListField(child=<A_FIELD_INSTANCE>, min_length=None,max_length=None)
    child - 用于验证列表中对象的字段实例。如果未提供此参数,则不会进行验证
    min_length - 列表包含的元素数量不少于该值
    max_length - 列表包含的元素个数不超过该值    
    验证整数列表:serializers.ListField(child=serializers.IntegerField(min_value=0, max_value=100))
    编写可重用的列表字段类:
    在可以在整个应用程序中重用我们自定义的 StringListField 类,而无需为其提供 child参数
    class StringListField(serializers.ListField):
        child = serializers.CharField()

DictField
    字典的字段类型
    DictField(child=<A_FIELD_INSTANCE>)
    child - 同上,但对元素个数没有要求
    验证字符串到字符串的映射的字段示例
    DictField(child=CharField())
    自定义字段类
    class DocumentField(DictField):
        child = CharField()

HStoreField
    预配置的 DictField 。与Django的postgres HStoreField 兼容
    child - 用于验证字典中的值的字段实例。默认子字段接受空字符串和空值
    子字段必须是 CharField 实例
    
JSONField
    json字段类型,用于验证传入数据是否有效的JSON结构    
    JSONField(binary)
    binary - 如果设置为 True 则该字段将输出并验证JSON编码的字符串,而不是原始数据结构。默认为 False

其他字段类型

ReadOnlyField
    只读类型,它只返回字段的值而不进行修改
    默认情况下,此字段用于 ModelSerializer 包含与属性相关的字段名称而不是模型字段

HiddenField
    隐藏的字段,它不根据用户输入获取值,而是从默认值或可调用的值中获取值    
    serializers.HiddenField(default=timezone.now)
    
ModelField
    可以绑定到任意模型字段的通用字段
    此字段用于 ModelSerializer 对应于自定义模型字段类。一般不使用
    
SerializerMethodField
    一个只读字段。它通过调用附加到的序列化类上的方法来获取值。可用于将任何类型的数据添加到序列化对象中
    相当于自定义字段数据
    SerializerMethodField(method_name=None)
    method_name - 要调用的序列化程序上方法的名称。如果不包含,则默认为get_<field_name>  
    extra_info = serializers.SerializerMethodField()
    def get_extra_info(self, obj): # obj参数表示每一条数据库记录
        return {
        'email': 'xxxx',
        'xxxx': 'xxxx',
        'status':obj.related_filed.some_filed,

15 、序列化器关系类型字段

关系字段用于表示模型之间的关联
Django中存在ForeignKey、MantToManyField和OneToOneField三种正向关系,以及反向关联和自定义关联
当继承 ModelSerializer 类的时候,包括关系型字段在内的所有字段会自动生成

StringRelatedField
    使用对象的 __str__ 方法来表示关联的对象
    这个字段其实也就是将关联对象的字符串表示形式的信息拿来,放到自己的序列化类中
    供API视图使用并渲染,然后传递给前端
    #Model示例
    def __str__(self):
        return '%d: %s' % (self.order, self.title)
    # serializer
    info = serializers.StringRelatedField(many=True)
    
    该字段是只读的


PrimaryKeyRelatedField
    PrimaryKeyRelatedField 使用关联对象的主键id值来表示对象
    info = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    默认情况下,这种字段关联方式是可读可写的。可以通过添加 read_only=True 标识,变成只读
    参数:
        queryset - 当对字段的输入数据进行验证的时候,用于查询模型实例的查询集。必须显式的提供这个参数,或者设置 read_only=True 。
        many - 如果关联的是一个复数数量的对象,必须将此参数设置为 True 。
        allow_null - 默认为False。如果设置为 True ,在可空的关联上,该字段将可以接收None 或空字符串。
        pk_field - 指定序列化/反序列化过程中,使用的主键字段类型。例如pk_field=UUIDField(format='hex') 将序列化一个UUID主键值
        
HyperlinkedRelatedField
    HyperlinkedRelatedField 使用关联对象的超链接形式来标识关联对象
    info = serializers.HyperlinkedRelatedField(many=True,read_only=True,view_name='track-detail')
    默认情况下,这种字段关联方式是可读可写的。可以通过添加 read_only=True 标识,变成只读
    这种关系字段适合包含单独主键或者slug参数的URLs,
    参数:
        view_name - 处理关联对象URL的视图。如果你使用的是标准的路由类,它必须是一个<modelname>-detail 格式的字符串。此参数必填
        queryset - 当对字段的输入数据进行验证的时候,用于查询模型实例的查询集。必须显式的提供这个参数,或者设置 read_only=True 。
        many - 如果关联的是一个复数数量的对象,必须将此参数设置为 True 。
        allow_null -默认为False。如果设置为 True ,在可空的关联上,该字段将可以接收None 或空字符串。
        lookup_field - 该参数的默认值为 'pk' ,表示用主键id在视图种查找关联的对象,一般我们不需要修改这个参数。它必须和对应视图的URL关键字参数一致,你这里如果改了,在视图中也必须跟着改。
        lookup_url_kwarg - 指定上面参数值的名字,一般使用 lookup_field 。大多数情况下,我们不用修改这个参数,默认就好,除非你需要自定义一大堆,不要给自己挖坑。
        format - 如果URL种使用了格式后缀,超链接字段将使用同样的格式后缀,除非使用这个 format 参数另外指定后缀形式
        
SlugRelatedField
    SlugRelatedField 使用某个指定的字段的值作为关联对象的表示形式。比如拿对象的名字、或者邮箱、或者昵称、或者地址等等
    info = serializers.SlugRelatedField(many=True,read_only=True,slug_field='title')
    默认情况下,这种字段关联方式是可读可写的。可以通过添加 read_only=True 标识,变成只读
    参数:
        slug_field - 指定用来表示关联对象的字段,该参数必填。
        queryset - 同上
        many - 同上
        allow_null - 同上
        
        
HyperlinkedIdentityField
    这种字段可以使用超链接身份关联,使用较少
    该字段只读
    view_name - 同前。必填。
    lookup_field - 同前
    lookup_url_kwarg - 同前
    format - 同前

嵌套关联
可以将序列化类作为字段,来表示嵌套关联
如果关联的是一个复数数量的一些,必须将many参数设置为True

class TextSerializer(serializers.ModelSerializer):
    # 设置嵌套关联的字段
    info = CategorySerializer(many=True,read_only=True)

    class Meta:
        model = Text
        fields = ('url', 'id', 'title', 'content', 'auth', 'category', 'created','info') #序列化

默认情况下,嵌套关联是只读的!如果你想设置可读写的嵌套关联,你必须自己实现create() 与/或 update() 方法,显式地指定如何保存子关系

#示例
def create(self, validated_data):
    infos_data = validated_data.pop('info')
    text = TextModel.objects.create(**validated_data)
    for info_data in infos_data:
        CategoryModel.objects.create(text=text, **track_data)
    return text

自定义关系类型字段
如果现有的关系样式都不适合,那实现一个完全自定义的关系字段
要自定义关系类型字段,必须先继承 RelatedField 类
然后实现 .to_representation(self, value) 方法
此方法将字段的目标作为“value”参数,并应返回对象序列化后的表示形式,“value”参数通常是模型实例
如果想实现可读写的关系类型字段,需要实现 .to_internal_value(self, data)方法
如果想提供一个基于 context 的动态查询集,需要覆盖 .get_queryset(self) 方法

16、认证系统

认证

身份验证是将传入的请求与一组鉴别凭据关联的机制,然后使用权限和限流策略来确定是否允许请求进入
再权限和限流检查发生之前,以及在执行其他代码之前,实在在视图的最开始处运行身份验证
也就是说认证过程优先级最高,最先被执行
request.user属性通常设置为contrib.auth包的user类的实例,这是Django原生的做法
request.auth属性用于任何其他附加的身份验证信息,例如表示请求中携带的身份验证令牌
上面两个位置request中的属性是认真和权限机制的核心数据

认证的机制

允许使用的认证模式一般以一个类的列表的配置形式存在
drf会尝试使用列表中的每个类进行认证,并使用第一个成功通过验证类的返回值设置request.user和request.auth
如果所有的认证类尝试了一遍都没有通过验证
request.user将被设置为django.contrib.auth.models.AnonymousUser的实例,也就是匿名用户
request.auth将被设置为None
drf的认证机制依赖Django的auth框架
未认证用户请求过程中,request.user和request.auth的值可以通过UNAUTHENTICATED_USER 和 UNAUTHENTICATED_TOKEN两个配置修改
认证功能核心源代码

位于request.py模块中


# 每个认证类的实例验证请求
def _authenticate(self):
    # 循环指定的所有认证类型
    for authenticator in self.authenticators:
        try:
            # 执行认证类的authenticate方法,返回值是一个(user,auth)元组
            user_auth_tuple = authenticator.authenticate(self)
        except exceptions.APIException: #如果认证失败或者错误
            self._not_authenticated() #执行认证失败后的方法
            raise # 向上抛出异常
        # 如果user_auth_tuple不为none 认证通过
        if user_auth_tuple is not None: 
            #将通过认证的认证类保存
            self._authenticator = authenticator
            # 将返回值赋值给request对象的user和auth
            self.user, self.auth = user_auth_tuple
            return #上面已经将需要的值保存了,所以什么也不返回
    # 走完循环都没有认真成功,执行认证失败后的方法
    self._not_authenticated()
    
#认证失败后的方法
def _not_authenticated(self):
    # request中认证器为None,也就是没有认证通过
    self._authenticator = None
    # 如果UNAUTHENTICATED_USER有设置值
    if api_settings.UNAUTHENTICATED_USER:
        # user = 设置的值,默认是AnonymousUser
        self.user = api_settings.UNAUTHENTICATED_USER()
    else:
        # 没有设置的话,user=None
        self.user = None
     # 如果UNAUTHENTICATED_TOKEN有设置值
    if api_settings.UNAUTHENTICATED_TOKEN:
        #auth = UNAUTHENTICATED_TOKEN的值
        self.auth = api_settings.UNAUTHENTICATED_TOKEN()
    else:
        # 没有设置的话 auth =None
        self.auth = None

配置认证方案

全局认证:通过DEFAULT_AUTHENTICATION_CLASSES配置项,可以进行全局性认证方案配置

#全局默认的认证配置
# 全局配置完之后 所有的视图都会进行认证
REST_FRAMEWORK = {
    
'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework.authentication.BasicAuthentication', #基础
    'rest_framework.authentication.SessionAuthentication', #会话
     )
}

视图级别的认证配置

# 基于视图类APIView的认证
# 导入认证模块
from rest_framework.authentication import TokenAuthentication,BaseAuthentication

# 继承APIView类 或者其他继承了APIView的视图类
class AuthViewSet(APIView):

    # 指定认证类
    authentication_classes = [TokenAuthentication, BaseAuthentication]


#使用@api_view装饰器指定视图级别认证

@api_view(['POST'])
@authentication_classes((TokenAuthentication, BaseAuthentication,))#指定认证
@permission_classes((IsAuthenticated,))#指定权限
def getInfo(request):
    ...

# 如果想要某个视图不使用认证功能
# 设置认证类为一个空列表,会覆盖全局配置,即为空不进行认证
authentication_classes = []

未认证和拒绝响应

#当未经身份验证的请求被拒绝时有两种响应
HTTP 401 Unauthorized : 响应会包含一个WWW-AUthenticate头部属性,用于指引用户如何认证
HTTP 403 Permission Denied :响应不会包含WWW-AUthenticate
#只能使用一个方案来确定响应类型,根据视图设置的第一个身份认证类确定响应类型
#如果请求成功但仍被拒绝执行,无论身份验证怎么样,都会返回403的响应

17、BasicAuthentication认证

BasicAuthentication认证介绍
BasicAuthentication使用HTTP基本的认证机制
通过用户名/密码的方式验证,通常用于测试工作,尽量不要线上使用
用户名和密码必须在HTTP报文头部,为 Authorization 属性提供值为 Basic amFjazpmZWl4dWVsb3ZlMTAw 的方式提供,其中 Basic 字符串是键,后面的一串乱码是通过base64 库使用明文的用户名和密码计算出的密文
BasicAuthentication属性

# 如果认证成功BasicAuthentication下面的属性
request.user设置为Django的User类的实例
request.auth设置为None
# 未认证成功
# 响应HTTP 401 Unauthorized,并携带对应头部信息
WWW_Authenticate: Basic realm = 'api'

BaseicAuthentication认证类源码解析

#继承BaseAuthentication认证基类
#BaseAuthentication只是留了authenticate和authenticate_header两个方法,待继承类实现
class BasicAuthentication(BaseAuthentication):

    www_authenticate_realm = 'api'

        # 使用HTTP的基本authentication属性,提供正确的用户名和密码,返回一个user,否则返回None
        def authenticate(self, request):
        # 从http报头读取密文。分割字符串
        auth = get_authorization_header(request).split()
        # 如果分割出来的字符串第一部分不是basic开头,认证失败返回None
        if not auth or auth[0].lower() != b'basic':
            return None
        # 如果长度等于1 只有basic,说明没有提供用户名和密码 认证失败
        if len(auth) == 1:
            msg = _('Invalid basic header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        # 如果分割出的数量 大于2,说明 basic 密文的格式不对,空格太对,认证失败
        elif len(auth) > 2:
            msg = _('Invalid basic header. Credentials string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            # 获取密文部分,使用base64库进行解码,尝试两种编码方式
            try:
                auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
            except UnicodeDecodeError:
                auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
            auth_parts = auth_decoded.partition(':')
        # 解码失败,返回认证失败的异常
        except (TypeError, UnicodeDecodeError, binascii.Error):
            msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
            raise exceptions.AuthenticationFailed(msg)
        # 解码成功,拿到明文的用户名和密码    
        userid, password = auth_parts[0], auth_parts[2]
        # 进行密码比对,返回认证结果
        return self.authenticate_credentials(userid, password, request)
    # 用户密码比对
    def authenticate_credentials(self, userid, password, request=None):
       
        credentials = {
            get_user_model().USERNAME_FIELD: userid,
            'password': password
        }
        user = authenticate(request=request, **credentials)

        if user is None:
            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))

        if not user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (user, None)
    # 设置Basic realm头
    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm

18、TokenAuthentication认证

TokenAuthentication认证介绍
TokenAuthentication是一种简单的基于令牌的HTTP认证
适用于CS架构,例如普通的桌面应用程序或移动客户端
TokenAuthentication认证使用

# 将rest_framework.authtoken 添加到 INSTALLED_APPS
INSTALLED_APPS = (
'rest_framework.authtoken',
)

# 配置认证类
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework.authentication.TokenAuthentication',
    )
}
python manage.py migrate

配置完成后需要运行迁移命令
rest_framework.authtoken实际是一个app或者说第三方模块,需要在数据库生成工作用的数据表

#为用户创建令牌
# 导包
from rest_framework.authtoken.models import Token
token = Token.objects.create(user='...')
print(token.key)

# 要进行身份验证的客户端,令牌密钥应包含在 authorization HTTP头部属性中
#键应该以字符串 "token" 作为前缀,用空格分隔两个字符串
# 示例
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

TokenAuthentication属性

#成功认证后 TokenAuthentication 提供下面的属性
request.user :设置为一个Django的 User 类的实例
request.auth :设置为一个 rest_framework.authtoken.models.Token 的实例

# 认证失败返回 HTTP 401 Unauthorized 响应,并携带下面的HTTP头部信息
WWW-Authenticate: Token

通过信号机制生成令牌
Django 内置了许多信号,允许用户的代码获得特定操作的通知。
例如在 Model 保存前触发的信号 pre_save、在 Model 保存后触发的信号 post_save 等
Django 中的信号主要包含以下三个要素:
发送者(sender):信号的发出方
信号(signal):发送的信号本身
接收者(receiver):信号的接收者

信号接收者其实就是一个简单的回调函数,将这个函数注册到信号上,当特定的事件发生时,发送者发送信号,回调函数被执行
Django 内置的信号,只需要定义回调函数并将它注册到信号上,这里的回调函数作为信号得接收者(receiver)。当程序执行到相应的操作时,自动触发信号,执行回调函数

'''
如果希望每个用户都有一个自动生成的令牌
可以捕获用户的post_save信号
这个信号是Django原生提供的
'''
from django.conf import settings
from django.db.models.signals import post_save  # 信号
from django.dispatch import receiver  # 接收器
from rest_framework.authtoken.models import Token


# 绑定信号机制
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_Auth_token(sender, instance=None, created=False, **kwargs):
    # 如果创建用户
    if created:
        # 创建令牌
        Token.objects.create(user=instance)

获取令牌

'''
用户从客户端使用用户名和密码,往提供令牌服务的API发送表单或json数据
验证通过后,API将用户的令牌以json格式返回给客户端
DRF提供了一个内置的视图 obtain_auth_token 用于实现这一功能
  '''
  
from rest_framework.authtoken import views
urlpatterns += [
    path('api-token-auth/', views.obtain_auth_token),
]

19、SessionAuthenticatio和自定义认证

SessionAuthentication认证介绍
SessionAuthentication使用了Django默认的会话后端
适合AJAX客户端等运行在同样会话上下文环境中的模式
是DRF默认的认证方式之一

SessionAuthentication认证属性

#认证成功 SessionAuthentication 提供下面的属性
request.user :设置为一个Django的 User 类的实例
request.auth :设置为None
# 认证不成功将返回 HTTP 403 Forbidden 响应,没有额外的头部信息

如果使用类似AJAX风格的API,并采用SessionAuthentication认证
当使用不安全的HTTP方法,比如 PUT , PATCH , POST 或者 DELETE,必须提供合法的CSRF令牌
DRF框架中的CSRF验证与标准Django的CSRF验证的工作方式略有不同
因为需要同时支持同一视图的会话和非会话身份验证
只有经过身份验证的请求才需要CSRF令牌,匿名请求可以在没有CSRF令牌的情况下发送
不适用于登录视图,登录视图应始终应用CSRF验证
RemoteUserAuthentication认证
使用Django的auth框架的认证功能
必须在AUTHENTICATION_BACKENDS 配置中使用django.contrib.auth.backends.RemoteUserBackend (或者继承它)
如果认证成功, RemoteUserAuthentication 提供下面的属性:
request.user :设置为一个Django的 User 类的实例
request.auth :设置为None
此认证一般使用不多
自定义认证

# 导入用户模型
from django.contrib.auth.models import User
# 导入认证模块
from rest_framework import authentication
# 异常模块
from rest_framework import exceptions


# 继承BaseAuthentication,实现BaseAuthentication两个待实现的方法
class UserAuthentication(authentication.BaseAuthentication):

    # 认证模块,返回None 或者user
    def authenticate(self, request):
        '''
        从头部获取用户信息
        头部自定义字段为MyUsername
        Django会自动拼上HTTP_全大写英文
        '''
        username = request.MEATA.get('HTTP_MYUSERNAME')
        # 没有用户信息返回None
        if not username:
            return None

        try:
            # 去User查找对应用户信息
            user = User.objects.get(username=username)

        except User.DoesNotExist:
            # 如果找不到对应用户 抛出异常
            raise exceptions.AuthenticationFailed('没有对应用户')

        # 找对对应用户 返回
        return (user, None)  # (request.user,request.auth)

20、JSON Web Token认证

JSON Web Token认证介绍
简称JWT认证,一般用于用户认证
JWT是一种相当新的标准,可用于基于token的身份验证
与内置的TokenAuthentication方案不同,JWT不需要使用数据库来验证令牌
优势:相较于传统的token,无需再服务端保存
基于传统token简单的实现认证
#传统的token认证
1.用户登录服务端返回token,并将token保存在服务端(缓存、db等都可以)
2.以后用户访问的时候,需要携带token,服务端获取携带token后去数据库获取token校验

#model示例
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    token = models.CharField(max_length=64, null=True, blank=True)

--------------
#view示例

class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        user = request.data.get('username')
        pwd = request.data.get('password')
        # 密码验证成功生成token,否则返回错误
        user_object = UserInfo.objects.filter(username=user, password=pwd).first()
        if not user_object:
            return Response({'code': 1, 'error': '用户名或者密码错误'})
        # 使用uuid的方式生成token
        random_string = str(uuid.uuid4())
        # 数据库保存token
        user_object.token = random_string
        user_object.save()
        return Response({'code': 0, 'data': random_string})


class OrderView(APIView):

    def get(self, request, *args, **kwargs):
        # 请求的时候携带token,后端获取token
        token = request.query_params.get('token')
        if not token:# 没有获取到token
            return Response({'code': 2, 'error': '登录成功之后才能访问'})
        # 获取到的token 与数据库的token不匹配
        user_object = UserInfo.objects.filter(token=token).first()
        if not user_object:
            return Response({'code': 3, 'error': 'token无效'})

        return Response({'code': 0, 'data': 'order list'})

jwt实现原理及流程代码示例
在这里插入图片描述
用户登录成功之后,服务端使用jwt创建一个token,并给用户返回,不在服务端存储
token由Header、Payload、Signature三部分组成,这三部分之间以小数点连接

# jwt token 
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU
#Header部分
第一段字符串:Header,内部包含算法和token类型,算法默认使用hs256,token类型是jwt
   {
    "alg": "HS256",
    "typ": "JWT"
  }
将上面JSON转换成字符串,然后做base64url加密,生成上述token第一段header字符串
# Payload部分
第二段字符串:Payload,我们自定义的值的地方,例如下方
{
    "id":"123",
    "name":"qi",
    "xxx":"aaa",
    "exp":1516239022 #超时时间,一般都要设置
}
将上面JSON转换成字符串,然后做base64url加密,生成上述token第二段Payload字符串
因为Payload部分是我们自定义的字段,可以使用base64url反解密,所以这部分不要放敏感内容
# Signature部分
第三段字符串:Signature
第一步:将第1、2部分的密文拼接起来
第二步:对钱2部分密文进行hs256加密 + 加盐
第三部:对hs256加密后的密文,在做base64url加密

以后用户再来访问的时候,需要携带token,后端需要对token进行校验

# 校验流程
第一步:获取token
第二步:对token进行切割,切割成三部分
第三步:对第二段进行base64url解密并获取payload信息
      获取超时时间验证是否超时
第四步:由于md5和hs256不能反解密
      我们再将第一段、第二段字符串拼接进行hs256加密
第五步:将新加密的密文和第三段通过base64解密后的密文对比
      如果密文相等,表示token未修改过,认证通过

如果仿造jwt token,将第二段密文base64反解密,修改了过期时间之后再加密传给服务端
那么即时过期时间验证通过,和第三段密文不匹配,也验证不通过
如果把修改后的第二段和第一段通过加密生成匹配的密文去验证,但是由于第三部分不止加密,并且还加盐,所以也不会通过

pip install pyjwt
------
#创建jwt token 流程示例

import jwt
import datetime


class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        user = request.data.get('username')
        pwd = request.data.get('password')
        user_object = UserInfo.objects.filter(username=user, password=pwd).first()
        if user_object:
            # 加密的盐值,自定义,不能变化,不然验证的时候无法通过
            salt = 'ahjgdsashjgdhjsagfdfasghfdghas'

            # 构造header,加密算法除了hs256还支持其他算法,参考文档支持内容
            headers = {
                'typ': 'jwt',
                'alg': 'HS256'
            }

            # 构造payload
            payload = {
                'user_id': user_object.id,
                'username': user_object.username,
                # 超时时间设置为3分钟
                'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=3)
            }

            # 生成jwt token
            token = jwt.encode(payload=payload, key=salt, algorithm='HS256', headers=headers).decode('utf-8')
            return Response({'code': 0, 'data': token})

        else:
            return Response({'code': 1, 'error': '用户名或者密码错误'})

---------
# jwt token 校验流程示例
 
class getInfoLIst(APIView):

    def get(self, request, *args, **kwargs):
        salt = 'ahjgdsashjgdhjsagfdfasghfdghas'

        # 获取token
        token = request.query_params.get('token')

        try:
            # 从token中获取payload,进行校验,包喊过期时间、第三段密文校验
            # true是校验合法性,False是不校验合法性
            verified_payload = jwt.decode(token, salt, True)
            return Response({'code': 0, 'data': verified_payload['user_id']})

        # 如果token过期,会抛出ExpiredSignatureError异常
        except jwt.exceptions.ExpiredSignatureError:
            return Response({'code': 1, 'error': 'token失效'})
        # 如果token认证失败,会抛出DecodeError异常
        except jwt.DecodeError:
            return Response({'code': 2, 'error': 'token认证失败'})
        # 如果token非法,会抛出InvalidTokenError异常
        except jwt.InvalidTokenError:
            return Response({'code': 3, 'error': '非法的token'})

-----------
基于jwt和drf实现认证示例
# 自定义utils-创建token
import jwt
from django.conf import settings
import datetime
def create_token(payload, timeout=3):
    '''

    :param payload: payload是我们自定义的部分,不同view可能制定字段不一样
                    所以可以其他字段外部定义好传入,在基础上添加超时时间即可
    :param timeout: token超时时间,默认为3分钟,
    :return:
    '''
    salt = settings.SECRET_KEY

    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }

    # 构造payload-给payload添加超时时间
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout)
    token = jwt.encode(payload=payload, key=salt, algorithm='HS256', headers=headers).decode('utf-8')
    return token

----------
#views-创建token
# 导入创建token的方法
from jwt_demo.extensions.jwt_utils import create_token


class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        user = request.data.get('username')
        pwd = request.data.get('password')
        user_object = UserInfo.objects.filter(username=user, password=pwd).first()
        if user_object:
            # 调用创建token的方法
            token = create_token({'user_id': user_object.id})

            return Response({'code': 0, 'data': token})

        else:
            return Response({'code': 1, 'error': '用户名或者密码错误'})

-------------------
认证-创建auth.py文件
from django.conf import settings
# 认证异常的包
from rest_framework.exceptions import AuthenticationFailed

# 继承认证基类
'''
认证类中可以有三种操作
1.抛出异常,后续不再执行
2.返回一个元组,request.user,request.auth
3.返回None

'''


class JwtQueryParamsAuthentication(BaseAuthentication):

    def authenticate_header(self, request):
        salt = settings.SECRET_KEY

        token = request.query_params.get('token')

        try:
            # 进行校验
            payload = jwt.decode(token, salt, True)

        # 抛出异常
        except jwt.exceptions.ExpiredSignatureError:
            raise AuthenticationFailed({'code': 1, 'error': 'token失效'})
        except jwt.DecodeError:
            raise AuthenticationFailed({'code': 2, 'error': 'token认证失败'})
        except jwt.InvalidTokenError:
            raise AuthenticationFailed({'code': 3, 'error': '非法的token'})

        return (payload, token)

--------------
#view-认证

#导入自定义认证类
from jwt_demo.extensions.auth import JwtQueryParamsAuthentication


class getInfoLIst(APIView):
    #指定自定义认证类 或者直接settings中配置全局
    authentication_classes = [JwtQueryParamsAuthentication,]
    def get(self, request, *args, **kwargs):
        ...

21、Permission源码解析和自定义权限类

如何确定权限

认证、限流,权限决定是否应该接收请求或拒绝访问
权限检查在视图的最开始处执行,在继续执行其他代码前
权限检查通常会使用request.user和request.auth属性中的身份认证信息来决定是否允许请求
不同级别的用户访问不同的api过程中,使用权限来控制访问的许可
DRF框架的权限被定义为一个权限类的列表,表示拥有列表中所有类型的权限
在运行视图代码主体之前,会检查列表中的每个权限
任何一个权限检查失败的话,会抛出 exceptions.PermissionDenied 或 exceptions.NotAuthenticated 异常,视图主体代码不会运行
权限检查失败时,会返回403 Forbidden或401 Unauthorized响应
响应规则如下:
请求已成功通过身份验证,但不具备访问权限,返回403 Forbidden响应。
请求未通过身份认证,并且最高优先级的认证类未使用 WWW-Authenticate 标头, 返回403 Forbidden响应。
请求未通过身份认证,但是最高优先级的认证类使用了 WWW-Authenticate 标头,返回HTTP 401未经授权的响应, 并附带适当的WWW-Authenticate报头

如何设置权限

#settings全局权限配置

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES':(
        #drf默认的权限,允许任何访问
        'rest_framework.permissions.AllowAny',
    )
}

------------

# 局部视图级别的权限配置
class UserInfo(ModelViewSet):
    # 列表传入要设置的权限
    permission_classses = []
'''
全局权限和局部权限都有配置,以局部配置为准,装饰器请求的权限设置同样

'''

源码解析

BasePermission:权限基类都直接返回True没有实现具体功能,留了两个坑待后续具体继承实现

class BasePermission(metaclass=BasePermissionMetaclass):
    def has_permission(self, request, view):
         return True
    def has_object_permission(self, request, view, obj):
        return True

AllowAny权限继承权限基类,没有做任何操作直接返回True,任何请求都可以访问

class AllowAny(BasePermission):

    def has_permission(self, request, view):
        return True

IsAuthenticated权限允许任何经过身份验证的用户访问API,拒绝任何未经身份验证的用户

class IsAuthenticated(BasePermission):

    def has_permission(self, request, view):
        # request.user,如果有值,说明当前有用户,判断用户是否登录
        # request.user.is_authenticated,判断用户是否通过认证
        # 如果两个都通过,bool返回True,可以后续执行,否则False不可执行
        return bool(request.user and request.user.is_authenticated)

IsAdminUser权限只允许管理员用户访问,拒绝其他任意身份用户访问

class IsAdminUser(BasePermission)

    def has_permission(self, request, view):
        # request.user,如果有值,说明当前有用户,判断用户是否登录
        # request.user.is_staff属性Django 的auth的属性,如果是True,说明是管理员
        # 如果两个都通过,bool返回True,可以后续执行,否则False不可执行
        return bool(request.user and request.user.is_staff)

IsAuthenticatedOrReadOnly权限:允许经过身份验证的用户完全访问(可读可写),未经过身份验证的用户只允许读取

class IsAuthenticatedOrReadOnly(BasePermission):


    def has_permission(self, request, view):
        return bool(
            # SAFE_METHODS是定义的安全方法list,GET、HEAD、OPTIONS三种请求方式
            # 如果请求方式是三种安全方式之一,直接可以访问,三种读取请求
            # 或者 request.user and request.user.is_authenticated,判断是否登录以及是否验证通过
            # 两种条件满足一种,如果为True,只读或者读写,否则拒绝访问
            request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated
        )

DjangoModelPermissions权限Django模型级别的权限,与Django标准的 django.contrib.auth model权限相关,此权限只能应用于具有 .queryset 属性集的视图。只有在用户通过身份验证并分配了相关模型权限的情况下,才可以授予此权限

class DjangoModelPermissions(BasePermission):
    #权限类属性设定
    perms_map = {
        'GET': [], # 空list,不需要具有模型权限
        'OPTIONS': [],# 空list,不需要具有模型权限
        'HEAD': [], # 空list,不需要具有模型权限
        'POST': ['%(app_label)s.add_%(model_name)s'], # POST 请求需要用户对 add 模型具有权限。
        'PUT': ['%(app_label)s.change_%(model_name)s'], #PUT请求要求用户对 change 模型具有权限。
        'PATCH': ['%(app_label)s.change_%(model_name)s'],#POST请求要求用户对 change 模型具有权限。
        'DELETE': ['%(app_label)s.delete_%(model_name)s'], #DELETE 请求需要用户对 delete 模型具有权限
    }

    authenticated_users_only = True   
  
    def get_required_permissions(self, method, model_cls):

        kwargs = { # 获取请求app_label和model_name
            'app_label': model_cls._meta.app_label,
            'model_name': model_cls._meta.model_name
        }
        # 请求方式不在设定的类属性中则抛出异常
        if method not in self.perms_map:
            raise exceptions.MethodNotAllowed(method)
        # self.perms_map[method]获取指定请求方式的值,拼接kwargs
        return [perm % kwargs for perm in self.perms_map[method]]
        
        
        
        
    def _queryset(self, view):
        #断言 view是否有get_queryset方法或者获取queryset指定的值
        assert hasattr(view, 'get_queryset') \
            or getattr(view, 'queryset', None) is not None, (
            'Cannot apply {} on a view that does not set '
            '`.queryset` or have a `.get_queryset()` method.'
        ).format(self.__class__.__name__)
        # 如果view有getqueryset_set
        if hasattr(view, 'get_queryset'):
            queryset = view.get_queryset()
            # queryset赋值view的get_queryset()方法
            # 如果不为空返回 queryset
            assert queryset is not None, (
                '{}.get_queryset() returned None'.format(view.__class__.__name__)
            )
            return queryset
        return view.queryset

    def has_permission(self, request, view):
    # 如果 ignore_model_permissions有值,返回True
    if getattr(view, '_ignore_model_permissions', False):
        return True
    # 如果没有登录或者 没有认证属性返回False
    if not request.user or (
       not request.user.is_authenticated and self.authenticated_users_only):
        return False
    
    queryset = self._queryset(view)
    # 通过self.get_required_permissions设定对应权限
    perms = self.get_required_permissions(request.method, queryset.model)
    return request.user.has_perms(perms)        
        
      
    '''   
    也可以通过自定义模型权限,重写以上的默认行为
    自定义模型权限,请覆盖 DjangoModelPermissions 并设置 .perms_map 属性
    在重写了 get_queryset() 方法的视图中使用此权限,有可能这个视图上却没有queryset 属性。在这种情况下,建议使用保护性的查询集来标记视图,以便确定所需的权限
    queryset = User.objects.none()
    '''

DjangoModelPermissionsOrAnonReadOnly权限继承DjangoModelPermissions,与之类似 ,但允许未经身份验证的用户对API的只读权限

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):

    authenticated_users_only = False

DjangoObjectPermissions权限
继承DjangoModelPermissions,Django的模型的对象级别的权限,粒度最细
与 DjangoModelPermissions 一样,此权限只能应用于具有 .queryset 属性或 .get_queryset() 方法的视图,只有在用户通过身份验证并且具有相关的每个对象权限 和相关的模型权限 后,才会被授予此权限
POST 请求要求用户对模型实例具有 add 权限。
PUT 和 PATCH 请求要求用户对模型示例具有 change 权限。
DELETE 请求要求用户对模型示例具有 delete 权限
和 DjangoModelPermissions 一样,可以通过重写 DjangoObjectPermissions 并设置 .perms_map 属性来使用

class DjangoObjectPermissions(DjangoModelPermissions):
 
    perms_map = {
        'GET': [],
        'OPTIONS': [],
        'HEAD': [],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

    def get_required_object_permissions(self, method, model_cls):
        kwargs = { # 获取app_label和model_name
            'app_label': model_cls._meta.app_label,
            'model_name': model_cls._meta.model_name
        }
        # 如果请求方式不在类属性定义中,抛出异常

        if method not in self.perms_map:
            raise exceptions.MethodNotAllowed(method)
        # 返回对应权限属性
        return [perm % kwargs for perm in self.perms_map[method]]

    def has_object_permission(self, request, view, obj):
       # 获取queryset、model以及当前user
        queryset = self._queryset(view) 
        model_cls = queryset.model
        user = request.user
        # 获取返回的权限属性
        perms = self.get_required_object_permissions(request.method, model_cls)
        # 如果返回的权限属性有任何一个为False
        if not user.has_perms(perms, obj):
            # 如果不是安全请求中的method,抛出404异常
            if request.method in SAFE_METHODS:
              
                raise Http404
            # 获取只读的请求,如果没有只读请求抛出404异常
            read_perms = self.get_required_object_permissions('GET', model_cls)        
            if not user.has_perms(read_perms, obj):
                raise Http404

  
            return False

        return True

自定义权限

自定义权限规范

1️⃣继承BasePermission权限基类,根据实现需要编写对应的基类方法
2️⃣has_permission(self,request,view)
	1、view针对哪个视图进行权限检查
	2、返回值True,通过权限检查
	3、返回值False,没有通过权限检查
3️⃣has_object_permission(self,request,view,obj)
	1、针对单独的对象进行权限检查
	2、如果视图不是继承了通用视图,需要显示的调用check_object_permissions(request,obj)
	3、如果继承了通用视图但是重写了get_object(),也需要显示的调用check_object_permissions(request,obj)
	4、因为在通用视图中自动调用了check_object_permissions,而非通用视图没有自动调用
4️⃣可以根据是否安全请求返回不同的权限,可以直接判断请求是否在permissions.SAFE_METHODS

if request.method in permissions.SAFE_METHODS:
      ‘’‘安全请求’‘’
    else:
      ‘’‘写入操作’‘’

5️⃣message,权限提示的异常信息,可根据需要决定是否要自定义
# 简单示例

from rest_framework import permissions


# 视图级别权限
class BlackListPermission(permissions.BasePermission):
    # 自定义权限提示信息
    message = '木子七是黑名单'

    def has_permission(self, request, view):
        # 如果登录名是木子七 返回False,不是木子七 返回True
        return not request.user.username == '木子七'


# 对象级别权限

class IsOwnerOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        # 如果是安全请求直接返回True
        if request.method in permissions.SAFE_METHODS:
            return True

        # 判断操作对象的用户名 是否和登录的用户名一致
        return obj.user.username == request.user.username

22、drf缓存

DRF原有缓存

Django 缓存、配置

# 缓存
1.定义:缓存是一类可以更快读取数据的介质统称,也指其它可以加快数据读取的方式,一般用来存储临时数据,常用介质的是读取速度更快的内存
2.意义:视图渲染有一定的成本,数据库的频繁查询过高,对于低频变动的页面可以考虑缓存技术,减少实际渲染的耗时,用户拿到响应的时间成本更低
3.分类: 整体缓存、局部缓存、
4.缓存逻辑: 如果有缓存,则取缓存数据,没有缓存,则先走视图,并且将数据存到缓存
# 缓存配置的数据库表需要用python命令创建出来 1.CACHES配置 2.命令创建LOCATION对应的数据表
# python3 manage.py createcachetable    
CACHES = {
    # 默认配置数据库缓存
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',  # 数据库引擎
        'LOCATION': 'my_cache_table',  # 缓存数据库表名
        'TIMEOUT': 300,  # 缓存保存时间 单位秒,默认值300
        'OPTIONS': {
            'MAX_ENTRIES': 300,  # 缓存最大数据条数
            'CULL_FREQUENCY': 2,  # 缓存条数达到最大值时,删除1/x的缓存数据
        },
    # 本地缓存
    'local': {
    'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    'LOCATION': 'unique-snowflask'
},
# 将缓存数据存在到本地文件夹
    'file': {
     'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
     'LOCATION': 'var/tmp/django_cache'  #文件夹的路径
 
 }
    }
}

--------------
Django中使用缓存-整体缓存
#导包引入缓存装饰器 cache_page
from django.views.decorators.cache import cache_page

@cache_page(30)  #缓存有效期:单位s
def my_view(request):
    pass

#导包引入缓存装饰器 cache_page
from django.views.decorators.cache import cache_page
urlpatterns = [
    path('rote/',cache_page(60))(my_view)),
]

------------------
Django中使用缓存-局部缓存
`# 方式1:使用caches['CACHE配置key']导入具体对象
from django.core.cache.cache impost caches
my_cache  = caches[' setting_key ']    # 参数为setting里缓存配置的定义的要使用对应缓存配置的key

# 方式2:使用cache 引入默认配置
from  django.core.cache impost cache #相当于直接引入了CACHES配置项中的default项``

-------------------------
set存储缓存
cache.set(key,value,timeout) # 存储缓存 # key:缓存的key,字符串类型 # value:python对象 # timeout:缓存存储时间(s),默认为CACHES中TIMEOUT值 # 返回值:None
 
 
get获取缓存
cache.get(key) # 获取缓存 #key:缓存的key #返回值:为key的具体值,如果没有数据,则返回None
 
add存储缓存
cache.add(key,value) #存储缓存-只在key不存在时生效 #返回值:True[存储成功] / False[存储失败]
 
cache.get_or_set尝试获取/存储缓存
cache.get_or_set(key,value,timeout) #先get获取key,如果获取不到,则执行set存储value #如果未获取到数据,则执行set操作 #返回值:value
 
cache.set_many批量存储缓存
cache.set_many(dict,timeout) # 批量存储缓存 #dict:key和value的字典 #返回值:插入不成功的key的数组
 
cache.get_many批量获取缓存数据
cache.get_many(key_list) # 批量获取缓存数据 #key_list:包含key的数组 #返回值:取到的key和value的字典
 
cache.delete删除缓存
cache.delete(key) #删除key的缓存数据 #返回值:None
 
cache.delete_many批量删除
cache.delete_many(key_list) #批量删除 #包含key的数组 #返回值:None
------------------
浏览器缓存-强缓存
不会向服务器发送请求,直接从缓存中读取资源
1.响应头-Expires
定义:缓存过期时间,用来指定缓存到期的时间,是服务器端具体的时间点
样例:Expires:Thu,02 Apr 2030 05:14:18 GMT
 
2.响应头-Cache-Control
在http/1.1中,Cache-Control主要用于控制网页缓存,比如当Cache-Control:max-age=120 代表请求创建时间后的120s,缓存失效
说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control
 
3.强缓存中的数据一旦过期,还需要跟服务器进行通信,从而获取最新的数据
------------------
浏览器缓存-协商缓存
 
Q:如果强缓存的数据是一些静态文件、大图片等
A:考虑大图片这类笔记费带宽且不易变化的数据,强缓存时间到期后,浏览器会去跟服务器协商,当前缓存是否可用,如果可用,服务器不必返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新数据
----------------------
last-modified响应头和if-modified-since请求头
1.last-modified为文件的最近修改时间,浏览器第一次请求静态文件时,服务器如果返回last-modified的响应头,则代表该资源为需要协商的缓存
2.当缓存到期后,浏览器将获取的last-modeified值做为请求头if-modified-since的值,与服务器发请求协商,服务端返回304[响应头为空]
代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]
-----------------
ETag响应头和if-None-Match请求头
1.etag是服务器响应请求时,返回当前资源文件的唯一一个标识(由服务器生成),只要资源有变化,Etag就会重新生成
2.缓存到期后,浏览器将ETag响应头的值做为if-none-Match请求头的值,发给服务器发请求协商,服务器接到请求头后,
比对文件标识,不一致则认为资源不可用,返回200响应码[响应体为最新资源],可用则返回304响应

Django为基于类的视图提供了一个 method_decorator 装饰器,用于为类视图添加缓存类别的装饰器, cache_page 和 vary_on_cookie
cache_page可以指定缓存时间(单位秒)

class UserViewSet(viewsets.Viewset):
    # 为每个用户缓存2个小时的请求url
    # cache_page 装饰器只缓存返回200状态的 GET 和 HEAD 响应
    @method_decorator(cache_page(60*60*2))
    @method_decorator(vary_on_cookie)
    def geyUserList(self, request, format=None):
         ...

drf-extensions缓存
drf-extensions缓存流程
- 收到用户请求时,会根据对应的参数生成一个key
- 通过对应的key去差值是否有对应的value,有的话封装成对应的response返回
- 如果没有对应的value,则执行视图方法,并将结果存储,以便下次使用
使用drf-extensions缓存

#安装
pip3.9 install drf-extensions

drf-extensions提供的cache_response装饰器可以直接应用于get方法上,而无需使用method_decorator进行转换
适用于继承了rest_framework.views.APIView的类,且需要返回一个rest_framework.response.Response的实例

from rest_framework_extensions.cache.decorators import cache_response
class getInfoLIst(APIView):
    authentication_classes = [JwtQueryParamsAuthentication, ]
    @cache_response(60*60,cache='info_cache')
    def get(self, request, *args, **kwargs):
        ...

cache_response装饰器参数
- timeout:缓存有效时间,默认为None,永久缓存
- key_func:指定缓存键,默认情况下,每个来自@cache_response装饰器的缓存数据都由key存储,使用DefaultKeyConstructor计算
- cache:指定装饰器在缓存结果时使用特定的缓存,一般情况下使用默认的default 缓存
cache_errors:默认情况下,每个response都会被缓存,包括错误,如果第一次请求的时候中间出现错误,那么在缓存有效期内,每一次的请求都将会直接将之前缓存的错误信息返回给我们

# 不提供参数的话,可以在settings中配置公共缓存配置
REST_FRAMEWORK_EXTENSIONS = {
    # 缓存时间
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
    # 缓存存储
    'DEFAULT_USE_CACHE': 'default',
    #cache_error
    'DEFAULT_CACHE_ERRORS':False

CacheResponseMixin
drf-extensions扩展对于缓存提供了三个扩展类:
- ListCacheResponseMixin : 提供了缓存返回列表数据的视图,本质是为 mixins.ListModelMixin 的list()添加了cache_response装饰器
- RetrieveCacheResponseMixin:用于返回单一数据的是图,本质是为 mixins.RetrieveModelMixin 添加了cache_response装饰器
- CacheResponseMixin:提供了List和Retrieve两种缓存,与ListModelMixin和RetrieveModelMixin一起配合使用

from rest_framework_extensions.cache.mixins import CacheResponseMixin

# 需要将CacheResponseMixin加入到我们的试图类函数第一个继承类位置
class UserViewSet(CacheResponseMixin,viewsets.ModelViewSet):
    serializer_class = UserSerializer
    ....

drf-extensions使用redis缓存

#安装
django-redis pip3.9 install django-redis
#settings
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",  
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
       "PASSWORD""r_password"
            "CONNECTION_POOL_KWARGS": {"max_connections": 100} # 配置连接池
        }
    }
}

23、限流Throttling及源码解析

什么是限流?
限流类似于权限机制,它也决定是否接受当前请求,用于控制客户端在某段时间内允许向API发出请求的次数,也就是频率
假设有客户端(比如爬虫程序)短时间发起大量请求,超过了服务器能够处理的能力,将会影响其他用户的正常使用
为了保证服务的稳定性,并防止接口受到恶意用户的攻击,我们可以对接口进行限流
又或者可以对未经身份验证的请求设置访问频率,对经过身份验证的请求不限制访问频率
限流也不止单指限制访问次数的措施,例如付费数据服务的特点访问次数
限流的应用场景:
区分用户场景,比如匿名和已登录,不同权限的用户不同的限流策略
API的不同,根据不同API设置不同的策略
请求的爆发期和持续期不同的限流策略
可以同时支持使用多个限流策略

限流的机制
限流和权限一样,执行视图前会依次检查所有的限流类,全部通过会执行View,任何一个检查失败,会抛出Exceptions.Throttled异常
在settings中,通过 DEFAULT_THROTTLE_CLASSES 设置限流类,通过DEFAULT_THROTTLE_RATES设置限流

DRF提供的两个常用限流类
AnonRateThrottle:对于匿名用户的限流,使用anon设置频率
UserRateThrottle:对于登录用户的限流, 使用user设置频率

全局限流类配置

DEFAULT_THROTTLE_RATES设置限流频率格式 次数/时间单位
second: 按秒设置频率次数
minute:按分钟设置频率次数
hour:按小时设置频率次数
day: 按天设置频率次数
REST_FRAMEWORK = {
    # 全局限流类的配置
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',  # 对于匿名用户的限流
        'rest_framework.throttling.UserRateThrottle' #对于登录用户的限流
    ),
    # 限流频率的配置
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day', # 未认证用户一天只许访问100次
        'user': '1000/day' # 认证用户一天可以访问1000次
        }
    }

视图级别限流类配置

#导入限流模块
from rest_framework import throttling 


class getInfoList(ModelViewSet):
    # 通过throttle_classes 设置该视图的限流类
    # 视图指定会覆盖settings设置的全局限流
    throttle_classes = (throttling.UserRateThrottle,)

    def infoList(self):
        ...

识别请求的客户端
既然要对请求进行限流,那么肯定要失识 别是谁发来的请求,然后进行对应的措施,不然无法确定请求者身份,那么就无法得知是不是需要限制的请求,常见的方法有以下几种
1、drf利用http报头的 x-forwarded-for 或者wsgi中的remote-addr变量来唯一标识客户端的IP地址
2、如果 存在x-forwarded-for 属性,则使用x-forwarded-for ,否则使用remote-addr
3、可以使用request.user的属性来标识请求,比如使用request.user.id 来标记唯一请求
4、使用IP地址对客户端请求进行限流,需要考虑使用伪造代理IP请求的情况

throttling源码解析

throttling源码一共有五个类
BaseThrottle: 限流基类
SimpleRateThrottle:频率校验类
AnonRateThrottle:匿名用户限流
UserRateThrottle:认证用户限流
ScopedRateThrottle:api视图级别的限流
BaseThrottle限流基类
没有去具体实现某些功能,跟权限类基类似,只是提供了占位方法

class BaseThrottle:

    # allow_request源码并没有直接实现功能,只是写好了方法占位,待后续继承实现
    # 该方法主要是处理是否允许请求通过
    # 如果后续继承基类实现该方法,允许请求通过返回True,不允许请求通过返回False
    def allow_request(self, request, view):
      
        raise NotImplementedError('.allow_request() must be overridden')

    # 获取IP地址
    def get_ident(self, request):
        # 获取请求头中真实IP地址
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        # 获取代理IP地址
        remote_addr = request.META.get('REMOTE_ADDR')
        # 获取设置的允许的最大代理数,默认不设置为None
        num_proxies = api_settings.NUM_PROXIES
       # 如果num_proxies不是None,说明设置了该值
        if num_proxies is not None:
            # 如果设置为0,或者 xff没有值
            if num_proxies == 0 or xff is None:
                # 返回代理IP地址
                return remote_addr
            #使用代理IP的话会有多个地址,使用逗号分割成一个list
            addrs = xff.split(',')
            ''' 
            通过min函数,拿到允许的代理数和IP地址长度最小的值,使用-变成负数
            在addrs列表中通过该下标取对应值
            '''
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()
        # 如果没有设置允许的代理数 并且xff有值则直接返回,否则返回remote_addr
        return ''.join(xff.split()) if xff else remote_addr

    # 等待时间,告诉客户端被限流,等待多久可以访问
    # 后续继承实现,可选
    def wait(self):
    
        return None

SimpleRateThrottle
频率控制类,继承了BaseThrottle,添加和重写了一些方法,重点是添加了get_cache_key 方法,但必须自己实现该方法

class SimpleRateThrottle(BaseThrottle):
    # 限流需要用到缓存,使用drf默认的缓存
    # 如果其他继承类想修改缓存机制,cache = caches['缓存名'] 进行修改
    cache = default_cache
    # time.time方法,但是并没有()进行实例调用
    # 类似计时器功能,在这里留好,后续调用
    timer = time.time  
    # 缓存设置,字符串格式化方法后续传参使用
    cache_format = 'throttle_%(scope)s_%(ident)s'
    # scope默认没有设置,该值是DEFAULT_THROTTLE_RATES中对应限流类的key
    scope = None
    # 限流频率默认的配置值
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, 'rate', None):
            # 从下面get_rate()方法获取访问频率限制的参数
            self.rate = self.get_rate()
            
        # 通过self.parse_rate方法获取限制的频率及持续时间赋值给num_requests
        self.num_requests, self.duration = self.parse_rate(self.rate)
    # 获取当前请求的标识
    def get_cache_key(self, request, view):
        raise NotImplementedError('.get_cache_key() must be overridden')
        
    # 获取settings频率设置限流类对应的key
    def get_rate(self):
       # 如果没有scope,抛出异常
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            # 从 self.THROTTLE_RATES 中获取设置的scope
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
    # 获取限流频率设置及持续时间
    def parse_rate(self, rate):
         # 如果没有设置频率限制,直接返回None
        if rate is None:
            return (None, None)
        # 在settings设置频率我们使用 num/type 设置值
        # 字符串使用/分割 ,获取两个对应的值
        num, period = rate.split('/')
        num_requests = int(num)
        #settings中设置时间单位以天为单位可以是day也可以是d
        # period[0]获取第一个字符为key,以秒为单位换算,秒就1,分就是60,天就是86400 
        # 如果需要扩展月、年等时间,可以扩展源码
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # 返回频率和持续时间
        return (num_requests, duration)

    # 是否允许请求通过,运行返回True,否则返回False
    def allow_request(self, request, view):
        # 如果没有设置限流频率,直接返回True
        if self.rate is None:
            return True
        # 获取用户标识赋值给self.key
        self.key = self.get_cache_key(request, view)
        # 没有用户标识直接返回True
        if self.key is None:
            return True
        # 获取历史访问时间戳
        self.history = self.cache.get(self.key, [])
        # 获取当前时间戳
        self.now = self.timer()

        # while循环,如果历史访问时间戳有值,拿到历史时间戳[-1]的数据,如果小于等于当前时间戳减去持续时间,弹出最后一个时间戳
        # 当前时间-持续时间,就相当于需要限制的时间区间,如果历史时间戳小于等于该区间,才不会继续pop
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        # 如果历史访问时间戳的列表长度大于等于我们设置频率数量,说明到了频率上限
        if len(self.history) >= self.num_requests:
            # 返回self.throttle_failure 对应False
            return self.throttle_failure()
        # 返回self.throttle_success 对应True
        return self.throttle_success()
    # 频率未到达上限时返回该方法
    def throttle_success(self):
        # 在历史请求时间戳列表,将当前时间插入该列表  
        self.history.insert(0, self.now)
        # 更新缓存内容
        self.cache.set(self.key, self.history, self.duration)
        # 返回True
        return True
    # 频率到达上限时返回该方法
    def throttle_failure(self):
       
        return False
    # 返回还需要多长时间可以进行下一次请求,可选方法
    def wait(self):
     
        if self.history:
            # 如果历史请求时间戳有值,剩余时间等于持续时间减去(当前时间-第一次请求)
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            # 剩余的时间等于持续时间
            remaining_duration = self.duration
        # 允许请求的次数 等于 允许的次数-已请求的次数+1
        available_requests = self.num_requests - len(self.history) + 1
        # 如果允许请求的次数小于等于0,返回None
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)

AnonRateThrottle

'''
匿名限流类:继承了SimpleRateThrottle,重写了 get_cache_key 方法
AnonRateThrottle 只会限制未经身份验证的用户。传入的请求的IP地址用于生成一个唯一的密钥。
允许的请求频率由以下各项之一确定(按优先顺序):
1.类的 rate 属性,可以通过继承 AnonRateThrottle 并设置该属性来修改这个值,优先级高
2.settings配置文件中 DEFAULT_THROTTLE_RATES['anon'] 配置项的值。优先级低
3.anonratetrottle 适用于想限制来自未知用户的请求频率的情况
'''
class AnonRateThrottle(SimpleRateThrottle):
    # 设置频率控制的key为anon
    scope = 'anon'
    # 重写get_cache_key方法
    def get_cache_key(self, request, view):
        # 如果请求用户是经过认证的用户,不需要进行限流,直接返回None
        if request.user.is_authenticated:
            return None  
        # 如果用户是未经认证的用户,将该类的scope和 用户的IP地址传入SimpleRateThrottle的self.cache_format类属性
        return self.cache_format % {
            'scope': self.scope,
            'ident': self.get_ident(request)
        }

UserRateThrottle
认证用户限流类:继承了SimpleRateThrottle,仅仅是重写了 get_cache_key 方法
UserRateThrottle 用于限制已认证的用户在整个API中的请求频率。用户ID用于生成唯一的密钥。未经身份验证的请求将使用传入的请求的IP地址生成一个唯一的密钥

'''
允许的请求频率由以下各项之一确定(按优先顺序):
1.类的 rate 属性,可以通过继承 UserRateThrottle 并设置该属性来修改这个值,优先级高
2.settings配置文件中 DEFAULT_THROTTLE_RATES['user'] 配置项的值。优先级低
'''
# 设置频率控制的key位anon
    scope = 'user'
    # 重写get_cache_key方法
    def get_cache_key(self, request, view):
        if request.user.is_authenticated:
            # 如果请求用户是认证用户,设置用户的唯一标识赋值给ident
            ident = request.user.pk
        else:
            #如果请求用户是非认证用户,通过get_ident获取请求ip赋值给ident
            ident = self.get_ident(request)
        # 设置SimpleRateThrottle中self.cache_format的值
        return self.cache_format % {
            'scope': self.scope,
            'ident': ident
        }

ScopedRateThrottle
用户对于每个视图的访问频次:继承了SimpleRateThrottle,重写了 get_cache_key 和allow_request 方法
ScopedRateThrottle 类用于限制对APIs特定部分的访问,也就是视图级别的限流,不是全局性的
只有当正在访问的视图包含 throttle_scope 属性时,才会应用此限制。然后,通过将视图的“scope”属性值与唯一的用户ID或IP地址连接,生成唯一的密钥。
允许的请求频率由 scope 属性的值在 DEFAULT_THROTTLE_RATES 中的设置确定

class ScopedRateThrottle(SimpleRateThrottle):
    
    scope_attr = 'throttle_scope'

    def __init__(self):

        pass

    def allow_request(self, request, view):
        #  从view获取self.scope_attr赋值给scope,如果view中没有指定,设置为None
        self.scope = getattr(view, self.scope_attr, None)
        # 如果没有设置scope,直接返回True
        if not self.scope:
            return True
        # 获取settings频率设置限流类对应的key  
        self.rate = self.get_rate()
        # 获取频率限制、持续时长
        self.num_requests, self.duration = self.parse_rate(self.rate)
        # 调用父类的allow_request 返回对应的结果
        return super().allow_request(request, view)
    # 获取用户唯一标识
    def get_cache_key(self, request, view):
         # 如果是认证用户 ident=用户唯一标识
        if request.user.is_authenticated:
            ident = request.user.pk
        else:
            # 非认证用户返回请求的ip
            ident = self.get_ident(request)
        # 设置父类的类属性
        return self.cache_format % {
            'scope': self.scope,
            'ident': ident
        }

自定义限流类

上面源码的类,我们一般使用的是后三个,如果源码提供的限流类无法满足我们的需求,我们可以写自定义的限流类
自定义限流类的步骤:
1. 继承BaseThrottle类或者根据场景继承其他限流类
2.实现allow_request方法,如果请求被允许,那么返回True,否则返回False
3.wait方法,是否实现根据自己场景
4.获取唯一标识的方法可以使用源码自由的,也可以自定义
场景案例1
假设我们的请求需要同时进行多个认证用户的限流措施,比如每小时限制100次,同时每天限制1000次

# 每小时的限流类
class UserHourRateThrottle(UserRateThrottle): 
    scope = 'userHour' 
# 每天的限流类
class UserDayRateThrottle(UserRateThrottle):
    scope = 'userDay' 

----------
# settings中进行配置
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
    # 配置我们自定义的限流类或者再view中进行局部的配置
    'testApi.throttles.UserHourRateThrottle', 
    'testApi.throttles.UserDayRateThrottle'
    ),
    
'DEFAULT_THROTTLE_RATES': {
    'userHour': '100/hour', # 每小时最多100次
    'userDay': '1000/day' # 每天最多100次
}
}

场景案例2
随机限制

import random
class RandomRateThrottle(throttling.BaseThrottle):
    def allow_request(self, request, view):
        # 如果随机的数字 不等于1,返回True,否则返回False
        return random.randint(1, 10) != 1
# 之后在settings进行配置或者局部配置

24.drf过滤、搜索、排序

DRF的过滤类

drf过滤器在filters模块中,主要有四个类
1.BaseFilterBackend:过滤基类,留好占位方法待后续继承
2.SearchFilter:继承BaseFilterBackend
3.OrderingFilter:继承BaseFilterBackend
4.DjangoObjectPermissionsFilter:继承BaseFilterBackend,3.9版本之后废除

过滤的使用
DRF通用列表视图的默认行为是返回一个模型的全部queryset
比如说模型存储了1W条数据,默认会将1W条全部取出,如果不想一次性取出,只需要其中的一部分,需要对查询的结果进行过滤
如果继承了GenericAPIView及以上的视图类,有派生出的 get_queryset方法只需在视图中重写.get_queryset()方法

根据模型字段进行过滤
# 继承的ModelViewSet
class GetInfoLIst(ModelViewSet):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    # 重写过滤方法
    def get_queryset(self):
        # 获取当前用户username
        username = self.request.user.username
        # 过滤出与当前用户username一致的信息
        return UserInfo.objects.filter(username=username)
------------
根据url路径进行过滤
#路由 
path('user/<str:username>/', UserList.as_view()),
# views
# url示例: 127.0.0.1:8000/user/username/
class UserList(ListAPIView):
    serializer_class = UserSerializer

    def get_queryset(self):
        # 获取请求路由中的username
        username = self.kwargs['username']
        return UserInfo.objects.filter(username=username)
------------
根据url携带参数进行过滤
#url 示例:127.0.0.1:8000/user/?username=xxxx
# 继承的ModelViewSet
class GetInfoLIst(ModelViewSet):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    # 重写过滤方法
    def get_queryset(self):
        #从url参数中获取username,如果没有则是None
        # query_params.get是从url获取参数的方法
        username = self.request.query_params.get('username',None)
       if username is None:
           return UserInfo.object.all()
       else:
        return UserInfo.objects.filter(username=username)

DRF三种过滤的工具

DjangoFilterBackend

#安装
pip3.9 install django-filter

#注册
INSTALLED_APPS = [
    'django_filters',
]

过滤配置-全局
与权限配置一样,过滤也分全局过滤配置和局部过滤配置

REST_FRAMEWORK = {
    # 全局过滤配置
    'DEFAULT_FILTER_BACKENDS':
        # 值是元组,将django_filters配置上,进行全局性的过滤
        ('django_filters.rest_framework.DjangoFilterBackend',)

}

过滤配置-局部

from django_filters.rest_framework import DjangoFilterBackend #导包
class UserList(ListAPIView):
    # 视图级别过滤
    filter_backends = (DjangoFilterBackend,)# 指定后端

字段过滤

from django_filters.rest_framework import DjangoFilterBackend #导包
class UserList(ListAPIView):

    filter_backends = (DjangoFilterBackend,) # 指定后端
    filterset_fields = ('username','id') #要过滤的字段
    
'''
将为指定的字段自动创建一个 FilterSet 类
可以发送类似请求: http://xxxx/api/xxx?username=xxxx&id=xxxx
Django-filter模块的默认模式是完全匹配模式,需要自定义匹配模式参考https://django-filter.readthedocs.io/en/latest/index.html
'''

SearchFilter搜索过滤
SearchFilter 类是DRF自带的过滤器,支持基于简单的单个查询参数的搜索,并且基于Django admin的搜索功能

from rest_framework import filters


class UserList(ListAPIView):
    filter_backends = (DjangoFilterBackend,filters.SearchFilter)  # 指定后端
    # 视图中设置了 search_fields 属性时,才会应用 SearchFilter 类
    # search_fields只支持文本类型字段,例如 CharField 或 TextField
    search_fields = ('username',)
    
'''
在url中默认的搜索参数是search
url示例 - http://xxxx/api/users?search=木子
会过滤出username=木子的信息
'''

修改url中的搜索参数

# 上述默认以及search字段进行搜索,如果想要修改默认的search字段
REST_FRAMEWORK = {
    # 全局过滤配置
    'DEFAULT_FILTER_BACKENDS':
        # 值是元组,将django_filters配置上,进行全局性的过滤
        ('django_filters.rest_framework.DjangoFilterBackend',)
    # 修改search_fields链接搜索时的字段
    "SEARCH_PARAM":"find"
    
}
""" 
url示例 - http://xxxx/api/users?find=木子

"""

匹配模式
DRF通用列表视图的默认行为是返回一个模型的全部queryset
比如说模型存储了1W条数据,默认会将1W条全部取出,如果不想一次性取出,只需要其中的一部分,需要对查询的结果进行过滤
如果继承了GenericAPIView及以上的视图类,有派生出的 get_queryset方法只需在视图中重写.get_queryset()方法
示例1-根据模型字段进行过滤#

# 继承的ModelViewSet
class GetInfoLIst(ModelViewSet):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    # 重写过滤方法
    def get_queryset(self):
        # 获取当前用户username
        username = self.request.user.username
        # 过滤出与当前用户username一致的信息
        return UserInfo.objects.filter(username=username)
示例2-根据url路径进行过滤#
#路由 
path('user/<str:username>/', UserList.as_view()),
 
# views
# url示例: 127.0.0.1:8000/user/username/
class UserList(ListAPIView):
    serializer_class = UserSerializer

    def get_queryset(self):
        # 获取请求路由中的username
        username = self.kwargs['username']
        return UserInfo.objects.filter(username=username)
示例3-根据url携带参数进行过滤#
#url 示例:127.0.0.1:8000/user/?username=xxxx
# 继承的ModelViewSet
class GetInfoLIst(ModelViewSet):
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer
    # 重写过滤方法
    def get_queryset(self):
        #从url参数中获取username,如果没有则是None
        # query_params.get是从url获取参数的方法
        username = self.request.query_params.get('username',None)
       if username is None:
           return UserInfo.object.all()
       else:
        return UserInfo.objects.filter(username=username)

DRF三种过滤的工具

DjangoFilterBackend

#安装
pip3.9 install django-filter

#注册
INSTALLED_APPS = [
    'django_filters',
]

过滤配置-全局
与权限配置一样,过滤也分全局过滤配置和局部过滤配置

REST_FRAMEWORK = {
    # 全局过滤配置
    'DEFAULT_FILTER_BACKENDS':
        # 值是元组,将django_filters配置上,进行全局性的过滤
        ('django_filters.rest_framework.DjangoFilterBackend',)
        
    
}
----------------
过滤配置-局部
from django_filters.rest_framework import DjangoFilterBackend #导包
class UserList(ListAPIView):
    # 视图级别过滤
    filter_backends = (DjangoFilterBackend,)# 指定后端
字段过滤
from django_filters.rest_framework import DjangoFilterBackend #导包
class UserList(ListAPIView):

    filter_backends = (DjangoFilterBackend,) # 指定后端
    filterset_fields = ('username','id') #要过滤的字段
    
'''
将为指定的字段自动创建一个 FilterSet 类
可以发送类似请求: http://xxxx/api/xxx?username=xxxx&id=xxxx
Django-filter模块的默认模式是完全匹配模式,需要自定义匹配模式参考https://django-filter.readthedocs.io/en/latest/index.html
'''

SearchFilter搜索过滤
SearchFilter 类是DRF自带的过滤器,支持基于简单的单个查询参数的搜索,并且基于Django admin的搜索功能

from rest_framework import filters


class UserList(ListAPIView):
    filter_backends = (DjangoFilterBackend,filters.SearchFilter)  # 指定后端
    # 视图中设置了 search_fields 属性时,才会应用 SearchFilter 类
    # search_fields只支持文本类型字段,例如 CharField 或 TextField
    search_fields = ('username',)
    
'''
在url中默认的搜索参数是search
url示例 - http://xxxx/api/users?search=木子
会过滤出username=木子的信息
'''
修改url中的搜索参数
# 上述默认以及search字段进行搜索,如果想要修改默认的search字段
REST_FRAMEWORK = {
    # 全局过滤配置
    'DEFAULT_FILTER_BACKENDS':
        # 值是元组,将django_filters配置上,进行全局性的过滤
        ('django_filters.rest_framework.DjangoFilterBackend',)
    # 修改search_fields链接搜索时的字段
    "SEARCH_PARAM":"find"
    
}
""" 
url示例 - http://xxxx/api/users?find=木子

"""

匹配模式
默认情况下,搜索不区分大小写,并使用部分匹配的模式
可以同时有多个搜索参数,用空格和/或逗号分隔
如果使用多个搜索参数,则仅当所有提供的模式都匹配时才在列表中返回对象

可以通过在 search_fields 前面添加各种字符来限制搜索行为
1.^ 以指定内容开始
2.= 完全匹配
3.@ 全文搜索(目前只支持Django的MySQL后端)
4.$ 正则搜索
search_fields = ('=username') # 用户名必须完全一致,不能局部一致

OrderingFilter排序
OrderingFilter 类支持简单的查询参数,以控制查询集的元素顺序

class UserList(ListAPIView):
    filter_backends = (filters.OrderingFilter)  # 指定后端
    ordering_filter = ('username',) # 指定可以排序的字段
    ordering_fields = '__all__' # 所有字段,和指定二选一
    
'''
url中的查询参数默认ordering
http://xxxx/api/user?ordering=username
和search一样,如果要修改默认的查询参数,可以通过 ORDERING_PARAM指定
'''
#排序
http://xxxx/api/user?ordering=username # 默认排序
http://xxxx/api/user?ordering=-username #反向排序
http://xxxx/api/user?ordering=username,age  #多个字段进行排序

指定默认的排序方式

class UserList(ListAPIView):
    filter_backends = (filters.OrderingFilter)  # 指定后端
    ordering_filter = ('username',) # 指定可以排序的字段
    ordering = ('username') #默认初始用username排序

自定义过滤

1.自定义通用过滤后端,需要继承 BaseFilterBackend 类
2.重写filter_queryset(self, request, queryset, view) 方法
3.应返回一个新的过滤后的查询集
除了允许客户端执行搜索和过滤之外,自定义过滤器后端还可以限制当前请求或用户能够访问的对象
# 继承filters.BaseFilterBackend
class IsOwnerFilterBackend(filters.BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 过滤与当前用户一致
        return queryset.filter(owner=request.user)
        
'''
在视图中,通过重写 get_queryset() 方法,也能实现上面的操作
编写自定义的过滤器后端,可以在不同的视图或所有的API上,方便的重用这一功能
'''

25、DRF实现分页

分页Pagination
当我们在PC 或者 App 有大量数据需要展示时,可以对数据进行分页展示。这时就用到了分页功能,分页使得数据更好的展示给用户
比如我们有1W+数据要返回给前端,数据量大一次性返回可能会比较慢,前端一次性展示1W+数据也会比较慢,用分页返回数据效果较好

前端分页和后端分页的区别

前端分页
前端分页是一次性把数据全部拿出来进行分页,比如500条数据,一次展示50条,点击下一页再展示下50条,依次类推

优缺点:
前端分页一次性把数据加载出来,翻页过程中不会再对后端发起请求,对服务器压力较小
但是当数据量过大的时候,比较耗费性能,加载速度会比较慢

后端分页
后端分页是根据前端的需求返回对应的数据给客户端,例如一共有500条数据,一次展示50条,前端传参请求一次只返回50条数据
传下一页的page再请求下一页的数据再返回50条

优缺点:
后端分页比较灵活,每次都单独请求数据,对前端性能要求不高, 更易保证数据准确性
因为每次翻页都需要请求后端拿到对应的数据,多用户同时请求会增加后端压力

如何选择分页
数据量比较小的时候建议一次性返回数据在前端进行分页,数据量庞大的话建议服务器分页单次请求单次返回

DRF中的分页

DRF中的分页介绍
在drf框架中允许自定制分页样式,可以设置每页显示的数量,并且支持以下操作
1.将分页链接作为响应内容的一部分
2.响应头中包含分页链接,比如Content-Range或Link

drf中使用通用视图或者视图集的时候会自动执行分页,如果使用的常规的APIView等,需要自己调用分页API
可以通过将分页类设置None来选择是否关闭分页功能
自有分页VIew源码示例

ListAPIView举例,ListAPIView继承mixins.ListModelMixin和GenericAPIView,下述代码可以看出源码本身包含了分页功能,如果使用常规VIew则需要自己实现对应逻辑
# ListModelMixin源码 如果是常规view要实现分页,在视图中实现下述代码即可
class ListModelMixin:

   def list(self, request, *args, **kwargs):
       queryset = self.filter_queryset(self.get_queryset())
       # self.paginate_queryset对原有的queryset数据集进行分页
       page = self.paginate_queryset(queryset)
       # 如果页号不为空
       if page is not None:
            #返回对应页的数据
           serializer = self.get_serializer(page, many=True)
           return self.get_paginated_response(serializer.data)
       # 如果页号为空则返回全部数据
       serializer = self.get_serializer(queryset, many=True)
       return Response(serializer.data)

分页的settings设置
DEFAULT_PAGINATION_CLASS和PAGE_SIZE必须同时设置,如果不设置或者只设置一个,就相当于是None不分页

后端分页是根据前端的需求返回对应的数据给客户端,例如一共有500条数据,一次展示50条,前端传参请求一次只返回50条数据
传下一页的page再请求下一页的数据再返回50条
 
优缺点:
后端分页比较灵活,每次都单独请求数据,对前端性能要求不高, 更易保证数据准确性
因为每次翻页都需要请求后端拿到对应的数据,多用户同时请求会增加后端压力
 
如何选择分页#
数据量比较小的时候建议一次性返回数据在前端进行分页,数据量庞大的话建议服务器分页单次请求单次返回

 
DRF中的分页
DRF中的分页介绍#
在drf框架中允许自定制分页样式,可以设置每页显示的数量,并且支持以下操作
将分页链接作为响应内容的一部分
响应头中包含分页链接,比如Content-Range或Link
 
drf中使用通用视图或者视图集的时候会自动执行分页,如果使用的常规的APIView等,需要自己调用分页API
可以通过将分页类设置None来选择是否关闭分页功能
 
自有分页VIew源码示例#
我们拿ListAPIView举例,ListAPIView继承mixins.ListModelMixin和GenericAPIView,下述代码可以看出源码本身包含了分页功能,如果使用常规VIew则需要自己实现对应逻辑
# ListModelMixin源码 如果是常规view要实现分页,在视图中实现下述代码即可
class ListModelMixin:
 
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # self.paginate_queryset对原有的queryset数据集进行分页
        page = self.paginate_queryset(queryset)
        # 如果页号不为空
        if page is not None:
             #返回对应页的数据
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        # 如果页号为空则返回全部数据
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

分页的settings设置
DEFAULT_PAGINATION_CLASS和PAGE_SIZE必须同时设置,如果不设置或者只设置一个,就相当于是None不分页

REST_FRAMEWORK = {
    
    # drf的分页类位于rest_framework.pagination中
    "DEFAULT_PAGINATION_CLASS":"",# 全局默认的指定分页类,如果视图想单独指定,与权限一样在view中单独设置
    "PAGE_SIZE": #每一页显示多少数据
    }

DRF中的分页类使用详解

BasePagination
分页的基类,与权限、限流等原理一样
PageNumberPagination
继承BasePagination,这种分页接收前端请求的页码参数,参数是第几页则请求第几页的数据,例如前端请求page=10,则返回第10页数据

# settings.py
REST_FRAMEWORK = {
    # 指定分页类为PageNumberPagination
    "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE":3 #每一页显示3条数据
    }

PageNumberPagination自定义分页类

from rest_framework import pagination


# 继承分页类
class PublicPagination(pagination.PageNumberPagination):
    page_size = 2  # 每页显示的默认数据个数
    page_query_param = 'page'  # 页号,第几页的参数 ,比如定义为pages,那么请求分页的参数就应该是pages
    page_size_query_param = 'page_size'  # 自己指定每页显示多少个数
    max_page_size = 100  # 最大允许设置的每页显示的数量
    
    # last_page_strings用于指定表示请求最后一页的参数
    # page=last的时候会直接到最后一页
    # 如果不改参数的话,可以不用设置,不设置的话默认参数就是last
    last_page_strings = 'last' 
''' 
自定义分页类
通过page_size指定默认的每页数据量,
page_size_query_param指定每页自定义的数据量的参数,如果请求page_size=4,则每页显示4个,否则走默认的2
max_page_size是允许设置的每页最大的数据量
'''

# 导入自定义的分页类
from .pagination import PublicPagination

class CategoryViewSet(ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    # 指定自定义的分页类,与权限、限量等不同,每个视图只允许指定一个分页类
    # 对指定视图设置分页类,会覆盖settings中默认的全局配置
    pagination_class = PublicPagination

LimitOwsetPagination
这个分页类就类似于查找数据库中查找的语法
比如数据库中 Select * from table limit 100,300,从第101条开始,取300的数据
在分页中limit用于指定取多少条数据,offset用于指定从多少条开始,与sql一样,offse+1开始

#view
class CategoryViewSet(ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    # 给该指定LimitOffsetPagination分页类,也可以在settings指定全局
    pagination_class = LimitOffsetPagination

-------------LimitOwsetPagination自定义分页类
# 继承LimitOffsetPagination分页类
class PublicLimitOffsetPagination(pagination.LimitOffsetPagination):
    default_limit = 2  # 用于指定默认的limit数量
    limit_query_param = 'lm' # 指定请求时候对应的limit参数名,如果是lm那么传参就是lm=
    offset_query_param = 'of' ## 指定请求时候对应的offset参数名,如果是lm那么传参就是of=
    max_limit = 4 # 最大的limit可设置数量

CursorPagination 基于光标的分页

CursorPagination分页类说明#
`显示一个正向和反向的控件,不允许我们任意导航到任意位置
`要求结果集中有应该唯一的不变的排序方式
`可以确保客户端在翻页时不会看到同一对象两次,即使在分页的同时有数据插入
`对于超级大的数据量,使用前两个分页可能会效率低下,基于光标的分页具有固定的时间属性,不会因为数据变大而减慢
`基于光标的分页的排序方式默认是使用created排序,如果使用默认排序则模型必须有created时间戳字段,首先显示最近添加的数据
`可以覆盖pagination类的ordering属性,或者使用OrderingFilter过滤器类和CursorPagination来修改排序
`使用时要注意应该有一个唯一不变的值,例如默认的created
#settings设置 
REST_FRAMEWORK = {
    # 指定CursorPagination分页类
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination",
    "PAGE_SIZE": 2  # 每一页显示3条数据
    }

自定义CursorPagination

#继承CursorPagination分页类
class PublicCursorPagination(pagination.CursorPagination):
    ordering = '-created'  #通过什么进行排序,默认created
    page_size = 3 # 每页数据量
    cursor_query_param = 'cs' #请求的参数字段,默认cursor

26、DRF版本控制

版本控制
版本控制是前后端分离开发一个非常重要的内容,比如说我们重要服务修改、升级等发生版本变化v1、v2、v3等,但是版本发生了变化比如 v1升级到了v2版本,v1版本还有业务在继续使用,相当于同时多个版本接口共存使用
DRF版本控制配置
DRF中的版本控制默认是不开启的,request.version返回None,当开启API版本控制后,request.version属性中将包含与当前版本相对于的字符串

#settings
#DRF的版本类都位于versioning模块中
REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.AcceptHeaderVersioning" #指定版本控制的类,如果不指定则默认是None
    
    # 以下三项同样可以在自定义版本类中使用类属性置顶局部字段
    'DEFAULT_VERSION': 'v1',  # 默认版本号
    'ALLOWED_VERSIONS': ['v1','v2','v3'],  # 有效版本,可以设置字典
    'VERSION_PARAM': 'version',  # 版本的参数值
}
''' 
如果在单独为视图设置局部的版本类
使用version_class  =  来指定

'''

DRF中所有的版本控制类

BaseVersioning版本控制基类,用于后面继承,与权限、限流等基类效果类似
AcceptHeaderVersioning该类要求前端将版本放在Accept请求头中
我们可以通过不同的版本改变对请求的行为,比如如果是v1版怎么序列化,v2版本怎么序列化

def get_serializer_class(self):
    if self.request.version == 'v1':
        ...
    else:
        ...

URLPathVersioning该类要求将版本作为URL路径的一部分
url conf中必须包含一个使用version关键字参数的匹配模式,路由可以获取对应的值,也就是版本信息

urlpatterns = [ re_path(r'^(?P<version>(v1|v2))/Category/$',CategoryViewSet), ]

NamespaceVersioning该类与URLPathVersioning相同,区别在于是在django应用程序中配置的,使用url conf中的明面空间 而不是url conf 中的关键字参数request.version 属性是根据与传入请求的路径匹配的 namespace 确定的

urlpatterns = [
    re_path(r'^v1/category/',CategoryViewSet , namespace='v1')),
    re_path(r'^v2/category/', CategoryViewSet, namespace='v2'))
]

HostNameVersioning该类通过主机名控制版本方案,需要客户端将请求的版本指定为URL中主机名的一部分

# 使用该方案主机名需要与下面正则匹配
^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$
# 示例 http://v1/0.0.0.0:1717/category/

QueryParameterVersioning该类是在 URL 中包含版本信息作为查询参数的方式

#示例
http://0.0.0.0:1717/category/?version=v1  

自定义版本方案自定义版本方案需要继承BaseVersioning类并重写determine_version方法

class TestVersion(versioning.BaseVersioning):
   
    def determine_version(self, request, *args, **kwargs):
        version = request.META.get('HTTP_TESTVERSION') #通过testversion字段指定版本
        if version is None: #判断version如果为None就会设置成默认的版本
            version = self.default_version

        if not self.is_allowed_version(version):#对版本号进行限制allowed_version,在settings中定义ALLOWD_VERSION=[V1,V2]
            
            raise exceptions.NotFound(self.invalid_version_message)
        return version

27、DRF解决跨域问题

Django Rest Framework提供了corsheaders模块解决跨域问题
安装模块

pip install django-cors-headers

注册应用

# 注册 corsheaders 模块

INSTALLED_APPS = [
    ...
    'corsheaders', 
]

配置中间件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware', # 配置中间件,位置必须在CommonMiddleware之前
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

配置参数

# 如果是DEBUG模式
if DEBUG:
    # 允许所有跨域行为
    CORS_ORIGIN_ALLOW_ALL = True

else:
    # 允许跨域的白名单
    # 域名不要以http:// 或者https:// 开头
    CORS_ORIGIN_WHITE_LIST = (
        'www.testApi.com' #信任的站点
    )

28、Django日志配置

Django使用Python内建的logging模块打印日志,配置由四个部分组成

  • 记录器:Logger
  • 处理程序:Handler
  • 过滤器:Filter
  • 格式化:formatter

记录器-LoggerLogger为日志系统的入口,每个logger命名都是bucket,可以向bucket写入需要处理的消息
Python定义以及几种日志级别:

  • DEBUG:用于调试目的的日志
  • INFO:普通的系统消息
  • WARNING:表示出现一个较小的问题
  • ERROR:表示出现一个较大的问题
  • CRITICAL:表示出现一个致命的问题
    处理逻辑:
    当一条消息传递给Logger的时候,消息的日志级别将与logger的日志级别进行比较
    如果消息的日志级别大于等于logger的日志消息,该消息绩效往下处理,如果小于,该消息被忽略
    Logger一旦决定消息需要处理,它将传递该消息给一个Handler
    |logger日志级别
级别描述
CRITICAL50关键错误/消息
ERROR40错误
WARNING30警告消息
INFO20通知消息
DEBUG10调试
NOTSET0无级别

logger配置
logger对应的值是字典,其中每一个健都是logger的名字,每一个值又是个字典

  • level(可选) - logger的级别
  • propagate(可选) - logger的传播设置
  • filters(可选) - logger的fillter的标识符列表
  • handlers(可选) - logger的handler的标识符列表
#settings
LOGGING = { 
    #记录器
    'loggers': {
        'reboot': {
            'handlers': ['reboot'],
            'level': 'INFO',
        }
    }
}

''' 
level配置的日志级别为INFO
那么在程序中 如果使用logger.DEBUG,DEBUG级别小于INFO,则不会处理
如果是大于等于INFO,则会交给handlers处理日志
'''

处理程序-Handler
handler决定如何处理logger中的每条消息,它表示一个特定的日志行为,例如将消息写到屏幕、文件中或者网络socket
与logger一样,handler也有一个日志解蔽,如果消息的日志级别小于handler的级别,handler将忽略这条消息
logger可以有多个handler,而每个handler可以有不同的日志级别

#配置示例 settings
LOGGING = {
    # 记录器    
    'loggers': {
        'reboot': {
            'handlers': ['reboot'], # 处理器对应下方handlers中的reboot
            'level': 'INFO',
        }
    },
    # 处理器
    'handlers': {
        'reboot': {
            'level': 'INFO',
            'class': 'logging.StreamHandler', # 以流的形式写入
            'formatter': 'reboot', # 指定交给哪个格式化处理
        }
    }
}
'''
loggers中的reboot,定义级别为INFO,如果满足INFO级别,交给handlers处理,loggers中的handler配置的是reboot
在下发的handlers配置reboot的具体处理方式
'''

logging.StreamHandler	                         类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息
logging.FileHandler	                             将日志消息写入文件filename。
logging.handlers.DatagramHandler(host,port)     发送日志消息给位于制定host和port上的UDP服务器。使用UDP协议,将日志信息发送到网络
logging.handlers.HTTPHandler(host, url) 	     使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器。
logging.handlers.RotatingFileHandler(filename)   将日志消息写入文件filename。如果文件的大小超出maxBytes制定的值,那么它将被备份为filenamel。
logging.handlers.SocketHandler	                 使用TCP协议,将日志信息发送到网络。
logging.handlers.SysLogHandler	                 日志输出到syslog
logging.handlers.NTEventLogHandler	             远程输出日志到Windows NT/2000/XP的事件日志
logging.handlers.SMTPHandler	                 远程输出日志到邮件地址
logging.handlers.MemoryHandler	                 日志输出到内存中的指定buffer

过滤器-Filters
filter用于对从logger传递给handler的日志记录进行额外的控制
默认情况满足日志级别的任何消息都被处理,可以使用filter,对日志处理添加额外的条件,例如只允许处理特定源的ERROR消息
filters还可以用于修改将要处理的日志记录的优先级,例如日志满足特定条件,可以通过filter将日志记录从ERROR降到WARNING
filters可以在logger上或者handler上,多个filter可以串联起来实现多层的filter行为
格式化-Formatters
日志记录需要转换成文本或者其他格式 ,formatter表示文本的格式
formatter通常由包含日志记录属性的Python格式字符串组成

#配置示例

LOGGING = {
    'version': 1,  # 版本
    'disable_existing_loggers': False,# 默认为True,True:设置已存在的logger失效。False:让已存在的logger不失效,保证日志信息完整。一般情况下设置为False
    # 记录器
    'loggers': {
        'reboot': {
            'handlers': ['reboot'],
            'level': 'INFO',
        }
    },
    # 处理器
    'handlers': {
        'reboot': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'reboot',  # 指定
        }
    },
    # 格式化
    'formatters': {
        'reboot': {
            'format': '%(asctime)s - %(pathname)s:%(lineno)d[%(levelname)s] - %(message)s'
        }
    }
}

日志使用

import logging
# 生成logger对象 参数指定交给哪个loggers处理
# 不传参可走Django内置的配置
logger = logging.getLogger('reboot') 

logger.info('xxx')

Django内置logger

  • django-获取所有日志
  • django.request-处理与请求相关的日志,5xx响应报出ERROR日志,4xx报出warning日志
  • django.db.backends-处理与数据库之间交互的日志
  • django.security.* -处理与安全相关的日志
  • django.db.backends.schemea-处理数据库迁移时的日志
完整配置
# 日志配置
cur_path = os.path.dirname(os.path.realpath(__file__))  # log_path是存放日志的路径
log_path = os.path.join(os.path.dirname(cur_path), 'Log')

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        # 日志格式
        'standard': {
            'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] '
                      '[%(levelname)s]- %(message)s'},
        'simple': {  # 简单格式
            'format': '%(levelname)s %(message)s'
        },
    },
    # 过滤
    'filters': {
    },
    # 定义具体处理日志的方式
    'handlers': {
        # 默认记录所有日志
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'django.log'.format(time.strftime('%Y-%m'))),
            'maxBytes': 1024 * 1024 * 5,  # 文件大小
            'backupCount': 5,  # 备份数
            'formatter': 'standard',  # 输出格式
            'encoding': 'utf-8',  # 设置默认编码,否则打印出来汉字乱码
        },
        # 输出错误日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'error-{}.log'.format(time.strftime('%Y-%m'))),
            'maxBytes': 1024 * 1024 * 5,  # 文件大小
            'backupCount': 5,  # 备份数
            'formatter': 'standard',  # 输出格式
            'encoding': 'utf-8',  # 设置默认编码
        },
        # 控制台输出
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
        # 输出info日志
        'info': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'info-{}.log'.format(time.strftime('%Y-%m'))),
            'maxBytes': 1024 * 1024 * 5,
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',  # 设置默认编码
        },
    },
    # 配置用哪几种 handlers 来处理日志
    'loggers': {
        # 类型 为 django 处理所有类型的日志, 默认调用
        'django': {
            'handlers': ['default', 'console'],
            'level': 'INFO',
            'propagate': False
        },
        # log 调用时需要当作参数传入
        'log': {
            'handlers': ['error', 'info', 'console', 'default'],
            'level': 'INFO',
            'propagate': True
        },
    }
}
 
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值