Skip to content

about

django3.2 + python3.9.7 + mysql5.7.20

由于Django默认的视图View提供的功能不尽如人意,所以drf中在原View的基础上做了封装,更方便我们使用,它就是APIView以及子类。

drf提供的视图主要作用:

  • 控制序列化器的执行(数据校验、保存、序列化与反序列化)。
  • 控制数据库查询的执行。
  • 对请求类和响应类做了进一步的封装。

APIView

APIView是REST framework提供的所有视图的基类,继承自Django的View父类。

APIViewView的不同之处在于:

  • 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象。
  • 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式。
  • 任何APIException异常都会被捕获到,并且处理成合适的响应信息。
  • 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。

支持定义的属性

  • authentication_classes 列表或元祖,身份认证类
  • permissoin_classes 列表或元祖,权限检查类
  • throttle_classes 列表或元祖,流量控制类

APIView中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。

请求与响应

本小节主要围绕:

python
# 学习django cbv时的View
from django.views import View

# 在django View的基础上做了封装
from rest_framework.views import APIView

# 对django HtppResponse做了封装
from rest_framework.response import Response

这三个类展开讲解,主要介绍其中的不同。

Request

drf的request对象,是通过Request类对django原生request对象做了进一步的封装:

python
from rest_framework.request import Request

对于不同的请求类型,我们后端获取数据也不太一样,通过postman发送请求,我们分别来看后端都是如何获取数据的。

get请求

get请求没啥说的:

python
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(APIView):
    def get(self, request):
        """
        postman发送请求
        GET http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
        """
        # 这个request是drf封装后的
        print(request)  # <rest_framework.request.Request: GET '/book/list/?k1=v1&k2=v2'>
        # 这个才是django原生的
        print(request._request)  # <WSGIRequest: GET '/book/list/?k1=v1&k2=v2'>
        print(request.GET)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        print(request.query_params)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        print(request.data)  # {}
        return Response({"type": "get", "code": 0})

get请求,你可以通过request.GET,也可以使用drf的request.query_params来获取数据。

POST请求,请求类型是json数据

python
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(APIView):
    def post(self, request):
        """
        postman
        POST Content-Type:application/json
        http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
        请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
        """
        print(request)  # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
        print(request.headers['Content-Type'])  # application/json
        print(request.POST)  # <QueryDict: {}>
        print(request.data)  # {'title': 'Python快速入门55', 'author': ['张开', '王开']}
        print(request.query_params)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        return Response({"type": "post", "code": 0})

很明显,如果请求体的数据是json,我们通过request.data获取即可。

如果url上携带了参数,我们通过request.query_params获取。

POST请求,请求类型是x-www-form-urlencoded数据

form表单提交的post请求,也是urlencoded类型。

python
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(APIView):
    def post(self, request):
        """
        postman
        POST Content-Type:application/x-www-form-urlencoded
        http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
        请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
        """
        print(request)  # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
        print(request.headers['Content-Type'])  # application/x-www-form-urlencoded
        print(request.POST)  # <QueryDict: {'title': ['Python快速入门55'], 'author': ['张开', '王开']}>
        print(request.POST.get("author")) # 王开
        print(request.POST.getlist("author"))  # ['张开', '王开']
        print(request.data)  # <QueryDict: {'title': ['Python快速入门55'], 'author': ['张开', '王开']}>
        print(request.data.get("author"))  # 王开
        print(request.data.getlist("author"))  # ['张开', '王开']
        print(request.query_params)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        return Response({"type": "post", "code": 0})

对于这样的请求,我们基本上可以通过request.POSTrequest.data来获取数据。

这个跟django原生的request.POST基本一样。

POST请求,请求类型是form-data数据

提交的数据包含文件。

python
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(APIView):
    def get(self, request):
        """
        postman发送请求
        GET
        """
        return Response({"type": "get", "code": 0})

    def post(self, request):
        """
        postman
        POST Content-Type:multipart/form-data; boundary=--------------------------251656075156781150213005
        http://127.0.0.1:8000/book/list/?k1=v1&k2=v2
        请求体: {"title": "Python快速入门55", "author": ["张开", "王开"]}
        """
        print(request)  # <rest_framework.request.Request: POST '/book/list/?k1=v1&k2=v2'>
        print(request.headers['Content-Type'])  # multipart/form-data; boundary=--------------------------251656075156781150213005
        print(request.POST)  # <QueryDict: {'k1': ['v1']}>
        print(request.FILES)  # <MultiValueDict: {'a.py': [<InMemoryUploadedFile: 牌.py (application/octet-stream)>]}>
        print(request.data)  # <QueryDict: {'k1': ['v1'], 'a.py': [<InMemoryUploadedFile: 牌.py (application/octet-stream)>]}>
        print(request.query_params)  # <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        return Response({"type": "post", "code": 0})

那么,按照原来的套路,对于文件的操作还是通过requet.POSTrequet.FILES结合来处理。

而drf也就是将它都封装到了request.data中了。

Response

前文有提过,这个Response就是drf对django的HttpResponse进行了封装:

python
# 对django HtppResponse做了封装
from rest_framework.response import Response

Renderer 渲染器

REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(客户端希望接收的数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

例如你直接用浏览器访问,Response则会render一个页面。

1832669305594970112.png

或者你可以通过url传参format=json格式获取json类型的返回:

1832669306396082176.png

如果你是postman发起get请求,也会默认得到json类型的数据。

这些都有赖于drf提供的解析器来根据请求来决定返回什么。

你可以在drf的配置文件中看到:

python
# 这么导入
from rest_framework import settings

# drf的很多设置都在这个配置文件中,我们目前只关注render的解析器
DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

当然了,我们也可以在我们的配置文件中,设置这两个参数,来决定如何返回:

python
# 我们自己项目的settings.py
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        # 'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器API渲染器
    )
}

如上配置,我们将默认的BrowsableAPIRenderer渲染器注释掉,那么浏览器直接访问也仅会的得到json类型的数据了。

构造方式

来看Response的其它配置:

python
from rest_framework.response import Response

Response(data, status=None, template_name=None, headers=None, content_type=None)
  • data:只需要传递Python的内建数据类型就可以了,drf会使用renderer渲染器处理data。注意,data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

  • status:状态码,默认是200,你也可以手动指定。

    python
    # 可以这样写
    return Response({'msg': 'ok'},status=204)
    
    # 也可以从def体统的状态码文件中导入
    from rest_framework import status
    return Response({'msg': 'ok'},status=status.HTTP_204_NO_CONTENT)
  • template_name: 模板名称,如果使用HTMLRenderer 时需指明;就是有些人觉得Response返回的那个页面丑,那么就可以通过这个模板自行定制。

  • headers: 用于存放响应头信息的字典;比如放一些cookie啊或者一些自定制的响应头啊都可以,例如:

    python
    return Response({'msg': 'ok'},status=204,headers={'xx':'oo'})
  • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据(accept请求头)来设置该参数。

除了上述这些参数之外,Response类的实例化对象还有一下这些属性,当然不常用就是了:

  • response.data:传给response对象的序列化后,但尚未render处理的数据。
  • response.status_code:状态码的数字。
  • response.content:经过render处理后的响应数据。

GenericAPIView

通用视图类(GenericAPIView)是APIView的子类,主要增加了操作序列化器和数据库查询的方法。

其主要作用是结合Mixin扩展类简化视图类的编写。

那么GenericAPIView给我们提供了哪些功能,先大致看下,后面会具体的应用:

  • 属性:

    • serializer_class,指明视图使用的序列化器类,如果没有指定get_serializer_class(self)方法,那么该属性为必填属性。
    • queryset,指明使用的数据查询集。
    • pagination_class ,指明分页控制类。
    • filter_backends ,指明过滤控制后端(查询一些数据的过滤条件等等)。
  • 方法:

    • get_serializer_class(self),如果一个视图类中根据不同的业务场景,需要使用多个序列化器类,那么就在这个方法中进行处理,如果有了该方法,就可以不用声明serializer_class属性了。

      python
      # 在我们的视图类中,重写get_serializer_class方法
      def get_serializer_class(self):
          if self.request.user.is_staff:
              return FullAccountSerializer
          return BasicAccountSerializer
    • get_serializer(self, *args, **kwargs),返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。

      注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。

      比如serializer = self.get_serializer(instance=self.get_object(),context={'pk':pk}),下面的request和view我们后面会用到,现在先了解一下,后面使用就知道了。

      • request 当前视图的请求对象。
      • view 当前请求的类视图对象。
      • format 当前请求期望返回的数据格式。
    • get_queryset(self),返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写,例如:

      python
      def get_queryset(self):
          user = self.request.user
          return user.accounts.all()
    • get_object(self),返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用。在试图中可以调用该方法获取详情信息的模型类对象。若详情访问的模型类对象不存在,会返回404。该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。

      python
      # url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
      class BookDetailView(GenericAPIView):
          queryset = BookInfo.objects.all()
          serializer_class = BookInfoSerializer
      
          def get(self, request, pk):
              book = self.get_object() # get_object()方法根据pk参数查找queryset中的数据对象
              serializer = self.get_serializer(book)
              return Response(serializer.data)

先来看我们原来使用APIView时,我们写的增删改查。

楔子

还是咱们之前序列化器部分用过的book表。

models.py

python
from django.db import models


class Book(models.Model):
    """ 书籍表 """
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    choice = ((1, "Python"), (2, "Linux"), (3, "Go"))
    category = models.IntegerField(choices=choice, verbose_name="书籍类别")
    pub_time = models.DateField(verbose_name="出版日期")
    publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社')
    author = models.ManyToManyField(to='author', verbose_name='作者')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name_plural = "书籍表"
        db_table = verbose_name_plural


class Publisher(models.Model):
    """ 出版社表 """
    title = models.CharField(max_length=32, verbose_name="出版社名称")
    address = models.CharField(max_length=255, verbose_name="地址")
    email = models.CharField(max_length=32, default="publisher@qq.com")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name_plural = "出版社表"
        db_table = verbose_name_plural


class Author(models.Model):
    """ 作者表 """
    name = models.CharField(max_length=32, verbose_name="作者名称")
    age = models.IntegerField(verbose_name="作者名称")
    phone = models.CharField(max_length=20, default='182111101111')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "作者表"
        db_table = verbose_name_plural

主路由urls.py

python
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('stu/', include("student.urls")),
    path('ser/', include("ser.urls")),
    path('book/', include("book.urls")),
]

book app的urls.py

python
from django.urls import path
from .views import BookView, BookRetrieveView

urlpatterns = [
    path('list/', BookView.as_view()),
    path('list/<int:pk>', BookRetrieveView.as_view()),
]

views.py

python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    category_info = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    author_info = serializers.SerializerMethodField(read_only=True)

    def get_category_info(self, obj):
        return obj.get_category_display()

    def get_publisher_info(self, obj):
        pub_obj = obj.publisher
        return {"id": pub_obj.id, "title": pub_obj.title}

    def get_author_info(self, obj):
        au_obj = obj.author.all()
        return [{"id": i.id, "name": i.name} for i in au_obj]

    class Meta:
        model = Book
        fields = "__all__"
        extra_kwargs = {
            "category": {"write_only": True},
            "publisher": {"write_only": True},
            "author": {"write_only": True},
        }


class BookView(APIView):
    """
    主要处理:
        get: 客户端请求所有书籍信息
        post: 添加书籍信息
    """

    def get(self, request):
        """ 返回所有书籍信息 """
        book_list = Book.objects.all()
        ser_obj = BookSerializer(instance=book_list, many=True)
        return Response(ser_obj.data)

    def post(self, request):
        """ 添加书籍 """
        data = request.data
        ser_obj = BookSerializer(data=data)
        if ser_obj.is_valid():
            ser_obj.save()
            print(222, ser_obj.data)
            return Response(ser_obj.data)
        else:
            return Response(ser_obj.errors)


class BookRetrieveView(APIView):
    """
    主要根据id进行:
        get: 客户端根据id获取单条书籍信息
        put: 更新书籍信息
        delete: 删除书籍信息
    """

    def get(self, request, pk):
        """ 返回单条书籍信息 """
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            ser_obj = BookSerializer(instance=book_obj)
            return Response(ser_obj.data)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

    def put(self, request, pk):
        """ 更新书籍 """
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            data = request.data
            ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
            if ser_obj.is_valid():
                ser_obj.save()
                print(4444, ser_obj.data)
                return Response(ser_obj.data)
            else:
                return Response(ser_obj.errors)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

    def delete(self, request, pk):
        """ 删除书籍 """
        # ret = book_obj = Book.objects.filter(pk=pk).first()
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            ser_obj = BookSerializer(instance=book_obj)
            # 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
            data = ser_obj.data
            book_obj.delete()
            return Response(data)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

因为目前我们都是通过请求类型对应方法名来处理该请求,那么问题来了,获取单条数据和获取所有数据都是get请求,但一个视图类中不能写两个同名的方法, 所以,折中,我们搞了两套路由和视图类来处理当前的增删改查,当然,这种问题后面我们会解决掉,暂时先这么写着。

我们结合postman来完成对book表的增删改查操作:

python
# GET
# http://127.0.0.1:8000/book/list/
# 结果太多,不做展示了

# POST
# http://127.0.0.1:8000/book/list/
{
    "category": 1,
    "publisher": 1,
    "author": [1,2],
    "title": "Python快速入门66",
    "pub_time": "2022-04-16"
}
# response
{
    "id": 15,
    "category_info": "Python",
    "publisher_info": {
        "id": 1,
        "title": "沙河出版社"
    },
    "author_info": [
        {
            "id": 1,
            "name": "张开"
        },
        {
            "id": 2,
            "name": "李开"
        }
    ],
    "title": "Python快速入门66",
    "pub_time": "2022-04-16"
}

# PUT
# http://127.0.0.1:8000/book/list/15
{
    "category": 2,
    "publisher": 2,
    "author": [2,3],
    "title": "Python快速入门6666",
    "pub_time": "2022-03-16"
}
# response
{
    "id": 15,
    "category_info": "Linux",
    "publisher_info": {
        "id": 2,
        "title": "高教园出版社"
    },
    "author_info": [
        {
            "id": 2,
            "name": "李开"
        },
        {
            "id": 3,
            "name": "王开"
        }
    ],
    "title": "Python快速入门6666",
    "pub_time": "2022-03-16"
}

# DELETE
# http://127.0.0.1:8000/book/list/13
# 请求无需携带参数
# response
{
    "id": 13,
    "category_info": "Linux",
    "publisher_info": {
        "id": 2,
        "title": "高教园出版社"
    },
    "author_info": [
        {
            "id": 2,
            "name": "李开"
        },
        {
            "id": 3,
            "name": "王开"
        }
    ],
    "title": "Python快速入门4",
    "pub_time": "2022-06-15"
}

看着非常完美,增删改查都可以,并且可以套用到其它模型类中。

但是,问题来了,如果模型类有很多,每个模型类都要这么写,代码逻辑都一致,这样就会显得很冗余。所以,我们要想招给它精简精简。

GenericAPIView快速上手

现在,我们要用GenericAPIView来优化我们的视图类的代码。

模型类、路由、序列化器都不变,我们就把视图类简单调整下。

python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.generics import GenericAPIView

class BookView(GenericAPIView):
    # 必填属性
    queryset = Book.objects.all()
    # 如果没有重写get_serializer_class方法,则必填serializer_class
    serializer_class = BookSerializer

    def get(self, request):
        """ 返回所有书籍信息 """
        # 原来这么写
        # book_list = Book.objects.all()
        # ser_obj = BookSerializer(instance=book_list, many=True)
        # return Response(ser_obj.data)

        # 现在这么写
        ser_obj = self.serializer_class(instance=self.get_queryset(), many=True)
        return Response(ser_obj.data)

    def post(self, request):
        """ 添加书籍 """
        # 原来这么写
        # data = request.data
        # ser_obj = BookSerializer(data=data)
        # if ser_obj.is_valid():
        #     ser_obj.save()
        #     print(222, ser_obj.data)
        #     return Response(ser_obj.data)
        # else:
        #     return Response(ser_obj.errors)

        # 现在这么写
        data = request.data
        ser_obj = self.serializer_class(data=data)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response(ser_obj.data)
        else:
            return Response(ser_obj.errors)

class BookRetrieveView(GenericAPIView):
    # 必填属性
    queryset = Book.objects.all()
    # 如果没有重写get_serializer_class方法,则必填serializer_class
    serializer_class = BookSerializer
    # 默认self.get_object会根据pk过滤单条数据,如果你用的是id,所以必须声明lookup_field
    # lookup_field = "id"

    def get(self, request, pk):
        """ 返回单条书籍信息 """
        # 原来这么写
        # book_obj = Book.objects.filter(pk=pk).first()
        # if book_obj:
        #     ser_obj = BookSerializer(instance=book_obj)
        #     return Response(ser_obj.data)
        # else:
        #     return Response({"code": 1, "msg": "请求的数据id错误"})

        # 现在这么写
        ser_obj = self.serializer_class(instance=self.get_object())
        # 如果根据id没有get到数据,就会在内部直接返回{"detail": "Not found."}
        # 如果有数据,就从这里返回
        return Response(ser_obj.data)

    def put(self, request, pk):
        """ 更新书籍 """
        # 原来这么写
        # book_obj = Book.objects.filter(pk=pk).first()
        # if book_obj:
        #     data = request.data
        #     ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
        #     if ser_obj.is_valid():
        #         ser_obj.save()
        #         print(4444, ser_obj.data)
        #         return Response(ser_obj.data)
        #     else:
        #         return Response(ser_obj.errors)
        # else:
        #     return Response({"code": 1, "msg": "请求的数据id错误"})

        # 现在这么写
        ser_obj = self.serializer_class(instance=self.get_object(), data=request.data, partial=True)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response(ser_obj.data)
        else:
            return Response(ser_obj.errors)

    def delete(self, request, pk):
        """ 删除书籍 """
        # 原来这么写
        # book_obj = Book.objects.filter(pk=pk).first()
        # if book_obj:
        #     ser_obj = BookSerializer(instance=book_obj)
        #     # 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
        #     data = ser_obj.data
        #     book_obj.delete()
        #     return Response(data)
        # else:
        #     return Response({"code": 1, "msg": "请求的数据id错误"})

        # 现在这么写
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            ser_obj = self.serializer_class(instance=book_obj)
            # 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
            data = ser_obj.data
            book_obj.delete()
            return Response(data)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

这么看,代码量并没有少多少!

GenericAPIView只是帮我们把数据库查询和调用序列化器类做了封装,目的是为了将一些公共性质的代码单独封装,其它感觉没啥用。

所以,我们往下继续学习,学习其它的Mixin扩展类,来进一步对简化我们的视图类。

Mixin系列扩展类

首先啊,Mixin系列的类一般叫做混合类、或者扩展类,通常都是搭配其它类来使用,单独使用的场景不多。

那么,搭配GenericAPIView类使用的Mixin混合类,也有这么几个:

python
from rest_framework.generics import GenericAPIView
# 搭配GenericAPIView使用的几个扩展类
from rest_framework.mixins import ListModelMixin		# 获取所有数据
from rest_framework.mixins import CreateModelMixin		# 添加一条数据
from rest_framework.mixins import RetrieveModelMixin	# 获取单条数据
from rest_framework.mixins import UpdateModelMixin		# 更新数据
from rest_framework.mixins import DestroyModelMixin		# 删除数据
  • ListModelMixin,获取多条数据的列表视图扩展类,提供list(request, *args, **kwargs)方法快速实现列表视图。

    返回200状态码。

    该Mixin的list方法会对数据进行过滤和分页。

  • CreateModelMixin,添加数据的创建视图扩展类,提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。

    如果序列化器对前端发送的数据验证失败,返回400错误。

  • RetrieveModelMixin,获取单条数据,详情视图扩展类,提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。

    如果存在,返回200, 否则返回404。

  • UpdateModelMixin,更新视图扩展类,提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。

    同时也提供partial_update(request, *args, **kwargs)方法,可以实现局部更新。

    成功返回200,序列化器校验数据失败时,返回400错误。

  • DestroyModelMixin,删除视图扩展类,提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。

    成功返回204,不存在返回404。

这几个类中也可以支持分页和条件过滤,后面我们会讲到。

来看示例,代码有变动的只有两个视图类:

python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import CreateModelMixin
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.mixins import UpdateModelMixin
from rest_framework.mixins import DestroyModelMixin


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

    def get(self, request):
        """ 返回所有书籍信息 """
        # 调用ListModelMixin中的list方法
        return self.list(request)

    def post(self, request):
        """ 添加书籍 """
        # 调用CreateModelMixin中的create方法
        return self.create(request)


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

    def get(self, request, pk):
        """ 返回单条书籍信息 """
        # 调用RetrieveModelMixin中的retrieve方法
        return self.retrieve(request, pk)

    def put(self, request, pk):
        """ 更新书籍 """
        # 调用UpdateModelMixin中的update方法
        return self.update(request, pk, partial=True)

    def delete(self, request, pk):
        """ 删除书籍 """
        # 调用DestroyModelMixin中的destroy方法
        # 默认删除成功,返回None,状态码是204
        return self.destroy(request, pk)

这几个类帮我们实现了增删改查,方便倒是方便了,当然了如果业务逻辑比较复杂的话,这几个类就不适用了啊。

当然了,你如果需要扩展,也可以自己扩展,不用Mixin扩展类。

GenericAPIView的视图子类

在Mixin系列扩展类中,我们调用其内部的方法帮我们实现了增删改查,那么在本小节,我们可以通过GenericAPIView的视图子类,继续简化我们的代码。

python
from rest_framework.generics import ListAPIView
"""
ListAPIView = GenericAPIView + ListModelMixin
提供了 get 方法,返回所有数据
"""
from rest_framework.generics import CreateAPIView
"""
CreateAPIView = GenericAPIView + CreateModelMixin
提供 post 方法,添加一条数据
"""
from rest_framework.generics import RetrieveAPIView
"""
RetrieveAPIView = GenericAPIView + RetrieveModelMixin
提供 get 方法,返回单条数据
"""
from rest_framework.generics import UpdateAPIView
"""
UpdateAPIView = GenericAPIView + UpdateModelMixin
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
"""
from rest_framework.generics import DestroyAPIView
"""
DestroyAPIView = GenericAPIView + DestroyModelMixin
提供 delete 方法,删除一条数据
"""
from rest_framework.generics import RetrieveUpdateAPIView
"""
RetrieveUpdateAPIView = GenericAPIView + RetrieveModelMixin + UpdateModelMixin
提供 get 方法,获取单条数据
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
"""
from rest_framework.generics import RetrieveUpdateDestroyAPIView
"""
RetrieveUpdateDestroyAPIView = GenericAPIView + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin
提供 get 方法,获取单条数据
提供 put 方法,更新所有字段的数据
提供 patch 方法,允许更新部分字段的数据,内部通过partial=True实现
提供 delete 方法,删除一条数据                         
"""

让我们的视图类根据情况继承上面的类,就不用继承那么多Minin系列的类了。

ListAPIView/CreateAPIView/RetrieveAPIView/UpdateAPIView/DestroyAPIView

先来看这几个视图类的用法。

还是只动两个视图类,其它不变:

python
from rest_framework.generics import ListAPIView
from rest_framework.generics import CreateAPIView
from rest_framework.generics import RetrieveAPIView
from rest_framework.generics import UpdateAPIView
from rest_framework.generics import DestroyAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView

class BookView(ListAPIView, CreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 查询所有记录的get方法由ListAPIView负责
    # 添加记录的post方法由CreateAPIView负责
    # 所以,我们都可以不写了


class BookRetrieveView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 查询单条记录的get方法由RetrieveAPIView负责
    # 更新的put方法由UpdateAPIView负责
    # 注意,全部字段都更新,客户端使用put请求提交,如果局部字段更新,就要使用patch请求了
    # 删除的delete由DestroyAPIView负责
    # 所以,我们都可以不写了

RetrieveUpdateAPIView/RetrieveUpdateDestroyAPIView

通过继承关系和提供的功能来看,RetrieveUpdateAPIView只有获取一条数据、更新(部分/全部)数据,没有删除功能;而RetrieveUpdateDestroyAPIView则在RetrieveUpdateAPIView的基础上提供了删除功能。

所以这里的示例我们只需要演示RetrieveUpdateDestroyAPIView的用法就行了。

python
from rest_framework import serializers
from rest_framework.generics import ListAPIView
from rest_framework.generics import CreateAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import DestroyAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    category_info = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    author_info = serializers.SerializerMethodField(read_only=True)

    def get_category_info(self, obj):
        return obj.get_category_display()

    def get_publisher_info(self, obj):
        pub_obj = obj.publisher
        return {"id": pub_obj.id, "title": pub_obj.title}

    def get_author_info(self, obj):
        au_obj = obj.author.all()
        return [{"id": i.id, "name": i.name} for i in au_obj]

    class Meta:
        model = Book
        fields = "__all__"
        extra_kwargs = {
            "category": {"write_only": True},
            "publisher": {"write_only": True},
            "author": {"write_only": True},
        }


class BookView(ListAPIView, CreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 查询所有记录的get方法由ListAPIView负责
    # 添加记录的post方法由CreateAPIView负责
    # 所以,我们都可以不写了


class BookRetrieveView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # RetrieveUpdateDestroyAPIView把需要的几个功能都实现了,所以都不用写了
    # 注意,全部字段都更新,客户端使用put请求提交,如果局部字段更新,就要使用patch请求了

# 或者用RetrieveUpdateAPIView,DestroyAPIView结合也行
# class BookRetrieveView(RetrieveUpdateAPIView,DestroyAPIView):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer

视图集ViewSet

目前为止,我们需要两个视图类来处理增删改查:通过两个路由和两个视图类,来解决带不带pk的两种情况。

那么现在,在本小节中,我们通过action字典,来将两个视图类合并为一个视图类(至于将两个路由合并成一个,将在路由部分讲),不同的请求通过action动作进行映射,让我们定义的方法名可以不再必须是getpost方式。

然后结合那Mixin扩展类或者GenericAPIView扩展子类实现增删改查。

本小节的示例,主要修改视图类和路由部分的代码,其它的代码不变,所以,models、序列化器类这些都用上面的,不再贴出来了。

ViewSet

python
from rest_framework.viewsets import ViewSet
"""
ViewSet = ViewSetMixin + APIView
"""

ViewSet作用类似于APIView,提供了身份认证、权限校验、限流等功能。

ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{'get':'list'})的映射处理工作。

在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。

上面这句话怎么理解呢?简单来说,就是通过不同的视图类的组合,我们的代码量也不同。

视图类只继承ViewSet

来看视图类只继承ViewSet的情况,我们的代码量:

python
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView

urlpatterns = [
    path('list/', BookView.as_view({"get": "get_all", "post": "create"})),
    path('list/<int:pk>', BookView.as_view({"get": "get_one", "put": "update", "delete": "destroy"})),
]

# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response

class BookView(ViewSet):
    def get_all(self, request):
        """ 返回所有书籍信息 """
        book_list = Book.objects.all()
        ser_obj = BookSerializer(instance=book_list, many=True)
        return Response(ser_obj.data)

    def create(self, request):
        """ 添加书籍 """
        data = request.data
        ser_obj = BookSerializer(data=data)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response(ser_obj.data)
        else:
            return Response(ser_obj.errors)

    def get_one(self, request, pk):
        """ 返回单条书籍信息 """
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            ser_obj = BookSerializer(instance=book_obj)
            return Response(ser_obj.data)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

    def update(self, request, pk):
        """ 更新书籍 """
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            data = request.data
            ser_obj = BookSerializer(instance=book_obj, data=data, partial=True)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.data)
            else:
                return Response(ser_obj.errors)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

    def destroy(self, request, pk):
        """ 删除书籍 """
        # ret = book_obj = Book.objects.filter(pk=pk).first()
        book_obj = Book.objects.filter(pk=pk).first()
        if book_obj:
            ser_obj = BookSerializer(instance=book_obj)
            # 这个ser_obj.data一定要写在删除之前,让其先序列化,然后再删
            data = ser_obj.data
            book_obj.delete()
            return Response(data)
        else:
            return Response({"code": 1, "msg": "请求的数据id错误"})

重要的:

  • ViewSet重写了as_view方法,所以路由中才支持写字典,进行映射。
  • 由于我们的视图类只继承ViewSet的话,则你可以自定义方法名来对应不同的请求类型。

ViewSet搭配Mixin扩展类使用

问题来了,你肯定想着,上面这些都可以通过学过的Mixin扩展类和视图子类来简化代码啊,的确可以。

python
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView

urlpatterns = [
    path('list/', BookView.as_view({"get": "list", "post": "create"})),
    path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin

class BookView(ViewSet, GenericAPIView, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

增删改查都由几个Mixin扩展类实现了,但Mixin扩展类又依赖GenericAPIView视图类,所以不能忘了继承GenericAPIView

ViewSet搭配GenericAPIView视图子类使用

这个就减少了很多继承代码。

python
# ---------------------- urls.py ----------------------
from django.urls import path
from .views import BookView

urlpatterns = [
    path('list/', BookView.as_view({"get": "list", "post": "create"})),
    path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

# ---------------------- views.py ----------------------
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.generics import RetrieveUpdateDestroyAPIView, ListCreateAPIView

class BookView(ViewSet, RetrieveUpdateDestroyAPIView, ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

增删改查由RetrieveUpdateDestroyAPIViewListCreateAPIView或其父类实现。

GenericViewSet

GenericViewSet没什么新花样,就是视图类继承了:

python
from rest_framework.viewsets import ViewSet, GenericViewSet
"""
GenericViewSet = ViewSet + GenericAPIView
"""

代码量少了那么一丢丢:

python
# 我们在 ViewSet 中这么继承
class BookView(ViewSet, GenericAPIView, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
# 现在通过 GenericViewSet 的继承变成了这样
class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

路由不变,所以就不贴出来了。

ModelViewSet

ModelViewSet的话,其实也没什么新花样,就是对视图类的继承又多套了个马甲:

python
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet
"""
ModelViewSet = GenericViewSet + CreateModelMixin + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + ListModelMixin
"""

代码量相对于GenericViewSet又少了那么一丢丢的继承代码:

python
# 我们在 GenericViewSet 中这么继承
class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
# 现在通过 ModelViewSet 的继承变成了这样
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

路由不变,所以就不贴出来了。

ReadOnlyModelViewSet

ReadOnlyModelViewSet的话,用的比较少,当然了,相对于上面几个视图集,就是调整了下视图类的继承,它长这样:

python
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
"""
ReadOnlyModelViewSet = GenericViewSet + RetrieveModelMixin + ListModelMixin
"""

见名知意,ReadOnlyModelViewSet只提供了查询单条和查询所有数据这两个方法,应用于某些只读的特殊场景。

当然,我们通过不同的视图类的搭配,也可以搞出来一些针对特殊场景的视图类,所以ReadOnlyModelViewSet没啥特别的,这里不再多表。

self.action

视图集还提供了一个self.action属性,这个属性就是当前请求的请求类型,我们可以在我们的视图类中,进行使用。

python
from rest_framework import serializers
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response

class BookView(ModelViewSet):
    queryset = Book.objects.all()
    # serializer_class = BookSerializer
    def get_serializer_class(self):
        print(self.action)
        # 根据请求的方法不同,我们在一些特殊场景中,可以使用不同的序列化器类
        if self.action.lower() == "list":
            return BookSerializer
        else:
            return Book2Serializer  # 这个 Book2Serializer 你可以替换为其它的序列化器类

路由routers

上面视图集部分,我们将两个视图类合并成一个视图类。

那么本章节,路由部分继续优化路由,将两个路由合并成一个路由。

Django rest framework提供了两个路由类:

python
from rest_framework.routers import DefaultRouter, SimpleRouter

这两个路由类,用法一致,区别就是DefaultRouter相比SimpleRouter多提供个根路由,后面会演示。

快速上手

首先models不变,视图类和序列化器类也不变:

python
from rest_framework import serializers
from rest_framework.viewsets import ViewSet, GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    category_info = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    author_info = serializers.SerializerMethodField(read_only=True)

    def get_category_info(self, obj):
        return obj.get_category_display()

    def get_publisher_info(self, obj):
        pub_obj = obj.publisher
        return {"id": pub_obj.id, "title": pub_obj.title}

    def get_author_info(self, obj):
        au_obj = obj.author.all()
        return [{"id": i.id, "name": i.name} for i in au_obj]

    class Meta:
        model = Book
        fields = "__all__"
        extra_kwargs = {
            "category": {"write_only": True},
            "publisher": {"write_only": True},
            "author": {"write_only": True},
        }

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

来看这两个路由类怎么用。

上面说了,DefaultRouterSimpleRouter用法一直,这里用SimpleRouter来演示。

主要修改urls.py

python
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView

router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
"""
prefix[必填参数]:路由前缀 
	http://127.0.0.1:8000/book/list/
	http://127.0.0.1:8000/book/list/1/  # 注意,如果没有最后的/,则会301重定向到带/的路由
viewset[必填参数]:视图类,注意不用写as_view
basename[可选参数]:路由别名
"""
print(router.urls)
"""
由下面打印结果可以看到,router对象内部会帮我们生成我们需要的那5个增删改查的接口
并且还很贴心的搞上了路由别名
[
    <URLPattern '^list/$' [name='book-list-list']>, 
    <URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>
]
"""
urlpatterns = [
    # path('list/', BookView.as_view({"get": "list", "post": "create"})),
    # path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]
# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls

你也可以通过router.register注册其它视图类。

这样,通过路由类和视图集类,就能让我们原来的两套视图类和两个路由都合并成一个,而且代码也非常简单。

添加路由的两种方式

上面的示例i,我们router.register注册之后,用的是urlpatterns += router.urls这种方式将router生成的路由列表合并到urlpatterns中。

那么除此之外,还有另外一种方式来实现:

python
from django.urls import path, include, re_path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView

router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
# print(router.urls)

urlpatterns = [
    # 方式1,通过include分发实现
    re_path(r"^", include(router.urls)),
    # path('list/', BookView.as_view({"get": "list", "post": "create"})),
    # path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

# 方式2,列表合并实现
# urlpatterns += router.urls

DefaultRouter和SimpleRouter的区别

上面反复说道,这俩路由类用法一致,那总要有区别吧!是的,来看区别。

python
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView

# router = SimpleRouter()
# router.register(prefix="list", viewset=BookView, basename='book-list')
# print(router.urls)
"""
[
    <URLPattern '^list/$' [name='book-list-list']>, 
    <URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>
]
"""
router = DefaultRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
print(router.urls)
"""
[
    <URLPattern '^list/$' [name='book-list-list']>, 
    <URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>, 
    # 下面是多出来的
    # 这俩支持加format参数
    <URLPattern '^list\.(?P<format>[a-z0-9]+)/?$' [name='book-list-list']>, 
    <URLPattern '^list/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='book-list-detail']>, 
    	http://127.0.0.1:8000/book/list?format=json
    	http://127.0.0.1:8000/book/list/?format=json
    
    # 下面这俩就是所谓的 DefaultRouter 支持的根路由
    <URLPattern '^$' [name='api-root']>, 
    <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>
]
"""

urlpatterns = [
    # path('list/', BookView.as_view({"get": "list", "post": "create"})),
    # path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls

通过注释部分的打印结果,DefaultRouter就多了api-root路由。这个是干啥的呢?

# 首先这是经过路由分发后,当前app的根路由
	http://127.0.0.1:8000/book
# 我们访问BookView视图类是这么访问的
	http://127.0.0.1:8000/book/list
	http://127.0.0.1:8000/book/list/1/

那么,使用DefaultRouter的话,你访问当前app的跟路由,它返回:

# http://127.0.0.1:8000/book/
# 返回
{
    "list": "http://127.0.0.1:8000/book/list/"
    # 如果你还注册了其它路由,都会在这里列出来
}

它返回的是当前app中路由中所有你注册的路由。

如果你使用SimpleRouter注册路由的话,你访问当前app的跟路由,它将返回一个Page not found at /book/的大黄页。

这就是这两个路由类的区别。

action装饰器

上面那两个路由类只能帮我们生成增删改查的那五个接口。

text
http://127.0.0.1:8000/book/list
http://127.0.0.1:8000/book/list/1/

问题来了,如果我想在视图类中,自定义其它视图方法来处理一些特殊的需求,那路由怎么生成?即怎么生成如下这种路由:

text
http://127.0.0.1:8000/book/detail/		# 带pk的场景
http://127.0.0.1:8000/book/frist_five/  # 取前五条,不带pk的场景
http://127.0.0.1:8000/book/last_five/   # 取后五条,不带pk的场景

针对这种情况,路由不变,我们需要在视图类中通过装饰器的方式来完成。

python
# --------------------- views.py ---------------------
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action  # 就这个装饰器
from rest_framework.viewsets import ModelViewSet

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

    @action(methods=['get', 'post'], detail=True, url_path='detail_book', url_name='detail_book')
    def detail_book(self, request, pk):
        """ 模拟特殊场景,根据pk查看某条记录的详情信息 """
        return Response(f"返回{pk}的记录详情")

    @action(methods=['get'], detail=False, url_path='first_five', url_name='first_five')
    def first_five(self, request):
        """ 模拟特殊场景,返回前五条记录 """
        return Response(f"返回前五条记录")
    
# --------------------- urls.py ---------------------
from django.urls import path
from rest_framework.routers import DefaultRouter, SimpleRouter
from .views import BookView

router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
print(router.urls)
"""
[
    <URLPattern '^list/$' [name='book-list-list']>, 
    <URLPattern '^list/(?P<pk>[^/.]+)/$' [name='book-list-detail']>, 
    # 下面两个是加了action装饰器后,生成的路由
    # 下面这个不带pk的路由倒没啥可说的
    <URLPattern '^list/first_five/$' [name='book-list-first_five']>, 
    # 但注意这个带pk的,它的pk不跟咱们通常定义的一样,在路由的最后,它是在中间,这点要特别注意
    <URLPattern '^list/(?P<pk>[^/.]+)/detail_book/$' [name='book-list-detail_book']>
]
"""

urlpatterns = [
    # path('list/', BookView.as_view({"get": "list", "post": "create"})),
    # path('list/<int:pk>', BookView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
]

# 别忘了将router生成的路由列表合并到urlpatterns中
urlpatterns += router.urls

关于action装饰器的几个参数介绍:

  • methods,允许哪些http请求类型访问当前视图方法。
  • detail,如果是True,则表示接收参数,那么你视图方法中也别忘了接收;如果是False,表示不需要参数。
  • url_path,路由路径,如果不指定,则默认使用当前视图方法名作为路由路径。
  • url_name,路由别名。

你可以通过下面的url访问了:

http://127.0.0.1:8000/book/list/first_five
http://127.0.0.1:8000/book/list/1/detail_book

action中,如果URL带正则的话,你需要注意:

python
class XXXXSet(GenericViewSet):
    # 注意,如果url_path是一个带正则的路径,那么我们不必写后面的终止符$,drf内部会自带终止符
    # 即,我们不要这么写
    #       url_path="query/(?P<order_number>[0-9]+)/$"
    # drf会渲染成
    #       xxx/xxx/query/(?P<order_number>[0-9]+)/$/$
    # 所以我们直接这么写即可
    #       url_path="query/(?P<order_number>[0-9]+)"
    # drf会渲染成
    #       xxx/xxx/query/(?P<order_number>[0-9]+)/$
    @action(detail=False, methods=['get'], url_path="query/(?P<order_number>[0-9]+)")
    def query(self, request, order_number):
        pass

路由类生成URL的方式

SimpleRouter

1832669307083948032.png

DefaultRouter

1832669307545321472.png

DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。