Django RESTful 系列教程(三)(上)

你好 Django REST Framework

在第二章,我們學(xué)習(xí)了 REST 開發(fā)的基本知識(shí)屏鳍,并且在沒有借助任何框架的情況下
完成了我們的 RESTful APP 的開發(fā)锈锤,雖然我們已經(jīng)考慮到了許多的情況闯割,但是我們的 APP
依然有許多的漏洞。在本章蛔趴,我們將會(huì)進(jìn)入 VueDjango REST framework
的學(xué)習(xí)。本章將會(huì)分為三個(gè)部分例朱,分別是:

  • 你好 Django REST Framework
  • 你好 Vue
  • 重構(gòu) APP

這就是我們的三個(gè)部分了孝情。第一個(gè)部分學(xué)習(xí) DRF 鱼蝉,第二個(gè)部分學(xué)習(xí) Vue ,最后一個(gè)部分為實(shí)戰(zhàn)部分箫荡。在上部分魁亦,我們會(huì)學(xué)習(xí)以下知識(shí)點(diǎn):

  1. 了解 DRF 的基本使用。
  2. 了解并能靈活使用序列化器羔挡。

這個(gè)部分的知識(shí)點(diǎn)看起來很少洁奈,其實(shí)等大家真正進(jìn)入他們的學(xué)習(xí)中時(shí),會(huì)發(fā)現(xiàn)其中的知識(shí)點(diǎn)也不少绞灼。當(dāng)然利术,這是一個(gè)教程,不是 DRF 官方文檔復(fù)讀機(jī)低矮,所以一旦在看教程的過程中有什么不懂的地方印叁,去查詢 DRF 文檔是個(gè)好習(xí)慣。同時(shí)军掂,本章也會(huì)涉及 python 的編程知識(shí)喉钢,由此可見,對(duì)于 web 后端的開發(fā)來說良姆,語言的基礎(chǔ)是多么重要肠虽。同樣的,如果遇到在自己查資料之后還不懂的地方玛追,評(píng)論留言或者提 ISSUE税课。

準(zhǔn)備工作

首先,我們需要安裝 DRF 痊剖,在終端中運(yùn)行:

pip install djangorestframework

創(chuàng)建一個(gè)新的項(xiàng)目:

python django-admin.py startproject api_learn

把路徑切換到項(xiàng)目路徑內(nèi)韩玩,創(chuàng)建一個(gè)新的 APP :

python manage.py startapp rest_learn

編輯你的 settings.py 文件,把我們的 APP 和 DRF 添加進(jìn)去:

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_learn'
]

編輯 rest_learnmodels.py

from django.db import models
class TestModel(models.Model):
    name = models.CharField(max_length=20)
    code = models.TextField()
    created_time = models.DateTimeField(auto_now_add=True)
    changed_time = models.DateTimeField(auto_now=True)

    def __str__(self): 
        return self.name

DateTimeField 是時(shí)間與日期字段陆馁,其中的參數(shù)意思是:

  • auto_now: 在實(shí)例每次被保存的時(shí)候就更新一次值找颓,在這里,我們把它作為修改值叮贩。所以 changed_time 字段的值會(huì)隨著實(shí)例的每次修改和保存而發(fā)生變化击狮,這樣就可以記錄實(shí)例的修改時(shí)間。
  • auto_now_add: 在實(shí)例被創(chuàng)建的時(shí)候就會(huì)自動(dòng)賦值益老。created_time 的值就會(huì)在實(shí)例被創(chuàng)建時(shí)自動(dòng)賦值彪蓬,這樣我們就可以記錄實(shí)例是在什么時(shí)候被創(chuàng)建的。

將我們的模型添加進(jìn)管理站點(diǎn)捺萌。
編輯 rest_learn 下的 admin.py:

from django.contrib import admin
from .models import TestModel

@admin.register(TestModel)
class TestModelAdmin(admin.ModelAdmin):
    pass

admin.register 是一個(gè)將 ModelAdmin 快速注冊(cè)模型到管理站點(diǎn)的裝飾器档冬,其中的參數(shù)
是需要被注冊(cè)的模型。

編輯 api_learn 下的 urls.py

from django.conf.urls import url, include
from django.contrib import admin
import rest_framework

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^drf-auth/',include('rest_framework.urls'))
]

第二個(gè) url 配置用于提供登錄的功能。

在項(xiàng)目路徑下運(yùn)行:

python manage.py makemigrations
python migrate

生成我們需要的數(shù)據(jù)庫文件酷誓。

創(chuàng)建管理員披坏。在終端中運(yùn)行:

(root) λ python manage.py createsuperuser
Username (leave blank to use 'administrator'): admin
Email address:
Password:
Password (again):
Superuser created successfully.

在項(xiàng)目路徑下創(chuàng)建兩個(gè)空文件,分別是 data.py盐数,rest_test.py刮萌。

data.py 主要用來為我們提供一些虛擬數(shù)據(jù)。

編輯 data.py娘扩。

from django import setup
import os
os.environ.setdefault('DJAGNO_SETTINGS_MODULE','api_learn.settings') # 在環(huán)境變量中設(shè)置配置文件
setup() # 加載配置文件

from rest_learn.models import TestModel

data_set = {
    'ls':"""import os\r\nprint(os.listdir())""",
    'pwd':"""import os\r\nprint(os.getcwd())""",
    'hello world':"""print('Hello world')"""
}
for name, code in data_set.items():
    TestModel.objects.create(name=name,code=code)

print('Done')

直接運(yùn)行 data.py 着茸,當(dāng)看到 Done 的時(shí)候就說明數(shù)據(jù)寫入已經(jīng)完成了。細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了琐旁,這和我們第一章的 單文件 Django 很相似涮阔。記得我們?cè)诘谝徽抡f過的嗎:

使用前先配置

我們需要使用 Django 的模型,所以需要先配置它灰殴,在這里我們選擇了使用第一章中的第二種方法來配置敬特。如果你忘了第一章講了些什么,快回去看看吧牺陶。這也是我們先講 單文件 Django 原因所在伟阔,知道了配置方法之后,我們就不需要再打開 shell 十分不方便的編寫代碼了掰伸。

為了確保萬無一失皱炉,大家可以選擇登錄進(jìn)后臺(tái)看看我們的數(shù)據(jù)是否寫入了數(shù)據(jù)庫。

本節(jié)剩下所有的代碼都會(huì)在 rest_test.py 中進(jìn)行狮鸭,所以先編寫好配置合搅。

編輯 rest_test.py

from django import setup
import os

# 加載配置
os.environ.setdefault('DJAGNO_SETTINGS_MODULE','api_learn.settings')
setup()


準(zhǔn)備工作已經(jīng)完成了,讓我們正式的開始學(xué)習(xí)歧蕉。

你好 Django REST Framework

在上一章的結(jié)尾我們知道我們的 APP 其實(shí)是不安全的灾部,因?yàn)槲覀儾]有對(duì)上傳的數(shù)據(jù)進(jìn)行任何的檢查,這使得我們的應(yīng)用隨時(shí)暴露在被攻擊的安全隱患之下惯退。同時(shí)赌髓,由于我們的應(yīng)用十分的小,我們并沒有考慮到其它的“內(nèi)容協(xié)商”催跪,如果在今后的應(yīng)用中我們需要用到 xml 格式的數(shù)據(jù)锁蠕,那么我們又需要重新編寫一次我們的代碼。我們的應(yīng)用代碼不僅不安全叠荠,同時(shí)也不靈活匿沛。這一次扫责,我們需要解決這個(gè)問題榛鼎。

序列化器

剛才我們說道机打,我們需要對(duì)上傳的數(shù)據(jù)進(jìn)行檢查,按照一般的思路实抡,我們一般會(huì)編寫一大堆的 if 語句來判斷上傳的數(shù)據(jù)是否符合要求秽荤。用腳指頭都可以知道這么做是最最笨的方法。再好一點(diǎn)的辦法是編寫一些工具函數(shù)來檢查數(shù)據(jù)是否符合要求黄鳍,比如我們的 name 字段的長(zhǎng)度是小于 20 個(gè)字符的推姻,數(shù)據(jù)類型是字符串。那我可以單獨(dú)編寫一個(gè)這樣的函數(shù):

def validate_name(post_data):
    name = post_data.get('name')
    if isinstance(name, str):
        if len(name) <= 20:
            return name
    raise Exception('Invalid name data.')

這樣我們直接在視圖邏輯中直接調(diào)用這個(gè)函數(shù)就可以了框沟。這個(gè)比單獨(dú)寫 if 語句要好一些了藏古。但是依然會(huì)有不少問題,如果中途我們的 name 字段的長(zhǎng)度被修改為 30 個(gè)字符了忍燥,那我們是不是要再改一次我們的 validate_name 函數(shù)呢拧晕?要是我們的 name 字段被修改成了 code_name ,那我們是不是還要再改一次呢梅垄?每一次的改動(dòng)都會(huì)牽扯到 validate_name 的改動(dòng)厂捞。 更要命的是,如果我們的數(shù)據(jù)類型發(fā)生了變化队丝,由于前端傳過來的數(shù)據(jù)都是字符串靡馁,我們需要對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換才可以保存到數(shù)據(jù)庫中,這樣就加大了我們的工作量机久。那有沒有更好的辦法呢臭墨?有,那就是我們的 Serializer 膘盖,也就是序列化器裙犹。

序列化器是什么?看它的名字我們就知道了衔憨,它是用來序列化數(shù)據(jù)的叶圃,我們?cè)诘诙轮懒耸裁词菙?shù)據(jù)的“序列化”,同時(shí)它也提供了對(duì)數(shù)據(jù)的驗(yàn)證功能践图,更棒的是掺冠,這個(gè)數(shù)據(jù)驗(yàn)證是雙向的。也就是說码党,它既可以驗(yàn)證前端上傳到后端的數(shù)據(jù)德崭,它也可以驗(yàn)證后端傳到前端的數(shù)據(jù)。前者比較好理解揖盘,那后者怎么理解呢眉厨?比如,我們前端需要的 created_time 字段的日期的格式為 '月-日-年' 兽狭,那么我們就可以在序列化器中寫好憾股,提前做好格式轉(zhuǎn)換的工作鹿蜀,把驗(yàn)證通過數(shù)據(jù)傳給前端。所以服球,我們序列化器的處理流程大概是這樣的:

client ----> views <-----> serializer <-----> model

以及茴恰,序列化器還可以規(guī)定前端可以修改哪些字段,前端可以知道哪些字段斩熊。我們只希望客戶端修改 namecode 兩個(gè)字段往枣,有的人可能會(huì)偷偷上傳 created_time 字段,要是我們沒有對(duì)它做攔截粉渠,我們的字段就會(huì)被隨意修改啦分冈,這樣可不太好。

說的很抽象霸株,我們來實(shí)際練習(xí)一下丈秩。接下來的所有代碼都是在 rest_test.py 中進(jìn)行的,大家接著剛才的代碼寫就行了淳衙。如果你對(duì)這些代碼有任何的別扭的感覺蘑秽,或者是“心頭堵的慌”的感覺,或者是產(chǎn)生了任何“這樣做好麻煩啊”之類的想法箫攀,請(qǐng)忍住肠牲,到后面你就明白了。

from rest_framework import serializers

class TestSeriOne(serializers.Serializer):
    name = serializers.CharField(max_length=20)

這樣我們就創(chuàng)建好了一個(gè)序列化器靴跛。對(duì) Django Form 很熟悉的同學(xué)或許已經(jīng)發(fā)現(xiàn)了缀雳,這不就很像是 Django 表單的寫法嗎?是的梢睛,事實(shí)上肥印,序列化器用的就是表單的邏輯,所以如果你熟悉 Django Form 的 API 绝葡,那你上手序列化器也會(huì)很快深碱。同時(shí),序列化器和表單一樣藏畅,擁有很多的字段敷硅,在之后的章節(jié)中我們會(huì)慢慢學(xué)習(xí)到它們,現(xiàn)在我們對(duì)字段的了解就先知道一點(diǎn)是一點(diǎn)愉阎。我們來使用一下我們的序列化器绞蹦。
接著在下面寫:

frontend_data = {
        'name':'ucag',
        'age':18
    }

test = TestSerilOne(data=frontend_data)
if test.is_valid():
    print(test.validated_data)

我們假設(shè)有一個(gè)前端上傳的數(shù)據(jù)為 frontend_data ,然后我們使用序列化器來驗(yàn)證上傳的數(shù)據(jù)榜旦。它的使用方法和表單一樣幽七,想要獲得合法的數(shù)據(jù)需要先運(yùn)行 .is_valid() 方法,在運(yùn)行這個(gè)方法后溅呢,如果驗(yàn)證通過澡屡,合法的數(shù)據(jù)就會(huì)被保存在 .validated_data 屬性中≡持浚現(xiàn)在直接運(yùn)行我們的 rest_test.py 腳本試試。不出意外的話挪蹭,你會(huì)看到這個(gè)結(jié)果:

OrderedDict([('name', 'ucag')])

我們可以看到亭饵,age 字段被序列化器給過濾掉了休偶,這樣就可以防止前端上傳一些奇奇怪怪的字段了梁厉。我們把剛才的序列化器修改一下,改成這個(gè)樣子:

class TestSerilOne(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    age = serializers.IntegerField()

我們新增加了一個(gè)字段踏兜。把我們的 frontend_data 改成這個(gè)樣子:

frontend_data = {
        'name':'ucag',
        'age':'18'
    }

其它什么都不變词顾,運(yùn)行 rest_test.py ,輸出變成了這樣:

OrderedDict([('name', 'ucag'), ('age', 18)])

好像沒什么不一樣碱妆。肉盹。。再仔細(xì)看看疹尾?看看 age 變成了什么上忍?我們傳進(jìn)去的數(shù)據(jù)是個(gè)字符串,但是在經(jīng)過驗(yàn)證之后纳本,它的類型就變成了整數(shù)型窍蓝!讓我們來看看,故意給它傳錯(cuò)誤的數(shù)據(jù)會(huì)發(fā)生什么繁成。
frontend_data 改成這樣:

frontend_data = {
        'name':'ucag',
        'age':'ucag'
    }

把之前的測(cè)試改成這樣:

test = TestSerilOne(data=frontend_data)
if not test.is_valid():
    print(test.errors)

輸出應(yīng)該是這樣的:

{'age': ['A valid integer is required.']}

序列化器把不符合要求的字段的錯(cuò)誤信息給放在了 .errors 屬性里吓笙。我們可以通過這個(gè)屬性來查看相應(yīng)的錯(cuò)誤信息,在前端上傳的數(shù)據(jù)出錯(cuò)的時(shí)候我們就可以直接把這個(gè)錯(cuò)誤直接發(fā)送給前端巾腕,而不用自己手寫錯(cuò)誤信息了面睛。

剛剛體驗(yàn)的是驗(yàn)證前端的數(shù)據(jù),現(xiàn)在我們來看看序列化器是怎么驗(yàn)證后端數(shù)據(jù)的尊搬。假設(shè)前端現(xiàn)在只想知道 name 字段的信息叁鉴,比如我們之前 APP 項(xiàng)目的代碼列表,我們需要顯示的僅僅就是代碼片段的名字》鹗伲現(xiàn)在就需要對(duì)后端數(shù)據(jù)做驗(yàn)證了亲茅。

注釋掉剛才做實(shí)驗(yàn)的代碼,接著在下面再創(chuàng)建一個(gè)序列化器:

# test = TestSerilOne(data=frontend_data)
# if not test.is_valid():
#     print(test.errors)

class TestSerilTwo(serializers.Serializer):
    name = serializers.CharField(max_length=20)

現(xiàn)在我們來使用它來驗(yàn)證后端的數(shù)據(jù)狗准,在下面接著寫:

from rest_learn.models import TestModel
code = TestModel.objects.get(name='ls')
test = TestSerilTwo(instance=code)
print(test.data)

運(yùn)行 rest_test.py 克锣,你的輸出會(huì)是這樣的:

{'name': 'ls'}

我們從模型中獲取了一個(gè)模型實(shí)例,然后通過 instance 參數(shù)把它放進(jìn)了序列化器里腔长,然后袭祟,我們通過 .data 屬性來訪問驗(yàn)證之后的數(shù)據(jù)±谈剑可以看到巾乳,只有 name 字段被提取了出來您没,codecreated_time胆绊、changed_time 字段都沒有出現(xiàn)在提取之后的數(shù)據(jù)里氨鹏。真的是很方便呀,那我想提取所有的模型實(shí)例該怎么辦呢压状?因?yàn)榍岸说拇a列表需要的是所有實(shí)例的名字信息啊仆抵。把我們之前做驗(yàn)證的代碼改成這樣:

from rest_learn.models import TestModel
codes = TestModel.objects.all()
test = TestSerilTwo(instance=codes,many=True)
print(test.data)

你會(huì)看到輸出是這個(gè)樣子的:

[OrderedDict([('name', 'hello world')]), OrderedDict([('name', 'pwd')]), OrderedDict([('name', 'ls')])]

此時(shí)的 .data 屬性變成了一個(gè)列表。我們提取了所有的模型實(shí)例种冬,通過 instance 參數(shù)傳遞進(jìn)了序列化器镣丑,通過 many=True 參數(shù)設(shè)置告訴序列化器我們傳進(jìn)去的是一個(gè)查詢集實(shí)例,這樣序列化器就會(huì)自己做相應(yīng)的處理了娱两。是不是特別方便莺匠?

到目前為止,我們的序列化器都是一個(gè)個(gè)字段手寫出來的十兢,通常趣竣,我們序列化的字段和模型的字段是統(tǒng)一的,那能不能通過模型來生成我們的序列化器呢旱物,就像模型表單那樣遥缕?當(dāng)然是可以的。
注釋掉之前的驗(yàn)證代碼异袄,接著在后面寫:

# from rest_learn.models import TestModel
# codes = TestModel.objects.all()
# test = TestSerilTwo(instance=codes,many=True)
# print(test.data)

from rest_learn.models import TestModel
class TestSerilThree(serializers.ModelSerializer):
    class Meta:
        model = TestModel
        fields = ['name','code','created_time','changed_time','id']
        read_only_fields = ['created_time','changed_time']

這一次通砍,我們繼承的是 DRF 的模型序列化器,通過 Meta 給模型序列化器傳模型烤蜕,通過 fields 來告訴序列化器我們需要序列化哪些字段封孙。那 read_only_fields 又是用來干什么的呢?

剛才我們說過讽营,序列化器是雙向驗(yàn)證的虎忌,對(duì)前端和后端都有驗(yàn)證。有時(shí)后端不希望某些字段被前端修改該橱鹏,這就導(dǎo)致了我們對(duì)前端和后端的序列化字段會(huì)有所不同膜蠢。一旦字段發(fā)生了變化,也就意味著序列化器也會(huì)發(fā)生變化莉兰,那該怎么辦呢挑围?那就是把我們不希望前端修改的字段放在 read_only_fields 選項(xiàng)里,這樣糖荒,當(dāng)序列化器在序列化前端的字段時(shí)杉辙,即便是前端有這些字段,序列化器也會(huì)忽略這些字段捶朵,這樣就可以防止別有用心的人暴力修改我們的字段蜘矢。

好像還不是很懂狂男?別著急,我們先用它試試看品腹,接著在下面寫:

code = TestModel.objects.get(name='ls')
codes = TestModel.objects.all()

# 前端寫入測(cè)試
frontend_data = {
    'name':'ModelSeril',
    'code':"""print('frontend test')""",
    'created_time':'2107-12-16'
}
test1 = TestSerilThree(data=frontend_data)
if test1.is_valid():
    print('Frontend test:',test1.validated_data)
# 后端傳出測(cè)試:
test2 = TestSerilThree(instance=code)
print('Backend single instance test:',test2.data)
test3 = TestSerilThree(instance=codes,many=True)
print('Backend multiple instances test',test3.data)

輸出應(yīng)該是這樣的:

Frontend test: OrderedDict([('name', 'ModelSeril'), ('code', "print('frontend test')")])

Backend single instance test: {'created_time': '2017-12-16T05:16:12.846759Z', 'name': 'ls', 'code': 'import os\r\nprint(os.listdir())', 'id': 3, 'changed_time': '2017-12-16T05:16:12.846759Z'}

Backend multiple instances test [OrderedDict([('name', 'hello world'), ('code', "print('Hello world')"), ('created_time', '2017-12-16T05:16:12.815559Z'), ('changed_time', '2017-12-16T05:16:12.815559Z'), ('id', 1)]), OrderedDict([('name', 'pwd'), ('code', 'import os\r\nprint(os.getcwd())'), ('created_time', '2017-12-16T05:16:12.831159Z'), ('changed_time', '2017-12-16T05:16:12.831159Z'), ('id', 2)]), OrderedDict([('name', 'ls'), ('code', 'import os\r\nprint(os.listdir())'), ('created_time', '2017-12-16T05:16:12.846759Z'), ('changed_time', '2017-12-16T05:16:12.846759Z'), ('id', 3)])]

我們可以看到岖食,模型序列化器正確的序列化了我們的模型實(shí)例,包括其中的 DateTimeField 字段舞吭,如果是我們手寫來處理泡垃,不知道會(huì)有多麻煩。

我們先看前端寫入的測(cè)試的輸出镣典,雖然我們的 frontend_data 有一個(gè) created_time 字段兔毙,但是在最后的 .validated_data 中根本就沒有它的身影唾琼,我們的序列化器成功的過濾掉了這個(gè)非法字段兄春。

再看后端傳出測(cè)試輸出,模型實(shí)例和查詢集實(shí)例的輸出結(jié)果都很正常锡溯。最重要的是赶舆,created_timechanged_time 兩個(gè)字段是被正常序列化了的,這兩個(gè)字段并沒有受到 read_only_fields 的影響祭饭,所以前端只能看到這個(gè)字段芜茵,不能修改這個(gè)字段。

這樣就方便許多了倡蝙!接下來我們進(jìn)入序列化器的進(jìn)階學(xué)習(xí)九串。

剛剛的序列化器結(jié)構(gòu)都很簡(jiǎn)單,使用起來也很簡(jiǎn)單寺鸥,要是有關(guān)系字段該怎么處理呢猪钮?我并不打算直接用模型序列化器來講解,因?yàn)槟P托蛄谢鞫紟臀覀儼压ぷ鞫纪瓿闪说ńǎ覀冏詈笫裁炊伎床坏娇镜汀K匀晃覀儊硎謱懸粋€(gè)能處理關(guān)系字段的序列化器。在開始之前笆载,注釋掉之前的實(shí)驗(yàn)代碼:

# code = TestModel.objects.get(name='ls')
# codes = TestModel.objects.all()

# 前端寫入測(cè)試
# frontend_data = {
#     'name':'ModelSeril',
#     'code':"""print('frontend test')""",
#     'created_time':'2107-12-16'
# }
# test1 = TestSerilThree(data=frontend_data)
# if test1.is_valid():
#     print('Frontend test:',test1.validated_data)
# 后端傳出測(cè)試:
# test2 = TestSerilThree(instance=code)
# print('Backend single instance test:',test2.data)
# test3 = TestSerilThree(instance=codes,many=True)
# print('Backend multiple instances test',test3.data)

在開始編寫之前扑馁,我們需要搞懂一個(gè)問題,序列化器到底是什么凉驻?它用起來的確很方便腻要,但是當(dāng)我們遇到問題時(shí)卻不知道從何下手,就像剛才的問題涝登,如何利用序列化器處理關(guān)系字段雄家?如果你去查看官方文檔,官方文檔會(huì)告訴你缀拭,使用 PrimaryKeyRelatedField 咳短,我相信第一次看到這個(gè)答案的你一定是一臉懵逼填帽,為什么?咙好?篡腌?為什么我的關(guān)系模型就成了一個(gè)字段了?勾效?嘹悼??我明明想要的是關(guān)系模型相關(guān)聯(lián)的實(shí)例對(duì)象啊层宫。杨伙。。你知道 PrimaryKeyRelatedField 是關(guān)系模型的主鍵萌腿。比如我們的 TestModelUser 表是關(guān)聯(lián)的限匣,如果我使用的是 PrimaryKeyRelatedField 字段,那序列化的結(jié)果出來就會(huì)是類似這樣的:

{
    user:1,
    code:'some code',
    name:'script name' 
}

TestModel 相關(guān)聯(lián)的 User 實(shí)例就變成了一個(gè)主鍵毁菱,我們可以通過訪問這個(gè)主鍵來訪問 UserTestModel 相關(guān)聯(lián)的實(shí)例米死。但是一般,我們想要的效果是這樣的:

{
    user:{
        'id':1,
        'email':'email@example.com',
        'name':'username'
    },
    code:'some code',
    name:'script name'
}

我們想要的是 User 實(shí)例的詳細(xì)信息贮庞,而不是再麻煩一次峦筒,用 PrimaryKeyRelatedField 的值再去查詢一次。而且更頭痛的是窗慎,如果使用 PrimaryKeyRelatedField, 在創(chuàng)建實(shí)例的時(shí)候物喷,你必須要先有一個(gè)相關(guān)聯(lián)的 User ,在創(chuàng)建 TestModel 時(shí)候再把這個(gè) User 的主鍵給傳進(jìn)去遮斥。也就是說峦失,你不能一次性就創(chuàng)建好 TestModelUser ,要先創(chuàng)建 User 再創(chuàng)建 TestModel伏伐,這個(gè)流程簡(jiǎn)直是讓人頭皮發(fā)麻宠进。如果我們想一次性創(chuàng)建好他們?cè)撛趺崔k呢?如果有心的同學(xué)去看看 DRF 的 release note 藐翎,就會(huì)知道材蹬,把 User 模型的序列化器當(dāng)作一個(gè)字段就行了。什么吝镣?堤器??序列化器當(dāng)成一個(gè)字段末贾?闸溃??這種操作也可以?辉川?從來沒見過這種操作啊表蝙。。在 Django 表單中也沒有見過這種操作啊乓旗。府蛇。怎么回事啊屿愚?汇跨?

淡定,同樣的妆距,我們先來做個(gè)實(shí)驗(yàn)穷遂,先體驗(yàn)下“序列化器當(dāng)作字段”是怎么回事。假設(shè)我們希望能在創(chuàng)建 User 的同時(shí)也能夠同時(shí)創(chuàng)建Profile娱据。 在 rest_test.py 下面接著寫:

class ProfileSerializer(serializers.Serializer):
    tel = serializers.CharField(max_length=15)
    height = serializers.IntegerField()

class UserSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    qq = serializers.CharField(max_length=15)
    profile = ProfileSerializer()

我們可以看到蚪黑,UserSerializerprofile 字段是 ProfileSerializer 。現(xiàn)在我們使用下這個(gè)序列化器吸耿。接著在下面寫:

frontend_data = {
    'name':'ucag',
    'qq':'88888888',
    'profile':{
        'tel':'66666666666',
        'height':'185'
    }
}

test = UserSerializer(data=frontend_data)
if test.is_valid():
    print(test.validated_data)

我們可以看到輸出是這樣的:

OrderedDict([('name', 'ucag'), ('qq', '88888888'), ('profile', OrderedDict([('tel', '66666666666'), ('height', 185)]))])

可以看到祠锣,我們的字段都被正確的序列化了酷窥。我們同時(shí)創(chuàng)建了 UserProfile 咽安。并且他們也是正確的關(guān)聯(lián)在了一起。

現(xiàn)在可以問蓬推,這是怎么回事呢妆棒?這是因?yàn)樾蛄谢髌鋵?shí)就是一個(gè)特殊的“序列化器字段”。怎么理解呢沸伏?再說的容易懂一點(diǎn)糕珊,因?yàn)樾蛄谢骱托蛄谢侄味枷喈?dāng)于 python 的同一種數(shù)據(jù)結(jié)構(gòu)——描述符。那描述符又是什么東西呢毅糟?官方文檔是這么說的:

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

一般地红选,描述符是擁有“綁定行為”的對(duì)象屬性,當(dāng)這個(gè)屬性被訪問時(shí)姆另,它的默認(rèn)行為會(huì)被描述符內(nèi)的方法覆蓋喇肋。這些方法是 __get__(), __set__(), __delete__() 。任何一個(gè)定義的有以上方法的對(duì)象都可以被稱為描述符迹辐。

說的太繞了蝶防,我們來簡(jiǎn)化一下。

  1. 描述符是有默認(rèn)行為的屬性明吩。
  2. 描述符是擁有 __get__(), __set__(), __delete__() 三者或者三者之一的對(duì)象间学。

所以,描述符是屬性,描述符也是對(duì)象低葫。

我們先來理解第一條详羡。描述符是屬性。什么是屬性呢嘿悬?對(duì)于 a.b 來說殷绍,b 就是屬性。這個(gè)屬性可以是任何東西鹊漠,可以是個(gè)方法主到,也可以是個(gè)值,也可以是任何其它的數(shù)據(jù)結(jié)構(gòu)躯概。當(dāng)我們寫 a.b 時(shí)就是在訪問 b 這個(gè)屬性登钥。
再理解第二條。描述符是對(duì)象娶靡。對(duì)象是什么呢牧牢?通常,我們都是使用 class 來定義一個(gè)對(duì)象姿锭。根據(jù)描述符定義塔鳍,有 __get__(), __set__(), __delete__() 之一或全部方法的對(duì)象都是描述符。

滿足以上兩個(gè)條件呻此,就可以說這個(gè)對(duì)象是描述符轮纫。

一般地,__get__(), __set__(), __delete__() 應(yīng)該按照如下方式編寫:

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

一般地焚鲜,描述符是作為對(duì)象屬性來使用的掌唾。

當(dāng)描述符是一個(gè)對(duì)象的屬性時(shí),如 a.b 忿磅,b 為一個(gè)描述符糯彬,則執(zhí)行a.b 相當(dāng)于執(zhí)行b.__get__(a)。 而 b.__get__(a) 的具體實(shí)現(xiàn)為 type(a).__dict__['b'].__get__(a, type(a)) 葱她。以上這個(gè)過程沒有為什么撩扒,因?yàn)?python 的實(shí)現(xiàn)就是這樣的。我們唯一需要理解的就是吨些,為什么會(huì)這樣實(shí)現(xiàn)搓谆。首先我們需要讀懂這個(gè)實(shí)現(xiàn)。

假設(shè)锤灿,a.b 中挽拔,aA 的實(shí)例,b 是描述符 B 的實(shí)例但校。

  1. type(a) 返回的是 a 的類型 A螃诅。那就變成了 A.__dict__['b'].__get__(a, type(a))

  2. A.__dict__['b'] 返回的是 A類屬性 b 的值。假設(shè) A.__dict__['b'] 的值為 Ab术裸,那么就變成了 Ab.__get__(a, type(a))倘是。

    我們?cè)谶@里暫停一下。注意 A.__dict__['b'] 返回的是 A 的類屬性袭艺,b 的值是一個(gè)描述符搀崭,也就是說,Ab 是個(gè)描述符猾编。那么連起來瘤睹,就變成了:

    • Ab ,也就是 b 答倡,是一個(gè)類屬性轰传,這個(gè)類屬性是個(gè)描述符。也就是描述符 B 的實(shí)例 A類屬性瘪撇。
  3. 最后一步就很簡(jiǎn)單了获茬,就是調(diào)用描述符的 __get__() 方法,也就是 Ab.__get__(a, A)倔既,也就是 b.__get__(a, A) 恕曲。到這里,大家可能會(huì)問一個(gè)問題渤涌,__get__ 的參數(shù)也就是 aA 是誰傳進(jìn)去的呢佩谣?,答案說出來很簡(jiǎn)單歼捏,但是很多時(shí)候有的同學(xué)容易繞進(jìn)去就出不來了稿存。答案就是:
    python 解釋器自己傳進(jìn)去的。就像是類方法的 self 一樣瞳秽,沒誰手動(dòng)傳 self 進(jìn)去,這都是 python 的設(shè)計(jì)者這樣設(shè)計(jì)的率翅。

一句話總結(jié)一下练俐。 當(dāng) bA 類屬性且為描述符時(shí),A 的實(shí)例 a 對(duì)于 b 訪問也就是a.b 就相當(dāng)于 b.__get__(a, A)冕臭。所以腺晾,此時(shí),對(duì)于 b 屬性的訪問結(jié)果就取決于 b__get__() 返回的結(jié)果了辜贵。

我們稍微再推理一下,就可以知道,如果一個(gè)對(duì)象的屬性是描述符對(duì)象震庭,而且這個(gè)對(duì)象本身也是描述符的話担钮,那么,這個(gè)對(duì)象的各種子類就可以相互作為彼此的屬性。說的很復(fù)雜蕉世,舉個(gè)簡(jiǎn)單的例子蔼紧。

我們來簡(jiǎn)單的運(yùn)用下剛才學(xué)到的知識(shí),在解釋器里輸入以下代碼:

In [1]: class Person: # 定義 Person 描述符
   ...:     def __init__(self, name=None):
   ...:         self.name = name
   ...:     def __set__(self, obj, value):
   ...:         if isinstance(value, str):
   ...:             self.name = value
   ...:         else:
   ...:             print('str is required!')
   ...:     def __get__(self, obj, objtype):
   ...:         return 'Person(name={})'.format(s
   ...: elf.name)
   ...: class Dad(Person):
   ...:     kid = Person('Son')
   ...: class Grandpa(Person):
   ...:     kid = Dad('Dad')
   ...:
   ...: dad = Dad('Dad')
   ...: gp = Grandpa('Granpa')
   ...:
   ...: print("Dad's kid:",dad.kid)
   ...: print("Grandpa's kid:",gp.kid)
   ...:
Dad's kid: Person(name=Son)
Grandpa's kid: Person(name=Dad)

In [2]: dad.kid = 18
str is required!

In [3]: dad.kid
Out[3]: 'Person(name=Son)'

可以看到狠轻,我們?cè)诙x描述符之后奸例,除了直接實(shí)例化使用他們之外,還把他們作為其它描述符的屬性向楼。描述符 Dad 的屬性 kid 也是一個(gè)描述符查吊。 我們的對(duì) kid 的賦值成功被 __set__ 攔截,并在賦值類型不規(guī)范時(shí)給出了我們事先寫好的警告湖蜕,并且原來的值也沒有被改變菩貌。

現(xiàn)在我們回到序列化器中來。序列化器和序列化器字段就是像這樣的描述符重荠,他們完全是同一種東西箭阶。所以他們完全可以作為彼此的類屬性來使用。一旦明白了這一點(diǎn)戈鲁,就可以有各種“騷操作”了仇参。序列化器最基本的字段描述符定義了字段的操作,所以不用我們自己重新去編寫 __get__ __set__ __delete__ 婆殿,DRF 已經(jīng)編寫好基本的邏輯诈乒,我們只需要調(diào)用現(xiàn)成的接口就可以實(shí)現(xiàn)自定義字段。在簡(jiǎn)單的繼承 serializers.Field 后就可以使用這些現(xiàn)成的接口了婆芦,這個(gè)接口是:
.to_representation(obj).to_internal_value(data) 怕磨。

  • .to_representation(obj): 它決定在訪問這個(gè)字段時(shí)的返回值應(yīng)該如何展示,obj 是 to_internal_value 返回的對(duì)象消约。 作用如同描述符的__get__肠鲫。應(yīng)該返回能夠被序列化的數(shù)據(jù)結(jié)構(gòu)。如數(shù)字或粮,字符串导饲,布爾值 date/time/datetime 或者 None
  • .to_internal_value(data): 它決定在對(duì)這個(gè)字段賦值時(shí)應(yīng)該進(jìn)行的操作氯材,data 是前端傳過來的字段值渣锦。作用如同描述符的__set__ 操作,應(yīng)該返回一個(gè) python 數(shù)據(jù)結(jié)構(gòu)氢哮。在發(fā)生錯(cuò)誤時(shí)袋毙,應(yīng)拋出 serializers.ValidationError

我們現(xiàn)在可以自己定義一個(gè)字段試試看冗尤,注釋掉之前的測(cè)試听盖,接著 rest_test.py 寫:

# frontend_data = {
#     'name':'ucag',
#     'qq':'88888888',
#     'profile':{
#         'tel':'66666666666',
#         'height':'185'
#     }
# }

# test = UserSerializer(data=frontend_data)
# if test.is_valid():
#     print(test.validated_data)
class TEL(object):
    """電話號(hào)碼對(duì)象"""
    def __init__(self, num=None):
        self.num = num
    def text(self, message):
        """發(fā)短信功能"""
        return self._send_message(message)
    def _send_message(self,message):
        """發(fā)短信"""
        print('Send {} to {}'.format(message[:10], self.num))
class TELField(serializers.Field):
    def to_representation(self, tel_obj):
        return tel_obj.num
    def to_internal_value(self, data):
        data = data.lstrip().rstrip().strip()
        if 8 <= len(data) <=11:
            return TEL(num=data)
        raise serializers.ValidationError('Invalid telephone number.')

這樣就完成了我們的“騷操作”字段胀溺。我們就可以這樣使用它,接著在下面寫:

class ContactSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    tel = TELField()

frontend_data = {
    'name':'ucag',
    'tel':'88888888'
}
test = ContactSerializer(data=frontend_data)
if test.is_valid():
    tel = test.validated_data['tel']
    print('TEL',tel.num)
    tel.text('這是一個(gè)騷字段')

直接運(yùn)行 rest_test.py媳溺,輸出如下:

TEL 88888888
Send 這是一個(gè)騷字段 to 88888888

我們自定義的字段就完成了月幌。

以上就是我們對(duì)序列化器的學(xué)習(xí)。目前我們就學(xué)習(xí)到這個(gè)程度悬蔽,序列化器剩下知識(shí)的都是一些 API 相關(guān)的信息扯躺,需要用到的時(shí)候直接去查就是了。我們已經(jīng)明白了序列化器的原理蝎困。以后遇到什么樣的數(shù)據(jù)類型處理都不怕了录语,要是遇到太奇葩的的需求,大不了我們自己寫一個(gè)字段禾乘。相關(guān)的細(xì)節(jié)我們?cè)谝院蟮膶W(xué)習(xí)中慢慢學(xué)習(xí)澎埠。

API View 與 URL 配置

這是 DRF 的又一個(gè)很重要的地方,在第二章始藕,我們自己編寫了 APIView 蒲稳,并且只支持一種內(nèi)容協(xié)商,DRF 為我們提供了功能更加完備的 APIView, 不僅支持多種內(nèi)容協(xié)商伍派,還支持對(duì) API 訪問頻率的控制江耀,對(duì)查詢結(jié)果過濾等等。

DRF 的 API 視圖有兩種使用方式诉植,一種是利用裝飾器祥国,一種是使用類視圖。

我們主要講類視圖 API 晾腔,裝飾器放在后面作為補(bǔ)充舌稀。

我們知道 Django 的視圖返回的是 HttpResponse 對(duì)象,并且默認(rèn)接收一個(gè) HttpRequest 對(duì)象灼擂,我們可以通過這個(gè)請(qǐng)求對(duì)象訪問到請(qǐng)求中響應(yīng)的數(shù)據(jù)壁查。同樣的,DRF 的 APIView 也是接收一個(gè)默認(rèn)的請(qǐng)求對(duì)象缤至,返回一個(gè)響應(yīng)對(duì)象潮罪。只是在 APIView 中的請(qǐng)求和響應(yīng)對(duì)象變成了 DRF 的請(qǐng)求和響應(yīng)對(duì)象。

DRF 的請(qǐng)求對(duì)象功能比 Django 自帶的要完備很多领斥,也強(qiáng)大很多。不僅原生支持 PUT 方法沃暗,還支持對(duì) POST URL 的參數(shù)解析等眾多功能月洛。

我們來看看 DRF 的請(qǐng)求對(duì)象都有哪些功能:

  1. .data: DRF 請(qǐng)求對(duì)象的 data 屬性包含了所有的上傳對(duì)象,甚至包括文件對(duì)象孽锥!也就是說嚼黔,我們可以只通過訪問 resquest.data 就能得到所有的上傳數(shù)據(jù)细层,包括 PUT 請(qǐng)求的!還支持多種數(shù)據(jù)上傳格式唬涧,前端不僅可以以 form 的形式上傳疫赎,還可以以 json 等眾多其它形式上傳數(shù)據(jù)!
  2. .query_params: query_params 屬性包含了所有的 URL 參數(shù)碎节,不僅僅是 GET 請(qǐng)求的參數(shù)捧搞,任何請(qǐng)求方法 URL 參數(shù)都會(huì)被解析到這里。
  3. .user: 和原生的 user 屬性作用相同狮荔。
  4. .auth: 包含額外的認(rèn)證信息胎撇。

當(dāng)然,DRF 的請(qǐng)求對(duì)象不止有這些功能殖氏,還有許多其它的功能晚树,大家可以去文檔里探索一下。

DRF 的響應(yīng)對(duì)象:

DRF 響應(yīng)對(duì)象接收以下參數(shù):

  1. data: 被序列化之后的數(shù)據(jù)雅采。將被用作響應(yīng)數(shù)據(jù)傳給前端爵憎。
  2. status: 狀態(tài)碼。
  3. headers: 響應(yīng)頭婚瓜。
  4. content_type: 響應(yīng)類型宝鼓。一般不需要我們手動(dòng)設(shè)置這個(gè)字段。

讓我們來看看 DRF 的 APIView 具體的應(yīng)用方法:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import get_user_model
User = get_user_model()
class ListUsers(APIView):
    def get(self, request, format=None):
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)

這就是最簡(jiǎn)單的 APIView 了闰渔。我們的 get 函數(shù)的 format 參數(shù)是用于控制和前端的內(nèi)容協(xié)商的席函,我們可以通過判斷這個(gè)值來決定返回什么樣類型的數(shù)據(jù)。同時(shí)冈涧,APIView 還有許多其它的參數(shù)供我們使用茂附,但是目前我們就暫時(shí)先了解到這里。

別忘了督弓,我們學(xué)習(xí)的是 REST 開發(fā)营曼,對(duì)應(yīng)的請(qǐng)求有對(duì)應(yīng)普適的規(guī)則。所以愚隧,在 APIView 基礎(chǔ)之上的類視圖是十分有用的——GenericView蒂阱,它就相當(dāng)與我們之前編寫的加了各種 Mixin 操作的 APIView,只不過 GenericView 提供的操作和功能比我們自己編寫的要豐富很多狂塘。同樣的录煤,GenericView 也是通過提供各種通用使用各種 Mixin 的類屬性和方法來提供不同的功能。所以我們就在這里簡(jiǎn)單的介紹一下這些類屬性和方法:

GenericView 提供的屬性有:

  1. queryset: 和我們的 APIView 中的 queryset 作用是相同的荞胡。
  2. serializer_class: 序列化器妈踊,GenericView 將會(huì)自動(dòng)應(yīng)用這個(gè)序列化器進(jìn)行相應(yīng)的數(shù)據(jù)處理工作。
  3. lookup_field: 和我們之前編寫的 lookup_args 作用相同泪漂,只是它只有一個(gè)值廊营,默認(rèn)為 'pk' 歪泳。
  4. lookup_url_kwarg: 在 URL 參數(shù)中用于查詢實(shí)例的參數(shù),在默認(rèn)情況下露筒,它的值等于 lookup_field呐伞。
  5. pagination_class: 用于分頁的類,默認(rèn)為 rest_framework.pagination.PageNumberPagination慎式,如果使它為 None 伶氢,就可以禁用分頁功能。
  6. filter_backends: 用于過濾查詢集的過濾后端瞬捕,可以在 DEFAULT_FILTER_BACKENDS 中配置鞍历。

提供的方法有:

  1. get_queryset(self): 獲取查詢集,默認(rèn)行為和我們編寫的 get_queryset 相同肪虎。
  2. get_object(self): 獲取當(dāng)前實(shí)例對(duì)象劣砍。
  3. filter_queryset(self, queryset): 在每一次查詢之后都使用它來過濾一次查詢集。并返回新的查詢集扇救。
  4. get_serializer_class(self): 獲取序列化器刑枝,可以通過編寫這個(gè)方法做到動(dòng)態(tài)的序列化器的更換。
  5. get_serializer_context(self): 返回一個(gè)作用于序列化器的上下文字典迅腔。默認(rèn)包含了 request, view,format 鍵装畅。如果這里不懂沒關(guān)系,我們后面還會(huì)講到沧烈。

當(dāng)然掠兄,GenericView 還提供了許多其它的功能,所以想要更多了解的同學(xué)可以去查閱官方文檔锌雀。沒看的也不用擔(dān)心蚂夕,我們?cè)谥髸?huì)慢慢的涉及到更多的知識(shí)點(diǎn)。

以上都介紹的很簡(jiǎn)單腋逆,我們要重點(diǎn)介紹的是下面的 ViewSet婿牍。
什么是 ViewSetViewSet 顧名思義就是一大堆的視圖集合惩歉。為什么要把一大推的視圖集合到一起呢等脂?因?yàn)樗麄兌际峭ㄓ玫摹>唧w體現(xiàn)在哪些地方呢撑蚌?他們都是符合 REST 規(guī)范的視圖上遥,所以只需要按照 REST 規(guī)范就可以使用這些視圖了。

比如争涌,像這樣露该,這是官方文檔的例子:

# views.py
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

# urls.py
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

看,我們使用了 ViewSet 之后就不用手動(dòng)的編寫 get 等等方法了第煮,只需要編寫對(duì)應(yīng)的操作函數(shù)就可以了解幼。更讓人驚喜的是,ViewSet 的使用方法和我們之前使用了 MethodMapMixinAPIView 是一模一樣的包警。通過方法映射到具體的操作函數(shù)上來撵摆。但是含有比這樣寫更酷的方法:

# urls.py
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet, base_name='user')
urlpatterns = router.urls

通過使用 Router 來自動(dòng)生成我們需要的 API 接口。這個(gè)等會(huì)兒再說害晦。我們先說說 GenericViewSetModelViewSet 特铝。
GenericViewSet: 只是簡(jiǎn)單的添加上了 GenericView 的功能。我們重點(diǎn)說 ModelViewSet壹瘟。
如果我們想要提供的 API 功能就是默認(rèn)符合 REST 規(guī)范的 API ,要是使用 ModelViewSet 的話鲫剿,我們就只需要提供一個(gè)參數(shù)就可以解決所有問題:

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()

是的,我們的視圖就這樣就寫完了稻轨。簡(jiǎn)化到兩行灵莲,要是你愿意,也可以簡(jiǎn)化到一行殴俱。再配合 Router 使用政冻,一共不超過十行代碼就可以完成我們之前寫了好幾百行的代碼完成的功能。

這里我們只是做簡(jiǎn)單的了解线欲,等到真正需要用的時(shí)候大家才可以學(xué)習(xí)到其中的奧妙明场。我們接下來說說 Router

Router 是用來幫我們自動(dòng)生成 REST API 的李丰。就像這種:

url(r'^users/$', name='user-list'),
url(r'^users/{pk}/$', name='user-detail'),
url(r'^accounts/$', name='account-list'),
url(r'^accounts/{pk}/$', name='account-detail')

自動(dòng)生成這些 API 苦锨,這些 API 都符合 REST 規(guī)范。

Router 的使用要結(jié)合我們上面學(xué)到的知識(shí)趴泌,本節(jié)我們就以 Roter 的使用收尾舟舒。

注釋掉之前的驗(yàn)證代碼,接著在后面寫:

# frontend_data = {
#     'name':'ucag',
#     'tel':'88888888'
# }
# test = ContactSerializer(data=frontend_data)
# if test.is_valid():
#     tel = test.validated_data['tel']
#     print('TEL',tel.num)
#     tel.text('這是一個(gè)騷字段')

from rest_framework.viewsets import ModelViewSet
class TestViewSet(ModelViewSet):
    queryset = TestModel.objects.all()

from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'codes', TestViewSet)
urlpatterns = router.urls
print(urlpatterns)

使用過程不多說踱讨,都是機(jī)械式的使用魏蔗,先使用 register 注冊(cè) url,第一個(gè)參數(shù)是 url 的前綴痹筛,就是想用什么開頭莺治,比如 url(r'^users/$', name='user-list') 就是以 users 開頭。視圖的名字 Router 會(huì)自己幫你加上帚稠,就兩種名字谣旁。一個(gè)是 <prefix>-list,一個(gè)是<prefix>-detail 滋早。當(dāng)然榄审,如果你想改也是可以改的。這個(gè)留到我們以后說杆麸。

直接運(yùn)行 rest_test.py 搁进,你應(yīng)該會(huì)看到以下輸出浪感。

[<RegexURLPattern testmodel-list ^codes/$>, 
<RegexURLPattern testmodel-list ^codes\.(?P<format>[a-z0-9]+)/?$>, 
<RegexURLPattern testmodel-detail ^codes/(?P<pk>[^/.]+)/$>, 
<RegexURLPattern testmodel-detail ^codes/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>, 
<RegexURLPattern api-root ^$>, <RegexURLPattern api-root ^\.(?P<format>[a-z0-9]+)/?$>]

這就是 Router 為我們生成的 API 了。細(xì)心的同學(xué)或許已經(jīng)發(fā)現(xiàn)了饼问,還有個(gè) api-root 的視圖影兽,訪問這個(gè) API 會(huì)返回所有的 list 視圖的 API ±掣铮可以通過這些鏈接訪問到所有的實(shí)例峻堰。


我們對(duì) DRF 的初步學(xué)習(xí)就到這里。很明顯盅视,我們的本節(jié)的重點(diǎn)就是序列化器捐名,所以大家務(wù)必掌握序列化器的相關(guān)知識(shí)點(diǎn),對(duì)視圖和 URL 配置不是怎么懂都沒有什么大的問題闹击,這些都只是 DRF API 調(diào)用的問題镶蹋,唯獨(dú)序列化器的使用和原理需要大家十分扎實(shí)的掌握。所以拇砰,最低的要求是起碼在本節(jié)結(jié)束后看到使用 DRF 的代碼梅忌,能夠明白它是什么意思,能夠模仿著寫出東西除破,最好能夠舉一反三牧氮。本節(jié)涉及的知識(shí)點(diǎn)的確有些難,不過一個(gè)星期理解這些知識(shí)點(diǎn)的時(shí)間也應(yīng)該足夠了瑰枫。下一節(jié)我們就要開始 Vue 的學(xué)習(xí)踱葛,相對(duì)來說會(huì)輕松一些了。大家加油

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末光坝,一起剝皮案震驚了整個(gè)濱河市尸诽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盯另,老刑警劉巖性含,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鸳惯,居然都是意外死亡商蕴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門芝发,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绪商,“玉大人,你說我怎么就攤上這事辅鲸「裼簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)例书。 經(jīng)常有香客問我锣尉,道長(zhǎng),這世上最難降的妖魔是什么雾叭? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任悟耘,我火速辦了婚禮,結(jié)果婚禮上织狐,老公的妹妹穿的比我還像新娘。我一直安慰自己筏勒,他們只是感情好移迫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著管行,像睡著了一般厨埋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捐顷,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天荡陷,我揣著相機(jī)與錄音,去河邊找鬼迅涮。 笑死废赞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叮姑。 我是一名探鬼主播唉地,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼传透!你這毒婦竟也來了耘沼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤朱盐,失蹤者是張志新(化名)和其女友劉穎群嗤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兵琳,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狂秘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闰围。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赃绊。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖羡榴,靈堂內(nèi)的尸體忽然破棺而出碧查,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布忠售,位于F島的核電站传惠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稻扬。R本人自食惡果不足惜卦方,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泰佳。 院中可真熱鬧盼砍,春花似錦、人聲如沸逝她。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黔宛。三九已至近刘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臀晃,已是汗流浹背觉渴。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留徽惋,地道東北人案淋。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寂曹,于是被迫代替她去往敵國和親哎迄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容