序列化
環(huán)境搭建
首先我們先新建一個(gè) restapi
項(xiàng)目并安裝上 django-rest-framework (DRF) 環(huán)境
$ pip install djangorestframework
$ python manage.py startnewproject restapi
$ cd restapi
$ python manage.py startnewapp douban
接著,我們需要在 setting.py
里的加入如下代碼:
INSTALLED_APPS = (
...
'rest_framework',
'douban',
)
建立模型
由于我炒雞喜歡看電影族操,所以仿著 douban-API
來(lái)做個(gè)簡(jiǎn)易的豆瓣電影的 rest-api 蛹含。
所以我們就用這個(gè)「仿豆瓣電影 api 」來(lái)作為栗子開始教程吧鲁豪!
編輯 douban/models.py
文件并加入以下代碼:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from django.db import models
# 舉個(gè)栗子
COUNTRY_CHOICES = (
('US', 'US'),
('Asia', 'Asia'),
('CN', 'CN'),
('TW', 'TW'),
)
TYPE_CHOICES = (
('Drama', 'Drama'),
('Thriller', 'Thriller'),
('Sci-Fi', 'Sci-Fi'),
('Romance', 'Romance' ),
('Comedy', 'Comedy')
)
GENDER_CHOICES = (
('male', 'male'),
('female', 'female')
)
class movies(models.Model):
title = models.CharField(max_length=100, blank=True, default='')
year = models.CharField(max_length=20)
# 在 director 關(guān)聯(lián)了 movies 類 和 celecrity 類, 在第4章會(huì)用到 celebrity 類
# director = models.ForeignKey('celebrity', related_name='movies')
country = models.CharField(choices=COUNTRY_CHOICES, default='US', max_length=20)
type = models.CharField(choices=TYPE_CHOICES, default='Romance', max_length=20)
rating = models.DecimalField(max_digits=3, decimal_places=1)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
# class celebrity(models.Model):
# name = models.CharField(max_length=100, blank=True, default='')
# age = models.IntegerField()
# gender = models.CharField(choices=GENDER_CHOICES, default='男', max_length=20)
接著在終端中運(yùn)行:
$ python manage.py makemigrations douban
$ python manage.py migrate
$ python manage.py syncdb
來(lái)創(chuàng)建一個(gè)新的 migrations 并在數(shù)據(jù)庫(kù)中生成表。
創(chuàng)建序列化類
在開始構(gòu)建 Web API 時(shí),我們首先要做的就是提供對(duì) movies
實(shí)例的序列化和反序列化(即對(duì)序列化后的實(shí)例進(jìn)行「解碼」)奏路,這樣才能生成可供瀏覽的 json
格式的 api 阔蛉。我們可以通過(guò)聲明「序列器」(一個(gè)和 Django 表單十分類似的玩意兒)來(lái)做到這一點(diǎn)弃舒。
在 restapi
目錄中創(chuàng)建一個(gè) serializer.py
文件,加入以下代碼:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from rest_framework import serializers
from douban.models import movies, COUNTRY_CHOICES, TYPE_CHOICES
class MoviesSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
year = serializers.CharField(max_length=20)
country = serializers.ChoiceField(choices=COUNTRY_CHOICES, default='US')
type = serializers.ChoiceField(choices=TYPE_CHOICES, default='Romance')
rating = serializers.DecimalField(max_digits=3, decimal_places=1)
def create(self, validated_data):
"""
根據(jù)接收到的 validated_data 創(chuàng)建一個(gè) movies 實(shí)例
"""
return movies.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
根據(jù)接收到的 validated_data 更新并返回一個(gè) movies 實(shí)例
"""
instance.title = validated_data.get('title', instance.title)
instance.year = validated_data.get('year', instance.year)
instance.country = validated_data.get('country', instance.country)
instance.type = validated_data.get('type', instance.type)
instance.rating = validated_data.get('rating', instance.rating)
instance.save()
return instance
序列器的第一個(gè)部分定義了要進(jìn)行序列化/反序列化的字段。
create()
和 update()
方法定義了符合規(guī)范的 movies 實(shí)例的創(chuàng)建和更新的方法聋呢。
序列器非常類似于 Django Form
表單苗踪,它包含了幾種對(duì)字段常見(jiàn)的驗(yàn)證標(biāo)識(shí)符,如 required
削锰、 max_length
通铲、 default
等。這些標(biāo)識(shí)符實(shí)現(xiàn)的功能類似于 Django 表單器贩,就不詳細(xì)解釋了颅夺。
所以序列器實(shí)現(xiàn)了以下兩個(gè)功能:
- 選擇相應(yīng)的模型
- 選擇要展現(xiàn)的字段(驗(yàn)證后的)
我們也可以通過(guò)使用 ModelSerializer
多快好省地的構(gòu)建序列器,這個(gè)我們?nèi)蘸笤僬f(shuō)蛹稍。
開始使用序列器
在開始項(xiàng)目之前吧黄,我們先熟悉下序列器,在終端中啟動(dòng) Django shell :
$ python manage.py shell
輸入以下代碼來(lái)創(chuàng)建2個(gè) Movies 實(shí)例
「荒野獵人」和「蝙蝠俠愛(ài)上超人」
from douban.models import Movies
from douban.serializer import MoviesSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
movies = Movies(title='The Revenant', year='2015', country='US', type='Drama', rating=7.9)
movies.save()
movies = Movies(title='Batman v Superman: Dawn of Justice', year='2016', country='US', type='Romance', rating=6.7)
movies.save()
然后將其中一個(gè)實(shí)例序列化
serializer = MoviesSerializer(movies)
serializer.data
#{'rating': u'7.9', 'title': u'The Revenant', 'country': 'US', 'year': u'2015', 'pk': None, 'type': 'Drama'}
接著我們將以上數(shù)據(jù)轉(zhuǎn)換為 JSON 格式唆姐,實(shí)現(xiàn)序列化
content = JSONRenderer().render(serializer.data)
content
#{"pk":null,"title":"The Revenant","year":"2015","country":"US","type":"Drama","rating":"7.9"}'
反序列化也類似拗慨,通過(guò)解析 Python 數(shù)據(jù)流并將數(shù)據(jù)流"引入"實(shí)例中即可
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
serializer = MoviesSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
#OrderedDict([(u'title', u'The Revenant'), (u'year', u'2015'), (u'country', 'US'), (u'type', 'Drama'), (u'rating', Decimal('7.9'))])
可見(jiàn), serializer和django form 有多么相似, 當(dāng)我們寫view時(shí), 這一相似性會(huì)更加明顯.
當(dāng)我們輸入?yún)?shù)many=True時(shí), serializer還能序列化queryset:
serializer = MoviesSerializer(Movies.objects.all(), many=True)
serializer.data
[OrderedDict([('pk', 1), ('title', u'Batman v Superman: Dawn of Justice'), ('year', u'2016'), ('country', 'US'), ('type', 'Romance'), ('rating', u'6.7')]), OrderedDict([('pk', 2), ('title', u'The Revenant'), ('year', u'2015'), ('country', 'US'), ('type', 'Drama'), ('rating', u'7.9')])]
使用更高級(jí)的 ModelSerializers
接著如果你按照官網(wǎng)的教程走下去,你會(huì)發(fā)現(xiàn)上面的 serializer.py
是個(gè)代碼冗雜的序列器厦酬,這不符合 Python 的風(fēng)格胆描。
所以我們要做的就是簡(jiǎn)化代碼。
DRF 提供了更為簡(jiǎn)便的 ModelSerializer
類可以解決這個(gè)問(wèn)題仗阅。
所以我們修改之前的 serializer.py
:
class MoviesSerializer(serializers.ModelSerializer):
class Meta:
model = Movies
fields = ('id', 'title', 'year', 'country', 'type', 'rating')
這種模式的序列器可以很方便地檢查 fields 中的每個(gè)字段
然后在終端中打開 Django shell
$ python manage.py shell
輸入以下代碼
from douban.serializer import MoviesSerializer
serializer = MoviesSerializer()
print(repr(serializer))
#MoviesSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=100, required=False)
year = CharField(max_length=20)
country = ChoiceField(choices=(('US', 'US'), ('Asia', 'Asia'), ('CN', 'CN'), ('TW', 'TW')), required=False)
type = ChoiceField(choices=(('Drama', 'Drama'), ('Thriller', 'Thriller'), ('Sci-Fi', 'Sci-Fi'), ('Romance', 'Romance'), ('Comedy', 'Comedy')), required=False)
rating = DecimalField(decimal_places=1, max_digits=3)
注: ModelSerializer
類僅僅是創(chuàng)建 serializer
類的一個(gè)快捷方法昌讲,它除了實(shí)現(xiàn)以下兩種方法外并沒(méi)有其余的功能:
- 聲明需要展現(xiàn)的字段
- 定義
create()
和update()
方法
使用 Django views 編寫序列器視圖
為了更好理解序列器,我們不使用 DRF 的其他特性减噪,僅僅用 Django views 模式來(lái)編寫序列器的視圖短绸。
我們會(huì)創(chuàng)建一個(gè) HttpResponse 的子類,這樣就能將數(shù)據(jù)以 json 格式返回筹裕。
編輯 douban/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 douban.models import Movies
from douban.serializer import MoviesSerializer
class JSONResponse(HttpResponse):
"""
將數(shù)據(jù)轉(zhuǎn)為 JSON 格式的 HttpResponse 子類
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
講道理的話醋闭,我們 api 的根目錄應(yīng)該能羅列出所有的 Movies 或者 能新建一個(gè) Movies
并且還需要一個(gè)用于展示、更新和刪除 Movies 的 views
編輯 douban/views.py
加入以下代碼:
#!/usr/bin/python
# -*- coding: utf-8 -*-
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 douban.models import Movies
from douban.serializer import MoviesSerializer
class JSONResponse(HttpResponse):
"""
將數(shù)據(jù)轉(zhuǎn)為 JSON 格式的 HttpResponse 子類
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
@csrf_exempt
def movies_list(request):
"""
羅列出所有的 Movies 或者 能新建一個(gè) Movies
"""
if request.method == 'GET':
movies = Movies.objects.all()
serializer = MoviesSerializer(movies, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = MoviesSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
@csrf_exempt
def movies_detail(request, pk):
"""
展示\更新或刪除一個(gè) Movies
"""
try:
movies = Movies.objects.get(pk=pk)
except Movies.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = MoviesSerializer(movies)
return JSONResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = MoviesSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data)
return JSONResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
movies.delete()
return HttpResponse(status=204)
我不是很弄明白這里關(guān)掉 csrf 的意義朝卒,那不如直接就不用 csrf 不就好了证逻?
不管了,先放著抗斤,以后回來(lái)看 ( 吐舌頭
最后修改 douban/url.py
導(dǎo)入相應(yīng)的視圖
from django.conf.urls import url
from douban import views
urlpatterns = [
url(r'^dbmovies/$', views.movies_list),
url(r'^dbmovies/(?P<pk>[0-9]+)/$', views.movies_detail),
]
并在 restapi/url.py
中 include 一下
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('douban.urls')),
]
這樣 url 和 views 就綁定好了囚企。
測(cè)試 Web API
在終端中輸入
$ python manage.py runserver
接著來(lái)瀏覽器中訪問(wèn) http://127.0.0.1/dbmovies/
如果出現(xiàn)如圖所示的 api 則說(shuō)明 Web api 返回成功。
(順便安利一個(gè) chrome 插件 — FeHelper 可以自動(dòng)格式化 JSON 代碼)
https://github.com/thehackercat/django-rest-framework-tutorial/blob