drf-视图集路由系统action装饰器

Posted 404 NotFound

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了drf-视图集路由系统action装饰器相关的知识,希望对你有一定的参考价值。

1.9个视图扩展类

1.两个视图基类:APIView、GenricAPIView
2.5个视图扩展类:CreateModelMixin,UpdateModelMixin,RetrieveModelMixin,ListModelMixin,DestroyModelMixin
3.9个视图子类:CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
(没有RetrieveUpdateAPIView)
    
4.利用9个视图自类编写5个接口(继承视图子类则不需要继承GenericAPIView):
views.py:
from rest_framework.generics import CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
class BookView(ListCreateAPIView):
#相当于继承了两个类:class BoookView(ListAPIView,CreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
# 继承9个子类不需要单独写某个方法,因为在父类ListCreateAPIView中已经写好了post和get方法。

class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
# 在其父类RetrieveUpdateDestroyAPIView中已经写好了put,get以及delete方法所以不需要自己写。并且在该BookDetailView方法中不需要上传pk,依然只需要拿到queryset就可以。

models.py:(和之前未变)
from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.ForeignKey(to=\'Publish\',on_delete=models.CASCADE)
    authors = models.ManyToManyField(to=\'Author\')

    @property
    def publish_detail(self):
        return \'name\':self.publish.name,\'addr\':self.publish.addr  # 

    @property
    def author_list(self):
        l = []
        for author_obj in self.authors.all():
            l.append(\'name\':author_obj.name,\'phone\':author_obj.phone)
        return l  # [,,]


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)

class Author(models.Model):
    name = models.CharField(max_length=32)
    phone = models.CharField(max_length=11)
    
serializer.py:
from rest_framework import serializers
from .models import Book
from rest_framework.exceptions import ValidationError

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = [\'name\',\'price\',\'publish\',\'authors\',\'publish_detail\',\'author_list\']
        extra_kwargs = 
            \'name\':\'max_length\':8,
            \'price\':\'max_length\':8,
            \'publish\':\'write_only\':True,
            \'authors\':\'write_only\':True,
            \'publish_detail\':\'read_only\':True,
            \'author_list\':\'read_only\':True
        

    def validate_name(self,name):
        book_obj = Book.objects.filter(name=name)
        if book_obj:
            raise ValidationError(f\'书籍name已存在\')
        return name
    
"""
新需求:如果不需要全部接口功能,只需要查询所有以及删除一个,如何实现?
"""
我们只需要修改继承的类即可,查询所有是ListAPIView,删除一个是DestroyAPIView。serializer.py和models.py中代码未修改。
class BookView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetailView(DestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

2.视图集

2.1 通过ModelViewSet编写5个接口

使用ModelViewSet模块写接口,可以只写一个视图类,不用再写类BookDetailView,但是在路由层需要将两种类型分开,但是关联的视图类是同一个视图类,因此视图类中可以简写。
urls.py:
urlpatterns = [
    path(\'admin/\', admin.site.urls),
# 不带参数的情况:get请求就会返回list(查所有),post请求就会返回create(新增一个) 
    path(\'books/\',views.BookView.as_view(\'get\':\'list\',\'post\':\'create\')),
    path(\'books/<int:pk>/\',views.BookView.as_view(\'get\':\'retrieve\',\'put\':\'update\',\'delete\':\'destroy\'))
]

views.py:
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
通过查看源码得知,ModelViewSet继承了类:CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,.ListModelMixin。所以也可以找得到这些类中的方法list、pos、get、update、delete。

"""
报错分析:该错误提示create()` must be implemented,原因是在进行创建图书操作时在序列化类中没有写create()方法,而继承的Serializer中也没有该方法所以会报错。解决方法是要么在序列化类中重写create()方法要么继承ModelSerializer,该类中有方法create()和update()。
"""

2.2 通过ReadOnlyModelViewSet编写2个只读接口

只读接口包括:读所有和只读一个。如果我们只想编写只读接口,我们可以用模块ReadOnlyModelViewSet。继承该模块之后只能编写2个查询接口,编写其他接口会直接报错。
urls.py:
urlpatterns = [
    path(\'admin/\', admin.site.urls),
    path(\'books/\',views.BookView.as_view(\'get\':\'list\')),
    path(\'books/<int:pk>/\',views.BookView.as_view(\'get\':\'retrieve\'))
]

views.py:
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookView(ReadOnlyModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

2.3 ViewSetMixin源码分析

1.请求来时,路由匹配成功,会执行views.BookView.as_view(\'get\':\'list\',\'post\':\'create\')(),我们需要找到as_view()方法。视图类BookView中没有,继续往上找:BookView>>>ModelViewSet>>>GenericViewSet>>>ViewSetMixin。
发现在ViewSetMixin中有as_view()方法。

2.as_view()源码如下:
	@classonlymethod
    def as_view(cls, actions=None, **initkwargs):
       ...
		# actions就是我们上传的字典\'get\':\'list\',如果不上传字典就会报错
        # actions must not be empty
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view(\'get\': \'list\')`")

		...
			# 去除了view函数的csrf校验,本质就是执行views.view。
        return csrf_exempt(view)
	
3.查找view:
view查找顺序:BookView>>>ModelViewSet>>>GenericViewSet>>>ViewSetMixin>>>as_view()>>>view。
    
源码:
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
				# cls是调用函数as_view()的类BookView(视图类)
				...
            for method, action in actions.items():
            # actions:\'get\':\'list\',\'post\':\'create\',一共经历两次循环,action是list、create
                handler = getattr(self, action)
				# self是视图类,从视图类开始找list:BookView>>>ModelViewSet>>>mixins.ListModelMixin,最终在类mixins.ListModelMixin中找到了list方法,handler此时就是list方法
                setattr(self, method, handler)
				# 将视图类中的get方法修改成了list方法,此时发送get请求就能执行list()
            self.request = request
            self.args = args
            self.kwargs = kwargs

            # And continue as usual
            return self.dispatch(request, *args, **kwargs)
        	# dispatch方法时APIView中的dispatch方法。
结论:
	1.只要继承了ViewSetMixin的视图类,路由写法就变了,需要上传actions参数(上传一个字典)。
	2.以\'get\': \'list\'为例,以后访问get就是访问list。以后视图类中的方法名,可以任意命名,只需要在路由中做好映射即可。

2.4 from rest_framework.viewsets包下的类

ModelViewSet:5个视图扩展类+GenericViewSet
ReadOnlyModelViewSet:2个视图扩展类(mixins.RetrieveModelMixin, mixins.ListModelMixin)+GenericViewSet
ViewSetMixin:魔法,重新定义了as_view,以后继承它,路由写法就变成了映射的写法(继承ViewSetMixin类一定要将它放在第一个)
ViewSet:ViewSetMixin+APIView
GenericViewSet:ViewSetMixin+GenericAPIView
    
以后想继承APIView,但是想改变路由写法,就继承ViewSet。想继承GenericViewSet,但是想改变路由写法,就继承GenericViewSet。

3.视图类大总结

1.两个视图基类:APIView、GenricAPIView
    
2.5个视图扩展类:CreateModelMixin,UpdateModelMixin,RetrieveModelMixin,ListModelMixin,DestroyModelMixin
    
3.9个视图子类:CreateAPIView,DestroyAPIView,ListAPIView,RetrieveAPIView,UpdateAPIView,ListCreateAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView,RetrieveUpdateDestroyAPIView
(没有RetrieveUpdateAPIView)

4.视图类:
ModelViewSet:路由写法变了,只需要写两行,5个接口都有了
ReadOnlyModelViewSet:路由写法变了,只需要写两行,2个只读接口都有了
ViewSetMixin:不是视图类,是魔法,路由写法变了,变成映射了
ViewSet:ViewSetMixin+APIView
GenericViewSet:ViewSetMixin+GenericAPIView

4.路由系统

4.1自动生成路由

1.用SimpleRouter来写(较为常见):
# 1.导入路由类
from rest_framework.routers import SimpleRouter
# 2.实例化得到对象
router = SimpleRouter()
# 3.注册:
router.register(\'books\',views.BookView,\'books\')
# 4.在urlpatterns中注册(两种方式选一种即可):
方式一:
	urlpatterns += router.urls
方式二:
	urlpatterns = [
    path(\'admin/\', admin.site.urls),
    path(\'api/v1/\',include(router.urls))
]
"""
用方式二可以在路由中增加api表示和版本信息,此后访问的路有需要变成
http://127.0.0.1:8000/api/v1/books/
或:http://127.0.0.1:8000/api/v1/books/5/
"""

2.用DefaultRouter来写:
from rest_framework.route rs import DefaultRouter
router = DefaultRouter()
router.register(\'books\',views.BookView,\'books\')
urlpatterns += router.urls
DefaultRouter和SimpleRouter的区别是:DefaultRouter会多附带一个默认的API根视图、返回一个包含所有列表视图超链接响应数据。
可以发现DefaultRouter的路由要比SimpleRouter多几个:

自动生成路由本质就是做映射,能够自动生成的前提是:视图类中要有5个方法:
get>>>list
get>>>retrieve
put>>>update
post>>>create
delele>>>destroy
ModelViewSet,ReadOnlyModelViewSet可以自动生成

9个视图子类+配合ViewSetMixin 才可以自动生成
5个视图扩展类+GenericAPIView+ViewSetMixin(ViewSetMixin+GenericAPIView=GenericViewSet)

5.action装饰器(配合自动生成路由使用)

"""
当我们自动生成路由时,我们想在路由中通过get方法定义到自定义名字的一个方法名字。这时需要用到action装饰器。action可以控制提交方式映射的对象。
"""
使用步骤:
views.py:
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
class SendView(ViewSet):
    @action(methods=[\'GET\'],detail=False,url_path=\'send_msg\',url_name=\'send_msg\')
"""
methods:指定请求方式,结果是个列表,列表内要传提交方式的大写,可以传一个,也可以传多个。
detail:上传的结果是True或者False,False:不带id的路径:
http://127.0.0.1:8000/api/v1/send/send_msg/?phone=111
True:带id的路径:
http://127.0.0.1:8000/api/v1/send/1/send_msg/?phone=111(并且在send_msg中还要加一个参数pk,此时pk是1)
url_path:拼接的路径,可以改,当前路径为:http://127.0.0.1:8000/api/v1/send/send_msg/?phone=111
url_name:别名,反向解析用,一般和函数名保持一致
"""
    def send_msg(self,request):
        phone = request.query_params.get(\'phone\')
        print(f\'成功向phone发送信息\')
        # print(self.action)  # send_msg  self.action拿到的是方法名
        return Response(\'code\':100,\'msg\':\'发送成功\')
    
urls.py:
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register(\'send\',views.SendView,\'send\')
urlpatterns = [
    path(\'admin/\', admin.site.urls),
    path(\'api/v1/\',include(router.urls))
]

补充思路:
	不同的action使用不同的序列化类:

class SendView(ViewSet):
    queryset = None
    serializer_class = \'序列化类\'
    
    def get_serializer(self,*args,**kwargs):
        if self.action == \'send_msg\':
            return \'某个序列化类\'
        else:
            return \'某个序列化类\'
        
    @action(methods=[\'GET\'],detail=False,url_path=\'send_www\',url_name=\'send_msg\')
    def send_msg(self,request):
       	pass
    
    @action(methods=[\'POST\'],detail=False,url_path=\'phone_msg\',url_name=\'phone_msg\')
    def phone_msg(self,request):
       	pass

6.登陆接口

编辑一个登陆接口,如果用户未登陆过第一次登陆就在UserToken中插一条随机字符串,登陆过再次登陆时就修改此条字符串:
urls.py:
router.register(\'user\',views.UserView,\'user\')


urlpatterns = [
    path(\'admin/\', admin.site.urls),
    path(\'api/v1/\',include(router.urls))
]

models.py:
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharFiel2023-02-13 10:00:02 星期一d(max_length=32)

class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=\'User\',on_delete=models.CASCADE)
    
views.py:
from .models import User,UserToken
import uuid

class UserView(ViewSet):
    @action(methods=[\'POST\'],detail=False)
    def login(self,request):
        username = request.data.get(\'username\')
        password = request.data.get(\'password\')
        user_obj = User.objects.filter(username=username,password=password).first()
        if user_obj:
            token = str(uuid.uuid4())
            UserToken.objects.update_or_create(user=user_obj,defaults=\'token\':token)
            return Response(\'code\':100,\'msg\':\'登陆成功\',\'token\':token)
        else:
            return Response(\'code\':100,\'msg\':\'用户名或密码错误\')

05 drf路由组件

一.路由Routers

对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。

REST framework提供了两个router

  • SimpleRouter
  • DefaultRouter

1.1 使用方法

1) 创建router对象,并注册视图集,例如

from rest_framework import routers

router = routers.SimpleRouter() #实例化
router.register(r‘router_stu‘, StudentModelViewSet, base_name=‘student‘)

SimpleRouter会产生两个路由:

<URLPattern ‘^books/$‘ [name=‘book-list‘]>, 
<URLPattern ‘^books/(?P<pk>[^/.]+)/$‘[name=‘book-detail‘]>

DefaultRouter会产生六个路由:

^books/$ [name=‘book-list‘]
^books/(?P<pk>[^/.]+)/$ [name=‘book-detail‘]  这两条跟simple一样

^$ [name=‘api-root‘]  根,根路径会显示出所有可以访问的地址
^.(?P<format>[a-z0-9]+)/?$ [name=‘api-root‘]


^books.(?P<format>[a-z0-9]+)/?$ [name=‘book-list‘]  http://127.0.0.1:8000/books.json
^books/(?P<pk>[^/.]+).(?P<format>[a-z0-9]+)/?$ [name=‘book-detail‘]  http://127.0.0.1:8000/books/1.json

2)把生成的路由列表追加到urlpatterns

urlpatterns += router.urls

1.2 action的使用

作用:给继承自ModelViewSet的视图类中定义的函数也添加路由

使用:

装饰器,放在被装饰的函数上方,method:请求方式,detail:是否带pk

class BookViewSet(ModelViewSet):
    queryset =Book.objects.all()
    serializer_class = BookSerializer
    
    @action(methods=[‘GET‘,‘POST‘],detail=True)
    def get_1(self,request,pk):
        print(pk)
        book=self.get_queryset()[:2]  # 从0开始截取一条
        ser=self.get_serializer(book,many=True)
        return Response(ser.data)
    
    # methods第一个参数,传一个列表,列表中放请求方式,
    # ^books/get_1/$ [name=‘book-get-1‘] 当向这个地址发送get请求,会执行下面的函数
    # detail:布尔类型 如果是True
    #^books/(?P<pk>[^/.]+)/get_1/$ [name=‘book-get-1‘]

以上是关于drf-视图集路由系统action装饰器的主要内容,如果未能解决你的问题,请参考以下文章

drf-路由组件

05 drf路由组件

使用DRF视图集时自定义action方法

DRF框架之路由Routers

drf框架 6 视图集与路由组件(开发最常用最高级) 三大认证原理 RBAC认证规则

DRF ---- 视图类 数据工具类 工具视图集 视图集