伺服器端介紹 - 學習該如何開發 Web

伺服器端介紹
======

* Overview: First steps
* 次頁  (en-US)

歡迎來到 MDN 伺服器端程式設計的初學者課程 ！在第一篇文章中，我們會用較為抽象的角度來探討 server-side programming，並且為你解答「這是什麼？」「這個和用戶端的程式有什麼不同？」以及「這個有什麼用？」 。在讀完這篇文章後，你將能明白如何透過 server-side coding 來為你的網站增添力量。

|  |  |
| --- | --- |
| 先決條件: | 基本電腦知識、對網路伺服器有基本了解。 |
| 目標: | 認識伺服器端的程式設計、它可以做什麼、它和用戶端的程式有什麼不一樣？ |

大多數的大型網站使用伺服器端程式（server-side code）來動態地顯示各種所需的資料，普遍的做法為從伺服端的資料庫中取出資料，並送至用戶端，再透過一些 code 來顯示它們（例如：HTML 與 JavaScript）。

也許，使用伺服器端程式的最大好處是為不同的瀏覽者量身打造網頁內容。動態網站根據使用者的偏好設定及興趣提供更為相關的內容，也可以儲存個人設定及資訊讓網站更易於使用 — 例如重複使用已儲存的信用卡資料來使付款流程更為順暢。

它也能讓網站透過信件或其他方式來和使用者互動，如發送通知與更新。這一切的一切都讓網站更能牢牢抓住使用者的心。

何謂伺服器端網站程式開發？
-------------

網頁瀏覽器使用超文本傳輸協定（**H**yper**T**ext **T**ransfer **P**rotocol, HTTP）與網頁伺服器（web servers）溝通。當你點選網頁上的連結、送出表單，或者執行搜尋，一段 **HTTP 請求**（**request**）會由你的瀏覽器送至目標伺服器。

該請求（request）包含一個用來指定受影響資源的 URL、一個定義行為的請求方法（例如對資源進行 get、delete 或 post）與當進行HTTP POST 方法 (en-US)時可能包含編碼於 URL 參數中的額外資訊（經由一段查詢字串送出的各個鍵值對），或是在關聯的cookies中。

網頁伺服器等待用戶端的請求訊息、獲得後處理它們，並以一個**HTTP 回應**（**response**）訊息回覆至網頁瀏覽器。該回應包含一個狀態訊息說明本次請求是否達成（例如："HTTP/1.1 200 OK"表示成功）。

成功對應於一個請求的回應主體（response body）應包含請求的資源（例如：一份新的 HTML 頁面或一張圖片等），這些可能將被用來顯示在網頁瀏覽器中。

### 靜態網站

以下的靜態網站（static site）圖展示一個基本的網頁伺服器架構，其中靜態網站意謂當無論何時有個特定資源的請求，伺服器始終回傳相同的硬編碼內容（hard-coded content）。當一個使用者想要引導到一個網頁時，瀏覽器送出的 HTTP "GET" 請求指的就是該資源的 URL。

此伺服器從它的檔案系統取回被請求的文件，並回傳一個包含此文件以及成功狀態碼（通常為 200 OK）的 HTTP 回應。若檔案因某些原因無法被取回，則回傳一個錯誤狀態（參見 用戶端錯誤回應 與 伺服器端錯誤回應）。

![A simplified diagram of a static web server.](/zh-TW/docs/Learn/Server-side/First_steps/Introduction/basic_static_app_server.png)

### 動態網站

一個動態網站的回應內容是當需要時*動態*產生的。在一個動態網站的 HTML 網頁通常是經由資料庫取得並插入資料至 HTML 範本的佔位符（placeholders）中而創造出來（相較於靜態網站，這對於儲存大量內容而言，這是一種相當有效率的做法）。

一個動態網站可以根據使用者或已存偏好設定提供的 URL 資訊回傳不同的資料，也可以以其他的作用方式呈現回應（例如：發送通知）。

用來支援一個動態網站的大部分的程式碼必須在伺服器執行。建立程式碼的方式稱為＂**伺服端程式設計（server-side programming）**＂或＂**後端腳本（back-end scripting）**＂。

下圖為*動態網站*（*dynamic website*）的基本架構。如同先前的圖說，瀏覽器發送 HTTP 請求至伺服器，接著伺服器處理請求後，回傳合適的 HTTP 回應。

對於*靜態*資源的請求處理方式如同靜態網站的方式（靜態資源為任何不會改變的檔案 — 通常為 CSS、JavaScript、圖片、預產生的 PDF 檔案等）。

![A simplified diagram of a web server that uses server-side programming to get information from a database and construct HTML from templates. This is the same diagram as is in the Client-Server overview.](/zh-TW/docs/Learn/Server-side/First_steps/Introduction/web_application_with_html_and_steps.png)

對於動態資源的請求方式則為轉送(2)至伺服端程式碼（如圖中的*網頁應用程式 Web Application*）。對於＂動態請求＂，伺服器解釋該請求、從資料庫讀取所需資訊(3)、與 HTML 範本結合取得的資料(4)，最後送回一個包含已產生 HTML 的回應(5,6)。

伺服端與用戶端程式設計是相同的嗎？
-----------------

讓我們把注意力集中在伺服端與用戶端的程式設計吧！在以下的每個案例中，程式碼完全不一樣：

* 他們有不同的目的與考量。
* 一般而言，他們不使用相同的程式語言（除了 JavaScript 可以使用在伺服端與用戶端以外）。
* 他們在不同的作業系統環境下執行。

執行在瀏覽器的程式碼被稱為**用戶端程式碼**（**client-side code**），它主要用來改善一個渲染的網頁外觀與行為。這包含選取與設定 UI 元件樣式、建立佈局、導覽、表格驗證等。相對的，伺服端網站程式設計大量涉及要回傳*哪些內容*給瀏覽器做為對請求的回應。伺服端程式碼處理驗證已提交的資料與請求、使用資料庫儲存及取得資料，和按需求發送正確的資料給用戶等任務。

用戶端程式碼以HTML、CSS與JavaScript撰寫 — 它執行在網頁瀏覽器內，並且僅有或無訪問底層的作業系統（包含對檔案系統的有限存取）。

網頁開發者不能控制使用者可能使用何種瀏覽器來檢視一個網站 — 瀏覽器與用戶端程式有著不同層度的相容性，並且用戶端程式的挑戰之一是如何妥善地處理瀏覽器支援的差異。

伺服端程式碼可以為任何程式語言 — 例如有名的伺服端網頁語言包括 PHP、Python、Ruby、C#與 NodeJS(JavaScript)。該伺服端程式碼擁有完整的作業系統存取權限，而且開發者能夠選擇他們想要的程式語言（以及特定版本）。

開發者們通常使用**網頁框架**（**web frameworks**）撰寫程式碼。網頁框架為功能函式、物件、規則與其他程式碼的集合，旨在解決常見問題、加速開發並簡化在特定域中面臨到的不同類型的任務。

再者，儘管用戶端與伺服端程式碼都使用框架，但會因為非常不同的域，而使得框架也不同。用戶端網頁框架簡化佈局與呈現的任務，而伺服端網頁框架則提供大量＂通用＂的網頁伺服器功能，否則你可能必須要自己實現（例如：對 sessions 的支援、對使用者認證的支援、簡易資料庫存取、樣板庫等）。

**備註：** Client-side frameworks are often used to help speed up development of client-side code, but you can also choose to write all the code by hand; in fact, writing your code by hand can be quicker and more efficient if you only need a small, simple web site UI.

In contrast, you would almost never consider writing the server-side component of a web app without a framework — implementing a vital feature like an HTTP server is really hard to do from scratch in say Python, but Python web frameworks like Django provide one out of the box, along with other very useful tools.

在伺服端，你能做什麼？
-----------

伺服端程式設計是非常有用的，因為它讓我們有效地遞送替單個使用者量身訂做的資訊，從而創造更棒的使用者體驗。

如 Amazon 這樣的公司使用伺服端程式設計來建構產品搜尋結果、根據顧客偏好與過往購物習慣提供針對性的產品建議、簡化購物過程等。

銀行使用伺服端程式設計來儲存帳號資訊，並讓已授權用戶檢視與進行交易。其他服務如 Facebook、Twitter、Instagram 與 Wikipedia 使用伺服端程式設計來突顯、分享與控制使用者存取到感興趣的內容。

一些常見的伺服端程式設計使用案例與效益列舉如下。你將會注意到這當中會有些重疊的部分！

### 高效率資訊儲存與遞送

想像一下，在 Amazon 可以找到多少產品，或者說在 Facebook 上有多少文章？對各個產品或文章建立各別的靜態網頁完全是不切實際的。

伺服端程式設計反而是可以讓我們將資訊儲存至資料庫，並且動態建構及回傳 HTML 與其他型態的檔案（例如：PDF、圖片等）。它也可以藉由合適的用戶端網頁框架（利用這個方式可以降低在伺服器的處理負擔，亦減少需要被送出的大量資料）僅回傳資料（JSON、XML (en-US)等）來進行畫面渲染。

伺服器並不侷限於從資料庫發送資訊，還可以回傳軟體工具的結果或是來自通訊服務的資料。這些內容甚至可以針對到收到它的用戶裝置類型。

由於資訊存在於資料庫中，它可以輕易地與其他商業系統進行分享與更新（例如：當產品在線上或在店家中售完，店家可能會更新該產品的庫存資料庫）。

**備註：** Your imagination doesn't have to work hard to see the benefit of server-side code for efficient storage and delivery of information:

1. Go to Amazon or some other e-commerce site.
2. Search for a number of keywords and note how the page structure doesn't change, even though the results do.
3. Open two or three different products. Note again how they have a common structure and layout, but the content for different products has been pulled from the database.

For a common search term ("fish", say) you can see literally millions of returned values. Using a database allows these to be stored and shared efficiently, and it allows the presentation of the information to be controlled in just one place.

### 客製化的使用者體驗

伺服器能保存及使用關於用戶的資訊，來提供一個方便且量身訂做的使用者體驗。例如，許多網站儲存信用卡資料讓這些資料無須再重新輸入。網站如 Google Maps 能使用已儲存或目前位置來提供導航資訊與搜尋或旅行歷史紀錄，以便於搜尋結果中突顯在地店家。

一個使用者習慣更深層的分析，可以使用在預測他的興趣以及更進一步客製回應與提醒，例如在地圖中提供你可能想去看得過去遊歷過的或是熱門的地點列表。

**備註：** Google Maps saves your search and visit history. Frequently visited or frequently searched locations are highlighted more than others.

Google search results are optimized based on previous searches.

1. Go to Google search.
2. Search for "football".
3. Now try typing "favourite" in the search box and observe the autocomplete search predictions.

Coincidence? Nada!

### 控制內容存取

伺服器端程式設計允許網站限制僅能由已授權的使用者存取，並提供資訊給那些只被允許觀看的使用者。

真實世界案例包括：

* 社交網路如 Facebook 允許使用者全權控制他們自己的資料，但是只有允許他們的朋友檢視或評論資料。該使用者決定誰能看到他們的資料，並推廣到他們的動態中會呈現誰的資料 — 授權是使用者體驗的核心部分！
* 你正所在的網站控制內容的存取：文章可被任何人看見，但是只有使用者登入才能編輯該內容。若要試試看，點擊本頁面上方的**編輯**按鈕 — 如果你已登入，你將會顯示編輯視窗；如果你沒有登入，你將會被導到註冊頁面。

**備註：** Consider other real examples where access to content is controlled. For example, what can you see if you go to the online site for your bank? Log in to your account — what additional information can you see and modify? What information can you see that only the bank can change?

### 儲存 session/state 資訊

伺服器端程式設計允許開發者利用**sessions** — 基本上，就是一個機制讓伺服器儲存目前的使用者資訊，並且基於這些資訊發送不同的回應。

例如，這允許網站了解一個使用者先前已登入過，以及將訂購歷史紀錄在他們的電子郵件中顯示連結，或者也許會儲存一個基本的遊戲狀態，讓使用者能再次回到網站的同時，拿回他們留在網站的資訊。

**備註：** Visit a newspaper site that has a subscription model and open a bunch of tabs (e.g. The Age). Continue to visit the site over a few hours/days. Eventually, you will start to be redirected to pages explaining how to subscribe, and you will be unable to access articles. This information is an example of session information stored in cookies.

### 提醒與溝通

伺服器能透過網站本身或經由電子郵件、SMS、即時通訊、影像或其他通訊服務，發送提醒訊息給一般或特定使用者。

一些範例包括：

* Facebook 與 Twitter 發送電子郵件與 SMS 訊息等新的通訊來提醒你。
* Amazon 定期發送產品電子郵件來提供相近於曾經已購買或你可能有興趣瀏覽的產品。
* 一個網頁伺服器可能會發送警告訊息給網站管理員，以警示伺服器的記憶體過低或有嫌疑的使用者型為。

**備註：** The most common type of notification is a "confirmation of registration". Pick almost any large site that you are interested in (Google, Amazon, Instagram, etc.) and create a new account using your email address. You will shortly receive an email confirming your registration, or requiring acknowledgment to activate your account.

### 資料分析

一個網站可能會收集很多包括使用者的資料：他們所搜尋的、他們所買的、他們所推薦的、他們在每個網頁停留的時間。伺服器端程式設計能根據資料分析以完善回應。

例如，Amazon 與 Google 都根據過往搜尋（與購買）紀錄來廣告產品。

**備註：** If you're a Facebook user, go to your main feed and look at the stream of posts. Note how some of the posts are out of numerical order - in particular, posts with more "likes" are often higher on the list than more recent posts.

Also look at what kind of ads you are being shown — you might see ads for things you looked at on other sites. Facebook's algorithm for highlighting content and advertising can be a bit of a mystery, but it is clear that it does depend on your likes and viewing habits!

總結
--

恭喜，你已經到達關於伺服器端程式設計的第一篇文章的結尾。

現在你已經學到伺服器端程式碼運作於網頁伺服器，他的主要任務是控制*哪些*資訊要發送給使用者（而用戶端程式碼主要掌握資料的結構與呈現給使用者）。

你也應該了解這是很有用的，當你身為伺服器端開發者時，因為它允許我們創建*有效*散播客製訊息與有些你可能會去做的好點子給單個使用者的網站。

最後，你應該了解伺服器端程式碼可以用很多種程式語言來撰寫，以及你應該使用網頁框架來讓整個程序變得更簡便。

在未來的文章，我們將協助你選擇最佳的網頁框架，做為你的第一個網站；接著，我們將帶你更詳細了解主要的用戶端-伺服端的互動。

* Overview: First steps
* 次頁  (en-US)


Django 教學 2: 創建一個骨架網站 - 學習該如何開發 Web

Django 教學 2: 創建一個骨架網站
=====================

* 前頁
* Overview: Django
* 次頁

Django 教學的第二篇文章，會展示怎樣創建一個網站的"框架"，在這個框架的基礎上，你可以繼續填充整站使用的 settings， urls，模型(models)，視圖(views)和模板(templates )。

|  |  |
| --- | --- |
| 前提: | 創建 Django 的開發環境。複習 Django 教學。 |
| 目標: | 能夠使用 Django 提供的工具，搭建你自己的網站。 |

概覽
--

這篇文章會展示怎樣創建一個網站的"框架"，在這個框架的基礎上,你可以繼續填充整站使用的 settings， urls，模型(models)，視圖(views)和模板(templates)（我們會在接下來的文章裡討論）。

搭建 「框架」 的過程很直接:

1. 使用 `django-admin`工具創建工程的文件夾，基本的文件模板和工程管理腳本（**manage.py**）。
2. 用 **manage.py** 創建一個或多個*應用*。

**備註：** 一個網站可能由多個部分組成，比如，主要頁面，博客，wiki，下載區域等。Django 鼓勵將這些部分作為分開的應用開發。如果這樣的話，在需要可以在不同的工程中復用這些應用。
3. 工程裡註冊新的應用。
4. 為每個應用分配 url。

為 locallibrary 這個項目創建的網站文件夾和它的工程文件夾都命名為*locallibrary*。我們只創建一個名為*catalog*的應用。最高層的項目文件結構如下所示：

```bash
locallibrary/         # Website folder
    manage.py         # Script to run Django tools for this project (created using django-admin)
    locallibrary/     # Website/project folder (created using django-admin)
    catalog/          # Application folder (created using manage.py)
```

接下來的部分，會詳細討論創建網站框架的過程，並會展示怎麼測試這些變化。最後，我們會討論在這個階段裡，你可以設置的全站配置。

創建專案項目
------

首先打開命令提示符/終端，確保你在虛擬環境中，導航到你要存放 Django 應用程序的位置（在文檔文件夾中，輕鬆找到它的位置），並為你的新網站，創建一個文件夾（在這種情況下：locallibrary）。然後使用 cd 命令進入該文件夾：

```bash
mkdir locallibrary
cd locallibrary
```

用`django-admin startproject`命令創建新項目，並進入該文件夾。

```bash
django-admin startproject locallibrary
cd locallibrary
```

`django-admin`工具會創建如下所示的文件夾結構

```bash
locallibrary/
    manage.py
    locallibrary/
        __init__.py
        settings.py
        urls.py
        wsgi.py
```

locallibrary 項目的子文件夾是整個網站的進入點：

* **\_\_init\_\_.py** 是一個空文件，指示 Python 將此目錄視為 Python 套件。
* **settings.py** 包含所有的網站設置。這是可以註冊所有創建的應用的地方，也是靜態文件，數據庫配置的地方，等等。
* **urls.py**定義了網站 url 到 view 的映射\*\*。\*\*雖然這裡可以包含所有的 url，但是更常見的做法是把應用相關的 url 包含在相關應用中，你可以在接下來的教程裡看到。
* **wsgi.py** 幫助 Django 應用和網絡服務器間的通訊。你可以把這個當作模板。

**manage.py**腳本可以創建應用，和資料庫通訊，啟動開發用網絡服務器。

創建 catalog 應用
-------------

接下來，在 locallibrary 項目裡，使用下面的命令創建 catalog 應用（和你項目的**manage.py**在同一個文件夾下）

```bash
python3 manage.py startapp catalog
```

**備註：** Linux/Mac OS X 應用可以使用上面的命令。在 windows 平台下應該改為： `py -3 manage.py startapp catalog`

如果你是 windows 系統，在這個部分用`py -3`替代`python3`。

如果你使用的是 Python 3.7.0，則應使用`py manage.py startapp catalog`

這個工具創建了一個新的文件夾，並為該應用創建了不同的文件（下面黑體所示）。絕大多數文件的命名和它們的目的有關（比如視圖函數就是**views.py，**模型就是**models.py，**測試是**tests.py，**網站管理設置是**admin.py，**註冊應用是**apps.py）**，並且還包含了為項目所用的最小模板。

執行命令後的文件夾結構如下所示：

```bash
locallibrary/
    manage.py
    locallibrary/
    catalog/
        admin.py
        apps.py
        models.py
        tests.py
        views.py
        __init__.py
        migrations/
```

除上面所說的文件外，我們還有：

* 一個*migration*文件夾，用來存放 「migrations」 ——當你修改你的數據模型時，這個文件會自動升級你的資料庫。
* **\_\_init\_\_.py** —一個空文件，Django/Python 會將這個文件作為Python 套件包並允許你在項目的其他部分使用它。

**備註：** 你注意到上面的文件裡有些缺失嘛？儘管有了 views 和 models 的文件，可是 url 映射，網站模板，靜態文件在哪裡呢？我們會在接下來的部分展示如何創建它們（並不是每個網站都需要，不過這個例子需要）。

註冊 catalog 應用
-------------

既然應用已經創建好了，我們還必須在項目裡註冊它，以便工具在運行時它會包括在裡面（比如在數據庫裡添加模型時）。在項目的 settings 裡，把應用添加進`INSTALLED_APPS` ，就完成了註冊。

打開項目設置文件 **locallibrary/locallibrary/settings.py**找到 `INSTALLED_APPS` 列表裡的定義。如下所示，在列表的最後添加新的一行。

```bash
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'catalog.apps.CatalogConfig',
]
```

新的這行，詳細說明了應用配置文件在( `CatalogConfig`) **/locallibrary/catalog/apps.py** 裡，當你創建應用時就完成了這個過程。

**備註：** 注意到`INSTALLED_APPS已经有许多其他的应用了` (還有 `MIDDLEWARE`,在 settings 的下面)。這些應用為 Django administration site 提供了支持和許多功能(包括會話，認證系統等)。

配置資料庫
-----

現在可以為項目配置資料庫了——為了避免性能上的差異，最好在生產和開發中使用同一種資料庫。你可以在資料庫 裡找到不同的設置方法(Django 文檔)。

在這個項目裡，我們使用 SQLite。因為在展示用的數據庫中，我們不會有很多並發存取的行為。同時，也因為 SQLite 不需要額外的配置工作。你可以在**settings.py**裡看到這個數據庫怎樣配置的。（更多信息如下所示）

```python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
```

因為我們使用 SQLite，不需要其他的設置了。我們繼續吧！

其他項目設置
------

settings.py 裡還包括其他的一些設置，現在只需要改變時區 —改為和標準tz 時區數據表 裡的字符串相同就可以了（數據表裡的 TZ 列有你想要的時區）。把`TIME_ZONE`的值改為你的時區，比如

```python
TIME_ZONE = 'Asia/Taipei'
```

有兩個設置你現在不會用到，不過你應該留意：

* `SECRET_KEY`. 這個密匙值，是 Django 網站安全策略的一部分。如果在開發環境中，沒有保護好這個密匙，把代碼投入生產環境時，最好用不同的密匙代替。（可能從環境變量或文件中讀取）。
* `DEBUG`. 這個會在 debug 日誌裡輸出錯誤信息，而不是輸入 HTTP 的返回碼。在生產環境中，它應設置為 false，因為輸出的錯誤信息，會幫助想要攻擊網站的人。
鏈接 URL 映射器
----------

在項目文件夾裡，創建網站時同時生成了 URL 映射器（**urls.py**）。儘管你可以用它來管理所有的 URL 映射，但是更常用的做法是把 URL 映射留到它們相關的應用中。

打開**locallibrary/locallibrary/urls.py** 注意指導文字解釋了一些使用 URL 映射器的方法。

```python
"""locallibrary URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
 https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
 1. Add an import: from my\_app import views
 2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
 1. Add an import: from other\_app.views import Home
 2. Add a URL to urlpatterns: path('', Home.as\_view(), name='home')
Including another URLconf
 1. Import the include() function: from django.urls import include, path
 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

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

URL 映射通過`urlpatterns` 變量管理，它是一個`path()`函數的 Python 列表。每個`path()`函數，要么將 URL 式樣(URL pattern)關聯到特定視圖( *specific view)*，當模式匹配時將會顯示，要么關聯到某個 URL 式樣列表的測試代碼。(第二種情況下，URL 式樣是目標模型裡的「基本 URL」). `urlpatterns` 列表初始化定義了單一函數，把所有帶有 'admin/' 模式的 URL，映射到`admin.site.urls`。這個函數，包含了 Administration 應用自己的 URL 映射定義。

**備註：** `path()`中的路由是一個字符串，用於定義要匹配的 URL 模式。該字符串可能包括一個命名變量（在尖括號中），例如`'catalog/<id>/'`。此模式將匹配 **/catalog/any\_chars** 等 URL，並將 any\_chars 作為參數名稱為`id` 的字符串，傳遞給視圖。我們將在後面的主題中，進一步討論路徑方法和路由模式

在`urlpatterns` 列表的下面一行，插入下面的代码。這個新項目包括一個 `path()` ，它使用模式 `catalog/` 轉發請求到模塊 `catalog.urls`（具有相對 URL **/catalog/urls.py** 的文件）。

```python
# Use include() to add paths from the catalog application
from django.conf.urls import include
from django.urls import path

urlpatterns += [
    path('catalog/', include('catalog.urls')),
]
```

現在我們把我們網站的根 URL(例如`127.0.0.1:8000`)，重新導向 URL `127.0.0.1:8000/catalog/`;這是項目中唯一的應用，所以我們最好這樣做。為了完成這個目標，我們使用一個特別的視圖函數( `RedirectView`)，當`path()`函數中的 url 式樣被識別以後（在這個例子中是根 url），就會把第一個參數，也就是新的相對 URL ，重定向到（`/catalog/`）。

把下面的代碼加到文件最後：

```python
#Add URL maps to redirect the base URL to our application
from django.views.generic import RedirectView
urlpatterns += [
    path('', RedirectView.as_view(url='/catalog/')),
]
```

將路徑函數的第一個參數留空，用以表示'/'。如果你將第一個參數寫為'/'，Django 會在你啟動開發服務器時給出以下警告：

```python
System check identified some issues:

WARNINGS:
?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'.
Remove this slash as it is unnecessary.
If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'.
```

Django 默認不提供 CSS，JavaScript 和圖像等靜態文件，但在創建站點時，開發 Web 服務器這樣做是有用的。作為此 URL 映射器的最終添加，你可以通過附加以下幾行，在開發期間啟用靜態文件的提供。

現在將以下最終區塊，添加到文件的底部：

```python
# Use static() to add url mapping to serve static files during development (only)
from django.conf import settings
from django.conf.urls.static import static

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
```

**備註：** 有許多方法可以擴充`urlpatterns`列表（上面我們只是使用`+=`運算符，附加一個新的列表項，來清楚地分隔舊代碼和新代碼）。我們可以改為在原始列表定義中，包含這個新的模式映射：

```python
urlpatterns = [
    path('admin/', admin.site.urls),
    path('catalog/', include('catalog.urls')),
    path('', RedirectView.as_view(url='/catalog/', permanent=True)),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
```

此外，我們將導入行（`from django.urls import include`）包含在使用它的代碼中（因此很容易看到我們添加的內容），但通常將所有導入行包含在一個 Python 文件的頂部。

最後一步，在**catalog**文件夾中，創建一個名為**urls.py**的文件，並添加以下文本，以定義（空）導入的`urlpatterns`。這是我們在構建應用程序時，添加模式的地方。

```python
from django.urls import path
from . import views

urlpatterns = [

]
```

測試網站框架
------

現在我們有了一個完整的框架項目。這個網站現在還什麼都不能做，但是我們仍然要運行，以確保我們的更改是有效的。

在運行前，我們應該先運行*數據庫遷移*。這會更新我們的數據庫並且包含所有安裝的應用（同時去除一些警告）。

### 運行資料庫遷移

Django 使用對象關係映射器（ORM），將 Django 代碼中的模型定義，映射到底層資料庫使用的數據結構。當我們更改模型定義時，Django 會跟踪更改，並創建資料庫遷移腳本(位於 **/locallibrary/catalog/migrations/** )，來自動遷移資料庫中的底層數據結構。

當我們創建網站時，Django 會自動添加一些模型，供網站的管理部分使用（稍後我們會解釋）。運行以下命令，來定義資料庫中這些模型的表（確認你位於包含 **manage.py** 的目錄中):

```bash
python3 manage.py makemigrations
python3 manage.py migrate
```

**警告：** 每次模型改變，都需要運行以上命令，來影響需要存放的數據結構（包括添加和刪除整個模型和單個字段）。

該\*\*`makemigrations`\*\*命令，創建（但不實施）項目中安裝的所有應用程序的遷移（你可以指定應用程序名稱，也可以為單個項目運行遷移）。這讓你有機會在應用這些遷移之前，檢查這些遷移代碼—當你是 Django 專家時，你可以選擇稍微調整它們。

這個 \*\*`migrate`\*\*命令，真正對你的資料庫實施遷移（Django 跟踪哪些已添加到當前資料庫）。

**備註：** 參見 Migrations (Django 文件) ，了解較少使用的遷移命令的其他信息。

### 運行網站

在開發期間，你首先要使用開發網頁服務器，然後用你本機的瀏覽器觀看，來測試你的網站。

**備註：** 這個開發網頁服務器並不夠強大，不足以用於生產使用，但是它使你在開發期間，能非常容易獲得你的 Django 網站和運行它，以此來進行快速測試。默認情況下，服務器會開通（`http://127.0.0.1:8000/`），但你也可以選擇其他端口。有關更多信息，查閱（django-admin and manage.py: runserver）(Django docs).

通過如下`runserver`命令，運行開發網頁服務器。（同樣的要在**manage.py**的目錄）

```bash
python3 manage.py runserver

 Performing system checks...

 System check identified no issues (0 silenced).
 September 22, 2016 - 16:11:26
 Django version 1.10, using settings 'locallibrary.settings'
 Starting development server at http://127.0.0.1:8000/
 Quit the server with CTRL-BREAK.
```

一旦服務器運行，你可以用你的瀏覽器導航到http://127.0.0.1:8000/ 查看。你應該會看到一個錯誤頁面，如下。

![Django Debug page for Django 2.0](/zh-TW/docs/Learn/Server-side/Django/skeleton_website/django_404_debug_page.png)

別擔心，這個錯誤頁面是預期的結果。因為我們沒有在 `catalogs.urls`模塊中，定義任何頁面或網址（即是當我們使用一個指向根目錄的 URL 時，會被重新定向的地方）。

**備註：** 上面的頁面，演示了一個很棒的 Django 功能 - 自動除錯日誌記錄。只要找不到頁面，或者代碼引發任何錯誤，就會顯示錯誤畫面，其中包含有用的信息。在這種情況下，我們可以看到我們提供的 URL，與我們的任何 URL 模式都不匹配（如列出的那樣）。在生產期間（當我們將網站放在網上時），日誌記錄將被關閉，在這種情況下，將提供信息量較少、但用戶友好的頁面。

這個時候，我們知道 Django 正在工作！

**備註：** 在進行重大更改時，你應該重新運行遷移，並重新測試站點。這不需要很長時間！

挑戰自我
----

該 **catalog/** 目錄包含應用程序的視圖、模型、和應用的其他部分，你可以打開這些文件並查看樣板。

如上所述，管理站點的 URL 映射，已經添加到項目的 **urls.py**。在瀏覽器中查看管理區域，看看會發生什麼（你可以從上面映射，推斷正確的 URL）。

總結
--

你現在已經創建了一個完整的骨架網站項目，你可以繼續加入網址、模型、視圖、和模版。

現在，Local Library website的骨架已經完成並運行了，是時候開始寫些代碼，讓網站做些它應該做的事了。

參見
--

* Writing your first Django app - part 1 (Django docs)
* Applications (Django Docs). Contains information on configuring applications.
* 前頁
* Overview: Django
* 次頁


Django 介紹 - 學習該如何開發 Web

Django 介紹
=========

* Overview: Django
* 次頁

在這第一篇 Django 文章中，我們將回答「什麼是 Django」這個問題，並概述這個網絡框架有什麼特性。我們將描述主要功能，包括一些高級功能，但我們並不會在本單元中詳細介紹。我們還會展示一些 Django 應用程序的主要構建模塊（儘管此時你還沒有要測試的開發環境）。

|  |  |
| --- | --- |
| 先備知識: |
 基本的電腦知識.對服務器端網站編程的一般了解
 ,特別是網站中客戶端-服務器交互的機制 (en-US)
 .
  |
| 目標: | 了解Django是什麼，它提供了哪些功能，以及Django應用程序的主要構建塊。 |

什麼是 Django?
-----------

Django 是一個高級的 Python 網路框架，可以快速開發安全和可維護的網站。由經驗豐富的開發者構建，Django 負責處理網站開發中麻煩的部分，因此你可以專注於編寫應用程序，而無需重新開發。

它是免費和開源的，有活躍繁榮的社區、豐富的文檔、以及很多免費和付費的解決方案。

Django 可以使你的應用具有以下優點:

完備

Django 遵循 「功能完備」 的理念，提供開發人員可能想要 「開箱即用」 的幾乎所有功能。因為你需要的一切，都是一個 」產品「 的一部分，它們都可以無縫結合在一起，遵循一致性設計原則，並且具有廣泛、和最新的文檔。

通用

Django 可以（並已經）用於構建幾乎任何類型的網站—從內容管理系統和維基，到社交網絡和新聞網站。它可以與任何客戶端框架一起工作，並且可以提供幾乎任何格式（包括 HTML、RSS、JSON、XML 等）的內容。你正在閱讀的網站就是基於 Django。

在內部，儘管它為幾乎所有可能需要的功能（例如幾個流行的資料庫，模版引擎等）提供了選擇，但是如果需要，它也可以擴展到使用其他組件。

安全

Django 幫助開發人員，通過提供一個被設計為 「做正確的事情」 來自動保護網站的框架，來避免許多常見的安全錯誤。例如，Django 提供了一種安全的方式，來管理用戶帳號和密碼，避免了常見的錯誤，比如將 session 放在 cookie 中這種易受攻擊的做法（取而代之的是，cookies 只包含一個密鑰，實際數據存儲在數據庫中），或直接存儲密碼，而不是密碼的 hash 值。

密碼 hash ，是讓密碼通過加密 hash 函數，而創建的固定長度值。 Django 能通過運行 hash 函數，來檢查輸入的密碼 - 就是將輸出的 hash 值，與存儲的 hash 值進行比較是否正確。然而由於功能的 「單向」 性質，假使存儲的 hash 值受到威脅，攻擊者也難以解出原始密碼。 （但其實有彩虹表-譯者觀點）

默認情況下，Django 可以防範許多漏洞，包括 SQL 注入，跨站點腳本，跨站點請求偽造，和點擊劫持 (請參閱 網站安全 相關信息，如有興趣)。

可擴展

Django 使用基於組件的 「無共享」 架構 (架構的每一部分獨立於其他架構，因此可以根據需要進行替換或更改)。在不同部分之間，有明確的分隔，意味著它可以通過在任何級別添加硬件，來擴展服務：緩存服務器，數據庫服務器，或應用程序服務器。一些最繁忙的網站，已經在 Django 架構下成功地縮放了網站的規模大小，以滿足他們的需求（例如 Instagram 和 Disqus，僅舉兩個例子，可自行添加）。

可維護

Django 代碼編寫，是遵照設計原則和模式，鼓勵創建可維護和可重複使用的代碼。特別是，它使用了不要重複自己（DRY）原則，所以沒有不必要的重複，減少了代碼的數量。 Django 還將相關功能，分組到可重用的 「應用程序」 中，並且在較低級別，將相關代碼分組或模塊（ 模型視圖控制器 Model View Controller (MVC) 模式）。

可移植

Django 是用 Python 編寫的，它在許多平台上運行。這意味著，你不受任務特定的服務器平台的限制，並且可以在許多種類的 Linux，Windows 和 Mac OS X 上運行應用程序。此外，Django 得到許多網路託管提供商的好評，他們經常提供特定的基礎設施，和託管 Django 網站的文檔。

Django 的起源?
-----------

Django 最初在 2003 年到 2005 年間，由負責創建和維護報紙網站的網絡團隊開發。在創建了許多網站後，團隊開始考慮、並重用許多常見的代碼和設計模式。這個共同的代碼，演變一個通用的網絡開發框架，2005 年 7 月，被開源為 「Django」 項目。

Django 不斷發展壯大 — 從 2008 年 9 月的第一個里程碑版本（1.0），到最近發布的（2.0）-（2018）版本。每個版本都添加了新功能，和錯誤修復，從支持新類型的數據庫，模版引擎和緩存，到添加 「通用」 視圖函數和類別（這減少了開發人員在一些編程任務必須編寫的代碼量）。

**備註：** 查看 Django 網站上的發行說明 release notes，看看最近版本發生了什麼變化，以及 Django 能做多少工作

Django 現在是一個蓬勃發展的合作開源項目，擁有數千個用戶和貢獻者。雖然它仍然具有反映其起源的一些功能，但 Django 已經發展成為，能夠開發任何類型的網站的多功能框架。

Django 有多受歡迎?
-------------

服務器端框架的受歡迎程度沒有任何可靠和明確的測量（儘管Hot Frameworks網站嘗試使用諸如計算每個平台的 GitHub 項目數量和 StackOverflow 問題的機制來評估流行度）。一個更好的問題是 Django 是否「足夠流行」，以避免不受歡迎的平台的問題。它是否繼續發展？如果你需要幫助，可以幫你嗎？如果你學習 Django，有機會獲得付費工作嗎？

基於使用 Django 的流行網站數量，為代碼庫貢獻的人數以及提供免費和付費支持的人數，那麼是的，Django 是一個流行的框架！

使用 Django 的流行網站包括：Disqus，Instagram，騎士基金會，麥克阿瑟基金會，Mozilla，國家地理，開放知識基金會，Pinterest 和開放棧（來源：Django home page ).

Django 是特定用途的?
--------------

Web 框架通常將自己稱為「特定」或「無限制」。

特定框架是對處理任何特定任務的「正確方法」有意見的框架。他們經常支持特定領域的快速發展（解決特定類型的問題），因為正確的做法是通常被很好地理解和記錄在案。然而，他們在解決其主要領域之外的問題時可能不那麼靈活，並且傾向於為可以使用哪些組件和方法提供較少的選擇。

相比之下，无限制的框架对于将组件粘合在一起以实现目标或甚至应使用哪些组件的最佳方式的限制较少。它们使开发人员更容易使用最合适的工具来完成特定任务，尽管你需要自己查找这些组件。

Django「有點有意義」，因此提供了「兩個世界的最佳」。它提供了一組組件來處理大多數 Web 開發任務和一個（或兩個）首選的使用方法。然而，Django 的解耦架構意味著你通常可以從多個不同的選項中進行選擇，也可以根據需要添加對全新的支持。

Django 代碼是什麼樣子?
---------------

在傳統的數據驅動網站中，Web 應用程序會等待來自 Web 瀏覽器（或其他客戶端）的 HTTP 請求。當接收到請求時，應用程序根據 URL 和可能的`POST` 數據或`GET` 數據中的信息確定需要的內容。根據需要，可以從數據庫讀取或寫入信息，或執行滿足請求所需的其他任務。然後，該應用程序將返回對 Web 瀏覽器的響應，通常通過將檢索到的數據插入 HTML 模板中的佔位符來動態創建用於瀏覽器顯示的 HTML 頁面。

Django 網絡應用程序通常將處理每個步驟的代碼分組到單獨的文件中：

![](/zh-TW/docs/Learn/Server-side/Django/Introduction/basic-django.png)

* **URLs:** 雖然可以通過單個功能來處理來自每個 URL 的請求，但是編寫單獨的視圖函數來處理每個資源是更加可維護的。URL 映射器用於根據請求 URL 將 HTTP 請求重定向到相應的視圖。URL 映射器還可以匹配出現在 URL 中的字符串或數字的特定模式，並將其作為數據傳遞給視圖功能。
* **View:** 視圖是一個請求處理函數，它接收 HTTP 請求並返回 HTTP 響應。視圖通過模型訪問滿足請求所需的數據，並將響應的格式委託給模板。
* **Models:** 模型是定義應用程序數據結構的 Python 對象，並提供在數據庫中管理（添加，修改，刪除）和查詢記錄的機制。
* **Templates:** 模板是定義文件（例如 HTML 頁面）的結構或佈局的文本文件，用於表示實際內容的佔位符。一個視圖可以使用 HTML 模板，從數據填充它動態地創建一個 HTML 頁面模型。可以使用模板來定義任何類型的文件的結構;它不一定是 HTML！

**備註：** Django 將此組織稱為「模型視圖模板（MVT）」架構。它與更加熟悉的Model View Controller架構有許多相似之處.

以下部分將為你提供 Django 應用程序的這些主要部分的想法（稍後我們將在進一步詳細介紹後，我們將在開發環境中進行更詳細的介紹）。

### 將請求發送到正確的視圖(urls.py)

URL 映射器通常存儲在名為 urls.py 的文件中。在下面的示例中，mapper（`urlpatterns`）定義了特定 URL 模式和相應視圖函數之間的映射列表。如果接收到具有與指定模式匹配的 URL（例如 r'^$'，下面）的 HTTP 請求，則將調用相關聯的視圖功能（例如 views.index）並傳遞請求。

```python
urlpatterns = [
    path('admin/', admin.site.urls),
    path('book/<int:id>/', views.book_detail, name='book\_detail'),
    path('catalog/', include('catalog.urls')),
    re_path(r'^([0-9]+)/$', views.best),
]
```

`urlpatterns` 對像是 `path()` 和/或 `re_path()` 函數的列表（Python 列表使用方括號定義，其中項目用逗號分隔，可以有一個可選的尾隨逗號。例如：`[item1, item2, item3,]`）。

兩種方法的第一個參數，是將要匹配的路由（模式）。`path()`方法使用尖括號，來定義將被捕獲、並作為命名參數傳遞給視圖函數的 URL 的部分。`re_path()`函數使用靈活的模式匹配方法，稱為正則表達式。我們將在後面的文章中討論這些內容！

第二個參數，是在匹配模式時將調用的另一個函數。註釋 `views.book_detail`表示該函數名為`book_detail()`，可以在名為`views`的模塊中找到（即在名為`views.py`的文件中）

### 處理請求(views.py)

視圖是 Web 應用程序的核心，從 Web 客戶端接收 HTTP 請求並返回 HTTP 響應。在兩者之間，他們編制框架的其他資源來訪問數據庫，渲染模板等。

下面的例子顯示了一個最小的視圖功能 index()，這可以通過我們的 URL 映射器在上一節中調用。像所有視圖函數一樣，它接收一個 HttpRequest 對像作為參數（request）並返回一個 HttpResponse 對象。在這種情況下，我們對請求不做任何事情，我們的響應只是返回一個硬編碼的字符串。我們會向你顯示一個請求，在稍後的部分中會提供更有趣的內容。

```python
## filename: views.py (Django view functions)

from django.http import HttpResponse

def index(request):
    # Get an HttpRequest - the request parameter
    # perform operations using information from the request.
    # Return HttpResponse
    return HttpResponse('Hello from Django!')
```

**備註：** 一點點 Python:

* Python 模塊是函數的「庫」，存放在單獨的文件中，我們可能希望在代碼中使用。在這裡，我們只導入了`HttpResponse`從對象`django.http`模塊，使我們可以在視圖中使用
 `from django.http import HttpResponse`。
 還有其他方法可以從模塊導入一些或所有對象
* 使用`def`，如上所示的關鍵字聲明函數，命參數在函數名稱後面的括號中列出：整行以冒號結尾。注意下一行是否都**縮進**。縮進很重要，因為它指定代碼行在該特定塊內(強制縮進是 Python 的一個關鍵特徵，而且是 Python 代碼很容易閱讀的一個原因)。

視圖通常存放在一個名為**views.py**的文件中。

### 定義數據模型(models.py)

Django Web 應用程序，通過被稱為模型的 Python 對象，來管理和查詢數據。模型定義存儲數據的結構，包括字段類型 以及字段可能的最大值，默認值，選擇列表選項，文檔幫助文本，表單的標籤文本等。模型的定義與底層數據庫無關-你可以選擇其中一個，作為項目設置的一部分。一旦你選擇了要使用的數據庫，你就不需要直接與之交談- 只需編寫模型結構和其他代碼，Django 可以處理與數據庫通信的所有辛苦的工作。

下面的代碼片段為`Team`對象，展示了一個非常簡單的 Django 模型。本`Team`類別是從 Django 的類別派生`models.Model`。它將團隊名稱和團隊級別，定義為字符字段，並為每個記錄指定了要存放的最大字符數。`team_level`可以是幾個值中的一個，因此，我們將其定義為一個選擇字段，並在被展示的數據、和被儲存的數據之間，建立映射，並設置一個默認值。

```python
# filename: models.py

from django.db import models

class Team(models.Model):
    team_name = models.CharField(max_length=40)

    TEAM_LEVELS = (
        ('U09', 'Under 09s'),
        ('U10', 'Under 10s'),
        ('U11', 'Under 11s'),
        ...  #list other team levels
    )
    team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
```

**備註：** Python 小知識:

* Python 支持「面向對象編程」，這是一種編程風格，我們將代碼組織到對像中，其中包括用於對該對象進行操作的相關數據和功能。對像也可以從其他對象繼承／擴展／派生，允許相關對象之間的共同行為被共享。在 Python 中，我們使用關鍵字**Class**定義對象的「藍圖」。我們可以根據類中的模型創建類型的多個特定實例。
* 例如，我們有個**Team**類，它來自於**Model**類。這意味著它是一個模型，並且將包含模型的所有方法，但是我們也可以給它自己的專門功能。在我們的模型中，我們定義了我們的數據庫需要存儲我們的數據字段，給出它們的具體名稱。Django 使用這些定義（包括字段名稱）來創建底層數據庫。

### 查詢數據(views.py)

Django 模型提供了一個，用於搜索數據庫的簡單查詢 API。這可以使用不同的標準（例如，精確，不區分大小寫，大於等等）來匹配多個字段，並且可以支持複雜語句（例如，你可以在擁有一個團隊的**U11**團隊上指定搜索名稱以「Fr 」開頭或以「al」結尾）。

代碼片段顯示了一個視圖函數（資源處理程序），用於顯示我們所有的**U09**團隊。**粗體**顯示如何使用模型查詢 API，過濾所有記錄，其中該 **team\_level**字段，具有正確的文本「 **U09** 」（請注意，該條件如何 filter()作為參數傳遞給該函數，該字段名稱和匹配類型由雙下劃線： **`team_level__exact`**）。

```python
## filename: views.py

from django.shortcuts import render
from .models import Team

def index(request):
    list_teams = Team.objects.filter(team_level__exact="U09")
    context = {'youngest\_teams': list_teams}
    return render(request, '/best/index.html', context)
```

此功能使用**render** ()功能創建**HttpResponse**發送回瀏覽器的功能。這個函數是一個快捷方式；它通過組合指定的 HTML 模版和一些數據來插入模版（在名為「 **content** 」的變量中提供）來創建一個**HTML**文件。在下一節中，我們將介紹如何在其中插入數據以創建**HTML**。

### 呈現數據(HTML 模版)

模板系統允許你使用佔位符指定輸出文檔的結構，以便在生成頁面時填充數據。模板通常用於創建 HTML，但也可以創建其他類型的文檔。 Django 支持其本機模板系統，和另一個流行的 Python 庫，名為 Jinja2（如果需要，它也可以支持其他系統）。

代碼片段，顯示了上一節中`render（）`函數調用的 HTML 模板的外觀。這個模板的編寫假設它在渲染時可以訪問名為`youngest_teams`的列表變量（包含在上面`render（）`函數中的上下文變量`context`中）。在 HTML 框架內部，我們有一個表達式，首先檢查`youngest_teams`變量是否存在，然後在`for`循環中迭代它。在每次迭代中，模板在`<li>` (en-US)元素中顯示每個團隊的`team_name`值。

```python
## filename: best/templates/best/index.html

<!DOCTYPE html>
<html lang="en">
<body>

 {% if youngest_teams %}
    <ul>
    {% for team in youngest_teams %}
        <li>{{ team.team_name }}</li>
    {% endfor %}
    </ul>
{% else %}
    <p>No teams are available.</p>
{% endif %}

</body>
</html>
```

你還能做什麼？
-------

前面的部分，展示了幾乎每個 Web 應用程序將使用的主要功能：URL 映射，視圖，模型和模版。Django 提供的其他內容包括：

* **表單** : HTML 表單用於收集用戶數據，以便在服務器上進行處理。Django 簡化了表單創建，驗證和處理。
* **用戶身份驗證和權限** : Django 包含了一個強大的用戶身份驗證和權限系統，該系統已經構建了安全性。
* **緩存** :與提供靜態內容相比，動態創建內容需要更大的計算強度（也更緩慢）。Django 提供靈活的緩存，以便你可以存儲所有或部分的頁面。如無必要，不會重新呈現網頁。
* **管理網站** :當你使用基本骨架創建應用時，就已經默認包含了一個 Django 管理站點。它十分輕鬆地創建了一個管理頁面，使網站管理員能夠創建、編輯和查看站點中的任何數據模型。
* **序列化數據** : Django 可以輕鬆地將數據序列化，並支持 XML 或 JSON 格式。這會有助於創建一個 Web 服務（Web 服務指數據純粹為其他應用程序或站點所用，並不會在自己的站點中顯示），或是有助於創建一個由客戶端代碼處理、和呈現所有數據的網站。
總結
--

恭喜，你已經完成了 Django 之旅的第一步！你現在應該了解 Django 的主要優點，一些關於它的歷史，以及 Django 應用程序的每個主要部分可能是什麼樣子。你還應該了解 Python 編程語言的一些內容，包括列表，函數和類別的語法。

你已經看到上面的一些真正的 Django 代碼，但與客戶端代碼不同，你需要設置一個開發環境來運行它。這是我們的下一步。

* Overview: Django
* 次頁


Django Tutorial Part 7: Sessions framework - 學習該如何開發 Web

Django Tutorial Part 7: Sessions framework
==========================================

* 前頁
* Overview: Django
* 次頁  (en-US)

本教程擴展了我們的LocalLibrary 網站，為主頁添加了一個基於會話的訪問計數器。這是一個相對簡單的例子，但它確實顯示了，如何使用會話框架，為匿名用戶提供持久的行為。

|  |  |
| --- | --- |
| Prerequisites: |
 Complete all previous tutorial topics, including
 Django Tutorial Part 6: Generic list and detail views |
| Objective: | To understand how sessions are used. |

概覽
--

我們在之前的教程中創建的LocalLibrary 網站允許用戶瀏覽目錄中的書籍和作者。 雖然內容是從數據庫動態生成的，但每個用戶在使用該網站時基本上都可以訪問相同的頁面和信息類型。

在一個"真實"的庫中，你可能希望根據用戶以前對網站的使用，首選項等為單個用戶提供定制的體驗。例如，你可以隱藏或存儲用戶下次訪問網站時之前已確認的警告消息，或尊重他們的偏好（例如，他們希望在每個頁面上顯示的搜索結果的數量）。

會話框架允許你實現這種行為，從而允許你基於每個站點訪問者存儲和檢索任意數據。

What are sessions?
------------------

Web 瀏覽器和服務器之間的所有通信都是通過 HTTP 協議進行的，該協議是無狀態的。該協議是無狀態的事實意味著客戶端和服務器之間的消息是完全相互獨立的-沒有基於先前消息的「序列」或行為的概念。因此，如果你想擁有一個跟踪與客戶之間正在進行的關係的站點，則需要自己實施。

會話是 Django（以及大多數 Internet）使用的機制，用於跟踪站點與特定瀏覽器之間的「狀態」。會話允許你在每個瀏覽器中存儲任意數據，並且只要瀏覽器連接，該數據就可用於站點。然後，與會話相關聯的單個數據項被一個``鍵''引用，該鍵既用於存儲又用於檢索數據。

Django 使用包含特殊會話 ID 的 cookie 來標識每個瀏覽器及其與站點的關聯會話。默認情況下，實際會話數據默認存儲在站點數據庫中（這比將數據存儲在 cookie 中更安全，因為 cookie 在 cookie 中更容易受到惡意用戶的攻擊）。你可以將 Django 配置為將會話數據存儲在其他位置（緩存，文件或是「安全」 Cookie），但是默認位置是一個很好且相對安全的選擇。

Enabling sessions
-----------------

當我們創建框架網站時（在教程 2 中），將自動啟用會話。

在項目文件的`INSTALLED_APPS` 和`MIDDLEWARE` 部分中進行配置(**locallibrary/locallibrary/settings.py**)，如下所示：

```python
INSTALLED_APPS = [
    ...
    'django.contrib.sessions',
    ....

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ....
```

Using sessions
--------------

你可以從`request` 參數（作為視圖的第一個參數傳入的`HttpRequest` ）中訪問視圖中的`session` 屬性。 此會話屬性表示與當前用戶的特定連接（或更確切地說，與當前瀏覽器的連接，由該站點的瀏覽器 cookie 中的會話 ID 標識）。

`session` 屬性是一個類似於字典的對象，你可以在視圖中隨意讀取和寫入多次，並根據需要對其進行修改。 你可以執行所有正常的字典操作，包括清除所有數據，測試是否存在鍵，循環訪問數據等。儘管如此，在大多數情況下，你只會使用標準的``字典''API 來獲取和設置值。

下面的代碼片段顯示瞭如何獲取，設置和刪除與當前會話（瀏覽器）相關的鍵「 `my_car`」的某些數據。

**備註：** Django 的一大優點是，你無需考慮將會話綁定到視圖中當前請求的機制。 如果我們在視圖中使用以下片段，我們將知道有關`my_car` 的信息僅與發送當前請求的瀏覽器相關聯。

```python
# Get a session value by its key (e.g. 'my\_car'), raising a KeyError if the key is not present
my_car = request.session['my\_car']

# Get a session value, setting a default if it is not present ('mini')
my_car = request.session.get('my\_car', 'mini')

# Set a session value
request.session['my\_car'] = 'mini'

# Delete a session value
del request.session['my\_car']
```

該 API 還提供了許多其他方法，這些方法主要用於管理關聯的會話 cookie。 例如，有一些方法可以測試客戶端瀏覽器是否支持 cookie，設置和檢查 cookie 到期日期以及從數據存儲中清除過期的會話。 你可以在如 How to use sessions 找到完整的 API（Django 文檔）。

Saving session data
-------------------

默認情況下，當會話已被修改（分配）或刪除時，Django 僅保存到會話數據庫並將會話 cookie 發送給客戶端。 如果你要使用上一節中所示的會話密鑰更新某些數據，則無需擔心！ 例如：

```python
# This is detected as an update to the session, so session data is saved.
request.session['my\_car'] = 'mini'
```

如果你要更新會話數據中的某些信息，則 Django 將不會識別你已對會話進行了更改並保存了數據（例如，如果要在「 `my_car`」數據中更改「 `wheels`」數據， 如下所示）。 在這種情況下，你需要將會話明確標記為已修改。

```python
# Session object not directly modified, only data within the session. Session changes not saved!
request.session['my\_car']['wheels'] = 'alloy'

# Set session as modified to force data updates/cookie to be saved.
request.session.modified = True
```

**備註：** 你可以更改行為，以便站點可以通過在你的項目設置（**locallibrary/locallibrary/settings.py**）中添加`SESSION_SAVE_EVERY_REQUEST = True` 來更新每個請求的數據庫/發送 cookie。

Simple example — getting visit counts
-------------------------------------

作為一個簡單的真實示例，我們將更新我們的庫以告知當前用戶他們訪問 LocalLibrary 主頁的次數。

打開/ **/locallibrary/catalog/views.py**，然後進行以下粗體顯示的更改。

```python
def index(request):
    ...

    num_authors = Author.objects.count()  # The 'all()' is implied by default.

    # Number of visits to this view, as counted in the session variable.
    num_visits = request.session.get('num\_visits', 0)
    request.session['num\_visits'] = num_visits + 1

    context = {
        'num\_books': num_books,
        'num\_instances': num_instances,
        'num\_instances\_available': num_instances_available,
        'num\_authors': num_authors,
        'num\_visits': num_visits,
    }

    # Render the HTML template index.html with the data in the context variable.
    return render(request, 'index.html', context=context)
```

在這裡，我們首先獲取`'num_visits'`會話密鑰的值，如果之前未設置，則將其設置為 0。 每次接收到請求時，我們都將增加該值並將其存儲回會話中（對於下一次用戶訪問該頁面）。 然後將`num_visits` 變量傳遞到我們的上下文變量中的模板。

**備註：** 我們也可能會在此處測試瀏覽器是否甚至支持 cookie（例如，請參閱How to use sessions）或設計我們的 UI，以便無論是否支持 cookie 都無關緊要。

將以下區塊底部看到的行添加到``動態內容''部分底部的主 HTML 模板(**/locallibrary/catalog/templates/index.html**)中以顯示上下文變量：

```django
<h2>Dynamic content</h2>

<p>The library has the following record counts:</p>
<ul>
  <li><strong>Books:</strong> {{ num\_books }}</li>
  <li><strong>Copies:</strong> {{ num\_instances }}</li>
  <li><strong>Copies available:</strong> {{ num\_instances\_available }}</li>
  <li><strong>Authors:</strong> {{ num\_authors }}</li>
</ul>

<p>
  You have visited this page {{ num\_visits }} time{{ num\_visits|pluralize }}.
</p>
```

保存更改，然後重新啟動測試服務器。 每次刷新頁面時，數字都會更新。

總結
--

現在，你知道使用會話來改善與匿名用戶的交互是多麼容易。

在接下來的文章中，我們將說明身份驗證和授權（權限）框架，並向你展示如何支持用戶帳戶。

See also
--------

* How to use sessions (Django docs)
* 前頁
* Overview: Django
* 次頁


Assessment: DIY Django mini blog - 學習該如何開發 Web

Assessment: DIY Django mini blog
================================

* 前頁  (en-US)
* Overview: Django

在這個評估中，你將使用你在 Django Web Framework (Python) 模組中獲得的知識，來創建一個非常基本的部落格。

|  |  |
| --- | --- |
| 前提: | 在開始時做這章節的任務之前，你應該已經看完這個模組的所有文章了。 |
| 目標: | 測試Django基礎的綜合應用，包含URL設定、模型、視圖、表單和模板。 |

專案簡介
----

需要顯示的頁面與對應的 URLs 和需求提列於下表：

| 頁面 | URL | 需求 |
| --- | --- | --- |
| 首頁 | `/` 和 `/blog/` | 關於此站的說明。 |
| 所有部落格文章的清單 | `/blog/blogs/` | 所有部落格文章的清單。* 所有使用者都能從側邊選單進入此頁。
* 清單按發布日期排序(新至舊)。
* 清單依照每頁5筆文章分頁。
* 清單內的每一筆項目顯示文章標題、發布日期與作者的名字。
* 文章標題連結至該至文章的詳細頁面。
* 作者的名字連結至該作者的詳細頁面。
 |
| 部落格作者(blogger) 詳細頁面 | `/blog/blogger/*<author-id>*` | 特定作者(由id指定)的資訊與他所發布的部落格文章。* 所有使用者都能從作者連結進入此頁(例如文章內的作者連結)。
* 包含一些關於作者本身的資訊。
* 文章清單按發布日期排序(新至舊)。
* 不用分頁。
* 文章清單只顯示文章標題與發佈日期。
* 文章標題連結至文章詳細頁面。
 |
| 部落格文章詳細頁面 | `/blog/*<blog-id>*` | 部落格文章詳細內容。* 任何使用者都能從部落格文章的清單進入此頁。
* 包含文章標題、作者、發布日期與內容。
* 文章的回覆必須呈現於底部。
* 文章的回覆必須按回覆時間排序(舊至新)。
* 已登入的使用者能看見新增回覆的連結。
* 文章與回覆需以純文字的方式顯示。不需要支援任何markup(例如連結、圖片、粗體/斜體等)。
 |
| 部落格作者清單 | `/blog/bloggers/` | 系統內的部落格作者清單。* 任何使用者都可以從側邊選單進入此頁。
* 作者名字連結至該作者的詳細頁面。
 |
| 回覆表單頁 | `/blog/*<blog-id>*/create` | 新增回覆於特定文章。* 只有登入的使用者可以由文章詳細頁面底部連結進入此頁。
* 提供能輸入回覆的表單(發布日期和文章標題不可被編輯)。
* 回覆被發表之後，頁面會轉址回該文章詳細頁。
* 使用者無法修改或是刪除他發表的回覆。
* 未登入的使用者會先被導至登入頁，登入之後才能發表回覆。一旦登入之後，他們便會被導至他們想發表回覆的文章頁。
* 回覆表單頁必須包含該文章的標題與連結。
 |
| 使用者身分認證頁 | `/accounts/*<standard urls>*` | 標準的Django身分驗證頁面，用來登入、登出及修改密碼。* 使用者能從側欄連結進入登入/登出頁面。
 |
| 管理者網頁 | `/admin/*<standard urls>*` | 管理者網頁必須能新增/編輯/刪除部落格文章、作者及回覆。* 管理者網頁的每筆文章記錄必須一併於其底下陳列出相關的回覆。
* 管理者網頁的每一筆回覆都要以75字的回覆內容作為顯示名稱。
* 其餘的紀錄使用基本的註冊即可。
 |

另外你應該要寫一些基本的測試來驗證:

* 所有的模型欄位都有正確的標示和長度。
* 所有的模型都有期望的物件名稱(例如 `__str__()` 回傳期望的值)。
* 模型有期望的 URL 給每篇文章與回覆。(例如`get_absolute_url()` 回傳期望的 URL)。
* BlogListView (所有文章的頁面) 可以從期望的位址進入(例如/blog/blogs)。
* BlogListView (所有文章的頁面) 可以從期望的位址名稱進入(例如'blogs')。
* BlogListView (所有文章的頁面) 使用期望的模板(例如預設值)。
* BlogListView 以每頁 5 筆項目分頁(至少第一頁是如此)。

**備註：** 當然你也可以跑很多其他的測試。但是我們會希望你至少實作以上列出的測試項目。

下一區塊顯示符合以上需求的網頁截圖。

截圖
--

The following screenshot provide an example of what the finished program should output.

### 列出所有的部落格文章

 這個頁面會列出所有部落格內的文章(可以從側邊選單的「所有文章」連結進入)。
 幾項提醒：

* 側邊選單也要列出目前登入的使用者。
* 每篇文章與部落客都能透過連結的方式進入。
* 必須要有分頁(每頁 5 筆資料)。
* 文章排列順序由最新至最舊。

![List of all blogs](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_allblogs.png)

### 列出所有部落客(文章作者)

 可以由側邊選單的「所有部落客」進入此頁面，並於頁面上提供連結至每一位部落客。
 從截圖可以發現到，並沒有任何一位使用者登入。

![List of all bloggers](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_blog_allbloggers.png)

### 部落格詳細頁

顯示某篇特定部落格文章的詳細內容。

![Blog detail with add comment link](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_blog_detail_add_comment.png)

 請注意每個評論都有日期與時間，並且由最後至最新排列(與部落格文章相反)。
 我們可以看見最底下有個連結連到新增評論的表單。當使用者沒有登入時，我們改以要求登入的連結代替。

![Comment link when not logged in](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_blog_detail_not_logged_in.png)

### 新增評論表單

這張表單用來新增評論，且使用者必須是登入狀態。當表單送出成功之後，我們必須回到相對應的部落格文章內容頁。

![Add comment form](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_comment_form.png)

### 作者資料

這頁顯示部落客的介紹資料以及列出他們所發表的部落格文章。

![Blogger detail page](/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog/diyblog_blogger_detail.png)

一步一腳印 Steps to complete
-----------------------

以下說明實作的步驟。

1. 建立一個此網站的專案及 app 骨架(可以參考Django 教學 2 : 建立一個網站骨架)。你也許會用『diyblog』作為專案名稱，『blog』作為 app 的名稱。
2. 建立部落格文章、評論與其他任何所需物件的模型。當你在思考怎麼設計的時候，請記得：
    * 每一個評論都只屬於一篇部落格文章，但每一個部落格文章可以有很多筆評論。
    * 部落格文章必須要依照發布時間排序(新至舊)，評論要依照發布排序(舊至新)。
    * 不是每位使用者都是部落客，但是每一位使用者都可以留下評論。
    * 部落客必須有介紹資訊。
3. 跑 migrations 以及創建一個新的超級使用者(superuser)。
4. 透過 admin 網站新稱一些部落格文章和評論。
5. 幫部落格文章列表頁與部落客列表頁建立視圖、模板及設定 URL。
6. 幫部落格文章詳細頁與部落客詳細頁建立視圖、模板及設定 URL。
7. 建立一個頁面包含可以新增評論的表單(記得只有已登入的使用者可以進入此頁!)
提示與小技巧
------

This project is very similar to the LocalLibrary tutorial. You will be able to set up the skeleton, user login/logout behaviour, support for static files, views, URLs, forms, base templates and admin site configuration using almost all the same approaches.

Some general hints:

1. The index page can be implemented as a basic function view and template (just like for the locallibrary).
2. The list view for blog posts and bloggers, and the detail view for blog posts can be created using the generic list and detail views.
3. The list of blog posts for a particular author can be created by using a generic list Blog list view and filtering for blog object that match the specified author.
    * You will have to implement `get_queryset(self)` to do the filtering (much like in our library class `LoanedBooksAllListView`) and get the author information from the URL.
    * You will also need to pass the name of the author to the page in the context. To do this in a class-based view you need to implement `get_context_data()` (discussed below).
4. The *add comment* form can be created using a function-based view (and associated model and form) or using a generic `CreateView`. If you use a `CreateView` (recommended) then:
    * You will also need to pass the name of the blog post to the comment page in the context (implement `get_context_data()` as discussed below).
    * The form should only display the comment "description" for user entry (date and associated blog post should not be editable). Since they won't be in the form itself, your code will need to set the comment's author in the `form_valid()` function so it can be saved into the model (as described here — Django docs). In that same function we set the associated blog. A possible implementation is shown below (`pk` is a blog id passed in from the URL/URL configuration).
     python
    ```python
        def form\_valid(self, form):
            """
     Add author and associated blog to form data before setting it as valid (so it is saved to model)
     """
            #Add logged-in user as author of comment
            form.instance.author = self.request.user
            #Associate comment with blog based on passed id
            form.instance.blog=get_object_or_404(Blog, pk = self.kwargs['pk'])
            # Call super-class form validation behaviour
            return super(BlogCommentCreate, self).form_valid(form)

    ```
    * You will need to provide a success URL to redirect to after the form validates; this should be the original blog. To do this you will need to override `get_success_url()` and "reverse" the URL for the original blog. You can get the required blog ID using the `self.kwargs` attribute, as shown in the `form_valid()` method above.

We briefly talked about passing a context to the template in a class-based view in the Django Tutorial Part 6: Generic list and detail views topic. To do this you need to override `get_context_data()` (first getting the existing context, updating it with whatever additional variables you want to pass to the template, and then returning the updated context). For example, the code fragment below shows how you can add a blogger object to the context based on their `BlogAuthor` id.

```python
class SomeView(generic.ListView):
    ...

    def get\_context\_data(self, \*\*kwargs):
        # Call the base implementation first to get a context
        context = super(SomeView, self).get_context_data(\*\*kwargs)
        # Get the blogger object from the "pk" URL parameter and add it to the context
        context['blogger'] = get_object_or_404(BlogAuthor, pk = self.kwargs['pk'])
        return context
```

Assessment
----------

The assessment for this task is available on Github here. This assessment is primarily based on how well your application meets the requirements we listed above, though there are some parts of the assessment that check your code uses appropriate models, and that you have written at least some test code. When you're done, you can check out our the finished example which reflects a "full marks" project.

Once you've completed this module you've also finished all the MDN content for learning basic Django server-side website programming! We hope you enjoyed this module and feel you have a good grasp of the basics!

* 前頁  (en-US)
* Overview: Django


Django 教學 1: 本地圖書館網站 - 學習該如何開發 Web

Django 教學 1: 本地圖書館網站
====================

* 前頁
* Overview: Django
* 次頁

我們實戰教學系列的第一篇，會解釋你將學到什麼。並提供一個「本地圖書館」 的例子，作為概述。在接下來的教學裡，我們會不斷完善和改進這個網站。

|  |  |
| --- | --- |
| 前提: | 閱讀 Django 介紹。在接下來的文章裡，你需要創建 Django 開發環境. |
| 目標: | 介紹教學裡使用的網站應用，讓讀者明白要討論的主題。 |

概覽
--

歡迎來到 MDN 的「本地圖書館」Django 教學。在教學裡，我們會開發一個網站，用來管理本地圖書館的目錄。

在這一系列的教學裡，你將:

* 運用 Django 的工具，創建網站和應用的框架。
* 啟動和停止開發用的服務器。
* 創建模型（models）用來表示應用裡的數據。
* 運用 Django 的 admin 網站，以填充網站數據。
* 面對不同的網路請求，創建視圖函數（views）取回相應的數據。並把數據用模板（templates ），渲染成 HTML ，展示在瀏覽器裡。
* 創建網路映射，將不同的 URL 模式，分發給特定的視圖函數（views）。
* 添加用戶認證和會話（sessions），管理網站行為和進入權限。
* 使用表單。
* 為應用編寫測試。
* 有效運用 Django 的安全系統。
* 把應用佈置到生產環境中。

關於這些主題，你已經學會了一些，並對其他的也有了簡單的了解。在這系列教學的最後，你會學到足夠多，而可以自己開發簡單的 Django 應用了。

本地圖書館網站
-------

本地圖書館，是我們在本系列教學裡，創建和不斷改善的網站。跟你期望的一樣，這個網站的目標，是為一個小型的圖書館，提供一個線上目錄。在這個小型圖書館裡，用戶能瀏覽書籍，和管理他們的帳號。

這個例子是精心挑選出來的，因為它可以根據我們的需要，增加或多或少的細節。也能用來展示，幾乎所有的 Django 特性。更重要的是，它提供了一條指南式的路線，在這條路線中，我們會用到 Django 網路框架最重要的功能：

* 在第一篇教學裡，我們會定義一個，簡單到只能瀏覽的圖書館。圖書館的會員，可以查找哪些書可以借閱。我們得以探索那些，幾乎所有網站都會運用的操作：閱讀和展示數據庫裡的內容。
* 接下來，圖書館會慢慢擴展，以展示更高級的 Django 特性。例如，我們會擴展功能，讓會員能夠保留圖書。這個特性會展示如何使用表單，並支持用戶認證。

儘管這是一個非常容易擴展的例子，它被稱為本地圖書館是有原因的——我們希望用最少的訊息，幫助你快速創建、和運用 Django。最後，我們會存儲圖書訊息，圖書數量，作者和其他重要訊息。我們不會儲存圖書館可能會儲存的其他訊息，或是提供一個支持多個圖書館、或是 」大型圖書館「 功能的建構。

我卡住了，從哪裡獲得源程式碼呢？
----------------

在學習本系列教程時，我們會提供合適的代碼片段，你可以粘貼複製，但是有些代碼我們希望你能自己擴展（在提示下）。

如果你卡在某個地方，你可以在 Github 裡找到網站的完整代碼。

總結
--

現在你對本地圖書館網站有了一些了解並知道你會學到什麼。是時候創建我們例子會用到的網站框架了。

* 前頁
* Overview: Django
* 次頁


Django Tutorial Part 8: User authentication and permissions - 學習該如何開發 Web

Django Tutorial Part 8: User authentication and permissions
===========================================================

* 前頁
* Overview: Django
* 次頁

在本教程中，我們將會展示如何允許用戶使用自己的帳戶登入到你的網站，以及如何根據用戶是否已登入和權限的不同來控制他們可以執行和查看的內容。作為展示的一部分，我們會擴展本地圖書館網站，添加登入頁面和登出頁面，以及用來查看已借閱的圖書的頁面——分為用戶與員工兩種不同頁面。

|  |  |
| --- | --- |
| 前提: |
 完成至
 Django 線上教學 7: 會話（Session）框架為止的所有主題。
  |
| 目標: | 了解如何設定與運用使用者驗證與權限機制。 |

大綱
--

Django 提供認證和授權（「permission」）系統，該系統建立在上一教程中討論的會話框架的基礎上。透過它可以驗證用戶憑證並定義個別用戶能夠執行的操作。該框架包括用於 `Users` 和 `Groups` 的內置模型（一般常用來一次性套用權限於一群用戶上的方式），用於指定用戶是否可以執行任務的權限/旗標，用於登入用戶的表單和視圖，以及 查看用於限制內容的工具。

**備註：** 從 Django 角度而言，身份驗證系統需要做到非常通用，因此不提供其他網頁身份驗證系統中提供的某些功能。需要解決一些常見問題的話可以透過第三方軟件包。例如，限制登錄嘗試和透過第三方進行身份驗證（例如 OAuth）。

在本教程中，我們將會展示如何在本地圖書館網站中啟用用戶身份驗證，並建立自己的登入和登出頁面，為模型添加權限以及控制對頁面的訪問。我們將根據身份驗證/權限顯示為用戶或是圖書館員設計的已借出書籍列表。

身份驗證系統非常有彈性，你可以根據需要從頭開始構建 URL，表單，視圖和模板，只透過提供的 API 來登入用戶。但是，在本文中，我們將為登入與登出頁面使用 Django 的「stock」身份驗證視圖和表單。我們仍然需要建立一些模板，但這很簡單。

我們還將向你展示如何建立權限，並在視圖和模板中檢查登入狀態和權限。

Enabling authentication
-----------------------

當我們創建框架網站時（在教程 2 中），身份驗證已自動啟用，因此你此時無需執行任何其他操作。

**備註：** 當我們使用 `django-admin startproject` 命令創建應用程序時，所有必要的配置都為我們完成了。用戶和模型權限的數據庫表是在我們首次調用 `python manage.py migrate` 時創建的。

該配置是在項目文件（**locallibrary/locallibrary/settings.py**）的 `INSTALLED_APPS` 和 `MIDDLEWARE` 部分中設置的，如下所示：

```python
INSTALLED_APPS = [
    ...
    'django.contrib.auth',  #Core authentication framework and its default models.
    'django.contrib.contenttypes',  #Django content type system (allows permissions to be associated with models).
    ....

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',  #Manages sessions across requests
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',  #Associates users with requests using sessions.
    ....
```

Creating users and groups
-------------------------

當我們在教程 4 中查看 Django 管理站點時，你已經創建了第一個用戶（這是一個超級用戶，使用命令 `python manage.py createsuperuser` 創建）。我們的超級用戶已經通過身份驗證，並且具有所有權限，因此我們需要創建一個測試用戶來代表普通站點用戶。我們將使用管理站點來創建本地圖書館組和網站登錄名，因為這是最快的方法之一。

**備註：** 你還可以通過編程方式創建用戶，如下所示。例如，如果要開發一個界面以允許用戶創建自己的登錄名，則必須這樣做（你不應授予用戶訪問管理站點的權限）。

```python
from django.contrib.auth.models import User

# Create user and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')

# Update fields and then save again
user.first_name = 'John'
user.last_name = 'Citizen'
user.save()
```

在下面，我們將首先創建一個組，然後創建一個用戶。即使我們還沒有添加庫成員的任何權限，但是如果以後需要添加，將它們一次添加到組中要比分別添加到每個成員要容易得多。

啟動開發服務器，然後在本地 Web 瀏覽器（`http://127.0.0.1:8000/admin/`）中導航到管理站點。使用你的超級用戶帳戶的憑據登錄到該站點。管理站點的頂層顯示所有模型，按「django 應用程序」排序。在「**Authentication and Authorisation**」部分，你可以單擊 **Users** 或 **Groups** 鏈接以查看其現有記錄。

![Admin site - add groups or users](/zh-TW/docs/Learn/Server-side/Django/Authentication/admin_authentication_add.png)

首先，讓我們為圖書館成員創建一個新組。

1. 單擊**Add**按鈕（在組旁邊）以創建一個新組；輸入該組的名稱「Library Members」。

 ![Admin site - add group](/zh-TW/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_group.png)
2. 我們不需要該組的任何權限，因此只需按**SAVE**（你將被帶到組列表）。

現在讓我們創建一個用戶：

1. 導航回到管理站點的主頁
2. 單擊「用戶」旁邊的「添加」按鈕以打開「添加用戶」對話框。

 ![Admin site - add user pt1](/zh-TW/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_user_prt1.png)
3. 輸入適合你的測試用戶的用戶名和密碼/密碼確認
4. 按**SAVE**創建用戶。
 管理站點將創建新用戶，並立即將你帶到「更改用戶」視窗，你可以在其中更改用戶名並為用戶模型的可選字段添加信息。這些字段包括名字，姓氏，電子郵件地址，用戶狀態和權限（僅應設置「活動」標誌）。在更下方的位置，你可以指定用戶的組和權限，並查看與該用戶相關的重要日期（例如，他們的加入日期和上次登錄日期）。

 ![Admin site - add user pt2](/zh-TW/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_user_prt2.png)
5. 在「組」部分中，從「可用組」列表中選擇「**Library Member**」組，然後按框之間的右箭頭將其移至「選擇的組」框中。
 ![Admin site - add user to group](/zh-TW/docs/Learn/Server-side/Django/Authentication/admin_authentication_user_add_group.png)
6. 我們在這裡不需要執行任何其他操作，因此只需再次選擇 **SAVE** 即可進入用戶列表。

就是這樣而已！ 現在，你將擁有一個「普通庫成員」帳戶，你將可以使用該帳戶進行測試（一旦我們實現了頁面以使其能夠登錄）。

**備註：** 你應該嘗試創建另一個庫成員用戶。另外，為圖書館員創建一個組，並為其添加用戶！

Setting up your authentication views
------------------------------------

Django 提供了創建身份驗證頁面所需的幾乎所有內容，以處理「開箱即用」的登錄，註銷和密碼管理。這包括 URL 映射器，視圖和表單，但不包括模板-我們必須創建自己的模板！

在本節中，我們顯示如何將默認系統集成到本地圖書館網站中並創建模板。我們將它們放在主項目 URL 中。

**備註：** 你不必使用任何代碼，但是你可能想要使用它，因為它使事情變得容易得多。如果你更改用戶模型（一個高級主題！），幾乎可以肯定需要更改表單處理代碼，但是即使如此，你仍然可以使用庫存視圖功能。

**備註：** 在這種情況下，我們可以合理地將身份驗證頁面（包括 URL 和模板）放入目錄應用程序中。但是，如果我們有多個應用程序，最好將這種共享的登錄行為分開，並使其在整個站點中都可用，這就是我們在此處顯示的內容！

### Project URLs

將以下內容添加到項目 urls.py 文件（**locallibrary/locallibrary/urls.py**）文件的底部：

```python
#Add Django site authentication urls (for login, logout, password management)
urlpatterns += [
    path('accounts/', include('django.contrib.auth.urls')),
]
```

導航到 `http://127.0.0.1:8000/accounts/` URL（注意尾隨斜杠！），然後 Django 將顯示一個錯誤，指出找不到此 URL，並列出了它嘗試的所有 URL。從中你可以看到將起作用的 URL，例如：

**備註：** 使用上述方法會在方括號中添加以下網址，這些網址可用於反轉網址映射。你無需執行其他任何操作-上面的 url 映射會自動映射以下提到的 URL。

```python
accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password\_change']
accounts/ password_change/done/ [name='password\_change\_done']
accounts/ password_reset/ [name='password\_reset']
accounts/ password_reset/done/ [name='password\_reset\_done']
accounts/ reset/<uidb64>/<token>/ [name='password\_reset\_confirm']
accounts/ reset/done/ [name='password\_reset\_complete']
```

現在嘗試導航到登錄 URL（`http://127.0.0.1:8000/accounts/login/`）。這將再次失敗，但是會顯示一條錯誤消息，告訴你我們在模板搜索路徑上缺少必需的模板（**registration/login.html**）。你會在頂部黃色部分看到以下幾行：

```python
Exception Type:    TemplateDoesNotExist
Exception Value:    registration/login.html
```

下一步是在搜索路徑上創建註冊目錄，然後添加 **login.html** 文件。

### Template directory

我們剛剛添加的 url（和隱式視圖）期望在模板搜索路徑中某個目錄 **/registration/** 中找到它們的關聯模板。

對於這個網站，我們將 HTML 頁面放在 **templates/registration/** 目錄中。此目錄應位於你的項目根目錄中，即與 **catalog** 和 **locallibrary** 文件夾相同的目錄中）。請立即創建這些文件夾。

**備註：** Your folder structure should now look like the below:
 locallibrary (django project folder)
 |\_catalog
 |\_locallibrary
 |\_templates **(new)**
 |\_registration

為了使這些目錄對模板加載器可見（即將該目錄放置在模板搜索路徑中），請打開項目設置（**/locallibrary/locallibrary/settings.py**），並更新 `TEMPLATES` 部分的 `DIRS` 行，如圖所示。

```python
TEMPLATES = [
    {
        ...
        'DIRS': ['./templates',],
        'APP\_DIRS': True,
        ...
```

### Login template

**警告：** 本文提供的身份驗證模板是 Django 演示登錄模板的非常基本/稍作修改的版本。你可能需要自定義它們以供自己使用！

創建一個名為 **/locallibrary/templates/registration/login.html** 的新 HTML 文件。為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}

  {% if form.errors %}
    <p>Your username and password didn't match. Please try again.</p>
  {% endif %}

  {% if next %}
    {% if user.is\_authenticated %}
      <p>Your account doesn't have access to this page. To proceed,
      please login with an account that has access.</p>
    {% else %}
      <p>Please login to see this page.</p>
    {% endif %}
  {% endif %}

  <form method="post" action="{% url 'login' %}">
    {% csrf\_token %}
    <table>
      <tr>
        <td>{{ form.username.label\_tag }}</td>
        <td>{{ form.username }}</td>
      </tr>
      <tr>
        <td>{{ form.password.label\_tag }}</td>
        <td>{{ form.password }}</td>
      </tr>
    </table>
    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next }}">
  </form>

  {# Assumes you setup the password\_reset view in your URLconf #}
  <p><a href="{% url 'password\_reset' %}">Lost password?</a></p>

{% endblock %}
```

該模板與我們之前看到的模板有一些相似之處-它擴展了我們的基本模板並覆蓋了內容塊。其餘代碼是相當標準的表單處理代碼，我們將在以後的教程中進行討論。現在你只需要知道的是，這將顯示一個表格，你可以在其中輸入用戶名和密碼，並且如果輸入無效的值，則在頁面刷新時會提示你輸入正確的值。

保存模板後，導航回到登錄頁面（`http://127.0.0.1:8000/accounts/login/`），你應該看到類似以下內容：

![Library login page v1](/zh-TW/docs/Learn/Server-side/Django/Authentication/library_login.png)

如果嘗試登錄將成功，並且你將被重定向到另一個頁面（默認情況下為 `http://127.0.0.1:8000/accounts/profile/`）。這裡的問題是，默認情況下，Django 期望登錄後將你帶到個人資料頁面，情況可能與否。由於你尚未定義此頁面，因此會出現另一個錯誤！

打開項目設置（**/locallibrary/locallibrary/settings.py**），然後將下面的文本添加到底部。現在，當你登錄時，默認情況下應將你重定向到網站主頁。

```python
# Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'
```

### Logout template

如果你導航到登出 URL（`http://127.0.0.1:8000/accounts/logout/`），則會看到一些奇怪的行為-你的用戶將被確定地註銷，但是你將被帶到 **Admin** 註銷頁面。那不是你想要的，僅僅是因為該頁面上的登錄鏈接將你帶到 **Admin** 登錄屏幕（並且僅對具有 `is_staff` 權限的用戶可用）。

創建並打開 **/locallibrary/templates/registration/logged\_out.html**。複製以下文本：

```django
{% extends "base\_generic.html" %}

{% block content %}
  <p>Logged out!</p>
  <a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}
```

這個模板非常簡單。它僅顯示一條消息，通知你已註銷，並提供一個鏈接，你可以按此鏈接返回登錄屏幕。如果再次進入註銷 URL，你應該看到以下頁面：

![Library logout page v1](/zh-TW/docs/Learn/Server-side/Django/Authentication/library_logout.png)

### Password reset templates

默認的密碼重置系統使用電子郵件向用戶發送重置鏈接。你需要創建表格以獲取用戶的電子郵件地址，發送電子郵件，允許他們輸入新密碼並在整個過程完成時註明。

以下模板可以用作起點。

#### 密碼重設表格

這是用於獲取用戶電子郵件地址（用於發送密碼重置電子郵件）的表格。創建 **/locallibrary/templates/registration/password\_reset\_form.html**，並為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}
  <form action="" method="post">
  {% csrf\_token %}
  {% if form.email.errors %}
    {{ form.email.errors }}
  {% endif %}
      <p>{{ form.email }}</p>
    <input type="submit" class="btn btn-default btn-lg" value="Reset password">
  </form>
{% endblock %}
```

#### 密碼重置完成

收集你的電子郵件地址後，將顯示此表單。創建 **/locallibrary/templates/registration/password\_reset\_done.html**，並為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}
  <p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
{% endblock %}
```

#### 密碼重置電子郵件

該模板提供了 HTML 電子郵件的文本，其中包含我們將發送給用戶的重置鏈接。創建 **/locallibrary/templates/registration/password\_reset\_email.html**，並為其提供以下內容：

```django
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol }}://{{ domain }}{% url 'password\_reset\_confirm' uidb64=uid token=token %}
```

#### 密碼重置確認

單擊密碼重置電子郵件中的鏈接後，即可在此頁面輸入新密碼。創建 **/locallibrary/templates/registration/password\_reset\_confirm.html**，並為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}
    {% if validlink %}
        <p>Please enter (and confirm) your new password.</p>
        <form action="" method="post">
        {% csrf\_token %}
            <table>
                <tr>
                    <td>{{ form.new\_password1.errors }}
                        <label for="id\_new\_password1">New password:</label></td>
                    <td>{{ form.new\_password1 }}</td>
                </tr>
                <tr>
                    <td>{{ form.new\_password2.errors }}
                        <label for="id\_new\_password2">Confirm password:</label></td>
                    <td>{{ form.new\_password2 }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="Change my password"></td>
                </tr>
            </table>
        </form>
    {% else %}
        <h1>Password reset failed</h1>
        <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
    {% endif %}
{% endblock %}
```

#### 密碼重置結束

這是最後一個密碼重設模板，密碼重設成功後將顯示此模板以通知你。創建 **/locallibrary/templates/registration/password\_reset\_complete.html**，並為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}
  <h1>The password has been changed!</h1>
  <p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}
```

### Testing the new authentication pages

現在你已經添加了 URL 配置並創建了所有這些模板，身份驗證頁面現在應該可以正常工作了！

你可以通過嘗試使用以下 URL 登錄然後註銷超級用戶帳戶來測試新的身份驗證頁面：

* `http://127.0.0.1:8000/accounts/login/`
* `http://127.0.0.1:8000/accounts/logout/`

你可以通過登錄頁面中的鏈接測試密碼重置功能。**請注意，Django 只會將重置電子郵件發送到已經存儲在其數據庫中的地址（用戶）！**

**備註：** 密碼重設系統要求你的網站支持電子郵件，這不在本文的討論範圍之內，因此該部分尚無法使用。要進行測試，請將以下行放在 settings.py 文件的末尾。這將記錄發送到控制台的所有電子郵件（因此你可以從控制台複製密碼重置鏈接）。

```python
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
```

有關更多信息，請參閱發送電子郵件 Django 文檔。

針對經過身份驗證的用戶進行測試
---------------

本節介紹如何根據用戶是否登錄來有選擇地控制用戶看到的內容。

### 在模板中測試

你可以使用 `{{ user }}` 模板變量在模板中獲取有關當前登錄用戶的信息（默認情況下，就像我們在框架中一樣設置項目時，該信息會添加到模板上下文中）。

通常，你將首先針對 `{{ user.is_authenticated }}` 模板變量進行測試，以確定該用戶是否有資格查看特定內容。為了演示這一點，接下來，我們將更新邊欄，以在用戶註銷時顯示「登錄」鏈接，在用戶登錄時顯示「註銷」鏈接。

打開基礎模板（**/locallibrary/catalog/templates/base\_generic.html**），然後將以下文本複製到 `sidebar` 塊中，緊接在 `endblock` 模板標籤之前。

```django
  <ul class="sidebar-nav">

    …

   {% if user.is\_authenticated %}
     <li>User: {{ user.get\_username }}</li>
     <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li>
   {% else %}
     <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}
  </ul>
```

如你所見，我們使用 `if`-`else`-`endif` 模板標籤根據 `{{ user.is_authenticated }}` 是否為真來有條件地顯示文本。如果用戶通過了身份驗證，那麼我們知道我們有一個有效的用戶，因此我們調用 `{{ user.get_username }}` 來顯示其名稱。

我們使用 `url` 模板標記和相應 URL 配置的名稱來創建登錄和註銷鏈接 URL。還要注意我們如何將 `?next={{request.path}}` 附加到 URL 的末尾。這是在鏈接的 URL 的末尾添加一個 URL 參數，其中包含當前頁面的地址（URL）。用戶成功登錄/註銷後，視圖將使用此 `next` 值將用戶重定向到他們首先單擊 login/logout 鏈接的頁面。

**備註：** 試試看！如果你在主頁上，然後單擊側欄中的「Login/Logout」，那麼在操作完成後，你應該回到同一頁面。

### 在視圖中測試

如果你使用的是基於函數的視圖，則限制訪問函數的最簡單方法是將 `login_required` 裝飾器應用於視圖函數，如下所示。如果用戶已登錄，則你的視圖代碼將正常執行。如果用戶未登錄，它將重定向到項目設置（`settings.LOGIN_URL`）中定義的登錄 URL，並將當前的絕對路徑作為 `next` URL 參數傳遞。如果用戶成功登錄，則他們將返回此頁面，但這次已通過身份驗證。

```python
from django.contrib.auth.decorators import login_required

@login\_required
def my\_view(request):
    ...
```

**備註：** 你可以通過在 `request.user.is_authenticated` 上進行測試來手動執行相同的操作，但是裝飾器要方便得多！

同樣，在基於類的視圖中限制對登錄用戶的訪問權限的最簡單方法是從 `LoginRequiredMixin` 派生。你需要首先在父類列表中，在主視圖類之前聲明此混合。

```python
from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    ...
```

它具有與 `login_required` 裝飾器完全相同的重定向行為。如果用戶未通過身份驗證，也可以指定其他位置來重定向用戶（`login_url`），並使用 URL 參數名稱代替「 next」來插入當前的絕對路徑（`redirect_field_name`）。

```python
class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect\_to'
```

有關更多詳細信息，請在此處查看 Django 文檔。

範例—列出當前用戶的書籍
------------

現在，我們知道瞭如何將頁面限制為特定用戶，讓我們創建當前用戶借閱的書籍的視圖。

不幸的是，我們還沒有任何方式讓用戶借書！因此，在創建圖書清單之前，我們將首先擴展 `BookInstance` 模型以支持借用的概念，並使用 Django Admin 應用程序將大量圖書借給我們的測試用戶。

### 模型

首先，我們將必須使用戶可以藉用 `BookInstance`（我們已經具有`status` 和`due_back`，但是在該模型和 User 之間還沒有任何關聯。我們將創建一個使用 `ForeignKey`（一對多）字段的方法，我們還需要一種簡單的機制來測試借出的書是否過期。

打開 **catalog/models.py**，然後從 `django.contrib.auth.models` 導入 `User` 模型（將其添加到文件頂部的前一個導入行下面，因此 `User` 可供使用它的後續代碼使用）：

```python
from django.contrib.auth.models import User
```

Ne 接下來，將`borrower` 字段添加到`BookInstance` 模型中：

```python
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
```

當我們在這裡時，讓我們添加一個屬性，我們可以從模板中調用該屬性，以告知特定的圖書實例是否過期。儘管我們可以在模板本身中進行計算，但是使用如下所示的屬性會更加高效。

將此添加到文件頂部附近：

```python
from datetime import date
```

現在，在`BookInstance`類中添加以下屬性定義：

```python
@property
def is\_overdue(self):
    if self.due_back and date.today() > self.due_back:
        return True
    return False
```

**備註：** 在進行比較之前，我們首先要驗證 `due_back`是否為空。空的 `due_back` 字段將導致 Django 拋出錯誤而不是顯示頁面：空值不可比。這不是我們希望用戶體驗的東西！

現在，我們已經更新了模型，我們需要在項目上進行新的遷移，然後應用這些遷移：

```bash
python3 manage.py makemigrations
python3 manage.py migrate
```

### Admin

現在打開 **catalog/admin.py**，然後將 `list_display` 和 `fieldsets` 中的 `borrower` 字段添加到 `BookInstanceAdmin` 類中，如下所示。這將使該字段在「管理」部分中可見，以便我們可以在需要時將 `User` 分配給 `BookInstance`。

```python
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
    list_display = ('book', 'status', 'borrower', 'due\_back', 'id')
    list_filter = ('status', 'due\_back')

    fieldsets = (
        (None, {
            'fields': ('book','imprint', 'id')
        }),
        ('Availability', {
            'fields': ('status', 'due\_back','borrower')
        }),
    )
```

### Loan a few books

現在可以將書借給特定用戶了，然後借出許多 `BookInstance` 記錄。將他們的 `borrowed` 字段設置為測試用戶，`status` 為「借用」，並設置將來和將來的到期日。

**備註：** 我們不會詳細說明該過程，因為你已經知道如何使用管理網站！

### On loan view

現在，我們將添加一個視圖，以獲取已借給當前用戶的所有書籍的列表。我們將使用我們熟悉的相同的通用的基於類的列表視圖，但是這次我們還將導入並從 `LoginRequiredMixin` 派生，以便只有登錄的用戶才能調用此視圖。我們還將選擇聲明 `template_name`，而不使用默認值，因為我們最終可能會擁有一些不同的 BookInstance 記錄列表，並具有不同的視圖和模板。

將以下內容添加到 **catalog/views.py**：

```python
from django.contrib.auth.mixins import LoginRequiredMixin

class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
    """Generic class-based view listing books on loan to current user."""
    model = BookInstance
    template_name ='catalog/bookinstance\_list\_borrowed\_user.html'
    paginate_by = 10

    def get\_queryset(self):
        return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due\_back')
```

為了將查詢限制為僅針對當前用戶的 `BookInstance` 對象，我們重新實現了 `get_queryset()`，如上所示。請注意，「o」是「借出」的存儲代碼，我們在 `due_back` 日期之前訂購，以便最先顯示最早的項目。

### URL conf for on loan books

現在打開\*\*/catalog/urls.py\*\*並添加指向上面視圖的`path()`（你可以將下面的文本複製到文件末尾）。

```python
urlpatterns += [
    path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]
```

### Template for on loan books

現在，我們需要為此頁面添加模板。首先，創建模板文件 **/catalog/templates/catalog/bookinstance\_list\_borrowed\_user.html** 並為其提供以下內容：

```django
{% extends "base\_generic.html" %}

{% block content %}
    <h1>Borrowed books</h1>

    {% if bookinstance\_list %}
    <ul>

      {% for bookinst in bookinstance\_list %}
      <li class="{% if bookinst.is\_overdue %}text-danger{% endif %}">
        <a href="{% url 'book-detail' bookinst.book.pk %}">{{ bookinst.book.title }}</a> ({{ bookinst.due\_back }})
      </li>
      {% endfor %}
    </ul>

    {% else %}
      <p>There are no books borrowed.</p>
    {% endif %}
{% endblock %}
```

該模板與我們先前為 `Book` 和 `Author` 物件創建的模板非常相似。這裡唯一的「新內容」是我們檢查在模型中添加的方法（`bookinst.is_overdue`），並使用它來更改過期項目的顏色。

開發服務器運行時，現在應該可以在瀏覽器中的 `http://127.0.0.1:8000/catalog/mybooks/` 上查看已登錄用戶的列表。在你的用戶登錄和註銷後進行嘗試（在第二種情況下，應將你重定向到登錄頁面）。

### Add the list to the sidebar

最後一步是將此新頁面的鏈接添加到側欄中。我們將其放在同一部分中，在該部分中為登錄用戶顯示其他信息。

打開基本模板（**/locallibrary/catalog/templates/base\_generic.html**）並將粗體顯示的行添加到側邊欄中，如圖所示。

```django
 <ul class="sidebar-nav">
   {% if user.is\_authenticated %}
   <li>User: {{ user.get\_username }}</li>

   <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>

   <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li>
   {% else %}
   <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}
 </ul>
```

### What does it look like?

當任何用戶登錄後，他們將在邊欄中看到「*My Borrowed*」，並且書的列表顯示如下（第一本書沒有截止日期，這是我們希望在以後的教程中解決的錯誤！） 。

![Library - borrowed books by user](/zh-TW/docs/Learn/Server-side/Django/Authentication/library_borrowed_by_user.png)

Permissions
-----------

權限與模型相關聯，並定義了具有權限的用戶可以在模型實例上執行的操作。默認情況下，Django 會自動為所有模型賦予添加，更改和刪除權限，從而允許具有權限的用戶通過管理站點執行關聯的操作。你可以定義自己的模型權限，並將其授予特定用戶。你還可以更改與同一模型的不同實例關聯的權限。

這樣，對視圖和模板中的權限進行的測試就非常類似於對身份驗證狀態的測試（實際上，對權限的測試也對身份驗證進行了測試）。

### Models

使用`permissions` 字段在模型「`class Meta`」部分中完成權限的定義。你可以在元組中根據需要指定任意數量的權限，每個權限本身都在嵌套的元組中定義，其中包含權限名稱和權限顯示值。例如，我們可以定義一個權限，以允許用戶標記已退回一本書，如下所示：

```python
class BookInstance(models.Model):
    ...
    class Meta:
        ...
        permissions = (("can\_mark\_returned", "Set book as returned"),)
```

然後，我們可以將權限分配給管理站點中的「圖書管理員」組。

打開 **catalog/models.py**，然後添加權限，如上所示。你將需要重新運行遷移（調用 `python3 manage.py makemigrations` 和 `python3 manage.py migrate`）以適當地更新數據庫。

### 模板

當前用戶的權限存儲在名為 `{{ perms }}` 的模板變量中。你可以使用關聯的 Django 應用中的特定變量名稱來檢查當前用戶是否具有特定權限，例如 如果用戶具有此權限，則 `{{ perms.catalog.can_mark_returned }}` 將為 `True`，否則為`False`。我們通常使用模板 `{% if %}` 標籤測試權限，如下所示：

```django
{% if perms.catalog.can\_mark\_returned %}
    <!-- We can mark a BookInstance as returned. -->
    <!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
```

### 視圖

可以在功能視圖中使用 `permission_required` 裝飾器來測試權限，或者在基於類的視圖中使用 `PermissionRequiredMixin` 來測試權限。模式和行為與登錄身份驗證的模式和行為相同，儘管當然你可能必須合理地添加多個權限。

視圖裝飾器函數：

```python
from django.contrib.auth.decorators import permission_required

@permission\_required('catalog.can\_mark\_returned')
@permission\_required('catalog.can\_edit')
def my\_view(request):
    ...
```

基於類的視圖需要權限的混合。

```python
from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'catalog.can\_mark\_returned'
    # Or multiple permissions
    permission_required = ('catalog.can\_mark\_returned', 'catalog.can\_edit')
    # Note that 'catalog.can\_edit' is just an example
    # the catalog application doesn't have such permission!
```

### 範例

我們不會在這裡更新本地圖書館；也許在下一個教程中！

挑戰自己
----

在本文的前面，我們向你展示瞭如何為當前用戶創建一個頁面，列出他們所借用的書。現在的挑戰是創建一個僅對圖書館員可見的相似頁面，該頁面顯示所有已借書的書，其中包括每個借書人的名字。

你應該能夠遵循與其他視圖相同的模式。主要區別在於你只需要將視圖限制為圖書館員即可。你可以根據用戶是否是工作人員來執行此操作（函數裝飾器：`staff_member_required`，模板變量：`user.is_staff`），但是我們建議你改用 `can_mark_returned` 權限和 `PermissionRequiredMixin`，如上一節所述。

**警告：** 請記住不要將你的超級用戶用於基於權限的測試（即使尚未定義權限，權限檢查也始終對超級用戶返回 true！）。而是創建一個圖書管理員用戶，並添加所需的功能。

完成後，你的頁面應類似於以下屏幕截圖。
 ![All borrowed books, restricted to librarian](/zh-TW/docs/Learn/Server-side/Django/Authentication/library_borrowed_all.png)

總結
--

出色的工作-你現在已經創建了一個網站，圖書館成員可以登錄並查看他們自己的內容，館員（具有正確的權限）可以用來查看所有借出的書及其借書人。目前，我們仍在查看內容，但是當你要開始修改和添加數據時，將使用相同的原理和技術。

在下一篇文章中，我們將研究如何使用 Django 表單來收集用戶輸入，然後開始修改一些存儲的數據。

參見
--

* User authentication in Django (Django docs)
* Using the (default) Django authentication system (Django docs)
* Introduction to class-based views > Decorating class-based views (Django docs)
* 前頁
* Overview: Django
* 次頁


Django Tutorial Part 6: Generic list and detail views - 學習該如何開發 Web

Django Tutorial Part 6: Generic list and detail views
=====================================================

* 前頁
* Overview: Django
* 次頁

本教程擴充了 LocalLibrary 網站，為書本與作者增加列表與細節頁面。此處我們將學到通用類別視圖，並演示如何降低你必須為一般使用案例撰寫的程式碼數量。我們也會更加深入 URL 處理細節，演示如何實施基本模式匹配。

|  |  |
| --- | --- |
| 前提: |
 Complete all previous tutorial topics, including
 Django Tutorial Part 5: Creating our home page.
  |
| 目的: |
 To understand where and how to use generic class-based views, and how to
 extract patterns from URLs and pass the information to views.
  |

Overview
--------

本教程中，通過為書本和作者添加列表和詳細信息頁面，我們將完成第一個版本的 LocalLibrary 網站（或者更準確地說，我們將向你展示如何實現書頁，並讓你自己創建作者頁面！） ）

該過程在創建索引頁面，我們在上一個教程中展示了該頁面。我們仍然需要創建 URL 地圖，視圖和模板。主要區別在於，對於詳細信息頁面，我們還有一個額外的挑戰，即從 URL 對於這些頁面，我們將演示一種完全不同的視圖類型：基於類別的通用列表和詳細視圖。這些可以顯著減少所需的視圖代碼量，有助於更容易編寫和維護。

本教程的最後一部分，將演示在使用基於類別的通用列表視圖時，如何對數據進行分頁。

Book list page
--------------

該書將顯示每條記錄的標題和作者，標題是指向相關圖書詳細信息頁面的超鏈接。該頁面將具有與站點中，所有其他頁面相同的結構和導航，因此，我們可以擴展在上一個教程中創建的基本模板 (**base\_generic.html**)。

### URL mapping

開啟/catalog/urls.py，並複製加入下面粗體顯示的代碼。就像索引頁面的方式，這個 path（）函數，定義了一個與 URL 匹配的模式（'books /'），如果 URL 匹配，將調用視圖函數（views.BookListView.as\_view（））和一個對應的特定映射的名稱。

```python
urlpatterns = [
    path('', views.index, name='index'),
    path('books/', views.BookListView.as_view(), name='books'),
]
```

正如前一個教程中所討論的，URL 必須已經先匹配了/ catalog，因此實際上將為 URL 調用的視圖是：/ catalog / books /。

我們將繼承現有的泛型視圖函數，該函數已經完成了我們希望此視圖函數執行的大部分工作，而不是從頭開始編寫自己的函數。對於基於 Django 類的視圖，我們通過調用類方法 as\_view（），來訪問適當的視圖函數。由此可以創建類的實例，並確保為 HTTP 請求正確的處理程序方法。

### View (class-based)

我們可以很容易地，將書本列表列表編寫為常規函數（就像我們之前的索引視圖一樣），進入查詢數據庫中的所有書本，然後調用 render（），將列表傳遞給指定的模板。然而，我們用另一種方 法取代，我們將使用基於類的通用列表視圖（ListView）-一個繼承自現有視圖的類。因為通用視圖，已經實現了我們需要的大部分功能，並且遵循 Django 最佳實踐，我們將能夠創建更強大的列表視圖，代碼更多，重複次數最多，最終維護所需。

開啟 catalog / views.py，將以下代碼複製到文件的底部：

```python
from django.views import generic

class BookListView(generic.ListView):
    model = Book
```

就是這樣！通用 view 將查詢數據庫，以獲取指定模型（Book）的所有記錄，然後呈現/locallibrary/catalog/templates/catalog/book\_list.html 的模板（我們將在下面創建）。在模板中，你可以使用所謂的 object\_list 或 book\_list 的模板變量（即通常為「 the\_model\_name\_list」），以訪問書本列表。

**備註：** This awkward path for the template location isn't a misprint — the generic views look for templates in `/application_name/the_model_name_list.html` (`catalog/book_list.html` in this case) inside the application's `/application_name/templates/` directory (`/catalog/templates/)`.

你可以添加屬性，以更改上面的某種行為。例如，如果需要使用同一模型的多個視圖，則可以指定另一個模板文件，或者如果 book\_list 對於特定模板用例不直觀，則可能需要使用不同的模板變量名稱。可能最有用的變更，是更改/過濾返回的結果子集-因此，你可能會列出其他用戶閱讀的前 5 本書，而不是列出所有書本。

```python
class BookListView(generic.ListView):
    model = Book
    context_object_name = 'my\_book\_list'   # your own name for the list as a template variable
    queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
    template_name = 'books/my\_arbitrary\_template\_name\_list.html'  # Specify your own template name/location
```

#### Overriding methods in class-based views

雖然我們不需要在這裡執行此操作，但你也可以覆寫某些類別方法。

例如，我們可以覆寫 get\_queryset（）方法，來更改返回的記錄列表。這比單獨設置 queryset 屬性更靈活，就像我們在前面的代碼片段中進行的那樣（儘管在這案例中沒有太大用處）：

```python
class BookListView(generic.ListView):
    model = Book

    def get\_queryset(self):
        return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
```

我們還可以重寫`get_context_data()` 以便將其他上下文變數傳遞給模組 (例如，默認情況下傳遞書籍列表). 下面的片段顯示瞭如何向上下文添加名為"`some_data`" 的變數（然後它將用作模組變數）

```python
class BookListView(generic.ListView):
    model = Book

    def get\_context\_data(self, \*\*kwargs):
        # Call the base implementation first to get the context
        context = super(BookListView, self).get_context_data(\*\*kwargs)
        # Create any data and add it to the context
        context['some\_data'] = 'This is just some data'
        return context
```

執行此操作時，務必遵循上面使用的模式：

* 首先從我們的 superclass 中獲取現有內文。
* 然後添加新的內文信息。
* 然後返回新的（更新後）內文。

**備註：** Check out Built-in class-based generic views (Django docs) for many more examples of what you can do.

### Creating the List View template

建立 HTML 及複製以下文字串到\*\*/locallibrary/catalog/templates/catalog/book\_list.html\*\* , 這是基於通用類的列表視圖所期望的默認模板文件 (默認在`catalog中名稱為Book` 的模組).

通用的 views 模板跟其他的模板沒有不同 (儘管傳遞給模板的內文/訊息當然可以不同). 與 index 模板一樣，我們在第一行中擴展了基本模板，然後更替名為 `content`的區塊。

```django
{% extends "base\_generic.html" %}

{% block content %}
  <h1>Book List</h1>
  {% if book\_list %}
    <ul>
      {% for book in book\_list %}
      <li>
        <a href="{{ book.get\_absolute\_url }}">{{ book.title }}</a>
        ({{book.author}})
      </li>
      {% endfor %}
    </ul>
  {% else %}
    <p>There are no books in the library.</p>
  {% endif %}
{% endblock %}
```

該視圖默認將上下文（書籍列表）作為`object_list` 和 `book_list` 別名傳遞;兩者都會起作用.

#### Conditional execution

我們使用 `if`, `else` 和 `endif` 模組標籤，以檢查`book_list` 是否已定義並且不為空。 如果 `book_list` 為空值, 則 `else` 子句回傳 text 說明沒有書可以列出. 如果`book_list`不是空值, 然後我們遍曆書籍清單。

```django
{% if book\_list %}
  <!-- code here to list the books -->
{% else %}
  <p>There are no books in the library.</p>
{% endif %}
```

The condition above only checks for one case, but you can test on additional conditions using the `elif` template tag (e.g. `{% elif var2 %}` ). For more information about conditional operators see: if, ifequal/ifnotequal, and ifchanged in Built-in template tags and filters (Django Docs).

#### For loops

The template uses the for and `endfor` template tags to loop through the book list, as shown below. Each iteration populates the `book` template variable with information for the current list item.

```django
{% for book in book\_list %}
  <li><!-- code here get information from each book item --></li>
{% endfor %}
```

While not used here, within the loop Django will also create other variables that you can use to track the iteration. For example, you can test the `forloop.last` variable to perform conditional processing the last time that the loop is run.

#### Accessing variables

The code inside the loop creates a list item for each book that shows both the title (as a link to the yet-to-be-created detail view) and the author.

```html
<a href="{{ book.get\_absolute\_url }}">{{ book.title }}</a> ({{book.author}})
```

We access the *fields* of the associated book record using the "dot notation" (e.g. `book.title` and `book.author`), where the text following the `book` item is the field name (as defined in the model).

We can also call *functions* in the model from within our template — in this case we call `Book.get_absolute_url()` to get an URL you could use to display the associated detail record. This works provided the function does not have any arguments (there is no way to pass arguments!)

**備註：** We have to be a little careful of "side effects" when calling functions in templates. Here we just get a URL to display, but a function can do pretty much anything — we wouldn't want to delete our database (for example) just by rendering our template!

#### Update the base template

Open the base template (**/locallibrary/catalog/templates/*base\_generic.html***) and insert **{% url 'books' %}** into the URL link for **All books**, as shown below. This will enable the link in all pages (we can successfully put this in place now that we've created the "books" url mapper).

```python
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="{% url 'books' %}">All books</a></li>
<li><a href="">All authors</a></li>
```

### What does it look like?

You won't be able to build book list yet, because we're still missing a dependency — the URL map for the book detail pages, which is needed to create hyperlinks to individual books. We'll show both list and detail views after the next section.

Book detail page
----------------

The book detail page will display information about a specific book, accessed using the URL `catalog/book/<id>` (where `<id>` is the primary key for the book). In addition to fields in the `Book` model (author, summary, ISBN, language, and genre), we'll also list the details of the available copies (`BookInstances`) including the status, expected return date, imprint, and id. This will allow our readers not just to learn about the book, but also to confirm whether/when it is available.

### URL mapping

Open **/catalog/urls.py** and add the '**book-detail**' URL mapper shown in bold below. This `path()` function defines a pattern, associated generic class-based detail view, and a name.

```python
urlpatterns = [
    path('', views.index, name='index'),
    path('books/', views.BookListView.as_view(), name='books'),
    path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]
```

For the *book-detail* path the URL pattern uses a special syntax to capture the specific id of the book that we want to see. The syntax is very simple: angle brackets define the part of the URL to be captured, enclosing the name of the variable that the view can use to access the captured data. For example, **<something>** , will capture the marked pattern and pass the value to the view as a variable "something". You can optionally precede the variable name with a converter specification that defines the type of data (int, str, slug, uuid, path).

In this case we use `'<int:pk>'` to capture the book id, which must be an integer, and pass it to the view as a parameter named `pk` (short for primary key).

**備註：** As discussed previously, our matched URL is actually `catalog/book/<digits>` (because we are in the **catalog** application, `/catalog/` is assumed).

**警告：** The generic class-based detail view *expects* to be passed a parameter named **pk**. If you're writing your own function view you can use whatever parameter name you like, or indeed pass the information in an unnamed argument.

#### Advanced path matching/regular expression primer

**備註：** You won't need this section to complete the tutorial! We provide it because knowing this option is likely to be useful in your Django-centric future.

The pattern matching provided by `path()` is simple and useful for the (very common) cases where you just want to capture *any* string or integer. If you need more refined filtering (for example, to filter only strings that have a certain number of characters) then you can use the re\_path() method.

This method is used just like `path()` except that it allows you to specify a pattern using a Regular expression. For example, the previous path could have been written as shown below:

```python
re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
```

*Regular expressions* are an incredibly powerful pattern mapping tool. They are, frankly, quite unintuitive and scary for beginners. Below is a very short primer!

The first thing to know is that regular expressions should usually be declared using the raw string literal syntax (i.e. they are enclosed as shown: **r'<your regular expression text goes here>'**).

The main parts of the syntax you will need to know for declaring the pattern matches are:

| Symbol | Meaning |
| --- | --- |
| ^ | Match the beginning of the text |
| $ | Match the end of the text |
| \d | Match a digit (0, 1, 2, ... 9) |
| \w | Match a word character, e.g. any upper- or lower-case character in the alphabet, digit or the underscore character (\_) |
| + | Match one or more of the preceding character. For example, to match one or more digits you would use `\d+`. To match one or more "a" characters, you could use `a+` |
| \* | Match zero or more of the preceding character. For example, to match nothing or a word you could use `\w*` |
| ( ) | Capture the part of the pattern inside the brackets. Any captured values will be passed to the view as unnamed parameters (if multiple patterns are captured, the associated parameters will be supplied in the order that the captures were declared). |
| (?P<*name*>...) | Capture the pattern (indicated by ...) as a named variable (in this case "name"). The captured values are passed to the view with the name specified. Your view must therefore declare an argument with the same name! |
| [ ] | Match against one character in the set. For example, [abc] will match on 'a' or 'b' or 'c'. [-\w] will match on the '-' character or any word character. |

Most other characters can be taken literally!

Lets consider a few real examples of patterns:

| Pattern | Description |
| --- | --- |
| **r'^book/(?P<pk>\d+)$'** |
 This is the RE used in our url mapper. It matches a string that has
 `book/` at the start of the line (**^book/**),
 then has one or more digits (`\d+`), and then ends (with no
 non-digit characters before the end of line marker).

 It also captures all the digits **(?P<pk>\d+)** and
 passes them to the view in a parameter named 'pk'.
 **The captured values are always passed as a string!**

 For example, this would match `book/1234` , and send a
 variable `pk='1234'` to the view.
  |
| **r'^book/(\d+)$'** |
 This matches the same URLs as the preceding case. The captured
 information would be sent as an unnamed argument to the view.
  |
| **r'^book/(?P<stub>[-\w]+)$'** |
 This matches a string that has `book/` at the start of the
 line (**^book/**), then has one or more characters that
 are *either* a '-' or a word character
 (**[-\w]+**), and then ends. It also captures this set of
 characters and passes them to the view in a parameter named 'stub'.

 This is a fairly typical pattern for a "stub". Stubs are URL-friendly
 word-based primary keys for data. You might use a stub if you wanted
 your book URL to be more informative. For example
 `/catalog/book/the-secret-garden` rather than
 `/catalog/book/33`.
  |

You can capture multiple patterns in the one match, and hence encode lots of different information in a URL.

**備註：** As a challenge, consider how you might encode an url to list all books released in a particular year, month, day, and the RE that could be used to match it.

#### Passing additional options in your URL maps

One feature that we haven't used here, but which you may find valuable, is that you can declare and pass additional options to the view. The options are declared as a dictionary that you pass as the third un-named argument to the `path()` function. This approach can be useful if you want to use the same view for multiple resources, and pass data to configure its behaviour in each case (below we supply a different template in each case).

```python
path('url/', views.my_reused_view, {'my\_template\_name': 'some\_path'}, name='aurl'),
path('anotherurl/', views.my_reused_view, {'my\_template\_name': 'another\_path'}, name='anotherurl'),
```

**備註：** Both extra options and named captured patterns are passed to the view as *named* arguments. If you use the **same name** for both a captured pattern and an extra option then only the captured pattern value will be sent to the view (the value specified in the additional option will be dropped).

### View (class-based)

Open **catalog/views.py**, and copy the following code into the bottom of the file:

```python
class BookDetailView(generic.DetailView):
    model = Book
```

That's it! All you need to do now is create a template called **/locallibrary/catalog/templates/catalog/book\_detail.html**, and the view will pass it the database information for the specific `Book` record extracted by the URL mapper. Within the template you can access the list of books with the template variable named `object` OR `book` (i.e. generically "`the_model_name`").

If you need to, you can change the template used and the name of the context object used to reference the book in the template. You can also override methods to, for example, add additional information to the context.

#### What happens if the record doesn't exist?

If a requested record does not exist then the generic class-based detail view will raise an `Http404` exception for you automatically — in production this will automatically display an appropriate "resource not found" page, which you can customise if desired.

Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-based view as a function, if you were **not** using the generic class-based detail view.

```python
def book\_detail\_view(request, primary_key):
    try:
        book = Book.objects.get(pk=primary_key)
    except Book.DoesNotExist:
        raise Http404('Book does not exist')

    # from django.shortcuts import get\_object\_or\_404
    # book = get\_object\_or\_404(Book, pk=primary\_key)

    return render(request, 'catalog/book\_detail.html', context={'book': book})
```

The view first tries to get the specific book record from the model. If this fails the view should raise an `Http404` exception to indicate that the book is "not found". The final step is then, as usual, to call `render()` with the template name and the book data in the `context` parameter (as a dictionary).

**備註：** The `get_object_or_404()` (shown commented out above) is a convenient shortcut to raise an `Http404` exception if the record is not found.

### Creating the Detail View template

Create the HTML file **/locallibrary/catalog/templates/catalog/book\_detail.html** and give it the below content. As discussed above, this is the default template file name expected by the generic class-based *detail* view (for a model named `Book` in an application named `catalog`).

```django
{% extends "base\_generic.html" %}

{% block content %}
  <h1>Title: {{ book.title }}</h1>

  <p><strong>Author:</strong> <a href="">{{ book.author }}</a></p>
  <!-- author detail link not yet defined -->
  <p><strong>Summary:</strong> {{ book.summary }}</p>
  <p><strong>ISBN:</strong> {{ book.isbn }}</p>
  <p><strong>Language:</strong> {{ book.language }}</p>
  <p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p>

  <div style="margin-left:20px;margin-top:20px">
    <h4>Copies</h4>

    {% for copy in book.bookinstance\_set.all %}
      <hr />
      <p
 class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">
        {{ copy.get\_status\_display }}
      </p>
      {% if copy.status != 'a' %}
        <p><strong>Due to be returned:</strong> {{ copy.due\_back }}</p>
      {% endif %}
      <p><strong>Imprint:</strong> {{ copy.imprint }}</p>
      <p class="text-muted"><strong>Id:</strong> {{ copy.id }}</p>
    {% endfor %}
  </div>
{% endblock %}
```

**備註：** The author link in the template above has an empty URL because we've not yet created an author detail page. Once that exists, you should update the URL like this:

```python
<a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
```

Though a little larger, almost everything in this template has been described previously:

* We extend our base template and override the "content" block.
* We use conditional processing to determine whether or not to display specific content.
* We use `for` loops to loop through lists of objects.
* We access the context fields using the dot notation (because we've used the detail generic view, the context is named `book`; we could also use "`object`")

The one interesting thing we haven't seen before is the function `book.bookinstance_set.all()`. This method is "automagically" constructed by Django in order to return the set of `BookInstance` records associated with a particular `Book`.

```python
{% for copy in book.bookinstance_set.all %}
<!-- code to iterate across each copy/instance of a book -->
{% endfor %}
```

需要這方法是因為我們僅在「一」那側 model（Book）定義一個`ForeignKey` (一對多)字段的關聯，也因為沒有任何的關聯被定義在「多」那側 model（BookInstance），故無法透過字段來取得相關的紀錄。為了克服這個問題，Django 建立一個 function 取名為「reverse lookup」供使用。function 的名字以一對多關係中該 `ForeignKey` 被定義在的那個模型名稱小寫，再在字尾加上`_set`（因此在 `Book` 創建的 function 名是 `bookinstance_set()`）。

**備註：** 在這我們使用 `all()` 取得所有紀錄 (預設)，你無法直接在 template 做是因為你無法指定引數到 function，但你可用 `filter()` 方法取得一個紀錄的子集 。

順帶一提，若你不再基於類的 view 或 model 定義順序（order），開發伺服器會將會報錯類似的訊息：
```

[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637
/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]>
  allow_empty_first_page=allow_empty_first_page, **kwargs)
```

That happens because the paginator object expects to see some ORDER BY being executed on your underlying database. Without it, it can't be sure the records being returned are actually in the right order!

This tutorial didn't reach **Pagination** (yet, but soon enough), but since you can't use `sort_by()` and pass a parameter (the same with `filter()` described above) you will have to choose between three choices:

1. Add a `ordering` inside a `class Meta` declaration on your model.
2. Add a `queryset` attribute in your custom class-based view, specifying a `order_by()`.
3. Adding a `get_queryset` method to your custom class-based view and also specify the `order_by()`.

If you decide to go with a `class Meta` for the `Author` model (probably not as flexible as customizing the class-based view, but easy enough), you will end up with something like this:

```python
class Author(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    date_of_death = models.DateField('Died', null=True, blank=True)

    def get\_absolute\_url(self):
        return reverse('author-detail', args=[str(self.id)])

    def \_\_str\_\_(self):
        return f'{self.last\_name}, {self.first\_name}'

    class Meta:
        ordering = ['last\_name']
```

Of course, the field doesn't need to be `last_name`: it could be any other.

And last, but not least, you should sort by an attribute/column that actually has a index (unique or not) on your database to avoid performance issues. Of course, this will not be necessary here (and we are probably getting ourselves too much ahead) if such small amount of books (and users!), but it is something to keep in mind for future projects.

What does it look like?
-----------------------

At this point we should have created everything needed to display both the book list and book detail pages. Run the server (`python3 manage.py runserver`) and open your browser to http://127.0.0.1:8000/.

**警告：** Don't click any author or author detail links yet — you'll create those in the challenge!

Click the **All books** link to display the list of books.

![Book List Page](/zh-TW/docs/Learn/Server-side/Django/Generic_views/book_list_page_no_pagination.png)

Then click a link to one of your books. If everything is set up correctly, you should see something like the following screenshot.

![Book Detail Page](/zh-TW/docs/Learn/Server-side/Django/Generic_views/book_detail_page_no_pagination.png)

Pagination
----------

If you've just got a few records, our book list page will look fine. However, as you get into the tens or hundreds of records the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this problem is to add pagination to your list views, reducing the number of items displayed on each page.

Django has excellent in-built support for pagination. Even better, this is built into the generic class-based list views so you don't have to do very much to enable it!

### Views

Open **catalog/views.py**, and add the `paginate_by` line shown in bold below.

```python
class BookListView(generic.ListView):
    model = Book
    paginate_by = 10
```

With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the template. The different pages are accessed using GET parameters — to access page 2 you would use the URL: `/catalog/books/?page=2`.

### Templates

Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might want to do this in all list views, we'll do this in a way that can be added to the base template.

Open **/locallibrary/catalog/templates/*base\_generic.html*** and copy in the following pagination block below our content block (highlighted below in bold). The code first checks if pagination is enabled on the current page. If so then it adds next and previous links as appropriate (and the current page number).

```python
{% block content %}{% endblock %}

{% block pagination %}
  {% if is_paginated %}
    <div class="pagination">
      <span class="page-links">
        {% if page_obj.has_previous %}
          <a href="{{ request.path }}?page={{ page\_obj.previous\_page\_number }}">previous</a>
        {% endif %}
        <span class="page-current">
          <p>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</p>
        </span>
        {% if page_obj.has_next %}
          <a href="{{ request.path }}?page={{ page\_obj.next\_page\_number }}">next</a>
        {% endif %}
      </span>
    </div>
  {% endif %}
{% endblock %}
```

The `page_obj` is a Paginator object that will exist if pagination is being used on the current page. It allows you to get all the information about the current page, previous pages, how many pages there are, etc.

We use `{{ request.path }}` to get the current page URL for creating the pagination links. This is useful, because it is independent of the object that we're paginating.

Thats it!

### What does it look like?

The screenshot below shows what the pagination looks like — if you haven't entered more than 10 titles into your database, then you can test it more easily by lowering the number specified in the `paginate_by` line in your **catalog/views.py** file. To get the below result we changed it to `paginate_by = 2`.

The pagination links are displayed on the bottom, with next/previous links being displayed depending on which page you're on.

![Book List Page - paginated](/zh-TW/docs/Learn/Server-side/Django/Generic_views/book_list_paginated.png)

Challenge yourself
------------------

The challenge in this article is to create the author detail and list views required to complete the project. These should be made available at the following URLs:

* `catalog/authors/` — The list of all authors.
* `catalog/author/<id>` — The detail view for the specific author with a primary key field named `<id>`

The code required for the URL mappers and the views should be virtually identical to the `Book` list and detail views we created above. The templates will be different, but will share similar behaviour.

**備註：**

* Once you've created the URL mapper for the author list page you will also need to update the **All authors** link in the base template. Follow the same process as we did when we updated the **All books** link.
* Once you've created the URL mapper for the author detail page, you should also update the book detail view template (**/locallibrary/catalog/templates/catalog/book\_detail.html**) so that the author link points to your new author detail page (rather than being an empty URL). The line will change to add the template tag shown in bold below.

```django
<p>
  <strong>Author:</strong>
  <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
</p>
```

When you are finished, your pages should look something like the screenshots below.

![Author List Page](/zh-TW/docs/Learn/Server-side/Django/Generic_views/author_list_page_no_pagination.png)

![Author Detail Page](/zh-TW/docs/Learn/Server-side/Django/Generic_views/author_detail_page_no_pagination.png)

Summary
-------

Congratulations, our basic library functionality is now complete!

In this article we've learned how to use the generic class-based list and detail views and used them to create pages to view our books and authors. Along the way we've learned about pattern matching with regular expressions, and how you can pass data from URLs to your views. We've also learned a few more tricks for using templates. Last of all we've shown how to paginate list views, so that our lists are managable even when we have many records.

In our next articles we'll extend this library to support user accounts, and thereby demonstrate user authentication, permissons, sessions, and forms.

See also
--------

* Built-in class-based generic views (Django docs)
* Generic display views (Django docs)
* Introduction to class-based views (Django docs)
* Built-in template tags and filters (Django docs).
* Pagination (Django docs)
* 前頁
* Overview: Django
* 次頁