Python學習之路17-Django入門

VPointer發表於2018-05-29

《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__.pysettings.pyurls.pywsgi.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]是自動提示)。

Python學習之路17-Django入門

"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),將得到如下頁面:

Python學習之路17-Django入門

這是最新版的Django的預設啟動介面。

2.2 建立應用程式(APP)

2.2.1 建立模型

Django專案由一系列應用程式組成,它們協同工作,讓專案成為一個整體。我們在專案根目錄下執行如下命令,建立一個名為learning_logs的應用程式:

python manage.py startapp learning_logs
複製程式碼

執行命令後,根目錄下會多出一個名為learning_logs的資料夾(筆者第一次接觸Django的時候發現這玩意兒居然叫做APP,和平時用的手機上的各種APP相差也太大了,很不適應),它的結構如下:

Python學習之路17-Django入門

重要的是其中的models.pyadmin.pyviews.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類只有兩個屬性textdate_added。模型中如CharFieldDateTimeField這些欄位還有很多,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
複製程式碼

makemigrationsDjango確定該如何修改資料庫,使其能夠儲存與我們定義的新模型相關聯的資料。從輸出可以看出,在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自動在管理網站中新增一些模型,如UserGroup,但對於我們建立的模型,必須手工註冊。

注意前面提到的和models.py在同一目錄的admin.py檔案,這就是註冊自行編寫的模型的地方,在該檔案中加入後兩行程式碼:

# 第一行程式碼是自帶的
from django.contrib import admin
from learning_logs.models import Topic

admin.site.register(Topic)
複製程式碼

現在登入Django自帶的網站管理頁面 http://localhost:8000/admin/ 登入剛才建立的超級使用者和密碼後將出現如下介面:

Python學習之路17-Django入門

在這裡你可以管理使用者和組,以及和模型Topic相關的資料。

現在先手動新增兩個主題:點選Add建立ChessRock Climbing

Python學習之路17-Django入門

2.2.4 定義模型Entry

為使能在每個主題下新增條目,需要定義Entry模型,EntryTopic的關係是多對一。同樣是在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的。在管理頁面中點選EntriesAdd按鈕,你將看到一個下拉選單,用於選擇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變數。

namespacelearning_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 時,將看到如下主頁:

Python學習之路17-Django入門

建立網頁的過程看起來可能很複雜,實際上這就是一個簡單的MVCModelViewController)模型,但在Django中被稱為MVTModelViewTemplate

Python學習之路17-Django入門

這種方式使程式碼結構清晰,修改方便,也讓我們能各司其職,比如,資料庫專家就專注於Model,程式設計師專注於ViewWeb設計人員專注於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連結,點選它,將跳轉到如下頁面:

Python學習之路17-Django入門

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/ 頁面,隨便點選一個主題,比如第一個,將得到以下介面:

Python學習之路17-Django入門

5. 小結

上述內容主要有:

  • 在虛擬環境中安裝Django,建立專案,並核實該專案已正確地建立;
  • 在專案中建立APP;
  • 建立表示APP資料的模型;
  • 建立資料庫,以及在修改模型後,Django可以為遷移資料庫提供什麼樣的幫助;
  • 建立超級使用者,並使用管理網路輸入初試資料;
  • Django shell;
  • 定義URL、建立檢視函式、網頁模板(包括繼承)。

下一篇中,我們將:

  • 建立對使用者友好而直觀的網頁,讓使用者無需通過管理網站就能新增新的主題和條目(個人感覺沒必要這麼做,Django自帶的管理系統挺好用的),以及編輯既有的條目;
  • 新增一個使用者註冊系統,讓使用者能夠建立賬戶。

迎大家關注我的微信公眾號"程式碼港" & 個人網站 www.vpointer.net ~

Python學習之路17-Django入門

相關文章