點我查看本文集的說明及目錄。
本項目相關(guān)內(nèi)容( github傳送 )包括:
實現(xiàn)過程:
項目總結(jié)及改進:
使用類視圖實現(xiàn) blog list 和 detail 視圖
內(nèi)容更新頻率:每周三、周五中午更新一篇。
CH1 創(chuàng)建一個博客應(yīng)用
我們將在這本書中學(xué)習(xí)如何創(chuàng)建可用于生產(chǎn)環(huán)境的完整 Django 項目。如果你還沒有安裝 Django ,那么請在本章第一節(jié)學(xué)習(xí)如何安裝。這章介紹如何使用Django創(chuàng)建一個簡單的博客應(yīng)用。本章的目的在于介紹框架如何工作,不同部分如何相互作用及如何通過基本功能創(chuàng)建 django 項目。你將在不過分關(guān)注細節(jié)的情況下創(chuàng)建一個完整的項目??蚣芗毠?jié)將在后續(xù)章節(jié)進行介紹。
本章涉及以下內(nèi)容:
- 安裝 Django 并創(chuàng)建第一個項目
- 設(shè)計模型并生成模型遷移文件
- 為模型創(chuàng)建 administration 網(wǎng)站
- 使用 Queryset 和 manager 工作
- 創(chuàng)建視圖、模板和 URLs
- 為 list 視圖添加分頁
- 使用 Django 類視圖
安裝 Django
如果你已經(jīng)安裝了 Django,可以跳過這一節(jié),直接從下一節(jié)(創(chuàng)建第一個項目)開始。作為 Python 庫 Django 可以在任何 python 環(huán)境中安裝。如果你還沒有安裝 Django,請閱讀下面的 Django 安裝快速向?qū)А?/p>
Django 可用于 Python2.7 和 Python3。本書的例子使用 Python3 。如果你使用 Linux 或 Mac OS X ,很可能已經(jīng)預(yù)先安裝了 python 。在 teminal 中輸入 python 來確定是否安裝了 python 。如果你看到下面的輸出,那么 python 已經(jīng)安裝好了。
Python 3.5.0 (v3.5.0:374f501f4567, Sep 12 2015, 11:00:19)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
如果你安裝的 python 版本低于 3.0 ,或者沒有安裝 python ,可以從http://www.python.org/download/ 下載Python3.5 并進行安裝。
筆者注:
筆者電腦同時安裝了 python2.7 和 python3.6。
在 terminal 輸入 python,可以看到下面的輸出:
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>>在 terminal 輸入 python3,可以看到下面的輸出:
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>>
由于我們將使用 Python3.0 ,因此不需要安裝數(shù)據(jù)庫,python 內(nèi)置 SQLite 數(shù)據(jù)庫,SQLite 是一個可用于 Django 開發(fā)的輕量級數(shù)據(jù)庫。如果希望將應(yīng)用部署到生產(chǎn)環(huán)境,那么請使用PostGreSQL、MySQL 或 Oracle 等高級數(shù)據(jù)庫。我們可以從https://docs.djangoproject.com/en/1.8/topics/install/#database-installation.了解如何在 Django 中使用這些數(shù)據(jù)庫。
筆者注:
python2.7 也不需要安裝數(shù)據(jù)庫。
創(chuàng)建獨立的 Python 運行環(huán)境
推薦使用 virtualenv 創(chuàng)建獨立的 Python 運行環(huán)境,以便針對不同的項目使用不同版本的 python 庫文件,這樣做比在系統(tǒng)中安裝 Python 庫文件更實用。使用 virtualenv 的另一個優(yōu)點是安裝 Python 庫文件時不需要管理權(quán)限。在 shell 中運行以下命令來安裝 virtualenv:
pip install virtualenv
安裝完 virtualenv 后,使用以下命令創(chuàng)建獨立的 Python 運行環(huán)境:
virtualenv my_env
這個命令將在 Python 環(huán)境中創(chuàng)建 my_env 目錄,虛擬環(huán)境處于激活狀態(tài)時,安裝的所有 Python 庫文件都將保存在 my_env/lib/python3.X/site-packages 目錄下。
如果系統(tǒng)自帶 Python 2.X ,我們又額外安裝了 Python 3.X,我們需要告訴虛擬環(huán)境使用后者。這時通過下面的步驟創(chuàng)建虛擬環(huán)境:
在terminal 中通過以下命令獲取 python3 的路徑:
which python3
? 這里得到的結(jié)果是:
/usr/local/bin/python3
使用以下命令創(chuàng)建獨立的 Python 運行環(huán)境:
virtualenv my_env -p /usr/local/bin/python3
運行以下命令激活虛擬環(huán)境:
source my_env/bin/active
shell 提示將使用括號包含激活的虛擬環(huán)境,看起來是這樣的:
(my_env)latop:~ user_name$
我們可以在任何時間使用 deactivate 命令關(guān)閉虛擬環(huán)境。
我們可以從 https://virtualenv.python/io/en/latest/ 找到更多虛擬環(huán)境的信息。
我們可以使用 virtualenvwrapper 來管理虛擬環(huán)境。這個工具可以可以幫助我們快速創(chuàng)建和管理虛擬環(huán)境,我們可以根據(jù) http://virtualenvwrapper.readthedocs.io/en/latest/ 的介紹安裝virtualenvwrapper 。
筆者注:
筆者習(xí)慣使用 PyCharm ,因此直接通過 PyCharm 創(chuàng)建虛擬環(huán)境,創(chuàng)建方法為:
創(chuàng)建項目時設(shè)置虛擬環(huán)境:
virtualenv.png
點擊右側(cè) Create VirtualEnv 創(chuàng)建虛擬環(huán)境。
通過 pycharm 的偏好設(shè)置-Project Interpreter 進行設(shè)置:
pycharm_virtualenv.png點擊右側(cè) Create VirtualEnv 創(chuàng)建虛擬環(huán)境。
注意,方法 2 要在使用 django-admin startproject 命令之前使用。
創(chuàng)建完成后,項目即可使用虛擬環(huán)境了。
使用 pip 安裝 Django
推薦使用 pip 安裝 Django, Python3.5 預(yù)裝了 pip,我們也可以從 https://pip.pypa.io/en/stable/installing/ 找到 pip 的安裝說明,在 shell 中運行以下命令安裝 Django:
pip install Django==1.11
筆者注:
==1.11 表示安裝Django 1.11,這里如果不指定版本號,會安裝 python 版本能夠滿足的最新版本。
Django 現(xiàn)在已經(jīng)安裝在虛擬環(huán)境的 site-packages/ 目錄下了。
現(xiàn)在檢查一下 Django 是否成功安裝了,在 terminal 中運行 python3 并導(dǎo)入 Django 來檢查它的版本:
>>> import django
>>> django.VERSION
(1, 11, 0, 'final', 1)
如果可以看到輸出,則表示我們已經(jīng)成功安裝了 Django。
我們也可以使用其它方法安裝 Django ,https://docs.djangoproject.com/en/1.11/topics/install/ 有詳細的安裝說明。
創(chuàng)建第一個項目
我們的第一個項目將完成一個博客網(wǎng)站。Django 提供了創(chuàng)建項目文件結(jié)構(gòu)的命令,在shell中運行以下命令:
django-admin startproject mysite
這個命令將創(chuàng)建一個名為 mysite 的 Django 項目。讓我們來看一下項目的結(jié)構(gòu):

這些文件的作用是:
manage.py:與項目進行交互的命令行工具。它是 django-admin.py 工具的輕量級封裝,我們不需要編輯這個文件。
-
mysite/:項目目錄包含以下文件:
__init__.py: 空白文件,表示將 mysite 文件夾當做一個模塊進行處理;settings.py:設(shè)置及配置項目。包括初始化缺省設(shè)置;urls.py:保存 URL 模式。這里的每個 url 都與一個視圖相匹配。wsgi.py: 項目作為 WSGI 應(yīng)用運行時的配置。
生成的 settings.py 文件是項目的基本配置(包含使用 SQLite 數(shù)據(jù)庫及默認安裝的 Django 應(yīng)用)。我們需要在數(shù)據(jù)庫中為初始應(yīng)用創(chuàng)建數(shù)據(jù)庫表。
打開 shell ,跳轉(zhuǎn)到項目的根目錄并運行以下命令:
cd mysite3
python manage.py migrate
將會輸出以下內(nèi)容:
Operations to perform:
Apply all migrations: admin, auth, 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 sessions.0001_initial... OK
項目初始應(yīng)用的數(shù)據(jù)庫表已經(jīng)建好了,我們對 migrate 命令有了初步了解。
運行開發(fā)服務(wù)器
Django 內(nèi)置一個快速運行代碼的輕量級 web服務(wù)器,這樣可以節(jié)約配置生產(chǎn)服務(wù)器的時間。Django 服務(wù)器運行期間會自動監(jiān)測代碼的變化并自動重載代碼。但是,它無法識別向項目中添加文件的操作,這種情況下需要手動重啟服務(wù)器。
打開 shell 并跳轉(zhuǎn)到項目的根目錄,運行以下命令來啟動開發(fā)服務(wù)器:
python manage.py runserver
你見看到以下輸出:
Performing system checks...
?
System check identified no issues (0 silenced).
October 24, 2017 - 04:48:13
Django version 1.11.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
現(xiàn)在在瀏覽器中打開 URL :http://127.0.0.1:8000/,你將看到一個告訴你項目成功運行的頁面,類似這樣:

django 開發(fā)服務(wù)器可以在自定義主機和端口運行,或者載入其它的 setting 文件,例如,可以這樣運行 manage.py 命令:
python manage.py runserver 127.0.0.1:8001 --settings=mysite.settings
這樣可以手動處理需要不同配置的多個環(huán)境。需要牢記的是,開發(fā)服務(wù)器只適用于開發(fā),但是并不適合生產(chǎn)。部署生產(chǎn)環(huán)境應(yīng)該使用 Apache、Gunicorn、uWSGI 等 web服務(wù)器以 WSGI 應(yīng)用的形式運行。使用不同 web服務(wù)器部署 django 的詳細文檔鏈接為:https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/。
此外,可以下載本書的第十三章,它將介紹如何為 Django 項目生成生產(chǎn)環(huán)境。
項目設(shè)置
打開 setting.py 文件來看一下項目配置。Django 在這個文件中進行了一些配置,但這只是一部分配置,我們可以從以下鏈接查看所有可用配置及其默認值:https://docs.djangoproject.com/en/1.11/ref/settings/。
下面的設(shè)置需要格外注意:
DEBUG :打開/關(guān)閉項目調(diào)試模式的布爾值。如果設(shè)置為 True ,當捕捉到異常時 Django 將顯示詳細錯誤信息。項目部署到生產(chǎn)環(huán)境時,要將其設(shè)為 False 。不要在生產(chǎn)環(huán)境中打開 DEBUG ,否則將可能暴露項目敏感信息。
ALLOW_HOST : DEBUG 模式或者運行測試下不使用該設(shè)置。一旦將網(wǎng)站遷移到生產(chǎn)環(huán)境并將 DEBUG 設(shè)置為 False ,則需要在該設(shè)置中添加域名/主機來使用 Django 網(wǎng)站。
-
INSTALLED_APPS :所有項目都需要編輯的設(shè)置,Django 從這個設(shè)置中讀取處于激活狀態(tài)的應(yīng)用。默認情況下,Django 包含以下應(yīng)用:
django.contrib.admin: administration 網(wǎng)站;
django.contrib.auth: 權(quán)限框架;
django.contrib.contenttypes: 內(nèi)容框架;
django.contrib.sessions: 會話框架;
django.contrib.messages: 消息框架;
django.contrib.staticfiles: 靜態(tài)文件管理框架;
MIDDLEWARE_CLASSSES:需要執(zhí)行的中間件元組。
ROOT_URLCONF :定義項目應(yīng)用的主 URL 模式的 Python 模塊。
DATABASES:設(shè)置項目使用的所有數(shù)據(jù)庫的字典。必須設(shè)置一個 default 數(shù)據(jù)庫。默認使用 SQLite3 數(shù)據(jù)庫。
LANGUAGE_CODE: 定義 Django 網(wǎng)站默認使用的語言。
如果你不理解這些內(nèi)容,不用擔(dān)心。我們會通過下面的章節(jié)熟悉這些設(shè)置。
項目和應(yīng)用
本書將會不斷的涉及項目和應(yīng)用這兩個術(shù)語。 Django 中,項目是指具有一些設(shè)置的 Django 安裝文件;應(yīng)用是模型、視圖、模板和 URLs 的組合。應(yīng)用與框架交互來提供一些特定功能,并可以在不同項目中重用。我們把項目當作網(wǎng)站,它可以包括幾個應(yīng)用,如博客、wiki、論壇等,這些應(yīng)用也可以用在其它項目中。
創(chuàng)建一個應(yīng)用
現(xiàn)在我們來創(chuàng)建第一個 Django 應(yīng)用。我們將從創(chuàng)建一個博客應(yīng)用開始。在 terminal 中跳轉(zhuǎn)到項目的根目錄并運行以下命令:
python manage.py startapp blog
它將創(chuàng)建應(yīng)用的基本結(jié)構(gòu),看起來是這樣的:

這些文件包括:
admin.py:該文件用于將模型注冊到 administration 網(wǎng)站。
migrations: 這個目錄將包含應(yīng)用的數(shù)據(jù)庫遷移記錄。它允許 Django 追蹤模型變化并進行相應(yīng)的數(shù)據(jù)庫同步。
models.py: 應(yīng)用的數(shù)據(jù)庫模型,所有的 Django 應(yīng)用都要有一個 models.py 文件,但這個文件可以是空的。
tests.py:該文件用于添加引用的測試程序。
-
views.py:該文件實現(xiàn)應(yīng)用邏輯。每個 view 接收一個 HTTP請求,對其進行處理并返回一個響應(yīng)。
?
設(shè)計 blog 數(shù)據(jù)模式
我們將從為 blog 定義初始數(shù)據(jù)模型開始。每個模型都是 django.db.models.Model 的子類,它的每個屬性都表示數(shù)據(jù)庫表的一個字段。Django 將為 models.py 中定義的每個模型創(chuàng)建一個數(shù)據(jù)庫表。為了便于數(shù)據(jù)查詢,當你創(chuàng)建一個模型后,Django 將提供一個 API 。
首先,我們將定義一個 Post 模型,在 blog 應(yīng)用的 models.py 文件中添加以下代碼:
from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
# Create your models here.
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
這是 blog 文章的基本模型,我們來看下這個模型的字段:
title:文章標題字段。這個字段是 CharField ,在 SQL 數(shù)據(jù)庫中將轉(zhuǎn)化為 VARCHAR 列。
slug:URLs 中使用的字段,slug 是只包含字母、數(shù)字、下劃線、連字符的短標簽。我們將使用 slug 字段為blog 文章創(chuàng)建漂亮、SEO 友好的 URLs 。我們?yōu)樵撟侄翁砑恿?unique_for_date 參數(shù),這樣我們可以使用date 和 slug 為文章創(chuàng)建標簽,設(shè)置該參數(shù)后將不能為相同日期的多篇文章使用相同的 slug 。
author:外鍵字段(ForeignKey)。這個字段定義了一個多對一關(guān)系。我們告訴 Django 每篇文章有一個作者而一個作者可以寫多篇文章。Django 將在數(shù)據(jù)庫中使用相關(guān)模型的 id 創(chuàng)建一個外鍵。在這里,我們使用Django 權(quán)限系統(tǒng)的 User 模型。我們使用 related_name 屬性為指定反向關(guān)系(從 User 到 Post )的名稱,后面我們將針對這一點學(xué)習(xí)更多知識。
body:這個字段是文章的正文。這個字段是 TextField ,在 SQL 數(shù)據(jù)庫中將轉(zhuǎn)化為Text 列。
publish:文章發(fā)表日期,我們使用 Django timezone 模塊的 now 方法作為默認值。該方法是時區(qū)敏感的datetime.now。
create:表示文章創(chuàng)建日期,這里我們使用 auto_now_add ,該字段將在創(chuàng)建對象時自動添加。
updated:表示文章最后一次更新日期,這里使用 auto_now ,當我們保存對象時將會自動更新該字段。
status:表示文章狀態(tài)的字段。我們使用 choices 參數(shù),該字段只能為給定的 choices 中的一個。
我們可以看到,Django 可以使用不同類型的字段來定義模型。可以從以下鏈接找到所有字段:https://docs.djangoproject.com/en/1.11/ref/models/fields/。
模型內(nèi)部的 Meta 類包含 metadata 。通過它告訴 Django 查詢數(shù)據(jù)庫時按照 publish 降序的順序?qū)Σ樵兘Y(jié)果進行排序。這里通過 - 前綴表示降序。
__str__方法是默認表示對象的方法。Django 將在很多地方使用它,比如 administration 網(wǎng)站。
注意:
Python3默認所有字符串都使用 unicode 編碼,因此我們使用
__str__方法,如果你使用 Pyhton2.X ,可以使用__unicode__方法代替__str__方法。
由于我們將處理 datetime,我們將安裝 pytz 模塊。這個模塊為 python 提供 timezone 定義而且 SQLite 需要用它來處理時間。打開 shell 并使用以下命令安裝 pytz :
pip install pytz
筆者注:
django 1.11 中,pytz 為 django 模塊的依賴庫文件,因此,安裝 django 時已經(jīng)自動安裝了 pytz,不需要再次安裝。
Django 支持時區(qū)敏感的 datetime 。我們可以在項目的 settings.py 中設(shè)置 USE_TZ 來激活/關(guān)閉時區(qū)。當使用startproject 創(chuàng)建一個項目時該設(shè)置為 True 。
激活你的應(yīng)用
為了讓 Django 追蹤我們的應(yīng)用并為應(yīng)用的模型創(chuàng)建數(shù)據(jù)庫表,我們需要激活該應(yīng)用。通過編輯項目的 settings.py 文件在 INSTALLED_APPS 中添加 blog 來激活應(yīng)用??雌饋硎沁@樣的:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
現(xiàn)在 Django 知道我們的應(yīng)用已經(jīng)激活而且能夠引入它的模型了。
創(chuàng)建并實現(xiàn) migrations
我們在數(shù)據(jù)庫中為模型創(chuàng)建一個數(shù)據(jù)表。Django 內(nèi)置遷移系統(tǒng)來追蹤模型的變化并將這些更新同步到數(shù)據(jù)庫中。migrate 命令將遷移 INSTALL_APPS 中所有應(yīng)用的模型,它對同步當前模型及數(shù)據(jù)庫。
首先,我們需要為剛剛創(chuàng)建的新模型創(chuàng)建 migration ,從項目的根目錄輸入以下命令:
python manage.py makemigrations blog
你將看到以下輸出:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
Django 剛剛在 blog 應(yīng)用的 migrations 目錄下創(chuàng)建了一個名為 0001_initial.py 的文件。你可以打開文件查看一下遷移文件。
讓我們查看一下 Django 為模型創(chuàng)建數(shù)據(jù)庫表時執(zhí)行的 SQL 代碼。sqlmigrate 命令根據(jù) migration 的名字返回 SQL 但不執(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 NULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_slug_b95473f2" ON "blog_post" ("slug");
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;
具體的輸出會因使用的數(shù)據(jù)庫稍有變化。上面是 SQLite 的輸出。你可以看到,Django 使用應(yīng)用名稱的小寫形式和數(shù)據(jù)庫表名稱的小寫形式組成數(shù)據(jù)庫表名( blog_post ),你也可以在 Meta 類中使用 db_table 屬性指定數(shù)據(jù)庫表名。Django 自動為每個模型創(chuàng)建一個主鍵字段,你也可以通過為模型的某個字段設(shè)置 primary_key=True 來設(shè)置主鍵。
我們來為模型同步數(shù)據(jù)庫,在項目根目錄運行以下命令:
python manage.py migrate
將會看到以下輸出:
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK
我們剛為 INSTALL_APPS 中的應(yīng)用實現(xiàn)了遷移。遷移之后模型與數(shù)據(jù)庫一致。(打開數(shù)據(jù)庫,將會發(fā)現(xiàn)名為blog_post 的數(shù)據(jù)庫表)。
如果編輯 models.py 文件來為已經(jīng)存在的模型添加、刪除或修改字段,或者添加新模型,需要使用makemigrations 命令創(chuàng)建新的遷移文件,Django 通過遷移文件追蹤模型變化,然后運行 migrate 命令來同步數(shù)據(jù)庫。
為模型創(chuàng)建 administration 網(wǎng)站
現(xiàn)在,我們已經(jīng)定義了 Post 模型,我們將創(chuàng)建一個簡單的 administration 網(wǎng)站來管理 blog 文章。Django 內(nèi)置非常有用的 administration 接口。接口通過讀取模型的 metadata 動態(tài)創(chuàng)建 Django admin 網(wǎng)站并提供穩(wěn)定的編輯內(nèi)容的接口,我們還可以使用它來編輯模型的展示形式。
筆者注:
后面的內(nèi)容,django admin 網(wǎng)站稱為 admin網(wǎng)站。
由于 django.contrib.admin 已經(jīng)自動放到項目 settings.py 的 INSTALLED_APPS 中了,因此我們無需再次添加。
創(chuàng)建 superuser
首先,我們需要創(chuàng)建一個管理 admin網(wǎng)站的用戶,跳轉(zhuǎn)到項目根目錄,運行以下命令:
python manage.py createsuperuser
你將看到如下輸出,根據(jù)提示逐步輸入你的用戶名、郵箱及密碼:
Username (leave blank to use 'apple'): admin
Email address: my_email@163.com
Password:
Password (again):
Superuser created successfully.
admin網(wǎng)站
現(xiàn)在,通過python manage.py runserver運行開發(fā)服務(wù)器,在瀏覽器中打開http://127.0.0.1:8000/admin,將會看到 admin網(wǎng)站的登錄頁面,如下圖所示:

使用上一步驟創(chuàng)建的用戶登錄,你將看到 admin網(wǎng)站的索引頁面如下圖所示:

這里的 Group 和 User 模型是 Django 權(quán)限框架( Django.contrib.auth )的模型。如果點擊 Users ,你將看到剛剛創(chuàng)建的用戶。blog 應(yīng)用的 Post 模型與 User 模型有一個關(guān)系(由 author 字段定義的關(guān)系)。
將模型添加到 admin網(wǎng)站
讓我們將 blog 模型添加到 admin網(wǎng)站。向 blog 應(yīng)用的 admin.py 文件寫入以下代碼:
from django.contrib import admin
?
from .models import Post
?
# Register your models here.
admin.site.register(Post)
現(xiàn)在在瀏覽器中重新載入 admin網(wǎng)站,你應(yīng)該在網(wǎng)站中看到如下圖所示的 Post 模型:

向 admin網(wǎng)站注冊一個模型后,可以得到一個接口,通過這個接口我們可以很容易的列出模型對象以及創(chuàng)建、修改、刪除模型對象。
點擊 Posts 右側(cè)的 Add 可以添加新的文章。你可以看到 Django 為我們剛剛創(chuàng)建的模型自動生成的表單,如下圖所示:
Django 為每種類型的字段使用不同的表單組件。即使 DateTimeField 這樣的復(fù)雜字段也通過接口(如 JavaScript date picker )進行展示。
填寫表單并點擊 save按鈕,你將重定向到文章列表頁面,在這里我們將看到剛剛成功添加的信息,如下圖所示:

自定義模型展示方式
現(xiàn)在,我們來看看如何自動以admin網(wǎng)站。編輯blog應(yīng)用的admin.py文件,將其更改為:
from django.contrib import admin
?
from .models import Post
?
?
# Register your models here.
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish', 'status')
?
?
admin.site.register(Post, PostAdmin)
我們告訴 admin網(wǎng)站使用繼承 ModelAdmin 的自定義類進行模型注冊。在這個類中,我們可以設(shè)置模型的展示及交互方式。這里的 list_display 屬性用來設(shè)置 admin 對象列表頁面展示的字段。
讓我們使用如下代碼設(shè)置更多自定義選項:
from django.contrib import admin
?
from .models import Post
?
?
# Register your models here.
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)
?
回到瀏覽器重新載入文章列表頁面,你將看到如下頁面:


你將看到文章列表頁面展示的字段是 list_display 設(shè)置的字段。列表頁面包括一個右側(cè)邊欄來允許按照 list_filters屬性設(shè)置的字段對列表進行過濾。頁面還多了一個搜索條,這是因為我們定義了 search_field 屬性。搜索條下方是一個日期面包屑導(dǎo)航菜單,這是由 date_hierarchy 屬性定義的。我們還可以看到文章默認通過 Status 和 Publish 字段排序,我們可以使用 ordering 屬性定義默認排序字段。
現(xiàn)在點擊右上角的 Add Post 鏈接,我們也將看到一些變化。當輸入新文章標題時會自動生成 slug 。我們使用prepopulated_field 告訴 Django 輸入 title 時自動生成 slug 。而且,通過設(shè)置 raw_id_fields 將 author 字段用 lookup 組件展示,當用戶數(shù)量很多時,這種操作從從下拉框中尋找更容易,如下圖所示。

點擊圖中的放大鏡,將會出現(xiàn)搜索頁面,如下圖所示:

我們已經(jīng)使用很少的代碼自定義了 admin網(wǎng)站的模型的展示形式,后續(xù)章節(jié)我們會涉及更多自定義及擴展 admin網(wǎng)站的方法。
使用Queryset和manager
我們已經(jīng)有了管理 blog 內(nèi)容的 admin網(wǎng)站,現(xiàn)在要學(xué)習(xí)如何從數(shù)據(jù)庫中獲取信息并與其進行交互。Django 內(nèi)置幫助我們創(chuàng)建、獲取、更新和刪除對象的數(shù)據(jù)庫API--Django Object-Relational-Mapper(ORM),它可以與MySQL、PostGreSQL、SQLite 及 Oracle 兼容。我們可以編輯項目 settings.py 中的 DATABASES 來定義項目使用的數(shù)據(jù)庫。Django 可以同時與多個數(shù)據(jù)庫工作。
一旦創(chuàng)建了數(shù)據(jù)模型,Django 免費提供與模型進行交互的 API,數(shù)據(jù)模型的官方文檔鏈接為:https://docs.djangoproject.com/en/1.11/ref/models/。
創(chuàng)建對象
打開 terminal ,在項目根目錄運行以下命令:
python manage.py shell
然后輸入以下信息:
In [1]: from django.contrib.auth.models import User
?
In [2]: from blog.models import Post
?
In [3]: user = User.objects.get(username='admin')
?
In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)
?
In [5]: post.save()
?
In [6]: Post.objects.create(title='Another post',slug='another-post',body='Post body',author=user)
Out[6]: <Post: another post>
筆者注:
原文中的 post.save() 是多余的,創(chuàng)建模型可以采用兩種方法:
方法1 :
In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)
In [5]: post.save()
post 定義了 Post 對象,然后通過 post.save() 方法保存到數(shù)據(jù)庫。
方法2:
Post.objects.create(title='another post',slug='another-post',body='Post body',author=user)
這里,Post.objects 為 Post 模型管理器,它的 create 方法可以將創(chuàng)建的模型保存到數(shù)據(jù)庫,不需要調(diào)用 save() 方法。
我們來分析下這些代碼,首先,我們獲取名為admin的用戶:
In [3]: user = User.objects.get(username='admin')
get() 方法允許我們從數(shù)據(jù)庫中獲取單個對象。注意,這個方法期望查詢結(jié)果為一個結(jié)果。如果數(shù)據(jù)庫沒有返回結(jié)果,這個方法將引發(fā) DoesNotExist 異常,如果數(shù)據(jù)庫返回的結(jié)果多于一個,這個方法將引發(fā)MultipleObjectsReturened 異常。這兩個異常都是模型類的屬性。
然后,我們使用自定義的 title,slug,body 和 author 創(chuàng)建了一個 Post 對象,其中 author 的值為上一步獲取的user:
In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)
注意:這一步并沒有影響數(shù)據(jù)庫。
最后,我們將 Post 對象保存到數(shù)據(jù)庫:
In [5]: post.save()
這個操作實現(xiàn)了一個 Insert SQL 。我們已經(jīng)看到了如何創(chuàng)建對象并保存到數(shù)據(jù)庫。我們也可以使用create()方法在數(shù)據(jù)庫中創(chuàng)建對象:
In [6]: Post.objects.create(title='another post',slug='another-post',body='Post body',author=user)
Out[6]: <Post: another post>
現(xiàn)在再次查看admin網(wǎng)站的Post列表,我們會發(fā)現(xiàn)下面三條記錄:

更新對象
現(xiàn)在,更改 post 的標題:
In [4]: post.title='New title'
?
In [5]: post.save()
?
In [6]: post
Out[6]: <Post: New title>
這次,save() 方法實現(xiàn) UPDATE SQL 語句。
筆者注:
我們也可以直接使用 update() 函數(shù)直接對數(shù)據(jù)庫中的記錄進行更改。
獲取對象
Django ORM 基于 QuerySet ,Queryset 是數(shù)據(jù)庫中可以使用多個 filter 進行限制的對象集合。我們已經(jīng)知道如何使用 get() 獲取單個對象。每個 Django 模型至少有一個 manager ,manager 默認的名字為 objects 。我們使用模型的 manager 獲取 Queryset 對象。使用 objects 的 all() 方法可以獲取一個數(shù)據(jù)庫表中的所有對象:
In [9]: all_post = Post.objects.all()
注意,這個 Queryset 并沒有執(zhí)行。Django 的 Queryset 是惰性的。這是為了保證 Queryset 高效。如果我們不將QuerySet 設(shè)為一個變量,而是直接寫到 Python shell 中,QuerySet 將被執(zhí)行,因為這樣做需要輸出結(jié)果:
In [10]: Post.objects.all()
Out[10]: <QuerySet [<Post: another post>, <Post: New title>, <Post: Why Django?>]>
使用 filter() 方法
我們可以使用 filter() 方法過濾 QuerySet ,比如獲取所有 2015年發(fā)布的文章:
In [12]: Post.objects.filter(publish__year=2015)
Out[12]: <QuerySet []>
我們也可以使用多個字段進行過濾,比如,我們要獲取用戶 admin 在 2015年發(fā)布的文章:
In [13]: Post.objects.filter(publish__year=2015,author__username='admin')
Out[13]: <QuerySet []>
這與多級 filter 效果一樣:
In [14]: Post.objects.filter(publish__year=2015).filter(author__username='admin')
Out[14]: <QuerySet []>
注意:
我們使用雙下劃線來獲取字段變量,比如
publish__year,還可以用雙下劃線獲取字段對應(yīng)的模型,比如author__username。
使用 exclude()
我們可以使用 manager 的 exclude() 方法排除某些結(jié)果,比如,我們要查詢 2015年發(fā)布的標題不以 Why 開頭的文章:
In [16]: Post.objects.filter(publish__year=2015).exclude(title__startswith='Why')
Out[16]: <QuerySet []>
使用 order_by()
我們可以使用 manager 的 order_by() 函數(shù)根據(jù)字段進行排序,比如,通過標題排序:
In [17]: Post.objects.order_by('title')
Out[17]: <QuerySet [<Post: Another post>, <Post: New title>, <Post: Why Django?>]>
上述查詢執(zhí)行的順序排序,倒序排序需要在字段前加上'-',比如:
In [18]: Post.objects.order_by('-title')
Out[18]: <QuerySet [<Post: Why Django?>, <Post: New title>, <Post: Another post>]>
刪除對象
如果要刪除對象,我們可以這樣實現(xiàn):
In [19]: post = Post.objects.get(pk=3)
In [20]: post.delete()
Out[20]: (1, {u'blog.Post': 1})
注意:
刪除對象將同時刪除依賴該對象的關(guān)系。
何時執(zhí)行 QuerySet
我們可以為 QuerySet 設(shè)置任意多的 filter ,QuerySet 被執(zhí)行之前并不會影響數(shù)據(jù)庫。只有下面的情況會觸發(fā)QuerySet 執(zhí)行:
進行第一次迭代時
進行切片時,比如 Post.objects.all()[:3]
進行 pickle 或者緩存
使用 repr() 或者 len() 函數(shù)
使用 list() 或 values() 函數(shù)
使用 bool()、or 、 and 或 if 語句進行測試
創(chuàng)建模型 Manager
正如我們上一節(jié)提到的,所有模型默認的 manager 為 objects ,它將獲取數(shù)據(jù)庫中的所有數(shù)據(jù)。我們也可以為模型自定義 manager ,我們將創(chuàng)建一個獲取所有 published 的文章的 manager 。
可以采用兩種方法為模型添加 manager :1. 添加額外的 manager 方法;2. 修改最初 manager 的 Queryset 。第一種方法看起來像 Post.objects.my_manager() ,第二種看起來像 Post.my_manager.all() 。manager 允許我們使用 Post.published 獲取文章。
編輯 blog 應(yīng)用的 models.py 文件來添加自定義 manager :
from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
# Create your models here.
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager, self).get_queryset().filter(
status='published')
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')
objects = models.Manager()
published = PublishedManager()
class Meta:
ordering = ('-publish',)
def __str__(self):
return self.title
get_queryset() 是獲取 QuerySet 的方法,自定義的 manger 增加了 filter(status='published') 限制。我們已經(jīng)定義了自定義 manager 并將其添加到 Post 模型中,現(xiàn)在我們用它來實現(xiàn)查詢,比如,我們需要查詢所有標題以 Who 開頭的已發(fā)表的文章:
In [4]: Post.published.filter(title__startswith='Who')
Out[4]: <QuerySet []>
建立 list 和 detail 視圖
現(xiàn)在我們已經(jīng)學(xué)習(xí)了一些 ORM 的知識,可以為 blog 創(chuàng)建視圖了。Django 視圖是一個 python 函數(shù),用來接收web請求并返回 web響應(yīng),視圖內(nèi)部實現(xiàn)獲得期望響應(yīng)的所有邏輯。
首先,我們將創(chuàng)建應(yīng)用視圖,然后為每個視圖定義 URL ,最后我們創(chuàng)建 HTML模板來渲染視圖生成的數(shù)據(jù)。每個視圖都將為模板傳入數(shù)據(jù),然后返回一個渲染好的 HTTP響應(yīng)。
創(chuàng)建 list 和 detail 視圖
我們先來創(chuàng)建一個展示文章列表的視圖,編輯 blog 應(yīng)用的 views.py 文件:
from django.shortcuts import render, get_object_or_404
?
from .models import Post
?
?
# Create your views here.
def post_list(request):
posts = Post.published.all()
return render(request, 'blog/post/list.html', {'posts': posts})
我們剛剛創(chuàng)建了第一個 Django 視圖,post_list 視圖只有一個參數(shù)( request對象)。所有視圖都需要輸入 request 。在這個視圖中,我們獲取所有 published 狀態(tài)的文章(通過前面的 published manager 獲得)。
最后,我們使用 Django 提供的 render 渲染模板。這個函數(shù)的輸入?yún)?shù)包括 request對象、模板路徑和模板渲染需要的數(shù)據(jù),它返回一個包含渲染文本的 HttpResponse對象。render() 快捷函數(shù)處理 request 的 context ,因此,給定模板可以獲得模板 context 處理器設(shè)置的任意變量。模板 context 處理器只是用來為 context 設(shè)置變量。我們將在第三章(擴展模板應(yīng)用)學(xué)習(xí)如何使用。
我們創(chuàng)建第二個視圖來展示一篇文章。向 views.py 中增加下面的函數(shù):
def post_detail(request, year, month, day, slug):
post = get_object_or_404(Post, slug=slug, status='published',
publish__year=year, publish__month=month,
publish__day=day)
return render(request, 'blog/post/detail.html', {'post': post})
這是文章詳細視圖,視圖的輸入?yún)?shù)包括獲取某天slug為slug的文章需要使用的 year、month、day、slug。注意,我們在創(chuàng)建 Post 模型時向 slug 字段設(shè)置了 unique_for_date 參數(shù),這樣,我們可以確定滿足某天某個 slug的文章是唯一的,首先,通過 get_object_or_404() 來獲取需要的文章,這個函數(shù)獲得滿足輸入?yún)?shù)的對象,如果沒找到相應(yīng)對象則加載 HTTP 404 。最后,我們使用 render() 函數(shù)來使用模板渲染數(shù)據(jù)。
為視圖添加 URL 模式
URL 模式由一個 python 正則表達式、一個視圖和一個項目范圍內(nèi)唯一的名稱組成。Django 遍歷每個 URL 模式然后停在第一個匹配的 URL 。然后,Django 導(dǎo)入匹配 URL 模式的視圖(傳入一個 HttpRequset 類實例、關(guān)鍵詞或位置參數(shù))并執(zhí)行。
如果你之前沒有處理過正則表達式,可能需要了解一下:https://docs.python.org/3/howto/regex.html。
在 blog 應(yīng)用的目錄下創(chuàng)建一個 urls.py 文件,并添加下面的代碼:
from django.conf.urls import url
from . import views
app_name = 'blog'
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})/(?P<slug>[-\w]+)/$',
views.post_detail, name='post_detail'), ]
筆者注:
根據(jù) django 1.11 的定義方法,這里設(shè)置了 app_name,代替在后面主 URL 中設(shè)置 app_name。
第一個 URL 模式并不接收任何參數(shù)并與 post_list 視圖匹配。第二個模式接收四個參數(shù)并與 post_detail 視圖匹配。讓我們看一下 URL 模式的正則表達式:
year:需要四位數(shù)字;
month:需要兩位數(shù)字,1-9 月需要前面加 0 ;
days:需要兩位數(shù)字;
slug:由單詞和連字符組成。
注意:
為每個 app 創(chuàng)建一個 urls.py 文件以便于其它項目重用你的應(yīng)用。
現(xiàn)在需要將 blog 應(yīng)用的 URL 模式放到項目的主 URL 模式中。編輯項目 mysite3 目錄下的 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'))]
這樣 Django 就可以將 blog 應(yīng)用 urls.py 中定義的 url 放到 blog/ 下。
為模型設(shè)置 URLs
我們可以使用上一節(jié)定義的 post_detail URL 來為 Post 對象創(chuàng)建 URL 。Django 可以通過為模型添加get_absolute_url() 方法來設(shè)置對象的 URL 。在這個方法里,我們使用 reverse() 方法實現(xiàn)通過名稱和參數(shù)創(chuàng)建URL 。編輯 models.py 文件并添加以下代碼:
from django.urls import reverse
class Post(models.Model):
# ...
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[self.publish.strftime('%Y'),
self.publish.strftime('%m'),
self.publish.strftime('%d'),
self.slug])
筆者注:
Django 1.11 使用 from django.urls import reverse 代替 Django1.8 中的 from django.core.urlresolvers import reverse。
注意,我們使用 strftime() 函數(shù)來創(chuàng)建 URL 。我們將在模板中使用 get_absolute_url() 方法。
筆者注:
官方相關(guān)材料:https://docs.djangoproject.com/en/1.11/ref/models/instances/#get_absolute_url
為視圖添加模板
我們已經(jīng)為應(yīng)用創(chuàng)建了視圖和 URL 模式?,F(xiàn)在需要添加模板來展示文章了。
在 blog 應(yīng)用下創(chuàng)建以下目錄和文件:

上圖是模板的文件結(jié)構(gòu)。base.html 文件將包含網(wǎng)站的 HTML 結(jié)構(gòu)并將內(nèi)容劃分為主內(nèi)容和邊欄。list.html 和detail.html 文件將繼承 base.html 文件來分別渲染文章列表視圖和細節(jié)視圖。
Django 的模板語言可以指定如何展示數(shù)據(jù)。它基于模板標簽(格式為 {% tag %} )、變量(格式為 {{ variable }})、過濾器(格式為 {{ variable|filter }} )??梢詮墓倬W(wǎng)查看所有內(nèi)置標簽和過濾器:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/。
在 base.html 中添加以下代碼:
{% load static %}
<!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>
筆者注:
Django 1.11 使用 {% load static %} 代替了原文的 {% load staticfiles %}。
{% load static %} 告訴 Django 加載 django.contrib.staticfiles 應(yīng)用提供的模板標簽和過濾器。加載完后,可以在整個模板范圍內(nèi)使用 {% static %} 標簽,通過這個標簽我們可以 include 本例中 blog 應(yīng)用 static 文件夾下的blog.css 文件。將這個目錄的文件復(fù)制到你項目中的相同位置來使用靜態(tài)文件。
我們可以看到這里有兩個 {% block %} 標簽。這些標簽告訴 Django 在該區(qū)域定義一個 block 。繼承這個模板的模板可以填充這個 block 。我們定義了一個名為 title 的 block 和一個名為 content 的 block 。
我們來編輯 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 %} 模板標簽來繼承 blog/base.html 。然后填充 title 和 content block 。模板遍歷所有文章,依次展示它們的標題、日期、作者、內(nèi)容,標題附帶指向文章頁面的鏈接。文章內(nèi)容使用了兩個模板過濾器:truncatewords 截取指定長度的內(nèi)容,linebreaks 將輸出轉(zhuǎn)換為 HTML 斷行格式。還可以添加任意過濾器應(yīng)用到之前的輸出。
打開 shell,在 blog 應(yīng)用根目錄運行 python manage.py runserver 命令來啟動開發(fā)服務(wù)器。在瀏覽器中打開http://http://127.0.0.1:8000/blog/,我們將看到下面的內(nèi)容,注意,這里需要設(shè)置一些狀態(tài)為 published 的文章,才能看到類似這樣的輸出:

現(xiàn)在,我們來編輯 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)在我們回到瀏覽器,點擊任意一篇文章的標題,將看到以下頁面:

它的 url 為 : http://127.0.0.1:8000/blog/2017/10/25/meet-django/。我們已經(jīng)創(chuàng)建了用戶友好的 blog 文章。
添加分頁
添加一定數(shù)量的文章后,我們會發(fā)現(xiàn)需要對文章進行分頁。Django內(nèi)置 paginator 類來幫助我們非常容易地實現(xiàn)分頁。
編輯 blog 應(yīng)用的 views.py 視圖導(dǎo)入 Django paginator 類,并修改 post_list 函數(shù):
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
object_list = Post.published.all()
paginator = Paginator(object_list, 3) # 3 post 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})
分頁是這樣工作的:
使用每頁想要顯示的文章數(shù)量創(chuàng)建 Paginator 實例;
從 request 的 GET 參數(shù)中獲取請求的頁碼;
從 Paginator 的 page() 方法獲取文章列表的 queryset ,并設(shè)為posts;
如果步驟 2 中得到的頁碼不是整數(shù),則取第一頁的文章進行展示,如果得到的頁碼大于最大分頁數(shù),那么取最后一頁的文章進行展示。
使用模板渲染步驟 2 中的 page 和步驟 3 中的 posts ,返回 httpResponse。
然后,我們需要創(chuàng)建一個展示分頁的模板,這個模板可以用于任意需要分頁的模板。在 blog 應(yīng)用的templates\blog 文件夾下的新建一個名為 pagination.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>
分頁模板需要一個 Page 對象來渲染前一頁鏈接、后一頁鏈接、展示當前頁碼及總頁碼。讓我們回到blog/post/list.html 模板將 pagination.html 模板添加到 {% content %} 的底部:
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
...
{% include "blog/pagination.html" with page=posts %}
{% endblock %}
筆者注:
原文中為 {% include "pagination.html" %},Django 1.11 需要使用 {% include "blog/pagination.html" %}
由于我們傳入模板的 Page 對象的名稱為 posts ,現(xiàn)在我們已經(jīng)將分頁模板放到了文章列表模板。我們可以采用這個方法來復(fù)用分頁模板。
現(xiàn)在,打開http://http://127.0.0.1:8000/blog/,我們會發(fā)現(xiàn)底部多了頁碼信息:

筆者注:
分頁功能正常顯示需要 Post 模型有 3 個以上對象,點擊頁面下方的 Next 后,后一頁的url 變?yōu)?http://127.0.0.1:8000/blog/?page=2 ,點擊previous會回到前一個頁面,前一個頁面的 url 由http://127.0.0.1:8000/blog/ 變?yōu)?http://127.0.0.1:8000/blog/?page=1。
使用類視圖
視圖是一個輸入 web請求輸出 web響應(yīng)的函數(shù),我們也可以使用類方法定義視圖。Django 提供視圖類,這些視圖類解決了 HTTP 匹配和其它功能。下面是創(chuàng)建視圖的另一種方法。
我們將使用 Django 提供的類視圖 ListView 來修改 post_list 視圖。 ListView 類視圖允許我們列出任意類型的對象。
編輯 blog 應(yīng)用的 views.py 文件并添加以下代碼:
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.objects.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
這個類視圖與前面的 post_list 視圖功能類似。我們告訴 ListView :
使用 queryset 獲取所有對象,我們也可以指定 model=Post ,Django 將為我們創(chuàng)建 Post.objects.all() 。
使用內(nèi)容變量 posts 表示查詢結(jié)果,如果不指 定context_object_name ,默認變量名為object_list。
將查詢結(jié)果按照每頁 3 項進行分頁。
使用自定義模板渲染頁面。如果我們不設(shè)置模板,ListView將使用 blog/post_list.html 作為默認模板。
現(xiàn)在打開 blog 應(yīng)用的 urls.py 文件,注釋掉原來的 post_list URL 并為 PostListView 添加 URL :
from django.conf.urls import url
from . import views
urlpatterns = [
# url(r'^$', views.post_list, name='post_list'),# post views
url(r'^$', views.PostListView.as_view(), name='post_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
views.post_detail, name='post_detail'), ]
為了保證分頁正常工作,我們需要向模板傳入正確的 page 對象,Django ListView 中 page 對象為 page_obj ,因此,我們需要修改 templates/blog/post/list.html ,將context最后一句更改為:
{% include "blog/pagination.html" with page=page_obj %}
筆者注:
整個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 %} {% include "blog/pagination.html" with page=page_obj %} {% endblock %}```
在瀏覽器中打開 http://127.0.0.1:8000/blog/,檢查是否與 post_list 視圖實現(xiàn)的頁面一樣。這是類視圖的簡單例子,我們將在第十章(Building an e-Learning Platform)及后續(xù)章節(jié)繼續(xù)學(xué)習(xí)。
總結(jié)
這一章,我們已經(jīng)通過創(chuàng)建一個簡單的 blog 應(yīng)用了解了 Django web 框架的基本內(nèi)容。我們設(shè)計了數(shù)據(jù)模型并為項目應(yīng)用了遷移,還為 blog 創(chuàng)建了視圖、模板和 URL,并實現(xiàn)了分頁。
下一章,我們將為 blog 應(yīng)用添加評論系統(tǒng)、標簽功能、并實現(xiàn)通過 e-mail 分享文章。

