《Python程式設計:從入門到實踐》筆記。
從本篇開始將是該書的最後一個專案,將用3篇文章來介紹Django的基礎。完成一個“學習筆記”的小網站。
1. 前言
在本篇中,我們將:
- 用Django來開發一個名為“學習筆記”(Learning Log)的專案;
- 為這個專案制定規範,然後為應用程式使用的資料定義模型;
- 使用Django的管理系統來輸入一些初試資料,再學習編寫檢視和模板,讓Django能夠為我們的網站建立網頁。
不過在開始之前,請先新建一個虛擬環境並安裝Django。如果沒有虛擬環境,通過pip
安裝的所有庫都會儲存到python的site-packages
目錄下。開發多個專案時,都會用同一個python,而某些專案並不需要其中的所有第三方庫,但如果將這些不需要庫的刪除,又會影響到其他專案。而且,如果A專案需要Django2.0.4,B專案需要Django2.0.0,這該怎麼辦?此時就需要虛擬環境。它其實就相當於一個新的資料夾,將新專案與其他專案隔離,本專案的庫不與其他專案的庫相關聯,類似於作業系統的多使用者概念。
如果使用的是Python 3,可以使用命令:
python -m venv ll_env
複製程式碼
如果該命令不成功,可能是沒有安裝virtualenv
模組:
pip install virtualenv
複製程式碼
然後建立並啟用虛擬環境:
virtualenv ll_env
# linux:
source ll_env/bin/activate
# windows:
ll_env\Scripts\activate
# 停用:
deactivate
複製程式碼
管理虛擬環境的庫還有很多,有興趣的話可以到網上搜一搜。
如果你使用的是新版的PyCharm,那麼它在新建專案的時候預設就會建立新的虛擬環境。
啟用虛擬環境後就可以安裝Django了:
pip install django
複製程式碼
2. 建立專案
2.1 在Django中建立專案
2.1.1 生成專案
在虛擬環境中執行如下命令:
# 主要最後有個實心句號!
# 這個句點讓新專案使用合適的目錄結構,這樣開發完成後可以輕鬆地將應用程式部署到伺服器
django-admin startproject learning_log .
複製程式碼
執行上述命令後,將多出一個manage.py
檔案和一個learning_log
資料夾,當然還有本身的一個ll_env
資料夾。
而在learning_log
資料夾中,又有四個檔案:__init__.py
,settings.py
,urls.py
,wsgi.py
。
manage.py
是一個簡單的程式,它接收命令並將其交給Django的相關部分去執行;settings.py
指定Django如何與你的系統互動以及如何管理專案,其實就是配置檔案;urls.py
告訴Django應建立哪些網頁來響應瀏覽器請求;wsgi.py
是web server gateway interface(Web伺服器閘道器介面)的縮寫,幫助Django提供它建立的檔案。
至於__init__.py
,它是個空檔案,Python的每個模組下必須要有這個檔案。
2.1.2 建立資料庫
Django將大部分與專案相關的資訊都儲存在資料庫中,所有還需要建立一個供Django使用的資料庫。依然是在虛擬環境下執行如下命令:
python manage.py migrate
複製程式碼
在PyCharm中的話,可以通過點選工具欄Tools中的Run manage.py Task(Ctrl+Alt+R),在彈出的命令列中直接輸入原命令中manage.py
後面的部分,後面的命令也可以這樣執行([appname]
是自動提示)。
"migrate"這個單詞其實是遷移的意思,並不是“建立(create)”。之所以使用這個詞,是因為一般將修改資料庫的過程稱為遷移資料庫(筆者資料庫學得渣,這段解釋直接從書裡照搬的,希望哪位大神在留言區解釋一波)。如果是剛建立的專案,並且第一次執行,將會得到如下輸出:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
-- snip --
Applying sessions.0001_initial... OK
複製程式碼
從第2行結果可以看出,Django將建立和修改資料庫看做是對資料庫的遷移,Apply all migrations確保資料庫結構與當前程式碼匹配(比如你修改了類的結構,新增了屬性,這就相當於修改了資料表)。
執行命令後,專案的根目錄下會多出一個名為db.sqlite3
的資料庫檔案。SQLite是一種使用單個檔案的輕量級資料庫,常用於開發簡單應用程式,它讓你不用太關注資料庫管理的問題。
2.1.3 執行專案
依然在專案的虛擬環境下輸入如下命令:
python manage.py runserver
複製程式碼
得到如下輸出:
Performing system checks...
System check identified no issues (0 silenced).
April 21, 2018 - 20:46:48
Django version 2.0.4, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
複製程式碼
現在在瀏覽器中位址列輸入localhost:8000
(或者127.0.0.1:8000
),將得到如下頁面:
這是最新版的Django的預設啟動介面。
2.2 建立應用程式(APP)
2.2.1 建立模型
Django專案由一系列應用程式組成,它們協同工作,讓專案成為一個整體。我們在專案根目錄下執行如下命令,建立一個名為learning_logs的應用程式:
python manage.py startapp learning_logs
複製程式碼
執行命令後,根目錄下會多出一個名為learning_logs
的資料夾(筆者第一次接觸Django的時候發現這玩意兒居然叫做APP,和平時用的手機上的各種APP相差也太大了,很不適應),它的結構如下:
重要的是其中的models.py
、admin.py
和views.py
檔案,我們將使用models.py
來定義我們要在應用程式中管理的資料。另外兩個檔案稍後再介紹。
開啟models.py
檔案,發現其中自帶兩行程式碼:
from django.db import models
# Create your models here.
複製程式碼
在程式碼層面,模型就是一個類,和之前的文章中的類一樣,包含屬性和方法。下面建立一個“主題”類:
from django.db import models
class Topic(models.Model):
"""使用者學習的主題"""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""返回模型的字串表示"""
return self.text
複製程式碼
Model
類是Django中的一個定義了模型基本功能的類。Topic
類只有兩個屬性text
和date_added
。模型中如CharField
,DateTimeField
這些欄位還有很多,Django自動根據資料庫的不同呼叫不同的SQL語句建立資料表,即遮蔽底層資料庫的差異。
同時還重寫了__str__()
方法,之所以說“重寫”是因為每個類都有這個方法,當直接將一個類A
放入print()
之類的語句中時,就會呼叫A
的__str__()
方法。如果沒有重寫這個方法,一般會輸出這個物件的記憶體地址之類的,大家可以蘇隨便寫個類試一試。
2.2.2 啟用模型
使用模型前,必須將APP包含到專案中,開啟settings.py
檔案,將APP新增到INSTALLED_APPS
中:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 新增你的app,上面的都是自帶的
"learning_logs.apps.LearningLogsConfig",
]
複製程式碼
這裡有個需要注意的地方,由於這本書的2016年出版的,當時Django還沒有到達2.0版本,所以在書中,註冊APP是這樣寫的:
INSTALLED_APPS = [
-- snip --
# 新增你的app,上面的都是自帶的
"learning_logs",
]
複製程式碼
現在官方文件中是按照第一種方式註冊APP,並且,最新版的Django在新建APP後,在APP的目錄下還多了一個apps.py
檔案,該檔案預設有一個根據APP名稱建立的類,此處為LearningLogsConfig
,內容如下:
from django.apps import AppConfig
class LearningLogsConfig(AppConfig):
name = 'learning_logs'
複製程式碼
回到主線,在終端中輸入:
python manage.py makemigrations learning_logs
# 輸出
Migrations for 'learning_logs':
learning_logs\migrations\0001_initial.py
- Create model Topic
複製程式碼
makemigrations
讓Django
確定該如何修改資料庫,使其能夠儲存與我們定義的新模型相關聯的資料。從輸出可以看出,在APP目錄下的migrations
資料夾中建立了一個名為0001_initial.py
的遷移檔案,該檔案將在資料庫中為模型Topic
建立一個表。
最後,在命令列中輸入:
python manage.py migrate
# 輸出:
Running migrations:
Applying learning_logs.0001_initial... OK
複製程式碼
總結:每當需要修改模型時,都採取如下三個步驟:修改models.py
,對你的APP呼叫makemigrations
,讓Django遷移專案migrate
。
2.2.3 Django管理網站
Django自帶管理後臺。首先為網站建立一個超級使用者。在中斷輸入:
python manage.py createsuperuser
複製程式碼
隨後按提示輸入使用者名稱和密碼即可,郵箱地址可以留空(直接回車)。
Django自動在管理網站中新增一些模型,如User
和Group
,但對於我們建立的模型,必須手工註冊。
注意前面提到的和models.py
在同一目錄的admin.py
檔案,這就是註冊自行編寫的模型的地方,在該檔案中加入後兩行程式碼:
# 第一行程式碼是自帶的
from django.contrib import admin
from learning_logs.models import Topic
admin.site.register(Topic)
複製程式碼
現在登入Django自帶的網站管理頁面 http://localhost:8000/admin/ 登入剛才建立的超級使用者和密碼後將出現如下介面:
在這裡你可以管理使用者和組,以及和模型Topic相關的資料。
現在先手動新增兩個主題:點選Add
建立Chess
和Rock Climbing
。
2.2.4 定義模型Entry
為使能在每個主題下新增條目,需要定義Entry
模型,Entry
與Topic
的關係是多對一。同樣是在models.py
中新增模型:
from django.db import models
class Topic(models.Model):
-- snip --
class Entry(models.Model):
"""學到的有關某個主題的具體知識"""
# 由於和“主題”是多對一的關係,所以“主題”是“條目”的外來鍵
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "entries"
def __str__(self):
"""返回模型的字串表示"""
# 由於條目包含的文字可能很長,只顯示前50個字元
return self.text[:50] + "..."
複製程式碼
注意其中巢狀了一個Meta
類,它用於管理模型的額外資訊。它讓我們能夠設定一個特殊屬性,讓Django在需要時使用Entries
來表示多個條目。如果沒有這個類,Django將使用Entrys
來表示多個條目(保證英語語法正確......不得不說,本書作者還是很細心的)。
新增了新模型,所以需要再次遷移資料庫,過程就是前面講的三個步驟中的後兩步。然後在admin.py
中註冊Entry
。
為了讓這個網站有一些初試資料,新增三個條目:兩個Chess
的,一個Rock Climbing
的。在管理頁面中點選Entries
的Add
按鈕,你將看到一個下拉選單,用於選擇Topic
,還有個文字框,用於輸入內容。隨便輸入一點內容就可以,具體內容不再詳細列出。
2.2.5 Django shell
輸入一些資料後,可通過互動式終端會話以程式設計方式檢視這些資料。這種互動式環境稱為Django shell,常用語測試專案和排除故障。以下是在shell中的一些操作:
(ll_env)learning_log$ python manage.py shell # 啟動shell
>>> from learning_logs.models import Topic
>>> Topic.objects.all() # 獲得模型Topic的所有例項
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]> # 返回了一個查詢集QuerySet
>>> topics = Topic.objects.all() # 查詢每個Topic物件
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing
>>> t = Topic.objects.get(id=1) # 根據id檢視Chess模型的具體內容
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2018, 4, 22, 2, 4, 3, 723045, tzinfo=<UTC>)
>>> t.entry_set.all() # 檢視該主題下的所有條目,通過外來鍵查詢,進行了人為換行
<QuerySet [<Entry: The opening is the first part of the game, roughly...>,
<Entry: In the opening phase of the game, it's important t...>]>
複製程式碼
為了通過外來鍵獲取資料,注意查詢時的語法:模型小寫名稱+下劃線+set
,如第19行程式碼。編寫使用者可請求的網頁時將使用這樣的語法。如果程式碼在shell中的行為符合預期,那麼它們在專案檔案中也能正確工作。
3. 建立網頁:學習筆記主頁
這一部分,書中內容和新版的Django出入比較多。
3.1 對映URL
Django建立網頁的過程通常分三個階段:定義URL、編寫檢視和編寫模板。
URL模式描述了URL是如何設計的,讓Django知道如何將瀏覽器請求與網站URL匹配,以確定返回哪個網頁。每個URL都被對映到特定的檢視——檢視函式獲取並處理網頁所需的資料。檢視函式通常呼叫一個模板,後者生成瀏覽器能夠理解的網頁。
目前,基礎URL(http://localhost:8000/ )返回預設的Django頁面,現在修改這個對映,將其對映到我們自己編寫的主頁。
開啟learning_log
資料夾中的urls.py
,將看到如下內容:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
# 書中的內容和實際的內容有些出入,以下是書中內容:
# 新版Django簡化了URL路由寫法
# from django.conf.urls import include, url
# from django.contrib import admin
#
# from django.conf.urls import include, url
# from django.contrib import admin
#
# urlpatterns = [
# url(r'^admin/', include(admin.site.urls)),
# ]
複製程式碼
變數urlpatterns
包含專案中的APP的URL,admin.site.urls
模組定義了可在管理網站中請求的所有URL。現在新增程式碼:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("learning_logs.urls")),
# 書中程式碼:
# url(r"", include("learning_logs.urls", namespace="learning_logs")),
]
複製程式碼
注意:書中在此處的include()
函式中傳入了關鍵字引數namespace="learning_logs"
,但在新版中,名稱空間(namespace) 是在APP的urls.py
中設定的:在urlpatterns
變數前新建一個值為"learning_logs"
的app_name
變數。
namespace
讓learning_logs
的URL同專案中的其他URL區分開,對專案進行擴充套件時,這樣做十分有用。
還需要在learning_logs
中建立另一個urls.py
檔案:
"""定義learning_logs的URL模式"""
from django.urls import path
# 從當前的urls.py模組所在的資料夾中匯入檢視
from . import views
# 該變數包含該APP中可請求的網頁
urlpatterns = [
# 主頁
path("", views.index, name="index"),
]
複製程式碼
path()
的第一個引數是正規表示式,第二個引數是要呼叫的檢視函式(當請求的URL和第一個引數匹配時呼叫),第三個引數為這個URL模式指定一個名字,相當於將這個模式儲存在變數index
中,以後每當需要提供這個主頁的連線時都使用這個名字,而不用再編寫URL。
3.2 編寫檢視
檢視函式接收請求中的資訊,準備好生成網頁所需的資料,再將這些資料傳送給瀏覽器,在傳送之前,還套用了網頁的模板(如果有模板的話)。
當我們在建立APP時,它的資料夾中有一個views.py
檔案,該檔案預設只有一個匯入語句,匯入了函式render()
,現在編寫這個檔案:
from django.shortcuts import render
def index(request):
"""學習筆記主頁"""
# 將請求的資料套用到模板中,然後返回給瀏覽器
# 第一個引數是原始請求物件,第二是可用於建立網頁的模板
return render(request, "learning_logs/index.html")
複製程式碼
3.3 編寫模板
模板定義了網頁的結構,指定了網頁時什麼樣的。每當網頁被請求時,Django將填入相關的資料。模板中能訪問檢視提供的任何資料。
現在建立上面程式碼中的index.html
模板:在learning_logs
資料夾中新建一個templates
資料夾,再在這個資料夾中新建一個和APP同名的資料夾,即learning_logs
資料夾,最後,在這個learning_logs
資料夾中新建index.html
檔案。看起來好像有點多餘,但這是Django能夠解析的目錄結構。index.html
檔案的內容如下:
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning,
for any topic your're learning about.</p>
複製程式碼
現在當你訪問http://localhost:8000 時,將看到如下主頁:
建立網頁的過程看起來可能很複雜,實際上這就是一個簡單的MVC
(Model
,View
,Controller
)模型,但在Django中被稱為MVT
(Model
,View
,Template
)
這種方式使程式碼結構清晰,修改方便,也讓我們能各司其職,比如,資料庫專家就專注於Model
,程式設計師專注於View
,Web
設計人員專注於Template
。
4. 建立其他網頁
我們繼續擴充套件我們的專案。建立兩個用於顯示資料的網頁,其中給一個列出所有的主題,另一個顯示特定主題的所有條目。對於每個網頁都指定URL模式,編寫一個檢視函式,並編寫一個模板。但這麼做之前,我們先建立一個父模板,專案中的其他模板都將繼承它。
4.1 模板繼承
4.1.1 父模板base.html
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
{% block content %}{% endblock content %}
複製程式碼
該檔案儲存在index.html
所在的目錄中。該檔案包含所有頁面都有的元素,其他模板繼承自它。目前為止,所有頁面共有的元素還只有頂端的標題。
我們將這個標題設定為到主頁的連結。為了建立這個連結,使用了模板標籤(花括號加百分號的組合),其中learning_logs
是專案的名稱空間,index就是這個專案主頁的URL模式名(注意翻看前面小節的程式碼)。
還建立了一個塊標籤(block
),這個塊名為content
,是一個佔位符,其中包含的資訊將由子模板指定。
4.1.2 子模板
從新編寫index.html
,使其繼承base.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Learning Log helps you keep track of your learning,
for any topic you're learning about.</p>
{% endblock content %}
複製程式碼
第1行程式碼匯入父模板中的所有內容;extends
後面跟父模板所在的路徑,雖然這倆檔案都在同一目錄下,但不能省略掉前面的learning_logs/
。
3到6行程式碼插入了一個名為content
的塊標籤,不是從父模板繼承的內容都包含在content
塊中。第6行程式碼指出內容定義的結束位置。
注意:在大型專案中,通常有一個用於整個網站的父模板base.html
,且網站的每個主要部分都有一個父模板。每個主要部分的父模板又都繼承自base.html
,而網站的每個網頁都繼承相應部分的父模板。
4.2 主題頁面
該頁面顯示使用者建立的所有主題。他是第一個需要使用資料的頁面。
4.2.1 定義URL模式
在APP的urls.py
中新增能轉到topics.html
的URL模式
urlpatterns = [
# 主頁
path("", views.index, name="index"),
path("topics/", views.topics, name="topics")
]
複製程式碼
4.2.2 新增檢視
在views.py
中新增相應的函式:
from django.shortcuts import render
from .models import Topic
def index(request):
-- snip --
def topics(request):
"""顯示所有的主題"""
topics = Topic.objects.order_by("date_added")
# 一個上下文字典,傳遞給模板
context = {"topics": topics}
return render(request, "learning_logs/topics.html", context)
複製程式碼
4.2.3 建立模板
建立一個模板,用於顯示所有的主題:
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>{{ topic }}</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
複製程式碼
在這個模板中,我們使用了一個相當於for
迴圈的模板標籤,它遍歷字典context
中的列表topics
。並且注意,該模板中沒有出現context
字樣,相當於模板自動從context
取得topics
的內容。
模板中使用的程式碼與Python程式碼存在一些重要差別:Python使用縮排來指出哪些程式碼行是for
迴圈的組成部分,而在模板中,每個for
迴圈都必須明確的指出結束為止。
要在模板中列印變數,需要將變數名用雙花括號括起來,每次迴圈時,該程式碼都會被替換為topic
的當前值。
還使用了空模板標籤(empty),它告訴Django在列表topics
為空時該怎麼辦。
這些花括號都不會出現在網頁中,它們只是用於告訴Django我們使用了一個模板變數。
修改父模板base.html
,使其包含能轉到主題頁面的連線(第3行為新增的程式碼,第2行最後新增了一個連字元):
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
複製程式碼
現在再次輸入http://localhost:8000 將看到我們新增的Topics
連結,點選它,將跳轉到如下頁面:
4.3 特定主題頁面
該頁面用於顯示該主題下的所有條目。和上面的步驟一樣,定義URL模式,編寫views.py
中的處理函式,編寫網頁模板。
4.3.1 URL模式
urlpatterns = [
-- snip --
path("topics/<int:topic_id>/", views.topic, name="topic"),
]
複製程式碼
4.3.2 新增檢視
def topic(request, topic_id):
"""顯示單個主題及其所有的條目"""
# 通過Topic的id獲得所有條目
topic = Topic.objects.get(id=topic_id)
# 前面的減號表示降序排序
entries = topic.entry_set.order_by("-date_added")
context = {"topic": topic, "entries": entries}
return render(request, "learning_logs/topic.html", context)
複製程式碼
第4,6行的程式碼叫做查詢,如果你和筆者一樣是個初學者,比起程式碼都寫完了,最後發現查詢語句有問題,再來修改,那麼先在Django shell中執行下程式碼看看結果,這樣更高效。
4.3.3 模板topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topic: {{ topic }}</p>
<p>Entries:</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
複製程式碼
在Django模板中,豎線(|)表示模板過濾器(對模板變數的值進行修改的函式)。過濾器date: 'M d, Y H:i'以這樣的格式顯示時間戳:April 22, 2018 16:09。接下來的一行顯示txt的完整值,而不僅僅是entry
的前50個字元。過濾器linebreaks
將包含換行符的長條目轉換成瀏覽器能夠理解的格式,以免顯示一個不換行的文字塊。
4.3.4 修改topics.html模板
-- snip --
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</li>
{% empty %}
-- snip --
複製程式碼
4.4 效果
現在我們回到http://localhost:8000/topics/ 頁面,隨便點選一個主題,比如第一個,將得到以下介面:
5. 小結
上述內容主要有:
- 在虛擬環境中安裝Django,建立專案,並核實該專案已正確地建立;
- 在專案中建立APP;
- 建立表示APP資料的模型;
- 建立資料庫,以及在修改模型後,Django可以為遷移資料庫提供什麼樣的幫助;
- 建立超級使用者,並使用管理網路輸入初試資料;
- Django shell;
- 定義URL、建立檢視函式、網頁模板(包括繼承)。
下一篇中,我們將:
- 建立對使用者友好而直觀的網頁,讓使用者無需通過管理網站就能新增新的主題和條目(個人感覺沒必要這麼做,Django自帶的管理系統挺好用的),以及編輯既有的條目;
- 新增一個使用者註冊系統,讓使用者能夠建立賬戶。
迎大家關注我的微信公眾號"程式碼港" & 個人網站 www.vpointer.net ~