利用 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. 一起来学node.js吧 node school简介

    node.js这几年火爆的简直丧心病狂,去lagou.com查查node.js的职位,那叫一个多. 要说火爆到什么程度,竟然有一个网站专门去教大家学习node.js, Node School. 进去逛 ...

  2. Entity Framework 出现 "此 ObjectContext 实例已释放,不可再用于需要连接的操作" 的错误

    原因 Entity的导航属性在View中使用,但是该Entity所在的Context已经在Controller中通过 using 释放掉:但是Entity又具有Deferred Query Evalu ...

  3. javascript面试题(一)

    答案和解析在问题下一行,为白色字体 单选题 1.以下哪条语句会产生运行错误:(a) A.var obj = ();//语法错误 B.var obj = [];//创建数组 C.var obj = {} ...

  4. webpack入坑之旅(六)配合vue-router实现SPA

    这是一系列文章,此系列所有的练习都存在了我的github仓库中vue-webpack,在本人有了新的理解与认识之后,会对文章有不定时的更正与更新.下面是目前完成的列表: webpack入坑之旅(一)不 ...

  5. jquery validate 隐藏域内容验证

  6. zabbix监控网络的出入口流量

    首先我们登录到zabbix 点击配置---->模板-->Template OS Linux 下的监控项 点击右上角的添加监控项目 我们的服务器是在Ucloud上的,我们的网卡名称为eth0 ...

  7. 简单Matrix 的方法说明记录

    查找资料加上自己理解  ,简单说明Android中Matrix怎么用(新手有错误的地方,希望指正,主要自己记录学习用的) Matrix包含一个3 X 3的矩阵,专门用于图像变换匹配. Matrix提供 ...

  8. iOS开发小技巧--巧用ImageView中的mode(解决图片被拉伸的情况)

    一.自己遇到的问题:在布局ImageView的时候,通过约束将ImageView布局好,但是里面的图片被拉伸的很难看.这时候就用到了Mode属性,如图: 代码实现方式: 二.让图片按照比例拉伸,并不是 ...

  9. 【BZOJ 3049】【USACO2013 Jan】Island Travels BFS+状压DP

    这是今天下午的互测题,只得了60多分 分析一下错因: $dis[i][j]$只记录了相邻的两个岛屿之间的距离,我一开始以为可以,后来$charge$提醒我有可能会出现来回走的情况,而状压转移就一次,无 ...

  10. spring-ant-处理zip

    因为java类型自带的不支持中文路径,不过两者使用的方式是一样的,只是apache压缩工具多了设置编码方式的接口,其他基本上是一样的.另外,如果使用org.apache.tools.zip.ZipOu ...