作者:HelloGitHub-追梦人物

文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库

此前在讨论基于模板引擎的开发方式和 django-rest-framework 开发的异同时说过,django-rest-framework 开发和传统的开发方式没有什么不同,区别仅在于返回的数据格式不同而已。

在基于模板引擎的开发方式中,博客首页文章列表的视图函数可能是这样的:

from django.shortcuts import render
from .models import Post def index(request):
post_list = Post.objects.all().order_by('-created_time')
return render(request, 'blog/index.html', context={'post_list': post_list})

在 django-rest-framework,代码逻辑是一样的,只是在最后返回结果时,返回资源序列化后的结果。

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status from .models import Post
from .serializers import PostListSerializer @api_view(http_method_names=["GET"])
def index(request):
post_list = Post.objects.all().order_by('-created_time')
serializer = PostListSerializer(post_list, many=True)
return Response(serializer.data, status=status.200)

暂且忽略掉资源序列化器 PostListSerializer,我们接下来会实现它,先把注意力放在主体逻辑上。

首先,我们从 rest_framework.decorators 中导入了 api_view 装饰器,并用它装饰了 index 视图函数,使其成为一个 RESTful API 视图函数。

为什么需要这个视图函数装饰器呢?之前说过,django-rest-framework 为 API 的开发提供了丰富的功能,包括内容协商、认证和鉴权、限流等等。这些过程 django 默认的视图函数在处理 HTTP 请求时是没有提供的,而经过 api_view 装饰后的视图,则提供了上述全部功能。

不过我们这里并没有看到任何内容协商、认证和鉴权、限流代码逻辑和配置,这是为什么呢?原因隐藏在 Python 的装饰器魔法里,django-rest-framework 对于上述功能有一套默认的处理逻辑,因此我们不需要进行任何配置,仅需使用 api_view 装饰一个 django 视图函数,所有功能全部自动开启。

视图函数里我们先从数据库获取文章列表资源,然后使用序列化器对其进行序列化,序列化后的数据存在 data 属性里,我们把它传递给 HTTP 响应类 Response,并将这个响应返回。

注意这个 Response 是从 rest_framework.response 中导入的,它类似于 django 的 HTTPResponse 响应类。实际上,这个类是 django-rest-framework 对 django 的模板响应类(SimpleTemplateResponse)的拓展(具体的细节可以不用了解,只要知道 django 使用它来渲染模板并构造 HTTP 响应即可),通常在 RESTful API 的视图函数中我们都会返回这个类,而不是 django 的 HTTP 响应类。此外,通过传入 status 参数,指定 HTTP 响应的状态码。

小贴士

请了解常用的 HTTP 状态码。在 RESTful 架构中,客户端通过 HTTP 请求动词表征对资源的操作意图,而服务端则使用 HTTP 状态码表示资源操作的结果。常用状态码及其含义如下:

200:通常表示请求成功。

201:表示资源创建成功。

400:表示客户端请求错误。

401:没有提供身份认证信息

403:没有操作权限

404 :访问的资源不存在

405:不支持的 HTTP 请求方法

500:服务器内部错误

HTTP 请求和响应过程,django-rest-framework 已经帮我们处理。但是资源的序列化,框架是无法自动化完成的,框架提供了基本的序列化器,我们需要自定义序列化逻辑。所以,让我们来定义 PostListSerializer 序列化器,用它来序列化文章列表。

序列化器由一系列的序列化字段(Field)组成,序列化字段的作用是,在序列化资源时,将 Python 数据类型转为原始数据类型(通常为字符类型或者二进制类型),以便在客户端和服务端之间传递;反序列化时,将原始数据类型转为 Python 数据类型。在转换过程中,还会进行数据合法性的校验。

先来看一个简单的例子(摘自 django-rest-framework 官网示例),理解序列化器的工作原理和功能。假设我们有一个 Python 类 Comment

from datetime import datetime

class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
self.content = content
self.created = created or datetime.now() comment = Comment(email='leila@example.com', content='foo bar')

根据 Comment 类 3 个属性的类型,定义一个序列化器,用于数据序列化和反序列化。我们在上一步教程的 交流的桥梁:评论功能 中介绍过表单(Form)的定义。实际上,django-rest-framework 序列化器的设计参考了 django 表单的设计。序列化器和表单也有很多相似功能,比如对输入数据进行校验等。序列化器的代码如下:

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()

自定义的序列化器都要继承 serializers.Serializer 基类,基类提供了数据序列化和反序列化的逻辑。根据被序列化对象的属性的数据类型,需要指定相应的序列化字段(Serializer Field)。django-rest-framework 提供了很多常用的序列化字段,例如本例中用于序列化 email 数据格式的 EmailField,用于序列化字符型数据格式的 CharField,用于序列化日期格式的 DateTimeField。在实际项目中,应该根据数据类型,选择合适的序列化字段。全部序列化字段,可以参考官方文档 Serializer fields

有了序列化器,就可以将 Comment 对象序列化了,序列化器用法如下:

>>> serializer = CommentSerializer(comment)
>>> serializer.data
# 输出:
{'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

首先将需要序列化的对象(comment)传入序列化器(CommentSerializer),构造一个序列化器对象(serializer),访问序列化器对象的 data 属性,就可以得到序列化后的数据。

被序列化对象序列化后的数据是一个扁平的 Python 字典,字典中的数据描述了这个对象资源。有了序列化生成的 Python 字典,我们就可以将字典数据进一步格式化为 JSON 字符串或者 XML 文档字符串,在客户端和服务端之间传输。试想,客户端服务端通常都通过 HTTP 协议传输数据,传输的数据只能是字符串或者二进制数据,不可能将一个 Python 的对象直接传递,这就是为什么要序列化的原因。一端接收到序列化的数据后,如果有需要,可以对数据进行反序列化,重新恢复为 Python 对象。

以上就是一个标准序列化器的定义。其关键点在于,根据被序列化对象属性的数据类型,选择合适的序列化字段。回顾我们在上一步教程的 交流的桥梁:评论功能 中对评论表单的定义,我们通过继承 ModelForm 定义了表单,而并没有显示地指定表单字段的类型。原因在于,对于 django 中的模型(Model),已经有了定义其数据类型的模型字段,因此 django 表单可以根据关联的模型,自动推测需要使用的表单字段,在背后帮我们完成表单字段的选择,简化了表单的定义。

和表单类似,django-rest-framework 的序列化器也可以根据关联的模型,自动检测被序列化模型各个属性的数据类型,推测需要使用的序列化字段,无需我们显示定义。此时,自定义的序列化器不再继承标准的 Serializer,而是继承其子类,ModelSerializer

我们来编写文章(Post)模型的序列化器代码。按照习惯,序列化器的代码位于相应应用的 serializers.py 模块中,因此在 blog 应用下新建一个 serializers.py 文件,写上如下代码:

from rest_framework import serializers
from .models import Post class PostListSerializer(serializers.ModelSerializer):
category = CategorySerializer()
author = UserSerializer() class Meta:
model = Post
fields = [
'id',
'title',
'created_time',
'excerpt',
'category',
'author',
'views',
]

使用 ModelSerializer 时,只需要在序列化器的内部类 Meta 中指定关联的模型,以及需要序列化的模型属性,django-rest-framework 就会根据各个属性的数据类型,自动推测需要使用的系列化字段,从而生成标准的序列化器。事实上,我们可以来看一下 django-rest-framework 最终生成的序列化器长什么样子:

class PostListSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(label='标题', max_length=70)
created_time = DateTimeField(label='创建时间', required=False)
excerpt = CharField(allow_blank=True, label='摘要', max_length=200, required=False)
category = CategorySerializer()
author = UserSerializer()

还需要注意一点,titlecreated_timeviews 这些属性都是原始的数据类型(字符型、日期型、整数类型)。而对于文章关联的 categoryauthor,它们本身也是一个对象,django-rest-framework 就无法推测该使用什么类型的系列化字段来序列化它们了。所以这里我们按照标准序列化器的定义方式,将这两个属性的系列化字段分别定义为 CategorySerializerUserSerializer,意思是告诉 django-rest-framework,请使用 CategorySerializerUserSerializer 来序列化关联的 categoryauthor。实际上,序列化器本身也是一个序列化字段。当然,CategorySerializerUserSerializer 目前还不存在,我们来定义他们:

from django.contrib.auth.models import User
from .models import Category, Post class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'id',
'name',
] class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'username',
] class PostListSerializer(serializers.ModelSerializer):
# ...

再来回顾一下我们的 API 视图函数代码:

@api_view(http_method_names=["GET"])
def index(request):
post_list = Post.objects.all().order_by('-created_time')
serializer = PostListSerializer(post_list, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

注意这里 PostListSerializer 的用法,构造序列化器时可以传入单个对象,序列化器会将其序列化为一个字典;也可以传入包含多个对象的可迭代类型(这里的 post_list 是一个 django 的 QuerySet),此时需要设置 many 参数为 True 序列化器会依次序列化每一项,返回一个列表。

api_view 装饰器传入 http_method_names 参数指定允许访问该 API 视图的 HTTP 方法。

现在我们已经有了视图函数,最后,我们需要给这个视图函数绑定 URL,在 blog 应用下的 urls.py 中加入绑定的代码:

path('api/index/', views.index)

启动开发服务器,打开浏览器访问 http://127.0.0.1:8000/api/index/ ,可以看到接口返回了文章列表 JSON 格式的数据(默认为 JSON)。

目前来说,这个接口其实作用不大。不过在后续的教程中,我们学习前端框架 Vue,那个时候,RESTful API 就有了它的用武之地了。

回顾一下 index API 视图函数的基本逻辑:

  1. 从数据库取数据
  2. 构造序列化器并将取出的数据序列化
  3. 返回响应

这其实是访问序列型的资源比较常见的逻辑,我们知道,django 专门为这种在 Web 开发中常用的逻辑提供了一系列基于类的通用视图,以提高代码的复用性和减少代码量。只是 django 的通用视图适用于基于模板引擎的开发方式,同样的,django-rest-framework 也提供了专门针对 RESTful API 开发过程中常用逻辑的类视图通用函数。接下来,让我们使用 django-rest-framework 提供的通用类视图,将首页 API 的视图函数改为类视图。


关注公众号加入交流群

第 3 篇:实现博客首页文章列表 API的更多相关文章

  1. 【Android 我的博客APP】1.抓取博客首页文章列表内容——网页数据抓取

    打算做个自己在博客园的博客APP,首先要能访问首页获取数据获取首页的文章列表,第一步抓取博客首页文章列表内容的功能已实现,在小米2S上的效果图如下: 思路是:通过编写的工具类访问网页,获取页面源代码, ...

  2. Python实现抓取CSDN博客首页文章列表

    1.使用工具: Python3.5 BeautifulSoup 2.抓取网站: csdn首页文章列表 http://blog.csdn.net/ 3.分析网站文章列表代码: 4.实现抓取代码: __a ...

  3. [js高手之路]Node.js实现简易的爬虫-抓取博客所有文章列表信息

    抓取目标:就是我自己的博客:http://www.cnblogs.com/ghostwu/ 需要实现的功能: 抓取博客所有的文章标题,超链接,文章摘要,发布时间 需要用到的库: node.js自带的h ...

  4. Typecho博客让文章列表页只显示摘要的方法

    在当前主题的 index.php 文件中找到代码 <?php $this->content('阅读剩余部分...'); ?> 将其替换为 <?php $this->exc ...

  5. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十七 ║Vue基础:使用Vue.js 来画博客首页+指令(一)

    缘起 书说前两篇文章<十五 ║ Vue前篇:JS对象&字面量&this>和 <十六 ║ Vue前篇:ES6初体验 & 模块化编程>,已经通过对js面向对 ...

  6. 巨高兴,偶的文章 “如何在服务器上配置ODBC来访问本机DB2for Windows服务器”被推荐至CSDN博客首页

    非常高兴,偶的文章 "如何在服务器上配置ODBC来访问本机DB2for Windows服务器"被推荐至CSDN博客首页,截图留念.                  文章被推荐在C ...

  7. 如何写出高质量的技术博客 这边文章出自http://www.jianshu.com/p/ae9ab21a5730 觉得不错直接拿过来了 好东西要大家分享嘛

        如何写出高质量的技术博客?答案是:如果你想,就一定能写出高质量的技术博客.看起来很唯心,但这就是事实.有足够愿力去做一件目标明确,有良好反馈系统的事情往往很简单.就是不停地训练,慢慢地,你自己 ...

  8. 使用django开发博客过程记录2——博客首页及博客详情的实现

    1.什么是CBV(Class-based views) 2.博客首页及博客详情实现 1.什么是CBV 什么是CBV?说白了就是以前是视图为处理请求返回响应的函数,有了cbv之后我们就可以用类处理请求和 ...

  9. Python爬虫小实践:爬取任意CSDN博客所有文章的文字内容(或可改写为保存其他的元素),间接增加博客访问量

    Python并不是我的主业,当初学Python主要是为了学爬虫,以为自己觉得能够从网上爬东西是一件非常神奇又是一件非常有用的事情,因为我们可以获取一些方面的数据或者其他的东西,反正各有用处. 这两天闲 ...

随机推荐

  1. B. The Monster and the Squirrel

    B. The Monster and the Squirrel Ari the monster always wakes up very early with the first ray of the ...

  2. 如何使用Rancher在OpenStack上创建K8S集群

    不可否认的是,OpenStack仍然是可行的云操作系统,并且被全世界许多互联服务提供商使用.而Rancher是业界最为广泛使用的Kubernetes管理平台,通过简洁直观的GUI集中管理企业IT中的多 ...

  3. 使用onclick/表单submit跳转到其他页面

    使用onclick 如果是本页显示可以直接用location,方法如下: - onclick="javascript:window.location.href='URL'" - o ...

  4. find的基本查询命令《一》

    一. find的基本查询命令 find命令最常用的是查找某个文件,如: find ./ -name "abc.txt" 则会在当前目录及子目录下查找abc.txt文件 更常用的是查 ...

  5. 典型的MVC架构图

    通常,当使用MVC时,应用程序中的每个逻辑部分都有一个单一的控制器.在这些控制器的前面还有一个Router:这是一个看门人,用于确定用户请求的内容,以便应用程序满足用户需要.常用php的mvc框架:Y ...

  6. Linux下修改efi启动项

    Linux下有一个efibootmgr工具可以编辑efi启动项,十分方便,简单介绍如下 直接运行efibootmgr会显示出当前所有efi启动项,每个启动项前都有相应编号, 可以使用efibootmg ...

  7. 计算机网络协议,PPP协议分析

    一.基本特点 1.PPP协议是计算机网络体系中第二层(数据链路层)的协议 2.PPP帧格式是以HDLC帧格式为基础,做了很少的改动(区别:PPP是面向字符的,而HDLC是面向位的) 3.PPP协议使用 ...

  8. Java第二十四天,线程安全

    线程安全 1.定义 多线程访问共享数据,会产生线程安全问题. 2.代码模拟 卖票Ticked类: package com.lanyue.day22; public class Person { pub ...

  9. C语言学生管理系统完善版

    #include<stdio.h>#include<string.h>#include <stdlib.h>#define M 100struct score    ...

  10. openpyxl 模块 读写Excel

    import openpyxl #写到execl中def write_execl(): book=openpyxl.Workbook() sheet=book.active #获取默认sheet # ...