Python系列19-Web應(yīng)用程序-Django入門

一.Django入門

當(dāng)今的網(wǎng)站實(shí)際上都是富應(yīng)用程序(rich application),就像成熟的桌面應(yīng)用程序一樣。Python提供了一組開(kāi)發(fā)Web應(yīng)用程序的卓越工具。在本章中,你將學(xué)習(xí)如何使用Django(http://djangoproject.com/ )來(lái)開(kāi)發(fā)一個(gè)名為“學(xué)習(xí)筆記”(Learning Log)的項(xiàng)目,這是一個(gè)在線日志系統(tǒng),讓你能夠記錄所學(xué)習(xí)的有關(guān)特定主題的知識(shí)。

我們將為這個(gè)項(xiàng)目制定規(guī)范,然后為應(yīng)用程序使用的數(shù)據(jù)定義模型。我們將使用Django的管理系統(tǒng)來(lái)輸入一些初始數(shù)據(jù),再學(xué)習(xí)編寫視圖和模板,讓Django能夠?yàn)槲覀兊木W(wǎng)站創(chuàng)建網(wǎng)頁(yè)。

Django是一個(gè)Web框架 ——一套用于幫助開(kāi)發(fā)交互式網(wǎng)站的工具。Django能夠響應(yīng)網(wǎng)頁(yè)請(qǐng)求,還能讓你更輕松地讀寫數(shù)據(jù)庫(kù)、管理用戶等。

1.1 建立項(xiàng)目

建立項(xiàng)目時(shí),首先需要以規(guī)范的方式對(duì)項(xiàng)目進(jìn)行描述,再建立虛擬環(huán)境,以便在其中創(chuàng)建項(xiàng)目。

1.1.1 制定規(guī)范

完整的規(guī)范詳細(xì)說(shuō)明了項(xiàng)目的目標(biāo),闡述了項(xiàng)目的功能,并討論了項(xiàng)目的外觀和用戶界面。與任何良好的項(xiàng)目規(guī)劃和商業(yè)計(jì)劃書一樣,規(guī)范應(yīng)突出重點(diǎn),幫助避免項(xiàng)目偏離軌道。這里不會(huì)制定完整的項(xiàng)目規(guī)劃,而只列出一些明確的目標(biāo),以突出開(kāi)發(fā)的重點(diǎn)。我們制定的規(guī)范如下:

我們要編寫一個(gè)名為“學(xué)習(xí)筆記”的Web應(yīng)用程序,讓用戶能夠記錄感興趣的主題,并在學(xué)習(xí)每個(gè)主題的過(guò)程中添加日志條目?!皩W(xué)習(xí)筆記”的主頁(yè)對(duì)這個(gè)網(wǎng)站進(jìn)行描述,
并邀請(qǐng)用戶注冊(cè)或登錄。用戶登錄后,就可創(chuàng)建新主題、添加新條目以及閱讀既有的條目。

1.1.2 建立虛擬環(huán)境

要使用Django,首先需要建立一個(gè)虛擬工作環(huán)境。虛擬環(huán)境 是系統(tǒng)的一個(gè)位置,你可以在其中安裝包,并將其與其他Python包隔離。

為項(xiàng)目新建一個(gè)目錄,將其命名為my_learning_log,再在終端中切換到這個(gè)目錄,并創(chuàng)建一個(gè)虛擬環(huán)境。如果你使用的是Python 3,可使用如下命令來(lái)創(chuàng)建虛擬環(huán)境

代碼:

python -m venv ll_env

測(cè)試記錄:

C:\Users\Administrator>E:

E:\>cd E:\python\my_learning_log

E:\python\my_learning_log>python -m venv ll_env

E:\python\my_learning_log>

如報(bào)錯(cuò),參考下一章節(jié) 安裝virtualenv

1.1.3 安裝virtualenv

安裝virtualenv

C:\>pip install virtualenv
Collecting virtualenv
  Downloading virtualenv-20.4.3-py2.py3-none-any.whl (7.2 MB)
     |████████████████████████████████| 7.2 MB 21 kB/s
Collecting importlib-resources>=1.0
  Downloading importlib_resources-5.1.2-py3-none-any.whl (25 kB)
Requirement already satisfied: six<2,>=1.9.0 in c:\users\administrator\appdata\local\programs\python\python36\lib\site-packages (from virtualenv) (1.12.0)
Collecting importlib-metadata>=0.12
  Downloading importlib_metadata-3.10.0-py3-none-any.whl (14 kB)
Collecting distlib<1,>=0.3.1
  Downloading distlib-0.3.1-py2.py3-none-any.whl (335 kB)
     |████████████████████████████████| 335 kB 15 kB/s
Collecting appdirs<2,>=1.4.3
  Downloading appdirs-1.4.4-py2.py3-none-any.whl (9.6 kB)
Collecting filelock<4,>=3.0.0
  Downloading filelock-3.0.12-py3-none-any.whl (7.6 kB)
Requirement already satisfied: typing-extensions>=3.6.4 in c:\users\administrator\appdata\local\programs\python\python36\lib\site-packages (from importlib-metad
ata>=0.12->virtualenv) (3.7.2)
Collecting zipp>=0.5
  Downloading zipp-3.4.1-py3-none-any.whl (5.2 kB)
Installing collected packages: zipp, importlib-resources, importlib-metadata, filelock, distlib, appdirs, virtualenv
Successfully installed appdirs-1.4.4 distlib-0.3.1 filelock-3.0.12 importlib-metadata-3.10.0 importlib-resources-5.1.2 virtualenv-20.4.3 zipp-3.4.1

1.1.4 激活虛擬環(huán)境

建立虛擬環(huán)境后,需要使用下面的命令激活它:

-- 激活虛擬環(huán)境
ll_env\Scripts\activate
-- 關(guān)閉虛擬環(huán)境
deactivate

測(cè)試記錄:

E:\python\my_learning_log>ll_env\Scripts\activate
(ll_env) E:\python\my_learning_log>deactivate
E:\python\my_learning_log>

如果關(guān)閉運(yùn)行虛擬環(huán)境的終端,虛擬環(huán)境也將不再處于活動(dòng)狀態(tài)。

1.1.5 安裝Django

創(chuàng)建并激活虛擬環(huán)境后,就可安裝Django了:

代碼:

pip install Django

測(cè)試記錄:

C:\Users\Administrator>E:

E:\>
E:\>cd E:\python\my_learning_log

E:\python\my_learning_log>pip install Django
Collecting Django
  Downloading Django-3.1.7-py3-none-any.whl (7.8 MB)
     |████████████████████████████████| 7.8 MB 1.6 MB/s
Collecting sqlparse>=0.2.2
  Downloading sqlparse-0.4.1-py3-none-any.whl (42 kB)
     |████████████████████████████████| 42 kB 420 kB/s
Requirement already satisfied: pytz in c:\users\administrator\appdata\local\programs\python\python36\lib\site-packages (from Django) (2018.7)
Collecting asgiref<4,>=3.2.10
  Downloading asgiref-3.3.1-py3-none-any.whl (19 kB)
Installing collected packages: sqlparse, asgiref, Django
Successfully installed Django-3.1.7 asgiref-3.3.1 sqlparse-0.4.1

E:\python\my_learning_log>

由于我們是在虛擬環(huán)境中工作,因此在所有的系統(tǒng)中,安裝Django的命令都相同:不需要指定標(biāo)志--user ,也無(wú)需使用python -m pip install package_name 這樣較長(zhǎng)的命令。

別忘了,Django僅在虛擬環(huán)境處于活動(dòng)狀態(tài)時(shí)才可用。

1.1.6 在Django中創(chuàng)建項(xiàng)目

在依然處于活動(dòng)的虛擬環(huán)境的情況下(ll_env包含在括號(hào)內(nèi)),執(zhí)行如下命令來(lái)新建一個(gè)項(xiàng)目

django-admin startproject my_learning_log .
dir
dir my_learning_log

測(cè)試記錄:

(ll_env) E:\python\my_learning_log>django-admin startproject learning_log .

(ll_env) E:\python\my_learning_log>
(ll_env) E:\python\my_learning_log>dir
 驅(qū)動(dòng)器 E 中的卷是 新加卷
 卷的序列號(hào)是 823D-B082

 E:\python\my_learning_log 的目錄

2021/04/01  15:59    <DIR>          .
2021/04/01  15:59    <DIR>          ..
2021/04/01  15:59    <DIR>          learning_log
2021/04/01  15:51    <DIR>          ll_env
2021/04/01  15:59               690 manage.py
               1 個(gè)文件            690 字節(jié)
               4 個(gè)目錄 56,977,850,368 可用字節(jié)

(ll_env) E:\python\my_learning_log>dir learning_log
 驅(qū)動(dòng)器 E 中的卷是 新加卷
 卷的序列號(hào)是 823D-B082

 E:\python\my_learning_log\learning_log 的目錄

2021/04/01  15:59    <DIR>          .
2021/04/01  15:59    <DIR>          ..
2021/04/01  15:59               417 asgi.py
2021/04/01  15:59             3,200 settings.py
2021/04/01  15:59               775 urls.py
2021/04/01  15:59               417 wsgi.py
2021/04/01  15:59                 0 __init__.py
               5 個(gè)文件          4,809 字節(jié)
               2 個(gè)目錄 56,977,850,368 可用字節(jié)

(ll_env) E:\python\my_learning_log>

1.1.7 創(chuàng)建數(shù)據(jù)庫(kù)

Django將大部分與項(xiàng)目相關(guān)的信息都存儲(chǔ)在數(shù)據(jù)庫(kù)中,因此我們需要?jiǎng)?chuàng)建一個(gè)供Django使用的數(shù)據(jù)庫(kù)。為給項(xiàng)目“學(xué)習(xí)筆記”創(chuàng)建數(shù)據(jù)庫(kù),請(qǐng)?jiān)谔幱诨顒?dòng)虛擬環(huán)境中的情況下執(zhí)行下
面的命令:

python manage.py migrate
dir

測(cè)試記錄:

(ll_env) E:\python\my_learning_log>python manage.py migrate
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 admin.0003_logentry_add_action_flag_choices... 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 auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

(ll_env) E:\python\my_learning_log>dir
 驅(qū)動(dòng)器 E 中的卷是 新加卷
 卷的序列號(hào)是 823D-B082

 E:\python\my_learning_log 的目錄

2021/04/01  16:42    <DIR>          .
2021/04/01  16:42    <DIR>          ..
2021/04/01  16:06    <DIR>          .idea
2021/04/01  16:42           131,072 db.sqlite3
2021/04/01  16:42    <DIR>          learning_log
2021/04/01  16:04    <DIR>          ll_env
2021/04/01  15:59               690 manage.py
               2 個(gè)文件        131,762 字節(jié)
               5 個(gè)目錄 56,942,923,776 可用字節(jié)

(ll_env) E:\python\my_learning_log>

我們將修改數(shù)據(jù)庫(kù)稱為遷移 數(shù)據(jù)庫(kù)。首次執(zhí)行命令migrate 時(shí),將讓Django確保數(shù)據(jù)庫(kù)與項(xiàng)目的當(dāng)前狀態(tài)匹配。在使用SQLite(后面將更詳細(xì)地介紹)的新項(xiàng)目中首次執(zhí)行這個(gè)命令時(shí),Django將新建一個(gè)數(shù)據(jù)庫(kù)。Django指出它將創(chuàng)建必要的數(shù)據(jù)庫(kù)表,用于存儲(chǔ)我們將在這個(gè)項(xiàng)目(Synchronize unmigrated apps,同步未遷移的應(yīng)用程序 )中使用的信息,再確保數(shù)據(jù)庫(kù)結(jié)構(gòu)與當(dāng)前代碼(Apply all migrations,應(yīng)用所有的遷移 )匹配。

我們運(yùn)行了命令ls ,其輸出表明Django又創(chuàng)建了一個(gè)文件——db.sqlite3。SQLite是一種使用單個(gè)文件的數(shù)據(jù)庫(kù),是編寫簡(jiǎn)單應(yīng)用程序的理想選擇,因?yàn)樗屇悴挥锰P(guān)注數(shù)據(jù)庫(kù)管理的問(wèn)題。

1.1.8 查看項(xiàng)目

下面來(lái)核實(shí)Django是否正確地創(chuàng)建了項(xiàng)目。為此,可執(zhí)行命令runserver ,如下所示:

(ll_env) E:\python\my_learning_log>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 01, 2021 - 16:44:58
Django version 3.1.7, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Django啟動(dòng)一個(gè)服務(wù)器,讓你能夠查看系統(tǒng)中的項(xiàng)目,了解它們的工作情況。當(dāng)你在瀏覽器中輸入U(xiǎn)RL以請(qǐng)求網(wǎng)頁(yè)時(shí),該Django服務(wù)器將進(jìn)行響應(yīng):生成合適的網(wǎng)頁(yè),并將其發(fā)送給瀏覽器。

Django通過(guò)檢查確認(rèn)正確地創(chuàng)建了項(xiàng)目;它指出了使用的Django版本以及當(dāng)前使用的設(shè)置文件的名稱;它指出了項(xiàng)目的URL。URL http://127.0.0.1:8000/表明項(xiàng)目將在你的計(jì)算機(jī)(即localhost)的端口8000上偵聽(tīng)請(qǐng)求。localhost是一種只處理當(dāng)前系統(tǒng)發(fā)出的請(qǐng)求,而不允許其他任何人查看你正在開(kāi)發(fā)的網(wǎng)頁(yè)的服務(wù)器。
現(xiàn)在打開(kāi)一款Web瀏覽器,并輸入U(xiǎn)RL:http://localhost:8000/;如果這不管用,請(qǐng)輸入http://127.0.0.1:8000/。你將看到類似于圖18-1所示的頁(yè)面,這個(gè)頁(yè)面是Django創(chuàng)建的,讓你知道
到目前為止一切正常?,F(xiàn)在暫時(shí)不要關(guān)閉這個(gè)服務(wù)器。若要關(guān)閉這個(gè)服務(wù)器,按Ctrl + C即可。

image.png

1.2 創(chuàng)建應(yīng)用程序

Django項(xiàng)目 由一系列應(yīng)用程序組成,它們協(xié)同工作,讓項(xiàng)目成為一個(gè)整體。我們暫時(shí)只創(chuàng)建一個(gè)應(yīng)用程序,它將完成項(xiàng)目的大部分工作。

當(dāng)前,在前面打開(kāi)的終端窗口中應(yīng)該還運(yùn)行著runserver 。請(qǐng)?jiān)俅蜷_(kāi)一個(gè)終端窗口(或標(biāo)簽頁(yè)),并切換到manage.py所在的目錄。激活該虛擬環(huán)境,再執(zhí)行命令startapp :

C:\Users\Administrator>E:

E:\>cd E:\python\my_learning_log

E:\python\my_learning_log>ll_env\Scripts\activate
(ll_env) E:\python\my_learning_log>python manage.py startapp learning_logs

(ll_env) E:\python\my_learning_log>dir
 驅(qū)動(dòng)器 E 中的卷是 新加卷
 卷的序列號(hào)是 823D-B082

 E:\python\my_learning_log 的目錄

2021/04/01  16:48    <DIR>          .
2021/04/01  16:48    <DIR>          ..
2021/04/01  16:06    <DIR>          .idea
2021/04/01  16:42           131,072 db.sqlite3
2021/04/01  16:42    <DIR>          learning_log
2021/04/01  16:48    <DIR>          learning_logs
2021/04/01  16:04    <DIR>          ll_env
2021/04/01  15:59               690 manage.py
               2 個(gè)文件        131,762 字節(jié)
               6 個(gè)目錄 56,942,854,144 可用字節(jié)

(ll_env) E:\python\my_learning_log>dir learning_logs
 驅(qū)動(dòng)器 E 中的卷是 新加卷
 卷的序列號(hào)是 823D-B082

 E:\python\my_learning_log\learning_logs 的目錄

2021/04/01  16:48    <DIR>          .
2021/04/01  16:48    <DIR>          ..
2021/04/01  16:48                66 admin.py
2021/04/01  16:48               105 apps.py
2021/04/01  16:48    <DIR>          migrations
2021/04/01  16:48                60 models.py
2021/04/01  16:48                63 tests.py
2021/04/01  16:48                66 views.py
2021/04/01  16:48                 0 __init__.py
               6 個(gè)文件            360 字節(jié)
               3 個(gè)目錄 56,942,854,144 可用字節(jié)

(ll_env) E:\python\my_learning_log>

命令startapp appname 讓Django建立創(chuàng)建應(yīng)用程序所需的基礎(chǔ)設(shè)施。如果現(xiàn)在查看項(xiàng)目目錄,將看到其中新增了一個(gè)文件夾learning_logs。打開(kāi)這個(gè)文件夾,看看Django都創(chuàng)建了什么。其中最重要的文件是models.py、admin.py和views.py。我們將使用models.py來(lái)定義我們要在應(yīng)用程序中管理的數(shù)據(jù)。admin.py和views.py將在稍后介紹。

1.2.1 定義模型

我們來(lái)想想涉及的數(shù)據(jù)。每位用戶都需要在學(xué)習(xí)筆記中創(chuàng)建很多主題。用戶輸入的每個(gè)條目都與特定主題相關(guān)聯(lián),這些條目將以文本的方式顯示。我們還需要存儲(chǔ)每個(gè)條目的時(shí)間戳,以便能夠告訴用戶各個(gè)條目都是什么時(shí)候創(chuàng)建的。

打開(kāi)文件models.py,看看它當(dāng)前包含哪些內(nèi)容:
models.py

from django.db import models

# Create your models here.

這為我們導(dǎo)入了模塊models,還讓我們創(chuàng)建自己的模型。模型告訴Django如何處理應(yīng)用程序中存儲(chǔ)的數(shù)據(jù)。在代碼層面,模型就是一個(gè)類,就像前面討論的每個(gè)類一樣,包含屬性和方法。下面是表示用戶將要存儲(chǔ)的主題的模型:
models.py

from django.db import models

class Topic(models.Model):
    """ 用戶學(xué)習(xí)的主題 """
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        """ 返回模型的字符串表示 """
        return self.text

我們創(chuàng)建了一個(gè)名為Topic 的類,它繼承了Model ——Django中一個(gè)定義了模型基本功能的類。Topic 類只有兩個(gè)屬性:text 和date_added 。

屬性text是一個(gè)CharField——由字符或文本組成的數(shù)據(jù)。需要存儲(chǔ)少量的文本,如名稱、標(biāo)題或城市時(shí),可使用CharField 。定義CharField 屬性時(shí),必須告訴Django該在數(shù)據(jù)庫(kù)中預(yù)留多少空間。在這里,我們將max_length 設(shè)置成了200(即200個(gè)字符),這對(duì)存儲(chǔ)大多數(shù)主題名來(lái)說(shuō)足夠了。

屬性date_added 是一個(gè)DateTimeField ——記錄日期和時(shí)間的數(shù)據(jù)。我們傳遞了實(shí)參auto_add_now=True ,每當(dāng)用戶創(chuàng)建新主題時(shí),這都讓Django將這個(gè)屬性自動(dòng)設(shè)置成當(dāng)前日期和時(shí)間。

1.2.2 激活模型

要使用模型,必須讓Django將應(yīng)用程序包含到項(xiàng)目中。為此,打開(kāi)settings.py(它位于目錄my_learning_log/learning_log中),你將看到一個(gè)這樣的片段,即告訴Django哪些應(yīng)用程序安裝在項(xiàng)目中:

settings.py

--snip--
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
--snip--

這是一個(gè)元組,告訴Django項(xiàng)目是由哪些應(yīng)用程序組成的。請(qǐng)將INSTALLED_APPS 修改成下面這樣,將前面的應(yīng)用程序添加到這個(gè)元組中:

--snip--
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # 我的應(yīng)用程序
    'learning_logs'
]
--snip--

通過(guò)將應(yīng)用程序編組,在項(xiàng)目不斷增大,包含更多的應(yīng)用程序時(shí),有助于對(duì)應(yīng)用程序進(jìn)行跟蹤。這里新建了一個(gè)名為My apps的片段,當(dāng)前它只包含應(yīng)用程序learning_logs。

接下來(lái),需要讓Django修改數(shù)據(jù)庫(kù),使其能夠存儲(chǔ)與模型Topic 相關(guān)的信息。為此,在終端窗口中執(zhí)行下面的命令:

(ll_env) E:\python\my_learning_log>python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
  learning_logs\migrations\0001_initial.py
    - Create model Topic

(ll_env) E:\python\my_learning_log>

命令makemigrations 讓Django確定該如何修改數(shù)據(jù)庫(kù),使其能夠存儲(chǔ)與我們定義的新模型相關(guān)聯(lián)的數(shù)據(jù)。輸出表明Django創(chuàng)建了一個(gè)名為0001_initial.py的遷移文件,這個(gè)文件將在數(shù)據(jù)庫(kù)中為模型Topic 創(chuàng)建一個(gè)表。

下面來(lái)應(yīng)用這種遷移,讓Django替我們修改數(shù)據(jù)庫(kù):

(ll_env) E:\python\my_learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0001_initial... OK

(ll_env) E:\python\my_learning_log>

這個(gè)命令的大部分輸出都與我們首次執(zhí)行命令migrate的輸出相同。我們需要檢查輸出行,在這里,Django確認(rèn)為learning_logs 應(yīng)用遷移時(shí)一切正常(OK )。
每當(dāng)需要修改“學(xué)習(xí)筆記”管理的數(shù)據(jù)時(shí),都采取如下三個(gè)步驟:修改models.py;對(duì)learning_logs 調(diào)用makemigrations ;讓Django遷移項(xiàng)目。

1.2.3 Django管理網(wǎng)站

為應(yīng)用程序定義模型時(shí),Django提供的管理網(wǎng)站(admin site)讓你能夠輕松地處理模型。網(wǎng)站的管理員可使用管理網(wǎng)站,但普通用戶不能使用。在本節(jié)中,我們將建立管理網(wǎng)站,并通過(guò)它使用模型Topic 來(lái)添加一些主題。

1. 創(chuàng)建超級(jí)用戶
Django允許你創(chuàng)建具備所有權(quán)限的用戶——超級(jí)用戶。權(quán)限決定了用戶可執(zhí)行的操作。最嚴(yán)格的權(quán)限設(shè)置只允許用戶閱讀網(wǎng)站的公開(kāi)信息;注冊(cè)了的用戶通??砷喿x自己的私有數(shù)據(jù),還可查看一些只有會(huì)員才能查看的信息。為有效地管理Web應(yīng)用程序,網(wǎng)站所有者通常需要訪問(wèn)網(wǎng)站存儲(chǔ)的所有信息。優(yōu)秀的管理員會(huì)小心對(duì)待用戶的敏感信息,因?yàn)橛脩魧?duì)其訪問(wèn)的應(yīng)用程序有極大的信任。

為在Django中創(chuàng)建超級(jí)用戶,請(qǐng)執(zhí)行下面的命令并按提示做:

(ll_env) E:\python\my_learning_log>python manage.py createsuperuser
Username (leave blank to use 'administrator'): ll_admin
Email address:
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

(ll_env) E:\python\my_learning_log>

你執(zhí)行命令createsuperuser 時(shí),Django提示你輸入超級(jí)用戶的用戶名。這里我們輸入的是ll_admin,但你可以輸入任何用戶名,比如電子郵件地址,也可讓這個(gè)字段為空。你需要輸入密碼兩次。

2. 向管理網(wǎng)站注冊(cè)模型
Django自動(dòng)在管理網(wǎng)站中添加了一些模型,如User 和Group ,但對(duì)于我們創(chuàng)建的模型,必須手工進(jìn)行注冊(cè)。

我們創(chuàng)建應(yīng)用程序learning_logs 時(shí),Django在models.py所在的目錄中創(chuàng)建了一個(gè)名為admin.py的文件:
admin.py

from django.contrib import admin

# Register your models here.

為向管理網(wǎng)站注冊(cè)Topic ,請(qǐng)輸入下面的代碼:

from django.contrib import admin
from learning_logs.models import  Topic

admin.site.register(Topic)

這些代碼導(dǎo)入我們要注冊(cè)的模型Topic ,再使用admin.site.register() 讓Django通過(guò)管理網(wǎng)站管理我們的模型。

現(xiàn)在,使用超級(jí)用戶賬戶訪問(wèn)管理網(wǎng)站:訪問(wèn)http://localhost:8000/admin/ ,并輸入你剛創(chuàng)建的超級(jí)用戶的用戶名和密碼。這個(gè)網(wǎng)頁(yè)讓你能夠添加和修改用戶和用戶組,還可以管理與剛才定義的模型Topic 相關(guān)的數(shù)據(jù)。

image.png
image.png

3. 添加主題
向管理網(wǎng)站注冊(cè)Topic 后,我們來(lái)添加第一個(gè)主題。為此,單擊Topics進(jìn)入主題網(wǎng)頁(yè),它幾乎是空的,這是因?yàn)槲覀冞€沒(méi)有添加任何主題。單擊Add,你將看到一個(gè)用于添加新主題的表單。在第一個(gè)方框中輸入Chess ,再單擊Save,這將返回到主題管理頁(yè)面,其中包含剛創(chuàng)建的主題

下面再創(chuàng)建一個(gè)主題,以便有更多的數(shù)據(jù)可供使用。再次單擊Add,并創(chuàng)建另一個(gè)主題Rock Climbing 。當(dāng)你單擊Save時(shí),將重新回到主題管理頁(yè)面,其中包含主題Chess和Rock Climbing。


image.png

1.2.4 定義模型Entry

要記錄學(xué)到的國(guó)際象棋和攀巖知識(shí),需要為用戶可在學(xué)習(xí)筆記中添加的條目定義模型。每個(gè)條目都與特定主題相關(guān)聯(lián),這種關(guān)系被稱為多對(duì)一關(guān)系,即多個(gè)條目可關(guān)聯(lián)到同一個(gè)
主題。
下面是模型Entry 的代碼:
models.py

from django.db import models

class Topic(models.Model):
    """ 用戶學(xué)習(xí)的主題 """
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        """ 返回模型的字符串表示 """
        return self.text


class Entry(models.Model):
    """ 學(xué)到的有關(guān)某個(gè)主題的具體知識(shí) """
    topic = models.ForeignKey(Topic, on_delete=models.DO_NOTHING)
    text = models.TextField()
    date_added = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = 'entries'

    def __str__(self):
        """ 返回模型的字符串表示 """
        return self.text[:50] + "..."

像Topic 一樣,Entry 也繼承了Django基類Model 。第一個(gè)屬性topic 是一個(gè)ForeignKey 實(shí)例。外鍵是一個(gè)數(shù)據(jù)庫(kù)術(shù)語(yǔ),它引用了數(shù)據(jù)庫(kù)中的另一條記錄;這些代碼將每個(gè)條目關(guān)聯(lián)到特定的主題。每個(gè)主題創(chuàng)建時(shí),都給它分配了一個(gè)鍵(或ID)。需要在兩項(xiàng)數(shù)據(jù)之間建立聯(lián)系時(shí),Django使用與每項(xiàng)信息相關(guān)聯(lián)的鍵。稍后我們將根據(jù)這些聯(lián)系獲取與特定主題相關(guān)聯(lián)的所有條目。

接下來(lái)是屬性text ,它是一個(gè)TextField 實(shí)例。這種字段不需要長(zhǎng)度限制,因?yàn)槲覀儾幌胂拗茥l目的長(zhǎng)度。屬性date_added 讓我們能夠按創(chuàng)建順序呈現(xiàn)條目,并在每個(gè)條目旁邊放置時(shí)間戳。

我們?cè)贓ntry 類中嵌套了Meta 類。Meta 存儲(chǔ)用于管理模型的額外信息,在這里,它讓我們能夠設(shè)置一個(gè)特殊屬性,讓Django在需要時(shí)使用Entries 來(lái)表示多個(gè)條目。如果沒(méi)有這個(gè)類, Django將使用Entrys來(lái)表示多個(gè)條目。最后,方法str() 告訴Django,呈現(xiàn)條目時(shí)應(yīng)顯示哪些信息。由于條目包含的文本可能很長(zhǎng),我們讓Django只顯示text 的前50個(gè)字符。我們還添加了一個(gè)省略號(hào),指出顯示的并非整個(gè)條目。

1.2.5 遷移模型Entry

由于我們添加了一個(gè)新模型,因此需要再次遷移數(shù)據(jù)庫(kù)。你將慢慢地對(duì)這個(gè)過(guò)程了如指掌:修改models.py,執(zhí)行命令python manage.py makemigrations app_name ,
再執(zhí)行命令python manage.py migrate 。
下面來(lái)遷移數(shù)據(jù)庫(kù)并查看輸出:

(ll_env) E:\python\my_learning_log>python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
  learning_logs\migrations\0002_entry.py
    - Create model Entry

(ll_env) E:\python\my_learning_log>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0002_entry... OK

(ll_env) E:\python\my_learning_log>

生成了一個(gè)新的遷移文件——0002_entry.py,它告訴Django如何修改數(shù)據(jù)庫(kù),使其能夠存儲(chǔ)與模型Entry 相關(guān)的信息。執(zhí)行命令migrate ,我們發(fā)現(xiàn)Django應(yīng)用了這種遷移且一切順利。

1.2.6 向管理網(wǎng)站注冊(cè)Entry

我們還需要注冊(cè)模型Entry 。為此,需要將admin.py修改成類似于下面這樣:
admin.py

from django.contrib import admin
from learning_logs.models import  Topic, Entry

admin.site.register(Topic)
admin.site.register(Entry)

返回到http://localhost/admin/ ,你將看到learning_logs下列出了Entries。單擊Entries的Add鏈接,或者單擊Entries再選擇Add entry。你將看到一個(gè)下拉列表,讓你能夠選擇要為哪個(gè)主題創(chuàng)建條目,還有一個(gè)用于輸入條目的文本框。從下拉列表中選擇Chess,并添加一個(gè)條目。下面是我添加的第一個(gè)條目。

The opening is the first part of the game, roughly the first ten moves or so. In the opening, it's a good idea to do three things— bring out your bishops and knights, try to control the center of the board, and castle your king.(國(guó)際象棋的第一個(gè)階段是開(kāi)局,大致是前10步左右。在開(kāi)局階段,最好做三件事情:將象和馬調(diào)出來(lái);努力控制棋盤的中間區(qū)域;用車將王護(hù)住。)

Of course, these are just guidelines. It will be important to learn when to follow these guidelines and when to disregard these suggestions.(當(dāng)然,這些只是指導(dǎo)原則。學(xué)習(xí)什么情況下遵守這些原則、什么情況下不用遵守很重要。)

當(dāng)你單擊Save時(shí),將返回到主條目管理頁(yè)面。在這里,你將發(fā)現(xiàn)使用text[:50] 作為條目的字符串表示的好處:管理界面中,只顯示了條目的開(kāi)頭部分而不是其所有文本,這使得管理多個(gè)條目容易得多。

再來(lái)創(chuàng)建一個(gè)國(guó)際象棋條目,并創(chuàng)建一個(gè)攀巖條目,以提供一些初始數(shù)據(jù)。下面是第二個(gè)國(guó)際象棋條目。

In the opening phase of the game, it's important to bring out your bishops and knights. These pieces are powerful and maneuverable enough to play a significant role in the beginning moves of a
game.(在國(guó)際象棋的開(kāi)局階段,將象和馬調(diào)出來(lái)很重要。這些棋子威力大,機(jī)動(dòng)性強(qiáng),在開(kāi)局階段扮演著重要角色。)

下面是第一個(gè)攀巖條目:

One of the most important concepts in climbing is to keep your weight on your feet as much as possible. There's a myth that climbers can hang all day on their arms. In reality, good climbers have
practiced specific ways of keeping their weight over their feet whenever possible.(最重要的攀巖概念之一是盡可能讓雙腳承受體重。有謬誤認(rèn)為攀巖者能依靠手臂的力量堅(jiān)持一整天。實(shí)際上,優(yōu)秀的攀巖者都經(jīng)過(guò)專門訓(xùn)練,能夠盡可能讓雙腳承受體重。)

繼續(xù)往下開(kāi)發(fā)“學(xué)習(xí)筆記”時(shí),這三個(gè)條目可為我們提供使用的數(shù)據(jù)。


image.png

1.2.7 Django shell

輸入一些數(shù)據(jù)后,就可通過(guò)交互式終端會(huì)話以編程方式查看這些數(shù)據(jù)了。這種交互式環(huán)境稱為Django shell,是測(cè)試項(xiàng)目和排除其故障的理想之地。下面是一個(gè)交互式shell會(huì)話示例:

(ll_env) E:\python\my_learning_log>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>> from learning_logs.models import Topic
>>> Topic.objects.all()
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>
>>>

在活動(dòng)的虛擬環(huán)境中執(zhí)行時(shí),命令python manage.py shell 啟動(dòng)一個(gè)Python解釋器,可使用它來(lái)探索存儲(chǔ)在項(xiàng)目數(shù)據(jù)庫(kù)中的數(shù)據(jù)。在這里,我們導(dǎo)入了模塊learning_logs.models 中的模型Topic ,然后使用方法Topic.objects.all() 來(lái)獲取模型Topic 的所有實(shí)例;它返回的是一個(gè)列表,稱為查詢集(queryset)。

我們可以像遍歷列表一樣遍歷查詢集。下面演示了如何查看分配給每個(gè)主題對(duì)象的ID:

>>> topics = Topic.objects.all()
>>> for topic in topics:
...     print(topic.id, topic)
...
1 Chess
2 Rock Climbing
>>>
>>>

我們將返回的查詢集存儲(chǔ)在topics 中,然后打印每個(gè)主題的id 屬性和字符串表示。從輸出可知,主題Chess的ID為1,而Rock Climbing的ID為2。

知道對(duì)象的ID后,就可獲取該對(duì)象并查看其任何屬性。下面來(lái)看看主題Chess的屬性text 和date_added 的值:

>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2021, 4, 1, 9, 21, 41, 378000, tzinfo=<UTC>)
>>>

我們還可以查看與主題相關(guān)聯(lián)的條目。前面我們給模型Entry 定義了屬性topic ,這是一個(gè)ForeignKey ,將條目與主題關(guān)聯(lián)起來(lái)。利用這種關(guān)聯(lián),Django能夠獲取與特定主題相關(guān)聯(lián)的所有條目,如下所示:

>>> 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...>]>
>>>

為通過(guò)外鍵關(guān)系獲取數(shù)據(jù),可使用相關(guān)模型的小寫名稱、下劃線和單詞set。例如,假設(shè)你有模型Pizza 和Topping ,而Topping通過(guò)一個(gè)外鍵關(guān)聯(lián)到Pizza ;如果你有一個(gè)名為my_pizza 的對(duì)象,表示一張比薩,就可使用代碼my_pizza.topping_set.all() 來(lái)獲取這張比薩的所有配料。

編寫用戶可請(qǐng)求的網(wǎng)頁(yè)時(shí),我們將使用這種語(yǔ)法。確認(rèn)代碼能獲取所需的數(shù)據(jù)時(shí),shell很有幫助。如果代碼在shell中的行為符合預(yù)期,那么它們?cè)陧?xiàng)目文件中也能正確地工作。如果代碼引發(fā)了錯(cuò)誤或獲取的數(shù)據(jù)不符合預(yù)期,那么在簡(jiǎn)單的shell環(huán)境中排除故障要比在生成網(wǎng)頁(yè)的文件中排除故障容易得多。我們不會(huì)太多地使用shell,但應(yīng)繼續(xù)使用它來(lái)熟悉對(duì)存儲(chǔ)在項(xiàng)目中的數(shù)據(jù)進(jìn)行訪問(wèn)的Django語(yǔ)法。

1.3 創(chuàng)建網(wǎng)頁(yè):學(xué)習(xí)筆記主頁(yè)

使用Django創(chuàng)建網(wǎng)頁(yè)的過(guò)程通常分三個(gè)階段:定義URL、編寫視圖和編寫模板。首先,你必須定義URL模式。URL模式描述了URL是如何設(shè)計(jì)的,讓Django知道如何將瀏覽器請(qǐng)求與網(wǎng)站URL匹配,以確定返回哪個(gè)網(wǎng)頁(yè)。

每個(gè)URL都被映射到特定的視圖 ——視圖函數(shù)獲取并處理網(wǎng)頁(yè)所需的數(shù)據(jù)。視圖函數(shù)通常調(diào)用一個(gè)模板,后者生成瀏覽器能夠理解的網(wǎng)頁(yè)。為明白其中的工作原理,我們來(lái)創(chuàng)建學(xué)習(xí)筆記的主頁(yè)。我們將定義該主頁(yè)的URL、編寫其視圖函數(shù)并創(chuàng)建一個(gè)簡(jiǎn)單的模板。

鑒于我們只是要確?!皩W(xué)習(xí)筆記”按要求的那樣工作,我們將暫時(shí)讓這個(gè)網(wǎng)頁(yè)盡可能簡(jiǎn)單。Web應(yīng)用程序能夠正常運(yùn)行后,設(shè)置樣式可使其更有趣,但中看不中用的應(yīng)用程序毫無(wú)意義。就目前而言,主頁(yè)只顯示標(biāo)題和簡(jiǎn)單的描述。

1.3.1 映射URL

用戶通過(guò)在瀏覽器中輸入U(xiǎn)RL以及單擊鏈接來(lái)請(qǐng)求網(wǎng)頁(yè),因此我們需要確定項(xiàng)目需要哪些URL。主頁(yè)的URL最重要,它是用戶用來(lái)訪問(wèn)項(xiàng)目的基礎(chǔ)URL。當(dāng)前,基礎(chǔ)URL(http://localhost:8000/)返回默認(rèn)的Django網(wǎng)站,讓我們知道正確地建立了項(xiàng)目。我們將修改這一點(diǎn),將這個(gè)基礎(chǔ)URL映射到“學(xué)習(xí)筆記”的主頁(yè)。

打開(kāi)項(xiàng)目主文件夾learning_log中的文件urls.py,你將看到如下代碼:
urls.py

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

前兩行導(dǎo)入了為項(xiàng)目和管理網(wǎng)站管理URL的函數(shù)和模塊。這個(gè)文件的主體定義了變量urlpatterns 。在這個(gè)針對(duì)整個(gè)項(xiàng)目的urls.py文件中,變量urlpatterns 包含項(xiàng)目中的應(yīng)用程序的URL。代碼包含模塊admin.site.urls ,該模塊定義了可在管理網(wǎng)站中請(qǐng)求的所有URL。

我們需要包含learning_logs的URL:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include(('learning_logs.urls', "learning_logs"), namespace="learning_logs")),
]

我們添加了一行代碼來(lái)包含模塊learning_logs.urls 。這行代碼包含實(shí)參namespace ,讓我們能夠?qū)earning_logs 的URL同項(xiàng)目中的其他URL區(qū)分開(kāi)來(lái),這在項(xiàng)目開(kāi)始擴(kuò)展時(shí)很有幫助。
默認(rèn)的urls.py包含在文件夾learning_log中,現(xiàn)在我們需要在文件夾learning_logs中創(chuàng)建另一個(gè)urls.py文件:

""" 定義learning_logs的URL模式 """

from django.conf.urls import url
from . import views

urlpatterns = [
    # 主頁(yè)
    url(r'^$', views.index, name='index'),
]

為弄清楚當(dāng)前位于哪個(gè)urls.py文件中,我們?cè)谶@個(gè)文件開(kāi)頭添加了一個(gè)文檔字符串。接下來(lái),我們導(dǎo)入了函數(shù)url ,因?yàn)槲覀冃枰褂盟鼇?lái)將URL映射到視圖。

我們還導(dǎo)入了模塊views ,其中的句點(diǎn)讓Python從當(dāng)前的urls.py模塊所在的文件夾中導(dǎo)入視圖。在這個(gè)模塊中,變量urlpatterns 是一個(gè)列表,包含可在應(yīng)用程序learning_logs 中請(qǐng)求的網(wǎng)頁(yè)。

實(shí)際的URL模式是一個(gè)對(duì)函數(shù)url() 的調(diào)用,這個(gè)函數(shù)接受三個(gè)實(shí)參。第一個(gè)是一個(gè)正則表達(dá)式。Django在urlpatterns 中查找與請(qǐng)求的URL字符串匹配的正則表達(dá)式,因此正則表達(dá)式定義了Django可查找的模式。

我們來(lái)看看正則表達(dá)式r'^$' 。其中的r 讓Python將接下來(lái)的字符串視為原始字符串,而引號(hào)告訴Python正則表達(dá)式始于和終于何處。脫字符(^ )讓Python查看字符串的開(kāi)頭,而美元符號(hào)讓Python查看字符串的末尾??傮w而言,這個(gè)正則表達(dá)式讓Python查找開(kāi)頭和末尾之間沒(méi)有任何東西的URL。Python忽略項(xiàng)目的基礎(chǔ)URL(http://localhost:8000/),因此這個(gè)正則表達(dá)式與基礎(chǔ)URL匹配。其他URL都與這個(gè)正則表達(dá)式不匹配。如果請(qǐng)求的URL不與任何URL模式匹配,Django將返回一個(gè)錯(cuò)誤頁(yè)面。

url() 的第二個(gè)實(shí)參指定了要調(diào)用的視圖函數(shù)。請(qǐng)求的URL與前述正則表達(dá)式匹配時(shí),Django將調(diào)用views.index (這個(gè)視圖函數(shù)將在下一節(jié)編寫)。第三個(gè)實(shí)參將這個(gè)URL模式的名稱指定為index,讓我們能夠在代碼的其他地方引用它。每當(dāng)需要提供到這個(gè)主頁(yè)的鏈接時(shí),我們都將使用這個(gè)名稱,而不編寫URL。

1.3.2 編寫視圖

視圖函數(shù)接受請(qǐng)求中的信息,準(zhǔn)備好生成網(wǎng)頁(yè)所需的數(shù)據(jù),再將這些數(shù)據(jù)發(fā)送給瀏覽器——這通常是使用定義了網(wǎng)頁(yè)是什么樣的模板實(shí)現(xiàn)的。

learning_logs中的文件views.py是你執(zhí)行命令python manage.py startapp 時(shí)自動(dòng)生成的,當(dāng)前其內(nèi)容如下:
views.py

from django.shortcuts import render

# 在這里創(chuàng)建視圖

當(dāng)前,這個(gè)文件只導(dǎo)入了函數(shù)render() ,它根據(jù)視圖提供的數(shù)據(jù)渲染響應(yīng)。下面的代碼演示了該如何為主頁(yè)編寫視圖:

from django.shortcuts import render

def index(request):
    """ 學(xué)習(xí)筆記的主頁(yè) """
    return render(request, 'learning_logs/index.html')

URL請(qǐng)求與我們剛才定義的模式匹配時(shí),Django將在文件views.py中查找函數(shù)index() ,再將請(qǐng)求對(duì)象傳遞給這個(gè)視圖函數(shù)。在這里,我們不需要處理任何數(shù)據(jù),因此這個(gè)函數(shù)只包含調(diào)用render() 的代碼。這里向函數(shù)render() 提供了兩個(gè)實(shí)參:原始請(qǐng)求對(duì)象以及一個(gè)可用于創(chuàng)建網(wǎng)頁(yè)的模板。下面來(lái)編寫這個(gè)模板。

1.3.3 編寫模板

模板定義了網(wǎng)頁(yè)的結(jié)構(gòu)。模板指定了網(wǎng)頁(yè)是什么樣的,而每當(dāng)網(wǎng)頁(yè)被請(qǐng)求時(shí),Django將填入相關(guān)的數(shù)據(jù)。模板讓你能夠訪問(wèn)視圖提供的任何數(shù)據(jù)。我們的主頁(yè)視圖沒(méi)有提供任何數(shù)據(jù),因此相應(yīng)的模板非常簡(jiǎn)單。

在文件夾learning_logs中新建一個(gè)文件夾,并將其命名為templates。在文件夾templates中,再新建一個(gè)文件夾,并將其命名為learning_logs。這好像有點(diǎn)多余(我們?cè)谖募Alearning_logs中創(chuàng)建了文件夾templates,又在這個(gè)文件夾中創(chuàng)建了文件夾learning_logs),但建立了Django能夠明確解讀的結(jié)構(gòu),即便項(xiàng)目很大,包含很多應(yīng)用程序亦如此。在最里面的文件夾learning_logs中,新建一個(gè)文件,并將其命名為index.html,再在這個(gè)文件中編寫如下代碼:
index.html

<p>Learing Log</p>

<p>Learning Log helps you keep track of your learning, for any topic you're
    learning about.</p>

這個(gè)文件非常簡(jiǎn)單。對(duì)于不熟悉HTML的讀者,這里解釋一下:標(biāo)簽<p></p> 標(biāo)識(shí)段落;標(biāo)簽<p> 指出了段落的開(kāi)頭位置,而標(biāo)簽</p> 指出了段落的結(jié)束位置。這里定義了兩個(gè)段落:第一個(gè)充當(dāng)標(biāo)題,第二個(gè)闡述了用戶可使用“學(xué)習(xí)筆記”來(lái)做什么。

現(xiàn)在,如果你請(qǐng)求這個(gè)項(xiàng)目的基礎(chǔ)URL——http://localhost:8000/,將看到剛才創(chuàng)建的網(wǎng)頁(yè),而不是默認(rèn)的Django網(wǎng)頁(yè)。Django接受請(qǐng)求的URL,發(fā)現(xiàn)該URL與模式r'^$' 匹配,因此調(diào)用函數(shù)views.index() ,這將使用index.html包含的模板來(lái)渲染網(wǎng)頁(yè),結(jié)果如下圖所示:

image.png

創(chuàng)建網(wǎng)頁(yè)的過(guò)程看起來(lái)可能很復(fù)雜,但將URL、視圖和模板分離的效果實(shí)際上很好。這讓我們能夠分別考慮項(xiàng)目的不同方面,且在項(xiàng)目很大時(shí),讓各個(gè)參與者可專注于其最擅長(zhǎng)的方面。例如,數(shù)據(jù)庫(kù)專家可專注于模型,程序員可專注于視圖代碼,而Web設(shè)計(jì)人員可專注于模板。

1.4 創(chuàng)建其他網(wǎng)頁(yè)

制定創(chuàng)建網(wǎng)頁(yè)的流程后,可以開(kāi)始擴(kuò)充“學(xué)習(xí)筆記”項(xiàng)目了。我們將創(chuàng)建兩個(gè)顯示數(shù)據(jù)的網(wǎng)頁(yè),其中一個(gè)列出所有的主題,另一個(gè)顯示特定主題的所有條目。對(duì)于每個(gè)網(wǎng)頁(yè),我們都將指定URL模式,編寫一個(gè)視圖函數(shù),并編寫一個(gè)模板。但這樣做之前,我們先創(chuàng)建一個(gè)父模板,項(xiàng)目中的其他模板都將繼承它。

1.4.1 模板繼承

創(chuàng)建網(wǎng)站時(shí),幾乎都有一些所有網(wǎng)頁(yè)都將包含的元素。在這種情況下,可編寫一個(gè)包含通用元素的父模板,并讓每個(gè)網(wǎng)頁(yè)都繼承這個(gè)模板,而不必在每個(gè)網(wǎng)頁(yè)中重復(fù)定義這些通用元素。這種方法能讓你專注于開(kāi)發(fā)每個(gè)網(wǎng)頁(yè)的獨(dú)特方面,還能讓修改項(xiàng)目的整體外觀容易得多。
1. 父模板
我們首先來(lái)創(chuàng)建一個(gè)名為base.html的模板,并將其存儲(chǔ)在index.html所在的目錄中。這個(gè)文件包含所有頁(yè)面都有的元素;其他的模板都繼承base.html。當(dāng)前,所有頁(yè)面都包含的元素只有頂端的標(biāo)題。我們將在每個(gè)頁(yè)面中包含這個(gè)模板,因此我們將這個(gè)標(biāo)題設(shè)置為到主頁(yè)的鏈接:
base.html

<p>
    <a href="{% url 'learning_logs:index' %}">Learning Log </a>
</p>

{%  block content %}{% endblock content %}

這個(gè)文件的第一部分創(chuàng)建一個(gè)包含項(xiàng)目名的段落,該段落也是一個(gè)到主頁(yè)的鏈接。為創(chuàng)建鏈接,我們使用了一個(gè)模板標(biāo)簽 ,它是用大括號(hào)和百分號(hào)({% %} )表示的。模板標(biāo)簽是一小段代碼,生成要在網(wǎng)頁(yè)中顯示的信息。在這個(gè)實(shí)例中,模板標(biāo)簽{% url 'learning_logs:index' %} 生成一個(gè)URL,該URL與learning_logs/urls.py中定義的名為index 的URL模式匹配。在這個(gè)示例中,learning_logs 是一個(gè)命名空間 ,而index 是該命名空間中一個(gè)名稱獨(dú)特的URL模式。

在簡(jiǎn)單的HTML頁(yè)面中,鏈接是使用錨 標(biāo)簽定義的:

<a href="link_url">link text</a>

讓模板標(biāo)簽來(lái)生成URL,可讓鏈接保持最新容易得多。要修改項(xiàng)目中的URL,只需修改urls.py中的URL模式,這樣網(wǎng)頁(yè)被請(qǐng)求時(shí),Django將自動(dòng)插入修改后的URL。在我們的項(xiàng)目中,每個(gè)網(wǎng)頁(yè)都將繼承base.html,因此從現(xiàn)在開(kāi)始,每個(gè)網(wǎng)頁(yè)都包含到主頁(yè)的鏈接。

我們插入了一對(duì)塊標(biāo)簽。這個(gè)塊名為content ,是一個(gè)占位符,其中包含的信息將由子模板指定。
子模板并非必須定義父模板中的每個(gè)塊,因此在父模板中,可使用任意多個(gè)塊來(lái)預(yù)留空間,而子模板可根據(jù)需要定義相應(yīng)數(shù)量的塊。

2. 子模板
現(xiàn)在需要重新編寫index.html,使其繼承base.html,如下所示:
index.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 %}

如果將這些代碼與原來(lái)的index.html進(jìn)行比較,可發(fā)現(xiàn)我們將標(biāo)題Learning Log替換成了從父模板那里繼承的代碼。子模板的第一行必須包含標(biāo)簽{% extends %} ,讓Django知道它繼承了哪個(gè)父模板。文件base.html位于文件夾learning_logs中,因此父模板路徑中包含learning_logs。這行代碼導(dǎo)入模板base.html的所有內(nèi)容,讓index.html能夠指定要在content 塊預(yù)留的空間中添加的內(nèi)容。

我們插入了一個(gè)名為content 的{% block %} 標(biāo)簽,以定義content 塊。不是從父模板繼承的內(nèi)容都包含在content 塊中,在這里是一個(gè)描述項(xiàng)目“學(xué)習(xí)筆記”的段落。我們使用標(biāo)簽{% endblock content %} 指出了內(nèi)容定義的結(jié)束位置。模板繼承的優(yōu)點(diǎn)開(kāi)始顯現(xiàn)出來(lái)了:在子模板中,只需包含當(dāng)前網(wǎng)頁(yè)特有的內(nèi)容。這不僅簡(jiǎn)化了每個(gè)模板,還使得網(wǎng)站修改起來(lái)容易得多。要修改很多網(wǎng)頁(yè)都包含的元素,只需在父模板中修改該元素,你所做的修改將傳導(dǎo)到繼承該父模板的每個(gè)頁(yè)面。在包含數(shù)十乃至數(shù)百個(gè)網(wǎng)頁(yè)的項(xiàng)目中,這種結(jié)構(gòu)使得網(wǎng)站改進(jìn)起來(lái)容易而且快捷得多。

1.4.2 顯示所有主題的頁(yè)面

有了高效的網(wǎng)頁(yè)創(chuàng)建方法,就能專注于另外兩個(gè)網(wǎng)頁(yè)了:顯示全部主題的網(wǎng)頁(yè)以及顯示特定主題中條目的網(wǎng)頁(yè)。所有主題頁(yè)面顯示用戶創(chuàng)建的所有主題,它是第一個(gè)需要使用數(shù)據(jù)的網(wǎng)頁(yè)。

1. URL模式
首先,我們來(lái)定義顯示所有主題的頁(yè)面的URL。通常,使用一個(gè)簡(jiǎn)單的URL片段來(lái)指出網(wǎng)頁(yè)顯示的信息;我們將使用單詞topics,因此URL http://localhost:8000/topics/將返回顯示所有主題的頁(yè)面。下面演示了該如何修改learning_logs/urls.py
urls.py

""" 定義learning_logs的URL模式 """

from django.conf.urls import url
from . import views

urlpatterns = [
    # 主頁(yè)
    url(r'^$', views.index, name='index'),

    # 顯示所有的主題
    url(r'^topics/$', views.topics, name='topics'),
]

我們只是在用于主頁(yè)URL的正則表達(dá)式中添加了topics/。Django檢查請(qǐng)求的URL時(shí),這個(gè)模式與這樣的URL匹配:基礎(chǔ)URL后面跟著topics ??梢栽谀┪舶备?,也可以省略它,但單詞topics 后面不能有任何東西,否則就與該模式不匹配。其URL與該模式匹配的請(qǐng)求都將交給views.py中的函數(shù)topics() 進(jìn)行處理。

2. 視圖
函數(shù)topics() 需要從數(shù)據(jù)庫(kù)中獲取一些數(shù)據(jù),并將其發(fā)送給模板。我們需要在views.py中添加的代碼如下:
views.py

from django.shortcuts import render
from .models import Topic

def index(request):
    """ 學(xué)習(xí)筆記的主頁(yè) """
    return render(request, 'learning_logs/index.html')

def topics(request):
    """ 顯示所有主題 """
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

我們首先導(dǎo)入了與所需數(shù)據(jù)相關(guān)聯(lián)的模型。函數(shù)topics() 包含一個(gè)形參:Django從服務(wù)器那里收到的request 對(duì)象。我們查詢數(shù)據(jù)庫(kù)——請(qǐng)求提供Topic 對(duì)象,并按屬性date_added 對(duì)它們進(jìn)行排序。我們將返回的查詢集存儲(chǔ)在topics 中。

我們定義了一個(gè)將要發(fā)送給模板的上下文。上下文是一個(gè)字典,其中的鍵是我們將在模板中用來(lái)訪問(wèn)數(shù)據(jù)的名稱,而值是我們要發(fā)送給模板的數(shù)據(jù)。在這里,只有一個(gè)鍵—值對(duì),它包含我們將在網(wǎng)頁(yè)中顯示的一組主題。創(chuàng)建使用數(shù)據(jù)的網(wǎng)頁(yè)時(shí),除對(duì)象request 和模板的路徑外,我們還將變量context 傳遞給render() 。

3. 模板
顯示所有主題的頁(yè)面的模板接受字典context ,以便能夠使用topics() 提供的數(shù)據(jù)。請(qǐng)創(chuàng)建一個(gè)文件,將其命名為topics.html,并存儲(chǔ)到index.html所在的目錄中。下面演示了如何在這個(gè)模板中顯示主題:
topics.html

{% 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 yes.</li>
    {% endfor %}
</ul>
{% endblock content %}

就像模板index.html一樣,我們首先使用標(biāo)簽{% extends %} 來(lái)繼承base.html,再開(kāi)始定義content 塊。這個(gè)網(wǎng)頁(yè)的主體是一個(gè)項(xiàng)目列表,其中列出了用戶輸入的主題。在標(biāo)準(zhǔn)HTML中,項(xiàng)目列表被稱為無(wú)序列表,用標(biāo)簽<ul></ul> 表示。我們使用了一個(gè)相當(dāng)于for 循環(huán)的模板標(biāo)簽,它遍歷字典context 中的列表topics 。模板中使用的代碼與Python代碼存在一些重要差別:Python使用縮進(jìn)來(lái)指出哪些代碼行是for 循環(huán)的組成部分,而在模板中,每個(gè)for 循環(huán)都必須使用{% endfor %} 標(biāo)簽來(lái)顯式地指出其結(jié)束位置。因此在模板中,循環(huán)類似于下面這樣:

{% for item in list %}
    do something with each item
{% endfor %}

在循環(huán)中,我們要將每個(gè)主題轉(zhuǎn)換為一個(gè)項(xiàng)目列表項(xiàng)。要在模板中打印變量,需要將變量名用雙花括號(hào)括起來(lái)。每次循環(huán)時(shí),代碼{{ topic }} 都被替換為topic 的當(dāng)前值。這些花括號(hào)不會(huì)出現(xiàn)在網(wǎng)頁(yè)中,它們只是用于告訴Django我們使用了一個(gè)模板變量。HTML標(biāo)簽<li></li> 表示一個(gè)項(xiàng)目列表項(xiàng),在標(biāo)簽對(duì)<ul></ul> 內(nèi)部,位于標(biāo)簽<li> 和</li> 之間的內(nèi)容都是一個(gè)項(xiàng)目列表項(xiàng)。

我們使用了模板標(biāo)簽{% empty %} ,它告訴Django在列表topics 為空時(shí)該怎么辦:這里是打印一條消息,告訴用戶還沒(méi)有添加任何主題。最后兩行分別結(jié)束for 循環(huán)和項(xiàng)目列表。

現(xiàn)在需要修改父模板,使其包含到顯示所有主題的頁(yè)面的鏈接:
base.html

<p>
    <a href="{% url 'learning_logs:index' %}">Learning Log </a> - 
    <a href="{% url 'learning_logs:topics' %}">Topics </a>
</p>

{%  block content %}{% endblock content %}

我們?cè)诘街黜?yè)的鏈接后面添加了一個(gè)連字符,然后添加了一個(gè)到顯示所有主題的頁(yè)面的鏈接——使用的也是模板標(biāo)簽url 。這一行讓Django生成一個(gè)鏈接,它與learning_logs/ urls.py中名為topics 的URL模式匹配。

現(xiàn)在如果你刷新瀏覽器中的主頁(yè),將看到鏈接Topics。單擊這個(gè)鏈接,將看到類似于下圖所示.


image.png
image.png

1.4.3 顯示特定主題的頁(yè)面

接下來(lái),我們需要?jiǎng)?chuàng)建一個(gè)專注于特定主題的頁(yè)面——顯示該主題的名稱及該主題的所有條目。同樣,我們將定義一個(gè)新的URL模式,編寫一個(gè)視圖并創(chuàng)建一個(gè)模板。我們還將修改顯示所有主題的網(wǎng)頁(yè),讓每個(gè)項(xiàng)目列表項(xiàng)都是一個(gè)鏈接,單擊它將顯示相應(yīng)主題的所有條目。

1. URL模式
顯示特定主題的頁(yè)面的URL模式與前面的所有URL模式都稍有不同,因?yàn)樗鼘⑹褂弥黝}的id 屬性來(lái)指出請(qǐng)求的是哪個(gè)主題。例如,如果用戶要查看主題Chess(其id 為1)的詳細(xì)頁(yè)面,URL將為http://localhost:8000/topics/1/。下面是與這個(gè)URL匹配的模式,它包含在learning_logs/urls.py中:
urls.py

""" 定義learning_logs的URL模式 """

from django.conf.urls import url
from . import views

urlpatterns = [
    # 主頁(yè)
    url(r'^$', views.index, name='index'),

    # 顯示所有的主題
    url(r'^topics/$', views.topics, name='topics'),

    # 特定主題的詳細(xì)頁(yè)面
    url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic')
]

我們來(lái)詳細(xì)研究這個(gè)URL模式中的正則表達(dá)式——r'^topics/(?P<topic_id>\d+)/$' 。r 讓Django將這個(gè)字符串視為原始字符串,并指出正則表達(dá)式包含在引號(hào)內(nèi)。這個(gè)表達(dá)式的第二部分(/(?P<topic_id>\d+)/ )與包含在兩個(gè)斜杠內(nèi)的整數(shù)匹配,并將這個(gè)整數(shù)存儲(chǔ)在一個(gè)名為topic_id 的實(shí)參中。這部分表達(dá)式兩邊的括號(hào)捕獲URL中的值;?P<topic_id> 將匹配的值存儲(chǔ)到topic_id 中;而表達(dá)式\d+ 與包含在兩個(gè)斜桿內(nèi)的任何數(shù)字都匹配,不管這個(gè)數(shù)字為多少位。

發(fā)現(xiàn)URL與這個(gè)模式匹配時(shí),Django將調(diào)用視圖函數(shù)topic() ,并將存儲(chǔ)在topic_id 中的值作為實(shí)參傳遞給它。在這個(gè)函數(shù)中,我們將使用topic_id 的值來(lái)獲取相應(yīng)的主題。

2. 視圖
函數(shù)topic() 需要從數(shù)據(jù)庫(kù)中獲取指定的主題以及與之相關(guān)聯(lián)的所有條目,如下所示:
views.py

from django.shortcuts import render
from .models import Topic

def index(request):
    """ 學(xué)習(xí)筆記的主頁(yè) """
    return render(request, 'learning_logs/index.html')

def topics(request):
    """ 顯示所有主題 """
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

def topic(request, topic_id):
    """顯示單個(gè)主題及其所有的條目"""
    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)

這是第一個(gè)除request 對(duì)象外還包含另一個(gè)形參的視圖函數(shù)。這個(gè)函數(shù)接受正則表達(dá)式(?P<topic_id>\d+) 捕獲的值,并將其存儲(chǔ)到topic_id 中。我們使用get() 來(lái)獲取指定的主題,就像前面在Django shell中所做的那樣。我們獲取與該主題相關(guān)聯(lián)的條目,并將它們按date_added 排序:date_added 前面的減號(hào)指定按降序排列,即先顯示最近的條目。我們將主題和條目都存儲(chǔ)在字典context 中,再將這個(gè)字典發(fā)送給模板topic.html。

3. 模板
這個(gè)模板需要顯示主題的名稱和條目的內(nèi)容;如果當(dāng)前主題不包含任何條目,我們還需向用戶指出這一點(diǎn):
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 yes.
    </li>
    {% endfor %}
</ul>

{% endblock content %}

像這個(gè)項(xiàng)目的其他頁(yè)面一樣,這里也繼承了base.html。接下來(lái),我們顯示當(dāng)前的主題,它存儲(chǔ)在模板變量{{ topic }} 中。為什么可以使用變量topic 呢?因?yàn)樗谧值鋍ontext 中。接下來(lái),我們開(kāi)始定義一個(gè)顯示每個(gè)條目的項(xiàng)目列表,并像前面顯示所有主題一樣遍歷條目。

每個(gè)項(xiàng)目列表項(xiàng)都將列出兩項(xiàng)信息:條目的時(shí)間戳和完整的文本。為列出時(shí)間戳,我們顯示屬性date_added 的值。在Django模板中,豎線(| )表示模板過(guò)濾器——對(duì)模板變量的值進(jìn)行修改的函數(shù)。過(guò)濾器date: 'M d, Y H:i' 以這樣的格式顯示時(shí)間戳:January 1, 2015 23:00。接下來(lái)的一行顯示text 的完整值,而不僅僅是entry 的前50個(gè)字符。過(guò)濾器linebreaks 將包含換行符的長(zhǎng)條目轉(zhuǎn)換為瀏覽器能夠理解的格式,以免顯示為一個(gè)不間斷的文本塊。我們使用模板標(biāo)簽{% empty %} 打
印一條消息,告訴用戶當(dāng)前主題還沒(méi)有條目。

4. 將顯示所有主題的頁(yè)面中的每個(gè)主題都設(shè)置為鏈接
在瀏覽器中查看顯示特定主題的頁(yè)面前,我們需要修改模板topics.html,讓每個(gè)主題都鏈接到相應(yīng)的網(wǎng)頁(yè),如下所示:
topics.html

{% extends "learning_logs/base.html" %}

{% block content %}
<p>Topics</p>
<ul>
    {% for topic in topics %}
    <li>
        <a href="{%  url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
    </li>
    {% empty %}
    <li>No topics have been added yes.</li>
    {% endfor %}
</ul>
{% endblock content %}

我們使用模板標(biāo)簽url 根據(jù)learning_logs中名為topic 的URL模式來(lái)生成合適的鏈接。這個(gè)URL模式要求提供實(shí)參topic_id ,因此我們?cè)谀0鍢?biāo)簽url 中添加了屬性topic.id?,F(xiàn)在,主題列表中的每個(gè)主題都是一個(gè)鏈接,鏈接到顯示相應(yīng)主題的頁(yè)面,如http://localhost:8000/topics/1/。

如果你刷新顯示所有主題的頁(yè)面,再單擊其中的一個(gè)主題,將看到類似于下圖的頁(yè)面。


image.png

參考

1.Python編程:從入門到實(shí)踐

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容