前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用django写接口(优化篇)

用django写接口(优化篇)

作者头像
陈宇明
发布2020-12-15 16:05:42
2.2K0
发布2020-12-15 16:05:42
举报
文章被收录于专栏:设计模式

作者:Kuky_xs

博客:https://www.jianshu.com/u/9fcd71535294

前言

系列文章:

django入门:环境及项目搭建

django入门:数据模型

django入门:视图及模版

django入门:Admin管理系统及表单

django入门:通用视图类重构视图

在《用django写接口(入门篇)》提到这篇会讲 views 的代码优化,在这之前,我们先适当了解下 DRF 中的 Request 和 Response。

  1. Request 继承 HttpRequest,里面有个 request.data 属性,可以处理任意数据,例如 'POST','PUT','PATCH',其用法类似表单中的 request.POST (参考 django 表单部分)
  2. Response 是一种 TemplateResponse 采用未呈现的内容,通过内容协商来确定正确的内容类型以返回给客户端,用法直接 return Response(data) 即可

了解完 Request 和 Response 我们将分别通过 @api_view,APIView 和通用视图类对 view 进行一些改造

api_view 注解重构

代码语言:javascript
复制
# ....import 省略

# 将该视图的请求方法写在注解中,表示该接口只接受列表内的请求方式
@api_view(['GET', 'POST'])
def post_list(request):
    if request.method == 'GET':
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        # 通过 Response 展示相应的数据
        return Response(serializer.data)
    elif requets.method == 'POST':
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            # 引入 status 模块,比数字标识符更加直观
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

然后运行项目,输入网址后,所展示的界面和之前的不同了,多了以下部分

代码语言:javascript
复制
HTTP 200 OK
Allow: GET, POST, OPTIONS
Content-Type: application/json
Vary: Accept

这些返回项,而且页面也不是 json了

优化后的列表接口信息

我们继续做一些修改,在 post_list 函数中加入 format 参数,默认值设置为 None,接着我们对 url 也做一些修改,通过 format_suffix_patterns 函数对接口返回的信息进行一些处理

代码语言:javascript
复制
# ....import 省略
app_name = 'api'

urlpatterns = [
    # url(r'^posts/$', views.post_list, name='api_posts'),
    url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail, name='api_post'),
]
# 增加这一行,这样我们就不需要逐一地添加对格式支持的 url 样式
urlpatterns = format_suffix_patterns(urlpatterns)

然后我们对我们接口请求的网址做些修改,在我们之前请求的网址末尾加入 .json 记得去除最末尾的 "/",然后我们又可以看到修改前返回的 json 格式数据啦(这边就不贴图啦~)。对于 detail 接口的修改我们也可以根据对 list 的修改进行相应修改,不做多余解释。

APIView 重构

代码语言:javascript
复制
# ....import 省略
class PostList(APIView):
    # 定义 GET 请求的方法,内部实现相同 @api_view
    def get(self, request, format=None):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    # 定义 POST 请求的方法
    def post(self, requets, format=None):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

然后对 url 进行部分修改即可

代码语言:javascript
复制
urlpatterns = [
    url(r'^posts/$', views.PostList.as_view(), name='posts'),
]

urlpatterns = fromat_suffix_patterns(urlpatterns)

然后我们又可以看到和上一个例子一样的界面

通过 mixins 和 generics 重构

代码语言:javascript
复制
class PostListMixins(mixins.ListModelMixin,
                     mixins.CreateModelMixin,
                     generics.GenericAPIView):
    # 指定列表
    queryset = Post.objects.all()
    # 指定序列化类
    serializer_class = PostSerializer
    
    def get(self, request, *args, **kwargs):
        # list 方法继承 ListModelMixin 而来
        return self.list(self, request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        # create 方法继承 CreateModelMixin 而来
        return self.create(self, request, *args, **kwargs)

# detail 视图通过 mixins 和 generics 改造
class PostDetailMixin(mixins.RetrieveModelMixin,
                      mixins.UpdateModelMixin,
                      mixins.DestroyModelMixin,
                      generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

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

    def put(self, request, *args, **kwargs):
        return self.update(self, request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(self, request, *args, **kwargs)

通用的基于类重构

代码语言:javascript
复制
# 列表视图
class PostListView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

# detail 视图
class PostDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

重构 view,重构 url

首先我们通过继承 ViewSet 来重构 view 类

代码语言:javascript
复制
class PostViewSet(viewset.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    # 推荐重写该方法,默认返回 status.HTTP_204_NO_CONTENT,
    # 会返回空信息,个人觉得不方便判断,当然按照个人喜好决定
    def destroy(self, request, *args, **kwargs):
        post = self.get_object()
        if post is not None:
            post.delete()
            return Response({"message": "delete succeed", "code": "200"}, 
                            status=status.HTTP_200_OK)
        return super(PostViewSet, self).destroy(self, request, *args, **kwargs)

然后我们通过 Routers 来重构 url

代码语言:javascript
复制
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'posts', view.PostViewSet)
router.register(r'post', view.PostViewSet)

# 然后我们需要在 project 下的 url 中去配置 router
from blog_api.urls import router as blog_api_router

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(blog_api_router.urls)),
]

真是人比人,气死人,代码居然到最后少了那么多,至于为什么,我们来看下 ModelViewSet 的源码吧(兄 dei 别排斥源码啊,这里真的很少很少的,但是又能让我们知道到底做了什么事)

代码语言:javascript
复制
class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

看到这是不是,觉得我们之前的优化都是一步接着一步来的,那我们继续看下 ModelMixin 的源码好了

代码语言:javascript
复制
class CreateModelMixin(object):
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        # 根据上传的参数,判断是否有效的 serializer
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 保存 serializer
        self.perform_create(serializer)
        # 返回响应头信息
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        # 分页
        page = self.paginate_queryset(queryset)
        if page is not None:
            # 返回该页的列表
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        # 没有分页则全部展示
        serializer = self.get_serializer(queryset, many=True)
        # 渲染 json 
        return Response(serializer.data)


class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        # 根据回传的 id 查找
        instance = self.get_object()
        # 将 instance 渲染成 json 展示
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        # 更新 instance 的值
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        # 保存更新后的数据
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        # 删除数据
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

其实内部的具体实现还是我们上一部分写的那些东西,接着,我觉得有必要把自己在 Android 端做的接口测试代码和运行结果贴出来,不然你们又会觉得我坑你们了......这边我为了偷懒(嗯对的,就是偷懒),我又写了一个只有单个字段的 model

Android 端 api

获取列表

获取列表结果

新建数据

新建数据返回结果

获取详情

获取详情返回结果

更新详情

更新详情返回结果

删除数据

删除数据返回结果

有坑!

在结束文章的最后,记录自己写的时候遇到的一个坑,当更新 ManyToMany 字段的时候,我们需要重新写 post 方法,直接传 id 是不能更新的,直接传 id 是不能更新的,直接传 id 是不能更新的(三遍三遍三遍)。

代码语言:javascript
复制
# 假设我们的 post 有一个 ManyToMany 字段 tags
class PostDetailView(APIView):
    
    # 更新的时候,需要约定好 ManyToMany 字段的 id 回传时候以什么方式间隔,例如我们用 "," 分隔
    def put(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post, data=request.data)
        if serializer.is_valid():
            # 记得先 clear,然后判断是否上传了 id,上传了 id 需要判断是否多个
            post.tags.clear()
            if request.data['tags']:
                if "," in request.data['tags']:
                    # 我们需要提取 request.data 中 tags 所对应的值,然后通过切割字符串取出 id
                    for i in request.data['tags'].split(","):
                        post.tags.add(i)
                else:
                    post.tags.add(request.data['tags'])
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

在 url 中还是之前的那样绑定

代码语言:javascript
复制
urlpatterns = [
    url(r"^post/(?P<pk>[0-9]+)/&", views.PostDetailView.as_view(), name="api_post"),
]

修改完后我们就可以开心的更新 M2M 字段了,httpie 命令行如下

代码语言:javascript
复制
http -a [username]:[password] PUT http://192.168.x.xxx:8080/api/post/9/ ...tags=1,2...

最后附上整个项目的地址:blog_project

https://github.com/kukyxs/blog_project

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码个蛋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档