ApiView的类属性

如下是ApiView所有的类属性,我们抽取一部分进行介绍:
可见这些类属性,都是使用drf配置文件的默认配置。

下列策略可以在全局设置 或者 在每一个视图类中设置。
允许依赖注入其他的设置文件, ApiView源码的settings类属性使测试更容易 (也就是不使用drf的api_settings)

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP
  • renderer_classes: 用于设置视图类的响应格式。默认情况会有两种响应格式,一种是响应json字符串,一种是对浏览器的响应。
  • parser_classes:用于定义视图类能够解析的请求格式。默认情况下使用3个解析类进行解析,可以解析如下媒体类型:multipart/form-dataapplication/x-www-form-urlencodedapplication/json
  • authentication_classes:认证类相关配置
  • throttle_classes:频率类相关配置
  • permission_classes:权限类相关配置

复习:

# APIView跟之前的View区别
    -传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
    -视图方法可以返回REST framework的Response对象-
    -任何APIException异常都会被捕获到,并且处理成合适的响应信息;
    -在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制

drf 配置文件之查找顺序

在apiView中使用了drf配置文件的默认配置。以下是配置的查找顺序。

方式三:查找顺序(一般就用内置的即可)
     1. 视图类  (局部配置)
     2. django settings  (全局配置) 
     3. drf api_settings (内置配置)
说明:
优先使用视图类中renderer_classes的配置,其次使用django项目配置文件settings中的配置,最后使用drf内置的api_settings的配置

在django的settings中应该按照如下格式写:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [    # 配置响应
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [  # 配置请求
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}
# 注意:所有配置都写在一个REST_FRAMEWORK里面!

drf之请求

APIView之请求相关配置

# 为什么需要进行请求相关配置?
可以定制某些CBV只能只能接收json格式,不能接收其他格式。也就是为了自定义该接口可接受编码格式。

# 默认情况下
前端上传json                  request.data里面是   ---> python字典
前端上传urlencode\formdata    request.data里面是   ---> QueryDict


# 方式一,在继承自APIView及其子类的的视图类中配置(局部配置)
# 总共有三个:from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
class BookView(APIView):
    parser_classes = [JSONParser,]
    
# 方式二:在配置文件中配置(影响所有,全局配置)
    -django有套默认配置,每个项目有个配置
    -drf有套默认配置,每个项目也有个配置---》就在django的配置文件中
    REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        # 'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        # 'rest_framework.parsers.MultiPartParser',
    ],
}
       
# 方式三:全局配了1个,某个视图类想要3个,怎么配?
    -只需要在视图类,配置3个即可
    -因为:先从视图类自身找,找不到,去项目的drf配置中找,再找不到,去drf默认的配置找
    
# 视图类方法中的request
    -data
    -__getattr__
    -query_params

drf之响应

APIView之响应相关配置

# 为什么要在CBV中设置响应相关配置?
因为对于drf的响应,如果使用浏览器和postman访问同一个接口,Response返回的格式是不一样的
	-drf做了个判断,如果是浏览器,好看一些,如果是postman只要json数据
    
    
# 方式一:在视图类中写(局部配置)
	-两个响应类---》找---》drf的配置文件中找--》两个类
    -from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    class BookView(APIView):
    	renderer_classes=[JSONRenderer,]

# 方式二:在项目配置文件中写(全局配置)
    REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}
    
# 方式三:使用顺序(一般就用内置的即可)
     1. renderer_classes
     2. django settings
     3. drf api_settings
说明:
优先使用视图类中renderer_classes的配置,其次使用django项目配置文件settings中的配置,最后使用drf内置的api_settings的配置

Response对象属性

# drf 的Response 源码分析
    -from rest_framework.response import Response
    -视图类的方法返回时,retrun Response ,走它的__init__, init中可以传什么参数
    -Responses最终继承httpresponse.
    
    
# Response init可以传的参数
    def __init__(self, 
                 data=None, 
                 status=None,
                 template_name=None, 
                 headers=None,
                 exception=False, 
                 content_type=None)
    
   -data:之前咱们写的ser.data  可以是字典或列表,字符串---》序列化后返回给前端---》前端在响应体中看到的就是这个 

   -status: http响应的状态码,默认是200,你可以改
        -drf在status包下,把所有http响应状态码都写了一遍,常量
        -from rest_framework.status import HTTP_200_OK
        -Response('dddd',status=status.HTTP_200_OK)
      
   -template_name:了解即可,修改响应模板的样子,BrowsableAPIRenderer定死的样子,后期公司可以自己定制

   -headers:响应头,http响应的响应头 示例:header={'xxx':'yyy'}
    
   -content_type :响应编码格式,一般不动
   
# 重点:data,status,headers

# 原生djagno,如何在响应头中加东西?

           '''
           四件套 render,redirect,HttpResponse,JsonResponse
           方法: 产生HttpResponse然后添加属性
           '''
         # 示例:
            obj = HttpResponse('dddd')
            obj['xxc'] = 'yyc'
            return obj
        
         响应头添加属性涉及知识 ---> 跨域 

drf Response提供很多的状态码:
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

在status内将所有状态码都写了一遍:
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

基于APIView + ModelSerializer写五个接口

视图类

from .models import Book
from .serializer import BookSerializer


class BookView(APIView):
    def get(self, request):
        books = Book.objects.all()
        ser = BookSerializer(instance=books, many=True)
        return Response(ser.data)

    def post(self, request):
        ser = BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            # 咱们现在只有ser序列化类的对象,但是咱们想要,新增的对象---》序列化成字典---》大前提,序列化类中的create方法一定要返回新增的对象
            return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})


class BookDetailView(APIView):
    def get(self, request, pk):
        books = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=books)
        return Response(ser.data)

    def put(self, request, pk):
        books = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=books, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

    def delete(self, request, pk):
        Book.objects.filter(pk=pk).delete()
        return Response({'code': 100, 'msg': '删除成功'})

序列化类

### ModelSerializer的使用
class BookSerializer(serializers.ModelSerializer):
    # 跟表有关联
    class Meta:
        model = Book
        fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
        extra_kwargs = {'name': {'max_length': 8},
                        'publish_detail': {'read_only': True},
                        'author_list': {'read_only': True},
                        'publish': {'write_only': True},
                        'authors': {'write_only': True},
                        }

路由

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookDetailView.as_view()),
]

基于GenericAPIview 写五个接口

如果需要再新写关于作者的五个接口 ----> 又需要写一个CBV
但是这两个CBV的区别仅仅在于使用的 和 不同,其他都是重复的代码。
这岂不是很麻烦?能不能通过继承的方式,优化代码?
于是就写了一个 GenericAPIview --继承于--> APIView ,以后可以基于这个类来写5个接口。

GenericAPIview必备设置

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

如果你想使用GenericAPIview,你需要从如下二种选择其一:

  1. 在视图类中设置如下属性 (常用)

    querysetserializer_class

  2. 重写GenericAPIview类的get_queryset()/get_serializer_class()方法

如果你重写了一个视图方法,那么重要的是 你应该调用get_queryset() 而不是直接的访问queryset属性。
因为queryset将只被设置一次,并且为了后续到来的所有请求,这个结果会被缓存。

总而言之,不要直接访问querysetserializer_class属性,而是使用GenericAPIview提供的各种方法获取。

查询所有

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

# 首先指定模型对象 和 序列化类
class BookView(GenericAPIView):
    queryset = Book.objects.all()
    # queryset = Book.objects 这样也是可行的
    serializer_class = BookSerializer

# 以下代码都是等效的
objs = Book.objects.all()
objs = self.get_queryset()

# 以下代码都是等效的
ser = self.get_serializer(instance=objs, many=True)
BookSerializer(instance=objs, many=True)

get_queryset()

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

get_queryset方法得到视图中的列表对象!
这个列表对象必须是一个可迭代的,也可以是一个queryset对象。

默认使用self.queryset来获取(视图中的列表对象):

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

应该总是使用这个方法来获取,而不是直接调用self.queryset

get_queryset源码做了些什么事?

  1. 如果没有在视图类中写queryset属性,然后就调用get_queryset,会抛出异常。
  2. 获取我们设置的queryset属性,如果是Queryset对象,则调用all()方法,最后将我们设置的queryset类属性返回出去。

get_serializer()

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

使用get_serializer()方法可以返回序列化器的实例,此序列化器,被应用于校验、反序列化前端输入和序列化后端输出。

get_serializer源码做了些什么事?

  1. 通过get_serializer_class方法获取了我们在视图类中指定的序列化类

  2. 添加了一个'context'参数传入我们的序列化类。

    相当于BookSerializer(instance=objs, many=True, context={一些数据...})

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

get_serializer_class()

get_serializer_class方法基本上什么事情都没有做,直接将序列化器返回,有需求可以重写get_serializer_class。
可以实现:不同的接口使用的序列化类不一样。序列化使用某一个序列化类,反序列化用另一个序列化类。

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

重写:

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

查询一个

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

这里会根据传入的pk参数查询出对应的模型对象,
正常情况下写查询一个的接口我们需要手动写orm( 比如Book.objects.filter(pk=pk)),根据主键将对象查出来。
这里因为使用了GenericAPIview,他会自动帮我们查。

get_object()

就是通过pk参数和get_object方法将模型对象查询出来的。

返回应用于详细视图的对象实例。默认使用 lookup_field 参数过滤基本的查询集。
该方法可以被重写以提供更复杂的行为,例如基于多个 URL 参数的对象查找。

lookup_field属性

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

如果你想使用pk之外的对象查找方式,可以设置lookup_field。如果有更复杂的查找需求,可以重写get_object()

以下给出一个示例:
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

修改查询条件为书籍的名字:

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

filter_queryset()

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

可见在get_object方法中,调用了get_queryset()获取了我们放在视图中的queryset,然后使用了filter_queryset()对我们从数据库中获取的queryset进行了过滤操作。

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

关于filter_queryset的解释是:
给他一个queryset,他会使用任何一个你正在使用的后端过滤器,进行过滤。

self.filter_backends:由于我们类中没有配置,所以会指向GenericAPIview类中的filter_backends。

在GenericAPIview类中默认使用的是drf配置文件中指定的默认过滤器。
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

然后drf配置文件中,默认是不过滤:
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

所有总而言之,默认就是不过滤,但是我们可以通过在自己的视图类中写filter_backends属性,来指定过滤器。

新增一个

    def post(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

修改一个

 def put(self, request, pk):
        obj = self.get_object()
        ser = self.get_serializer(instance=obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

删除一个

  def delete(self, request, pk):
        obj = self.get_object()
        obj.delete()
        return Response({'code': 100, 'msg': '删除成功'})

更多GenericAPIview 类属性

基本设置

  • queryset - 用于从视图返回对象的查询结果集。通常,你必须设置此属性或者重写 get_queryset() 方法。如果你重写了一个视图的方法,重要的是你应该调用 get_queryset() 方法而不是直接访问该属性,因为 queryset 将被计算一次,这些结果将为后续请求缓存起来。
  • serializer_class - 用于验证和反序列化输入以及用于序列化输出的Serializer类。 通常,你必须设置此属性或者重写get_serializer_class() 方法。
  • lookup_field - 用于执行各个model实例的对象查找的model字段。默认为 'pk'。 请注意,在使用超链接API时,如果需要使用自定义的值,你需要确保在API视图序列化类设置查找字段。
  • lookup_url_kwarg - 应用于对象查找的URL关键字参数。它的 URL conf 应该包括一个与这个值相对应的关键字参数。如果取消设置,默认情况下使用与 lookup_field相同的值。

配置文件相关

以下属性用于在与列表视图一起使用时控制分页。

  • pagination_class - 当分页列出结果时应使用的分页类。默认值与 DEFAULT_PAGINATION_CLASS 设置的值相同,即 'rest_framework.pagination.PageNumberPagination'

  • filter_backends - 用于过滤查询集的过滤器后端类的列表。默认值与DEFAULT_FILTER_BACKENDS 设置的值相同。

基于GenericAPIView + 5个视图扩展类写五个接口

虽然使用了GenericAPIview类写五个接口,但是写的代码还是太多了,并没有减少代码呀!
CBV类中的方法 getpostputdelete代码都是重复的,是不是可以再进行优化?

drf的作者自然想到了这一点,他提供了5个视图扩展类,帮我们写了这一部分代码!
先导入五个视图扩展类:

from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin

查询所有

class BookView(GenericAPIView, ListModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

现在就只需要使用self.list调用ListModeMixin类中写的代码:
【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

和我们之前写的差不多,先获取queryset然后将其传入序列化类。中间还进行了过滤,分页操作。

查询一个

class BookDetailView(GenericAPIView, RetrieveModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request, pk):  # 注意要传入这个pk
        return self.retrieve(request)

对应关系

这样很方便的就可以实现五个接口,我们只需要知道方法直接的对应关系就行了:

ListModelMixin      -->  list      -->  查询所有
RetrieveModelMixin  -->  retrieve  -->  查询一个
CreateModelMixin    -->  create    -->  新增一个
UpdateModelMixin    -->  update    -->  修改一个
DestroyModelMixin   -->  destroy   -->  删除一个

代码:

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)

class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request, pk):
        return self.retrieve(request)

    def put(self, request, pk):
        return self.update(request)

    def delete(self, request, pk):
        return self.destroy(request)

虽然已经简化了代码,但是其实还是有重复的部分比如这些请求方法,之后还会进行封装。

继承具体视图类写五个接口

什么叫具体视图类?Concrete View Classes
也就是drf作者已经帮你把CBV需要写的接口代码都写好了,打包成了一个个类,你只需要直接继承这些类,你的视图类就会有相应的接口,是不是很神奇 ~ ~

drf提供如下具体视图类:

【Django drf】视图类APIView之五层封装 ApiView的类属性 drf配置文件-LMLPHP

注意:没有DestroyUpdateAPIView

直接上代码:

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView


class BookView(ListCreateAPIView):  # 查询所有 新增一个 
    queryset = Book.objects
    serializer_class = BookSerializer


class BookDetailView(RetrieveUpdateDestroyAPIView):   # 查询一个 修改一个 删除一个
    queryset = Book.objects
    serializer_class = BookSerializer
    

使用ModelViewSet写五个接口

之前我们使用两个CBV写五个接口的原因是:
查询一个和查询所有都是使用get请求,为了解耦合,避免在类中的get方法中写太多代码,所以将其拆成两个CBV。

而使用ModelViewSet可以实现,一个视图类写5个接口。

from rest_framework.viewsets import ModelViewSet

class BookView(ModelViewSet):
    queryset = Book.objects
    serializer_class = BookSerializer

参考

02-12 13:59