drf 接口开发框架

Django REST Framework(以下简称drf)是Django框架中,用来开发Web接口的一个插件框架。

https://www.django-rest-framework.org/

drf简介

通过前面学习我们知道,Django的一个缺点就是前后端高度耦合,甚至出现了form组件这种邪路,做些个人信息展示网站之类的站点确实方便,但这已经无法满足现代较大型应用系统的建设需求了。

现在前端的发展非常迅速,早已经不是撸几个静态页,每个页面写千十来行难以维护的JQuery代码的时代了,前端项目的工程化和用户体验也得到了极大提升,现在后端更应把精力放在接口和业务逻辑上。Django本体在这方面并没有多考虑,因此直接使用Django编写Web接口,需要写很多非业务功能性的重复代码,浪费时间,drf就是为了解决这个问题而设计的。

drf的主要功能

drf封装了很多常用功能,便于我们开发接口:

  • 便于使用的请求和响应组件
  • Serializer序列化器
  • 接口认证和权限控制组件
  • 基于CRUD封装的组合视图、Mixins(一些可组合的CRUD功能)

除此以外还有很多实用的特性。

配置drf框架

Django默认没有内置drf框架,我们需要手动安装相关依赖。

pip install djangorestframework

然后在settings.py中,加入相关配置。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    ...
]

drf基础用法

使用drf响应JSON数据

这里我们编写一个简单的例子工程books,使用drf的序列化器和响应组件,返回一组JSON数据。

下面例子中,我们有三个Model,书籍(Book)、分类(Category)、作者(Author)。

模型代码就不再多说了,这里我们关注serializers.py,它是我们定义drf序列化器的文件。

serializers.py

from rest_framework import serializers
from books.models import *


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['pk', 'name']


class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['pk', 'name', 'gender']


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['pk', 'name', 'isbn', 'publish_time', 'category', 'authors']

里面内容其实很简单,就是模型配置到序列化器上。这里我们使用的是ModelSerializer,它是一个相对比较通用的序列化器,能够直接将指定的模型字段映射为JSON。

views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from books.models import *
from books.serializers import *


@api_view(['GET'])
def get_book_list(request):
    book_list = Book.objects.all()
    rsp_data = BookSerializer(book_list, many=True)
    return Response({
        'rspCode': 0,
        'rspMsg': '查询成功',
        'data': rsp_data.data
    }, status=200)

urls.py

urlpatterns = [
    ...
    path('books/', views.get_book_list),
    ...
]

views.py中,我们编写了一个Function based view,注意@api_view这个装饰器,它是drf提供的,用于处理drf专用请求响应组件,使用drf时我们需要带上这个装饰器(注:还有一种Class based view的写法,可以参考文档)。

代码逻辑很简单,我们查询出结果列表后,将其传入序列化器。many=True这个关键字参数意味着返回的结果是多个而不是一个。

最后返回一个drf提供的Response对象,其中第一个参数是一个字典类型,代表返回的JSON数据,其中rsp_data.data是序列化结果的字典类型数据。Response中比较常用的关键字参数status,是我们可以自定义的HTTP响应码。

用浏览器直接访问接口http://localhost:8000/books,会返回一个调试页面。

下面我们再编写一个根据ID查询数据的接口。

views.py

@api_view(['GET'])
def get_book_by_pk(request, pk):
    book_list = Book.objects.filter(pk=pk)
    if len(book_list) != 0:
        rsp_data = BookSerializer(book_list[0])
        return Response({
            'rspCode': 0,
            'rspMsg': '查询成功',
            'data': rsp_data.data
        })
    else:
        return Response({
            'rspCode': 1,
            'rspMsg': '查询无结果',
            'data': None
        }, status=404)

urls.py

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

上面代码中,我们通过一个re_path指定了一个带路径参数的路由,在views.py中,我们通过路径参数传入的主键进行查询,查询结果通过drf的序列化器和Response组件返回。如果查询无结果,则返回空时对应的内容,这里我们还设置了HTTP响应码为404

封装Response

实际开发中,我们推荐返回这种格式的内容(不推荐用HTTP自带的响应状态和响应码):

{
    'rspCode': 0,
    'rspMsg': '操作成功',
    'data': [...]
}

drf中,我们可以继承自带的Response封装我们自己的响应组件,下面是一个例子。

from rest_framework.response import Response


class APIResponse(Response):
    """定制Response"""

    def __init__(self, data=None, api_code='0', api_msg='操作成功',
                 status=None, template_name=None, headers=None,
                 exception=False, content_type=None):
        api_data = {
            'rspCode': api_code,
            'rspMsg': api_msg,
            'data': data
        }
        super().__init__(api_data, status,
                         template_name, headers,
                         exception, content_type)

    @staticmethod
    def success(data=None, status=None, template_name=None, headers=None,
                exception=False, content_type=None):
        return APIResponse(data, '0', '操作成功', status, template_name,
                           headers, exception,
                           content_type)

    @staticmethod
    def failure(data=None, api_code='5000', api_msg='操作失败',
                status=None, template_name=None, headers=None,
                exception=False, content_type=None):
        return APIResponse(data, api_code, api_msg,
                           status, template_name,
                           headers, exception,
                           content_type)

这里我们增加了两个参数api_codeapi_msg,用于指定接口响应码和响应信息。

使用drf读取请求内容

之前我们看到drf对响应做了一个叫Response的封装,其实drf对请求也做了封装,使用非常简单。

读取请求参数

下面例子中,我们改造了前面查询所有书籍列表的功能,加入了一个可选参数category_id,传入该参数,则查询该分类下的书籍,不传则查询全部。

@api_view(['GET'])
def get_book_list(request):
    params = request.query_params
    category_id = params.get('category_id')
    if category_id is not None:
        # 存在分类参数,查询该分类的书籍
        category_list = Category.objects.filter(pk=category_id)
        if len(category_list) != 0:
            category = category_list[0]
            book_list = category.book_set.all()
        else:
            return Response({
                'rspCode': 1,
                'rspMsg': '查询无结果',
                'data': None
            })
    else:
        # 不存在分类参数,查询全部书籍
        book_list = Book.objects.all()

    rsp_data = BookSerializer(book_list, many=True)
    return Response({
        'rspCode': 0,
        'rspMsg': '查询成功',
        'data': rsp_data.data
    }, status=200)

drf对请求对象做了封装,使用request.query_params能够得到请求参数的字典表示。

读取请求JSON

下面例子我们实现添加一个书籍,这里我们采用POST请求,请求体的书籍信息存储在POST请求体中。

@api_view(['POST'])
def add_book(request):
    req_data = request.data
    category_id = req_data.get('category_id')
    category = Category.objects.get(pk=category_id)
    authors_id = req_data.get('authors_id')

    book = Book(name=req_data.get('name'), isbn=req_data.get('isbn'), publish_time=now(), category=category)
    book.save()

    for author_id in authors_id:
        author = Author.objects.get(pk=author_id)
        book.authors.add(author)
    book.save()

    return Response({
        'rspCode': 0,
        'rspMsg': '添加成功',
        'data': None
    })

通过drf提供的request.data,我们可以很容易获得请求体。取出的内容是JSON映射的字典类型,我们的请求信息中其实还包含了一个数组,也是能够正确解析的。

注:出于篇幅考虑,上面代码省去了一些必要的校验,实际开发中应该对请求字段做校验,一些关联模型是否存在也需要校验并实现相关操作。

由于是POST请求,我们可以通过Postman工具进行测试:

请求体如下。

{
    "name":"testbook",
    "isbn":"66666666",
    "category_id":1,
    "authors_id": [1,2]
}

序列化组件

ModelSerializer

之前我们代码中,使用的都是这个ModelSerializer,一般情况下,这个内置序列化器能够满足我们绝大部分需求,这里我们再详细介绍一下。

简单使用

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['pk', 'name', 'gender']

最简单的配置ModelSerializer写法如上述代码,我们配置好关联模型,以及显示的字段即可。

嵌套序列化

有时候我们需要嵌套序列化,前面我们的Book模型会返回关联的Category和Author,但仅仅返回了主键,如果要嵌套返回完整的内容,则需要将序列化器嵌套定义。

class BookSerializer(serializers.ModelSerializer):

    authors_set = AuthorSerializer(source='authors', many=True)

    class Meta:
        model = Book
        fields = ['pk', 'name', 'isbn', 'publish_time', 'category', 'authors', 'authors_set']

上面代码中,我们在BookSerializer中嵌套了一个author_set字段,它是通过序列化器AuthorSerializer实现的。注意其中的几个关键字参数,source表示模型Book中对应的字段,many指定其中内容是多个(这里我们Book和Author是多对多定义的)。

增加自定义字段

有时候我们需要在返回信息中增加原本模型没有的字段,这可以通过SerializerMethodField实现。

class BookSerializer(serializers.ModelSerializer):

    authors_cnt = serializers.SerializerMethodField()

    class Meta:
        model = Book
        fields = ['pk', 'name', 'isbn', 'publish_time', 'category', 'authors', 'authors_cnt']

    def get_authors_cnt(self, obj):
        return obj.authors.count()

上面代码中,我们新增了一个authors_cnt字段,表示该Book的作者数,但显然数据模型中没有这个字段,我们是通过get_authors_cnt函数中的逻辑返回的。

自定义序列化器

实际上,我们可以自定义序列化器。drf中,序列化器的基础类是Serializer,它也是ModelSerializer的父类。

我们可以看一下Serializer的构造函数(来自BaseSerializer):

def __init__(self, instance=None, data=empty, **kwargs)
  • instance:序列化时传入Django模型实例
  • data:用于反序列化
  • context:可用于传递额外数据

下面例子中,我们通过自定义序列化器对模型Author进行序列化:

serializers.py

class AuthorSerializer2(serializers.Serializer):
    name = serializers.CharField()
    gender = serializers.SerializerMethodField()

    def get_gender(self, obj):
        return obj.get_gender_display()

views.py

@api_view(['GET'])
def get_author_list(request):
    authors = Author.objects.all()
    resp_data = AuthorSerializer2(instance=authors, many=True)
    return Response({
        'rspCode': 0,
        'rspMsg': '查询成功',
        'data': resp_data.data
    })

和直接使用ModelSerializer相比,我们手动定义了几个模型原有的字段,同时使用SerializerMethodField定义了一个定制化的字段。

实际上,ModelSerializer就是通过指定的fields属性,自动包装了一些字段,因此我们通常都是使用ModelSerializer,这样更加方便一些。

认证和权限组件

drf封装了一些权限组件(基于auth),我们开发中无需编写检查权限的代码,配置好相关组件即可。

例子代码:

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated


@api_view(['POST'])
@permission_classes([IsAuthenticated])
def query_data(request):
    # ...

上面代码中,我们使用permission_classes装饰器配置了一个一个IsAuthenticated,未认证的匿名用户请求这个接口,就会返回HTTP 403 Forbidden

drf封装了很多权限组件,比如检查是否认证、是否为管理员(is_staff)等,可以在参考中文档查阅。

限流组件

drf的限流(throttle)组件能够限制IP或用户在特定时间段内的访问次数。

全局配置中,我们可以指定throttle class,以及对应的默认最大频率限制。下面我们为全局配置了AnonRateThrottle,并限制最大请求频率为每分钟一次。

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '1/minute'
    }
}

请求频率,可以通过secondhourminuteday指定。

GenericAPIView和Mixins

这部分由于封装耦合度过高,在实际开发中,其实很难用起来,就不再介绍了。

RESTful是个理想状态下的设计,并不是完全切合实际需求的,我们的实际项目需要考虑很多额外的东西,这种情况下drf中这些高度封装的组件可扩展性就不够理想了。

关于CamelCase问题

在实际使用DRF开发中,其实有一个问题:Python的代码规范是Snake case的,而我们主流的前后端语言,如JavaScript、Java等一般是Camel case的,使用HTTP协议与其它系统收发Json报文时,DRF默认生成的Json键也是小写下划线的形式,无论改哪一边,对代码风格破坏都比较大。

自定义DRF的ParserRenderer组件能够统一解决这个问题,实际上有现成的轮子可用,我们不必再自己写了:https://github.com/vbabiy/djangorestframework-camel-case

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。