撰寫您的第一個 Django 應用程式,第 2 部分

本教學課程從教學課程 1結束的地方開始。我們將設定資料庫、建立您的第一個模型,並快速介紹 Django 自動產生的管理網站。

在哪裡取得協助

如果您在進行本教學課程時遇到問題,請前往常見問題的取得協助章節。

資料庫設定

現在,開啟 mysite/settings.py。它是一個普通的 Python 模組,其中包含代表 Django 設定的模組級變數。

預設情況下,DATABASES 設定使用 SQLite。如果您是資料庫新手,或者只是想試用 Django,這是最簡單的選擇。SQLite 包含在 Python 中,因此您無需安裝任何其他東西來支援您的資料庫。但是,當您開始第一個真實專案時,您可能需要使用更具擴展性的資料庫(如 PostgreSQL),以避免日後切換資料庫的麻煩。

如果您想使用其他資料庫,請參閱自訂並執行資料庫的詳細資訊

當您正在編輯 mysite/settings.py 時,將 TIME_ZONE 設定為您的時區。

另外,請注意檔案頂端的 INSTALLED_APPS 設定。它保留了在此 Django 實例中啟用的所有 Django 應用程式的名稱。應用程式可以在多個專案中使用,您可以將它們打包並分發以供其他人在其專案中使用。

預設情況下,INSTALLED_APPS 包含以下應用程式,這些應用程式都隨 Django 提供

預設情況下包含這些應用程式,以方便常見的情況。

但是,其中一些應用程式至少使用一個資料庫表,因此我們需要在可以使用它們之前在資料庫中建立表。為此,請執行以下命令

$ python manage.py migrate
...\> py manage.py migrate

migrate 命令會查看 INSTALLED_APPS 設定,並根據 mysite/settings.py 檔案中的資料庫設定和應用程式隨附的資料庫遷移(我們稍後會介紹)建立任何必要的資料庫表。您將看到其套用的每個遷移的訊息。如果您有興趣,請執行資料庫的命令列用戶端,然後輸入 \dt (PostgreSQL)、SHOW TABLES; (MariaDB、MySQL)、.tables (SQLite) 或 SELECT TABLE_NAME FROM USER_TABLES; (Oracle) 來顯示 Django 建立的表。

對於極簡主義者

就像我們上面說的,預設應用程式包含在常見情況下,但並非每個人都需要它們。如果您不需要它們中的任何或全部,請隨意在執行 migrate 之前從 INSTALLED_APPS 中註解或刪除適當的行。 migrate 命令只會執行 INSTALLED_APPS 中應用程式的遷移。

建立模型

現在,我們將定義您的模型 – 本質上是您的資料庫佈局,以及其他中繼資料。

哲學

模型是關於您的資料的單一、明確的資訊來源。它包含您儲存的資料的基本欄位和行為。Django 遵循DRY 原則。目標是在一個地方定義您的資料模型,並從中自動衍生事物。

這包括遷移 - 例如,與 Ruby On Rails 不同,遷移完全是從您的模型檔案中衍生而來,並且本質上是 Django 可以滾動以更新您的資料庫結構來符合您當前模型的歷史記錄。

在我們的投票應用程式中,我們將建立兩個模型:QuestionChoiceQuestion 有一個問題和發佈日期。Choice 有兩個欄位:選項的文字和投票總數。每個 Choice 都與一個 Question 相關聯。

這些概念由 Python 類別表示。編輯 polls/models.py 檔案,使其如下所示

polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

在這裡,每個模型都由一個繼承 django.db.models.Model 的類別表示。每個模型都有許多類別變數,每個變數都代表模型中的資料庫欄位。

每個欄位都由 Field 類別的實例表示 – 例如,字元欄位的 CharField 和日期時間的 DateTimeField。這會告訴 Django 每個欄位保留什麼類型的資料。

每個 Field 實例的名稱(例如 question_textpub_date)是欄位的名稱,採用機器友好的格式。您將在您的 Python 程式碼中使用此值,並且您的資料庫將使用它作為欄名稱。

您可以使用 Field 的選用第一個位置引數來指定人類可讀的名稱。這會在 Django 的幾個自省部分中使用,並且兼作文件。如果未提供此欄位,Django 將使用機器可讀的名稱。在本例中,我們僅為 Question.pub_date 定義了人類可讀的名稱。對於此模型中的所有其他欄位,欄位的機器可讀名稱足以作為其人類可讀名稱。

某些 Field 類別具有必要的引數。例如,CharField 要求您給它一個 max_length。這不僅在資料庫結構中使用,而且在驗證中使用,我們很快就會看到。

Field 也可以有各種選用引數;在本例中,我們將 votesdefault 值設定為 0。

最後,請注意使用 ForeignKey 定義了關係。這告訴 Django 每個 Choice 都與一個 Question 相關。Django 支援所有常見的資料庫關係:多對一、多對多和一對一。

啟用模型

這一小段模型程式碼為 Django 提供了大量資訊。有了它,Django 能夠

  • 為此應用程式建立資料庫綱要(CREATE TABLE 陳述式)。

  • 建立一個 Python 資料庫存取 API,用於存取 QuestionChoice 物件。

但首先我們需要告訴專案已安裝 polls 應用程式。

哲學

Django 應用程式是「可插拔」的:您可以在多個專案中使用一個應用程式,並且可以散佈應用程式,因為它們不必與特定的 Django 安裝綁定。

若要將應用程式包含在專案中,我們需要在 INSTALLED_APPS 設定中新增對其組態類別的參考。 PollsConfig 類別位於 polls/apps.py 檔案中,因此其點狀路徑為 'polls.apps.PollsConfig'。編輯 mysite/settings.py 檔案,並將該點狀路徑新增至 INSTALLED_APPS 設定。它看起來會像這樣

mysite/settings.py
INSTALLED_APPS = [
    "polls.apps.PollsConfig",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

現在 Django 知道要包含 polls 應用程式。讓我們執行另一個命令

$ python manage.py makemigrations polls
...\> py manage.py makemigrations polls

您應該會看到類似以下的內容

Migrations for 'polls':
  polls/migrations/0001_initial.py
    + Create model Question
    + Create model Choice

執行 makemigrations,您是在告訴 Django 您對模型進行了一些變更(在此情況下,您建立了新的模型),並且您希望將這些變更儲存為遷移

遷移是 Django 儲存模型變更(以及資料庫綱要)的方式 - 它們是磁碟上的檔案。 如果您願意,可以閱讀新模型的遷移檔案;它是 polls/migrations/0001_initial.py 檔案。 別擔心,您不需要每次 Django 建立遷移檔案時都閱讀它們,但它們被設計為可由人類編輯,以防您想手動調整 Django 變更內容的方式。

有一個命令可以為您執行遷移並自動管理您的資料庫綱要 - 這稱為 migrate,我們稍後會介紹它 - 但首先,讓我們看看該遷移會執行哪些 SQL。 sqlmigrate 命令會接受遷移名稱並傳回它們的 SQL

$ python manage.py sqlmigrate polls 0001
...\> py manage.py sqlmigrate polls 0001

您應該會看到類似以下的內容(我們已重新格式化以提高可讀性)

BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL,
    "question_id" bigint NOT NULL
);
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");

COMMIT;

請注意以下幾點

  • 確切的輸出會因您使用的資料庫而異。上面的範例是為 PostgreSQL 生成的。

  • 表名是透過組合應用程式的名稱(polls)和模型的小寫名稱(questionchoice)自動產生的。(您可以覆寫此行為。)

  • 主鍵 (ID) 會自動新增。(您也可以覆寫此行為。)

  • 依照慣例,Django 會將 "_id" 附加到外鍵欄位名稱。(是的,您也可以覆寫此行為。)

  • 外鍵關係是透過 FOREIGN KEY 約束明確表示的。不必擔心 DEFERRABLE 部分;它是在告訴 PostgreSQL 在交易結束之前不要強制執行外鍵。

  • 它會根據您使用的資料庫進行調整,因此會自動處理特定於資料庫的欄位類型,例如 auto_increment (MySQL)、bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (PostgreSQL) 或 integer primary key autoincrement (SQLite)。欄位名稱的引號也是如此 - 例如,使用雙引號或單引號。

  • sqlmigrate 命令實際上不會在您的資料庫上執行遷移 - 而是將其列印到螢幕上,以便您可以查看 Django 認為需要哪些 SQL。這對於檢查 Django 將要執行的操作,或者您是否有需要 SQL 指令碼的資料庫管理員來說很有用。

如果您有興趣,您也可以執行 python manage.py check;這會檢查專案中的任何問題,而無需進行遷移或接觸資料庫。

現在,再次執行 migrate,以在您的資料庫中建立這些模型表

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK
...\> py manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK

migrate 命令會採用所有尚未套用的遷移(Django 會使用資料庫中一個名為 django_migrations 的特殊表來追蹤已套用的遷移),並針對您的資料庫執行它們 - 本質上是將您對模型所做的變更與資料庫中的綱要同步。

遷移非常強大,可讓您在開發專案時隨著時間的推移變更模型,而無需刪除資料庫或資料表並建立新的資料庫或資料表 - 它專門用於即時升級資料庫,而不會遺失資料。 我們將在本教學的後續部分更詳細地介紹它們,但現在,請記住進行模型變更的三步驟指南

之所以使用單獨的命令來建立和套用遷移,是因為您會將遷移提交到您的版本控制系統並與您的應用程式一起傳送;它們不僅讓您的開發更輕鬆,還可以供其他開發人員使用,並在生產環境中使用。

請閱讀 django-admin 文件,以取得有關 manage.py 公用程式可以執行的完整資訊。

使用 API

現在,讓我們進入互動式 Python Shell,並試用 Django 為您提供的免費 API。若要叫用 Python Shell,請使用此命令

$ python manage.py shell
...\> py manage.py shell

我們在這裡使用這個而不是僅僅輸入「python」,是因為 manage.py 設定了 DJANGO_SETTINGS_MODULE 環境變數,這會提供 Django 您 mysite/settings.py 檔案的 Python 匯入路徑。

進入 Shell 後,請瀏覽 資料庫 API

>>> from polls.models import Choice, Question  # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

等一下。 <Question: Question object (1)> 並不是這個物件的有用表示法。 讓我們透過編輯 Question 模型(在 polls/models.py 檔案中)並將 __str__() 方法新增至 QuestionChoice 來修正此問題

polls/models.py
from django.db import models


class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text


class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

在您的模型中新增 __str__() 方法非常重要,不僅僅是為了方便您自己在處理互動式提示時,而且因為物件的表示法會用於整個 Django 自動產生的管理介面。

我們也在此模型中新增一個自訂方法

polls/models.py
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

請注意新增的 import datetimefrom django.utils import timezone,以分別參考 Python 的標準 datetime 模組和 Django 在 django.utils.timezone 中的時區相關公用程式。 如果您不熟悉 Python 中的時區處理,可以在 時區支援文件中了解更多資訊。

儲存這些變更,然後再次執行 python manage.py shell 以啟動新的 Python 互動式 Shell

>>> from polls.models import Choice, Question

# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith="What")
<QuerySet [<Question: What's up?>]>

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set (defined as "choice_set") to hold the "other side" of a ForeignKey
# relation (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three choices.
>>> q.choice_set.create(choice_text="Not much", votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text="The sky", votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text="Just hacking again", votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith="Just hacking")
>>> c.delete()

關於模型關聯的更多資訊,請參閱存取相關物件。關於如何使用雙底線透過 API 執行欄位查詢的更多資訊,請參閱欄位查詢。關於資料庫 API 的完整細節,請參閱我們的資料庫 API 參考

Django 管理介面簡介

哲學

為您的員工或客戶產生新增、變更和刪除內容的管理網站,是一項繁瑣且不需要太多創意的工作。因此,Django 完全自動化了模型管理介面的建立。

Django 是在新聞編輯室環境中撰寫的,內容發布者」和「公眾」網站之間有非常明確的分隔。網站管理員使用該系統來新增新聞報導、事件、體育賽事比分等,這些內容會顯示在公眾網站上。Django 解決了為網站管理員建立統一介面來編輯內容的問題。

管理介面並非設計給網站訪客使用。它是給網站管理員使用的。

建立管理員使用者

首先,我們需要建立一個可以登入管理網站的使用者。執行以下指令

$ python manage.py createsuperuser
...\> py manage.py createsuperuser

輸入您想要的的使用者名稱,然後按下 Enter 鍵。

Username: admin

接著系統會提示您輸入想要的電子郵件地址

Email address: admin@example.com

最後一步是輸入您的密碼。系統會要求您輸入密碼兩次,第二次是為了確認第一次輸入的密碼。

Password: **********
Password (again): *********
Superuser created successfully.

啟動開發伺服器

Django 管理網站預設為啟用狀態。讓我們啟動開發伺服器並探索它。

如果伺服器尚未啟動,請像這樣啟動它

$ python manage.py runserver
...\> py manage.py runserver

現在,開啟網頁瀏覽器,並前往您本機網域的「/admin/」路徑,例如 http://127.0.0.1:8000/admin/。您應該會看到管理介面的登入畫面

Django admin login screen

由於翻譯預設為開啟,如果您設定了LANGUAGE_CODE,登入畫面將會以指定的語言顯示(如果 Django 有適當的翻譯)。

進入管理網站

現在,嘗試使用您在上一步中建立的超級使用者帳戶登入。您應該會看到 Django 管理索引頁面

Django admin index page

您應該會看到幾種可編輯的內容類型:群組和使用者。它們是由 django.contrib.auth 所提供,這是 Django 內建的身份驗證框架。

讓投票應用程式在管理介面中可修改

但是我們的投票應用程式在哪裡呢?它沒有顯示在管理索引頁面上。

只需要再做一件事:我們需要告訴管理介面 Question 物件具有管理介面。要做到這一點,請開啟 polls/admin.py 檔案,並編輯成如下所示

polls/admin.py
from django.contrib import admin

from .models import Question

admin.site.register(Question)

探索免費的管理功能

現在我們已經註冊了 Question,Django 知道它應該顯示在管理索引頁面上

Django admin index page, now with polls displayed

點擊「Questions」。現在您位於問題的「變更列表」頁面。此頁面會顯示資料庫中的所有問題,並讓您選擇一個來變更它。您會看到我們稍早建立的「What's up?」問題。

Polls change list page

點擊「What’s up?」問題來編輯它

Editing form for question object

這裡有幾點需要注意

  • 表單是根據 Question 模型自動產生的。

  • 不同的模型欄位類型(DateTimeFieldCharField)對應到適當的 HTML 輸入小工具。每種類型的欄位都知道如何在 Django 管理介面中顯示自己。

  • 每個 DateTimeField 都會取得免費的 JavaScript 捷徑。日期會取得「今天」捷徑和日曆彈出視窗,而時間會取得「現在」捷徑和一個方便的彈出視窗,其中列出了常用的輸入時間。

頁面底部為您提供幾個選項

  • 儲存 – 儲存變更並返回此類物件的變更列表頁面。

  • 儲存並繼續編輯 – 儲存變更並重新載入此物件的管理頁面。

  • 儲存並新增另一個 – 儲存變更並載入此類型物件的新空白表單。

  • 刪除 – 顯示刪除確認頁面。

如果「發佈日期」的值與您在教學 1中建立問題的時間不符,這可能表示您忘記為 TIME_ZONE 設定設定正確的值。變更它,重新載入頁面並檢查是否顯示正確的值。

透過點擊「今天」和「現在」捷徑來變更「發佈日期」。然後點擊「儲存並繼續編輯」。然後點擊右上角的「歷史記錄」。您會看到一個頁面,其中列出透過 Django 管理介面對此物件所做的所有變更,以及進行變更的人的時間戳記和使用者名稱

History page for question object

當您熟悉模型 API 並熟悉管理網站後,請閱讀本教學的第 3 部分,以了解如何為我們的投票應用程式新增更多檢視。

返回頂部