本文轉(zhuǎn)至:http://www.itdecent.cn/p/05810d38f93a
創(chuàng)建第一個(gè)django應(yīng)用
運(yùn)行以下命令:
python manage.py startapp blog
這個(gè)命令會(huì)創(chuàng)建應(yīng)用的基本目錄結(jié)構(gòu),類似下方結(jié)構(gòu):
blog/
__init__.py
admin.py
migrations/
__init__.py
models.py
tests.py
views.py
設(shè)計(jì)博客的數(shù)據(jù)模型
我們將要為你的博客設(shè)計(jì)初始的數(shù)據(jù)模型。每一個(gè)model都是一個(gè)Python類,繼承了django.db.models.model,其中的每一個(gè)屬性對(duì)應(yīng)了一個(gè)數(shù)據(jù)庫字段。Djnago將會(huì)為models.py中的每一個(gè)定義好的model創(chuàng)建好一張表。當(dāng)你創(chuàng)建好一個(gè)model,Django就能提供一個(gè)非常實(shí)用的API來方便的查詢數(shù)據(jù)庫。
首先,我們定義一個(gè)POST model。在blog應(yīng)用下的models.py文件中添加以下內(nèi)容:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique_for_date='publish')
author = models.ForeignKey(User, related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
class Meta:
ordering = ('-publish',)
def __str__(self):
return self.title
這是一個(gè)給博客帖子使用的基礎(chǔ)model。讓我們來看下剛才在model中定義的各個(gè)字段含義:
- title: 這個(gè)字段對(duì)應(yīng)帖子的標(biāo)題。這是一個(gè)CharField,在SQL數(shù)據(jù)庫中對(duì)應(yīng)VARCHAR類型
- slug:這是一個(gè)用在URLs中的字段。一個(gè)slug是一個(gè)短標(biāo)簽之包含字母,數(shù)字,下劃線或連接線。我們將通過使用slug字段來構(gòu)建一個(gè)漂亮的,友好的URLs給我們的博客帖子。我們可以給每個(gè)帖子使用日期和slug來構(gòu)建URLs通過添加 unique_for_date參數(shù)給這個(gè)字段。
- author:這是一個(gè) ForeignKey。這個(gè)字段定義了一個(gè)many-to-one(多對(duì)一)的關(guān)系。我們告訴Django每一個(gè)帖子是被一個(gè)用戶編輯而一個(gè)用戶又能編輯多個(gè)帖子。通過這個(gè)字段,Django將會(huì)在數(shù)據(jù)庫中通過有關(guān)聯(lián)的model主鍵來創(chuàng)建一個(gè)外鍵。在這個(gè)例子中,我們關(guān)聯(lián)上了django權(quán)限系統(tǒng)的User model。我們指定了User和Post之間關(guān)聯(lián)的名字,通過related_name屬性。在之后我們將會(huì)學(xué)習(xí)到更多相關(guān)的內(nèi)容。
- body:這是帖子的內(nèi)容。這個(gè)字段是TextField,在SQL數(shù)據(jù)中對(duì)應(yīng)TEXT類型。
- publish:這個(gè)字段存儲(chǔ)了帖子發(fā)布的時(shí)間。我們使用Djnago的 timezone now方法來設(shè)定默認(rèn)值。
- created:這個(gè)字段存儲(chǔ)了帖子創(chuàng)建的時(shí)間。因?yàn)槲覀冊(cè)谶@兒使用了auto_now_add,當(dāng)一個(gè)對(duì)象被創(chuàng)建的時(shí)候這個(gè)字段會(huì)自動(dòng)保存當(dāng)前日期。
- updated:這個(gè)字段存儲(chǔ)了帖子更新的時(shí)間。因?yàn)槲覀冊(cè)谶@兒使用了auto_now,當(dāng)我們保存一個(gè)對(duì)象的時(shí)候當(dāng)前字段將會(huì)自動(dòng)更新到當(dāng)前日期。
- status:這個(gè)字段是用來展示帖子的狀態(tài)。我們使用了一個(gè)choices參數(shù),所有這個(gè)字段的值只能在給予的選擇內(nèi)容中選擇。
就像你所看到的的,Django帶來了不同的字段類型所以你能夠定義你的models。你可以找到所有的字段類型通過訪問 Django Model field reference
在model中的Meta類包含元數(shù)據(jù)。我們告訴Django查詢數(shù)據(jù)庫的時(shí)候默認(rèn)返回的是根據(jù)publish字段進(jìn)行降序排列過的結(jié)果。我們使用負(fù)號(hào)來指定降序排列。
str()方法是對(duì)象默認(rèn)的可讀表現(xiàn)。Django將會(huì)在很多地方用到它例如管理平臺(tái)。
如果你之前使用過Python2.X,請(qǐng)注意在Python2中所有的strings都使用unicode,因?yàn)槲覀兊沫h(huán)境是python3,所以使用
str()方法。unicode()方法已經(jīng)廢棄。
Django內(nèi)置支持對(duì)日期時(shí)區(qū)的處理。你可以在項(xiàng)目的settings.py文件中通過USE_TZ來設(shè)置激活或停用對(duì)時(shí)區(qū)的支持。當(dāng)你通過startproject命令來創(chuàng)建一個(gè)新項(xiàng)目的時(shí)候這個(gè)設(shè)置默認(rèn)為True。
激活你的應(yīng)用
為了讓Django能保持跟蹤你的應(yīng)用并且通過models來創(chuàng)建數(shù)據(jù)表,我們必須激活你的應(yīng)用。為起到效果,編輯settings.py文件在INSTALLED_APPS設(shè)置中添加blog。類似如下顯示:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
)
現(xiàn)在Django已經(jīng)知道我們項(xiàng)目中的應(yīng)用是激活的狀態(tài)并且將會(huì)審查其中的models。
創(chuàng)建和進(jìn)行數(shù)據(jù)庫遷移
讓我們?yōu)閙odel在數(shù)據(jù)庫中創(chuàng)建一張表格。Django自帶一個(gè)遷移系統(tǒng)來跟蹤每一次models的變化并且會(huì)同步到數(shù)據(jù)庫。migrate命令會(huì)應(yīng)用到所有在INSTALLED_APPS中的應(yīng)用,它會(huì)根據(jù)models來同步數(shù)據(jù)庫。
python manage.py makemigrations blog
你會(huì)看到以下輸出:
Migrations for 'blog':
0001_initial.py;
- Create model Post
Django已經(jīng)在blog應(yīng)用下的migrations目錄中創(chuàng)建了一個(gè)0001——initial.py文件。你可以打開這個(gè)文件來看下。
讓我們來看下Django根據(jù)我們的model將會(huì)為在數(shù)據(jù)庫中創(chuàng)建的表格而執(zhí)行的SQL代碼。sqlmigrate命令會(huì)返回一串SQL而不會(huì)去執(zhí)行。執(zhí)行以下命令來看下輸出:
python manage.py sqlmigrate blog 0001
輸出類似如下:
BEGIN;
--
-- Create model Post
--
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created" datetime NOT N
ULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");
CREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");
COMMIT;
這些輸出是根據(jù)你正在使用的數(shù)據(jù)庫。這些SQL語句是為SQLite準(zhǔn)備的。就像你所看見的,Django生成的表名前綴為應(yīng)用名之后跟上modle的小寫(blog_post),但是你也可以自定義表名在models的Meta類中通過db_table屬性。Django會(huì)自動(dòng)為每個(gè)model創(chuàng)建一個(gè)主鍵,但是你可以自己指定主鍵通過設(shè)置primarry_key=True在期中一個(gè)model的字段你上。
讓我們根據(jù)新的model來同步數(shù)據(jù)庫。運(yùn)行以下的命令來應(yīng)用存在的數(shù)據(jù)遷移:
python manage.py migrate
我們剛剛為INSTALLED_APPS中所有的應(yīng)用進(jìn)行了數(shù)據(jù)遷移,包括我們的blog應(yīng)用。在進(jìn)行了數(shù)據(jù)遷移之后,數(shù)據(jù)庫將會(huì)和我們的models對(duì)應(yīng)。
如果你編輯了models.py文件無論你是添加,刪除,還是改變了之前存在的字段,或者你添加了一個(gè)新的models,你將必須做一次新的遷移通過使用makemigrations命令。數(shù)據(jù)遷移允許Django來保持對(duì)model改變的跟蹤。然后你必須通過migrate命令來保持?jǐn)?shù)據(jù)庫與我們的models同步。
為你的models創(chuàng)建一個(gè)管理站點(diǎn)
如今,我們已經(jīng)定義好了Post model,我們將要?jiǎng)?chuàng)建一個(gè)簡單的管理站點(diǎn)來管理博客的帖子。Django內(nèi)置了一個(gè)非常有用的管理接口來編輯內(nèi)容。這個(gè)Django管理站點(diǎn)會(huì)根據(jù)你的model元數(shù)據(jù)進(jìn)行動(dòng)態(tài)構(gòu)建并且提供可讀的接口來編輯內(nèi)容。你可以對(duì)這個(gè)站點(diǎn)進(jìn)行自由的定制,配置你的models在其中的展示方式。
請(qǐng)記住,django.contrib.admin已經(jīng)被包含在我們項(xiàng)目的INSTALLED_APPS設(shè)置中,我們不需要再額外添加。
創(chuàng)建一個(gè)超級(jí)用戶
首先,我們需要?jiǎng)?chuàng)建一個(gè)用戶來管理這個(gè)管理站點(diǎn)。運(yùn)行以下的命令:
python manage.py createsuperuser
Django管理站點(diǎn)
現(xiàn)在,通過python manage.py runserver
命令來啟動(dòng)開發(fā)服務(wù),之后在瀏覽器中打開 http://127.0.0.1:8000/admin/ 。你會(huì)看到管理站點(diǎn)的登錄頁面;
使用你之前創(chuàng)建的超級(jí)用戶信息登錄。你將會(huì)看到管理站點(diǎn)的首頁

這很簡單,對(duì)吧?當(dāng)你在Django的管理頁面注冊(cè)了一個(gè)model,你會(huì)獲得一個(gè)非常友好有用的接口通過簡單的方式允許你在列表中編輯,創(chuàng)建,刪除對(duì)象操作。點(diǎn)擊Posts右側(cè)的Add鏈接來添加一個(gè)新帖子。你將會(huì)看到Django為你的model動(dòng)態(tài)生成了一個(gè)可編輯輸入頁面。
自定義models的展示形式
現(xiàn)在我們來看下如何自動(dòng)管理站點(diǎn)。編輯博客應(yīng)用下的admin.py文件,我們通過使用繼承ModelAdmin的自定義類來告訴Django管理站點(diǎn)注冊(cè)了哪些我們自己的model。在這個(gè)類中,我們可以包含一些信息來定制管理頁面中如何展示和交互model。list_display屬性允許你在管理對(duì)象的列表頁面展示你想展示的model中的字段。
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish', 'status')
list_filter = ('status', 'created', 'publish', 'author')
search_fields = ('title', 'body')
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ('author',)
date_hierarchy = 'publish'
ordering = ['status', 'publish']
admin.site.register(Post, PostAdmin)
重新刷新管理站點(diǎn)的頁面,現(xiàn)在應(yīng)該如下所示:

你可以看到在帖子列表頁中展示的字段都是你在list-dispaly屬性中指定的。列表頁面現(xiàn)在包含了一個(gè)右邊欄允許你根據(jù)list_filter屬性中指定的字段來過濾返回結(jié)果。在頁面還出現(xiàn)了一個(gè)搜索框。這是因?yàn)槲覀冞€定義了一個(gè)搜索字段通過使用search_fields屬性。在搜索框的下方,有個(gè)可以通過時(shí)間層快速操作的欄。這是通過定義date_hierarchy屬性。你還能看到這些帖子可以通過Status和Publish列進(jìn)行了默認(rèn)排序。那是因?yàn)槟阒付四J(rèn)排序通過使用ordering屬性。
現(xiàn)在,點(diǎn)擊Add post鏈接。你還會(huì)在這兒看到更多的改變。當(dāng)你輸入完成新帖子的標(biāo)題,slug字段將會(huì)自動(dòng)填充。我們告訴Django通過輸入的標(biāo)題來填充slug字段是通過使用prepoupulated_fields屬性。同時(shí),如今的author字段展示變?yōu)榱艘粋€(gè)搜索控件,這樣當(dāng)你的用戶達(dá)到成千上萬的時(shí)候比使用下拉的選擇更加的人性化,如下圖所示:

通過短短的幾行代碼,我們就在管理站點(diǎn)中自定義了我們的model的展示形式。還有更多的方式可以用來自定義Django的管理站點(diǎn)。在這本書的后面,我們還會(huì)進(jìn)一步講述。
使用QuerySet和managers
現(xiàn)在,你已經(jīng)有個(gè)完整的管理站點(diǎn)來管理你的博客內(nèi)容,是時(shí)候?qū)W習(xí)如何從數(shù)據(jù)庫中檢索信息和操作了。Django自帶了一個(gè)強(qiáng)大的數(shù)據(jù)庫抽象API可以讓你輕松的創(chuàng)建,檢索,更新以及刪除對(duì)象。Django的Object-relational Mapper(ORM)可以兼容MySQL,PostgreSQL,SQLite以及Oracle。請(qǐng)記住你可以在你項(xiàng)目下的setting.py中編輯DATABASES設(shè)置來指定數(shù)據(jù)庫。Django可以同時(shí)與多個(gè)數(shù)據(jù)庫進(jìn)行工作,這樣你可以編寫數(shù)據(jù)庫路由來操作數(shù)據(jù)通過任何你喜歡的方式。
一旦你創(chuàng)建好了你的數(shù)據(jù)models,Django會(huì)提供你一個(gè)API來與它們進(jìn)行交互。你可以找到數(shù)據(jù)model的官方參考文檔通過訪問 https://docs.djangoproject.com/en/1.8/ref/models/
創(chuàng)建對(duì)象
打開終端運(yùn)行以下命令來打開Python shell:
python manage.py shell
然后依次輸入以下內(nèi)容:
from django.contrib.auth.models import User
from blog.models import Post
user = User.objects.get(username='admin')
Post.objects.create(title='One more post', slug='one-more-post', body='Post body.', author=user)
post.save()
讓我們來研究下這些代碼做了什么。首先,我們?nèi)』亓艘粋€(gè)username為admin的用戶對(duì)象:
user = User.objects.get(username='admin')
get()方法允許你從數(shù)據(jù)庫取回一個(gè)單獨(dú)的對(duì)象。注意這個(gè)方法只希望有唯一的一個(gè)匹配在查詢中。如果在數(shù)據(jù)庫中沒有返回結(jié)果,這個(gè)方法會(huì)拋出一個(gè)DoesNotExist異常,如果數(shù)據(jù)庫返回多個(gè)匹配結(jié)果,將會(huì)拋出一個(gè)MultipleObjectsReturned異常。當(dāng)查詢執(zhí)行的時(shí)候,所有的異常都是model類的屬性。
然后,我們來創(chuàng)建一個(gè)擁有自定義標(biāo)題,slug和內(nèi)容的Post實(shí)例,該實(shí)例中author關(guān)聯(lián)上我們之前去除的user,如下所示:
post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)
# 這個(gè)對(duì)象只是存在內(nèi)存中,并沒有執(zhí)行到數(shù)據(jù)庫中
最后,我們通過使用save()方法來保存該對(duì)象到數(shù)據(jù)庫中:
post.save()
這步操作將會(huì)在之后執(zhí)行一段SQL的插入語句。我們已經(jīng)知道如何在內(nèi)存中創(chuàng)建一個(gè)對(duì)象并且之后才在數(shù)據(jù)庫中進(jìn)行插入,但是我們也可以直接在數(shù)據(jù)庫中創(chuàng)建對(duì)象通過使用create()方法,如下所示:
post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)
更新對(duì)象
現(xiàn)在,改變這篇帖子的標(biāo)題并且再次保存對(duì)象:
>>> post.title = 'New title'
>>> post.save()
這一次,save()方法執(zhí)行了一條更新語句。
# 你對(duì)對(duì)象的改變一直存在內(nèi)存中直到你執(zhí)行到save()方法。
取回對(duì)象
Django的Object-relational mapping(ORM)是以QuerySet為基礎(chǔ)。QuerySet是從你的數(shù)據(jù)庫中根據(jù)一些過濾條件范圍取回的結(jié)果進(jìn)行采集產(chǎn)生的對(duì)象。你已經(jīng)知道通過get()方法可以從數(shù)據(jù)庫中取回單獨(dú)的對(duì)象。就像你所看到的:Post.objects.get()
。每一個(gè)Django model至少有一個(gè)manager,默認(rèn)叫做objects。你能獲得一個(gè)QuerySet對(duì)象就是通過你的models的manager。獲取table中所有的對(duì)象,你只需要在默認(rèn)的objects manager上使用all()方法即可:
>>> all_posts = Post.objects.all()
我們創(chuàng)建一個(gè)返回?cái)?shù)據(jù)庫中所有對(duì)象的QuerySet。注意這個(gè)QuerySet并還沒有執(zhí)行。Django的QuerySets是惰性的,他們只會(huì)被動(dòng)的去執(zhí)行。這樣可以保證QuerySet非常效率。如果沒有把QuerySet賦值給一個(gè)變量,直接在Python shell中編寫,這樣QuerySet的SQL語句將立馬執(zhí)行因?yàn)槲覀兤仁顾谳敵鲋蟹祷亟Y(jié)果:
>>> Post.objects.all()
使用filter()方法
過濾QuerySet,你可以在manager上使用filter()方法。舉個(gè)例子,我們可以返回所有在2015年發(fā)布的帖子,如下所示:
Post.objects.filter(publish__year=2015)
你也可以使用多個(gè)字段來進(jìn)行過濾。舉個(gè)例子,我們可以返回2015年發(fā)布的所有作者用戶名為admin的帖子,如下所示:
Post.objects.filter(publish__year=2015, author__username='admin')
上面的寫法和下面的寫法產(chǎn)生的結(jié)果是一致的:
Post.objects.filter(publish__year=2015).filter(author__username='admin')
使用exclude()
你可以在manager上使用exclude()方法來排除某些返回結(jié)果。例如:我們可以返回所有2015年發(fā)布的帖子但是這些帖子的題目開頭不能是Why:
Post.objects.filter(publish__year=2015).exclude(title__startswith='Why')
使用order_by()
你可以對(duì)結(jié)果進(jìn)行排序通過在manager上使用order_by()方法來對(duì)不同的字段進(jìn)行排序。例如:你可以取回所有對(duì)象通過它們的標(biāo)題進(jìn)行排序:
Post.objects.order_by('title')
默認(rèn)是升序。你可以通過負(fù)號(hào)來指定使用降序:
Post.objects.order_by('-title')
刪除對(duì)象
如果你想刪除一個(gè)對(duì)象,你可以對(duì)對(duì)象實(shí)例進(jìn)行下面的操作:
post = Post.objects.get(id=1)post.delete()
# 請(qǐng)注意,刪除對(duì)象也將刪除其中的依賴關(guān)系
QuerySet什么時(shí)候會(huì)執(zhí)行
你可以連接許多過濾給QuerySet只要你喜歡而且不會(huì)在數(shù)據(jù)庫中執(zhí)行直到這個(gè)QuerySet被執(zhí)行。QuerySet只有在以下情況中才會(huì)執(zhí)行:
- 在你第一次迭代它們的時(shí)候
- 當(dāng)你對(duì)它們的實(shí)例進(jìn)行切片:
Post.objects.all()[:3] - 當(dāng)你對(duì)它們進(jìn)行了打包或緩存
- 當(dāng)你對(duì)它們調(diào)用了
repr()或len()方法 - 當(dāng)你明確的對(duì)它們調(diào)用了
list()方法 - 當(dāng)你再一些聲明中測(cè)試它們,例如
bool(), or, and, or if
創(chuàng)建model manager
我們之前提到過, objects是每一個(gè)models的默認(rèn)manager,會(huì)返回?cái)?shù)據(jù)庫中所有的結(jié)果。但是我們也可以我們的models定義一些自定義的manager。我們準(zhǔn)備創(chuàng)建一個(gè)自定義的manager來返回所有狀態(tài)為已發(fā)布的帖子。
有兩種方式來為你的models添加managers:你可以添加臨時(shí)的manager方法或者繼承manager的QuerySets進(jìn)行修改。
第一種方法類似Post.objects.my_manager(),
第二種方法類似Post.my_manager.all()。我們的manager將會(huì)允許我們返回所有帖子通過使用Post.published。
編輯你的博客應(yīng)用下的models.py文件添加如下代碼來創(chuàng)建一個(gè)manager:
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager,
self).get_queryset()\
.filter(status='published')
class Post(models.Model):
# ...
objects = models.Manager() # The default manager
published = PublishedManager() # Our custom manager
get_queryset()是返回執(zhí)行的QuerySet的方法。我們通過使用它來包含我們自定義的過濾在完整的QuerySet中。我們定義我們自定義的manager然后添加到Post model中。我們現(xiàn)在可以來執(zhí)行它。例如,
我們可以返回所有標(biāo)題開頭為Who的并且是已經(jīng)發(fā)布的帖子:
Post.published.filter(title__startswith='Who')
構(gòu)建列表和詳情views
現(xiàn)在你學(xué)會(huì)了一些ORM的基本使用方法,你已經(jīng)準(zhǔn)備好為博客應(yīng)用創(chuàng)建views了。一個(gè)Django view 就是一個(gè)Python方法可以接收一個(gè)web請(qǐng)求然后返回一個(gè)web響應(yīng)。在vies中通過邏輯處理返回期望的響應(yīng)。
首先我們會(huì)創(chuàng)建一個(gè)應(yīng)用view,然后我們將會(huì)為每個(gè)view定義一個(gè)URL,最后,我們將會(huì)為每個(gè)views生成的數(shù)據(jù)通過創(chuàng)建HTML templates來進(jìn)行渲染。每個(gè)view將會(huì)通過一個(gè)變量來渲染template并且會(huì)返回一個(gè)經(jīng)過渲染輸出的HTTP響應(yīng)。
創(chuàng)建列表和詳情views
讓我們開始創(chuàng)建一個(gè)視圖來展示帖子的列表。編輯你的博客應(yīng)用下中views.py文件,如下所示:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.published.all()
return render(request, 'blog/post/list.html', {'posts': posts})
你剛創(chuàng)建了你的第一個(gè)Django view。post_list view將request對(duì)象作為唯一的參數(shù)。記住所有的的view都有需要這個(gè)參數(shù)。在這個(gè)view中,我們獲取到了所有狀態(tài)為已發(fā)布的帖子通過使用我們之前創(chuàng)建的published manager。
最后,我們使用Django提供的快捷方法render()來渲染帖子列表通過給予的template。這個(gè)函數(shù)將request對(duì)象,template路徑,和給予渲染的變量最為參數(shù)。它返回一個(gè)渲染文本(一般是HTML代碼)HttpResponse對(duì)象。render()方法將許多變量投入template環(huán)境中,以便template環(huán)境中進(jìn)行調(diào)用。你會(huì)在第三章,擴(kuò)展你的博客應(yīng)用學(xué)習(xí)到。
讓我們創(chuàng)建第二個(gè)view來展示一篇單獨(dú)的帖子。添加如下代碼到views.py文件中:
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
return render(request,
'blog/post/detail.html',
{'post': post})
這是一個(gè)帖子詳情view。這個(gè)view使用year,month,day以及post參數(shù)來獲取一個(gè)發(fā)布的帖子通過給予的slug和日期。請(qǐng)注意,當(dāng)我們創(chuàng)建Post model的時(shí)候,我們?cè)趕lgu字段上添加了unique_for_date參數(shù)。這樣我們可以確保只有一個(gè)帖子會(huì)帶有給予的slug,因此,我們能取回單獨(dú)的帖子通過日期和slug。在這個(gè)詳情view中,我們通過使用get_object_or_404()快捷方法來檢索期望的帖子。這個(gè)函數(shù)能取回匹配給予的參數(shù)的對(duì)象,或者返回一個(gè)HTTP 404異常當(dāng)沒有匹配的對(duì)象找到。最后,我們使用render()快捷方法來使用template去渲染取回的帖子。
為你的views添加URL patterns
一個(gè)URL pattern 是由一個(gè)Python正則表達(dá),一個(gè)view,一個(gè)全項(xiàng)目范圍內(nèi)的命名組成。Django在運(yùn)行中會(huì)遍歷所有URL pattern直到第一個(gè)匹配的請(qǐng)求URL才停止。之后,Django導(dǎo)入匹配的URL pattern中的view并且執(zhí)行它,使用關(guān)鍵字或指定參數(shù)來執(zhí)行一個(gè)HttpRequest類的實(shí)例。如果你之前沒有接觸過正則表達(dá)式,你需要去稍微了解下,通過訪問 https://docs.python.org/3/howto/regex.html 。
在博客應(yīng)用目錄下創(chuàng)建一個(gè)urls.py文件,輸入以下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
# post views
url(r'^$', views.post_list, name='post_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
r'(?P<post>[-\w]+)/$',
views.post_detail,
name='post_detail'),
]
第一條URL pattern并沒有帶入任何參數(shù),它映射post_list view。第二條URL pattern帶上了一下4個(gè)參數(shù)映射到post_detail view。讓我們看下這個(gè)URL pattern中的正則表達(dá)式:
* year:需要四位數(shù)
* month:需要兩位數(shù)。不及兩位數(shù),開頭帶上0,比如 01,02
* day:需要兩位數(shù)。不及兩位數(shù)開頭帶上0
* post:可以由單詞和連字符組成
為每一個(gè)應(yīng)用創(chuàng)建單獨(dú)的urls.py文件是最好的方法來保證你的應(yīng)用可以為別的項(xiàng)目再度使用。
現(xiàn)在你需要將你博客中的URL patterns包含到項(xiàng)目的主URL patterns中。編輯你的項(xiàng)目中的mysite文件夾中urls.py文件,如下所示:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/', include('blog.urls', namespace='blog', app_name='blog')),
]
通過這樣的方式,你告訴Django在blog/路徑下包含了博客應(yīng)用中的urls.py定義的URL patterns。你可以給他們一個(gè)命名空間叫做blog這樣你可以方便的引用這個(gè)URLs組。
models的標(biāo)準(zhǔn)URLs
你可以使用之前定義的post_detil URL對(duì)Post對(duì)象構(gòu)建標(biāo)準(zhǔn)URL。Django的慣例是添加get_absolute_url()方法給model用來返回一個(gè)對(duì)象的標(biāo)準(zhǔn)URL。在這個(gè)方法中,我們使用reverse()方法允許你通過名字和可選的參數(shù)來構(gòu)建URLS。編輯你的models.py文件添加如下代碼:
from django.core.urlresolvers import reverse
Class Post(models.Model):
# ...
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[self.publish.year,
self.publish.strftime('%m'),
self.publish.strftime('%d'),
self.slug])
請(qǐng)注意,我們通過使用strftime()方法來保證個(gè)位數(shù)的月份和日期需要帶上0來構(gòu)建URL.我們將會(huì)在我們的templates中使用get_absolute_url()方法。
為你的views創(chuàng)建templates
我們?yōu)槲覀兊膽?yīng)用創(chuàng)建了views和URL patterns?,F(xiàn)在該添加模板來展示人性化的帖子了。在你的博客應(yīng)用目錄下創(chuàng)建一下目錄結(jié)構(gòu)和文件:
templates/
blog/
base.html
post/
list.html
detail.html
以上就是我們的templates的文件目錄結(jié)構(gòu)。base.html文件將會(huì)包含站點(diǎn)主要的HTML結(jié)構(gòu),分割內(nèi)容區(qū)域和導(dǎo)航欄。list.html和detail.html文件會(huì)繼承base.html文件來渲染各自的博客帖子列表和詳情view。
Django有一個(gè)強(qiáng)大的templates語言允許你指定數(shù)據(jù)的展示形式。它立足于templates tags, 例如{% tag %},{{ variable }}以及templates filters,可以對(duì)變量進(jìn)行過濾,例如{{ variable|gilter }}。你可以找到所有的內(nèi)置templates tags和filters通過訪問 https://docs.djangoproject.com/en/1.8/ ref/templates/builtins/ 。
讓我們來編輯base.html文件添加如下代碼:
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
</div>
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog.</p>
</div>
</body>
</html>
{% load staticfiles %} 告訴Django去加載django.contrib.staticfiles應(yīng)用提供的staticfiles temaplate tags。通過加載,你可以在這個(gè)template中使用{% static %}template filter。通過使用這個(gè)template filter,你可以包含一些靜態(tài)文件比如說blog.css文件,你可以在本書的范例代碼例子中知道,在博客應(yīng)用的static/目錄中(譯者注:給大家個(gè)地址去拷貝 https://github.com/levelksk/django-by-example-book )拷貝這個(gè)目錄到你的項(xiàng)目下的相同路徑來使用這些靜態(tài)文件。
你可以看到有兩個(gè){% block %}tags。這些是用來告訴Django我們想在這個(gè)區(qū)域中定義一個(gè)block。繼承這個(gè)template的templates可以使用自定義的內(nèi)容來填充block。我們定義了一個(gè)block叫做title,另一個(gè)block叫做content。
讓我們編輯post/list.html文件使它如下所示:
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h2>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% endblock %}
通過{% extends %}
template tag,我們告訴Django需要繼承blog/base.html template。然后我們?cè)?em>title和content blocks中填充內(nèi)容。我們通過循環(huán)迭代帖子來展示他們的標(biāo)題,日期,作者和內(nèi)容,在中標(biāo)題還包括一個(gè)帖子的標(biāo)準(zhǔn)URL鏈接。在帖子的內(nèi)容中,我們應(yīng)用了兩個(gè)template filters: truncatewords用來縮短內(nèi)容限制在一定的字?jǐn)?shù)內(nèi),linebreaks用來轉(zhuǎn)換內(nèi)容中的換行符為HTML的換行符。你可以連接許多tempalte filters只要你喜歡,每一個(gè)都會(huì)應(yīng)用到上個(gè)輸出生成的結(jié)果上。
打開終端執(zhí)行命令python manage.py runserver來啟動(dòng)開發(fā)環(huán)境。瀏覽器中打開 http://127.0.0.1:8000/blog/ 你會(huì)看到運(yùn)行的結(jié)果。注意,你需要添加一些發(fā)布狀態(tài)的帖子才能在這兒看到它們。你會(huì)看到如下圖所示:

這之后,讓我們來編輯
post/detail.html文件使它如下所示:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
{% endblock %}
現(xiàn)在,你可以返回你的瀏覽器中點(diǎn)擊其中一個(gè)帖子的標(biāo)題來看帖子的詳細(xì)view。你會(huì)看到類似的以下輸出:

添加頁碼
當(dāng)你開始給你的博客添加內(nèi)容,你很快會(huì)意識(shí)到你需要將帖子分頁顯示。Django有一個(gè)內(nèi)置的pagination類允許你方便的管理分頁。
編輯博客應(yīng)用下的views.py文件導(dǎo)入Django的paginator類修改post_list如下所示:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
object_list = Post.published.all()
paginator = Paginator(object_list, 3) # 3 posts in each page
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer deliver the first page
posts = paginator.page(1)
except EmptyPage:
# If page is out of range deliver last page of results
posts = paginator.page(paginator.num_pages)
return render(request,'blog/post/list.html',{'page': page, 'posts': posts})
pagination是如何工作的:
- 我們通過希望在每頁中顯示的對(duì)象的數(shù)量實(shí)例化了
Paginator類
- 我們獲取到
pageGET參數(shù)來獲取頁碼 - 我們通過調(diào)用Paginator的
page()方法在期望的頁面中獲得了對(duì)象 - 如果
page參數(shù)不是一個(gè)整形數(shù)字,我們就返回第一頁的結(jié)果。如果這個(gè)參數(shù)的數(shù)字超出了最后的頁數(shù),我們就展示最后一頁的結(jié)果 - 我們傳遞頁數(shù)并且取到對(duì)象給template。
現(xiàn)在,我們必須創(chuàng)建一個(gè)template來展示分頁處理,它可以被任意的template包含來使用頁碼。在博客應(yīng)用的templates文件夾下創(chuàng)建一個(gè)新文件命名為pagination.html。在文件中添加如下HTML代碼:
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">Previous</a>
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}.
</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Next</a>
{% endif %}
</span>
</div>
這個(gè)分頁template期望一個(gè)Page對(duì)象為了渲染上一頁與下一頁的鏈接并且展示頁面數(shù)據(jù)和所有頁面的結(jié)果。讓我們回到blog/post/list.htm tempalte中將pagination.htmltemplate包含在{% content %}block中,如下所示:
{% block content %}
...
{% include "pagination.html" with page=posts %}
{% endblock %}
我們傳遞給template的Page對(duì)象叫做posts,我們將分頁tempalte包含在帖子列表template中指定參數(shù)來對(duì)它進(jìn)行渲染。這是一種方法你可以重復(fù)利用你的分頁template對(duì)不同的models views進(jìn)行分頁處理。
現(xiàn)在,在你的瀏覽器中打開 http://127.0.0.1:8000/blog/ 你會(huì)看到帖子列表的底部有分頁信息:

使用基于類的views
當(dāng)一個(gè)view被調(diào)用通過web請(qǐng)求并且返回一個(gè)web響應(yīng),你還可以將你的views定義成類方法。Django為此定義了基礎(chǔ)的view類。它們都從View類繼承而來,View類可以操控HTTP方法分派和其他的功能。這是一個(gè)可代替的方法來創(chuàng)建你的views。
我們準(zhǔn)備使我們的post_list view轉(zhuǎn)變?yōu)橐粋€(gè)基于類的視圖通過使用Django提供的通ListView。這個(gè)基礎(chǔ)view允許你對(duì)任意的對(duì)象進(jìn)行排列。
編輯你的博客應(yīng)用下的views.py文件,如下所示:
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
這個(gè)基于類的的view類似與之前的post_list view。這兒,我們告訴ListView做了以下操作:
使用一個(gè)特定的QuerySet代替取回所有的對(duì)象。代替定義一個(gè)
queryset屬性,我們可以指model = Post,并且Django將會(huì)構(gòu)建一個(gè)通過的Post.objects.all() QuerySet給我們。
使用環(huán)境變量posts給查詢結(jié)果。如果我們不指定任意的context_object_name默認(rèn)的變量將會(huì)是object_list。
對(duì)結(jié)果進(jìn)行分頁處理每頁只顯示3個(gè)對(duì)象。
使用自定義的template來渲染頁面。如果我們不設(shè)置默認(rèn)的template,ListView將會(huì)使用blog/post_list.html。
現(xiàn)在,打開你的博客應(yīng)用下的urls.py文件,在post_listURL pattern之前添加一個(gè)新的URL pattern使用PostlistView類,如下所示:
from django.conf.urls import url
from . import views
urlpatterns = [
# post views
# url(r'^$', views.post_list, name='post_list'),
url(r'^$', views.PostListView.as_view(), name='post_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',views.post_detail,name='post_detail'),
]
為了保持分頁處理能工作,我們必須將正確的頁面對(duì)象傳遞給tempalte。Django的ListView通過叫做page_obj的變量來傳遞被選擇的頁面,所以你必須編輯你的post_list_html template因此去包含使用了正確的變量的分頁處理,如下所示:
{% include "pagination.html" with page=page_obj %}
在你的瀏覽器中打開 http://127.0.0.1:8000/blog/ 然后檢查每一樣事情是否都和之前的post_list view一樣工作。這是一個(gè)簡單的基于類的view例子通過使用Django提供的通用類。