Django后端开发学习笔记Django REST Framework基于类的视图

Posted 梆子井欢喜坨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django后端开发学习笔记Django REST Framework基于类的视图相关的知识,希望对你有一定的参考价值。

学习参考:
【1】Django REST Framework教程(3): 基于类的视图APIView, GenericAPIView和视图集


DRF推荐使用基于类的视图(CBV)来开发API, 并提供了4种开发CBV开发模式。

1. 使用基础APIView类

DRF的APIView类继承了Django自带的View类, 一样可以按请求方法调用不同的处理函数,比如get方法处理GET请求,post方法处理POST请求。不过DRF的APIView要强大得多。它不仅支持更多请求方法,而且对Django的request对象进行了封装,可以使用request.data获取用户通过POST, PUT和PATCH方法发过来的数据,而且支持插拔式地配置认证、权限和限流类。

# blog/views.py
# 使用基础APIView类
from rest_framework.views import APIView
from django.http import Http404
from .models import Article
from .serializers import ArticleSerializer


class ArticleList(APIView):
    """
    List all articles, or create a new article.
    """
    def get(self, request, format=None):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            # 注意:手动将request.user与author绑定
            serializer.save(author=request.user)
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ArticleDetail(APIView):
    """
    Retrieve, update or delete an article instance.
    """
    def get_object(self, pk):
        try:
            return Article.objects.get(pk=pk)
        except Article.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        article = self.get_object(pk)
        serializer = ArticleSerializer(instance=article, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        article = self.get_object(pk)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

可以发现,不需要在对用户的请求方法进行判断。该视图可以自动将不同请求转发到相应处理方法,逻辑上也更清晰。

# blog/urls.py
from django.urls import re_path
from rest_framework.urlpatterns import format_suffix_patterns

from . import views

urlpatterns = [
	# 基于函数的视图
    # re_path(r'^articles/$', views.article_list),
    # re_path(r'^articles/(?P<pk>[0-9]+)$', views.article_detail),
    # 指向新的基于类的视图
    re_path(r'^articles/$', views.ArticleList.as_view()),
    re_path(r'^articles/(?P<pk>[0-9]+)$', views.ArticleDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

2. 使用Mixins类和GenericAPI类混配

使用基础的APIView类并没有大量简化代码。发现增删改查操作相关的代码包括返回内容对所有模型几乎都是一样的。假设现在需要对文章类别Category模型也进行序列化和反序列化,只需要复制Article视图代码,将Article模型改成Category模型, 序列化类由ArticleSeralizer类改成CategorySerializer类就行了。

对于这些通用的增删改查行为,DRF已经提供了相应的Mixin类。Mixin类可与generics.GenericAPI类联用,灵活组合成你所需要的视图。
现在来使用Mixin类和generics.GenericAPI类重写我们的类视图。

# blog/views.py
# 使用GENERIC APIView & Mixins
from rest_framework import mixins
from rest_framework import generics

class ArticleList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

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

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

    # 将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

GenericAPIView 类继承了APIView类,提供了基础的API视图。它对用户请求进行了转发,并对Django自带的request对象进行了封装。不过它比APIView类更强大,因为它还可以通过queryset和serializer_class属性指定需要序列化与反序列化的模型或queryset及所用到的序列化器类。

ListModelMixin 和 CreateModelMixin类则分别引入了.list() 和 .create() 方法,当用户发送get请求时调用Mixin提供的list()方法,将指定queryset序列化后输出,发送post请求时调用Mixin提供的create()方法,创建新的实例对象。在两个视图中,我们明确地将get和post方法绑定到适当的操作。

DRF还提供RetrieveModelMixin, UpdateModelMixin和DestroyModelMixin类,实现了对单个对象实例的查、改和删操作,如下所示:


class ArticleDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    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)

Q: 已经有get, post, delete等方法了,为什么mixin类引入的方法要以list, create, retrieve, destroy方法命名呢?

A: 因为请求方法不如操作名字清晰,比如get方法同时对应了获取对象列表和单个对象两种操作,使用list和retrieve方法后则很容易区分。另外post方法接受用户发过来的请求数据后,有时只需转发不需要创建模式对象实例,所以post方法不能简单等于create方法。

值得注意的是我们定义的序列化器ArticleSeralizer类并不包含author这个字段的,这是因为我们希望在创建article实例时我们将author与request.user进行手动绑定。在前面的例子中我们使用serializer.save(author=request.user)这一方法进行手动绑定。
新的ArticleList视图类中,使用.perform_create这个钩子函数是CreateModelMixin类自带的,用于执行创建对象时需要执行的其它方法。类似的钩子函数还有UpdateModelMixin提供的.perform_update方法和DestroyModelMixin提供的.perform_destroy方法。

3. 使用通用视图generics.*类, 比如 generics.ListCreateAPIView

将Mixin类和GenericAPI类混配,已经帮助我们减少了一些代码,但我们还可以做得更好,比如将get请求与mixin提供的list方法进行绑定感觉有些多余。幸好DRF还提供了一套常用的将 Mixin 类与 GenericAPI类已经组合好了的视图,开箱即用,可以进一步简化我们的代码,如下所示:

# generic class-based views
from rest_framework import generics

class ArticleList(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    # 将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Article.objects.all()
    serializer_class =ArticleSerializer

generics.ListCreateAPIView类支持List、Create两种视图功能,分别对应GET和POST请求。generics.RetrieveUpdateDestroyAPIView支持Retrieve、Update、Destroy操作,其对应方法分别是GET、PUT和DELETE。
其它常用generics.*类视图还包括ListAPIView, RetrieveAPIView, RetrieveUpdateAPIView等等。

4. 使用视图集ViewSet和ModelViewSet

使用通用视图generics.*类后视图代码已经大大简化,但是ArticleList和ArticleDetail两个类中queryset和serializer_class属性依然存在代码重复。使用视图集可以将两个类视图进一步合并,一次性提供List、Create、Retrieve、Update、Destroy这5种常见操作,这样queryset和seralizer_class属性也只需定义一次就好, 如下所示:

# blog/views.py
from rest_framework import viewsets

class ArticleViewSet(viewsets.ModelViewSet):
    # 用一个视图集替代ArticleList和ArticleDetail两个视图
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    # 自行添加,将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

使用视图集后,我们需要使用DRF提供的路由router来分发urls,因为一个视图集现在对应多个urls的组合,而不像之前的一个url对应一个视图函数或一个视图类。

# blog/urls.py

from django.urls import re_path
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'articles', viewset=views.ArticleViewSet)

urlpatterns = [
    # re_path(r'^articles/$', views.ArticleList.as_view()),
    # re_path(r'^articles/(?P<pk>[0-9]+)$', views.ArticleDetail.as_view()),
]
urlpatterns += router.urls

一个视图集对应List、Create、Retrieve、Update、Destroy这5种操作。有时候我只需要其中的几种操作,该如何实现呢?答案是在urls.py中指定方法映射即可,如下所示:

# blog/urls.py

from django.urls import re_path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
article_list = views.ArticleViewSet.as_view(
    {
        'get': 'list',
        'post': 'create'
    })

article_detail = views.ArticleViewSet.as_view({
    'get': 'retrieve', # 只处理get请求,获取单个记录
})

urlpatterns = [
    re_path(r'^articles/$', article_list),
    re_path(r'^articles/(?P<pk>[0-9]+)$', article_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

这里需要注意format_suffix_patterns和router.urls不可以混用!

5. 总结对比

  • 基础的API类:可读性最高,代码最多,灵活性最高。当你需要对的API行为进行个性化定制时,建议使用这种方式。
  • 通用generics.*类:可读性好,代码适中,灵活性较高。当你需要对一个模型进行标准的增删查改全部或部分操作时建议使用这种方式。
  • 使用视图集viewset: 可读性较低,代码最少,灵活性最低。当你需要对一个模型进行标准的增删查改的全部操作且不需定制API行为时建议使用这种方式。

以上是关于Django后端开发学习笔记Django REST Framework基于类的视图的主要内容,如果未能解决你的问题,请参考以下文章

Django后端开发学习笔记Django REST Framework基于类的视图

Django后端开发学习笔记Django REST Framework基于类的视图

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django基本概念