利用 Django REST framework 编写 RESTful API

Updateat 2015/12/3: 增加 filter

最近在玩 Django,不得不说 rest_framework 真乃一大神器,可以轻易的甚至自动化的搞定很多事情,比如:

  • 自动生成符合 RESTful 规范的 API

    • 支持 OPTION、HEAD、POST、GET、PATCH、PUT、DELETE
    • 根据 Content-Type 来动态的返回数据类型(如 text、json)
  • 生成 browserable 的交互页面(自动为 API 生成非常友好的浏览器页面)
  • 非常细粒度的权限管理(可以细粒度到 field 级别)

示意图


安装

$ pip install djangorestframework
$ pip install markdown

概述

Django Rest framework 的流程大概是这样的

  1. 建立 Models
  2. 依靠 Serialiers 将数据库取出的数据 Parse 为 API 的数据(可用于返回给客户端,也可用于浏览器显示)
  3. ViewSet 是一个 views 的集合,根据客户端的请求(GET、POST等),返回 Serialiers 处理的数据
    • 权限 Premissions 也在这一步做处理
  4. ViewSet 可在 Routers 进行注册,注册后会显示在 Api Root 页上
  5. 在 urls 里注册 ViewSet 生成的 view,指定监听的 url

希望全面细致了解的人请移步去看官方文档,我这里就不一步步的细说了,而是分块来进行介绍


准备工作 & Models

让我们来写个小项目练练手

  1. 先用 manage.py startproject rest 来生成一个项目
  2. 再用 manage.py createsuperuser 创建用户(后面权限管理会用到)
  3. 初始化数据库 manage.py migrate

然后当然是编写 models,为了展示 rest_framework 的强大之处,我给 models 定义了一个自定义的 field

# myproject/myapp/models.py

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import
import cPickle as pickle from django.db import models
from django.contrib.auth.models import User class SerializedField(models.TextField): """序列化域
用 pickle 来实现存储 Python 对象
"""
__metaclass__ = models.SubfieldBase # 必须指定该 metaclass 才能使用 to_python def validate(self, val):
raise isinstance(val, basestring) def to_python(self, val):
"""从数据库中取出字符串,解析为 python 对象"""
if val and isinstance(val, unicode):
return pickle.loads(val.encode('utf-8')) return val def get_prep_value(self, val):
"""将 python object 存入数据库"""
return pickle.dumps(val) class MyModel(models.Model): created_at = models.DateTimeField(auto_now_add=True)
# 注意这里建立了一个外键
owner = models.ForeignKey(User, related_name='mymodels')
field = models.CharField(max_length=100)
options = SerializedField(max_length=1000, default={})

Serializers

定义好了 Models,我们可以开始写 Serializers,这个相当于 Django 的 Form

# myproject/myapp/serializers.py

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import
import json from django.contrib.auth.models import User
from rest_framework import serializers from ..models import MyModel
from .fields import MyCustField class MyCustField(serializers.CharField):
"""为 Model 中的自定义域额外写的自定义 Serializer Field""" def to_representation(self, obj):
"""将从 Model 取出的数据 parse 给 Api"""
return obj def to_internal_value(self, data):
"""将客户端传来的 json 数据 parse 给 Model"""
return json.loads(data.encode('utf-8')) class UserSerializer(serializers.ModelSerializer): class Meta:
model = User # 定义关联的 Model
fields = ('id', 'username', 'mymodels') # 指定返回的 fields # 这句话的作用是为 MyModel 中的外键建立超链接,依赖于 urls 中的 name 参数
# 不想要这个功能的话完全可以注释掉
mymodels = serializers.HyperlinkedRelatedField(
many=True, queryset=MyModel.objects.all(),
view_name='model-detail'
) class MySerializer(serializers.ModelSerializer): options = MyCustField(
max_length=1000, style={'base_template': 'textarea.html'},
) class Meta:
model = MyModel
fields = ('id', 'owner', 'field', 'options')
read_only_fields = ('owner',) # 指定只读的 field def create(self, validated_data):
"""响应 POST 请求"""
# 自动为用户提交的 model 添加 owner
validated_data['owner'] = self.context['request'].user
return MyModel.objects.create(**validated_data) def update(self, instance, validated_data):
"""响应 PUT 请求"""
instance.field = validated_data.get('field', instance.field)
instance.save()
return instance

ViewSet

定义好了 Serializers,就可以开始写 viewset 了

其实 viewset 反而是最简单的部分,rest_framework 原生提供了四种 ViewSet

  • ViewSet
  • GenericViewSet
    • 继承于 GenericAPIView
  • ModelViewSet
    • 自身提供了六种方法
    • list
    • create
    • retrieve
    • update
    • partial_update
    • destroy
  • ReadOnlyModelViewSet

我比较喜欢用 ModelViewSet,然后再用 Premissions 来管理权限

# myproject/myapp/views.py

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import from django.contrib.auth.models import User
from rest_framework import permissions, viewsets, renderers
from rest_framework.decorators import (
permission_classes, detail_route
)
from rest_framework.response import Response from .serializers import MySerializer, UserSerializer
from .models import MyModel class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# 指定权限,下面马上讲到
permission_classes = (permissions.IsAuthenticated,) class ModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
def plaintext(self, request, *args, **kwargs):
"""自定义 Api 方法"""
model = self.get_object()
return Response(repr(model))

我在 ModelViewSet 中自定义了方法 plaintext,rest_framework 中对于自定义的 viewset 方法提供了两种装饰器

  • list_route
  • detail_route

区别就是 list_route 的参数不包含 pk(对应 list),而 detail_route 包含pk(对应 retrieve)

看一段代码就懂了

@list_route(methods=['post', 'delete'])
def custom_handler(self, request):
pass @detail_route(methods=['get'])
def custom_handler(self, request, pk=None):
pass

Filters

前面根据 serializers 和 viewset 我们已经可以很好的提供数据接口和展示了。但是有时候我们需要通过 url参数 来对数据进行一些排序或过滤的操作,为此,rest-framwork 提供了 filters 来满足这一需求。

全局filter

可以在 settings 里指定应用到全局的 filter:

REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
}

viewset 的 filter

也可以为 viewset 分别指定 filter,方法就是在定义 viewset 的时候定义一个名为filter_backend 的类变量:

class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer = UserSerializer
filter_backends = (filters.DjangoFilterBackend,)

默认的 filter

rest-framework 提供了几个原生的 filter:

  • SearchFilter
filter_backends = (filters.SearchFilter,)
search_fields = ('username', 'email') # 指定搜索的域

请求 http://example.com/api/users?search=russell

  • OrderingFilter
filter_backends = (filters.OrderingFilter,)
ordering_fields = ('username', 'email')

请求 http://example.com/api/users?ordering=account,-username

自定义 filter

自定义 filter 非常简单,只需要定义 filter_queryset(self, request, queryset, view) 方法,并返回一个 queryset 即可。

直接贴一个我写的例子:

class NodenameFilter(filters.BaseFilterBackend):

    """根据 nodename 来删选
[nodename]: NeiWang
""" def filter_queryset(self, request, queryset, view):
nodename = request.QUERY_PARAMS.get('nodename')
if nodename:
return queryset.filter(nodename=nodename)
else:
return queryset

如果参数匹配有误,想要抛出异常的话,也可以自定义 APIError,举个例子:

from rest_framework.exceptions import APIException

class FilterError(APIException):
status_code = 406
default_detail = 'Query arguments error!'

然后在 viewset 里直接抛出 raise FilterError 即可。


Premissions

顾名思义就是权限管理,用来给 ViewSet 设置权限,使用 premissions 可以方便的设置不同级别的权限:

  • 全局权限控制
  • ViewSet 的权限控制
  • Method 的权限
  • Object 的权限

被 premission 拦截的请求会有如下的返回结果:

  • 当用户已登录,但是被 premissions 限制,会返回 HTTP 403 Forbidden
  • 当用户未登录,被 premissions 限制会返回 HTTP 401 Unauthorized

默认的权限

rest_framework 中提供了七种权限

  • AllowAny # 无限制
  • IsAuthenticated # 登陆用户
  • IsAdminUser # Admin 用户
  • IsAuthenticatedOrReadOnly # 非登录用户只读
  • DjangoModelPermissions # 以下都是根据 Django 的 ModelPremissions
  • DjangoModelPermissionsOrAnonReadOnly
  • DjangoObjectPermissions

全局权限控制

在 settings.py 中可以设置全局默认权限

# settings.py

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}

ViewSet 的权限

可以设置 permission_classes 的类属性来给 viewset 设定权限,restframework 会检查元组内的每一个 premission,必须要全部通过才行。

class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# 设置权限,是一个元组
permission_classes = (permissions.IsAuthenticated,)

自定义权限

Premissions 可以非常方便的定制,比如我就自己写了一个只允许 owner 编辑的权限

# myproject/myapp/premissions.py

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): def has_permission(self, request, view):
"""针对每一次请求的权限检查"""
if request.method in permissions.SAFE_METHODS:
return True def has_object_permission(self, request, view, obj):
"""针对数据库条目的权限检查,返回 True 表示允许"""
# 允许访问只读方法
if request.method in permissions.SAFE_METHODS:
return True # 非安全方法需要检查用户是否是 owner
return obj.owner == request.user

urls & routers

# myproject/myapp/urls.py

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import unicode_literals, absolute_import from django.conf.urls import url, patterns, include
from rest_framework.routers import DefaultRouter from . import views # as_view 方法生成 view
# 可以非常方便的指定 `{Http Method: View Method}`
user_detail = views.UserViewSet.as_view({'get': 'retrieve'})
user_list = views.UserViewSet.as_view({'get': 'list', 'post': 'create'}) # plaintext 是我的自定义方法,也可以非常方便的指定
modal_plain = views.ModelViewSet.as_view({'get': 'plaintext'})
model_detail = views.ModelViewSet.as_view({'get': 'retrieve', 'post': 'create'})
model_list = views.ModelViewSet.as_view({'get': 'list', 'post': 'create'}) # router 的作用就是自动生成 Api Root 页面
router = DefaultRouter()
router.register(r'models', views.ModelViewSet)
router.register(r'users', views.UserViewSet) # 不要忘了把 views 注册到 urls 中
urlpatterns = patterns(
'',
url(r'^', include(router.urls)), # Api Root
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^models/(?P<pk>[0-9]+)/$', model_detail, name='model-detail'),
url(r'^models/(?P<pk>[0-9]+)/plain/$', modal_plain, name='model-plain'),
url(r'^models/$', model_list, name='model-list'),
url(r'^users/$', user_list, name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'),
)

时间仓促,就介绍这些,以后有空再介绍一下在 Django 用 JWT 作为身份凭证。下面是一些效果图

  • Api Root

  • Users


Reference

利用 Django REST framework 编写 RESTful API的更多相关文章

  1. Django编写RESTful API(一):序列化

    欢迎访问我的个人网站:www.comingnext.cn 关于RESTful API 现在,在开发的过程中,我们经常会听到前后端分离这个技术名词,顾名思义,就是前台的开发和后台的开发分离开.这个技术方 ...

  2. Django编写RESTful API(四):认证和权限

    欢迎访问我的个人网站:www.comingnext.cn 前言: 按照前面几篇文章里那样做,使用Django编写RESTful API的基本功能已经像模像样了.我们可以通过不同的URL访问到不同的资源 ...

  3. Django Rest Framework 教程及API向导

    Django Rest Framework 教程及API向导. 一.请求(Request)REST_FRAMEWORK 中的 Request 扩展了标准的HttpRequest,为 REST_FRAM ...

  4. Spring Boot 2.x 编写 RESTful API (六) 事务

    用Spring Boot编写RESTful API 学习笔记 Transactional 判定顺序 propagation isolation 脏读 不可重复读 幻读 不可重复读是指记录不同 (upd ...

  5. Spring Boot 2.x 编写 RESTful API (五) 单元测试

    用Spring Boot编写RESTful API 学习笔记 概念 驱动模块 被测模块 桩模块 替代尚未开发完毕的子模块 替代对环境依赖较大的子模块 (例如数据访问层) 示例 测试 Service @ ...

  6. Spring Boot 2.x 编写 RESTful API (四) 使用 Mybatis

    用Spring Boot编写RESTful API 学习笔记 添加依赖 <dependency> <groupId>org.mybatis.spring.boot</gr ...

  7. Spring Boot 2.x 编写 RESTful API (三) 程序层次 & 数据传输

    用Spring Boot编写RESTful API 学习笔记 程序的层次结构 相邻层级的数据传输 JavaBean 有一个 public 的无参构造方法 属性 private,且可以通过 get.se ...

  8. Spring Boot 2.x 编写 RESTful API (二) 校验

    用Spring Boot编写RESTful API 学习笔记 约束规则对子类依旧有效 groups 参数 每个约束用注解都有一个 groups 参数 可接收多个 class 类型 (必须是接口) 不声 ...

  9. Spring Boot 2.x 编写 RESTful API (一) RESTful API 介绍 & RestController

    用Spring Boot编写RESTful API 学习笔记 RESTful API 介绍 REST 是 Representational State Transfer 的缩写 所有的东西都是资源,所 ...

随机推荐

  1. Bootstrap系列 -- 43. 固定导航条

    很多情况之一,设计师希望导航条固定在浏览器顶部或底部,这种固定式导航条的应用在移动端开发中更为常见.Bootstrap框架提供了两种固定导航条的方式:  .navbar-fixed-top:导航条固定 ...

  2. Spring Boot 连接MySql数据库

    Spring Boot 以后也许会成为入门Spring的首选! 记一下Spring Boot 成功连接Mysql数据库的方法步骤! 一.新建Maven工程,不全Maven所需文件夹,在pom.xml引 ...

  3. 16-head 简明笔记

    显示文件的头部 head [options] [file-list] 参数 file-list 为要head显示的文件的路径名列表.当指定多个文件时,head在显示每个文件的前几行内容之前显示对应的文 ...

  4. Linux IO Scheduler(Linux IO 调度器)

    每个块设备或者块设备的分区,都对应有自身的请求队列(request_queue),而每个请求队列都可以选择一个I/O调度器来协调所递交的request.I/O调度器的基本目的是将请求按照它们对应在块设 ...

  5. 关于volatile的可见性问题

    volatile的定义是:volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的‘可见性’,可见性的意思是当一个线程修改一个共享变量时,另外一个线程能够读到这个修改的值 ...

  6. Linux_虚拟机_安装VMware Tools

    以root用户执行,否则可能会出现权限不足,无法执行的情况 一.点击安装Vmware Tools   二.拖动安装文件到桌面并解压   三.双击并[在终端中运行]   四.根据提示回车或输入yes   ...

  7. 哈希 poj 3274

    n个牛 二进制最多k位 给你n个数 求max(j-i)&&对应二进制位的和相同 7    1  1  1  倒的 6    0  1  1 7    1  1  1 2    0  1 ...

  8. Windows上python的virtualenv 安装及使用

    源地址:http://blog.csdn.net/liuchunming033/article/details/46008301 VirtualEnv可以方便的解决不同项目对类库的依赖问题. 现实测试 ...

  9. java classloader

    一个jvm中默认的classloader有Bootstrap ClassLoader.Extension   ClassLoader.App ClassLoader,分别各司其职: Bootstrap ...

  10. Yii2 Redis的使用

    Yii2 redis扩展    下载 将下载的解压,改名为redis,放在vendor\yiisoft\yii2\目录下 包含Connection.php,ActiveRecord.php等文件 修改 ...