HelloDjango 系列教程:Django 的接客之道

削微寒發表於2019-08-06

HelloDjango 系列教程:Django 的接客之道

文中涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫

Web 服務簡單的說就是處理請求,每個請求就像是一個“顧客”。首先熱情地把顧客迎接進來,然後滿足使用者的個性化需求,最後讓顧客心滿意足的離開。Django 作為一個 web 框架,能夠讓開發者有更多的精力和時間去應付複雜多變的需求,而不是把時間花費在招店小二、做飯的廚子、服務員等。那麼下面我們就來看看 Django 的接客之道吧。

Django 處理 HTTP 請求

Web 應用的互動過程其實就是 HTTP 請求與響應的過程。無論是在 PC 端還是移動端,我們通常使用瀏覽器來上網,上網流程大致來說是這樣的:

  1. 我們開啟瀏覽器,在位址列輸入想訪問的網址,比如 https://zmrenwu.com/ (當然你也可能從收藏夾裡直接開啟網站,但本質上都是一樣的)。
  2. 瀏覽器知道我們想要訪問哪個網址後,它在後臺幫我們做了很多事情。主要就是把我們的訪問意圖包裝成一個 HTTP 請求,發給我們想要訪問的網址所對應的伺服器。通俗點說就是瀏覽器幫我們通知網站的伺服器,說有人來訪問你啦,訪問的請求都寫在 HTTP 報文裡了,你按照要求處理後告訴我,我再幫你迴應他!
  3. 伺服器處理了HTTP 請求,然後生成一段 HTTP 響應給瀏覽器。瀏覽器解讀這個響應,把相關的內容在瀏覽器裡顯示出來,於是我們就看到了網站的內容。比如你訪問了我的部落格主頁 https://zmrenwu.com/ ,伺服器接收到這個請求後就知道使用者訪問的是首頁,首頁顯示的是全部文章列表,於是它從資料庫裡把文章資料取出來,生成一個寫著這些資料的 HTML 文件,包裝到 HTTP 響應裡發給瀏覽器,瀏覽器解讀這個響應,把 HTML 文件顯示出來,我們就看到了文章列表的內容。

因此,django 作為一個 Web 框架,它的使命就是處理流程中的第二步。即接收瀏覽器發來的 HTTP 請求,返回相應的 HTTP 響應。於是引出這麼幾個問題:

  1. django 如何接收 HTTP 請求?
  2. django 如何處理這個 HTTP 請求?
  3. django 如何生成 HTTP 響應?

對於如何處理這些問題,django 有其一套規定的機制。我們按照 django 的規定,就能開發出所需的功能。

Hello 檢視函式

我們先以一個最簡單的 Hello World 為例來看看 django 處理上述問題的機制是怎麼樣的。

繫結 URL 與檢視函式

首先 django 需要知道當使用者訪問不同的網址時,應該如何處理這些不同的網址(即所說的路由)。django 的做法是把不同的網址對應的處理函式寫在一個 urls.py 檔案裡,當使用者訪問某個網址時,django 就去會這個檔案裡找,如果找到這個網址,就會呼叫和它繫結在一起的處理函式(叫做檢視函式)。

下面是具體的做法,首先在 blog 應用的目錄下建立一個 urls.py 檔案,這時你的目錄看起來是這樣:

blog\
    __init__.py
    admin.py
    apps.py
    migrations\
        0001_initial.py
        __init__.py
    models.py
    tests.py
    views.py
    urls.py

在 blog\urls.py 中寫入這些程式碼:

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

我們首先從 django.urls 匯入了 path 函式,又從當前目錄下匯入了 views 模組。然後我們把網址和處理函式的關係寫在了 urlpatterns 列表裡。

繫結關係的寫法是把網址和對應的處理函式作為引數傳給 path 函式(第一個引數是網址,第二個引數是處理函式),另外我們還傳遞了另外一個引數 name,這個引數的值將作為處理函式 index 的別名,這在以後會用到。

注意這裡我們的網址實際上是一個規則,django 會用這個規則去匹配使用者實際輸入的網址,如果匹配成功,就會呼叫其後面的檢視函式做相應的處理。

比如說我們本地開發伺服器的域名是 http://127.0.0.1:8000 ,那麼當使用者輸入網址 http://127.0.0.1:8000 後,django 首先會把協議 http、域名 127.0.0.1 和埠號 8000 去掉,此時只剩下一個空字串,而 '' 的模式正是匹配一個空字串,於是二者匹配,django 便會呼叫其對應的 views.index 函式。

注意

在 blogproject 目錄下(即 settings.py 所在的目錄),原本就有一個 urls.py 檔案,這是整個工程專案的 URL 配置檔案。而我們這裡新建了一個 urls.py 檔案,且位於 blog 應用下。這個檔案將用於 blog 應用相關的 URL 配置,這樣便於模組化管理。不要把兩個檔案搞混了。

編寫檢視函式

第二步就是要實際編寫我們的 views.index 檢視函式了,按照慣例檢視函式定義在 views.py 檔案裡:

blog/views.py

from django.http import HttpResponse

def index(request):
    return HttpResponse("歡迎訪問我的部落格首頁!")

我們前面說過,Web 伺服器的作用就是接收來自使用者的 HTTP 請求,根據請求內容作出相應的處理,並把處理結果包裝成 HTTP 響應返回給使用者。

這個兩行的函式體現了這個過程。它首先接受了一個名為 request 的引數,這個 request 就是 django 為我們封裝好的 HTTP 請求,它是類 HttpRequest 的一個例項。然後我們便直接返回了一個 HTTP 響應給使用者,這個 HTTP 響應也是 django 幫我們封裝好的,它是類 HttpResponse 的一個例項,只是我們給它傳了一個自定義的字串引數。

瀏覽器接收到這個響應後就會在頁面上顯示出我們傳遞的內容 :歡迎訪問我的部落格首頁!

配置專案 URL

還差最後一步了,我們前面建立了一個 urls.py 檔案,並且繫結了 URL 和檢視函式 index,但是 django 並不知道。django 匹配 URL 模式是在 blogproject 目錄(即 settings.py 檔案所在的目錄)的 urls.py 下的,所以我們要把 blog 應用下的 urls.py 檔案包含到 blogproject\urls.py 裡去,開啟這個檔案看到如下內容:

blogproject/urls.py

"""
一大段註釋
"""

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

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

修改成如下的形式:

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

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

我們這裡匯入了一個 include 函式,然後利用這個函式把 blog 應用下的 urls.py 檔案包含了進來。此外 include 前還有一個 '',這是一個空字串。這裡也可以寫其它字串,django 會把這個字串和後面 include 的 urls.py 檔案中的 URL 拼接。比如說如果我們這裡把 '' 改成 'blog/',而我們在 blog\urls.py 中寫的 URL 是 '',即一個空字串。那麼 django 最終匹配的就是 blog/ 加上一個空字串,即 blog/。

執行結果

執行 pipenv run python manage.py runserver 開啟開發伺服器,在瀏覽器輸入開發伺服器的地址 http://127.0.0.1:8000/ ,可以看到 django 返回的內容了。

歡迎訪問我的部落格首頁!

使用 django 模板系統

這基本上就上 django 的開發流程了,寫好處理 HTTP 請求和返回 HTTP 響應的檢視函式,然後把檢視函式繫結到相應的 URL 上。

但是等一等!我們看到在檢視函式裡返回的是一個 HttpResponse 類的例項,我們給它傳入了一個希望顯示在使用者瀏覽器上的字串。但是我們的部落格不可能只顯示這麼一句話,它有可能會顯示很長很長的內容。比如我們釋出的部落格文章列表,或者一大段的部落格文章。我們不能每次都把這些大段大段的內容傳給 HttpResponse

django 對這個問題給我們提供了一個很好的解決方案,叫做模板系統。django 要我們把大段的文字寫到一個檔案裡,然後 django 自己會去讀取這個檔案,再把讀取到的內容傳給 HttpResponse。讓我們用模板系統來改造一下上面的例子。

首先在我們的專案根目錄(即 manage.py 檔案所在目錄)下建立一個名為 templates 的資料夾,用來存放我們的模板。然後在 templates 目錄下建立一個名為 blog 的資料夾,用來存放和 blog 應用相關的模板。

當然模板存放在哪裡是無關緊要的,只要 django 能夠找到的就好。但是我們建立這樣的資料夾結構的目的是把不同應用用到的模板隔離開來,這樣方便以後維護。我們在 templates\blog 目錄下建立一個名為 index.html 的檔案,此時你的目錄結構應該是這樣的:

HelloDjango-blog-tutorial\
    manage.py
    ...
    templates\
        blog\
            index.html

注意

再一次強調 templates 目錄位於專案根目錄,而 index.html 位於 templates\blog 目錄下,而不是 blog 應用下,如果弄錯了你可能會得到一個 TemplateDoesNotExist 異常。如果遇到這個異常,請回來檢查一下模板目錄結構是否正確。

在 templates\blog\index.html 檔案裡寫入下面的程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
</head>
<body>
<h1>{{ welcome }}</h1>
</body>
</html>

這是一個標準的 HTML 文件,只是裡面有兩個比較奇怪的地方:{{ title }}{{ welcome }}。這是 django 規定的語法。用 {{ }} 包起來的變數叫做模板變數。django 在渲染這個模板的時候會根據我們傳遞給模板的變數替換掉這些變數。最終在模板中顯示的將會是我們傳遞的值。

注意:

index.html 必須以 UTF-8 的編碼格式儲存,且小心不要往裡面新增一些特殊字元,否則極有可能得到一個 UnicodeDecodeError 這樣的錯誤。

模板寫好了,還得告訴 django 去哪裡找模板,在 settings.py 檔案裡設定一下模板檔案所在的路徑。在 settings.py 找到 TEMPLATES 選項,它的內容是這樣的:

blogproject/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.djangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中 DIRS 就是設定模板的路徑,在 [] 中寫入 os.path.join(BASE_DIR, 'templates'),即像下面這樣:

blogproject/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
    },
]

這裡 BASE_DIR 是 settings.py 在配置開頭前面定義的變數,記錄的是工程根目錄 HelloDjango-blog-tutorial 的值。在這個目錄下有模板檔案所在的目錄 templates,於是利用os.path.join 把這兩個路徑連起來,構成完整的模板路徑,django 就知道去這個路徑下面找我們的模板了。

檢視函式可以改一下了:

blog/views.py

from django.shortcuts import render


def index(request):
    return render(request, 'blog/index.html', context={
        'title': '我的部落格首頁',
        'welcome': '歡迎訪問我的部落格首頁'
    })

這裡我們不再是直接把字串傳給 HttpResponse 了,而是呼叫 django 提供的 render 函式。這個函式根據我們傳入的引數來構造 HttpResponse

我們首先把 HTTP 請求傳了進去,然後 render 根據第二個引數的值 blog/index.html 找到這個模板檔案並讀取模板中的內容。之後 render 根據我們傳入的 context 引數的值把模板中的變數替換為我們傳遞的變數的值,{{ title }} 被替換成了 context 字典中 title 對應的值,同理 {{ welcome }} 也被替換成相應的值。

最終,我們的 HTML 模板中的內容字串被傳遞給 HttpResponse 物件並返回給瀏覽器(django 在 render 函式裡隱式地幫我們完成了這個過程),這樣使用者的瀏覽器上便顯示出了我們寫的 HTML 模板的內容了。

HelloDjango 系列教程:Django 的接客之道

關注公眾號加入交流群,一起討論有趣的技術話題

相關文章