利用 Django REST framework 编写 RESTful API

利用 Django REST framework 编写 RESTful API

自动生成符合 RESTful 规范的 API

支持 OPTION、HEAD、POST、GET、PATCH、PUT、DELETE

根据Content-Type来动态的返回数据类型(如 text、json)

生成 browserable 的交互页面(自动为 API 生成非常友好的浏览器页面)

非常细粒度的权限管理(可以细粒度到 field 级别)

示意图

安装

$pipinstalldjangorestframework$pipinstallmarkdown

概述

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-8from__future__importunicode_literals,absolute_importimportcPickleaspicklefromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportUserclassSerializedField(models.TextField):"""序列化域用 pickle 来实现存储 Python 对象"""__metaclass__=models.SubfieldBase# 必须指定该 metaclass 才能使用 to_pythondefvalidate(self,val):raiseisinstance(val,basestring)defto_python(self,val):"""从数据库中取出字符串,解析为 python 对象"""ifvalandisinstance(val,unicode):returnpickle.loads(val.encode('utf-8'))returnvaldefget_prep_value(self,val):"""将 python object 存入数据库"""returnpickle.dumps(val)classMyModel(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-8from__future__importunicode_literals,absolute_importimportjsonfromdjango.contrib.auth.modelsimportUserfromrest_frameworkimportserializersfrom..modelsimportMyModelfrom.fieldsimportMyCustFieldclassMyCustField(serializers.CharField):"""为 Model 中的自定义域额外写的自定义 Serializer Field"""defto_representation(self,obj):"""将从 Model 取出的数据 parse 给 Api"""returnobjdefto_internal_value(self,data):"""将客户端传来的 json 数据 parse 给 Model"""returnjson.loads(data.encode('utf-8'))classUserSerializer(serializers.ModelSerializer):classMeta:model=User# 定义关联的 Modelfields=('id','username','mymodels')# 指定返回的 fields# 这句话的作用是为 MyModel 中的外键建立超链接,依赖于 urls 中的 name 参数# 不想要这个功能的话完全可以注释掉mymodels=serializers.HyperlinkedRelatedField(many=True,queryset=MyModel.objects.all(),view_name='model-detail')classMySerializer(serializers.ModelSerializer):options=MyCustField(max_length=1000,style={'base_template':'textarea.html'},)classMeta:model=MyModelfields=('id','owner','field','options')read_only_fields=('owner',)# 指定只读的 fielddefcreate(self,validated_data):"""响应 POST 请求"""# 自动为用户提交的 model 添加 ownervalidated_data['owner']=self.context['request'].userreturnMyModel.objects.create(**validated_data)defupdate(self,instance,validated_data):"""响应 PUT 请求"""instance.field=validated_data.get('field',instance.field)instance.save()returninstance

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-8from__future__importunicode_literals,absolute_importfromdjango.contrib.auth.modelsimportUserfromrest_frameworkimportpermissions,viewsets,renderersfromrest_framework.decoratorsimport(permission_classes,detail_route)fromrest_framework.responseimportResponsefrom.serializersimportMySerializer,UserSerializerfrom.modelsimportMyModelclassUserViewSet(viewsets.ModelViewSet):queryset=User.objects.all()serializer_class=UserSerializer# 指定权限,下面马上讲到permission_classes=(permissions.IsAuthenticated,)classModelViewSet(viewsets.ModelViewSet):queryset=MyModel.objects.all()serializer_class=MySerializerpermission_classes=(permissions.IsAuthenticatedOrReadOnly,)@detail_route(renderer_classes=[renderers.StaticHTMLRenderer])defplaintext(self,request,*args,**kwargs):"""自定义 Api 方法"""model=self.get_object()returnResponse(repr(model))

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

list_route

detail_route

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

看一段代码就懂了

@list_route(methods=['post','delete'])defcustom_handler(self,request):pass@detail_route(methods=['get'])defcustom_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的类变量:

classUserListView(generics.ListAPIView):queryset=User.objects.all()serializer=UserSerializerfilter_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 即可。

直接贴一个我写的例子:

classNodenameFilter(filters.BaseFilterBackend):"""根据 nodename 来删选[nodename]: NeiWang"""deffilter_queryset(self,request,queryset,view):nodename=request.QUERY_PARAMS.get('nodename')ifnodename:returnqueryset.filter(nodename=nodename)else:returnqueryset

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

fromrest_framework.exceptionsimportAPIExceptionclassFilterError(APIException):status_code=406default_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.pyREST_FRAMEWORK={'DEFAULT_PERMISSION_CLASSES':('rest_framework.permissions.AllowAny',),}

ViewSet 的权限

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

classUserViewSet(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-8from__future__importunicode_literals,absolute_importfromrest_frameworkimportpermissionsclassIsOwnerOrReadOnly(permissions.BasePermission):defhas_permission(self,request,view):"""针对每一次请求的权限检查"""ifrequest.methodinpermissions.SAFE_METHODS:returnTruedefhas_object_permission(self,request,view,obj):"""针对数据库条目的权限检查,返回 True 表示允许"""# 允许访问只读方法ifrequest.methodinpermissions.SAFE_METHODS:returnTrue# 非安全方法需要检查用户是否是 ownerreturnobj.owner==request.user

urls & routers

# myproject/myapp/urls.py#! /usr/bin/env python# -*- coding: utf-8from__future__importunicode_literals,absolute_importfromdjango.conf.urlsimporturl,patterns,includefromrest_framework.routersimportDefaultRouterfrom.importviews# 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 Rooturl(r'^api-auth/',include('rest_framework.urls',namespace='rest_framework')),url(r'^models/(?P[0-9]+)/$',model_detail,name='model-detail'),url(r'^models/(?P[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[0-9]+)/$',user_detail,name='user-detail'),)

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

Api Root

Users

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容