一杯茶的時間,上手 Django 框架開發

Django 是 Python 社區的兩大最受歡迎的 Web 框架之一(另一個是 Flask)。憑藉功能強大的腳手架和諸多開箱即用的組件,用 Django 搭建 Web 應用快速而又省力。然而,也正是因為過於強大,想要駕馭它需要花費不少的力氣。本文將通過實現一個新聞發布網站帶你快速熟悉 Django 框架,讓你能夠騎上這匹快馬,在 Web 開發的戰場上盡情馳騁。

提示 這篇文章寫作時用的是 Django 2.x 版本,發表時已經推出了 3.x 版本。不過經過筆者測試,這篇文章中的程式碼對於 2.x 和 3.x 都適用哦!

起步

Django 由 Adrian Holovaty 和 Simon Willison 在 2003 年的秋天寫成,並在 2005 年正式發布。他們倆當時為一個新聞報社製作網站,對快速開發有著比較高的需求,並且希望能夠在開發的同時也能夠讓非技術人員為網站添加內容。於是這也使得 Django 具備了兩項鮮明的特點:

•高度強調可復用性可插拔性,內置大量現成的成熟組件,開發效率極高•自帶與資料庫聯動的後台管理系統,能夠在開發的同時創建內容

Django 的名字取自吉他手 Django Reinhardt,發音為 JANG-goh(諧音「尖狗」),但實際上 Django 的吉祥物是一隻長著翅膀的小馬。

在這篇教程中,我們也將向 Django 的起源致敬——手把手帶你開發一個新聞發布網站,並且可以從後台管理系統中添加新聞,展示到網站首頁上。

預備知識

本教程假定你已經知道了:

•基本的 Python 3 語言知識,包括使用 pip 安裝包•了解 HTTP 協議基礎知識,瀏覽器和伺服器之間是如何互動的

學習目標

讀完這篇教程後,你將掌握 Django MTV 框架的精髓:

•M(Model):創建數據模型,並執行資料庫遷移•T(Template):寫出基本的 Django 模板,並從視圖中傳入數據•V(View):在視圖中訪問資料庫,實現業務邏輯,渲染模板,並接入路由表

雖然 Django 還有很多知識點,但是理解了 MTV,後面的知識點學習起來也就輕鬆多啦。

安裝 Django 並啟用腳手架

本文假定你已經安裝好了 Python 3 和 pip,那麼可以直接用 pip 安裝 Django:

pip install django

直接用 pip 在全局安裝 Django 的確不是一個很好的做法,用虛擬環境更符合最佳實踐。為了減少初學者們的認知負擔,在這裡就簡化了安裝過程。熟悉 pipenv[1] 等虛擬環境工具的老司機當然可以自行使用哈。

安裝好 Django 後,我們用 Django 自帶的腳手架工具 django-admin 創建項目:

django-admin startproject django_news  cd django_news

生成的項目骨架及每個文件的作用如下所示:

django_news  ├── django_news              // 項目全局文件目錄  │   ├── __init__.py  │   ├── settings.py          // 全局配置  │   ├── urls.py              // 全局路由  │   └── wsgi.py              // WSGI服務介面(暫時不用糾結這個是神馬)  └── manage.py                // 項目管理腳本

我們使用 manage.py 來運行開發伺服器(Development Server):

python manage.py runserver

提示 細心的你會發現出現了一行鮮紅色的提示:You have 17 unapplied migration(s)…(省略 n 個字元)。不用擔心,我們會在接下來的步驟中詳細講解來龍去脈。

按照提示,我們通過瀏覽器訪問 localhost:8000,可以看到歡迎介面:

提示 Django 開發伺服器可以保持開啟,並且後面修改程式碼會自動重新載入,非常方便。後面運行其他命令時,再打開一個終端(命令行)即可。

一切準備就緒,韁繩已在你手中!

創建第一個自定義 Django App

在上一節中我們講到,Django 是一個高度模組化的框架。具體而言,一個 Django 應用由多個子應用組成,我們一般稱之為 App(注意不是我們常說的移動應用 APP,而是 Application 的簡寫),如下圖所示。

Django App 的類別

Django App 一般分為三大類(根據來源):

內置:即 Django 框架自帶的應用,包括 admin(後台管理)、auth(身份鑒權)、sessions(會話管理)等等•自定義:即用來實現我們自身業務邏輯的應用,這裡我們將創建一個新聞展示應用•第三方:即社區提供的應用,數量極其豐富,功能涵蓋幾乎所有方面,能夠大大減少開發成本

所有的 Django 應用都在 django_news/settings.py 的 INSTALLED_APPS 列表中定義:

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

實現自定義 App

話不多說,讓我們來創建第一個自定義 App,名稱為 news:

python manage.py startapp news

生成的 news 應用文件夾結構如下所示:

news                     // news 應用目錄  ├── __init__.py          // 初始化模組  ├── admin.py             // 後台管理配置  ├── apps.py              // 應用配置  ├── migrations           // 資料庫遷移文件目錄  │   └── __init__.py      // 資料庫遷移初始化模組  ├── models.py            // 數據模型  ├── tests.py             // 單元測試  └── views.py             // 視圖

這個子目錄裡面乍一看好多文件啊!這是因為 Django 始終堅持解耦的原則——盡量減少程式碼之間的耦合,把不相關的程式碼拆成多個模組,讓同一個模組具有內聚性。相信我,等到後面慢慢熟悉之後,你會對每一個模組都了如指掌的。

實際上,每個 Django App 的組織結構符合 Django 的 MTV 法則——Model(模型)+ Template(模板)+ View(視圖)。MTV 與大家比較熟悉的 MVC 在思想上非常相似,但是命名有比較大的出入,如下表所示:

大家熟知的 View,在 Django 裡面代表的是業務邏輯,也就是 MVC 中的控制器哦!

將自定義 App 添加到全局配置

最後,我們在 settings.py 中將 news 應用加入 INSTALLED_APPS 中:

# ...    INSTALLED_APPS = [      'django.contrib.admin',      'django.contrib.auth',      'django.contrib.contenttypes',      'django.contrib.sessions',      'django.contrib.messages',      'django.contrib.staticfiles',      'news',  ]

至此,我們已經創建了第一個 Django 應用!但是現在這個應用還沒有任何內容,我們接下來將逐步完善這個應用。

理解視圖:業務邏輯的編寫

也許你已經注意到,通過訪問 localhost:8000/admin 已經可以訪問後台管理系統了(雖然會跳轉到登錄介面)。接下來,我們也希望能夠訪問到剛才創建的 news 應用。因此,這一步中我們將:

•在視圖(View)中寫一點業務邏輯•接入路由,使其能夠被訪問

Django 的路由系統

Django 的路由系統是由全局路由子應用路由組成。簡單來說,根據用戶輸入的 URL,全局路由表進行匹配並選擇正確的子應用路由,再由所選擇的子應用路由匹配並選擇正確的視圖(View)。整個流程如下圖所示:

例如,用戶訪問 example.com/apple/buy,然後全局路由根據 /apple/buy 先選擇 apple 的路由表,再從 apple 路由表中根據 /buy 選擇 /buy 路由,然後執行 /buy 對應的 BuyView 視圖,返回給用戶結果。

編寫第一個視圖

對視圖訪問的流程大致了解之後,我們就可以開始動手了。首先打開 news/views.py,寫一個簡單的視圖函數,返回一串 Hello World!:

from django.http import HttpResponse      def index(request):      return HttpResponse('Hello World!')

上面這個 index 函數可以說是一個最簡單的視圖函數了,實際大部分應用的視圖要比這複雜得多。Django 同時支援基於函數的視圖(FBV,Function-based View)和基於類的視圖(CBV,Class-based View),這裡顯然是 FBV,接收一個 request 請求對象作為參數,返回了一個 HttpResponse 對象。

將視圖接入路由

接著,我們要讓路由系統能夠訪問到剛才寫好的視圖函數。因此先實現子應用 news 的路由表,創建 news/urls.py 文件如下:

from django.urls import path    from . import views    urlpatterns = [      path('', views.index, name='index'),  ]

每一個 Django 路由表模組(urls.py)中都約定必須包含一個 urlpatterns 列表用來存放路由映射表。列表中每個元素是一個用 django.urls.path 函數封裝好的路由映射,通常接收以下三個參數:

route:必須,即實際的訪問路由,空字元串等於 /,即空路由•view:必須,該路由將要訪問的視圖•name:可選,該路由的名稱,方便後續在模板中使用

我們將剛剛寫好的 news 路由表接入全局路由表。由於我們希望新聞能夠展示在首頁(即通過 / 就能訪問,無需 /news),因此 news 應用路由在全局路由中的 URL 是一個空字元串。在 django_news/urls.py 中修改如下:

from django.contrib import admin  from django.urls import path, include    urlpatterns = [      path('admin/', admin.site.urls),      path('', include('news.urls')),  ]

這裡使用 django.urls.include 函數將 news 應用的路由表接入進來,並且 include 函數的參數是路由模組路徑的字元串 news.urls,省去了手動 import 的麻煩。

注意 添加路由規則時順序是很重要的,因為在嘗試匹配時會按照從上到下的順序進行,因此應該把最模糊的路由(即空路由)放在最下面。

如果你開發伺服器還在運行(如果沒有可以再打開),訪問 localhost:8000,就可以看到那一串熟悉的字元了:

理解模板:網頁前端的實現

上一步中,我們學會了如何實現視圖,並將其接入路由配置中,使其能夠被用戶訪問。接下來,我們將實現一個 Django 模板作為網頁前端,從而給用戶呈現更豐富的內容。

提示 如果你有過其他模板(或者類似技術)的開發經驗,例如 Jinja、EJS 或是 JSP 等,對 Django 模板會有一種似曾相識的感覺。如果你不了解什麼是模板引擎,也不用擔心,簡單的理解就是一個可以填充內容、甚至能夠加入程式碼邏輯的類似 HTML 文檔,最終會被轉換成瀏覽器能夠識別的 HTML 文檔。

Django 模板語言基礎

Django 模板本質上是一個 HTML 文檔,只不過通過一些特殊的語法實現數據的填充。這裡我們講解一下最常用的三個語法:

表達式插值

最常用的語法,沒有之一。通過在一對花括弧 {{}} 放入一個表達式,就能夠在視圖中傳入表達式中變數的內容,並最終渲染成包含變數具體內容的 HTML 程式碼。需要注意的是,所支援的表達式僅支援以下形式(可以自由組合):

<!-- 單個變數 -->  {{ variable }}    <!-- 獲取字典的鍵或對象的屬性 -->  {{ dict.key }}  {{ object.attribute }}    <!-- 獲取列表中的某個元素 -->  {{ list.0 }}

例如,模板這樣寫:

<h1>{{ name }}</h1>  <p>{{ news.title }}</p>  <p>{{ news.visitors.0 }}</p>

如果我們在視圖中傳入以下上下文字典(Context Dictionary):

{      'name': 'Tuture',      'news': {          'title': 'Hello World',          'visitors': ['Tom', 'Marc'],      }  }

那麼最終渲染成的 HTML 程式碼就是:

<h1>Tuture</h1>  <p>Hello World</p>  <p>Tom</p>

條件語句

條件語句的定義如下:

{% if is_true %}    <h1>It is true!</h1>  {% else %}    <h1>It is false!</h1>  {% endif %}

如果變數 is_true 為真,那麼最終渲染出來的就是 <h1>It is true!</h1>,否則就是 <h1>It is false!</h1>。注意:整個條件語句必須{% endif %} 結束,並且 {% else %}可選的。

循環語句

循環語句用來在模板上展示任意長的列表內容。其語法如下:

{% for elem in some_list %}    <p>{{ elem }}</p>  {% endfor %}

如果傳入的 some_list['Apple', 'Banana', 'Orange'],那麼渲染出的 HTML 程式碼就是:

<p>Apple</p>  <p>Banana</p>  <p>Orange</p>

實現第一個 Django 模板

到了動手時間了,我們先實現第一個 Django 模板。在 news 目錄中創建一個 templates 目錄,再在 templates 目錄中創建一個 news 目錄,並在內層的 news 目錄中創建 index.html 文件:

mkdir -p news/templates/news  touch news/templates/news/index.html

思考 聽上去很麻煩,只創建 news/templates,然後把模板放裡面不就好了,為什麼還要再創建一個 news 目錄?這是由於 Django 的模板查找機制會將所有應用裡面的模板全部收集到一起,如果兩個模板的名字衝突,就會導致其中一個模板不能被正確訪問。如果放在 news 子文件夾裡面,就能夠通過 news/index.html 訪問,通過命名空間的機制避免了衝突。

模板的程式碼如下:

{% if news_list %}    <ul>    {% for elem in news_list %}      <li>        <h3>{{ elem.title }}</h3>        <p>{{ elem.content }}</p>      </li>    {% endfor %}    </ul>  {% else %}    <p>暫無新聞</p>  {% endif %}

這短短几行模板程式碼卻很好地覆蓋了我們剛剛講述的三個模板語法:表達式插值、條件語句和循環語句。如果忘記其中某個地方是什麼意思的話,翻上去看看吧!

完成模板的編寫後,我們要在視圖中對其進行渲染。打開 news/views.py 文件,修改程式碼如下:

from django.shortcuts import render      def index(request):      context = {          'news_list': [              {                  "title": "圖雀寫作工具推出了新的版本",                  "content": "隨隨便便就能寫出一篇好教程,真的很神奇",              },              {                  "title": "圖雀社區正式推出快速入門系列教程",                  "content": "一杯茶的功夫,讓你快速上手,絕無擔憂",              },          ]      }        return render(request, 'news/index.html', context=context)

這裡我們調用 django.shortcuts.render 函數來渲染模板,這個函數通常接受三個參數(有其他參數,但是這裡我們不關心):

request:請求對象,直接把視圖的參數 request 傳進來就可以•template_name:模板名稱,這裡就是我們剛剛創建的 news/index.htmlcontext:傳入模板的上下文對象,必須是一個字典,字典中的每個鍵對應模板中的變數。這裡我們弄了些假數據,假裝是從資料庫裡面取來的。

再訪問 localhost:8000,看一下我們的首頁是不是有內容了:

完美!

理解模型:和資料庫的聯動

Django 的 MTV,我們已經講了 T(Template)和 V(View),現在來到了最後一關:M(Model)了。數據模型是 Django 入門最大的難點,消化這一步的內容需要花點力氣,但是相信我,當你邁過 M 這最後一關,你便能真正上手 Django 開發了!下面我們先介紹一下 Django 的數據模型設計。

Django 在數據模型方面的設計堪稱典範,列舉一些閃光點:

•由於高度解耦的設計,可輕鬆切換各種關係型資料庫(默認的 SQLite,可選 MySQL、PostgreSQL、Oracle 等等)•強大的 ORM(Object Relation Mapping,對象關係映射)模組,使得用 Python 操作資料庫非常輕鬆,免去了使用 SQL 的麻煩•優秀的資料庫遷移機制(Migration),修改數據模式(Schema)比較方便,能夠適應不斷變化的功能需求

對於初學者而言,我們暫且選擇默認的 SQLite 資料庫,省去了配置資料庫的煩惱。在後面的進階教程中,我們會切換到其他適合生產環境的資料庫。

理解 ORM

簡單來說,ORM 能夠將面向對象的程式碼轉換成相應的 SQL 語句,從而對資料庫進行操作。SQL 是用於訪問和處理資料庫的標準的電腦語言,但是直接寫在程式碼裡面顯然難以維護,而且對使用者的要求也非常高,寫的糟糕的 SQL 程式碼查詢效率非常低下。因此,使用設計良好的 ORM 不僅讓程式碼可讀性更好,也能幫助開發者進行查詢優化,節省不少力氣。

我們來看一些簡單的 Django ORM 例子:

# 查詢所有模型  # 等價於 SELECT * FROM Blog  Blog.objects.all()    # 查詢單個模型  # 等價於 SELECT * FROM Blog WHERE ID=1  Blog.objects.get(id=1)    # 添加單個模型  # 等價於 INSERT INTO Blog (title, content) VALUES ('hello', 'world')  blog = Blog(title='hello', content='world')  blog.save()

有木有感覺操作起來比 SQL 方便很多呢?

理解資料庫遷移

資料庫遷移是指將用 Django 定義的模型轉換成 SQL 程式碼(即遷移文件),並在資料庫中進行建表操作(或更新表)。看下面這張圖就知道了:

一般的開發流程就是這樣:

1.用 Django 定義了一個新的數據模型2.用 makemigrations 命令創建遷移文件(存儲在子應用的 migrations 目錄裡面)3.用 migrate 命令執行遷移4.在開發中發現第 1 步中定義的模型不完善,更新數據模型5.跳轉到第 2 步,反覆循環

實現第一個數據模型

終於到了動手的環節。我們首先定義數據模型 Post ,包括標題 title 欄位和 content 欄位,程式碼如下:

from django.db import models      class Post(models.Model):      title = models.CharField(max_length=200)      content = models.TextField()        def __str__(self):          return self.title

定義好之後,運行以下命令創建遷移文件:

python manage.py makemigrations

可以看到輸出如下:

Migrations for 'news':    news/migrations/0001_initial.py      - Create model Post

並且成功地自動創建了 news/migrations/0001_initial.py 遷移腳本。接著我們進行資料庫遷移:

python manage.py migrate

輸出如下圖所示:

資料庫遷移完成後,我們就可以創建用於登錄後台管理的超級用戶:

python manage.py createsuperuser

按照提示填寫用戶名和密碼即可。然後訪問 localhost:8000/admin,進入後台系統的登錄頁面:

填入剛才設置的用戶名和密碼,進入後台管理頁面:

咦,我們剛才創建的 news 應用還有 Post 模型去哪了?

配置後台管理介面

那是因為我們沒有實現 news 應用的後台管理介面。在 news/admin.py 中填入程式碼如下:

from django.contrib import admin    from .models import Post    admin.site.register(Post)

再次進入後台管理系統,可以看到我們的 news 應用和 Post 模型了:

點擊 Posts 一欄的 +Add 按鈕,開始添加新聞(內容隨意):

大概添加個兩三條新聞就差不多啦。你也可以進一步探索後台管理系統,包括修改新聞、添加用戶等等,都可以。

在視圖中添加數據查詢

最後,我們在視圖中加入從資料庫中查詢的程式碼:

from django.shortcuts import render    from .models import Post      def index(request):      context = { 'news_list': Post.objects.all() }      return render(request, 'news/index.html', context=context)

訪問網站首頁,可以看到剛才在後台管理系統添加的新聞了:

大功告成!在這篇教程中,我們完成了一個新聞發布網站,並且可以從後台管理系統中添加新聞,最終展示到我們的網站首頁上。

希望這篇教程能夠讓你對 Django 最重要的一些概念和操作有了基本的了解。Django 還有很多很多的高級玩法,例如數據模型中的高級查詢、欄位索引、更換資料庫等等,模板中的繼承機制、內部標籤等等,還有視圖中如何處理各類請求(POST、PUT等),我們會在後續更多教程中逐一為大家講解,不見不散!

想要學習更多精彩的實戰技術教程?來圖雀社區[2]逛逛吧。如果您覺得我們的教程寫得還不錯,請記得給我們點 在看 哦!鼓勵我們更快更好地寫完剩下的教程!你也可以在評論區留言,告訴我們想要實現什麼功能,我們一定會仔細考慮的哦!

References

[1] pipenv: https://pipenv.kennethreitz.org [2] 圖雀社區: https://tuture.co?utm_source=juejin_zhuanlan