Python django drf 框架 视图层总结

内容概要

  • 两个视图基类

  • 五个视图扩展类

  • 九个视图子类

  • action装饰器
    image

视图基类

1.APIView

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

2.APIView类属性

APIVIew
    	-类属性:
            renderer_classes # 响应格式
    		parser_classes #能够解析的请求格式
    		authentication_classes#认证类
    		throttle_classes#频率类
    		permission_classes#权限类

3.基于APIView+ModelSerializer+Resposne写5个接口

# urls 路由层
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('book/', views.BookView.as_view()),
    path('book/<int:pk>/', views.BookDetailView.as_view()),
]
# views 视图层
from rest_framework.views import APIView
from app01 import models
from rest_framework.response import Response
from app01 import serializer
class BookView(APIView):
    def get(self, request):
        book_query_set = models.Book.objects.all()
        serializer_obj = serializer.BookSerializer(instance=book_query_set,many=True)
        return Response(serializer_obj.data)

    def post(self,request):
        serializer_obj = serializer.BookSerializer(data=request.data)
        if serializer_obj.is_valid():
            serializer_obj.save()
            return Response({"msg":'新增成功'})
        return Response({"msg":serializer_obj.errors})

class BookDetailView(APIView):
    def get(self,request,pk):
        book = models.Book.objects.filter(pk=pk).first()
        serializer_obj = serializer.BookSerializer(instance=book)
        return Response(serializer_obj.data)

    def put(self,request,pk):
        book = models.Book.objects.filter(pk=pk).first()
        serializer_obj = serializer.BookSerializer(instance=book,data=request.data)
        if serializer_obj.is_valid():
            serializer_obj.save()
            return Response({"msg": '修改成功'})
        return Response({"msg": serializer_obj.errors})

    def delete(self,request,pk):
        models.Book.objects.filter(pk=pk).delete()
        return Response()
# serializer 序列化类
from rest_framework import serializers
from app01 import models


class BookSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.Book
        fields = ['name', 'price','publish', 'authors', 'publish_detail', 'author_list']  # 序列化反序列化字段
        extra_kwargs = {
            "publish": {"write_only": True},
            "authors": {"write_only": True},
            # 'publish_detail': {'read_only': True},  # dict
            # 'author_list': {'read_only': True},  # list
        }

    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

4.GenericAPIView

问题:
	如果需要再新写关于作者的五个接口 ----> 又需要写一个CBV
但是这两个CBV的区别仅仅在于使用的 表模型 和 序列化类 不同,其他都是重复的代码。
	这岂不是很麻烦?能不能通过继承的方式,优化代码?
    
如果你想使用GenericAPIview,你需要从如下二种选择其一:

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

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

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

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

5.GenericAPIView类属性/方法

我们通过案例来解释说明:
    
# 首先指定模型对象 和 序列化类
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()

img

get_queryset # 源码解析

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

img

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

get_serializer # 源码解析

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

	2.添加了一个'context'参数传入我们的序列化类。
    相当于BookSerializer(instance=objs, many=True, context={一些数据...})
get_serializer_class()

img

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

image-20230207155004892

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

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

image-20230207155101660

如果你想使用pk之外的对象查找方式,可以设置lookup_field。如果有更复杂的查找需求,可以重写get_object()。
# 更多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 设置的值相同。

6.基于GenericAPIView+5个视图扩展类

视图类


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

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

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    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.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.destroy(request, *args, **kwargs)

序列化类

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()),
]

五个视图扩展类

五个视图扩展类并不是视图类
	我们通过导入
from rest_framework.mixins import ListModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin,CreateModelMixin
	来使用五个视图类,
因为我们在编写项目的时候可能一个CBV里面不需要全部的接口,这样的话我们就可以通过视图扩展类+GenericAPIView来实现

九个视图子类

# 两个视图基类 
1.APIView       2.GenericAPIView
APIView:       renderer_classes响应格式类 parser_classes请求解析类    跟数据库解耦合
GenericAPIView:queryset数据集 serializer_class序列化类                跟数据库耦合

# 5个视图扩展类 (提供方法)
ListModelMixin      -->  list      -->  查询所有
RetrieveModelMixin  -->  retrieve  -->  查询一个
CreateModelMixin    -->  create    -->  新增一个
UpdateModelMixin    -->  update    -->  修改一个
DestroyModelMixin   -->  destroy   -->  删除一个

# 9个视图子类 
继承关系公式: 视图子类 = n * 视图扩展类 + GenericAPIView 

# 示例:
ListAPIView     =  ListModelMixin     + GenericAPIView 
RetrieveAPIView =  RetrieveModelMixin + GenericAPIView 
CreateAPIView   =  CreateModelMixin   + GenericAPIView 
...
RetrieveDestroyAPIView = RetrieveModelMixin + DestroyModelMixin + GenericAPIView 
RetrieveUpdateDestroyAPIView = RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + GenericAPIView

'''
总结:9个视图子类都继承GenericAPIView
'''

使用视图子类编写五个接口

# 
## 路由
urlpatterns = [
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookView.as_view()),
]

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


class BookDetailView(RetrieveUpdateDestroyAPIView): # 新增一个,修改一个,删除一个
    queryset = Book.objects.all()
    serializer_class = BookSerializer

视图集

1.继承ModelViewSet类写五个接口
# 路由
urlpatterns = [
    path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
    path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

# 视图类
class BookView(ModelViewSet):  # 查询所有,新增一个
    queryset = Book.objects.all()
    serializer_class = BookSerializer

我们可以看一下modelviewset内部的继承关系

img

从注释也可以看出来他继承了 5个视图扩展类,也就是说ModelViewSet内部具备所有的"动作",也就是例如:
create()、list()、update()、retrieve()、destroy()这些方法
但是我们请求来了,还是会调用视图类中的get()、post()、put()、delete()这些方法.

只要调用方法就会到继承的五个视图扩展类中找寻相关方法!
	我们在urls中编写的 as_view()
里面还编写了请求方式和对应的方法
	这是因为ModelViewSet继承的最后一个类GenericViewSet,他的父类ViewSetMixin重写了as_view。

	可以得知,一旦继承ModelViewSet,路由层的写法就变了!
现在需要这样写:
urlpatterns = [
    path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
    path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

# 这样写的意思是:

对于books/这个路由:
get请求 --执行--> list方法
post请求 --执行--> create方法

对于books/<int:pk>/这个路由:
get请求 --执行--> retrieve方法
put请求 --执行--> updata方法
delete请求 --执行--> destroy方法
2.继承 ReadOnlyModelView编写2个只读接口
# 路由
urlpatterns = [
    path('books/', views.BookView.as_view({'get': 'list'})),
    path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve'})),
]

# 视图类
class BookView(ReadOnlyModelViewSet):  # 查询所有,新增一个
    queryset = Book.objects.all()
    serializer_class = BookSerializer
查看 readonlymodelview内部继承关系

img

这个类中只有list方法和retrieve方法。他同样继承了魔法类ViewSetMixin。
所以继承这个类就只能写两个只读接口:查询所有、查询一个
ViewSetMixin源码分析
查找as_view方法
路由写法为什么变了?
导致路由写法变了的原因是: ViewSetMixin
当请求来了之后,会执行ViewSetMixin类中的as_view方法的返回值

请求来了

路由匹配成功get请求,

匹配成功books,

会执行 views.BookView.as_view({'get': 'list', 'post': 'create'})()

读as_view【这个as_view是ViewSetMixin的as_view】

ViewSetMixin的as_view()方法

@classonlymethod
def as_view(cls, actions=None, **initkwargs):
    # 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
    if not actions:
        raise TypeError("The `actions` argument must be provided when "
                        "calling `.as_view()` on a ViewSet. For example "
                        "`.as_view({'get': 'list'})`")
	# 。。。。其他代码不用看
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        if 'get' in actions and 'head' not in actions:
            actions['head'] = actions['get']
        self.action_map = actions
        for method, action in actions.items():
            handler = getattr(self, action)
            setattr(self, method, handler)

        return self.dispatch(request, *args, **kwargs)
    # 去除了csrf校验
    return csrf_exempt(view)

如果不给actions传参数,直接抛出异常。
也就是不给as_view()传字典,就会抛出异常。
as_view执行完后会返回内层函数view:(这里执行的view是去除了csrf校验的)

# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执
行ViewSetMixin----》as_view----》内的view()---》代码贴过来
    def view(request, *args, **kwargs):
            #actions 是传入的字典--->{'get': 'list', 'post': 'create'}
            self.action_map = actions
            # 第一次循环:method:get,action:list
            # 第一次循环:method:post,action:create
            for method, action in actions.items():
                # 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
                # handler就是视图类中的list方法
                handler = getattr(self, action)
                # 反射修改:把method:get请求方法,handler:list
                # 视图类的对象的get方法,变成了list
                setattr(self, method, handler)

            return self.dispatch(request, *args, **kwargs) #dispatch是APIView的
        
# 关于这里self.dipatch的说明
self.dipatch是APIView的dispatch
'''
self.dipatch --进行--> 封装新request, 执行三大认证 --调用--> django view的dispatch
'''
# 关于反射的总结
	反射得到的是我们继承的List create方法
	反射修改对象的属性 比如将get方法修改为存放list方法
	最后的dispatch作用是获取你写的CBV类中的get方法(此时get方法 --> list方法)。
	魔法类可以修改对象中的属性所指向的方法。
        
 # 关于整体的总结:
	-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw)
    -2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
    	-只要传入actions,以后访问get就是访问list,访问post,就是访问create
    -3 其他执行跟之前一样 
    -4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】