Linux中國

Python Web 應用程序 Django 框架簡介

在本系列(由四部分組成)的前三篇文章中,我們討論了 PyramidFlaskTornado 這 3 個 Web 框架。我們已經構建了三次相同的應用程序,最終我們遇到了 Django。總的來說,Django 是目前 Python 開發人員使用的主要 Web 框架,並且原因顯而易見。它擅長隱藏大量的配置邏輯,讓你專註於能夠快速構建大型應用程序。

也就是說,當涉及到小型項目時,比如我們的待辦事項列表應用程序,Django 可能有點像用消防水管來進行水槍大戰。讓我們來看看它們是如何結合在一起的。

關於 Django

Django 將自己定位為「一個鼓勵快速開發和整潔、實用的設計的高級 Python Web 框架。它由經驗豐富的開發人員構建,解決了 Web 開發的很多麻煩,因此你可以專註於編寫應用程序而無需重新發明輪子」。而且它確實做到了!這個龐大的 Web 框架附帶了非常多的工具,以至於在開發過程中,如何將所有內容組合在一起協同工作可能是個謎。

除了框架本身很大,Django 社區也是非常龐大的。事實上,它非常龐大和活躍,以至於有一個網站專門用於為人們收集第三方包,這些第三方包可集成進 Django 來做一大堆事情。包括從身份驗證和授權到完全基於 Django 的內容管理系統,電子商務附加組件以及與 Stripe(LCTT 譯註:美版「支付寶」)集成的所有內容。至於不要重新發明輪子:如果你想用 Django 完成一些事情,有人可能已經做過了,你只需將它集成進你的項目就行。

為此,我們希望使用 Django 構建 REST API,因此我們將使用流行的 Django REST 框架。它的工作是將 Django 框架(Django 使用自己的模板引擎構建 HTML 頁面)轉換為專門用於有效地處理 REST 交互的系統。讓我們開始吧。

Django 啟動和配置

$ mkdir django_todo
$ cd django_todo
$ pipenv install --python 3.6
$ pipenv shell
(django-someHash) $ pipenv install django djangorestframework

作為參考,我們使用的是 django-2.0.7djangorestframework-3.8.2

與 Flask, Tornado 和 Pyramid 不同,我們不需要自己編寫 setup.py 文件,我們並不是在做一個可安裝的 Python 發布版。像很多事情一樣,Django 以自己的方式處理這個問題。我們仍然需要一個 requirements.txt 文件來跟蹤我們在其它地方部署的所有必要安裝。但是,就 Django 項目中的目標模塊而言,Django 會讓我們列出我們想要訪問的子目錄,然後允許我們從這些目錄中導入,就像它們是已安裝的包一樣。

首先,我們必須創建一個 Django 項目。

當我們安裝了 Django 後,我們還安裝了命令行腳本 django-admin。它的工作是管理所有與 Django 相關的命令,這些命令有助於我們將項目整合在一起,並在我們繼續開發的過程中對其進行維護。django-admin 並不是讓我們從頭開始構建整個 Django 生態系統,而是讓我們從標準 Django 項目所需的所有必要文件(以及更多)的基礎上開始。

調用 django-adminstart-project 命令的語法是 django-admin startproject <項目名稱> <存放目錄>。我們希望文件存於當前的工作目錄中,所以:

(django-someHash) $ django-admin startproject django_todo .

輸入 ls 將顯示一個新文件和一個新目錄。

(django-someHash) $ ls
manage.py   django_todo

manage.py 是一個可執行命令行 Python 文件,它最終成為 django-admin 的封裝。因此,它的工作與 django-admin 是一樣的:幫助我們管理項目。因此得名 manage.py

它在 django_todo 目錄里創建了一個新目錄 django_todo,其代表了我們項目的配置根目錄。現在讓我們深入研究一下。

配置 Django

可以將 django_todo 目錄稱為「配置根目錄」,我們的意思是這個目錄包含了通常配置 Django 項目所需的文件。幾乎所有這個目錄之外的內容都只關注與項目模型、視圖、路由等相關的「業務邏輯」。所有連接項目的點都將在這裡出現。

django_todo 目錄中調用 ls 會顯示以下四個文件:

(django-someHash) $ cd django_todo
(django-someHash) $ ls
__init__.py settings.py urls.py     wsgi.py
  • __init__.py 文件為空,之所以存在是為了將此目錄轉換為可導入的 Python 包。
  • settings.py 是設置大多數配置項的地方。例如項目是否處於 DEBUG 模式,正在使用哪些資料庫,Django 應該定位文件的位置等等。它是配置根目錄的「主要配置」部分,我們將在一會深入研究。
  • urls.py 顧名思義就是設置 URL 的地方。雖然我們不必在此文件中顯式寫入項目的每個 URL,但我們需要讓此文件知道在其他任何地方已聲明的 URL。如果此文件未指向其它 URL,則那些 URL 就不存在。
  • wsgi.py 用於在生產環境中提供應用程序。就像 Pyramid、 Tornado 和 Flask 暴露了一些 「app」 對象一樣,它們用來提供配置好的應用程序,Django 也必須暴露一個,就是在這裡完成的。它可以和 GunicornWaitress 或者 uWSGI 一起配合來提供服務。

設置 settings

看一看 settings.py,它裡面有大量的配置項,那些只是默認值!這甚至不包括資料庫、靜態文件、媒體文件、任何集成的鉤子,或者可以配置 Django 項目的任何其它幾種方式。讓我們從上到下看看有什麼:

  • BASE_DIR 設置目錄的絕對路徑,或者是 manage.py 所在的目錄。這對於定位文件非常有用。
  • SECRET_KEY 是用於 Django 項目中加密簽名的密鑰。在實際中,它用於會話、cookie、CSRF 保護和身份驗證令牌等。最好在第一次提交之前,儘快應該更改 SECRET_KEY 的值並將其放置到環境變數中。
  • DEBUG 告訴 Django 是以開發模式還是生產模式運行項目。這是一個非常關鍵的區別。
    • 在開發模式下,當彈出一個錯誤時,Django 將顯示導致錯誤的完整堆棧跟蹤,以及運行項目所涉及的所有設置和配置。如果在生產環境中將 DEBUG 設置為 True,這可能成為一個巨大的安全問題。
    • 在生產模式下,當出現問題時,Django 會顯示一個簡單的錯誤頁面,即除錯誤代碼外不提供任何信息。
    • 保護我們項目的一個簡單方法是將 DEBUG 設置為環境變數,如 bool(os.environ.get(&apos;DEBUG&apos;, &apos;&apos;))
  • ALLOWED_HOSTS 是應用程序提供服務的主機名的列表。在開發模式中,這可能是空的;但是在生產環境中,如果為項目提供服務的主機不在 ALLOWED_HOSTS 列表中,Django 項目將無法運行。這是設置為環境變數的另一種情況。
  • INSTALLED_APPS 是我們的 Django 項目可以訪問的 Django 「apps」 列表(將它們視為子目錄,稍後會詳細介紹)。默認情況下,它將提供:
    • 內置的 Django 管理網站
    • Django 的內置認證系統
    • Django 的數據模型通用管理器
    • 會話管理
    • Cookie 和基於會話的消息傳遞
    • 站點固有的靜態文件的用法,比如 css 文件、js 文件、任何屬於我們網站設計的圖片等。
  • MIDDLEWARE 顧名思義:幫助 Django 項目運行的中間件。其中很大一部分用於處理各種類型的安全,儘管我們可以根據需要添加其它中間件。
  • ROOT_URLCONF 設置基本 URL 配置文件的導入路徑。還記得我們之前見過的那個 urls.py 嗎?默認情況下,Django 指向該文件以此來收集所有的 URL。如果我們想讓 Django 在其它地方尋找,我們將在這裡設置 URL 位置的導入路徑。
  • TEMPLATES 是 Django 用於我們網站前端的模板引擎列表,假如我們依靠 Django 來構建我們的 HTML。我們在這裡不需要,那就無關緊要了。
  • WSGI_APPLICATION 設置我們的 WSGI 應用程序的導入路徑 —— 在生產環境下使用的東西。默認情況下,它指向 wsgi.py 中的 application 對象。這很少(如果有的話)需要修改。
  • DATABASES 設置 Django 項目將訪問那些資料庫。必須設置 default 資料庫。我們可以通過名稱設置別的資料庫,只要我們提供 HOSTUSERPASSWORDPORT、資料庫名稱 NAME 和合適的 ENGINE。可以想像,這些都是敏感的信息,因此最好將它們隱藏在環境變數中。查看 Django 文檔了解更多詳情。
    • 注意:如果不是提供資料庫的每個單個部分,而是提供完整的資料庫 URL,請查看 djdatabaseurl
  • AUTH_PASSWORD_VALIDATORS 實際上是運行以檢查輸入密碼的函數列表。默認情況下我們有一些,但是如果我們有其它更複雜的驗證需求:不僅僅是檢查密碼是否與用戶的屬性匹配,是否超過最小長度,是否是 1000 個最常用的密碼之一,或者密碼完全是數字,我們可以在這裡列出它們。
  • LANGUAGE_CODE 設置網站的語言。默認情況下它是美國英語,但我們可以將其切換為其它語言。
  • TIME_ZONE 是我們 Django 項目後中自動生成的時間戳的時區。我強調堅持使用 UTC 並在其它地方執行任何特定於時區的處理,而不是嘗試重新配置此設置。正如這篇文章 所述,UTC 是所有時區的共同點,因為不需要擔心偏移。如果偏移很重要,我們可以根據需要使用與 UTC 的適當偏移來計算它們。
  • USE_I18N 將讓 Django 使用自己的翻譯服務來為前端翻譯字元串。I18N = 國際化(internationalization,「i」 和 「n」 之間共 18 個字元)。
  • USE_L10N L10N = 本地化(localization,在 l 和 n 之間共 10 個字元) 。如果設置為 True,那麼將使用數據的公共本地格式。一個很好的例子是日期:在美國它是 MM-DD-YYYY。在歐洲,日期往往寫成 DD-MM-YYYY。
  • STATIC_URL 是用於提供靜態文件的主體部分。我們將構建一個 REST API,因此我們不需要考慮靜態文件。通常,這會為每個靜態文件的域名設置根路徑。所以,如果我們有一個 Logo 圖像,那就是 http://<domainname>/<STATIC_URL>/logo.gif

默認情況下,這些設置已準備就緒。我們必須改變的一個選項是 DATABASES 設置。首先,我們創建將要使用的資料庫:

(django-someHash) $ createdb django_todo

我們想要像使用 Flask、Pyramid 和 Tornado 一樣使用 PostgreSQL 資料庫,這意味著我們必須更改 DATABASES 設置以允許我們的伺服器訪問 PostgreSQL 資料庫。首先是引擎。默認情況下,資料庫引擎是 django.db.backends.sqlite3,我們把它改成 django.db.backends.postgresql

有關 Django 可用引擎的更多信息,請查看文檔。請注意,儘管技術上可以將 NoSQL 解決方案整合到 Django 項目中,但為了開箱即用,Django 強烈偏向於 SQL 解決方案。

接下來,我們必須為連接參數的不同部分指定鍵值對。

  • NAME 是我們剛剛創建的資料庫的名稱。
  • USER 是 Postgres 資料庫用戶名。
  • PASSWORD 是訪問資料庫所需的密碼。
  • HOST 是資料庫的主機。當我們在本地開發時,localhost127.0.0.1 都將起作用。
  • PORT 是我們為 Postgres 開放的埠,它通常是 5432

settings.py 希望我們為每個鍵提供字元串值。但是,這是高度敏感的信息。任何負責任的開發人員都不應該這樣做。有幾種方法可以解決這個問題,一種是我們需要設置環境變數。

DATABASES = {
    &apos;default&apos;: {
        &apos;ENGINE&apos;: &apos;django.db.backends.postgresql&apos;,
        &apos;NAME&apos;: os.environ.get(&apos;DB_NAME&apos;, &apos;&apos;),
        &apos;USER&apos;: os.environ.get(&apos;DB_USER&apos;, &apos;&apos;),
        &apos;PASSWORD&apos;: os.environ.get(&apos;DB_PASS&apos;, &apos;&apos;),
        &apos;HOST&apos;: os.environ.get(&apos;DB_HOST&apos;, &apos;&apos;),
        &apos;PORT&apos;: os.environ.get(&apos;DB_PORT&apos;, &apos;&apos;),
    }
}

在繼續之前,請確保設置環境變數,否則 Django 將無法工作。此外,我們需要在此環境中安裝 psycopg2,以便我們可以與資料庫通信。

Django 路由和視圖

讓我們在這個項目中實現一些函數。我們將使用 Django REST 框架來構建 REST API,所以我們必須確保在 settings.py 中將 rest_framework 添加到 INSTALLED_APPS 的末尾。

INSTALLED_APPS = [
    &apos;django.contrib.admin&apos;,
    &apos;django.contrib.auth&apos;,
    &apos;django.contrib.contenttypes&apos;,
    &apos;django.contrib.sessions&apos;,
    &apos;django.contrib.messages&apos;,
    &apos;django.contrib.staticfiles&apos;,
    &apos;rest_framework&apos;
]

雖然 Django REST 框架並不專門需要基於類的視圖(如 Tornado)來處理傳入的請求,但類是編寫視圖的首選方法。讓我們來定義一個類視圖。

讓我們在 django_todo 創建一個名為 views.py 的文件。在 views.py 中,我們將創建 「Hello, world!」 視圖。

# in django_todo/views.py
from rest_framework.response import JsonResponse
from rest_framework.views import APIView

class HelloWorld(APIView):
    def get(self, request, format=None):
        """Print &apos;Hello, world!&apos; as the response body."""
        return JsonResponse("Hello, world!")

每個 Django REST 框架基於類的視圖都直接或間接地繼承自 APIViewAPIView 處理大量的東西,但針對我們的用途,它做了以下特定的事情:

* 根據 HTTP 方法(例如 GET、POST、PUT、DELETE)來設置引導對應請求所需的方法
* 用我們需要的所有數據和屬性來填充 `request` 對象,以便解析和處理傳入的請求 
* 採用 `Response` 或 `JsonResponse`,每個調度方法(即名為 `get`、`post`、`put`、`delete` 的方法)返回並構造格式正確的 HTTP 響應。

終於,我們有一個視圖了!它本身沒有任何作用,我們需要將它連接到路由。

如果我們跳轉到 django_todo/urls.py,我們會到達默認的 URL 配置文件。如前所述:如果 Django 項目中的路由不包含在此處,則它不存在。

我們在給定的 urlpatterns 列表中添加所需的 URL。默認情況下,我們有一整套 URL 用於 Django 的內置管理後端系統。我們會完全刪除它。

我們還得到一些非常有用的文檔字元串,它告訴我們如何向 Django 項目添加路由。我們需要調用 path(),伴隨三個參數:

  • 所需的路由,作為字元串(沒有前導斜線)
  • 處理該路由的視圖函數(只能有一個函數!)
  • 在 Django 項目中路由的名稱

讓我們導入 HelloWorld 視圖並將其附加到主路徑 / 。我們可以從 urlpatterns 中刪除 admin 的路徑,因為我們不會使用它。

# django_todo/urls.py, after the big doc string
from django.urls import path
from django_todo.views import HelloWorld

urlpatterns = [
    path(&apos;&apos;, HelloWorld.as_view(), name="hello"),
]

好吧,這裡有一點不同。我們指定的路由只是一個空白字元串,為什麼它會工作?Django 假設我們聲明的每個路由都以一個前導斜杠開頭,我們只是在初始域名後指定資源路由。如果一條路由沒有去往一個特定的資源,而只是一個主頁,那麼該路由是 &apos;&apos;,實際上是「沒有資源」。

HelloWorld 視圖是從我們剛剛創建的 views.py 文件導入的。為了執行此導入,我們需要更新 settings.py 中的 INSTALLED_APPS 列表使其包含 django_todo。是的,這有點奇怪。以下是一種理解方式。

INSTALLED_APPS 指的是 Django 認為可導入的目錄或包的列表。它是 Django 處理項目的各個組件的方式,比如安裝了一個包,而不需要經過 setup.py 的方式。我們希望將 django_todo 目錄視為可導入的包,因此我們將該目錄包含在 INSTALLED_APPS 中。現在,在該目錄中的任何模塊也是可導入的。所以我們得到了我們的視圖。

path 函數只將視圖函數作為第二個參數,而不僅僅是基於類的視圖。幸運的是,所有有效的基於 Django 類的視圖都包含 .as_view() 方法。它的工作是將基於類的視圖的所有優點匯總到一個視圖函數中並返回該視圖函數。所以,我們永遠不必擔心轉換的工作。相反,我們只需要考慮業務邏輯,讓 Django 和 Django REST 框架處理剩下的事情。

讓我們在瀏覽器中打開它!

Django 提供了自己的本地開發伺服器,可通過 manage.py 訪問。讓我們切換到包含 manage.py 的目錄並輸入:

(django-someHash) $ ./manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
August 01, 2018 - 16:47:24
Django version 2.0.7, using settings &apos;django_todo.settings&apos;
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

runserver 執行時,Django 會檢查以確保項目(或多或少)正確連接在一起。這不是萬無一失的,但確實會發現一些明顯的問題。如果我們的資料庫與代碼不同步,它會通知我們。毫無疑問,因為我們沒有將任何應用程序的東西提交到我們的資料庫,但現在這樣做還是可以的。讓我們訪問 http://127.0.0.1:8000 來查看 HelloWorld 視圖的輸出。

咦?這不是我們在 Pyramid、Flask 和 Tornado 中看到的明文數據。當使用 Django REST 框架時,HTTP 響應(在瀏覽器中查看時)是這樣呈現的 HTML,以紅色顯示我們的實際 JSON 響應。

但不要擔心!如果我們在命令行中使用 curl 快速訪問 http://127.0.0.1:8000,我們就不會得到任何花哨的 HTML,只有內容。

# 注意:在不同的終埠窗口中執行此操作,在虛擬環境之外
$ curl http://127.0.0.1:8000
"Hello, world!"

棒極了!

Django REST 框架希望我們在使用瀏覽器瀏覽時擁有一個人性化的界面。這是有道理的,如果在瀏覽器中查看 JSON,通常是因為人們想要檢查它是否正確,或者在設計一些消費者 API 時想要了解 JSON 響應。這很像你從 Postman 中獲得的東西。

無論哪種方式,我們都知道我們的視圖工作了!酷!讓我們概括一下我們做過的事情:

  1. 使用 django-admin startproject <項目名稱> 開始一個項目
  2. 使用環境變數來更新 django_todo/settings.py 中的 DEBUGSECRET_KEY,還有 DATABASES 字典
  3. 安裝 Django REST 框架,並將它添加到 INSTALLED_APPS
  4. 創建 django_todo/views.py 來包含我們的第一個類視圖,它返迴響應 「Hello, world!」
  5. 更新 django_todo/urls.py,其中包含我們的根路由
  6. django_todo/settings.py 中更新 INSTALLED_APPS 以包含 django_todo

創建模型

現在讓我們來創建數據模型吧。

Django 項目的整個基礎架構都是圍繞數據模型構建的,它是這樣編寫的,每個數據模型夠可以擁有自己的小天地,擁有自己的視圖,自己與其資源相關的 URL 集合,甚至是自己的測試(如果我們想要的話)。

如果我們想構建一個簡單的 Django 項目,我們可以通過在 django_todo 目錄中編寫我們自己的 models.py 文件並將其導入我們的視圖來避免這種情況。但是,我們想以「正確」的方式編寫 Django 項目,因此我們應該儘可能地將模型拆分成符合 Django Way™(Django 風格)的包。

Django Way 涉及創建所謂的 Django 「應用程序」,它本身並不是單獨的應用程序,它們沒有自己的設置和諸如此類的東西(雖然它們也可以)。但是,它們可以擁有一個人們可能認為屬於獨立應用程序的東西:

  • 一組自建的 URL
  • 一組自建的 HTML 模板(如果我們想要提供 HTML)
  • 一個或多個數據模型
  • 一套自建的視圖
  • 一套自建的測試

它們是獨立的,因此可以像獨立應用程序一樣輕鬆共享。實際上,Django REST 框架是 Django 應用程序的一個例子。它包含自己的視圖和 HTML 模板,用於提供我們的 JSON。我們只是利用這個 Django 應用程序將我們的項目變成一個全面的 RESTful API 而不用那麼麻煩。

要為我們的待辦事項列表項創建 Django 應用程序,我們將要使用 manage.pystartapp 命令。

(django-someHash) $ ./manage.py startapp todo

startapp 命令成功執行後沒有輸出。我們可以通過使用 ls 來檢查它是否完成它應該做的事情。

(django-someHash) $ ls
Pipfile      Pipfile.lock django_todo  manage.py    todo

看看:我們有一個全新的 todo 目錄。讓我們看看裡面!

(django-someHash) $ ls todo
__init__.py admin.py    apps.py     migrations  models.py   tests.py    views.py

以下是 manage.py startapp 創建的文件:

  • __init__.py 是空文件。它之所以存在是因為此目錄可看作是模型、視圖等的有效導入路徑。
  • admin.py 不是空文件。它用於在 Django admin 中規範化這個應用程序的模型,我們在本文中沒有涉及到它。
  • apps.py 這裡基本不起作用。它有助於規範化 Django admin 的模型。
  • migrations 是一個包含我們數據模型快照的目錄。它用於更新資料庫。這是少數幾個內置了資料庫管理的框架之一,其中一部分允許我們更新資料庫,而不必拆除它並重建它以更改 Schema。
  • models.py 是數據模型所在。
  • tests.py 是測試所在的地方,如果我們需要寫測試。
  • views.py 用於我們編寫的與此應用程序中的模型相關的視圖。它們不是一定得寫在這裡。例如,我們可以在 django_todo/views.py 中寫下我們所有的視圖。但是,它在這個應用程序中更容易將我們的概念理清。在覆蓋了許多概念的擴展應用程序的關係之間會變得更加密切。

它並沒有為這個應用程序創建 urls.py 文件,但我們可以自己創建。

(django-someHash) $ touch todo/urls.py

在繼續之前,我們應該幫自己一個忙,將這個新 Django 應用程序添加到 django_todo/settings.py 中的 INSTALLED_APPS 列表中。

# settings.py
INSTALLED_APPS = [
    &apos;django.contrib.admin&apos;,
    &apos;django.contrib.auth&apos;,
    &apos;django.contrib.contenttypes&apos;,
    &apos;django.contrib.sessions&apos;,
    &apos;django.contrib.messages&apos;,
    &apos;django.contrib.staticfiles&apos;,
    &apos;rest_framework&apos;,
    &apos;django_todo&apos;,
    &apos;todo&apos; # <--- 添加了這行
]

檢查 todo/models.py 發現 manage.py 已經為我們編寫了一些代碼。不同於在 Flask、Tornado 和 Pyramid 實現中創建模型的方式,Django 不利用第三方來管理資料庫會話或構建其對象實例。它全部歸入 Django 的 django.db.models 子模塊。

然而,建立模型的方式或多或少是相同的。要在 Django 中創建模型,我們需要構建一個繼承自 models.Modelclass,將應用於該模型實例的所有欄位都應視為類屬性。我們不像過去那樣從 SQLAlchemy 導入列和欄位類型,而是直接從 django.db.models 導入。

# todo/models.py
from django.db import models

class Task(models.Model):
    """Tasks for the To Do list."""
    name = models.CharField(max_length=256)
    note = models.TextField(blank=True, null=True)
    creation_date = models.DateTimeField(auto_now_add=True)
    due_date = models.DateTimeField(blank=True, null=True)
    completed = models.BooleanField(default=False)

雖然 Django 的需求和基於 SQLAlchemy 的系統之間存在一些明顯的差異,但總體內容和結構或多或少相同。讓我們來指出這些差異。

我們不再需要為對象實例聲明自動遞增 ID 的單獨欄位。除非我們指定一個不同的欄位作為主鍵,否則 Django 會為我們構建一個。

我們只是直接引用數據類型作為列本身,而不是實例化傳遞數據類型對象的 Column 對象。

Unicode 欄位變為 models.CharFieldmodels.TextFieldCharField 用於特定最大長度的小文本欄位,而 TextField 用於任何數量的文本。

TextField 應該是空白的,我們以兩種方式指定它。blank = True 表示當構建此模型的實例,並且正在驗證附加到該欄位的數據時,該數據是可以為空的。這與 null = True 不同,後者表示當構造此模型類的表時,對應於 note 的列將允許空白或為 NULL。因此,總而言之,blank = True 控制如何將數據添加到模型實例,而 null = True 控制如何構建保存該數據的資料庫表。

DateTime 欄位增加了一些屬性,並且能夠為我們做一些工作,使得我們不必修改類的 __init__ 方法。對於 creation_date 欄位,我們指定 auto_now_add = True。在實際意義上意味著,當創建一個新模型實例時,Django 將自動記錄現在的日期和時間作為該欄位的值。這非常方便!

auto_now_add 及其類似屬性 auto_now 都沒被設置為 True 時,DateTimeField 會像其它欄位一樣需要預期的數據。它需要提供一個適當的 datetime 對象才能生效。due_date 列的 blanknull 屬性都設置為 True,這樣待辦事項列表中的項目就可以成為將來某個時間點完成,沒有確定的日期或時間。

BooleanField 最終可以取兩個值:TrueFalse。這裡,默認值設置為 False

管理資料庫

如前所述,Django 有自己的資料庫管理方式。我們可以利用 Django 提供的 manage.py 腳本,而不必編寫任何關於資料庫的代碼。它不僅可以管理我們資料庫的表的構建,還可以管理我們希望對這些表進行的任何更新,而不必將整個事情搞砸!

因為我們構建了一個新模型,所以我們需要讓資料庫知道它。首先,我們需要將與此模型對應的模式放入代碼中。manage.pymakemigrations 命令對我們構建的模型類及其所有欄位進行快照。它將獲取該信息並將其打包成一個 Python 腳本,該腳本將存在於特定 Django 應用程序的 migrations 目錄中。永遠沒有理由直接運行這個遷移腳本。它的存在只是為了讓 Django 可以使用它作為更新資料庫表的基礎,或者在我們更新模型類時繼承信息。

(django-someHash) $ ./manage.py makemigrations
Migrations for &apos;todo&apos;:
  todo/migrations/0001_initial.py
    - Create model Task

這將查找 INSTALLED_APPS 中列出的每個應用程序,並檢查這些應用程序中存在的模型。然後,它將檢查相應的 migrations 目錄中的遷移文件,並將它們與每個 INSTALLED_APPS 中的模型進行比較。如果模型已經升級超出最新遷移所應存在的範圍,則將創建一個繼承自最新遷移文件的新遷移文件,它將自動命名,並且還會顯示一條消息,說明自上次遷移以來發生了哪些更改。

如果你上次處理 Django 項目已經有一段時間了,並且不記得模型是否與遷移同步,那麼你無需擔心。makemigrations 是一個冪等操作。無論你運行 makemigrations 一次還是 20 次,migrations 目錄只有一個與當前模型配置的副本。更棒的是,當我們運行 ./manage.py runserver 時,Django 檢測到我們的模型與遷移不同步,它會用彩色文本告訴我們以便我們可以做出適當的選擇。

下一個要點是至少讓每個人訪問一次:創建一個遷移文件不會立即影響我們的資料庫。當我們運行 makemigrations 時,我們布置我們的 Django 項目定義了給定的表應該如何創建和最終查找。我們仍要將這些更改應用於資料庫。這就是 migrate 命令的用途。

(django-someHash) $ ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo
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 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 sessions.0001_initial... OK
  Applying todo.0001_initial... OK

當我們應用這些遷移時,Django 首先檢查其他 INSTALLED_APPS 是否有需要應用的遷移,它大致按照列出的順序檢查它們。我們希望我們的應用程序最後列出,因為我們希望確保,如果我們的模型依賴於任何 Django 的內置模型,我們所做的資料庫更新不會受到依賴性問題的影響。

我們還有另一個要構建的模型:User 模型。但是,因為我們正在使用 Django,事情有一些變化。許多應用程序需要某種類型的用戶模型,Django 的 django.contrib.auth 包構建了自己的用戶模型供我們使用。如果無需用戶所需要的身份驗證令牌,我們可以繼續使用它而不是重新發明輪子。

但是,我們需要那個令牌。我們可以通過兩種方式來處理這個問題。

  • 繼承 Django 的 User 對象,我們自己的對象通過添加 token 欄位來擴展它
  • 創建一個與 Django 的 User 對象一對一關係的新對象,其唯一目的是持有一個令牌

我習慣於建立對象關係,所以讓我們選擇第二種選擇。我們稱之為 Owner,因為它基本上具有與 User 類似的內涵,這就是我們想要的。

出於純粹的懶惰,我們可以在 todo/models.py 中包含這個新的 Owner 對象,但是不要這樣做。Owner 沒有明確地與任務列表上的項目的創建或維護有關。從概念上講,Owner 只是任務的所有者。甚至有時候我們想要擴展這個 Owner 以包含與任務完全無關的其他數據。

為了安全起見,讓我們創建一個 owner 應用程序,其工作是容納和處理這個 Owner 對象。

(django-someHash) $ ./manage.py startapp owner

不要忘記在 settings.py 文件中的 INSTALLED_APPS 中添加它。 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'django_todo', 'todo', 'owner' ]

如果我們查看 Django 項目的根目錄,我們現在有兩個 Django 應用程序:

(django-someHash) $ ls
Pipfile      Pipfile.lock django_todo  manage.py    owner        todo

owner/models.py 中,讓我們構建這個 Owner 模型。如前所述,它與 Django 的內置 User 對象有一對一的關係。我們可以用 Django 的 models.OneToOneField 強制實現這種關係。

# owner/models.py
from django.db import models
from django.contrib.auth.models import User
import secrets

class Owner(models.Model):
    """The object that owns tasks."""
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    token = models.CharField(max_length=256)
    def __init__(self, *args, **kwargs):
        """On construction, set token."""
        self.token = secrets.token_urlsafe(64)
        super().__init__(*args, **kwargs)

這表示 Owner 對象對應到 User 對象,每個 user 實例有一個 owner 實例。on_delete = models.CASCADE 表示如果相應的 User 被刪除,它所對應的 Owner 實例也將被刪除。讓我們運行 makemigrationsmigrate 來將這個新模型放入到我們的資料庫中。

(django-someHash) $ ./manage.py makemigrations
Migrations for &apos;owner&apos;:
  owner/migrations/0001_initial.py
    - Create model Owner
(django-someHash) $ ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo
Running migrations:
  Applying owner.0001_initial... OK

現在我們的 Owner 需要擁有一些 Task 對象。它與上面看到的 OneToOneField 非常相似,只不過我們會在 Task 對象上貼一個 ForeignKey 欄位指向 Owner

# todo/models.py
from django.db import models
from owner.models import Owner

class Task(models.Model):
    """Tasks for the To Do list."""
    name = models.CharField(max_length=256)
    note = models.TextField(blank=True, null=True)
    creation_date = models.DateTimeField(auto_now_add=True)
    due_date = models.DateTimeField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    owner = models.ForeignKey(Owner, on_delete=models.CASCADE)

每個待辦事項列表任務只有一個可以擁有多個任務的所有者。刪除該所有者後,他們擁有的任務都會隨之刪除。

現在讓我們運行 makemigrations 來獲取我們的數據模型設置的新快照,然後運行 migrate 將這些更改應用到我們的資料庫。

(django-someHash) django $ ./manage.py makemigrations
You are trying to add a non-nullable field &apos;owner&apos; to task without a default; we can&apos;t do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py

不好了!出現了問題!發生了什麼?其實,當我們創建 Owner 對象並將其作為 ForeignKey 添加到 Task 時,要求每個 Task 都需要一個 Owner。但是,我們為 Task 對象進行的第一次遷移不包括該要求。因此,即使我們的資料庫表中沒有數據,Django 也會對我們的遷移進行預先檢查,以確保它們兼容,而我們提議的這種新遷移不是。

有幾種方法可以解決這類問題:

  1. 退出當前遷移並構建一個包含當前模型配置的新遷移
  2. 將一個默認值添加到 Task 對象的 owner 欄位
  3. 允許任務為 owner 欄位設置 NULL

方案 2 在這裡沒有多大意義。我們建議,默認情況下,任何創建的 Task 都會對應到某個默認所有者,儘管默認所有者不一定存在。 方案 1 要求我們銷毀和重建我們的遷移,而我們應該把它們留下。

讓我們考慮選項 3。在這種情況下,如果我們允許 Task 表為所有者提供空值,它不會很糟糕。從這一點開始創建的任何任務都必然擁有一個所有者。如果你的資料庫表並非不能接受重新架構,請刪除該遷移、刪除表並重建遷移。

# todo/models.py
from django.db import models
from owner.models import Owner

class Task(models.Model):
    """Tasks for the To Do list."""
    name = models.CharField(max_length=256)
    note = models.TextField(blank=True, null=True)
    creation_date = models.DateTimeField(auto_now_add=True)
    due_date = models.DateTimeField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    owner = models.ForeignKey(Owner, on_delete=models.CASCADE, null=True)
(django-someHash) $ ./manage.py makemigrations
Migrations for &apos;todo&apos;:
  todo/migrations/0002_task_owner.py
    - Add field owner to task
(django-someHash) $ ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo
Running migrations:
  Applying todo.0002_task_owner... OK

酷!我們有模型了!歡迎使用 Django 聲明對象的方式。

出於更好的權衡,讓我們確保無論何時製作 User,它都會自動與新的 Owner 對象對應。我們可以使用 Django 的 signals 系統來做到這一點。基本上,我們確切地表達了意圖:「當我們得到一個新的 User 被構造的信號時,構造一個新的 Owner 並將新的 User 設置為 Owneruser 欄位。」在實踐中看起來像這樣:

# owner/models.py
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

import secrets

class Owner(models.Model):
    """The object that owns tasks."""
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    token = models.CharField(max_length=256)

    def __init__(self, *args, **kwargs):
        """On construction, set token."""
        self.token = secrets.token_urlsafe(64)
        super().__init__(*args, **kwargs)

@receiver(post_save, sender=User)
def link_user_to_owner(sender, **kwargs):
    """If a new User is saved, create a corresponding Owner."""
    if kwargs[&apos;created&apos;]:
        owner = Owner(user=kwargs[&apos;instance&apos;])
        owner.save()

我們設置了一個函數,用於監聽從 Django 中內置的 User 對象發送的信號。它正在等待 User 對象被保存之後的情況。這可以來自新的 User 或對現有 User 的更新。我們在監聽功能中辨別出兩種情況。

如果發送信號的東西是新創建的實例,kwargs [&apos;created&apos;] 將具有值 True。如果是 True 的話,我們想做點事情。如果它是一個新實例,我們創建一個新的 Owner,將其 user 欄位設置為創建的新 User 實例。之後,我們 save() 新的 Owner。如果一切正常,這將提交更改到資料庫。如果數據沒通過我們聲明的欄位的驗證,它將失敗。

現在讓我們談談我們將如何訪問數據。

訪問模型數據

在 Flask、Pyramid 和 Tornado 框架中,我們通過對某些資料庫會話運行查詢來訪問模型數據。也許它被附加到 request 對象,也許它是一個獨立的 session 對象。無論如何,我們必須建立與資料庫的實時連接並在該連接上進行查詢。

這不是 Django 的工作方式。默認情況下,Django 不利用任何第三方對象關係映射(ORM)與資料庫進行通信。相反,Django 允許模型類維護自己與資料庫的對話。

django.db.models.Model 繼承的每個模型類都會附加一個 objects 對象。這將取代我們熟悉的 sessiondbsession。讓我們打開 Django 給我們的特殊 shell,並研究這個 objects 對象是如何工作的。

(django-someHash) $ ./manage.py shell
Python 3.7.0 (default, Jun 29 2018, 20:13:13)
[Clang 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

Django shell 與普通的 Python shell 不同,因為它知道我們正在構建的 Django 項目,可以輕鬆導入我們的模型、視圖、設置等,而不必擔心安裝包。我們可以通過簡單的 import 訪問我們的模型。

>>> from owner.models import Owner
>>> Owner
<class &apos;owner.models.Owner&apos;>

目前,我們沒有 Owner 實例。我們可以通過 Owner.objects.all() 查詢它們。

>>> Owner.objects.all()
<QuerySet []>

無論何時我們在 <Model> .objects 對象上運行查詢方法,我們都會得到 QuerySet。為了我們的目的,它實際上是一個列表,這個列表向我們顯示它是空的。讓我們通過創建一個 User 來創建一個 Owner

>>> from django.contrib.auth.models import User
>>> new_user = User(username=&apos;kenyattamurphy&apos;, email=&apos;kenyatta.murphy@gmail.com&apos;)
>>> new_user.set_password(&apos;wakandaforever&apos;)
>>> new_user.save()

如果我們現在查詢所有的 Owner,我們應該會找到 Kenyatta。

>>> Owner.objects.all()
<QuerySet [<Owner: Owner object (1)>]>

棒極了!我們得到了數據!

序列化模型

我們將在 「Hello World」 之外來回傳遞數據。因此,我們希望看到某種類似於 JSON 類型的輸出,它可以很好地表示數據。獲取該對象的數據並將其轉換為 JSON 對象以通過 HTTP 提交是數據序列化的一種方式。在序列化數據時,我們正在獲取我們目前擁有的數據並重新格式化以適應一些標準的、更易於理解的形式。

如果我用 Flask、Pyramid 和 Tornado 這樣做,我會在每個模型上創建一個新方法,讓用戶可以直接調用 to_json()to_json() 的唯一工作是返回一個 JSON 可序列化的(即數字、字元串、列表、字典)字典,其中包含我想要為所討論的對象顯示的任何欄位。

對於 Task 對象,它可能看起來像這樣:

class Task(Base):
    ...all the fields...

    def to_json(self):
        """Convert task attributes to a JSON-serializable dict."""
        return {
            &apos;id&apos;: self.id,
            &apos;name&apos;: self.name,
            &apos;note&apos;: self.note,
            &apos;creation_date&apos;: self.creation_date.strftime(&apos;%m/%d/%Y %H:%M:%S&apos;),
            &apos;due_date&apos;: self.due_date.strftime(&apos;%m/%d/%Y %H:%M:%S&apos;),
            &apos;completed&apos;: self.completed,
            &apos;user&apos;: self.user_id
        }

這不花哨,但它確實起到了作用。

然而,Django REST 框架為我們提供了一個對象,它不僅可以為我們這樣做,還可以在我們想要創建新對象實例或更新現有實例時驗證輸入,它被稱為 ModelSerializer

Django REST 框架的 ModelSerializer 是我們模型的有效文檔。如果沒有附加模型,它們就沒有自己的生命(因為那裡有 Serializer 類)。它們的主要工作是準確地表示我們的模型,並在我們的模型數據需要序列化並通過線路發送時,將其轉換為 JSON。

Django REST 框架的 ModelSerializer 最適合簡單對象。舉個例子,假設我們在 Task 對象上沒有 ForeignKey。我們可以為 Task 創建一個序列化器,它將根據需要將其欄位值轉換為 JSON,聲明如下:

# todo/serializers.py
from rest_framework import serializers
from todo.models import Task

class TaskSerializer(serializers.ModelSerializer):
    """Serializer for the Task model."""
    class Meta:
        model = Task
        fields = (&apos;id&apos;, &apos;name&apos;, &apos;note&apos;, &apos;creation_date&apos;, &apos;due_date&apos;, &apos;completed&apos;)

在我們新的 TaskSerializer 中,我們創建了一個 Meta 類。Meta 的工作就是保存關於我們試圖序列化的東西的信息(或元數據)。然後,我們會注意到要顯示的特定欄位。如果我們想要顯示所有欄位,我們可以簡化過程並使用 __all __。或者,我們可以使用 exclude 關鍵字而不是 fields 來告訴 Django REST 框架我們想要除了少數幾個欄位以外的每個欄位。我們可以擁有儘可能多的序列化器,所以也許我們想要一個用於一小部分欄位,而另一個用於所有欄位?在這裡都可以。

在我們的例子中,每個 Task 和它的所有者 Owner 之間都有一個關係,必須在這裡反映出來。因此,我們需要借用 serializers.PrimaryKeyRelatedField 對象來指定每個 Task 都有一個 Owner,並且該關係是一對一的。它的所有者將從已有的全部所有者的集合中找到。我們通過對這些所有者進行查詢並返回我們想要與此序列化程序關聯的結果來獲得該集合:Owner.objects.all()。我們還需要在欄位列表中包含 owner,因為我們總是需要一個與 Task 相關聯的 Owner

# todo/serializers.py
from rest_framework import serializers
from todo.models import Task
from owner.models import Owner

class TaskSerializer(serializers.ModelSerializer):
    """Serializer for the Task model."""
    owner = serializers.PrimaryKeyRelatedField(queryset=Owner.objects.all())

    class Meta:
        model = Task
        fields = (&apos;id&apos;, &apos;name&apos;, &apos;note&apos;, &apos;creation_date&apos;, &apos;due_date&apos;, &apos;completed&apos;, &apos;owner&apos;)

現在構建了這個序列化器,我們可以將它用於我們想要為我們的對象做的所有 CRUD 操作:

  • 如果我們想要 GET 一個特定的 Task 的 JSON 類型版本,我們可以做 TaskSerializer((some_task).data
  • 如果我們想接受帶有適當數據的 POST 來創建一個新的 Task,我們可以使用 TaskSerializer(data = new_data).save()
  • 如果我們想用 PUT 更新一些現有數據,我們可以用 TaskSerializer(existing_task, data = data).save()

我們沒有包括 delete,因為我們不需要對 delete 操作做任何事情。如果你可以刪除一個對象,只需使用 object_instance.delete()

以下是一些序列化數據的示例:

>>> from todo.models import Task
>>> from todo.serializers import TaskSerializer
>>> from owner.models import Owner
>>> from django.contrib.auth.models import User
>>> new_user = User(username=&apos;kenyatta&apos;, email=&apos;kenyatta@gmail.com&apos;)
>>> new_user.save_password(&apos;wakandaforever&apos;)
>>> new_user.save() # creating the User that builds the Owner
>>> kenyatta = Owner.objects.first() # 找到 kenyatta 的所有者
>>> new_task = Task(name="Buy roast beef for the Sunday potluck", owner=kenyatta)
>>> new_task.save()
>>> TaskSerializer(new_task).data
{&apos;id&apos;: 1, &apos;name&apos;: &apos;Go to the supermarket&apos;, &apos;note&apos;: None, &apos;creation_date&apos;: &apos;2018-07-31T06:00:25.165013Z&apos;, &apos;due_date&apos;: None, &apos;completed&apos;: False, &apos;owner&apos;: 1}

使用 ModelSerializer 對象可以做更多的事情,我建議查看文檔以獲得更強大的功能。否則,這就是我們所需要的。現在是時候深入視圖了。

查看視圖

我們已經構建了模型和序列化器,現在我們需要為我們的應用程序設置視圖和 URL。畢竟,對於沒有視圖的應用程序,我們無法做任何事情。我們已經看到了上面的 HelloWorld 視圖的示例。然而,這總是一個人為的、概念驗證的例子,並沒有真正展示 Django REST 框架的視圖可以做些什麼。讓我們清除 HelloWorld 視圖和 URL,這樣我們就可以從我們的視圖重新開始。

我們要構建的第一個視圖是 InfoView。與之前的框架一樣,我們只想打包並發送一個我們用到的路由的字典。視圖本身可以存在於 django_todo.views 中,因為它與特定模型無關(因此在概念上不屬於特定應用程序)。

# django_todo/views.py
from rest_framework.response import JsonResponse
from rest_framework.views import APIView

class InfoView(APIView):
    """List of routes for this API."""
    def get(self, request):
        output = {
            &apos;info&apos;: &apos;GET /api/v1&apos;,
            &apos;register&apos;: &apos;POST /api/v1/accounts&apos;,
            &apos;single profile detail&apos;: &apos;GET /api/v1/accounts/<username>&apos;,
            &apos;edit profile&apos;: &apos;PUT /api/v1/accounts/<username>&apos;,
            &apos;delete profile&apos;: &apos;DELETE /api/v1/accounts/<username>&apos;,
            &apos;login&apos;: &apos;POST /api/v1/accounts/login&apos;,
            &apos;logout&apos;: &apos;GET /api/v1/accounts/logout&apos;,
            "user&apos;s tasks": &apos;GET /api/v1/accounts/<username>/tasks&apos;,
            "create task": &apos;POST /api/v1/accounts/<username>/tasks&apos;,
            "task detail": &apos;GET /api/v1/accounts/<username>/tasks/<id>&apos;,
            "task update": &apos;PUT /api/v1/accounts/<username>/tasks/<id>&apos;,
            "delete task": &apos;DELETE /api/v1/accounts/<username>/tasks/<id>&apos;
        }
        return JsonResponse(output)

這與我們在 Tornado 中所擁有的完全相同。讓我們將它放置到合適的路由並繼續。為了更好的測試,我們還將刪除 admin/ 路由,因為我們不會在這裡使用 Django 管理後端。

# in django_todo/urls.py
from django_todo.views import InfoView
from django.urls import path

urlpatterns = [
    path(&apos;api/v1&apos;, InfoView.as_view(), name="info"),
]

連接模型與視圖

讓我們弄清楚下一個 URL,它將是創建新的 Task 或列出用戶現有任務的入口。這應該存在於 todo 應用程序的 urls.py 中,因為它必須專門處理 Task對象而不是整個項目的一部分。

# in todo/urls.py
from django.urls import path
from todo.views import TaskListView

urlpatterns = [
    path(&apos;&apos;, TaskListView.as_view(), name="list_tasks")
]

這個路由處理的是什麼?我們根本沒有指定特定用戶或路徑。由於會有一些路由需要基本路徑 /api/v1/accounts/<username>/tasks,為什麼我們只需寫一次就能一次又一次地寫它?

Django 允許我們用一整套 URL 並將它們導入 django_todo/urls.py 文件。然後,我們可以為這些導入的 URL 中的每一個提供相同的基本路徑,只關心可變部分,你知道它們是不同的。

# in django_todo/urls.py
from django.urls import include, path
from django_todo.views import InfoView

urlpatterns = [
    path(&apos;api/v1&apos;, InfoView.as_view(), name="info"),
    path(&apos;api/v1/accounts/<str:username>/tasks&apos;, include(&apos;todo.urls&apos;))
]

現在,來自 todo/urls.py 的每個 URL 都將以路徑 api/v1/accounts/<str:username>/tasks 為前綴。

讓我們在 todo/views.py 中構建視圖。

# todo/views.py
from django.shortcuts import get_object_or_404
from rest_framework.response import JsonResponse
from rest_framework.views import APIView

from owner.models import Owner
from todo.models import Task
from todo.serializers import TaskSerializer

class TaskListView(APIView):
    def get(self, request, username, format=None):
        """Get all of the tasks for a given user."""
        owner = get_object_or_404(Owner, user__username=username)
        tasks = Task.objects.filter(owner=owner).all()
        serialized = TaskSerializer(tasks, many=True)
        return JsonResponse({
            &apos;username&apos;: username,
            &apos;tasks&apos;: serialized.data
        })

這裡一點代碼裡面有許多要說明的,讓我們來看看吧。

我們從與我們一直使用的 APIView 的繼承開始,為我們的視圖奠定基礎。我們覆蓋了之前覆蓋的相同 get 方法,添加了一個參數,允許我們的視圖從傳入的請求中接收 username

然後我們的 get 方法將使用 username 來獲取與該用戶關聯的 Owner。這個 get_object_or_404 函數允許我們這樣做,添加一些特殊的東西以方便使用。

如果無法找到指定的用戶,那麼查找任務是沒有意義的。實際上,我們想要返回 404 錯誤。get_object_or_404 根據我們傳入的任何條件獲取單個對象,並返回該對象或引發 Http 404 異常。我們可以根據對象的屬性設置該條件。Owner 對象都通過 user 屬性附加到 User。但是,我們沒有要搜索的 User 對象,我們只有一個 username。所以,當你尋找一個 Owner 時,我們對 get_object_or_404 說:通過指定 user__username(這是兩個下劃線)來檢查附加到它的 User 是否具有我想要的 username。通過 QuerySet 過濾時,這兩個下劃線表示 「此嵌套對象的屬性」。這些屬性可以根據需要進行深度嵌套。

我們現在擁有與給定用戶名相對應的 Owner。我們使用 Owner 來過濾所有任務,只用 Task.objects.filter 檢索它擁有的任務。我們可以使用與 get_object_or_404 相同的嵌套屬性模式來鑽入連接到 TasksOwnerUsertasks = Task.objects.filter(owner__user__username = username)).all()),但是沒有必要那麼寬鬆。

Task.objects.filter(owner = owner).all() 將為我們提供與我們的查詢匹配的所有 Task 對象的QuerySet。很棒。然後,TaskSerializer 將獲取 QuerySet 及其所有數據以及 many = True 標誌,以通知其為項目集合而不是僅僅一個項目,並返回一系列序列化結果。實際上是一個詞典列表。最後,我們使用 JSON 序列化數據和用於查詢的用戶名提供傳出響應。

處理 POST 請求

post 方法看起來與我們之前看到的有些不同。

# still in todo/views.py
# ...other imports...
from rest_framework.parsers import JSONParser
from datetime import datetime

class TaskListView(APIView):
    def get(self, request, username, format=None):
        ...

    def post(self, request, username, format=None):
        """Create a new Task."""
        owner = get_object_or_404(Owner, user__username=username)
        data = JSONParser().parse(request)
        data[&apos;owner&apos;] = owner.id
        if data[&apos;due_date&apos;]:
            data[&apos;due_date&apos;] = datetime.strptime(data[&apos;due_date&apos;], &apos;%d/%m/%Y %H:%M:%S&apos;)

        new_task = TaskSerializer(data=data)
        if new_task.is_valid():
            new_task.save()
            return JsonResponse({&apos;msg&apos;: &apos;posted&apos;}, status=201)

        return JsonResponse(new_task.errors, status=400)

當我們從客戶端接收數據時,我們使用 JSONParser().parse(request) 將其解析為字典。我們將所有者添加到數據中並格式化任務的 due_date(如果存在)。

我們的 TaskSerializer 完成了繁重的任務。它首先接收傳入的數據並將其轉換為我們在模型上指定的欄位。然後驗證該數據以確保它適合指定的欄位。如果附加到新 Task 的數據有效,它將使用該數據構造一個新的 Task 對象並將其提交給資料庫。然後我們發回適當的「耶!我們做了一件新東西!」響應。如果沒有,我們收集 TaskSerializer 生成的錯誤,並將這些錯誤發送回客戶端,並返回 400 Bad Request 狀態代碼。

如果我們要構建 put 視圖來更新 Task,它看起來會非常相似。主要區別在於,當我們實例化 TaskSerializer 時,我們將傳遞舊對象和該對象的新數據,如 TaskSerializer(existing_task,data = data)。我們仍然會進行有效性檢查並發回我們想要發回的響應。

總結

Django 作為一個框架是高度可定製的,每個人都有自己打造 Django 項目的方式。我在這裡寫出來的方式不一定是 Django 建立項目的確切方式。它只是 a) 我熟悉的方式,以及 b) 利用 Django 的管理系統。當你將概念切分到不同的小塊時,Django 項目的複雜性會增加。這樣做是為了讓多個人更容易為整個項目做出貢獻,而不會麻煩彼此。

然而,作為 Django 項目的大量文件映射並不能使其更高效或自然地偏向於微服務架構。相反,它很容易成為一個令人困惑的獨石應用,這可能對你的項目仍然有用,它也可能使你的項目難以管理,尤其是隨著項目的增長。

仔細考慮你的需求並使用合適的工具來完成正確的工作。對於像這樣的簡單項目,Django 可能不是合適的工具。

Django 旨在處理多種模型,這些模型涵蓋了不同的項目領域,但它們可能有一些共同點。這個項目是一個小型的雙模型項目,有一些路由。即便我們把它構建更複雜,也只有七條路由,而仍然只是相同的兩個模型。這還不足以證明一個完整的 Django 項目。

如果我們期望這個項目能夠拓展,那麼將會是一個很好的選擇。這不是那種項目。這就是使用火焰噴射器來點燃蠟燭,絕對是大材小用了。

儘管如此,Web 框架就是一個 Web 框架,無論你使用哪個框架。它都可以接收請求並做出任何響應,因此你可以按照自己的意願進行操作。只需要注意你選擇的框架所帶來的開銷。

就是這樣!我們已經到了這個系列的最後!我希望這是一次啟發性的冒險。當你在考慮如何構建你的下一個項目時,它將幫助你做出的不僅僅是最熟悉的選擇。請務必閱讀每個框架的文檔,以擴展本系列中涉及的任何內容(因為它沒有那麼全面)。每個人都有一個廣闊的世界。愉快地寫代碼吧!

via: https://opensource.com/article/18/8/django-framework

作者:Nicholas Hunt-Walker 選題:lujun9972 譯者:MjSeven 校對:Bestony, wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國