Django初學(xué)者入門(mén)指南1-初識(shí)(譯&改)
Django初學(xué)者入門(mén)指南2-基礎(chǔ)知識(shí)(譯&改)
Django初學(xué)者入門(mén)指南3-高級(jí)概念(譯&改)
Django初學(xué)者入門(mén)指南4-登錄認(rèn)證(譯&改)
Django初學(xué)者入門(mén)指南5-存儲(chǔ)數(shù)據(jù)(譯&改)
Django初學(xué)者入門(mén)指南6-基于類(lèi)的頁(yè)面(譯&改)
Django初學(xué)者入門(mén)指南7-部署發(fā)布(譯&改)
簡(jiǎn)介
歡迎來(lái)到Django教程的第二部分妻献!在上一部分中,我們安裝了所需的python3.6,并在虛擬環(huán)境中運(yùn)行django1.11忧陪,同時(shí)也已經(jīng)創(chuàng)建了我們的第一個(gè)項(xiàng)目。接下來(lái),我們將繼續(xù)在同一個(gè)項(xiàng)目中編寫(xiě)代碼脊髓。
在下一節(jié)中,我們討論將要開(kāi)發(fā)的項(xiàng)目來(lái)了解一些背景知識(shí)栅受。然后學(xué)習(xí)Django的基礎(chǔ)知識(shí):模型(models
)将硝、管理員(admin
)、視圖(views
)屏镊、模板(templates
)和路由(URLs
)依疼。
讓我們動(dòng)起來(lái)!
網(wǎng)頁(yè)面板
不知道大家是不是這樣而芥,就我個(gè)人而言律罢,通過(guò)實(shí)際的例子和代碼片段可以讓我更快地掌握想要學(xué)習(xí)的內(nèi)容。我較難通過(guò)抽象簡(jiǎn)單的例子棍丐,如Class A
误辑、Class B
和經(jīng)典示例foo(bar)
,來(lái)學(xué)習(xí)和掌握知識(shí)歌逢。所以我不打算這么干巾钉,在開(kāi)始學(xué)習(xí)模型、視圖以及其他東西前趋翻,讓我們花點(diǎn)時(shí)間來(lái)簡(jiǎn)單討論一下我們要開(kāi)發(fā)的這個(gè)項(xiàng)目睛琳。
如果你有web開(kāi)發(fā)的經(jīng)驗(yàn)盒蟆,覺(jué)得這部分比較啰嗦的話踏烙,可以簡(jiǎn)單瀏覽一下插圖了解我們要構(gòu)建的東西师骗,就可以跳到本教程的模型(models)部分。
但如果你是一位web開(kāi)發(fā)新手讨惩,那就強(qiáng)烈建議你繼續(xù)閱讀辟癌,這將給你講解關(guān)于web應(yīng)用程序建模和設(shè)計(jì)的方法,web開(kāi)發(fā)荐捻,乃至程序開(kāi)發(fā)黍少,可不僅僅知識(shí)編碼而已。
用例圖
我們的項(xiàng)目是一個(gè)論壇处面,整體思路是通過(guò)維護(hù)幾個(gè)版塊(board)厂置,它們類(lèi)似于類(lèi)別,區(qū)分各個(gè)版塊的不同內(nèi)容方向魂角。在某一版塊里昵济,用戶(hù)可以通過(guò)創(chuàng)建新的主題(topic)來(lái)開(kāi)始新的討論。在某一主題中野揪,其他用戶(hù)可以發(fā)布访忿、回復(fù)帖子(post)來(lái)參與討論。
我們需要找到一種方法來(lái)區(qū)分普通用戶(hù)和管理員用戶(hù)斯稳,因?yàn)橹挥泄芾韱T才應(yīng)該創(chuàng)建新的版塊海铆。下面是我們的主要用例和每種類(lèi)型用戶(hù)的角色的概述:
類(lèi)圖
我們可以依據(jù)用例圖來(lái)考慮項(xiàng)目的實(shí)體(entities)。實(shí)體就是是我們將創(chuàng)建的模型挣惰,它與我們的Django應(yīng)用程序?qū)⑻幚淼臄?shù)據(jù)密切相關(guān)卧斟。
為了能夠?qū)崿F(xiàn)上一節(jié)中描述的用例,我們至少需要實(shí)現(xiàn)以下模型:版塊(board)憎茂、主題(topic)唆涝、帖子(post)和用戶(hù)(user)。
花時(shí)間思考模型之間的相互關(guān)系也至關(guān)重要唇辨。圖中的實(shí)線告訴我們廊酣,在主題(topic)中,需要一個(gè)字段來(lái)標(biāo)識(shí)它屬于哪個(gè)版塊(board)赏枚。同樣亡驰,帖子(post)需要一個(gè)字段來(lái)表示它屬于哪個(gè)主題(topic),這樣就可以在討論中只列出帖子(post)在一個(gè)特定的主題(topic)中創(chuàng)建饿幅。最后凡辱,需要在主題(topic)和帖子(post)中增加字段來(lái)記錄是誰(shuí)發(fā)起了討論,這樣就可以確定誰(shuí)在發(fā)布回復(fù)栗恩。
我們還可以給版塊(board)與用戶(hù)(user)模型建立聯(lián)系透乾,這樣就可以確定誰(shuí)創(chuàng)建了一個(gè)特定的版塊(board)。但這些信息與應(yīng)用程序無(wú)關(guān)。還有其他方法可以跟蹤這些信息乳乌,稍后我們?cè)龠M(jìn)行探討捧韵。
現(xiàn)在我們已經(jīng)有了基本的類(lèi)圖,必須考慮每個(gè)模型將攜帶什么樣的信息汉操。這個(gè)過(guò)程很容易考慮得過(guò)于寬泛再来,我們?cè)囍炎⒁饬性谥匾狞c(diǎn)上,僅關(guān)注開(kāi)發(fā)所需的信息磷瘤。后面可以使用遷移(migrations)來(lái)改進(jìn)模型芒篷,我們將在下一篇教程中詳細(xì)介紹這一點(diǎn)。
下圖的設(shè)計(jì)包含了我們現(xiàn)在需要的信息:
這個(gè)類(lèi)圖強(qiáng)調(diào)了模型之間的關(guān)系采缚,線條和箭頭最終將被轉(zhuǎn)換成字段针炉。
對(duì)于Board模型,我們將從兩個(gè)字段開(kāi)始:name
和description
扳抽。name
字段必須是唯一的篡帕,以避免重復(fù)的線路板名稱(chēng)。這個(gè)description
只是為了給大家一個(gè)提示摔蓝,說(shuō)明這個(gè)版塊是關(guān)于什么的赂苗。
Topic模型由四個(gè)字段組成:subject
,last_update
上次更新的時(shí)間贮尉,用于定義主題排序拌滋,starter
用于標(biāo)識(shí)發(fā)起Topic的User,以及一個(gè)名為board
的字段猜谚,用于定義特定Topic屬于哪個(gè)Board败砂。
Post模型將有一個(gè)message
字段,該字段將用于存儲(chǔ)帖子回復(fù)的文本魏铅;一個(gè)created_at
的時(shí)間字段昌犹,主要用于在Topic內(nèi)對(duì)Post進(jìn)行排序;一個(gè)updated_at的時(shí)間字段览芳,用于記錄User何時(shí)編輯了某個(gè)帖子斜姥。與時(shí)間字段一樣,我們還需要引用User模型:created_by
和update_by
沧竟。
最后是User模型铸敏,在類(lèi)圖中,只提到了字段username
悟泵、password
杈笔、email
和is_superuser
標(biāo)志,這就是現(xiàn)在要使用的全部?jī)?nèi)容糕非。需要注意的是蒙具,我們不需要?jiǎng)?chuàng)建User模型球榆,因?yàn)镈jango已經(jīng)在contrib包中提供了一個(gè)內(nèi)置的User模型。我們可以直接使用它禁筏。
關(guān)于類(lèi)圖中的多重性(數(shù)字1
持钉、0..*
,等等)融师,這里簡(jiǎn)單說(shuō)明一下:
一個(gè)Topic必須與一個(gè)(1
)Board關(guān)聯(lián)(即不能為空)右钾,一個(gè)Board可以關(guān)聯(lián)多個(gè)Topic或者沒(méi)有(0..*
)蚁吝。也就是說(shuō)Board可能沒(méi)有一個(gè)Topic而存在旱爆。
一個(gè)Topic應(yīng)該至少有一個(gè)Post(主題帖Post),也可以有很多Post(1..*
)窘茁。一個(gè)Post必須與一個(gè)Topic(1
)關(guān)聯(lián)怀伦。
一個(gè)Topic必須有一個(gè)且只有一個(gè)User關(guān)聯(lián):主題發(fā)起者User(1
)。一個(gè)User可能有許多或沒(méi)有Topic(0..*
)山林。
一個(gè)Post必須有一個(gè)房待,并且只有一個(gè)User與Post的created_by
(1
)關(guān)聯(lián)。一個(gè)User可能創(chuàng)建了許多或沒(méi)有Post(0..*
)驼抹。Post還有一個(gè)updated_by
字段與User關(guān)聯(lián)桑孩,多重性0..1
表示updated_by
字段可能為空(Post未編輯),最多只能關(guān)聯(lián)一個(gè)User(最多被1個(gè)用戶(hù)編輯)框冀。
<span id='Figure_4'>
繪制類(lèi)圖的另一種方法是強(qiáng)調(diào)字段而不是模型之間的關(guān)系:
</span>
上面的表示與前面的表示相同流椒,它也更接近于我們將使用Django模型API設(shè)計(jì)的內(nèi)容。在這個(gè)類(lèi)圖中明也,我們可以更清楚地看到在Post模型中宣虾,關(guān)聯(lián)topic
、created_by
温数、updated_by
成為模型字段绣硝。另一個(gè)有趣的地方是,在Topic模型中撑刺,我們現(xiàn)在有一個(gè)名為posts()
的operation(一個(gè)類(lèi)方法)鹉胖。我們將通過(guò)實(shí)現(xiàn)一個(gè)反向關(guān)系來(lái)實(shí)現(xiàn)這一點(diǎn),Django將在數(shù)據(jù)庫(kù)中自動(dòng)執(zhí)行一個(gè)查詢(xún)够傍,返回屬于特定Topic的所有Post列表甫菠。
類(lèi)圖完成了,這就夠了王带!為了繪制本節(jié)中的圖表淑蔚,我使用了StarUML工具。
線框
在花了一些時(shí)間設(shè)計(jì)應(yīng)用程序模型之后愕撰,我喜歡創(chuàng)建一些線框草圖來(lái)定義需要做的事情刹衫,并且對(duì)我們的發(fā)展方向有一個(gè)清晰的了解醋寝。
然后基于這些線框,我們可以更深入地了解應(yīng)用程序中涉及的實(shí)體带迟。
<span id='Figure_5'>
首先音羞,我們需要在主頁(yè)上顯示所有的版塊Board:
</span>
如果用戶(hù)點(diǎn)擊一個(gè)版塊Board的鏈接仓犬,比如Django嗅绰,應(yīng)該顯示出所有主題Topic:
這里有兩個(gè)主要功能:用戶(hù)單擊“New topic(新建主題)”按鈕創(chuàng)建新主題搀继,或者用戶(hù)單擊某個(gè)主題查看或參與討論窘面。
“New topic(新建主題)”頁(yè)面:
進(jìn)入主題后的頁(yè)面,顯示帖子和討論:
如果用戶(hù)單擊“Reply(回復(fù))”按鈕叽躯,將看到下面的頁(yè)面财边,并以時(shí)間倒序顯示帖子的摘要(最新消息在最上面):
要繪制線框,可以使用免費(fèi)工具draw.io点骑。
模型Models
這些模型基本上是應(yīng)用程序數(shù)據(jù)庫(kù)表的表示改橘。在本節(jié)中孽文,我們要做的是創(chuàng)建上一節(jié)中建模的類(lèi)的Django表示:Board蜡歹、Topic和Post赡突。User模型已經(jīng)在Django的一個(gè)名為auth的內(nèi)置應(yīng)用程序中定義,它在INSTALLED APPS
配置下的命名空間django.contrib.auth
中.
我們將在boards/models.py文件中完成所有的模型工作袁辈,(參考 圖 4)菜谣。完成后的內(nèi)容如下:
from django.db import models
from django.contrib.auth.models import User
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
class Topic(models.Model):
subject = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now_add=True)
board = models.ForeignKey(Board, related_name='topics')
starter = models.ForeignKey(User, related_name='topics')
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, related_name='posts')
updated_by = models.ForeignKey(User, null=True, related_name='+')
所有模型都應(yīng)該是django.db.models.Model的子類(lèi)。每個(gè)類(lèi)將被轉(zhuǎn)換為database table吵瞻。每個(gè)字段是django.db.models.Field的子類(lèi)(Django core的定義)葛菇,并將被轉(zhuǎn)換為database columns。
字段CharField
橡羞、DateTimeField
等都是django.db.models.Field眯停,它們包含在Django core中,隨時(shí)可以使用卿泽。
這里我們只使用CharField
莺债、TextField
、DateTimeField
和ForeignKey
字段來(lái)定義模型签夭。但是Django提供了各種各樣的選項(xiàng)來(lái)表示不同類(lèi)型的數(shù)據(jù)齐邦,比如IntegerField
、BooleanField
第租、DecimalField
等等措拇,根據(jù)實(shí)際情況進(jìn)行定義。
有些字段需要參數(shù)慎宾,例如CharField
丐吓。我們應(yīng)該設(shè)置一個(gè)max_length
浅悉。此信息將用于創(chuàng)建database column。Django需要知道database column的大小券犁。Django Forms API還將使用max_length
參數(shù)來(lái)驗(yàn)證用戶(hù)輸入术健。這個(gè)我們后面再探討。
在Board模型定義中粘衬,我們還為name
字段設(shè)置了參數(shù)unique=True
荞估,顧名思義,這個(gè)字段將在數(shù)據(jù)庫(kù)級(jí)別強(qiáng)制唯一稚新。
在Post模型中勘伺,created_at
字段有一個(gè)可選參數(shù),auto_now_add
設(shè)置為True
枷莉。這將指示Django在創(chuàng)建Post對(duì)象時(shí)自動(dòng)設(shè)置為當(dāng)前的日期和時(shí)間娇昙。
在模型之間創(chuàng)建關(guān)系的一種方法是使用ForeignKey
字段尺迂。它將在模型之間創(chuàng)建鏈接笤妙,并在數(shù)據(jù)庫(kù)級(jí)別創(chuàng)建適當(dāng)?shù)年P(guān)系。ForeignKey
字段需要一個(gè)位置參數(shù)引用與之相關(guān)的模型噪裕。
例如蹲盘,在Topic模型中,board
字段是Board模型的ForeignKey
膳音。它告訴Django一個(gè)Topic實(shí)例只與一個(gè)Board實(shí)例相關(guān)召衔。related_name
參數(shù)將用于創(chuàng)建反向關(guān)系,其中Board實(shí)例將訪問(wèn)屬于它的Topic實(shí)例列表祭陷。
Django會(huì)自動(dòng)創(chuàng)建這種反向關(guān)系苍凛,related_name
是可選的,如果我們不為它設(shè)置一個(gè)名稱(chēng)兵志,Django將用以下規(guī)則生成它:(class_name)_set
醇蝴。例如,在Board模型中想罕,Topic實(shí)例將在Topic_set
屬性下可用悠栓。我們把它改名為topics
,讓它感覺(jué)更自然按价。
在Post模型中惭适,updated_by
字段設(shè)置related_name='+'
。這個(gè)設(shè)置告訴Django我們不需要這種反向關(guān)系楼镐,所以Django將會(huì)忽略這個(gè)反向關(guān)系癞志。
下面您可以看到類(lèi)圖和用Django生成模型的源代碼之間的關(guān)系。綠線表示我們?nèi)绾翁幚矸聪蜿P(guān)系框产。
此時(shí)凄杯,你可能會(huì)問(wèn):“主鍵呢师溅?我應(yīng)該怎么處理?”如果我們不為模型指定主鍵盾舌,Django將自動(dòng)生成它墓臭。所以這樣就可以了。在下一節(jié)中妖谴,你將更好地了解它是如何工作的窿锉。
遷移模型
下一步我們讓Django來(lái)生成數(shù)據(jù)庫(kù),以供項(xiàng)目的使用膝舅。
打開(kāi)終端嗡载,啟動(dòng)虛擬環(huán)境,來(lái)到manage.py所在的文檔目錄下仍稀,執(zhí)行下面的命令:
python manage.py makemigrations
你可以看到下面的輸出文字:
Migrations for 'boards':
boards/migrations/0001_initial.py
- Create model Board
- Create model Post
- Create model Topic
- Add field topic to post
- Add field updated_by to post
這里的意思是洼滚,Django在boards/migrations目錄下創(chuàng)建了一個(gè)名為0001_initial.py的遷移文件,它描述了應(yīng)用程序模型的當(dāng)前狀態(tài)技潘,下一步Django就會(huì)使用這個(gè)文件來(lái)創(chuàng)建數(shù)據(jù)庫(kù)表和列database tables and columns遥巴。
遷移文件被轉(zhuǎn)換成SQL語(yǔ)句。如果您熟悉SQL享幽,可以運(yùn)行以下命令來(lái)檢查將在數(shù)據(jù)庫(kù)中執(zhí)行的SQL指令:
python manage.py sqlmigrate boards 0001
如果你不熟悉SQL铲掐,不用擔(dān)心。在本教程系列中值桩,我們不會(huì)直接使用SQL摆霉。所有的工作都將使用Django ORM自動(dòng)完成,這是一個(gè)與數(shù)據(jù)庫(kù)通信的抽象層奔坟。
下面的命令是將生成的遷移文件應(yīng)用到數(shù)據(jù)庫(kù)中:
python manage.py migrate
接下來(lái)你應(yīng)該會(huì)看到如下的輸出文字:
Operations to perform:
Apply all migrations: admin, auth, boards, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying boards.0001_initial... OK
Applying sessions.0001_initial... OK
因?yàn)檫@是我們首次遷移數(shù)據(jù)庫(kù)携栋,migrate
命令還應(yīng)用了Django contrib內(nèi)置應(yīng)用程序中的遷移文件,這些文件列在INSTALLED_apps
里咳秉。
Applying boards.0001_initial... OK
指的就是我們前面生成的遷移文件婉支。
好了,數(shù)據(jù)庫(kù)準(zhǔn)備好了滴某,我們可以繼續(xù)開(kāi)發(fā)了磅摹!
** 注意:** SQLite是一款可以在生產(chǎn)環(huán)境直接使用的數(shù)據(jù)庫(kù)產(chǎn)品,許多公司的成千上萬(wàn)個(gè)產(chǎn)品都是使用的SQLite數(shù)據(jù)庫(kù)霎奢,如Android和iOS設(shè)備户誓、還有主流的網(wǎng)絡(luò)瀏覽器、Windows 10幕侠、macOS等等帝美。但它并不適用于所有場(chǎng)景,SQLite在大容量網(wǎng)站晤硕、寫(xiě)密集應(yīng)用悼潭、巨型數(shù)據(jù)集庇忌、高并發(fā)的使用場(chǎng)景中的表現(xiàn),不如MySQL舰褪、PostgreSQL皆疹、Oracle等數(shù)據(jù)庫(kù)。
我們將在項(xiàng)目開(kāi)發(fā)期間使用SQLite占拍,因?yàn)樗芊奖懵跃停也恍枰惭b任何其他東西。需要將項(xiàng)目部署到生產(chǎn)環(huán)境中時(shí)晃酒,我們將切換到PostgreSQL表牢,對(duì)于簡(jiǎn)單的網(wǎng)站來(lái)說(shuō)這樣就可以了。但對(duì)于復(fù)雜的網(wǎng)站贝次,最好使用相同的數(shù)據(jù)庫(kù)進(jìn)行開(kāi)發(fā)和生產(chǎn)崔兴。
嘗試使用Models API
使用Python進(jìn)行開(kāi)發(fā)的最大優(yōu)點(diǎn)之一是交互式shell。我一直在用它蛔翅,這是一種快速的方法敲茄,可以用它調(diào)試Libraries和APIs功能。
使用manage.py工具可以直接啟動(dòng)shell:
python manage.py shell
這與只需鍵入python
調(diào)用交互式控制臺(tái)非常相似搁宾,使用python manage.py shell
會(huì)自動(dòng)將我們的項(xiàng)目添加到sys.path
并加載Django折汞,這意味著我們可以導(dǎo)入項(xiàng)目的模型和任何其他資源并使用它。
讓我們首先引入Board類(lèi):
from boards.models import Board
通過(guò)下面的代碼創(chuàng)建一個(gè)Board對(duì)象:
board = Board(name='Django', description='This is a board about Django.')
如果需要將該對(duì)象的數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)中盖腿,只需要像下面一樣,調(diào)用save
方法:
board.save()
這個(gè)save
方法可以創(chuàng)建或者更新對(duì)象的數(shù)據(jù)损同。我們這里創(chuàng)建Board對(duì)象時(shí)并未賦值id翩腐,Django會(huì)自動(dòng)為它分配一個(gè)值,通過(guò)下面的方式我們可以查看值:
board.id
1
你同樣可以通過(guò)這種方式訪問(wèn)該對(duì)象的其他屬性:
board.name
'Django'
board.description
'This is a board about Django.'
當(dāng)需要更新對(duì)象的屬性時(shí)膏燃,按照下面的方法即可:
board.description = 'Django discussion board.'
board.save()
Django模型類(lèi)都有一個(gè)特殊屬性茂卦,我們稱(chēng)之為模型管理器(Model Manager)。我們主要在執(zhí)行查詢(xún)語(yǔ)句時(shí)使用這個(gè)屬性组哩,可以通過(guò)objects
屬性去訪問(wèn)它等龙。舉個(gè)例子,我們可以用它來(lái)直接創(chuàng)建Board對(duì)象:
board = Board.objects.create(name='Python', description='General discussion about Python.')
board.id
2
board.name
'Python'
現(xiàn)在伶贰,我們就有兩個(gè)版塊了蛛砰,可以通過(guò)objects
來(lái)查看所有已經(jīng)存儲(chǔ)在數(shù)據(jù)庫(kù)中的版塊對(duì)象數(shù)據(jù):
Board.objects.all()
<QuerySet [<Board: Board object>, <Board: Board object>]>
這個(gè)查詢(xún)結(jié)果是一個(gè)查詢(xún)數(shù)據(jù)集QuerySet,在后面的教程中我們會(huì)深入探討黍衙。這個(gè)數(shù)據(jù)集就是在數(shù)據(jù)庫(kù)中讀取出來(lái)的對(duì)象列表泥畅。這里我們可以看出,數(shù)據(jù)庫(kù)中存儲(chǔ)了2條版塊數(shù)據(jù)對(duì)象琅翻,打印的時(shí)候輸出為對(duì)象類(lèi)型Board object位仁,這是因?yàn)槲覀儧](méi)有定義和實(shí)現(xiàn)Board類(lèi)的__str__
方法柑贞。
__str__
方法是對(duì)象的字符串描述,這里我們可以返回版塊的名稱(chēng)聂抢。
先讓我們離開(kāi)shell:
exit()
編輯boards應(yīng)用程序文件目錄下的models.py:
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
現(xiàn)在讓我們?cè)賮?lái)試試:
python manage.py shell
from boards.models import Board
Board.objects.all()
<QuerySet [<Board: Django>, <Board: Python>]>
現(xiàn)在看起來(lái)更清楚了對(duì)吧钧嘶?
我們可以把QuerySet看成一個(gè)列表。比方說(shuō)需要將列表里的數(shù)據(jù)枚舉出來(lái)并逐個(gè)打印描述:
boards_list = Board.objects.all()
for board in boards_list:
print(board.description)
它的輸出應(yīng)該是下面這個(gè):
Django discussion board.
General discussion about Python.
同樣的琳疏,當(dāng)我們想用模型管理器(Model Manager)查詢(xún)某一個(gè)對(duì)象時(shí)康辑,可以使用get
方法:
django_board = Board.objects.get(id=1)
django_board.name
'Django'
在使用get
方法時(shí)一定要注意,如果我們?cè)噲D獲取一個(gè)不存在的對(duì)象轿亮,比如說(shuō)id=3
的版塊疮薇,它將拋出一個(gè)異常boards.models.DoesNotExist
:
board = Board.objects.get(id=3)
boards.models.DoesNotExist: Board matching query does not exist.
我們也可以在get
方法里使用其他的對(duì)象屬性進(jìn)行查詢(xún),但最好是可以定位到某一個(gè)對(duì)象的屬性值我注,否則這個(gè)查詢(xún)方法將返回多個(gè)結(jié)果可能導(dǎo)致錯(cuò)誤按咒。
Board.objects.get(name='Django')
<Board: Django>
也需要注意查詢(xún)語(yǔ)句是對(duì)大小寫(xiě)敏感的case sensitive,通過(guò)django
你將查找不到你想要的版塊:
Board.objects.get(name='django')
boards.models.DoesNotExist: Board matching query does not exist.
模型操作摘要
下面是我們?cè)诒竟?jié)中學(xué)習(xí)的方法和操作的摘要但骨,以Board模型為參考励七。大寫(xiě)Board表示類(lèi),小寫(xiě)board
表示Board模型類(lèi)的實(shí)例(或?qū)ο螅?/p>
操作 | 示例代碼 |
---|---|
創(chuàng)建一個(gè)對(duì)象奔缠,但不保存到數(shù)據(jù)庫(kù) | board = Board() |
保存或更新一個(gè)數(shù)據(jù) | board.save() |
創(chuàng)建一個(gè)對(duì)象掠抬,并保存到數(shù)據(jù)庫(kù) | Board.objects.create(name='...', description='...') |
查詢(xún)所有的對(duì)象,返回查詢(xún)集 | Board.objects.all() |
通過(guò)屬性查詢(xún)符合條件的對(duì)象 | Board.objects.get(id=1) |
在下一節(jié)中校哎,我們將開(kāi)始編寫(xiě)頁(yè)面并在HTML頁(yè)面中顯示版塊Board两波。
頁(yè)面視圖(Views),模版(Templates),和靜態(tài)文件(Static Files)
現(xiàn)在我們的應(yīng)用已經(jīng)有一個(gè)顯示Hello, World!
的頁(yè)面home
。
<details>
<summary>原始版本</summary>
原始版本的myproject/urls.py
from django.conf.urls import url
from django.contrib import admin
from boards import views
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^admin/', admin.site.urls),
]
</details>
修訂版本的myproject/urls.py
from django.urls import re_path
from django.contrib import admin
from boards import views
urlpatterns = [
re_path(r'^$', views.home, name='home'),
re_path(r'^admin/', admin.site.urls),
]
boards/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse('Hello, World!')
我們可以將這個(gè)頁(yè)面作為第一個(gè)頁(yè)面來(lái)繼續(xù)開(kāi)發(fā)闷哆,圖 5設(shè)計(jì)了首頁(yè)的樣式腰奋,在一個(gè)表格中展示版塊列表和版塊的部分信息。
首先要做的就是引入Board模型抱怔,并列舉出所有的版塊對(duì)象:
boards/views.py
from django.http import HttpResponse
from .models import Board
def home(request):
boards = Board.objects.all()
boards_names = list()
for board in boards:
boards_names.append(board.name)
response_html = '<br>'.join(boards_names)
return HttpResponse(response_html)
保存后刷新頁(yè)面你將看到下圖所示的樣子:
這里就到此為止吧劣坊,我們不會(huì)像這樣渲染HTML。對(duì)于這個(gè)簡(jiǎn)單的視圖屈留,只需要一個(gè)版塊對(duì)象的列表局冰,然后頁(yè)面渲染部分就交給Django Template Engine來(lái)完成吧。
Django Template Engine(模板引擎)
在項(xiàng)目目錄下灌危,與boards同級(jí)的位置創(chuàng)建一個(gè)名為templates的文件夾:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/ <-- 是的康二,放這里!
| +-- manage.py
+-- venv/
在templates目錄下,創(chuàng)建一個(gè)html文件乍狐,取名為home.html:
templates/home.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
</head>
<body>
<h1>Boards</h1>
{% for board in boards %}
{{ board.name }} <br>
{% endfor %}
</body>
</html>
在上面的例子中赠摇,我們將原始HTML與一些特殊標(biāo)記{% for ... in ... %}
和{{ variable }}
混合使用,這是Django模板語(yǔ)言的一部分。上面的示例演示了如何使用for
遍歷對(duì)象列表藕帜,{{ board.name }}
在HTML模板中讀取版塊的名稱(chēng)烫罩,生成一個(gè)動(dòng)態(tài)HTML文檔。
在使用這個(gè)HTML頁(yè)面之前洽故,我們必須告訴Django在哪里可以找到應(yīng)用程序的模板贝攒。
打開(kāi)在myproject目錄中的settings.py,搜索TEMPLATES
變量并將DIRS
鍵設(shè)置為os.path.join(BASE_DIR, 'templates')
:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates')
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
這行代碼的意思就是將項(xiàng)目位置和/templates
拼起來(lái)时甚,這樣就可以得到templates的完整路徑.
我們可以在python shell中進(jìn)行調(diào)試:
python manage.py shell
from django.conf import settings
settings.BASE_DIR
'/Users/vitorfs/Development/myproject'
import os
os.path.join(settings.BASE_DIR, 'templates')
'/Users/vitorfs/Development/myproject/templates'
從上面可以看出隘弊,這個(gè)方式可以獲取到templates文件夾的完整路徑.
現(xiàn)在讓我們來(lái)更新home頁(yè)面:
boards/views.py
from django.shortcuts import render
from .models import Board
def home(request):
boards = Board.objects.all()
return render(request, 'home.html', {'boards': boards})
可以得到下面的頁(yè)面:
我們?cè)賰?yōu)化HTML模板,添加一個(gè)列表:
templates/home.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
</head>
<body>
<h1>Boards</h1>
<table border="1">
<thead>
<tr>
<th>Board</th>
<th>Posts</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
{% for board in boards %}
<tr>
<td>
{{ board.name }}<br>
<small style="color: #888">{{ board.description }}</small>
</td>
<td>0</td>
<td>0</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
測(cè)試首頁(yè)(homepage)
測(cè)試將始終貫穿這個(gè)教程荒适,我們將討論不同的測(cè)試概念和策略梨熙。
讓我們來(lái)創(chuàng)建第一個(gè)測(cè)試用例,這里將會(huì)用到boards應(yīng)用程序目錄下的tests.py文件:
boards/tests.py
from django.core.urlresolvers import reverse
from django.test import TestCase
class HomeTests(TestCase):
def test_home_view_status_code(self):
url = reverse('home')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
這是一個(gè)簡(jiǎn)單的測(cè)試用例刀诬,但是它非常有用咽扇。我們測(cè)試請(qǐng)求的返回的狀態(tài)碼(status_code),斷言(assertEquals)返回的狀態(tài)碼為200
陕壹,200
表明請(qǐng)求成功(success).
通常我們可以直接在控制臺(tái)中看到請(qǐng)求返回的狀態(tài)碼(status_code):
如果出現(xiàn)未捕獲的異常质欲、語(yǔ)法錯(cuò)誤或其他任何情況,Django將返回狀態(tài)代碼500糠馆,這意味著Internal Server Error∷晃埃現(xiàn)在,假設(shè)我們的應(yīng)用程序有100個(gè)頁(yè)面又碌,只使用一個(gè)命令為所有視圖編寫(xiě)這個(gè)簡(jiǎn)單的測(cè)試九昧,就可以測(cè)試所有視圖是否都返回成功代碼,這樣用戶(hù)就不會(huì)在任何地方看到任何錯(cuò)誤消息赠橙。但如果沒(méi)有自動(dòng)化測(cè)試耽装,我們將需要逐個(gè)檢查所有頁(yè)面。
可以通過(guò)下面的命令執(zhí)行Django的測(cè)試:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.041s
OK
Destroying test database for alias 'default'...
現(xiàn)在我們可以測(cè)試Django是否為請(qǐng)求的URL返回了正確的view函數(shù)期揪。這也是一個(gè)有用的測(cè)試,因?yàn)殡S著開(kāi)發(fā)的進(jìn)展规个,您將看到urls.py模塊可以變得非常大和復(fù)雜凤薛,而URL路由是通過(guò)正則匹配定位的,在某些特定情況下可能匹配到目標(biāo)以外的URL诞仓,因此Django最終可能返回錯(cuò)誤的view函數(shù)缤苫。
按下面的方式編寫(xiě)測(cè)試用例:
boards/tests.py
from django.core.urlresolvers import reverse
from django.urls import resolve
from django.test import TestCase
from .views import home
class HomeTests(TestCase):
def test_home_view_status_code(self):
url = reverse('home')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
def test_home_url_resolves_home_view(self):
view = resolve('/')
self.assertEquals(view.func, home)
在第二個(gè)測(cè)試方法里,我們使用了resolve
方法墅拭,Django使用這個(gè)方法將url與urls.py中的模塊進(jìn)行匹配活玲。所以這個(gè)測(cè)試就是保證通過(guò)URL/
返回的是首頁(yè)(homepage)。
再測(cè)試一次看看:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.027s
OK
Destroying test database for alias 'default'...
如果你希望看到更多測(cè)試的詳細(xì)日志,設(shè)置verbosity
到更高的級(jí)別:
python manage.py test --verbosity=2
Creating test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Operations to perform:
Synchronize unmigrated apps: messages, staticfiles
Apply all migrations: admin, auth, boards, contenttypes, sessions
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying boards.0001_initial... OK
Applying sessions.0001_initial... OK
System check identified no issues (0 silenced).
test_home_url_resolves_home_view (boards.tests.HomeTests) ... ok
test_home_view_status_code (boards.tests.HomeTests) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.017s
OK
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
設(shè)置verbosity
參數(shù)會(huì)打印出不同程度的日志詳情:0表示無(wú)輸出舒憾、1表示正常輸出镀钓、2表示詳細(xì)輸出。
靜態(tài)文件(Static Files)配置
靜態(tài)文件指的是CSS镀迂、JavaScripts丁溅、字體、圖片或我們可以用來(lái)生成界面的任何其他資源探遵。
事實(shí)上窟赏,Django不提供這些文件,除非能在開(kāi)發(fā)過(guò)程中為我們提供更多的便利箱季。不過(guò)Django提供了一些功能來(lái)幫助我們管理靜態(tài)文件涯穷。這些功能由已在INSTALLED_APPS
配置中列出的django.contrib.staticfiles應(yīng)用程序提供。
有這么多的前端組件庫(kù)可用藏雏,沒(méi)有理由繼續(xù)使用簡(jiǎn)陋的HTML文檔拷况,我們可以很容易地將bootstrap4添加到我們的項(xiàng)目中。Bootstrap是一個(gè)開(kāi)源工具包诉稍,用于使用HTML蝠嘉、CSS和JavaScript進(jìn)行開(kāi)發(fā)。
在myproject根目錄下杯巨,與boards蚤告、templates、myproject文件夾一起服爷,新建一個(gè)名為static的文件夾杜恰,在static文件夾中再創(chuàng)建一個(gè)名為css的文件夾:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/
| |-- static/ <-- 這里
| | +-- css/ <-- 這里這里!
| +-- manage.py
+-- venv/
打開(kāi)getbootstrap.com仍源,下載他們的最新Release版本:
下載Compiled CSS and JS版本.
在電腦上打開(kāi)下載好的bootstrap-4.0.0-beta-dist.zip文件(這里可能是其他更新的版本)心褐,并將css/bootstrap.min.css拷貝到剛剛創(chuàng)建的css目錄下:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/
| |-- static/
| | +-- css/
| | +-- bootstrap.min.css <-- 這里
| +-- manage.py
+-- venv/
下一步就是配置Django項(xiàng)目,讓它能夠定位到靜態(tài)文件笼踩。打開(kāi)settings.py文件逗爹,在文檔最后面,緊跟STATIC_URL
添加下面的代碼:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
還記得嗎嚎于?跟之前配置TEMPLATES
一樣掘而。
現(xiàn)在我們需要在我們的html文件中使用這些靜態(tài)文件(Bootstrap CSS):
templates/home.html
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<!-- body suppressed for brevity ... -->
</body>
</html>
首先我們?cè)谀0遄铋_(kāi)始加上{% load static %}
。
{% static %}
這個(gè)標(biāo)簽會(huì)通過(guò)設(shè)置文件settings.py去找到資源文件的目錄STATIC_URL
于购,在這里就將{% static 'css/bootstrap.min.css' %}
表示為/static/css/bootstrap.min.css袍睡,最終就是訪問(wèn)到http://127.0.0.1:8000/static/css/bootstrap.min.css。
假如需要將STATIC_URL
改為子域名https://static.example.com/肋僧,就需要修改配置為STATIC_URL=https://static.example.com/
斑胜,這樣的話{% static 'css/bootstrap.min.css' %}
將會(huì)訪問(wèn)到https://static.example.com/css/bootstrap.min.css.
如果你還不能理解上面的工作原理控淡,別擔(dān)心,你只需要記住在需要使用CSS止潘、JavaScript或者圖片文件時(shí)增加{% static %}
標(biāo)簽就可以了掺炭。我們會(huì)在后面更加詳細(xì)的談到這個(gè)問(wèn)題,但現(xiàn)在覆山,所有的配置都搞定了竹伸。
刷新鏈接127.0.0.1:8000,我們就可以看到它的作用:
現(xiàn)在我們來(lái)編輯一下HTML模板簇宽,使用一些更友好的界面元素:
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<div class="container">
<ol class="breadcrumb my-4">
<li class="breadcrumb-item active">Boards</li>
</ol>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Board</th>
<th>Posts</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
{% for board in boards %}
<tr>
<td>
{{ board.name }}
<small class="text-muted d-block">{{ board.description }}</small>
</td>
<td class="align-middle">0</td>
<td class="align-middle">0</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
然后再刷新:
到目前為止勋篓,我們還是使用交互式控制臺(tái)(python manage.py shell
)來(lái)對(duì)數(shù)據(jù)進(jìn)行維護(hù)和管理。但我們需要一個(gè)更好的方法魏割。在下一節(jié)中譬嚣,將實(shí)現(xiàn)一個(gè)管理界面來(lái)進(jìn)行管理。
Django管理模塊簡(jiǎn)介
當(dāng)創(chuàng)建一個(gè)項(xiàng)目時(shí)钞它,Django就自動(dòng)為我們創(chuàng)建了Django Admin應(yīng)用程序并記錄在INSTALLED_APPS
下拜银。
舉兩個(gè)用戶(hù)管理權(quán)限的例子:在博客類(lèi)應(yīng)用中,作者角色有編寫(xiě)和發(fā)布文章的權(quán)限遭垛;而在電商類(lèi)網(wǎng)站中尼桶,工作人員有創(chuàng)建、編輯锯仪、刪除產(chǎn)品的權(quán)限泵督。
現(xiàn)在,我們先為Django管理員添加版塊的管理權(quán)限庶喜。
首先創(chuàng)建一個(gè)超級(jí)管理員賬戶(hù):
python manage.py createsuperuser
按照說(shuō)明補(bǔ)充信息(可以自行決定信息內(nèi)容):
Username (leave blank to use 'vitorfs'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
現(xiàn)在讓我們打開(kāi)網(wǎng)頁(yè): http://127.0.0.1:8000/admin/
使用剛剛創(chuàng)建的超級(jí)管理員進(jìn)行登錄小腊,輸入username和password:
現(xiàn)在已經(jīng)可以看到默認(rèn)為我們添加了一些功能,可以配置Users和Groups的權(quán)限久窟,稍后我們?cè)賹?duì)此進(jìn)行深入討論秩冈。
添加對(duì)版塊Board的管理功能非常簡(jiǎn)單,只需要打開(kāi)boards應(yīng)用目錄下的admin.py文件斥扛,添加下面的代碼:
boards/admin.py
from django.contrib import admin
from .models import Board
admin.site.register(Board)
保存admin.py文件入问,點(diǎn)擊刷新按鈕:
好了,現(xiàn)在我們可以點(diǎn)擊Boards去查看在數(shù)據(jù)庫(kù)中的版塊信息:
如果需要添加一個(gè)版塊到數(shù)據(jù)庫(kù)中稀颁,點(diǎn)擊Add Board按鈕:
然后點(diǎn)擊save按鈕:
我們可以打開(kāi)首頁(yè)http://127.0.0.1:8000來(lái)看看是否添加成功:
小結(jié)
在本教程中队他,我們探討了許多新概念。我們?yōu)槲覀兊捻?xiàng)目定義了一些需求峻村,創(chuàng)建了第一個(gè)模型,遷移了數(shù)據(jù)庫(kù)锡凝,開(kāi)始使用模型API粘昨。我們創(chuàng)建了第一個(gè)視圖并編寫(xiě)了一些單元測(cè)試。我們還配置了Django模板引擎、靜態(tài)文件张肾,并將bootstrap4庫(kù)添加到項(xiàng)目中芭析。最后,我們簡(jiǎn)要介紹了Django管理接口吞瞪。
項(xiàng)目的源代碼可以在GitHub上找到馁启。項(xiàng)目的當(dāng)前狀態(tài)可以在發(fā)布標(biāo)簽v0.2-lw下找到。下面的鏈接將帶您找到正確的位置:
https://github.com/sibtc/django-ginners-guide/tree/v0.2-lw