Django初學(xué)者入門(mén)指南2-基礎(chǔ)知識(shí)(譯&改)

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ā)布(譯&改)

>>原文地址 By Vitor Freitas

簡(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ù)的角色的概述:

圖 1: 用戶(hù)權(quán)限用例圖
類(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)

圖 2 : 基本類(lèi)圖

花時(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)在需要的信息:

圖 3: 包含基本關(guān)系和基礎(chǔ)信息的類(lèi)圖

這個(gè)類(lèi)圖強(qiáng)調(diào)了模型之間的關(guān)系采缚,線條和箭頭最終將被轉(zhuǎn)換成字段针炉。

對(duì)于Board模型,我們將從兩個(gè)字段開(kāi)始:namedescription扳抽。name字段必須是唯一的篡帕,以避免重復(fù)的線路板名稱(chēng)。這個(gè)description只是為了給大家一個(gè)提示摔蓝,說(shuō)明這個(gè)版塊是關(guān)于什么的赂苗。

Topic模型由四個(gè)字段組成:subjectlast_update上次更新的時(shí)間贮尉,用于定義主題排序拌滋,starter用于標(biāo)識(shí)發(fā)起TopicUser,以及一個(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_byupdate_by沧竟。

最后是User模型铸敏,在類(lèi)圖中,只提到了字段username悟泵、password杈笔、emailis_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è)(1Board關(guān)聯(lián)(即不能為空)右钾,一個(gè)Board可以關(guān)聯(lián)多個(gè)Topic或者沒(méi)有(0..*)蚁吝。也就是說(shuō)Board可能沒(méi)有一個(gè)Topic而存在旱爆。

版塊和主題的一對(duì)多關(guān)系

一個(gè)Topic應(yīng)該至少有一個(gè)Post(主題帖Post),也可以有很多Post1..*)窘茁。一個(gè)Post必須與一個(gè)Topic1)關(guān)聯(lián)怀伦。

主題和帖子的一對(duì)多關(guān)系

一個(gè)Topic必須有一個(gè)且只有一個(gè)User關(guān)聯(lián):主題發(fā)起者User1)。一個(gè)User可能有許多或沒(méi)有Topic0..*)山林。

用戶(hù)和主題的一對(duì)多關(guān)系

一個(gè)Post必須有一個(gè)房待,并且只有一個(gè)UserPostcreated_by1)關(guān)聯(lián)。一個(gè)User可能創(chuàng)建了許多或沒(méi)有Post0..*)驼抹。Post還有一個(gè)updated_by字段與User關(guān)聯(lián)桑孩,多重性0..1表示updated_by字段可能為空(Post未編輯),最多只能關(guān)聯(lián)一個(gè)User(最多被1個(gè)用戶(hù)編輯)框冀。

帖子和用戶(hù)的對(duì)應(yīng)關(guān)系

<span id='Figure_4'>
繪制類(lèi)圖的另一種方法是強(qiáng)調(diào)字段而不是模型之間的關(guān)系:
</span>

圖 4: 強(qiáng)調(diào)類(lèi)字段的類(lèi)圖

上面的表示與前面的表示相同流椒,它也更接近于我們將使用Django模型API設(shè)計(jì)的內(nèi)容。在這個(gè)類(lèi)圖中明也,我們可以更清楚地看到在Post模型中宣虾,關(guān)聯(lián)topiccreated_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>

圖 5: 項(xiàng)目首頁(yè),顯示所有的版塊

如果用戶(hù)點(diǎn)擊一個(gè)版塊Board的鏈接仓犬,比如Django嗅绰,應(yīng)該顯示出所有主題Topic

圖6 : 版塊內(nèi)容,顯示該版塊下的所有主題

這里有兩個(gè)主要功能:用戶(hù)單擊“New topic(新建主題)”按鈕創(chuàng)建新主題搀继,或者用戶(hù)單擊某個(gè)主題查看或參與討論窘面。

“New topic(新建主題)”頁(yè)面:

圖7 : “New topic(新建主題)”頁(yè)面

進(jìn)入主題后的頁(yè)面,顯示帖子和討論:

圖8 :指定主題下的帖子

如果用戶(hù)單擊“Reply(回復(fù))”按鈕叽躯,將看到下面的頁(yè)面财边,并以時(shí)間倒序顯示帖子的摘要(最新消息在最上面):

圖9 :回復(fù)帖子的頁(yè)面

要繪制線框,可以使用免費(fèi)工具draw.io点骑。


模型Models

這些模型基本上是應(yīng)用程序數(shù)據(jù)庫(kù)表的表示改橘。在本節(jié)中孽文,我們要做的是創(chuàng)建上一節(jié)中建模的類(lèi)的Django表示:Board蜡歹、TopicPost赡突。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莺债、TextFieldDateTimeFieldForeignKey字段來(lái)定義模型签夭。但是Django提供了各種各樣的選項(xiàng)來(lái)表示不同類(lèi)型的數(shù)據(jù)齐邦,比如IntegerFieldBooleanField第租、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)試LibrariesAPIs功能。

使用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蚤告、templatesmyproject文件夾一起服爷,新建一個(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)行登錄小腊,輸入usernamepassword

現(xiàn)在已經(jīng)可以看到默認(rèn)為我們添加了一些功能,可以配置UsersGroups的權(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

上一節(jié):Django初學(xué)者入門(mén)指南1-初識(shí)(譯&改)

下一節(jié):Django初學(xué)者入門(mén)指南3-高級(jí)概念(譯&改)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芍秆,一起剝皮案震驚了整個(gè)濱河市惯疙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妖啥,老刑警劉巖霉颠,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荆虱,居然都是意外死亡蒿偎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)怀读,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诉位,“玉大人,你說(shuō)我怎么就攤上這事菜枷〔钥罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵犁跪,是天一觀的道長(zhǎng)椿息。 經(jīng)常有香客問(wèn)我,道長(zhǎng)坷衍,這世上最難降的妖魔是什么寝优? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮枫耳,結(jié)果婚禮上乏矾,老公的妹妹穿的比我還像新娘。我一直安慰自己迁杨,他們只是感情好钻心,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著铅协,像睡著了一般捷沸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狐史,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天痒给,我揣著相機(jī)與錄音说墨,去河邊找鬼。 笑死苍柏,一個(gè)胖子當(dāng)著我的面吹牛尼斧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播试吁,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼棺棵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了熄捍?” 一聲冷哼從身側(cè)響起烛恤,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎治唤,沒(méi)想到半個(gè)月后棒动,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宾添,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年船惨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缕陕。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粱锐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扛邑,到底是詐尸還是另有隱情怜浅,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布蔬崩,位于F島的核電站恶座,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沥阳。R本人自食惡果不足惜跨琳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桐罕。 院中可真熱鬧脉让,春花似錦、人聲如沸功炮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)薪伏。三九已至滚澜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫁怀,已是汗流浹背博秫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工潦牛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挡育。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像朴爬,于是被迫代替她去往敵國(guó)和親即寒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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