利用 Django REST framework 編寫 RESTful API

利用 Django REST framework 編寫 RESTful API

Updateat 2015/12/3: 增長 filterhtml

最近在玩 Django,不得不說 rest_framework 真乃一大神器,能夠輕易的甚至自動化的搞定不少事情,好比:node

  • 自動生成符合 RESTful 規範的 API
    • 支持 OPTION、HEAD、POST、GET、PATCH、PUT、DELETE
    • 根據 Content-Type 來動態的返回數據類型(如 text、json)
  • 生成 browserable 的交互頁面(自動爲 API 生成很是友好的瀏覽器頁面)
  • 很是細粒度的權限管理(能夠細粒度到 field 級別)

示意圖python


安裝

$ pip install djangorestframework $ pip install markdown 

概述

Django Rest framework 的流程大概是這樣的git

  1. 創建 Models
  2. 依靠 Serialiers 將數據庫取出的數據 Parse 爲 API 的數據(可用於返回給客戶端,也可用於瀏覽器顯示)
  3. ViewSet 是一個 views 的集合,根據客戶端的請求(GET、POST等),返回 Serialiers 處理的數據
    • 權限 Premissions 也在這一步作處理
  4. ViewSet 可在 Routers 進行註冊,註冊後會顯示在 Api Root 頁上
  5. 在 urls 裏註冊 ViewSet 生成的 view,指定監聽的 url

但願全面細緻瞭解的人請移步去看官方文檔,我這裏就不一步步的細說了,而是分塊來進行介紹github


準備工做 & Models

讓咱們來寫個小項目練練手數據庫

  1. 先用 manage.py startproject rest 來生成一個項目
  2. 再用 manage.py createsuperuser 建立用戶(後面權限管理會用到)
  3. 初始化數據庫 manage.py migrate

而後固然是編寫 models,爲了展現 rest_framework 的強大之處,我給 models 定義了一個自定義的 fielddjango

# 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 的 Formjson

# 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 了api

其實 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

相關文章
相關標籤/搜索