Tutorial 1: Serialization 序列化
安裝基本環境
pip install django
pip install djangorestframework
pip install pygments # We'll be using this for the code highlighting
開始 創建測試環境
創建一個 django 工程
django-admin.py startproject tutorial
在工程中創建一個 app
python manage.py startapp snippets
在 tutorial/settings.py
文件中修改:聲明 app (自己創建的 app 和 rest_framework)
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
創建 Model 模塊
修改 snippets/models.py
文件:
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
將創建的 Model 建立相應的 db 表,更新數據庫:
python manage.py makemigrations snippets
python manage.py migrate
Creating a Serializer class 創建序列化器類
snippets
app 目錄下創建 serializers.py
:
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
Working with Serializers 使用 序列化器類
開啟 shell
python manage.py shell
創建 model 對象,保存到數據庫中:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
序列化對象:將 model 對象轉化為 python 基本數據類型
serializer = SnippetSerializer(snippet)
serializer.data
# {'title': '', 'id': 3, 'code': 'create by new\n', 'style': 'friendly', 'language': 'python', 'linenos': False}
將 python 基本數據,渲染成 json
content = JSONRenderer().render(serializer.data)
content
# b'{"id":3,"title":"","code":"create by new\\n","linenos":false,"language":"python","style":"friendly"}'
反序列化 json-》object
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
將一個反序列化出來的 object 存到數據庫中:
serializer = SnippetSerializer(data=data) # data 是 json->object 轉化出來的 object
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
查詢一組數據:
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('langua......
Using ModelSerializers 使用 模型序列化
將原來的 snippets/serializers.py
文件內容替換為:
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
使用這個的一個好處是,可以直接看到 serializer 實例的 屬性 by printing its representation:
python manage.py shell
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', read_only=True)
# title = CharField(allow_blank=True, max_length=100, required=False)
# code = CharField(style={'base_template': 'textarea.html'})
# linenos = BooleanField(required=False)
# language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
# style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
需要注意的是 ModelSerializer 類沒有做什么特殊的處理,他只是一個 Serializer 類的簡化:
- An automatically determined set of fields.
- Simple default implementations for the create() and update() methods.
Writing regular Django views using our Serializer 使用我們定義的 可序列化對象編寫正常的 django View
下面的例子不使用 rest-framework 的特性,按照 django 的方法來建立 view
創建一個 HttpResponse 的子類,用來將 data-》 json
snippets/views.py
:
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
api: 獲取已存在的 snippets ,或者創建新的 snippets
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
csrf_exempt
表示 POST 不需要 CSRF token,通常我們不會這樣做,rest-framework 中對于這個有更好的解決方案,這里只是一個簡單的例子。
api:獲取一個獨立的 snippets,用于 retrieve, update or delete
@csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data)
return JSONResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
聲明上面的 view snippets/urls.py
: ( wire these views up)
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
聲明 url (wire up the root urlconf) tutorial/urls.py
:
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
Tutorial 2: Requests and Responses 請求和回復
Request objects
rest-framework 采用 Request 對象,它繼承自 HttpRequest,提供了更加靈活的請求解析
其核心是屬性: request.data
request.POST # Only handles form data. Only works for 'POST' method.
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
Response objects
return Response(data) # Renders to content type as requested by the client.
Status codes
status
module 提供了請求code, 比如:HTTP_400_BAD_REQUEST
這樣比直接使用 數字code 要更加易懂
Wrapping API views 包裝 API 視圖
兩種包裝方式:
-
@api_view
修飾方法視圖 -
APIView
包裝以類為視圖的api
包裝提供了一些新的特性,比如
- 確認視圖接收了 Request 實例, 修改 Response 。
- 返回 405 Method Not Allowed
- 捕獲 ParseError
Pulling it all together 使用上面介紹的 rest-framework 的特性編寫程序
修改 snippets/views.py
: 1. 去除 JSONResponse 2. 修改 view
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a snippet instance.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
優點:
- 代碼更加簡潔
- 使用 HTTP_400_BAD_REQUEST 而不是 400
Adding optional format suffixes to our URLs 給 url 添加后綴
修改 view:添加 format 參數
def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):
修改 urls.py
urlpatterns = format_suffix_patterns(urlpatterns)
發起請求回去不同的數據格式:
-
設置請求頭
Accept:application/json # Request JSON Accept:text/html # Request HTML
-
使用 url 后綴
http http://127.0.0.1:8000/snippets.json # JSON suffix http http://127.0.0.1:8000/snippets.api # Browsable API suffix
Tutorial 3: Class-based Views 類視圖
Rewriting our API using class-based views 重寫 view
views.py
:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
修改 urls.py
:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Using mixins
使用類視圖好處:
- 可復用
- create/retrieve/update/delete 操作在 rest-framework 中都有相應的封裝類
修改 views.py
:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
添加了基本類:GenericAPIView
mixins 方法擴展類: ListModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin
Using generic class-based views 使用 generic 基本類
使用 mixin 類能夠簡化很多代碼,但是可以做到更加簡潔,使用包含了 mixin 功能的 generic 類
修改 views.py
:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Tutorial 4: Authentication & Permissions 認證和權限
目的:
- Code snippets are always associated with a creator.
- Only authenticated users may create snippets.
- Only the creator of a snippet may update or delete it.
- Unauthenticated requests should have full read-only access.
Adding information to our model 修改 model
修改 Snippet/models.py
: 添加兩個屬性
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
添加 保存方法:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
修改完數據后更新數據庫:這里更新是直接刪除舊的數據庫,創建新的數據庫
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
創建用戶:
python manage.py createsuperuser
Adding endpoints for our User models 添加用戶控制入口
serializers.py
: 添加 User 序列化器
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
添加 User 對應的 View,修改 views.py
:
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
添加 url urls.py
:
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
Associating Snippets with Users 關聯 Snippets 和 users
修改 views.py
中的 SnippetList
類:重寫下面的方法,讓
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Updating our serializer 更新 序列化器
上面的代碼將 Snippets 和創建它的 user 關聯在一起,下面修改 序列化器:添加 field
owner = serializers.ReadOnlyField(source='owner.username')
Adding required permissions to views 添加請求權限到 views
rest-framework 中有很多權限類用于限制哪些用戶可以請求views,下面我們只用 IsAuthenticatedOrReadOnly 來設置權限
在 views.py
中添加 SnippetList ,SnippetDetail 類屬性
from rest_framework import permissions
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Adding login to the Browsable API 添加游覽器登入api
修改 urls.py
添加 登入視圖url:
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
Object level permissions 對象級別的權限
下面設置權限: 創建 Snippets 的用戶才能修改這個 Snippets
創建 snippets/permissions.py
: 添加自定義的權限類
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
將權限添加到 SnippetDetail 中 views.py
:
from snippets.permissions import IsOwnerOrReadOnly
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.
Authenticating with the API 認證api
目前位置我們還沒有設置任何 authentication classes ,和權限有關的類,所以目前這個工程的默認認證類是:SessionAuthentication,BasicAuthentication
可以在請求時設置 Basic Auth: 用戶名:密碼 訪問api
Tutorial 5: Relationships & Hyperlinked APIs 關系和超鏈接 APIs
Creating an endpoint for the root of our API 為接口創建一個入口
添加下面代碼:snippets/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
上面代碼
- 使用了 rest-framework 提供的
reverse
方法來轉化url - 其對應的 URL 模版,我們會在后面的代碼中看到
snippets/urls.py
Creating an endpoint for the highlighted snippets
添加 snippets/views.py
:
from rest_framework import renderers
from rest_framework.response import Response
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
添加 snippets/urls.py
:
url(r'^$', views.api_root),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
Hyperlinking our API 為 api 創建超級鏈接
處理 實體類 之間的關系有很多種方式:
- 使用主鍵
- Using hyperlinking between entities. 使用超級鏈接
- Using a unique identifying slug field on the related entity.
- Using the default string representation of the related entity.
- Nesting the related entity inside the parent representation.
- Some other custom representation.
下面使用超級鏈接的方式來處理。
修改 序列化器(serializers) 繼承 HyperlinkedModelSerializer
HyperlinkedModelSerializer 和 ModelSerializer 的區別:
- It does not include the id field by default. 默認不包含 id
- It includes a url field, using HyperlinkedIdentityField。 包含了 url 屬性
- Relationships use HyperlinkedRelatedField, instead of PrimaryKeyRelatedField。 關系維護使用 HyperlinkedRelatedField
修改 snippets/serializers.py
:
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
Making sure our URL patterns are named 確保 url 中定義了參數 name
snippets/urls.py
:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
# Login and logout views for the browsable API
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
Adding pagination 添加分頁
tutorial/settings.py
:
REST_FRAMEWORK = {
'PAGE_SIZE': 10
}