学习参考:

【1】Django REST Framework教程(1): 为什么要学习DRF, 什么是序列化和RESTful的API

【2】Django REST Framework教程(2): 序列化器介绍及以博客为例开发基于函数视图的API

【3】阮一峰: RESTful API 设计指南

1. 数据的序列化

1.1 序列化(seralization)与反序列化

序列化:将属于自己语言的数据类型或对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)
反序列化:将请求的数据(如JSON格式的字符串)变为模型类对象

2. RESTful风格

REST是REpresentational State Transfer三个单词的缩写,由Roy Fielding于2000年论文中提出。简单来说,就是用URI表示资源,用HTTP方法(GET, POST, PUT, DELETE)表征对这些资源进行操作。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

REST的规范是资源的URI地址是固定不变的,对同一资源应使用不同的HTTP请求方法进行不同的操作,比如常见的增删查改。

[POST]    https://api.example.com/v1/users   // 新增
[GET]     https://api.example.com/v1/users/1 // 查询
[PATCH]   https://api.example.com/v1/users/1 // 更新
[PUT]     https://api.example.com/v1/users/1 // 覆盖,全部更新
[DELETE]  https://api.example.com/v1/users/1 // 删除

一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

协议、域名和版本
尽量使用https协议,使用专属域名来提供API服务,并在URL里标注api版本,如下所示

https://api.example.com/v1
https://www.example.com/api/v1 

有时候URL比较长,可能由多个单词组成,建议使用中划线"-“分割,而不是下划线”_“作为分隔符,另外每个url的结尾不能加斜线”/"。

过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。符合RESTful规范的API应该支持过滤。
下面是一些常见的过滤参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个姓名排序,以及排序顺序。
?user_type_id=1:指定筛选条件,比如用户类型

DRF与django-filter联用可以轻松实现过滤。

状态码(Status Codes)
服务器在处理客户端请求后还应向用户返回响应的状态码和提示信息,常见的有以下一些状态码。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误

3. Django REST Framework使用

Django REST framework (DRF)是基于Django实现的一个RESTful风格API框架,能够帮助我们快速开发RESTful风格的API。
文档地址如下:

以博客为例,使用DRF提供的序列化器(Serializers类)开发两个API接口并测试。
这两个API端点的简单描述如下所示:

接口描述:文章列表资源
GET请求获取文章列表资源, POST请求提交新文章
接口地址: http://127.0.0.1:8000/api/v1/articles
请求方式:GET, POST
返回参数:JSON格式文章列表和状态码

接口描述:单篇文章资源
GET获取文章详情, PUT修改,DELETE删除
接口地址: http://127.0.0.1:8000/api/v1/articles/{id}
请求方式:GET, PUT, DELETE
返回参数: GET和PUT(JSON格式文章详情和状态码), DELETE(状态码)

3.1 DRF的安装

pip install djangorestframework

将 rest_framework 注册到项目的INSTALL_APPS中

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'blog', # 应用名称为blog
]

3.2 自定义序列化器(serializers.py)

在后面的笔记中,我们会始终使用一个博客网站的数据结构作为例子。
首先创建模型:

简单地画下UML图:
在这里插入图片描述

用户(User)与文章(Article)是单对多的关系(ForeinKey),因为一个用户可以发表多篇文章.

# blog/models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model

User = get_user_model()

class Article(models.Model):
    """Article Model"""
    STATUS_CHOICES = (
        ('p', _('Published')),
        ('d', _('Draft')),
    )

    title = models.CharField(verbose_name=_('Title (*)'), max_length=90, db_index=True)
    body = models.TextField(verbose_name=_('Body'), blank=True)
    author = models.ForeignKey(User, verbose_name=_('Author'), on_delete=models.CASCADE, related_name='articles')
    status = models.CharField(_('Status (*)'), max_length=1, choices=STATUS_CHOICES, default='s', null=True, blank=True)
    create_date = models.DateTimeField(verbose_name=_('Create Date'), auto_now_add=True)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['-create_date']
        verbose_name = "Article"
        verbose_name_plural = "Articles"

利用DRF开发Web API的第一步总是自定义序列化器(serializers)。
序列化器的作用是将模型实例(比如用户、文章)序列化和反序列化为诸如json之类的表示形式。一个模型实例可能有许多字段属性,但一般情况下你不需要把所有字段信息以JSON格式数据返回给用户。序列化器定义了需要对一个模型实例的哪些字段进行序列化/反序列化, 并可对客户端发送过来的数据进行验证和存储。

REST framework提供了Serializer类和ModelSerializer类两种方式自定义序列化器。前者需手动指定需要序列化和反序列化的字段,后者根据模型(model)生成需要序列化和反序列化的字段,可以使代码更简洁。

3.2.1 使用Serializers类

注意author的定义,设为ReadOnlyField,我们希望自动使用登录用户的id为author进行文章的创建,不允许用户自己修改author字段。

# blog/serializers.py
# 使用Serializers类

from rest_framework import serializers
from .models import Article
from django.contrib.auth import get_user_model

User = get_user_model()

class ArticleSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=True, allow_blank=True, max_length=90)
    body = serializers.CharField(required=False, allow_blank=True)
    author = serializers.ReadOnlyField(source="author.id")
    status = serializers.ChoiceField(choices=Article.STATUS_CHOICES, default='p')
    create_date = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        """
        Create a new "article" instance
        """
        return Article.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Use validated data to return an existing `Article`instance。"""
        instance.title = validated_data.get('title', instance.title)
        instance.body = validated_data.get('body', instance.body)
        instance.status = validated_data.get('status', instance.status)
        instance.save()
        return instance

create()和update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。

注意:定义序列化器时一定要注明哪些是仅可读字段(read-only fields),哪些是普通字段。对于read-only fields,客户端是不需要也不能够通过POST或PUT请求提交相关数据进行反序列化的。

3.2.2 使用ModelSerializers类

使用ModelSerializer类可以重构我们的序列化器类,使整体代码更简洁。
注:由于author字段以User的主键为外键,所以再编写视图(views.py)时要特别注意

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

内嵌类Meta的三个属性介绍:

  • fields = ['name', 'address', 'books']或者fields='__all__'可以指定字段进行序列化、反序列化,以及连表查询时可以查询到的字段。
  • exclude = ['name']指查询的时候不包括该字段。
  • depth = 1 值代表深度次数
    深度查询指的是当一张表有关联的表时,在查询查自己的表时顺便将关联的表的内容也查出来,如果被深度查询的外键采用__all__,会将所关联表的所有字段都查出来。如果将深度值设置为2则将所关联表的其他关联的表也查出来,就这样一层一层深入,已经查过的表就不查了,所以不会出现死循环。

Python补充知识: __all__的使用方法
参考来源:https://www.cnblogs.com/ghylpb/p/12115512.html

在默认情况下,如果使用“from 模块名 import *”这样的语句来导入模块,程序会导入该模块中所有不以下画线开头的成员(包括变量、函数和类)。但在一些场景中,我们并不希望每个成员都被暴露出来供外界使用,此时可借助于模块的 __all__ 变量,将变量的值设置成一个列表,只有该列表中的成员才会被暴露出来。

'''测试__all__变量的模块'''
def hello(): 
    print("Hello, Python")
def world():
    print("Pyhton World is funny")
def test():    
    print('--test--')
# 定义__all__变量,指定默认只导入hello和world两个成员
__all__ = ['hello', 'world']

# 导入all_module模块内所有成员
from all_module import *
hello()
world()
test() # 会提示找不到test()函数

3.3 编写API视图(views.py)

3.3.1 基于函数的视图(Functional Based View, FBV)

# blog/views.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from .models import Article
from .serializers import ArticleSerializer

@api_view(['GET', 'POST'])
def article_list(request):

    """
    List all articles, or create a new article.
    """
    if request.method == 'GET':
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            # Very important. Associate request.user with 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)


@api_view(['GET', 'PUT', 'DELETE'])
def article_detail(request, pk):
    """
    Retrieve,update or delete an article instance。"""
    try:
        article = Article.objects.get(pk=pk)
    except Article.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = ArticleSerializer(article, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

由于序列化器中author是read-only字段,用户是无法通过POST提交来修改的,我们在创建Article实例时需手动将author和request.user绑定,如下所示:

serializer.save(author=request.user)

DRF提供的@api_view这个非常重要的装饰器,实现了以下几大功能:

  • 与Django传统函数视图相区分,强调这是API视图,并限定了可以接受的请求方法。
  • 拓展了django原来的request对象。新的request对象不仅仅支持request.POST提交的数据,还支持其它请求方式如PUT或PATCH等方式提交的数据,所有的数据都在request.data字典里。这对开发Web API非常有用。

不再显式地将请求或响应绑定到特定的内容类型比如HttpResponse和JSONResponse,我们统一使用Response方法返回响应,该方法支持内容协商,可根据客户端请求的内容类型返回不同的响应数据

3.3.2 基于类的视图(Class Based View, CBV)

使用类视图可以有效的提高代码复用,因为类是可以被继承的,可以拓展的。特别是将一些可以共用的功能抽象成Mixin类或基类后可以减少重复造轮子的工作。

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

  • 使用基础APIView类
  • 使用Mixins类和GenericAPI类混配
  • 使用通用视图generics.*类, 比如 generics.ListCreateAPIView
  • 使用视图集ViewSet和ModelViewSet

具体见Django后端开发学习笔记(5)Django REST Framework基于类的视图

3.4 给网址添加可选的格式后缀(urls.py)

Web API 的一个常见模式是在 url 上使用文件扩展名来为给定的媒体类型提供端点,例如 http://example.com/api/users.json 来提供 JSON 表示。为了让响应不再与单一内容类型连接,我们可以为API路径添加对格式后缀的支持。使用格式后缀给我们明确指定了给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的URL。

使用format_suffix_patterns时,必须确保将 ‘format’ 关键字参数添加到相应的视图中。

def article_list(request, format=None):
	# do something

更新blog/urls.py文件,给现有的URL后面添加一组format_suffix_patterns。

from django.urls import re_path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None)
# 返回 URL 模式列表
# 其中包括附加到所提供的每个 URL 模式的格式后缀模式
# urlpatterns:必需, URL 模式列表
# suffix_required:可选布尔值, 指示 URL 中的后缀是可选或必需, 默认为False 表示默认后缀为可选
# -allowed:可选, 有效格式后缀的列表或元组, 如果未提供将使用通配符格式的后缀模式

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

urlpatterns = format_suffix_patterns(urlpatterns)

DRF默认是以可浏览的api形式展示返回响应结果的(articles.api),如果你只需要返回最简单的json格式的数据,只需要在访问地址后面加上.json后缀即可(articles.json),如下所示:
在这里插入图片描述注意,不能与routers混用,会报重复定义的错误,目测rest_framework.routers默认实现了这个功能。

小结
(1)定义序列化器时一定要注意区分read-only字段和常规字段,read-only字段通常对应用户不能自己操作(添加或修改)的数据。
(2)DRF中使用函数开发API视图千万别忘了使用@api_view这个重要的装饰器。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐