利用 Django REST framework 编写 RESTful API
利用 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 的流程大概是这样的
- 建立 Models
- 依靠 Serialiers 将数据库取出的数据 Parse 为 API 的数据(可用于返回给客户端,也可用于浏览器显示)
- ViewSet 是一个 views 的集合,根据客户端的请求(GET、POST等),返回 Serialiers 处理的数据
- 权限 Premissions 也在这一步做处理
- ViewSet 可在 Routers 进行注册,注册后会显示在 Api Root 页上
- 在 urls 里注册 ViewSet 生成的 view,指定监听的 url
希望全面细致了解的人请移步去看官方文档,我这里就不一步步的细说了,而是分块来进行介绍
准备工作 & Models
让我们来写个小项目练练手
- 先用
manage.py startproject rest
来生成一个项目 - 再用
manage.py createsuperuser
创建用户(后面权限管理会用到) - 初始化数据库
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 的 ModelPremissionsDjangoModelPermissionsOrAnonReadOnly
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的更多相关文章
- Django编写RESTful API(一):序列化
欢迎访问我的个人网站:www.comingnext.cn 关于RESTful API 现在,在开发的过程中,我们经常会听到前后端分离这个技术名词,顾名思义,就是前台的开发和后台的开发分离开.这个技术方 ...
- Django编写RESTful API(四):认证和权限
欢迎访问我的个人网站:www.comingnext.cn 前言: 按照前面几篇文章里那样做,使用Django编写RESTful API的基本功能已经像模像样了.我们可以通过不同的URL访问到不同的资源 ...
- Django Rest Framework 教程及API向导
Django Rest Framework 教程及API向导. 一.请求(Request)REST_FRAMEWORK 中的 Request 扩展了标准的HttpRequest,为 REST_FRAM ...
- Spring Boot 2.x 编写 RESTful API (六) 事务
用Spring Boot编写RESTful API 学习笔记 Transactional 判定顺序 propagation isolation 脏读 不可重复读 幻读 不可重复读是指记录不同 (upd ...
- Spring Boot 2.x 编写 RESTful API (五) 单元测试
用Spring Boot编写RESTful API 学习笔记 概念 驱动模块 被测模块 桩模块 替代尚未开发完毕的子模块 替代对环境依赖较大的子模块 (例如数据访问层) 示例 测试 Service @ ...
- Spring Boot 2.x 编写 RESTful API (四) 使用 Mybatis
用Spring Boot编写RESTful API 学习笔记 添加依赖 <dependency> <groupId>org.mybatis.spring.boot</gr ...
- Spring Boot 2.x 编写 RESTful API (三) 程序层次 & 数据传输
用Spring Boot编写RESTful API 学习笔记 程序的层次结构 相邻层级的数据传输 JavaBean 有一个 public 的无参构造方法 属性 private,且可以通过 get.se ...
- Spring Boot 2.x 编写 RESTful API (二) 校验
用Spring Boot编写RESTful API 学习笔记 约束规则对子类依旧有效 groups 参数 每个约束用注解都有一个 groups 参数 可接收多个 class 类型 (必须是接口) 不声 ...
- Spring Boot 2.x 编写 RESTful API (一) RESTful API 介绍 & RestController
用Spring Boot编写RESTful API 学习笔记 RESTful API 介绍 REST 是 Representational State Transfer 的缩写 所有的东西都是资源,所 ...
随机推荐
- SQL语言概述
功能概述 DDL,数据库定义语言,创建,修改,删除数据库,表,视图,索引,约束条件等 DML,数据库操纵语言,对数据库中的数据进行增,删,改,查 DCL,数据库定义语言,对数据库总数据的访问设置权限 ...
- java发送邮件
1.需要用到javax.mail怎么下载呢?百度javax.mail就会看见http://www.oracle.com/technetwork/java/index-138643.html实际上这个项 ...
- 【JavaEE企业应用实战学习记录】requestListener
package sanglp.servlet; import javax.servlet.*; import javax.servlet.annotation.WebListener; import ...
- <string> 与<string.h>、<cstring>的区别
<string.h> <string.h>是C版本的头文件,包含比如strcpy.strcat之类的字符串处理函数. <cstring> 在C++标准化(1998年 ...
- PHP中错误处理集合
PHP错误处理 错误的分类 通常分3种: 语法错误: 程序运行之前,都要先检查语法.如果语法有错误,就会立即报错,并且不会去执行程序. 运行时错误: 就是在程序语法检查通过后,,开始运行程序并在此过程 ...
- linux安装软件的学习
Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器.基于RPM包管理,能够从指定的服务器自动下载 ...
- Android studio 提高导入项目的速度
最近在下载了一些开源的项目在学习,在导入as时,速度要好慢,如项目大点,就更慢了,实在是坑啊! 那有没有方法能导入时间快点呀! 分析发现,as在导入项目是首先是读 来自为知笔记(Wiz)
- 2016年GitHub 排名前 100 的安卓、iOS项目简介(收藏)
排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果, 然后过滤了跟 Android 不相关的项目, 所以排名并不具备任何官方效力, 仅供参考学习, 方便初学者 ...
- Swift开发小技巧--扫描二维码,二维码的描边与锁定,设置扫描范围,二维码的生成(高清,无码,你懂得!)
二维码的扫描,二维码的锁定与描边,二维码的扫描范围,二维码的生成(高清,无码,你懂得!),识别相册中的二维码 扫描二维码用到的三个重要对象的关系,如图: 1.懒加载各种类 // MARK: - 懒加载 ...
- Entity Framework Code First (四)Fluent API - 配置属性/类型
上篇博文说过当我们定义的类不能遵循约定(Conventions)的时候,Code First 提供了两种方式来配置你的类:DataAnnotations 和 Fluent API, 本文将关注 Flu ...