Django Tutorial Part 3: Using models - 學習該如何開發 Web

Django Tutorial Part 3: Using models
====================================

* 前頁
* Overview: Django
* 次頁

本文介紹如何為 LocalLibrary 網站定義模型。它解釋了模型是什麼、聲明的方式以及一些主要字段類型。它還簡要展示了你可以訪問模型數據的幾個主要方法。

|  |  |
| --- | --- |
| 前提: | Django 教學 2: 創建骨架網站。 |
| 目標: | 能夠設計和創建自己的模型，選擇適當的欄位。 |

概覽
--

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

本教程將介紹如何定義和訪問 LocalLibrary 範例網站的模型。

設計 LocalLibrary 模型
------------------

在你投入開始編寫模型之前，花幾分鐘時間考慮我們需要存放的數據、以及不同物件之間的關係。

我們知道，我們需要存放書籍的信息（標題，摘要，作者，語言，類別，ISBN），並且我們可能有多個副本（具有全域唯一的 ID，可用狀態等）。我們可以存放更多關於作者的信息，而不僅僅是他的名字，或多個作者的相同或相似的名稱。我們希望能根據書名，作者名，語言和類別對信息進行排序。

在設計模型時，為每個「物件」分別設置模型（相關信息分組）是有意義的。在這種情況下，明顯的物件是書籍，書本實例和作者。

你可能想要使用模型，來表示選擇列表選項（例如：選擇下拉列表），而不是硬編碼，將選項編寫進網站—這是當所有選項面臨未知、或改變時候的建議。在本網站，模型的明顯候選，包括書籍類型（例如：科幻小說，法國詩歌等）和語言（英語，法語，日語）。

一旦我們已經決定了我們的模型和字段，我們需要考慮它們的關聯性。Django 允許你來定義一對一的關聯（`OneToOneField`），一對多（`ForeignKey`）和多對多（`ManyToManyField`）。

思考一下，在網站中，我們將定義模型展示在下面 UML 關聯圖中（下圖）。如以上，我們創建了書的模型（書的通用細節），書本實例（系統中特定物理副本的書籍狀態），和作者。我們也決定了各類型模型，以便通過管理界面創建／選擇值。我們決定不給`BookInstance:status`一個模型 —我們硬編碼了（`LOAN_STATUS`）的值，因為我們不希望其改變。在每個框中，你可以看到模型名稱，字段名稱和類型，以及方法和返回類型。

該圖顯示模型之間的關係，包括它們的多重性。多重性是圖中的數字，顯示可能存在於關係中的每個模型的數量（最大值和最小值）。例如，盒子之間的連接線，顯示書和類型相關。書模型中數字表明，一本書必須有一個或多個類型（想要多少就多少），而類型(Genres)模型線的另一端的數字(0..\*)，表明它可以有零個或多個關聯書本(可以有這個書籍類別，也有對應的書；也可以是有這個書籍類別，但沒有對應的書)。

![LocalLibrary Model UML](/zh-TW/docs/Learn/Server-side/Django/Models/local_library_model_uml.svg)

**備註：** 下一節提供一個基本解釋模型的定義與使用，當你在讀的時候，也需要一邊考慮如何構建上圖中的每個模型。

模型入門
----

本節簡要概述了模型定義，和一些重要的字段、和字段參數。

### 模型定義

模型通常在 app 中的 **models.py** 檔案中定義。它們是繼承自 `django.db.models.Model`的子類， 可以包括屬性，方法和描述性資料(metadata)。下面區段為一個名為`MyModelName`的「典型」模型範例碼：

```python
from django.db import models

class MyModelName(models.Model):
    """A typical class defining a model, derived from the Model class."""

    # Fields
    my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
    ...

    # Metadata
    class Meta:
        ordering = ['-my\_field\_name']

    # Methods
    def get\_absolute\_url(self):
            """Returns the url to access a particular instance of MyModelName."""
            return reverse('model-detail-view', args=[str(self.id)])

    def \_\_str\_\_(self):
        """String for representing the MyModelName object (in Admin site etc.)."""
        return self.field_name
```

在下面章節中，我們將更詳細解釋模型的每個功能。

#### 字段

模型可以有任意數量的字段、任何類型的字段 — 每個字段都表示我們要存放在我們的一個資料庫中的一欄數據(a column of data)。每筆資料庫記錄（列 row）將由每個字段值之一組成。我們來看看上面看到的例子。

```python
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
```

在上面例子中，有個叫 `my_field_name` 的單一字段，其類型為 `models.CharField` — 這意味著這個字段將會包含字母、數字字符串。使用特定的類別分配字段類型，這些類別，決定了用於將數據存放在資料庫中的記錄的類型，以及從 HTML 表單接收到值（即構成有效值）時使用的驗證標準。字段類型還可以獲取參數，進一步指定字段如何存放或如何被使用。在這裡的情況下，我們給了字段兩個參數：

* `max_length=20` — 表示此字段中值的最大長度為 20 個字符的狀態。
* `help_text="Enter field documentation"` — 提供一個幫助用戶的文本標籤，讓用戶知道當前透過 HTML 表單輸入時要提供什麼值。

字段名稱用於在視圖和模版中引用它。字段還有一個標籤，它被指定一個參數（`verbose_name`），或者通過大寫字段的變量名的第一個字母，並用空格替換下劃線（例如`my_field_name` 的默認標籤為 My field name ）。

如果模型在表單中呈現（例如：在管理站點中），則聲明該字段的順序，將影響其默認順序，但可能會被覆蓋。

##### 常用字段參數

當聲明很多／大多數不同的字段類型時，可以使用以下常用參數：

* help\_text :提供 HTML 表單文本標籤(eg i 在管理站點中),如上所述。
* verbose\_name :字段標籤中的可讀性名稱，如果沒有被指定，Django 將從字段名稱推斷默認的詳細名稱。
* default :該字段的默認值。這可以是值或可呼叫物件(callable object)，在這種情況下，每次創建新紀錄時都將呼叫該物件。
* null：如為 `True`，即允許 Django 於資料庫該欄位寫入 `NULL`（但欄位型態如為 `CharField` 則會寫入空字串）。預設值是 `False`。
* blank :如果\*\*`True`\*\*，表單中的字段被允許為空白。默認是`False`，這意味著 Django 的表單驗證將強制你輸入一個值。這通常搭配 `NULL=True` 使用，因為如果要允許空值，你還希望數據庫能夠適當地表示它們。
* choices :這是給此字段的一組選項。如果提供這一項，預設對應的表單部件是「該組選項的列表」，而不是原先的標准文本字段。
* primary\_key :如果是 True，將當前字段設置為模型的主鍵（主鍵是被指定用來唯一辨識所有不同表記錄的特殊數據庫欄位(column)）。如果沒有指定字段作為主鍵，則 Django 將自動為此添加一個字段。

還有許多其他選項 — 你可以在這裡看到完整的字段選項。

##### 常用字段類型

以下列表描述了一些更常用的字段類型。

* CharField 是用來定義短到中等長度的字段字符串。你必須指定`max_length`要存儲的數據。
* TextField 用於大型任意長度的字符串。你可以`max_length`為該字段指定一個字段，但僅當該字段以表單顯示時才會使用（不會在數據庫級別強制執行）。
* IntegerField 是一個用於存儲整數（整數）值的字段，用於在表單中驗證輸入的值為整數。
* DateField 和DateTimeField 用於存儲／表示日期和日期／時間信息（分別是`Python.datetime.date` 和 `datetime.datetime` 對象）。這些字段可以另外表明（互斥）參數 `auto_now=Ture` （在每次保存模型時將該字段設置為當前日期），`auto_now_add`（僅設置模型首次創建時的日期）和 `default`（設置默認日期，可以被用戶覆蓋）。
* EmailField 用於存儲和驗證電子郵件地址。
* FileField 和ImageField 分別用於上傳文件和圖像（`ImageField` 只需添加上傳的文件是圖像的附加驗證）。這些參數用於定義上傳文件的存儲方式和位置。
* AutoField 是一種 **IntegerField** 自動遞增的特殊類型。如果你沒有明確指定一個主鍵，則此類型的主鍵將自動添加到模型中。
* ForeignKey 用於指定與另一個數據庫模型的一對多關係（例如，汽車有一個製造商，但製造商可以製作許多汽車）。關係的「一」側是包含密鑰的模型。
* ManyToManyField 用於指定多對多關係（例如，一本書可以有幾種類型，每種類型可以包含幾本書）。在我們的圖書館應用程序中，我們將非常類似地使用它們 ForeignKeys，但是可以用更複雜的方式來描述組之間的關係。這些具有參數 `on_delete` 來定義關聯記錄被刪除時會發生什麼（例如，值 `models.SET_NULL` 將簡單地設置為值 NULL ）。

還有許多其他類型的字段，包括不同類型數字的字段（大整數，小整數，浮點數），布林值，URLs，唯一 ids 和其他 「時間相關」 的信息（持續時間，時間等）。你可以查閱完整列表 .

#### 元數據(Metadata)

你可以通過宣告 class Meta 來宣告模型級別的元數據，如圖所示：

```python
class Meta:
    ordering = ['-my\_field\_name']
```

此元數據最有用的功能之一是控制在查詢模型類型時返回之記錄的默認排序。你可以透過在`ordering` 屬性的字段名稱列表中指定匹配順序來執行此操作，如上所示。排序將依賴字段的類型（字符串字段按字母順序排序，而日期字段按時間順序排序）。如上所示，你可以使用減號（-）前綴字段名稱以反轉排序順序。

例如，如果我們選擇依照此預設來排列書單：

```python
ordering = ['title', '-pubdate']
```

書單通過標題依據--字母排序--排列，從 A 到 Z，然後再依每個標題的出版日期，從最新到最舊排列。

另一個常見的屬性是 `verbose_name` ,一個 `verbose_name` 說明單數和複數形式的類別。

```python
verbose_name = 'BetterName'
```

其他有用的屬性允許你為模型創建和應用新的「訪問權限」（預設權限會被自動套用），允許基於其他的字段排序，或聲明該類是」抽象的「（你無法創建的記錄基類，並將由其他型號派生）。

許多其他元數據選項控制模型中必須使用哪些數據庫以及數據的存儲方式。（如果你需要模型映射一個現有數據庫，這會有用）。

完整有用的元數據選項在這裡Model metadata options (Django docs).

#### 方法(Methods)

一個模型也可以有方法。

\*\*最起碼，在每個模型中，你應該定義標準的 Python 類方法`__str__()` \*\*，**來為每個物件返回一個人類可讀的字符串**。此字符用於表示管理站點的各個記錄（以及你需要引用模型實例的任何其他位置）。通常這將返回模型中的標題或名稱字段。

```python
def \_\_str\_\_(self):
    return self.field_name
```

Django 方法中另一個常用方法是 `get_absolute_url()` ，這函數返回一個在網站上顯示個人模型記錄的 URL（如果你定義了該方法，那麼 Django 將自動在「管理站點」中添加「在站點中查看「按鈕在模型的記錄編輯欄）。`get_absolute_url()`的典型示例如下：

```python
def get\_absolute\_url(self):
    """Returns the url to access a particular instance of the model."""
    return reverse('model-detail-view', args=[str(self.id)])
```

**備註：** 假設你將使用 URL`/myapplication/mymodelname/2` 來顯示模型的單個記錄（其中「2」是 id 特定記錄），則需要創建一個 URL 映射器來將響應和 id 傳遞給「模型詳細視圖」 （這將做出顯示記錄所需的工作）。以上示例中，`reverse()`函數可以「反轉」你的 url 映射器（在上訴命名為「model-detail-view」的案例中，以創建正確格式的 URL。

當然要做這個工作，你還是要寫 URL 映射，視圖和模版！

你可以定義一些你喜歡的其他方法，並從你的代碼或模版調用它們（只要它們不帶任何參數）。

### 模型管理

一旦你定義了模型類，你可以使用它們來創建，更新或刪除記錄，並運行查詢獲取所有記錄或特定的記錄子集。當我們定義我們的視圖，我們將展示給你在這個教程如何去做。

#### 創建和修改記錄

要創建一個記錄，你可以定義一個模型實例，然後呼叫 `save()`。

```python
# Create a new record using the model's constructor.
record = MyModelName(my_field_name="Instance #1")

# Save the object into the database.
record.save()
```

**備註：** 如果沒有任何的欄位被宣告為`主鍵`，這筆新的紀錄會被自動的賦予一個主鍵並將主鍵欄命名為 `id`。上例的那筆資料被儲存後，試著查詢這筆紀錄會看到它被自動賦予 1 的編號。

你可以透過「點(dot)的語法」取得或變更這筆新資料的欄位(字段)。你需要呼叫 `save()` 將變更過的資料存進資料庫。

```python
# Access model field values using Python attributes.
print(record.id) #should return 1 for the first record.
print(record.my_field_name) # should print 'Instance #1'

# Change record by modifying the fields, then calling save().
record.my_field_name = "New Instance Name"
record.save()
```

#### 搜尋紀錄

你可以使用模型的 `objects` 屬性(由 base class 提供)搜尋符合某個條件的紀錄。You can search for records that match a certain criteria using the model's attribute (provided by the base class).

**備註：** 要用"抽象的"模型還有欄位說明怎麼搜尋紀錄可能會有點令人困惑。我們會以一個 Book 模型，其包含`title`與`genre`字段，而 genre 也是一個僅有`name`一個字段的模型。

我們可以取得一個模型的所有紀錄，為一個 `QuerySet` 使用`objects.all()。` `QuerySet` 是一個可迭代的物件，表示他含有多個物件，而我們可以藉由迭代/迴圈取得每個物件。

```python
all_books = Book.objects.all()
```

Django 的 `filter()` 方法讓我們可以透過符合特定文字或數值的字段篩選回傳的`QuerySet`。例如篩選書名裡有 "wild" 的書並且計算總數，如下面所示。

```python
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = Book.objects.filter(title__contains='wild').count()
```

要比對的字段與比對方法都要被定義在篩選的參數名稱裡，並且使用這個格式：`比對字段__比對方法` (請注意上方範例中的 `title` 與 `contains` 中間隔了兩個底線唷)。在上面我們使用大小寫區分的方式比對`title` 。還有很多比對方式可以使用： `icontains` (不區分大小寫), `iexact` (大小寫區分且完全符合), `exact` (不區分大小寫但完全符合) 還有 `in`, `gt` (大於), `startswith`, 之類的。全部的用法在這裡。

有時候你會須要透過某個一對多的字段來篩選(例如一個 `外鍵`)。 這樣的狀況下，你可以使用兩個底線來指定相關模型的字段。例如透過某個特定的 genre 名稱篩選書籍，如下所示：

```python
# 會比對到: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')
```

**備註：** 你可隨心地使用雙底線 (\_\_) 來探索更多層的關係 (`ForeignKey`/`ManyToManyField`). 例如, 一本 `Book` 有許多不同的 types, 其進一步定義有參數 name 關聯的"cover"：`type__cover__name__exact='hard'.`

還有很多是你可以用索引(queries)來做的，包含從相關的模型做向後查詢(backwards searches)、連鎖過濾器(chaining filters)、回傳「值的小集合」等。更多資訊可以到 Making queries (Django Docs) 查詢。

定義 LocalLibrary 模型
------------------

這部份我們會開始定義圖書館的模型。

先打開 models.py (在 */locallibrary/catalog/*)，頁面的最上方可以看到樣板導入了 models 模組，其包含了模型的基本類別 `models.Model` ，能使我們的模型能夠繼承。

```python
from django.db import models

# Create your models here.
```

### 書籍類型模型 (Genre model)

複製下方 `Genre` 模型的程式碼，並貼在你的 `models.py` 檔案底部，這個模型是用來儲存書籍類型的資訊 — 例如：該本書是否為科幻小說、羅曼史、軍事歷史等。

就像先前提到的，我們以「模型」的方式建立一個書籍類型模型，而非以自由文本(free text)或者選擇列表(selection list)的方式，這樣做讓我們可以透過資料庫的形式而非硬編碼(hard coded)的方式來管理所有可能的值。

```python
class Genre(models.Model):
    """Model representing a book genre."""
    name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')

    def \_\_str\_\_(self):
        """String for representing the Model object."""
        return self.name
```

此模型有一個單一的 `CharField` 字段(`name`) 被用來描述書籍類別(限制輸入字元長度最多 200 個，同時也有提示文本(help\_text) )。

在模型最下方我們宣告一個 `__str__()` 方法來簡單回傳被特定一筆紀錄定義的書籍類別名稱。

因為詳細名稱(verbose name)沒有被定義，所以字段在形式上會被稱為 `Name` 。

### 書本模型 (Book model)

複製下方 Book 模型的程式碼，並貼在你的 `models.py` 檔案底部，這個 `Book` 模型一般來說代表一個可用書本的所有資訊，但並非包含特定的物理實例(physical instance)或者副本資訊(copy)，此模型使用 `CharField` 來表示書的 `title` 和 `isbn` (國際標準書號)(note how the `isbn` specifies its label as "ISBN" using the first unnamed parameter because the default label would otherwise be "Isbn").，另外此模型使用 `TextField` 來存 `summary` ，因為此文本可能會很長。

```python
from django.urls import reverse #Used to generate URLs by reversing the URL patterns

class Book(models.Model):
    """Model representing a book (but not a specific copy of a book)."""
    title = models.CharField(max_length=200)
    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)

    # Foreign Key used because book can only have one author, but authors can have multiple books
    # Author as a string rather than object because it hasn't been declared yet in the file.
    summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book')
    isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')

    # ManyToManyField used because genre can contain many books. Books can cover many genres.
    # Genre class has already been defined so we can specify the object above.
    genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')

    def \_\_str\_\_(self):
        """String for representing the Model object."""
        return self.title

    def get\_absolute\_url(self):
        """Returns the url to access a detail record for this book."""
        return reverse('book-detail', args=[str(self.id)])
```

「書籍類別」(`genre`)是一個 `ManyToManyField` ，因此一本書可以有很多書籍類別，而一個書結類別也能夠對應到很多本書。作者(`author`)被宣告為外鍵(`ForeignKey`)，因此每本書只會有一名作者，但一名作者可能會有多本書(實際上，一本書可能會有多名作者，不過這個案例不會有，所以在別的例子這種作法可能會有問題)

在上面兩個宣告關聯性模型的敘述句內，關聯的對象都是用對象的模型類或字串的方式作為首個未具名參數的方式傳入句內做宣告。在關聯對象尚未被定義前，若要參照到該對象，必須使用該對象名稱字串的方式來宣告關聯性！還有一些 `author` 欄位的其它值得一提的參數：`null=True` 表示如果沒有作者的話，允許在資料庫中存入 `Null` 值；`on_delete=models.SET_NULL` 表示如果某筆作者紀錄被刪除的話，與該作者相關連的欄位都會被設成 `Null`。

這個模型也定義了 `__str__()` ，使用書本的 `title` 字段來表示一筆 `Book` 的紀錄。而最後一個方法，`get_absolute_url()` ，則會回傳一個可以被用來存取該模型細節紀錄的 URL (要讓其有效運作，我們必須定義一個 URL 的映射，我們將其命名為 `book-detail` ，另外還得定義一個關聯示圖(view)與模板(template) )。

### 書本詳情模型 (BookInstance model)

接下來，複製下方 `BookInstance` 的模型，貼在其他模型下面，這個 `BookInstance` 模型表示一個特定的書籍副本(可會被某人借走)，並且包含如「副本是否可用」、「預計歸還日期」、「版本說明」或「版本細節」等資訊，還有一個在圖書館中唯一的 id 。

有些字段(fields)和方法(methods)現在你也熟悉了。此模型使用了：

* `ForeignKey` 用來辨識相關的 `Book` (每本書可以有很多副本，但每個副本只能有一個`Book`)
* `CharField` 用來表示該本書的版本說明(特定版本)

```python
import uuid # Required for unique book instances

class BookInstance(models.Model):
    """Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library')
    book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True)
    imprint = models.CharField(max_length=200)
    due_back = models.DateField(null=True, blank=True)

    LOAN_STATUS = (
        ('m', 'Maintenance'),
        ('o', 'On loan'),
        ('a', 'Available'),
        ('r', 'Reserved'),
    )

    status = models.CharField(
        max_length=1,
        choices=LOAN_STATUS,
        blank=True,
        default='m',
        help_text='Book availability',
    )

    class Meta:
        ordering = ['due\_back']

    def \_\_str\_\_(self):
        """String for representing the Model object."""
        return f'{self.id} ({self.book.title})'
```

我們額外宣告了一些新的字段(field)類別(types)：

* `UUIDField` 被用來將 `id` 字段再這個模型中設定為 `primary_key` ，這類別的字段會分配一個全域唯一的值給每一個實例(instance)，也就是任何一本你能在圖書館找到的書。
* `DateField` 會被用來設定 `due_back` 的日期(紀錄書本何時會被歸還，可再被使用，或者是否正在保養期)，這個字段允許 `blank` 或 `null` 值，而當元數據模型 (`Class Meta`)收到請求(query)時也會使用此字段來做資料排序。
* `status` 是一個 `CharField` 字段，用來定義一個選項列表。你可以看到，我們定義了一個包含「鍵-值對元組」的元組(tuple) 並使其成為選項的參數，鍵-值對中的值會陳列出來並可以被使用者選擇，當選項被選定後，鍵(key)也會被儲存下來。我們也設定了預設的鍵值為 "m" (maintenance) 用來表示當每本書在初始創造還未放上書架時是不可被使用的。

而 `__str__()` 模型用來表示 `BookInstance` 這個物件的「唯一 ID」和「相關之 `Book` 書本名稱(title)」的組合。

**備註：** 關於 Python 的小提醒：

* 從 Python3.6 開始，你可以使用「字串插值語法」(又稱做 f-string)：
 `f'{self.id} ({self.book.title})'`
* 在舊版 Python 這部分的教學中，我們則使用了另一種有效的 formatted string 語法
 (e.g. `'{0} ({1})'.format(self.id,self.book.title)`)

### 作者模型(Author model)

複製下方 `Author` 的模型程式碼並貼在 **models.py** 文件的最下方。

現在所有的字段(fields)與方法(methods)你應該都熟悉了，此模型定義了作者的「名」、「姓」、「出生年月日」、「死亡日期(非必填)」。該模型也指定，預設情況下，`__str__()` 方法會回傳作者姓名(按照姓、名排序)。而 `get_absolute_url()` 方法會反轉 author-detail 的 URL 映射，來獲得顯示單個作者的 URL。

```python
class Author(models.Model):
    """Model representing an author."""
    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)

    class Meta:
        ordering = ['last\_name', 'first\_name']

    def get\_absolute\_url(self):
        """Returns the url to access a particular author instance."""
        return reverse('author-detail', args=[str(self.id)])

    def \_\_str\_\_(self):
        """String for representing the Model object."""
        return f'{self.last\_name}, {self.first\_name}'
```

再次執行資料庫遷移(database migrations)
------------------------------

你的所有模型都建立好了，現在必須再次執行你的資料庫 migrations 指令來將這些修改內容更信到資料庫中。

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

語言模型(Language model) — 挑戰
-------------------------

請想像一下，現在來了一位善心人士捐了一堆用不同語言寫的書(例如：波斯語)，而你的挑戰是必須制定一個最好在我們的圖說館網站呈現的方式，並把它做成模組。

幾件事情需要思考：

* 「語言」需要與 `Book` 、`BookInstance` 或其他物件(Object)相關聯嗎？
* 「不同語言」能以什麼形式來表示？
 模型？自由文本字段(free text field)？硬編碼選擇列表(hard-coded selection list)？

當你決定好了，就開始動手吧！你可以在Github 的這裡看到我們是怎麼思考的。

小結
--

在這篇文章我們學到如何定義模型，並且利用這些資訊來設計與實作適合的模型給 *LocalLibrary 網站。*

*再來我們要稍微撇開建立網站，先來看看 Django 的管理站(Django Administration site)，這個管理站能讓我們加入一些資料到圖書館中，讓我們再來能夠透過「示圖(views)與模板(templates)」(當然我們現在都還沒建立)來展示。*

延伸閱讀
----

* Writing your first Django app, part 2 (Django docs)
* Making queries (Django Docs)
* QuerySet API Reference (Django Docs)
* 前頁
* Overview: Django
* 次頁


Django Tutorial Part 5: Creating our home page - 學習該如何開發 Web

Django Tutorial Part 5: Creating our home page
==============================================

* 前頁
* Overview: Django
* 次頁

我們現在可以添加代碼，來顯示我們的第一個完整頁面 - LocalLibrary 網站的主頁，顯示每個模型類型有多少條記錄，並提供我們其他頁面的側邊欄導航鏈接。一路上，我們將獲得編寫基本 URL 地圖和視圖、從數據庫獲取記錄、以及使用模板的實踐經驗。

|  |  |
| --- | --- |
| 前提: |
 讀 the
 Django Introduction. 完成上章節 (including
 Django Tutorial Part 4: Django admin site).
  |
| 目的: | 了解如何創建簡單的URL映射和視圖（沒有數據編碼在URL中）以及如何從模型中獲取數據並創建模版。概要 |

總覽
--

在定義了模型並創建了一些可以使用的初始庫記錄之後，是時候編寫將這些信息呈現給用戶的代碼了。 我們要做的第一件事是確定我們要在頁面中顯示的信息，並定義用於返回這些資源的 URL。 然後，我們將創建一個 URL 映射器，視圖和模板來顯示頁面。

下圖描述了主要數據流，以及處理 HTTP 請求和響應時所需的組件。 當我們已經實現了模型時，我們將創建的主要組件是：

* URL 映射器將支持的 URL（以及 URL 中編碼的任何信息）轉發到適當的視圖功能。
* 查看函數可從模型中獲取所需的數據，創建顯示數據的 HTML 頁面，並將頁面返回給用戶以在瀏覽器中查看。
* 在視圖中呈現數據時要使用的模板。

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

正如你將在下一節中看到的那樣，我們將顯示 5 頁，這是太多信息，無法在一篇文章中進行記錄。 因此，本文將重點介紹如何實現主頁，我們將在後續文章中介紹其他頁面。 這應該使你對 URL 映射器，視圖和模型在實踐中如何工作有很好的端到端理解。

定義資源 URL
--------

由於此版本的 LocalLibrary 對於最終用戶基本上是只讀的，因此我們只需要提供該網站的登錄頁面（主頁），以及顯示書籍和作者的列表和詳細視圖的頁面。

我們頁面所需的 URL 是：

* `catalog/` — 主頁/索引頁面。
* `catalog/books/` — 所有書籍的清單。
* `catalog/authors/` — 所有作者列表。
* `catalog/book/<id>` — 特定書的詳細信息視圖，其主鍵為 `<id>` (the default)。 因此，例如`/catalog/book/3`，作為添加的第三本書。
* `catalog/author/<id>` — 特定作者的詳細信息視圖，其主鍵字段名為 `<id>`。 例如，為第 11 位作者添加了`/catalog/author/11`。

前三個 URL 用於列出索引，書籍和作者。 它們不對任何其他信息進行編碼，並且雖然返回的結果將取決於數據庫中的內容，但為獲取信息而運行的查詢將始終相同。

相比之下，最後兩個 URL 用於顯示有關特定書籍或作者的詳細信息-這些 URL 編碼要顯示在 URL 中的項目的標識（如上顯示為<id>）。 URL 映射器可以提取編碼信息並將其傳遞給視圖，然後將動態確定從數據庫中獲取哪些信息。 通過在我們的 URL 中編碼信息，我們只需要一個 URL 映射，視圖和模板即可處理每本書（或作者）。

**備註：** Django 允許你以自己喜歡的任何方式來構造 URL-你可以如上所示在 URL 主體中編碼信息或使用 URL `GET` 參數（例如`/book/?id=6`）。 無論使用哪種方法，都應保持 URL 的整潔，邏輯和可讀性(在此處查看 W3C 建議).

Django 文檔傾向於建議在 URL 正文中編碼信息，他們認為這種做法鼓勵更好的 URL 設計。

如概述中所述，本文的其餘部分描述瞭如何構造索引頁。

創建索引頁面
------

我們將創建的第一頁是索引頁 (`catalog/`)。 這將顯示一些靜態 HTML，以及數據庫中不同記錄的一些計算出的「計數」。 為了完成這項工作，我們必須創建一個 URL 映射，視圖和模板。

**備註：** 值得在本節中多加註意。大多數材料是所有頁面共有的。

### URL mapping

創建skeleton website 時，我們更新了**locallibrary/urls.py**文件，以確保每當收到以 `catalog/` 開頭的 URL 時， *URLConf* 模組 `catalog.urls` 都將處理其餘的子字符串。

來自 **locallibrary/urls.py**的以下代碼片段包括`catalog.urls` 模塊：

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

**備註：** 每當 Django 遇到導入函數 `django.urls.include()`時，它都會在指定的結束字符處分割 URL 字符串，並將剩餘的子字符串發送到所包含的*URLconf* 模塊以進行進一步處理。

我們還為*URLConf* 模塊創建了一個佔位符文件，名為 **/catalog/urls.py**。 將以下行添加到該文件：

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

`path()`函數定義以下內容：

* URL 模式，它是一個空字符串：`''`。 處理其他視圖時，我們將詳細討論 URL 模式。
* 如果檢測到 URL 模式，將調用一個視圖函數：`views.index`, 它是**views.py**文件中名為`index()` 的函數。

`path()` 函數還指定一個`name`參數，它是此特定 URL 映射的唯一標識符。 你可以使用該名稱來「反向」映射器，即，動態創建指向映射器旨在處理的資源的 URL。 例如，通過在模板中添加以下鏈接，我們可以使用 name 參數從任何其他頁面鏈接到我們的主頁：

```html
<a href="{% url 'index' %}">Home</a>.
```

**備註：** 我們可以對上面的鏈接進行硬編碼 (例如`<a href="/catalog/">Home</a>`), 但是如果我們更改主頁的模式 (例如更改為 `/catalog/index`) 則模板將不再 正確鏈接。 使用反向 URL 映射更加靈活和健壯！

### View (function-based)

View 是一個用來處理 HTTP 請求的函式，根據需求從資料庫取得資料，通過使用 HTML 模板呈現此數據來生成 HTML , 並且在一個 HTTP 回應中返回 HTML 來呈現給用戶。Index view 遵循這個模型 — 獲取有關數據庫中有多少 `Book`, `BookInstance`, 可用的 `BookInstance` 還有 `Author` 的訊息, 然後把他們傳遞給模板進行顯示。

打開**catalog/views.py**, 並且注意該文件已經導入 render() 快捷功能已使用模板和數據生成 HTML 文件。

```python
from django.shortcuts import render

# Create your views here.
```

將以下代碼複製到文件底部。 第一行導入將用於訪問所有視圖中的數據的模型類。

```python
from .models import Book, Author, BookInstance, Genre

def index(request):
    """View function for home page of site."""

    # Generate counts of some of the main objects
    num_books = Book.objects.all().count()
    num_instances = BookInstance.objects.all().count()

    # Available books (status = 'a')
    num_instances_available = BookInstance.objects.filter(status__exact='a').count()

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

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

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

視圖函數的第一部分使用模型類上的 `objects.all()` 屬性獲取記錄數。 它還獲取具有狀態字段值為「 a」（可用）的`BookInstance` 物件列表。 在上一教程(Django Tutorial Part 3: Using models > Searching for records)中，你可以找到更多有關如何從模型進行訪問的信息。.

在函數的最後，我們調用 `render()` 函數來創建並返回 HTML 頁面作為響應（此快捷功能包裝了許多其他函數，從而簡化了這種非常常見的用例）。它以原始 `request` 物件 (一個 `HttpRequest`), 帶有數據佔位符的 HTML 模板以及上下文 `context` 變量包含將插入到這些佔位符中的數據的 Python 字典）為參數。

在下一節中，我們將詳細討論模板和上下文變量。 讓我們開始創建模板，以便實際上可以向用戶顯示內容！

### Template

模板是一個文本文件，用於定義文件（例如 HTML 頁面）的結構或佈局，並使用佔位符表示實際內容。 Django 會在你的應用程序名為'**templates**'的目錄中自動查找模板。 因此，例如，在我們剛剛添加的索引視圖中， `render()` 函數將有望能夠找到文件 **/locallibrary/catalog/templates/*index.html***，如果找不到該文件，則會引發錯誤。 如果你保存以前的更改並返回瀏覽器，則可以看到此信息-訪問`127.0.0.1:8000`現在將為你提供一個相當直觀的錯誤消息"`TemplateDoesNotExist at /catalog/`"以及其他詳細信息。

**備註：** Django 將根據項目的設置文件在許多位置查找模板（搜索已安裝的應用程序是默認設置！）。 你可以在 Templates （Django docs）中找到有關 Django 如何查找模板及其支持的模板格式的更多信息。

#### Extending templates

索引模板的頭部和身體將需要標準的 HTML 標記，以及用於導航的部分（到我們尚未創建的站點中的其他頁面）以及用於顯示一些介紹性文本和我們的書籍數據的部分。 對於我們網站上的每個頁面，大部分文本（HTML 和導航結構）都是相同的。 Django 模板語言允許你聲明一個基本模板，然後擴展它，而不是強迫開發人員在每個頁面中都複製此"樣板" ，只需替換每個特定頁面上不同的部分即可。

例如，基本模板 **base\_generic.html** 可能類似於以下文本。 如你所見，其中包含一些"通用" HTML 以及標題，側邊欄和內容的部分，這些部分使用命名的`block` 和`endblock` 模板標記進行了標記（以粗體顯示）。 區塊可以為空，或包含將在默認情況下用於派生頁面的內容。

**備註：** 模板*tags* 類似於可以在模板中使用的功能，可以在模板中循環使用列表，基於變量的值執行條件操作等。除了模板標記之外，模板語法還允許你引用模板變量（傳遞給 模板），並使用*template filters*，該過濾器可重新格式化變量（例如，將字符串設置為小寫）。

```django
<!DOCTYPE html>
<html lang="en">
  <head>
    {% block title %}
      <title>Local Library</title>
    {% endblock %}
  </head>
  <body>
    {% block sidebar %}
      <!-- insert default navigation text for every page -->
    {% endblock %}
    {% block content %}
      <!-- default content text (typically empty) -->
    {% endblock %}
  </body>
</html>
```

當我們想為特定視圖定義模板時，我們首先指定基本模板（帶有`extends` 模板標籤-請參見下一個代碼清單）。 如果我們要在模板中替換任何節，則使用與基本模板中相同的`block`/`endblock`節來聲明這些節。

例如，下面的代碼片段顯示了我們如何使用`extends` 模板標籤並覆蓋`content` 區塊。 生成的最終 HTML 將具有基本模板中定義的所有 HTML 和結構（包括你在`title` 區塊中定義的默認內容），但是將新的`content` 區塊插入到默認模板中。

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

{% block content %}
  <h1>Local Library Home</h1>
  <p>
    Welcome to LocalLibrary, a website developed by
    <em>Mozilla Developer Network</em>!
  </p>
{% endblock %}
```

#### The LocalLibrary base template

下面列出了我們計劃用於 *LocalLibrary* 網站的基本模板。 如你所見，其中包含一些 HTML 以及 `title`、`sidebar` 和 `content`。 我們有一個默認標題（我們可能想要更改）和一個默認側邊欄，其中帶有指向所有書籍和作者列表的鏈接（我們可能不想更改，但是如果需要的話，我們允許範圍通過將其放在一個區塊中）。

**備註：** 我們還引入了兩個附加的模板標籤：`url` 和`load static`。這些將在以下各節中討論。

創建一個新文件 **/locallibrary/catalog/templates/*base\_generic.html***，並為其提供以下內容：

```django
<!DOCTYPE html>
<html lang="en">
  <head>
    {% block title %}
    <title>Local Library</title>
    {% endblock %}
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
 rel="stylesheet"
 href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
 integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
 crossorigin="anonymous" />

    <!-- Add additional CSS in static file -->
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/styles.css' %}" />
  </head>
  <body>
    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-2">
          {% block sidebar %}
          <ul class="sidebar-nav">
            <li><a href="{% url 'index' %}">Home</a></li>
            <li><a href="">All books</a></li>
            <li><a href="">All authors</a></li>
          </ul>
          {% endblock %}
        </div>
        <div class="col-sm-10 ">{% block content %}{% endblock %}</div>
      </div>
    </div>
  </body>
</html>
```

該模板包括來自 Bootstrap 的 CSS，以改進 HTML 頁面的佈局和表示方式。 使用 Bootstrap 或其他客戶端 Web 框架是創建吸引人的頁面的快速方法，該頁面可以在不同的瀏覽器大小上很好地擴展。

基本模板還引用了本地 CSS 文件 (**styles.css**) ，該文件提供了一些其他樣式。 創建 **/locallibrary/catalog/static/css/styles.css**並為其提供以下內容：

```css
.sidebar-nav {
  margin-top: 20px;
  padding: 0;
  list-style: none;
}
```

#### The index template

創建 HTML 文件 **/locallibrary/catalog/templates/*index.html*** 並為其提供以下內容。 如你所見，我們在第一行中擴展了基本模板，然後使用該模板的新內容塊替換默認`content` 區塊。

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

{% block content %}
  <h1>Local Library Home</h1>
  <p>
    Welcome to LocalLibrary, a website developed by
    <em>Mozilla Developer Network</em>!
  </p>
  <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>
{% endblock %}
```

在 *Dynamic content* 部分中，我們聲明了要從視圖中包含的信息的佔位符（*template variables*）。 變量使用「雙括號」或「把手」語法標記（請參見上面的粗體）。

**備註：** 因為變量具有雙括號 (`{{ num_books }}`)，而標籤則用百分號括在單括號中擴展為 (`{% extends "base_generic.html" %}`)，所以你可以輕鬆識別是要處理模板變量還是模板標籤（函數）。

這裡要注意的重要一點是，這些變量是使用我們在視圖的`render()` 函數中傳遞給`context` 字典的鍵命名的（請參見下文）; 呈現模板時，這些將被其*values* 替換。

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

return render(request, 'index.html', context=context)
```

#### Referencing static files in templates

你的項目可能會使用靜態資源，包括 JavaScript，CSS 和圖像。 由於這些文件的位置可能未知（或可能會更改），因此 Django 允許你相對於`STATIC_URL` 全局設置在模板中指定這些文件的位置（默認框架網站將`STATIC_URL` 的值設置為'`/static/`'，但你可以選擇將其託管在內容分發網絡或其他地方）。

在模板中，你首先調用指定為「 static」的`load` 模板標籤以添加此模板庫（如下所示）。 加載靜態文件後，你可以使用`static` 模板標籤，指定感興趣文件的相對 URL。

```django
<!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}" />
```

如果需要，你可以以相同的方式將圖像添加到頁面中。 例如：

```django
{% load static %}
<img
 src="{% static 'catalog/images/local\_library\_model\_uml.png' %}"
 alt="UML diagram"
 style="width:555px;height:540px;" />
```

**備註：** 上面的更改指定了文件的位置，但是 Django 默認不提供文件。創建網站框架時 (created the website skeleton)，雖然我們在全局 URL 映射器(**/locallibrary/locallibrary/urls.py**)中啟用了由開發 Web 服務器提供的服務，但你仍需要安排它們在生產中提供。 我們待會再看。

有關使用靜態文件的更多信息，請參閱管理靜態文件 Managing static files (Django docs)。

#### Linking to URLs

上面的基本模板引入了`url` 模板標籤。

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

此標記採用在 **urls.py**中調用的 `path()`函數的名稱以及關聯視圖將從該函數接收的任何參數的值，並返回可用於鏈接到資源的 URL。

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

此時，我們應該已經創建了顯示索引頁面所需的所有內容。 運行服務器(`python3 manage.py runserver`)，然後打開瀏覽器到http://127.0.0.1:8000/。 如果一切設置正確，則你的站點應類似於以下螢幕截圖。

![Index page for LocalLibrary website](/zh-TW/docs/Learn/Server-side/Django/Home_page/index_page_ok.png)

**備註：** 你將無法使用**All books**和**All authors**鏈接，因為尚未定義這些頁面的路徑，視圖和模板（當前我們僅在`base_generic.html` html 模板中插入了這些鏈接的佔位符）。

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

這裡有兩個任務可以測試你對模型查詢，視圖和模板的熟悉程度。

1. LocalLibrary base template 已定義`title` 欄。 在 index template中覆蓋此塊並為頁面創建一些新標題。

**備註：** Extending templates 部分介紹瞭如何創建塊並將其擴展到另一個模板中。
2. 修改 view以生成包含特定單詞（不區分大小寫）的流派計數和書籍計數，並將其傳遞給`context` （這與我們創建並使用`num_books` 和`num_instances_available`的方式大致相同）。 然後更新 index template 以使用這些變量。
Summary
-------

現在，我們已經為網站創建了主頁-一個 HTML 頁面，該頁面顯示了數據庫中的一些記錄計數，並具有指向其他尚待創建頁面的鏈接。 在此過程中，我們學習了很多有關 url 映射器，視圖，使用我們的模型查詢數據庫，如何從視圖中將信息傳遞到模板以及如何創建和擴展模板的基本信息。

在下一篇文章中，我們將基於我們的知識來創建其他四個頁面。

See also
--------

* Writing your first Django app, part 3: Views and Templates (Django docs)
* URL dispatcher (Django docs)
* View functions (DJango docs)
* Templates (Django docs)
* Managing static files (Django docs)
* Django shortcut functions (Django docs)
* 前頁
* Overview: Django
* 次頁


Django Tutorial Part 4: Django admin site - 學習該如何開發 Web

Django Tutorial Part 4: Django admin site
=========================================

* 前頁
* Overview: Django
* 次頁

現在，我們已經為本地圖書館網站 LocalLibrary 創建了模型，我們接下來使用 Django 管理網站，去添加 一些 「真實的」 書本數據。首先，我們展示如何用管理網站註冊模型，然後展示如何登錄和創建一些數據。本文最後，我們介紹可以進一步改進管理網站的建議。

|  |  |
| --- | --- |
| 前提: |
 先完成:
 Django Tutorial Part 3: Using models.
  |
| 目標: | 了解 Django 管理站的優點與侷限，並使用它來為我們的模型新增一些資料。 |

概覽
--

Django 管理應用程序可以使用你的模型，自動構建可用於創建，查看，更新和刪除記錄的網站區域。這可以在開發過程中，節省大量的時間，從而很容易測試你的模型，並了解你是否擁有正確的數據。根據網站的類型，管理應用程序也可用於管理生產中的數據。 Django 項目建議僅用於內部數據管理（即僅供管理員或組織內部人員使用），因為以模型為中心的方法，不一定是所有用戶最好的界面，並且暴露了大量不必要的關於模型的細節。

創建基礎項目時，自動完成所有的配置文件，包含你的網站中的管理應用程序在內（有關所需實際依賴關係的信息，如有需要請看 Django docs here)。其結果是，要將模型添加到管理應用程序，你必須做的，僅僅是註冊他們。在本文末尾，我們將簡要介紹，如何進一步配置管理區域，以更好地顯示我們的模型數據。

註冊模型後，我們將展示，如何創建一個新的 「超級用戶」，登錄到該網站，並創建一些書籍，作者，書籍實例和書籍類別。這些將有助於測試我們將在下一個教程中，開始創建的視圖和模板。

註冊模型(Registering models )
-------------------------

首先，我們從 catalog app 中打開 **admin.py** (**/locallibrary/catalog/admin.py**)，目前它長的像下面區塊，注意它已經幫你導入 `django.contrib.admin`：

```python
from django.contrib import admin

# Register your models here.
```

將下方的程式碼複製貼在 **admin.py** 文件下方以註冊所有模型，這段程式碼簡單來說就是先將模型導入，再呼叫 `admin.site.register` 函式來註冊每個模型。

```python
from .models import Author, Genre, Book, BookInstance

admin.site.register(Book)
admin.site.register(Author)
admin.site.register(Genre)
admin.site.register(BookInstance)
```

**備註：** 如果你在上一章節最後有接受挑戰並建立一個書本的「語言模型」 (查看模型教學文章)，你必需也要導入並註冊該模型！

這是**註冊模型**最簡單的方式。

而管理站則是高度用戶化的，我們會在接下來繼續說明其它註冊你的模型的方式。

創建超級用戶(Creating a superuser)
----------------------------

為了能夠登入管理站，我們需要一個有啟用員工狀態(*Staff* status)的使用者帳號，另外為了要能檢視與產生資料，我們也需要讓這個使用者帳號擁有管理所有物件的權限，因此，你可以透過 **manage.py** 來創建一個擁有所有網站存取權限的超級用戶(superuser)。

在與 **manage.py** 同一個資料夾中執行下方指令，建立一個超級用戶，你會被提示要輸入「使用者名稱」、「使用者 e-mail」和「強度夠高的密碼」。

```bash
python3 manage.py createsuperuser
```

當完成指令輸入後，一個新的超級用戶就會被加進資料庫中，再來只要重新啟動開發用 server ，你便可以進行登入測試：

```bash
python3 manage.py runserver
```

登入並開始使用網站
---------

要登入網站，必須先連上 */admin* URL (e.g. http://127.0.0.1:8000/admin) 並且輸入你的超級用戶的使用者名稱與密碼(你會被重新導向登入頁面，輸入你的帳密後會再回到 */admin* URL)。

網站中的這部分羅列了所有以我們安裝的 app 分組的模型，你可以點擊模型名稱進入陳列所有與其相關連資料的頁面，而你可以進一步編輯它們，或者你也可以直接點擊模型名稱旁邊的 **Add** 連結來開始創建該類型的資料。

![Admin Site - Home page](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_home.png)

點擊 Books 右邊的 **Add** 連結來新增一本新書(會產生如下方的對話方塊)，可以去觀察每個字段(field)、小部件、提示文字(如果有的話)是如何對應到你的模型的。

在字段中輸入值，你可以透過各個字段旁邊的 **+** 按鈕來新增「作者」或「書籍類別」(或者從列表中選擇你已經新增的值)，當你完成後可以點選 **SAVE**, **Save and add another**, 或 **Save and continue editing** 來儲存該筆資料。

![Admin Site - Book Add](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_book_add.png)

**備註：** 在這邊我們希望你花點時間在你的 app 中新增一些書本、作者和書及類型(例如：奇幻等)。請確保每位作者與每種書籍類型都分別關聯了一本以上的書(這在文章稍後的實作的時候，會讓你的列表與細節視圖更加豐富有趣)。

當你新增完書本後，點擊上方書籤的 **Home** 連結回到主要管理頁面，接著點擊 **Books** 連結來展示目前的書本清單(你也可以點及其他連結看看其他模型的列表)，現在你已經加了幾本書，畫面應該會與下方截圖類似，你可以看到下方陳列了每本書的標題，這是我們在上一篇文章所提到的 Book 模型中的 `__str__()` 方法所回傳的值。

![Admin Site - List of book objects](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_book_list.png)

在列表中，如果要刪掉你不想要的書，只需要先勾選欲刪除書本的勾選方框，從動作下拉選單選擇刪除動作(delete action)，接著點選 **GO** 按鈕即可，另外你也可以點選 **ADD BOOK** 按鈕來新增一本書。

你可以點擊書名來編輯它，下方顯示的書本編輯頁面幾乎與 **Add** 頁面相同，主要差異在於頁面的標題(Change book)以及增加了 **Delete**, **HISTORY** 和 **VIEW ON SITE** 按鈕(會有這個按鈕出現是因為我們之前在模型中有定義了 `get_absolute_url()` 的方法)

![Admin Site - Book Edit](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_book_modify.png)

現在透過頁面上方的索引連結回到 **Home** 頁面，然後看看 **Author** 和 **Genre** 列表，你在新增書本的時候應該已經新增了一些資料，不過你還可以再新增更多。

你還沒有任何書本實例(Book Instances)，因為這不會在建立書本時就產生(但你可以在新增 `BookInstance` 資料時新增 `Book` ，這是 `ForeignKey` 字段的性質)。現在回到 Home 頁面然後點擊 Book instances 的 **Add** 按鈕，畫面會呈現如下圖的頁面，注意第一列有個很長、全域唯一的 id 編碼，它可以用來區分每本書在圖書館裡的每個副本。

![Admin Site - BookInstance Add](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_bookinstance_add.png)

幫你的每本書都新增幾筆不同的資料，有些資料的狀態(Status)請設成 *Available ，有些則設成 On loan，如果狀態為* **not** *Available，那記得需要設定到期日(Due back* date\*)。\*

就是這樣！你現在已經學會了如何建立與使用管理站(administration site)，你也為你的 `Book`, `BookInstance`, `Genre`, 和 `Author` 模型建立了幾筆資料，再來當我們建立好視圖(Views)以及模板(Templates)後，就會開始來使用它們。

進階組態(Advanced configuration)
----------------------------

Django 在「透過註冊模型的資訊建立管理站」這方面做得非常好：

* 每個模型都有各自的資料列表，每筆資料都藉由模型的 `__str__()` 方法來做分辨，而且會連結到更詳細的視圖/表格以便後續編輯，而且在預設情況下，這個視圖(View)的上方有一個「動作清單(action menu)」，你可以使用裡面的 delete 功能來執行資料的刪除作業。
* 用於編輯和新增紀錄的模型詳細紀錄表單包含了模型中的所有字段，並依照宣告順序垂直排列。

你可以進一步訂製介面讓它更好用，以下是你可以進一步做的：

* 列表視圖(List views)：
    + 為每一筆紀錄增加額外的字段/資訊陳列。
    + 為這些紀錄列表增加篩選器(例如：使用日期、使用狀態進行過濾)
    + 為動作選單(action menu)添加額外的動作，並選擇是否要讓此選單在表格中呈現。
* 細節視圖(Detail views)：
    + 選擇那些字段要隨著「順序、分組、可否編輯、是否被模組使用、取向」而陳列(或排除)。
    + 添加相關的字段來允許內聯編輯(inline editing)(例如：添加一個功能讓你可以在新增一個作者的時候也順便能夠新增或編輯他的書本記錄)。

這部分我們將要來看幾個有助於改善 *LocalLibrary 介面的小變化，包含了添加更多資訊到* `Book` 和 `Author` 模型列表，以及改善編輯視圖的排版。我們不會改變 `Language` 和 `Genre` 的模型外貌因為他們都各只有 1 個字段，這樣做沒好處！

你可以在 The Django Admin site (Django Docs) 找到關於管理站訂製選擇的完整參考。

### 註冊一個 模型管理 類別 (ModelAdmin class)

為了要改變模型在管理站的陳列方式，你需要定義一個模型管理(ModelAdmin)類別 (他是用來描述排版的)，並且將它與其他模型一起註冊。

我們現在先從 `Author` 模型開始。打開 catalog app 中的 **admin.py** 檔案(**/locallibrary/catalog/admin.py**)，並將先前註冊 `Author` 模型的程式碼註解(在程式碼前面加一個 # 前綴)：

```js
# admin.site.register(Author)
```

現在加上一個新的 `AuthorAdmin` 類別與註冊函式，如下方所示：

```python
# Define the admin class
class AuthorAdmin(admin.ModelAdmin):
    pass

# Register the admin class with the associated model
admin.site.register(Author, AuthorAdmin)
```

現在我們要為 `Book` 以及 `BookInstance` 模型添加 `ModelAdmin` 類別，我們一樣要先把原本的註冊程式碼註解：

```js
#admin.site.register(Book)
#admin.site.register(BookInstance)
```

現在我們要創造並註冊新的模型；為了達到示範的目的，我們會使用 `@register` 裝飾器替代先前做法來註冊模型(這跟 `admin.site.register()` 的語法做的事情完全一樣)：

```python
# Register the Admin classes for Book using the decorator
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    pass

# Register the Admin classes for BookInstance using the decorator
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
    pass
```

目前為止我們的管理類別都是空的(可以看到 "`pass"`)，所以我們的管理行為都不會改變！現在我們可以來進一步定義我們的「特定模型的管理行為」。

### 配置列表視圖(Configure list views)

我們的 *LocalLibrary 目前條列出所有作者，而他們都是使用以模型的* `__str__()` *方法產生的物件名稱。如過你只有少數幾個作者，那倒還好，但如果作者很多，你最後可能會有非常多副本。因此為了區別他們，或者你只是想呈現更多作者的有趣訊息，你可以使用「列表展示」(*list\_display*)來位視圖添加額外的字段。*

將你的 `AuthorAdmin` 類別以下方程式碼取代。下方程式碼可以看出來，列表中被展示出來的字段名稱會被以需要的排序宣告為元組(tuple)形式。

```python
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('last\_name', 'first\_name', 'date\_of\_birth', 'date\_of\_death')
```

現在把網站導向作者列表，上方所設定的字段應該會被陳列出來，如下：

![Admin Site - Improved Author List](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_author_list.png)

至於我們的 `Book` 模型，我們將額外添加 `author` 和 `genre` 兩樣。`author` 是一個`ForeignKey` 外鍵字段(一對一)關係，因此他將會透過關聯紀錄的 `__str__()` 值來表示。

將 `BookAdmin` 類別以下方區段程式碼取代：

```python
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'display\_genre')
```

很不幸地，我們無法直接在 `list_display` 中指定「書籍類別」(genre field)字段，因為它是一個 `ManyToManyField` (多對多字段)，因為如果這樣做會造成很大的資料庫讀寫「成本」，所以 Django 會預防這樣的狀況發生，因此，取而代之，我們將定義一個 `display_genre` 函式以「字串」形式得到書籍類別。(下方有定義此函式)

**備註：** Getting the `genre` may not be a good idea here, because of the "cost" of the database operation. We're showing you how because calling functions in your models can be very useful for other reasons — for example to add a *Delete* link next to every item in the list.

將以下程式碼添加到`Book`模型（**models.py**）。 這會從`genre`記錄的的頭三個值（如果有的話）創建一個字符串, 和創建一個在管理者網站中出現的`short_description`標題。

```python
    def display\_genre(self):
        """Create a string for the Genre. This is required to display genre in Admin."""
        return ', '.join(genre.name for genre in self.genre.all()[:3])

    display_genre.short_description = 'Genre'
```

保存模型並更新管理員後，打開你的網站並轉到「Books」列表頁面； 你應該會看到類似以下的書籍清單：

![Admin Site - Improved Book List](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_book_list.png)

`Genre` 模型(如果定義了語言模型，則還有 `Language` 模型）都有一個欄位，因此沒有必要為它們創建其他模型以顯示欄位。

**備註：** 更新 `BookInstance` 模型列表用來顯示狀態和預期的返回日期是有價值的。 我們在本文結尾處添加了一個挑戰！

### 加入列表過濾器 (List Filter)

當你的列表有很多個記錄時, 加入列表過濾器可以幫助你過濾想顯示的記錄。加入`list_filter`這個屬性就可以。請用以下的程式碼來取代原本的 `BookInstanceAdmin` 類別

```python
class BookInstanceAdmin(admin.ModelAdmin):
    list_filter = ('status', 'due\_back')
```

現在的列表視圖右邊會多了一個過濾器。你可以選擇 dates 和 status 來做過濾:

![Admin Site - BookInstance List Filters](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_bookinstance_list_filters.png)

### 組織詳細視圖佈局

默認情況下，局部視圖按照模型中聲明的順序垂直排列所有字段。 你可以更改聲明的順序，顯示（或排除）哪些字段，使用分段來組織資訊，水平顯示還是垂直顯示字段，甚至管理表單中使用哪些編輯小部件。

**備註：** *LocalLibrary* 模型相對簡單，因此我們無須更改佈局。 但我們仍然會進行一些更改，向你展示如何進行。

#### 控制那些欄位顯示並佈置

更新你的 `AuthorAdmin` 類別用來新增 `fields` 這行,如同下列所示 (粗體):

```python
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('last\_name', 'first\_name', 'date\_of\_birth', 'date\_of\_death')
    fields = ['first\_name', 'last\_name', ('date\_of\_birth', 'date\_of\_death')]
```

`fields` 屬性僅按順序列出了要在表單上顯示的那些欄位。 默認情況下，字段是垂直顯示的，但是如果你進一步將它們分組到一個元組中，它們將水平顯示（如上面的「日期」字段中所示）。

在你的網站上，轉到作者詳細信息視圖-現在應如下所示：

![Admin Site - Improved Author Detail](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_author_detail.png)

**備註：** 你還可以使用 `exclude` 屬性來聲明要從表單中排除的屬性列表（將顯示模型中的所有其他屬性）。

#### Sectioning the detail view

You can add "sections" to group related model information within the detail form, using the fieldsets attribute.

In the `BookInstance` model we have information related to what the book is (i.e. `name`, `imprint`, and `id`) and when it will be available (`status`, `due_back`). We can add these in different sections by adding the text in bold to our `BookInstanceAdmin` class.

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

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

Each section has its own title (or `None`, if you don't want a title) and an associated tuple of fields in a dictionary — the format is complicated to describe, but fairly easy to understand if you look at the code fragment immediately above.

Now navigate to a book instance view in your website; the form should appear as shown below:

![Admin Site - Improved BookInstance Detail with sections](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_bookinstance_detail_sections.png)

### Inline editing of associated records

Sometimes it can make sense to be able to add associated records at the same time. For example, it may make sense to have both the book information and information about the specific copies you've got on the same detail page.

You can do this by declaring inlines, of type TabularInline (horizonal layout) or StackedInline (vertical layout, just like the default model layout). You can add the `BookInstance` information inline to our `Book` detail by adding the lines below in bold near your `BookAdmin`:

```python
class BooksInstanceInline(admin.TabularInline):
    model = BookInstance

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'display\_genre')
    inlines = [BooksInstanceInline]
```

Now navigate to a view for a `Book` in your website — at the bottom you should now see the book instances relating to this book (immediately below the book's genre fields):

![Admin Site - Book with Inlines](/zh-TW/docs/Learn/Server-side/Django/Admin_site/admin_improved_book_detail_inlines.png)

In this case all we've done is declare our tabular inline class, which just adds all fields from the *inlined* model. You can specify all sorts of additional information for the layout, including the fields to display, their order, whether they are read only or not, etc. (see TabularInline for more information).

**備註：** There are some painful limits in this functionality! In the screenshot above we have three existing book instances, followed by three placeholders for new book instances (which look very similar!). It would be better to have NO spare book instances by default and just add them with the **Add another Book instance** link, or to be able to just list the `BookInstance`s as non-readable links from here. The first option can be done by setting the `extra` attribute to 0 in `BooksInstanceInline` model, try it by yourself.

自我挑戰
----

在本節中我們學到了很多東西，所以現在該你嘗試一些事情了。

1. 對於 `BookInstance` 列表視圖(list view)，添加代碼以顯示 `books`，`status`，`due back date` 和 `id`（而不是默認的 `__str__()` 文本）。
2. 使用與`Book/BookInstance`相同的方法將`Book`項目的內聯列表添加到`Author` 的詳細視圖(detail view)中。
小結
--

就是這樣！ 你現在已經了解瞭如何以最簡單和改進的形式設置管理者網站，如何創建超級用戶，以及如何瀏覽管理者網站，查看，刪除和更新記錄。 在此過程中，你已經創建了許多 Books，BookInstances，Genres 和 Authors，一旦我們創建了自己的 view 和 templates，便可以列出和顯示這些記錄。

延伸閱讀
----

* Writing your first Django app, part 2: Introducing the Django Admin (Django docs)
* The Django Admin site (Django Docs)
* 前頁
* Overview: Django
* 次頁


架設 Django 開發環境 - 學習該如何開發 Web

架設 Django 開發環境
==============

* 前頁
* Overview: Django
* 次頁

現在，你知道什麼是 Django。那麼我們將向你展示如何在 Windows，Linux（Ubuntu）和 Mac OSX 上設置和測試 Django 開發環境—無論你常用哪種操作系統，本文應該都能讓你開始開發 Django 應用程序。

|  |  |
| --- | --- |
| 先備知識: | 知道如何打開終端或命令行。了解如何在計算機的操作系統上安裝軟件包。 |
| 目標: | 在你的計算機操作系統上運行Django（2.0）開發環境。 |

Django 開發環境概覽
-------------

Django 使你輕鬆設置自己的電腦，以便開始開發網絡應用。這部分介紹在開發環境可以取得什麼，並概述了部分設置和配置選項。本文的其餘部分，介紹了在 Ubuntu，Mac OS X 和 Windows 上，安裝 Django 開發環境的*推薦方法*，以及如何測試。

### 什麼是 Django 開發環境?

開發環境是在本地計算機上安裝 Django，你可以在將 Django 部署到生產環境之前，用於開發和測試 Django 應用程序。

Django 本身提供的主要工具，是一組用於創建和使用 Django 項目的 Python 腳本，以及可用於在你的計算機的瀏覽器上，測試本地（即，你的計算機，而不是外部 Web 服務器）Django 網絡應用程序的簡單開發網路服務器 。

還有其他外部工具, 它們構成了開發環境的一部分, 我們將不再贅述。這些包括 文本編輯器 text editor (en-US) 或編輯代碼的 IDE，以及像 Git 這樣的源代碼控制管理工具，用於安全地管理不同版本的代碼。我們假設你已經安裝了一個文本編輯器。

### 什麼是 Django 設置選項?

Django 如何在安裝和配置方面非常靈活。Django 可以：

* 安裝在不同的操作系統上。
* 可以一起使用 Python 3 和 Python 2.
* 通過 Python 包索引（PyPi）安裝，和在許多情況下主機的包管理器應用程序。
* 配置為使用幾個數據庫之一，可能需要單獨安裝和配置。
* 運行在主系統 Python 環境中或在單獨的 Python 虛擬環境中運行。

每個選項都需要略微不同的配置和設置。以下小節解釋了你的一些選擇。對於本文的其餘部分，我們將介紹 Django 在少見的操作系統上的設置，考量該模塊的其餘部分。

**備註：** 其他可能的安裝選項在官方 Django 文檔中介紹。相應文件點擊這裡。

#### 支持哪些操作系統?

幾乎任何可以運行 Python 編程語言的機器可以運行 Django 網絡應用程序：Windows，Mac OSX，Linux/Unix，Solaris，僅舉幾例。幾乎任何計算機都應該在開發過程中運行 Django 所需的性能。

在本文中。我們將提供 Windows，Mac OS X 和 Linux/Unix 的說明。

#### 你應該使用什麼版本的 Python?

我們建議你使用最新版本 - 在編寫本文時，這是 Python 3.7。

如果需要，可以使用 Python 3.4 或更高版本（將來的版本中將刪除 Python 3.4 支持）。

**備註：** Python 2.7 不能與 Django 2.0 一起使用（Django 1.11.x 系列是最後一個支持 Python 2.7 的系列）。

#### 我們在哪裡下載 Django?

有三個地方可以下載 Django:

* Python 包含庫(PyPi),使用**pip**工具.這是獲取最新穩定版本的 Django 的最佳方式.
* 使用計算機軟件包管理器中的版本。與操作系統捆綁在一起的 Django 的分發提供了一種熟悉的安裝機制。請注意，打包版本可能相當舊，只能安裝到系統 Python 環境中（可能不是你想要的）。
* 可以從源代碼獲取並安裝的最新版本的 Python。這不是推薦給初學者，但是當你準備好開始貢獻給 Django 本身的時候，它是必需的。

本文介紹如何從 PyPi 安裝 Django，從獲得最新的穩定版本。

#### 哪個數據庫?

Django 支持四個主要數據庫（PostgreSQL，MySQL，Oracle 和 SQLite），還有一些社區庫，可以為其他流行的 SQL 和 NOSQL 數據庫，提供不同級別的支持。我們建議你為生產和開發，選擇相同的數據庫（儘管 Django 使用其對象關係映射器（ORM）抽像出許多數據庫差異，但是仍然存在可以避免的潛在問題 ).

對於本文（和本模塊的大部分），我們將使用將數據存放在文件中的 SQLite 數據庫。SQLite 旨在用作輕量級數據庫，不能支持高級並發。然而，這確實是唯讀的應用程序的絕佳選擇。

**備註：** 當你使用標準工具（django-admin）啟動你的網站項目時，Django 將默認配置為使用 SQLite。用來入門，這是一個很好的選擇，因為它不需要額外的配置和設置。

#### 安裝到整個本機系統還是 Python 虛擬環境中?

安裝 Python3 時，你將獲得一個由所有 Python3 代碼共享的單一全局環境。雖然你可以在環境中，安裝任何你喜歡的 Python 軟件包，但你一次只能安裝每個軟件包的一個特定版本。

**備註：** 安裝到全局環境中的 Python 應用程序可能會相互衝突（即，如果它們依賴於同一程序包的不同版本）。

如果你將 Django 安裝到默認/全局環境中，那麼你將只能在計算機上，定位一個版本的 Django。如果你想要創建新網站（使用最新版本的 Django）同時仍然維護依賴舊版本的網站，這可能是一個問題。

因此，經驗豐富的 Python / Django 開發人員，通常在獨立的 Python 虛擬環境中，運行 Python 應用程序。這樣可以在一台計算機上，實現多個不同的 Django 環境。 Django 開發團隊本身建議你使用 Python 虛擬環境！

本模塊假設你已將 Django 安裝到虛擬環境中，我們將向你展示如何做。

安裝 Python 3
-----------

為了使用 Django，你需要安裝 Python3.同樣你需要Python 包管理工具 — *pip3* —用來管理(安裝，更新和刪除)Django 和其他 Python 應用程序使用的 Python 軟件包／庫。

本書簡要說明如何根據需要檢查什麼版本，並根據需要安裝新版本，適用於**Ubuntu Linux 16.04, Mac OS X, and Windows 10。**

**備註：** 根據你的平台,你還可以從操作系統自己的軟件包管理器或其他機制安裝 Python / pip。對於大多數平台，你可以從https://www.python.org/downloads/下載所需的安裝文件，並使用適當的平台特定方法進行安裝。

### Ubuntu 18.04

Ubuntu Linux 18.04 LTS 默認包含 Python 3.6.5。你可以通過在 bash 終端中運行以下命令來確認：

```bash
python3 -V
 Python 3.6.5
```

然而，在默認情況下，為 Python 3（包括 Django）安裝軟件包的 Python 包管理工具**不可用。你**可以使用以下方式將**pip3**安裝在**bash**終端\*\*：\*\*

```bash
sudo apt install python3-pip
```

### macOS X

Mac OS X "El Capitan" 不包括 Python 3.你可以通過在 bash 終端中運行一下命令來確認：

```bash
python3 -V
 -bash: python3: command not found
```

你可以輕鬆從python.org安裝 Python 3（以及 pip3 工具）:

1. 下載所需的安裝程序:
    1. 點擊https://www.python.org/downloads/
    2. 選擇**Download Python 3.7.0**按鈕(確切的版本號可能不同).
2. 使用 Finder 找到文件，然後雙擊包文件。遵循安裝提示。
 （一般能拖拽就拖拽）

你現在可以檢查 Pyhon 3 來確認成功安裝，如下所示:

```bash
python3 -V
 Python 3.7.0
```

你也可以通過列出可用的軟件包來檢查 pip3 是否安裝:

```bash
pip3 list
```

### Windows 10

windows 默認不安裝,但你可以從python.org輕鬆安裝它（以及 pip3 工具）：

1. 下載所需版本:
    1. 點擊 https://www.python.org/downloads/
    2. 選擇 **Download Python 3.7.0** 按鈕(確切的版本號可能不同).
    3. 通過雙擊下載的文件並按照提示安裝 Python

你可以通過在命令提示符中輸入以下文本來驗證是否安裝了 Python:

```bash
py -3 -V
 Python 3.7.0
```

默認情況下，Windows 安裝程序包含 pip3（python 包管理器，你可以列出安裝的軟件包）:

```bash
pip3 list
```

**備註：** 安裝程序應設置上述命令工作所需的一切。但是，如果你收到無法找到 Python 的消息，則可能忘記將其添加到系統路徑中。你可以通過再次運行安裝程序，選擇「修改」"Modify"，然後選中第二頁上標有「將 Python 添加到環境變量」"Add Python to environment variables"的框來執行此操作。

在 Python 虛擬環境中使用 Django
-----------------------

我們將用於創建虛擬環境的庫是 virtualenvwrapper（Linux 和 macOS X）和 virtualenvwrapper-win （Windows），後者又使用 virtualenv (en-US)工具。包裝工具為所有平台上的接口管理創建了一致的界面。

### 安裝虛擬環境軟體

#### Ubuntu 虛擬環境設置

安裝 Python 和 pip 之後，你可以安裝 virtualenvwrapper（包括 virtualenv）。可在此處找到官方安裝指南，或按照以下說明操作。

使用 pip3 安裝該工具：

```bash
sudo pip3 install virtualenvwrapper
```

然後將以下行添加到 shell 啟動文件的末尾（這是主目錄中的隱藏文件名\*\*.bashrc\*\*）。這些設置了虛擬環境應該存在的位置，開發項目目錄的位置以及使用此軟件包安裝的腳本的位置 ：

```bash
export WORKON\_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER\_PYTHON=/usr/bin/python3
export VIRTUALENVWRAPPER\_VIRTUALENV\_ARGS=' -p /usr/bin/python3 '
export PROJECT\_HOME=$HOME/Devel
source /usr/local/bin/virtualenvwrapper.sh
```

**備註：** `VIRTUALENVWRAPPER_PYTHON` 和 `VIRTUALENVWRAPPER_VIRTUALENV_ARGS` 變量指向 Python3 的正常安裝位置，`source /usr/local/bin/virtualenvwrapper.sh`指向`virtualenvwrapper.sh`腳本的正常位置。如果 virtualenv 在測試時不起作用，那麼要檢查的一件事是 Python 和腳本位於預期的位置（然後適當地更改啟動文件）。

你可以使用`which virtualenvwrapper.sh` 和 `which python3`.的命令找到系統的正確位置。

然後在終端中運行以下命令重新加載啟動文件：

```bash
source ~/.bashrc
```

此時你應該看到一堆腳本正在運行，如下所示：

```bash
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject
...
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details
```

現在，你可以使用`mkvirtualenv`命令創建新的虛擬環境。

#### macOS X 虛擬環境設置

在 macOS X 上設置 virtualenvwrapper 與在 Ubuntu 上幾乎完全相同（同樣，你可以按照官方安裝指南或下面的說明進行操作。

使用 pip 安裝 virtualenvwrapper（並捆綁 virtualenv），如圖所示。

```bash
sudo pip3 install virtualenvwrapper
```

然後將以下幾行添加到 shell 啟動文件的末尾。

```bash
export WORKON\_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER\_PYTHON=/usr/bin/python3
export PROJECT\_HOME=$HOME/Devel
source /usr/local/bin/virtualenvwrapper.sh
```

**備註：** `VIRTUALENVWRAPPER_PYTHON`變量指向 Python3 的正常安裝位置，`source /usr/local/bin/virtualenvwrapper.sh`指向`virtualenvwrapper.sh`腳本的正常位置。如果 virtualenv 在測試時不起作用，那麼要檢查的一件事，是 Python 和腳本位於預期的位置（然後適當地更改啟動文件）。

例如，對 macOS 進行的一次安裝測試，最終在啟動文件中需要以下幾行：

```bash
export WORKON\_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER\_PYTHON=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3
export PROJECT\_HOME=$HOME/Devel
source /Library/Frameworks/Python.framework/Versions/3.7/bin/virtualenvwrapper.sh
```

你可以使用`which virtualenvwrapper.sh` 和 `which python3`的命令找到系統的正確位置。

這幾行與 Ubuntu 相同，但啟動文件是主目錄中、名稱不同的隱藏文件\*\*.bash\_profile\*\*。

**備註：** 如果在查找程序中找不到要編輯的\*\*.bash-profile\*\*，也可以使用 nano 在終端中打開它。

命令看起來像這樣：

```bash
cd ~  # Navigate to my home directory
ls -la #List the content of the directory. YOu should see .bash\_profile
nano .bash_profile # Open the file in the nano text editor, within the terminal
# Scroll to the end of the file, and copy in the lines above
# Use Ctrl+X to exit nano, Choose Y to save the file.
```

然後通過在終端中，進行以下調用，來重新加載啟動文件：

```bash
source ~/.bash_profile
```

此時，你可能會看到一堆腳本正在運行（與 Ubuntu 安裝相同的腳本）。你現在應該能夠使用`mkvirtualenv`命令，創建新的虛擬環境。

#### Windows 10 虛擬環境設置

安裝virtualenvwrapper-win比設置 virtualenvwrapper 更簡單，因為你不需要配置工具存放虛擬環境信息的位置（有默認值）。你需要做的就是，在命令提示符中運行以下命令：

```bash
pip3 install virtualenvwrapper-win
```

現在，你可以使用`mkvirtualenv`命令創建新的虛擬環境

### 創建虛擬環境

一旦你安裝了 virtualenvwrapper 或 virtualenvwrapper-win，那麼在所有平台上使用虛擬環境都非常相似。

現在，你可以使用`mkvirtualenv`命令創建新的虛擬環境。當此命令運行時，你將看到正在設置的環境（你看到的是略微特定 於平台的）。當命令完成時，新的虛擬環境，將處於活動狀態 - 你可以看到這一點，因為提示的開頭，將是括號中環境的名稱（如下所示）。
```

$ mkvirtualenv my_django_environment

Running virtualenv with interpreter /usr/bin/python3
...
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details
(my_django_environment) ubuntu@ubuntu:~$
```

現在，你可以在虛擬環境中，安裝 Django，並開始開發。

**備註：** 從本文開始（實際上是本系列教學），請假設任何命令都在 Python 虛擬環境中運行，就像我們在上面設置的那樣。

### 使用虛擬環境

你應該知道其他一些有用的命令（工具文檔中有更多，但這些是你經常使用的命令）：

* `deactivate` — 退出當前的 Python 虛擬環境
* `workon` — 列出可用的虛擬環境
* `workon name_of_environment` — 激活指定的 Python 虛擬環境
* `rmvirtualenv name_of_environment` — 刪除指定的環境
安裝 Django
---------

一旦你創建了一個虛擬環境，並調用了`workon`來輸入它，就可以使用 pip3 來安裝 Django。

```bash
pip3 install django
```

你可以通過運行以下命令來測試 Django 是否安裝（這只是測試 Python 可以找到 Django 模塊）：

```bash
# Linux/macOS X
python3 -m django --version
 2.0

# Windows
py -3 -m django --version
 2.0
```

**備註：** 如果上面的 Windows 命令沒有顯示 django 模塊，請嘗試：

```bash
py -m django --version
```

在 Windows 中，Python 3 腳本通過在命令前面加上`py -3`來啟動，儘管這可能會因具體安裝而異。如果遇到任何命令問題，請嘗試省略`-3`修飾符。在 Linux / macOS X 中，命令是`python3`。

**警告：** 本教程的其餘部分，使用 Linux 命令來調用 Python 3（python3）。如果你在 Windows 上工作，只需將此前綴替換為：`py -3`

測試你的安裝
------

上面的測試可以工作，但它不是很有趣。一個更有趣的測試是創建一個骨架項目並看到它工作。要做到這一點，先在你的命令提示符/終端導航到你想存儲你**Django**應用程序的位置。為你的測試站點創建一個文件夾並瀏覽它。

```bash
mkdir django_test
cd django_test
```

然後，你可以使用**django-admin**工具創建一個名為「 **mytestsite** 」的新骨架站點，如圖所示。創建網站後，你可以導航到文件夾，你將在其中找到管理項目的主要腳本，名為**manage.py**。

```bash
django-admin startproject mytestsite
cd mytestsite
```

我們可以使用**manage.py**和 `runserver` 命令，從此文件夾內運行開發 Web 服務器，如圖所示。

```bash
$ python3 manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

December 29, 2017 - 03:03:47
Django version 2.0, using settings 'mytestsite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

**備註：** 以上命令顯示 Linux / macOS X 命令。此時你可以忽略有關「14 個未應用的遷移」的警告！（"14 unapplied migration(s)" ）

一旦服務器運行，你可以通過導航到本地 Web 瀏覽器上的以下 URL 來查看該站點：`http://127.0.0.1:8000/`。你應該看到一個如下所示的網站：

![Django Skeleton App Homepage](/zh-TW/docs/Learn/Server-side/Django/development_environment/django_skeleton_app_homepage_django_4_0.png)

總結 Summary
----------

你現在已在計算機上啟動並運行 Django 開發環境。

在測試部分，你還簡要了解了，我們如何使用`django-admin startproject`，創建一個新的 Django 網站，並使用開發 Web 服務器（`python3 manage.py runserver`）在瀏覽器中運行它。在下一篇文章中，我們將擴展此過程，構建一個簡單、但完整的 Web 應用程序。

參見
--

* Quick Install Guide (Django docs)
* How to install Django — Complete guide (Django docs) - includes information on how to remove Django
* How to install Django on Windows (Django docs)
* 前頁
* Overview: Django
* 次頁


Django Tutorial Part 9: Working with forms - 學習該如何開發 Web

Django Tutorial Part 9: Working with forms
==========================================

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

在本教程中，我們將向你展示，如何在 Django 中使用 HTML 表單，特別是編寫表單以創建，更新和刪除模型實例的最簡單方法。作為本演示的一部分，我們將擴展 LocalLibrary 網站，以便圖書館員，可以使用我們自己的表單（而不是使用管理員應用程序）更新圖書，創建，更新和刪除作者。

|  |  |
| --- | --- |
| 前提: |
 完成先前所有的教程, 包含
 Django Tutorial Part 8: User authentication and permissions (en-US).
  |
| 目的: |
 了解如何製作表單來向用戶取得資訊並更新資料庫。了解**通用類別表單編輯視圖** ( generic class-based form editing views )
 能夠大幅簡化用於單一模型的表單製作。
  |

概述
--

HTML 表單是網頁上的一組一個或多個字段/小組件，可用於從用戶收集信息以提交到服務器。 表單是一種用於收集用戶輸入的靈活機制，因為有合適的小部件可以輸入許多不同類型的數據，包括文本框，複選框，單選按鈕，日期選擇器等。表單也是與服務器共享數據的相對安全的方式， 因為它們允許我們在具有跨站點請求偽造保護的`POST` 請求中發送數據。

儘管到目前為止，本教程中尚未創建任何表單，但我們已經在 Django Admin 網站中遇到過這些表單-例如，下面的屏幕截圖顯示了一種用於編輯我們的Book 模型的表單，該表單由許多選擇列表和 文字編輯器。

![Admin Site - Book Add](/zh-TW/docs/Learn/Server-side/Django/Forms/admin_book_add.png)

使用表單可能會很複雜！開發人員需要為表單編寫 HTML，在服務器上（也可能在瀏覽器中）驗證並正確清理輸入的數據，使用錯誤消息重新發布表單以通知用戶任何無效字段，並在成功提交數據後處理數據，最後以某種方式回應用戶以表示成功。 Django 表單通過提供一個框架使你能夠以編程方式定義表單及其字段，然後使用這些對像生成表單 HTML 代碼並處理許多驗證和用戶交互，從而完成了所有這些步驟中的大量工作。

在本教程中，我們將向你展示創建和使用表單的幾種方法，尤其是通用編輯表單視圖如何顯著減少創建表單來操縱表單所需的工作量。楷模。在此過程中，我們將擴展本地圖書館應用程序，方法是添加一個允許圖書館員續訂圖書的表格，並創建頁面以創建，編輯和刪除圖書和作者（複製上面顯示的表格的基本版本以編輯圖書） ）。

HTML 表單
-------

首先簡要介紹一下 HTML Forms。 考慮一個簡單的 HTML 表單，其中有一個用於輸入某些「團隊」名稱的文本字段及其相關標籤：

![Simple name field example in HTML form](/zh-TW/docs/Learn/Server-side/Django/Forms/form_example_name_field.png)

表單在 HTML 中定義為 `<form>...</form>` 標記內元素的集合，其中至少包含`type="submit"`.的`input`元素。

```html
<form action="/team\_name\_url/" method="post">
  <label for="team\_name">Enter name: </label>
  <input
 id="team\_name"
 type="text"
 name="name\_field"
 value="Default name for team." />
  <input type="submit" value="OK" />
</form>
```

雖然這裡只有一個用於輸入團隊名稱的文本字段，但是表單可以具有任意數量的其他輸入元素及其關聯的標籤。字段的`type` 屬性定義將顯示哪種小部件。字段的 `name` 和`id` 用於標識 JavaScript / CSS / HTML 中的字段，而 `value`定義該字段在首次顯示時的初始值。匹配的團隊標籤是使用`label` 標籤指定的（請參見上面的「輸入名稱」），其中的 `for` 字段包含相關`input`的`id` 值。

`submit` 輸入將顯示為一個按鈕（默認情況下），用戶可以按下該按鈕以將表單中所有其他輸入元素中的數據上載到服務器（在這種情況下，僅是`team_name`）。表單屬性定義用於發送數據的 HTTP`method` 以及服務器上數據的目的地（`action`）：

* `action`: 提交表單時，將數據發送到該資源/ URL 進行處理。如果未設置（或設置為空字符串），則表單將被提交回當前頁面 URL。
* `method`: 用於發送數據的 HTTP 方法：post 或 get。
    + 如果數據將導致服務器數據庫的更改，則應始終使用`POST` 方法，因為這樣可以使它更能抵抗跨站點的偽造請求攻擊。
    + `GET` 方法應僅用於不更改用戶數據的表單（例如搜索表單）。建議你在希望添加書籤或共享 URL 時使用。

服務器的角色是首先呈現初始表單狀態-包含空白字段，或預填充初始值。用戶按下「提交」按鈕後，服務器將從 Web 瀏覽器接收帶有值的表單數據，並且必須驗證信息。如果表單包含無效數據，則服務器應再次顯示該表單，這一次將在「有效」字段中顯示用戶輸入的數據，並顯示描述無效字段問題的消息。服務器收到包含所有有效表單數據的請求後，便可以執行適當的操作（例如，保存數據，返回搜索結果，上傳文件等），然後通知用戶。

可以想像，創建 HTML，驗證返回的數據，在需要時使用錯誤報告重新顯示輸入的數據以及對有效數據執行所需的操作都需要花費大量精力才能「正確」。 Django 通過刪除一些繁瑣且重複的代碼，使此操作變得更加容易！

Django 表單處理流程
-------------

Django 的表單處理使用了我們在以前的教程中學到的所有相同技術（用於顯示有關模型的信息）：視圖獲取請求，執行所需的任何操作，包括從模型中讀取數據，然後生成並返回 HTML 頁面（ 從模板中，我們傳遞一個包含要顯示的數據的上下文）。 使事情變得更加複雜的是，服務器還需要能夠處理用戶提供的數據，並在出現任何錯誤時重新顯示頁面。

 下面顯示了 Django 處理表單請求的過程流程圖，該流程圖從對包含表單的頁面的請求（以綠色顯示）開始。

 ![Updated form handling process doc.](/zh-TW/docs/Learn/Server-side/Django/Forms/form_handling_-_standard.png)

根據上圖，Django 表單處理的主要功能是：

1. 在用戶第一次請求時顯示默認表單。
    * 該表單可能包含空白字段（例如，如果你正在創建新記錄），或者可能會預先填充有初始值（例如，如果你正在更改記錄或具有有用的默認初始值）。
    * 由於此表單與任何用戶輸入的數據均不相關（儘管它可能具有初始值），因此在這一點上被稱為未綁定。
2. 從提交請求中接收數據並將其綁定到表單。
    * 將數據綁定到表單意味著當我們需要重新顯示表單時，用戶輸入的數據和任何錯誤均可用。
3. 清理並驗證數據。
    * 清理數據會對輸入執行清理操作（例如，刪除可能用於向服務器發送惡意內容的無效字符），並將其轉換為一致的 Python 類型。
    * 驗證會檢查該值是否適合該字段（例如，日期範圍正確，時間不要太短或太長等）
4. 如果任何數據無效，則這次重新顯示該表單，其中包含用戶填充的所有值和問題字段的錯誤消息。
5. 如果所有數據均有效，請執行所需的操作（例如，保存數據，發送和發送電子郵件，返回搜索結果，上傳文件等）
6. 完成所有操作後，將用戶重定向到另一個頁面。

Django 提供了許多工具和方法來幫助你完成上述任務。 最基本的是 `Form`類，它簡化了表單 HTML 的生成和數據清除/驗證的過程。 在下一節中，我們將使用頁面的實際示例描述表單如何工作，以使圖書館員可以續訂書籍。

**備註：** 當我們討論 Django 的更多「高級」表單框架類時，了解`Form`的使用方式將對你有所幫助。

使用表單和功能視圖續訂表單
-------------

接下來，我們將添加一個頁面，以使圖書館員可以續借借來的書。 為此，我們將創建一個允許用戶輸入日期值的表單。 我們將從當前日期（正常藉閱期）起 3 週內為該字段提供初始值，並添加一些驗證以確保館員不能輸入過去的日期或將來的日期。 輸入有效日期後，我們會將其寫入當前記錄的`BookInstance.due_back` 字段中。

該示例將使用基於函數的視圖和`Form` 類。 以下各節說明表單的工作方式，以及你需要對正在進行的 LocalLibrary 項目進行的更改。

### Form

`Form`類是 Django 表單處理系統的核心。 它指定表單中的字段，其佈局，顯示小部件，標籤，初始值，有效值，以及（一旦驗證）與無效字段關聯的錯誤消息。 該類還提供了使用預定義格式（表，列表等）在模板中呈現自身的方法，或用於獲取任何元素的值（啟用細粒度手動呈現）的方法。

#### 申報表格

`Form` 的聲明語法與聲明`Model`的語法非常相似，並且具有相同的字段類型（和一些相似的參數）。 這是有道理的，因為在兩種情況下，我們都需要確保每個字段都處理正確的數據類型，被限制為有效數據並具有顯示/文檔描述。

要創建一個表單，我們導入`Form` 庫，從`Form` 類派生，並聲明表單的字段。 下面顯示了我們的圖書館圖書續訂表格的一個非常基本的表格類：

```python
from django import forms

class RenewBookForm(forms.Form):
    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
```

#### Form fields

In this case we have a single `DateField` for entering the renewal date that will render in HTML with a blank value, the default label "*Renewal date:*", and some helpful usage text: "*Enter a date between now and 4 weeks (default 3 weeks).*" As none of the other optional arguments are specified the field will accept dates using the input\_formats: YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16), and will be rendered using the default widget: DateInput.

There are many other types of form fields, which you will largely recognise from their similarity to the equivalent model field classes: `BooleanField`, `CharField`, `ChoiceField`, `TypedChoiceField`, `DateField`, `DateTimeField`, `DecimalField`, `DurationField`, `EmailField`, `FileField`, `FilePathField`, `FloatField`, `ImageField`, `IntegerField`, `GenericIPAddressField`, `MultipleChoiceField`, `TypedMultipleChoiceField`, `NullBooleanField`, `RegexField`, `SlugField`, `TimeField`, `URLField`, `UUIDField`, `ComboField`, `MultiValueField`, `SplitDateTimeField`, `ModelMultipleChoiceField`, `ModelChoiceField`.

The arguments that are common to most fields are listed below (these have sensible default values):

* required: If `True`, the field may not be left blank or given a `None` value. Fields are required by default, so you would set `required=False` to allow blank values in the form.
* label: The label to use when rendering the field in HTML. If label is not specified then Django would create one from the field name by capitalising the first letter and replacing underscores with spaces (e.g. *Renewal date*).
* label\_suffix: By default a colon is displayed after the label (e.g. Renewal date\*\*:\*\*). This argument allows you to specify a different suffix containing other character(s).
* initial: The initial value for the field when the form is displayed.
* widget: The display widget to use.
* help\_text (as seen in the example above): Additional text that can be displayed in forms to explain how to use the field.
* error\_messages: A list of error messages for the field. You can override these with your own messages if needed.
* validators: A list of functions that will be called on the field when it is validated.
* localize: Enables the localisation of form data input (see link for more information).
* disabled: The field is displayed but its value cannot be edited if this is `True`. The default is `False`.

#### Validation

Django provides numerous places where you can validate your data. The easiest way to validate a single field is to override the method `clean_<fieldname>()` for the field you want to check. So for example, we can validate that entered `renewal_date` values are between now and 4 weeks by implementing `clean_renewal_date()` as shown below.

```python
from django import forms

from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
import datetime #for checking renewal date range.

class RenewBookForm(forms.Form):
    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")

    def clean\_renewal\_date(self):
        data = self.cleaned_data['renewal\_date']

        #Check date is not in past.
        if data < datetime.date.today():
            raise ValidationError(_('Invalid date - renewal in past'))

        #Check date is in range librarian allowed to change (+4 weeks).
        if data > datetime.date.today() + datetime.timedelta(weeks=4):
            raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))

        # Remember to always return the cleaned data.
        return data
```

There are two important things to note. The first is that we get our data using `self.cleaned_data['renewal_date']` and that we return this data whether or not we change it at the end of the function. This step gets us the data "cleaned" and sanitised of potentially unsafe input using the default validators, and converted into the correct standard type for the data (in this case a Python `datetime.datetime` object).

The second point is that if a value falls outside our range we raise a `ValidationError`, specifying the error text that we want to display in the form if an invalid value is entered. The example above also wraps this text in one of Django's translation functions `ugettext_lazy()` (imported as `_()`), which is good practice if you want to translate your site later.

**備註：** There are numerious other methods and examples for validating forms in Form and field validation (Django docs). For example, in cases where you have multiple fields that depend on each other, you can override the Form.clean() function and again raise a `ValidationError`.

That's all we need for the form in this example!

#### Copy the Form

Create and open the file **locallibrary/catalog/forms.py** and copy the entire code listing from the previous block into it.

### URL Configuration

Before we create our view, let's add a URL configuration for the *renew-books* page. Copy the following configuration to the bottom of **locallibrary/catalog/urls.py**.

```python
urlpatterns += [
    path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'),
]
```

The URL configuration will redirect URLs with the format **/catalog/book/*<bookinstance id>*/renew/** to the function named `renew_book_librarian()` in **views.py**, and send the `BookInstance` id as the parameter named `pk`. The pattern only matches if `pk` is a correctly formatted `uuid`.

**備註：** We can name our captured URL data "`pk`" anything we like, because we have complete control over the view function (we're not using a generic detail view class that expects parameters with a certain name). However `pk`, short for "primary key", is a reasonable convention to use!

### View

As discussed in the Django form handling process above, the view has to render the default form when it is first called and then either re-render it with error messages if the data is invalid, or process the data and redirect to a new page if the data is valid. In order to perform these different actions, the view has to be able to know whether it is being called for the first time to render the default form, or a subsequent time to validate data.

For forms that use a `POST` request to submit information to the server, the most common pattern is for the view to test against the `POST` request type (`if request.method == 'POST':`) to identify form validation requests and `GET` (using an `else` condition) to identify the initial form creation request. If you want to submit your data using a `GET` request then a typical approach for identifying whether this is the first or subsequent view invocation is to read the form data (e.g. to read a hidden value in the form).

The book renewal process will be writing to our database, so by convention we use the `POST` request approach. The code fragment below shows the (very standard) pattern for this sort of function view.

```python
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
import datetime

from .forms import RenewBookForm

def renew\_book\_librarian(request, pk):
    book_inst=get_object_or_404(BookInstance, pk = pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned\_data as required (here we just write it to the model due\_back field)
            book_inst.due_back = form.cleaned_data['renewal\_date']
            book_inst.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )

    # If this is a GET (or any other method) create the default form.
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        form = RenewBookForm(initial={'renewal\_date': proposed_renewal_date,})

    return render(request, 'catalog/book\_renew\_librarian.html', {'form': form, 'bookinst':book_inst})
```

First we import our form (`RenewBookForm`) and a number of other useful objects/methods used in the body of the view function:

* `get_object_or_404()`: Returns a specified object from a model based on its primary key value, and raises an `Http404` exception (not found) if the record does not exist.
* `HttpResponseRedirect`: This creates a redirect to a specified URL (HTTP status code 302).
* `reverse()`: This generates a URL from a URL configuration name and a set of arguments. It is the Python equivalent of the `url` tag that we've been using in our templates.
* `datetime`: A Python library for manipulating dates and times.

In the view we first use the `pk` argument in `get_object_or_404()` to get the current `BookInstance` (if this does not exist, the view will immediately exit and the page will display a "not found" error). If this is *not* a `POST` request (handled by the `else` clause) then we create the default form passing in an `initial` value for the `renewal_date` field (as shown in bold below, this is 3 weeks from the current date).

```python
    book_inst=get_object_or_404(BookInstance, pk = pk)

    # If this is a GET (or any other method) create the default form
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        form = RenewBookForm(initial={'renewal\_date': proposed_renewal_date,})

    return render(request, 'catalog/book\_renew\_librarian.html', {'form': form, 'bookinst':book_inst})
```

After creating the form, we call `render()` to create the HTML page, specifying the template and a context that contains our form. In this case the context also contains our `BookInstance`, which we'll use in the template to provide information about the book we're renewing.

If however this is a `POST` request, then we create our `form` object and populate it with data from the request. This process is called "binding" and allows us to validate the form. We then check if the form is valid, which runs all the validation code on all of the fields — including both the generic code to check that our date field is actually a valid date and our specific form's `clean_renewal_date()` function to check the date is in the right range.

```python
    book_inst=get_object_or_404(BookInstance, pk = pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned\_data as required (here we just write it to the model due\_back field)
            book_inst.due_back = form.cleaned_data['renewal\_date']
            book_inst.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )

    return render(request, 'catalog/book\_renew\_librarian.html', {'form': form, 'bookinst':book_inst})
```

If the form is not valid we call `render()` again, but this time the form value passed in the context will include error messages.

If the form is valid, then we can start to use the data, accessing it through the `form.cleaned_data` attribute (e.g. `data = form.cleaned_data['renewal_date']`). Here we just save the data into the `due_back` value of the associated `BookInstance` object.

**警告：** While you can also access the form data directly through the request (for example `request.POST['renewal_date']` or `request.GET['renewal_date']` (if using a GET request) this is NOT recommended. The cleaned data is sanitised, validated, and converted into Python-friendly types.

The final step in the form-handling part of the view is to redirect to another page, usually a "success" page. In this case we use `HttpResponseRedirect` and `reverse()` to redirect to the view named `'all-borrowed'` (this was created as the "challenge" in Django Tutorial Part 8: User authentication and permissions (en-US)). If you didn't create that page consider redirecting to the home page at URL '/').

That's everything needed for the form handling itself, but we still need to restrict access to the view to librarians. We should probably create a new permission in `BookInstance` ("`can_renew`"), but to keep things simple here we just use the `@permission_required` function decorator with our existing `can_mark_returned` permission.

The final view is therefore as shown below. Please copy this into the bottom of **locallibrary/catalog/views.py**.

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

from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
import datetime

from .forms import RenewBookForm

@permission\_required('catalog.can\_mark\_returned')
def renew\_book\_librarian(request, pk):
    """
 View function for renewing a specific BookInstance by librarian
 """
    book_inst=get_object_or_404(BookInstance, pk = pk)

    # If this is a POST request then process the Form data
    if request.method == 'POST':

        # Create a form instance and populate it with data from the request (binding):
        form = RenewBookForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():
            # process the data in form.cleaned\_data as required (here we just write it to the model due\_back field)
            book_inst.due_back = form.cleaned_data['renewal\_date']
            book_inst.save()

            # redirect to a new URL:
            return HttpResponseRedirect(reverse('all-borrowed') )

    # If this is a GET (or any other method) create the default form.
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
        form = RenewBookForm(initial={'renewal\_date': proposed_renewal_date,})

    return render(request, 'catalog/book\_renew\_librarian.html', {'form': form, 'bookinst':book_inst})
```

### The template

Create the template referenced in the view (**/catalog/templates/catalog/book\_renew\_librarian.html**) and copy the code below into it:

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

    <h1>Renew: {{bookinst.book.title}}</h1>
    <p>Borrower: {{bookinst.borrower}}</p>
    <p{% if bookinst.is\_overdue %} class="text-danger"{% endif %}>Due date: {{bookinst.due\_back}}</p>

    <form action="" method="post">
        {% csrf\_token %}
        <table>
        {{ form }}
        </table>
        <input type="submit" value="Submit" />
    </form>

{% endblock %}
```

Most of this will be completely familiar from previous tutorials. We extend the base template and then redefine the content block. We are able to reference `{{bookinst}}` (and its variables) because it was passed into the context object in the `render()` function, and we use these to list the book title, borrower and the original due date.

The form code is relatively simple. First we declare the `form` tags, specifying where the form is to be submitted (`action`) and the `method` for submitting the data (in this case an "HTTP POST") — if you recall the HTML Forms overview at the top of the page, an empty `action` as shown, means that the form data will be posted back to the current URL of the page (which is what we want!). Inside the tags we define the `submit` input, which a user can press to submit the data. The `{% csrf_token %}` added just inside the form tags is part of Django's cross-site forgery protection.

**備註：** Add the `{% csrf_token %}` to every Django template you create that uses `POST` to submit data. This will reduce the chance of forms being hijacked by malicious users.

All that's left is the `{{form}}` template variable, which we passed to the template in the context dictionary. Perhaps unsurprisingly, when used as shown this provides the default rendering of all the form fields, including their labels, widgets, and help text — the rendering is as shown below:

```html
<tr>
  <th><label for="id\_renewal\_date">Renewal date:</label></th>
  <td>
    <input
 id="id\_renewal\_date"
 name="renewal\_date"
 type="text"
 value="2016-11-08"
 required />
    <br />
    <span class="helptext"
 >Enter date between now and 4 weeks (default 3 weeks).</span
 >
  </td>
</tr>
```

**備註：** It is perhaps not obvious because we only have one field, but by default every field is defined in its own table row (which is why the variable is inside `table` tags above). This same rendering is provided if you reference the template variable `{{ form.as_table }}`.

If you were to enter an invalid date, you'd additionally get a list of the errors rendered in the page (shown in bold below).

```html
<tr>
  <th><label for="id\_renewal\_date">Renewal date:</label></th>
  <td>
    <ul class="errorlist">
      <li>Invalid date - renewal in past</li>
    </ul>
    <input
 id="id\_renewal\_date"
 name="renewal\_date"
 type="text"
 value="2015-11-08"
 required />
    <br />
    <span class="helptext"
 >Enter date between now and 4 weeks (default 3 weeks).</span
 >
  </td>
</tr>
```

#### Other ways of using form template variable

Using `{{form}}` as shown above, each field is rendered as a table row. You can also render each field as a list item (using `{{form.as_ul}}` ) or as a paragraph (using `{{form.as_p}}`).

What is even more cool is that you can have complete control over the rendering of each part of the form, by indexing its properties using dot notation. So for example we can access a number of separate items for our `renewal_date` field:

* `{{form.renewal_date}}:` The whole field.
* `{{form.renewal_date.errors}}`: The list of errors.
* `{{form.renewal_date.id_for_label}}`: The id of the label.
* `{{form.renewal_date.help_text}}`: The field help text.
* etc!

For more examples of how to manually render forms in templates and dynamically loop over template fields, see Working with forms > Rendering fields manually (Django docs).

### Testing the page

If you accepted the "challenge" in Django Tutorial Part 8: User authentication and permissions (en-US) you'll have a list of all books on loan in the library, which is only visible to library staff. We can add a link to our renew page next to each item using the template code below.

```django
{% if perms.catalog.can\_mark\_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a>{% endif %}
```

**備註：** Remember that your test login will need to have the permission "`catalog.can_mark_returned`" in order to access the renew book page (perhaps use your superuser account).

You can alternatively manually construct a test URL like this — `http://127.0.0.1:8000/catalog/book/<bookinstance_id>/renew/` (a valid bookinstance id can be obtained by navigating to a book detail page in your library, and copying the `id` field).

### What does it look like?

If you are successful, the default form will look like this:

![](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_renew_default.png)

The form with an invalid value entered, will look like this:

![](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_renew_invalid.png)

The list of all books with renew links will look like this:

![](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_renew_allbooks.png)

ModelForms
----------

Creating a `Form` class using the approach described above is very flexible, allowing you to create whatever sort of form page you like and associate it with any model or models.

However if you just need a form to map the fields of a *single* model then your model will already define most of the information that you need in your form: fields, labels, help text, etc. Rather than recreating the model definitions in your form, it is easier to use the ModelForm helper class to create the form from your model. This `ModelForm` can then be used within your views in exactly the same way as an ordinary `Form`.

A basic `ModelForm` containing the same field as our original `RenewBookForm` is shown below. All you need to do to create the form is add `class Meta` with the associated `model` (`BookInstance`) and a list of the model `fields` to include in the form (you can include all fields using `fields = '__all__'`, or you can use `exclude` (instead of `fields`) to specify the fields *not* to include from the model).

```python
from django.forms import ModelForm
from .models import BookInstance

class RenewBookModelForm(ModelForm):
    class Meta:
        model = BookInstance
        fields = ['due\_back',]
```

**備註：** This might not look like all that much simpler than just using a `Form` (and it isn't in this case, because we just have one field). However if you have a lot of fields, it can reduce the amount of code quite significantly!

The rest of the information comes from the model field definitions (e.g. labels, widgets, help text, error messages). If these aren't quite right, then we can override them in our `class Meta`, specifying a dictionary containing the field to change and its new value. For example, in this form we might want a label for our field of "*Renewal date*" (rather than the default based on the field name: *Due date*), and we also want our help text to be specific to this use case. The `Meta` below shows you how to override these fields, and you can similarly set `widgets` and `error_messages` if the defaults aren't sufficient.

```python
class Meta:
    model = BookInstance
    fields = ['due\_back',]
    labels = { 'due\_back': _('Renewal date'), }
    help_texts = { 'due\_back': _('Enter a date between now and 4 weeks (default 3).'), }
```

To add validation you can use the same approach as for a normal `Form` — you define a function named `clean_field_name()` and raise `ValidationError` exceptions for invalid values. The only difference with respect to our original form is that the model field is named `due_back` and not "`renewal_date`".

```python
from django.forms import ModelForm
from .models import BookInstance

class RenewBookModelForm(ModelForm):
    def clean\_due\_back(self):
       data = self.cleaned_data['due\_back']

       #Check date is not in past.
       if data < datetime.date.today():
           raise ValidationError(_('Invalid date - renewal in past'))

       #Check date is in range librarian allowed to change (+4 weeks)
       if data > datetime.date.today() + datetime.timedelta(weeks=4):
           raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))

       # Remember to always return the cleaned data.
       return data

    class Meta:
        model = BookInstance
        fields = ['due\_back',]
        labels = { 'due\_back': _('Renewal date'), }
        help_texts = { 'due\_back': _('Enter a date between now and 4 weeks (default 3).'), }
```

The class `RenewBookModelForm` below is now functionally equivalent to our original `RenewBookForm`. You could import and use it wherever you currently use `RenewBookForm`.

Generic editing views
---------------------

The form handling algorithm we used in our function view example above represents an extremely common pattern in form editing views. Django abstracts much of this "boilerplate" for you, by creating generic editing views for creating, editing, and deleting views based on models. Not only do these handle the "view" behaviour, but they automatically create the form class (a `ModelForm`) for you from the model.

**備註：** In addition to the editing views described here, there is also a FormView class, which lies somewhere between our function view and the other generic views in terms of "flexibility" vs "coding effort". Using `FormView` you still need to create your `Form`, but you don't have to implement all of the standard form-handling pattern. Instead you just have to provide an implementation of the function that will be called once the submitted is known to be be valid.

In this section we're going to use generic editing views to create pages to add functionality to create, edit, and delete `Author` records from our library — effectively providing a basic reimplementation of parts of the Admin site (this could be useful if you need to offer admin functionality in a more flexible way that can be provided by the admin site).

### Views

Open the views file (**locallibrary/catalog/views.py**) and append the following code block to the bottom of it:

```python
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Author

class AuthorCreate(CreateView):
    model = Author
    fields = '\_\_all\_\_'
    initial={'date\_of\_death':'05/01/2018',}

class AuthorUpdate(UpdateView):
    model = Author
    fields = ['first\_name','last\_name','date\_of\_birth','date\_of\_death']

class AuthorDelete(DeleteView):
    model = Author
    success_url = reverse_lazy('authors')
```

As you can see, to create the views you need to derive from `CreateView`, `UpdateView`, and `DeleteView` (respectively) and then define the associated model.

For the "create" and "update" cases you also need to specify the fields to display in the form (using in same syntax as for `ModelForm`). In this case we show both the syntax to display "all" fields, and how you can list them individually. You can also specify initial values for each of the fields using a dictionary of *field\_name*/*value* pairs (here we arbitrarily set the date of death for demonstration purposes — you might want to remove that!). By default these views will redirect on success to a page displaying the newly created/edited model item, which in our case will be the author detail view we created in a previous tutorial. You can specify an alternative redirect location by explicitly declaring parameter `success_url` (as done for the `AuthorDelete` class).

The `AuthorDelete` class doesn't need to display any of the fields, so these don't need to be specified. You do however need to specify the `success_url`, because there is no obvious default value for Django to use. In this case we use the `reverse_lazy()` function to redirect to our author list after an author has been deleted — `reverse_lazy()` is a lazily executed version of `reverse()`, used here because we're providing a URL to a class-based view attribute.

### Templates

The "create" and "update" views use the same template by default, which will be named after your model: **model\_name\_form.html** (you can change the suffix to something other than **\_form** using the `template_name_suffix` field in your view, e.g. `template_name_suffix = '_other_suffix'`)

Create the template file **locallibrary/catalog/templates/catalog/author\_form.html** and copy in the text below.

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

{% block content %}
<form action="" method="post">
  {% csrf\_token %}
  <table>
    {{ form.as\_table }}
  </table>
  <input type="submit" value="Submit" />
</form>
{% endblock %}
```

This is similar to our previous forms, and renders the fields using a table. Note also how again we declare the `{% csrf_token %}` to ensure that our forms are resistant to CSRF attacks.

The "delete" view expects to find a template named with the **format model\_name\_confirm\_delete.html** (again, you can change the suffix using `template_name_suffix` in your view). Create the template file **locallibrary/catalog/templates/catalog/author\_confirm\_delete.html** and copy in the text below.

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

{% block content %}

<h1>Delete Author</h1>

<p>Are you sure you want to delete the author: {{ author }}?</p>

<form action="" method="POST">
  {% csrf\_token %}
  <input type="submit" value="Yes, delete." />
</form>

{% endblock %}
```

### URL configurations

Open your URL configuration file (**locallibrary/catalog/urls.py**) and add the following configuration to the bottom of the file:

```python
urlpatterns += [
    path('author/create/', views.AuthorCreate.as_view(), name='author\_create'),
    path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author\_update'),
    path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author\_delete'),
]
```

There is nothing particularly new here! You can see that the views are classes, and must hence be called via `.as_view()`, and you should be able to recognise the URL patterns in each case. We must use `pk` as the name for our captured primary key value, as this is the parameter name expected by the view classes.

The author create, update, and delete pages are now ready to test (we won't bother hooking them into the site sidebar in this case, although you can do so if you wish).

**備註：** Observant users will have noticed that we didn't do anything to prevent unauthorised users from accessing the pages! We leave that as an exercise for you (hint: you could use the `PermissionRequiredMixin` and either create a new permission or reuse our `can_mark_returned` permission).

### Testing the page

First login to the site with an account that has whatever permissions you decided are needed to access the author editing pages.

Then navigate to the author create page: http://127.0.0.1:8000/catalog/author/create/, which should look like the screenshot below.

![Form Example: Create Author](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_create_author.png)

Enter values for the fields and then press **Submit** to save the author record. You should now be taken to a detail view for your new author, with a URL of something like `http://127.0.0.1:8000/catalog/author/10`.

You can test editing records by appending */update/* to the end of the detail view URL (e.g. `http://127.0.0.1:8000/catalog/author/10/update/`) — we don't show a screenshot, because it looks just like the "create" page!

Last of all we can delete the page, by appending delete to the end of the author detail-view URL (e.g. `http://127.0.0.1:8000/catalog/author/10/delete/`). Django should display the delete page shown below. Press **Yes, delete.** to remove the record and be taken to the list of all authors.

![](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_delete_author.png)

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

Create some forms to create, edit and delete `Book` records. You can use exactly the same structure as for `Authors`. If your **book\_form.html** template is just a copy-renamed version of the **author\_form.html** template, then the new "create book" page will look like the screenshot below:

![](/zh-TW/docs/Learn/Server-side/Django/Forms/forms_example_create_book.png)

Summary
-------

Creating and handling forms can be a complicated process! Django makes it much easier by providing programmatic mechanisms to declare, render and validate forms. Furthermore, Django provides generic form editing views that can do *almost all* the work to define pages that can create, edit, and delete records associated with a single model instance.

There is a lot more that can be done with forms (check out our See also list below), but you should now understand how to add basic forms and form-handling code to your own websites.

See also
--------

* Working with forms (Django docs)
* Writing your first Django app, part 4 > Writing a simple form (Django docs)
* The Forms API (Django docs)
* Form fields (Django docs)
* Form and field validation (Django docs)
* Form handling with class-based views (Django docs)
* Creating forms from models (Django docs)
* Generic editing views (Django docs)
* 前頁  (en-US)
* Overview: Django
* 次頁  (en-US)


何謂無障礙網頁？ - 學習該如何開發 Web

何謂無障礙網頁？
========

* Overview: Accessibility
* 次頁  (en-US)

這篇文章給「到底什麼是無障礙網頁」的模塊，開了個好起頭：以下將包括我們該考慮什麼樣的用戶以及理由、不同的人要在 web 用什麼工具互動、還有如何令無障礙網頁成為 web 開發的一部分。

|  |  |
| --- | --- |
| 先決要求： | 基本資訊能力、還有對 HTML 與 CSS 有基本的理解。 |
| 目標： | 熟悉無障礙網頁，包含它是什麼、還有它如何影響作為 web 開發者的你。 |

到底什麼是無障礙網頁？
-----------

無障礙是盡可能令更多人，使用你網站的實做：一般來說，我們會認為這屬於身心障礙者的範疇，但它其實會涵蓋其他群體：像是使用行動設備、或者網速很慢的人。

也可以把無障礙想成：所有人，不論他們的能力或環境如何，都要同等看待、並給予同等機會。就如同我們不能把坐在輪椅的人，排除在某棟物理大樓之外：目前的公共建築，通常都會有電梯或輪椅坡道；我們也不能排除視障或手機用戶，使用我們的網站。儘管我們生而不同，但我們都是人，因此，我們都擁有相同的人權。

無障礙是好事，但也是某些國家法律的一部分，也能開啟一些關係到你的服務或產品的市場。

無障礙與其所需之最佳實做將使大家受益：

* 提昇無障礙的語意 HTML 能增進 SEO，令網站更容易被找到、也更易於銷售。
* 關注無障礙網頁會展現優良的倫理道德、以增進你的公共形象。
* 無障礙網頁的一些作法也能令網站易於給其他族群使用，像是手機用戶、低網速……等等。事實上，任何人都能從此類改進中獲益。
* 我們還要提提某些地區的法律規定嗎？
你在鎖定什麼樣的障礙者？
------------

身心障礙者和非身心障礙者一樣多元，他們的障別也是如此。重點是，不要光用自己使用電腦和 web 的角度去思考這件事：*你並不是無障礙網頁的用戶*。下文將解釋應當考慮的障別，還有他們訪問 web 內容的特殊工具（通常稱作 **assistive technologies**、**AT**、**輔助工具**、**輔具**）。

**備註：** 世界衛生組織的殘疾與健康指出「超過 10 億人，約佔世界人口的 15%，患有某種形式的殘疾。」、且「1.1 億至 1.9 億成年人有很嚴重的功能性障礙。」。

### 視覺障礙

視覺障礙包括盲人、低度視覺、色盲……等等。這類的用戶會使用擴視器（screen magnifier，可能是物理擴視機、或是軟體的縮放功能：當代多數瀏覽器和作業系統都有這種功能），也有些人會用螢幕閱讀器（screen reader，朗讀數位文字的軟體）：

* 有些是商業軟體，例如 JAWS（Windows）和 Window Eyes（Windows）。
* 也有些是自由軟體，例如 NVDA（Windows）、ChromeVox（Chrome、Windows、Mac OS X）、Orca（Linux）
* 還有些是系統內建，例如 VoiceOver（Mac OS X 與 iOS）、Narrator（Microsoft Windows）、ChromeVox（ChromeOS）、TalkBack（Android）。

熟悉螢幕閱讀器是個好主意；你得設定好螢幕閱讀器、還要會使用它，以理解其工作原理。請參見cross browser testing screen readers guide (en-US)以深入理解。以下影片提供了簡單的體驗。

在統計方面，世界衛生組織表明：「全球視力受損人數約 2.53 億：3600 萬人患有盲症，2.17 億人有中度至重度視力損害。」（請參見視力損害和盲症）。如果網站編寫不正確，你的網站就會失去如此龐大的和重要的用戶群：這大約是全美國的總人口數。

### 聽覺障礙

聽覺障礙者、或聾人，是指聽力低落、或毫無聽力的群體。聽覺障礙者通常會用輔具（請參見Assistive Devices for People with Hearing, Voice, Speech, or Language Disorders），但沒有給電腦/web 使用的專門輔具。

不過，請記得有專門技術，會針對有聲內容，提供閱讀的替代文字。有簡單的文本記錄（text transcript）、也有能在影片出現的追蹤文字（text track），例如字幕。接下來將有文章深入探討。

聽覺障礙者也是龐大的人口，世界衛生組織在耳聾和聽力損失指出：「全球有 3.6 億人患有殘疾性聽力損失」。

### 行動障礙

有運動障礙的人，可能是純粹身體問題（例如： 肢體殘缺或癱瘓）或是四肢無力、失去控制等神經或遺傳疾病。他們可能難以使用滑鼠做出精確的手部動作，甚至可能只能使用頭指針（head pointer）操作電腦。

這些殘疾也可能是老化的結果，而不是受到創傷或疾病，或可能是硬體的限制——有些使用者可能沒有滑鼠。

通常影響開發者開發網站的需求是要能使用鍵盤操作網頁——我們會在後續的文內討論使用鍵盤操作網頁。雖然這個需求有些麻煩，但這是一個很好的主意，請開發者嘗試看看。例如：你可以使用 Tab 鍵在表單中切換填寫項目嗎？你可以在我們的跨瀏覽器測試中找到更多關於使用鍵盤控制網頁的相關資訊 (en-US)。

統計數據顯示，許多人有行動障礙。 美國疾病控制和預防中心的殘疾統計數據（涵蓋範圍為非法人的 18 歲以上成人）顯示，在美國，有身體機能障礙者佔成人人口的 15.1% 。

### 認知障礙

最後一類，可能是最廣泛出現的殘疾——認知障礙。認知障礙廣義可涵蓋從精神疾病到學習困難，包含ADHD（注意缺陷多動障礙）、自閉症患者、思覺失調以及許多其他相關疾病。這些與記憶、解決問題能力、理解能力與注意力等問題相關的疾病，都大大影響病患的日常生活。

這些疾病會影響病患使用網站，而最常見的問題就是他們會在理解如何完成網頁操作、記憶以往完成網頁操作的過程、以及經歷各種不同的操作流程與不一致的佈局/導覽列/其他頁面功能時，增加許許多多的挫敗感。

 與其他網路無障礙問題不同，我們無法快速解決認知障礙引起的網路操作問題；
 你唯一能做的就是設計網站時，盡可能符合邏輯性、一致性與可用性，例如：

* 頁面一致 － 導覽列、頁眉、頁腳和主要內容區塊在每一個頁面都位在相同的位置。
* 網站內工具設計精良，易於使用。
* 多個階段的流程（例如註冊），被細分有邏輯性的步驟，並提醒使用者如何進行流程，以及他需要多長的時間才能完成流程。
* 工作流程是合乎邏輯的、簡單的，並且盡可能減少需要與網站互動的流程
 例如：註冊和登入網站常常就是很複雜且不必要的。
* 適量呈現頁面資訊，盡量讓頁面呈現的資訊不要過多或過密。
* 適當的用語，頁面呈現的用語須標準或通俗易懂，不要充滿不必要的行話或俚語。
* 內容的重點處適時以某些方式（例如：使用螢光色標示）突顯出來。
* 使用者操作錯誤的地方也需要標示出來，並且提供相關訊息或解決方法，幫助使用者解決錯誤。

上述這些並不是「無障礙技術」，但他們是很好的設計理念。他們會讓使用者受益良多，因此他們應該成為你開發網站的工作標準之一。

康乃爾大學 2014 年的殘疾報告顯示，2014 年美國 21 歲至 64 歲的人中，有 4.5% 的人有認知障礙。

**備註：** WebAIM 的認知頁面提供了更多資訊，值得一讀

專案引入無障礙
-------

有一個很常見的迷思，就是在專案管理方面，無障礙屬於昂貴的「外加」成本。如果有以下情形的話，這個迷思*的確會*發生：

* 針對明顯有障礙的網站，想要「重寫」為無障礙網站。
* 在專案後期才想到無障礙網頁相關問題時。

如果在專案初期就考慮到無障礙網頁，大多數無障礙內容的成本可以最小化。

 當在規劃專案時，將無障礙測試納入你的測試中，就像測試其他功能一樣（例如：移動裝置 UI 測試）。
 及早測試、經常測試，理想情況是運行自動化測試以檢測缺少的功能（例如：圖片缺少替代文字 (en-US)、不好的連結文字 － 請參見 Element relationships and context (en-US)），並做一些測試，為了讓殘疾人士能夠操作更複雜的網站功能。例如：

* 日期選擇器是否可以提供給螢幕閱讀器的人使用？
* 如果內容動態更新了，視障人士是否能馬上收到資訊？
* 使用鍵盤或觸控式介面的人能順利操作我的 UI 按鈕嗎？

 你應該記錄自己網站中有問題的地方，並努力讓這些地方變成無障礙或考慮解決方案 / 替代方案，並持續進行測試。文字內容（你可以在下一章看到更多資訊）要做到無障礙很簡單，但是如你想要在網站中放上更多絢麗的 3D 圖形，那應該要怎麼做呢？
 你應該評估專案的預算，並考慮有什麼解決方案可以讓這些絢麗的 3D 圖形容易被存取。
 例如：你可以支付高額的費用，讓那些多媒體內容轉錄成無障礙設備可存取的資訊，雖然昂貴，卻是可行的方案。

雖然要實現 100% 無障礙網頁是一種難以達成的理想，世事難料，你總是會遇到一些不常見的問題，導致某個使用者難以使用網頁，但你還是應該盡可能做到無障礙網頁。如果你打算使用 WebGL 製作的炫麗的 3D 圓餅圖，可以撰寫替代的文字，來傳達圖表的資訊。或者你可以只使用表格來傳達資訊而不使用 3D 圓餅圖，這樣每個人都可以一目了然表格傳達的資訊、網頁傳輸編碼更快、CPU 使用率更低，並且更容易維護。

但是如果你是一個有趣的 3D 藝術畫廊網站開發者，那麼期待這些藝術品能夠傳達給視障人士，就不太合理，因為圖畫這類的藝術品本身就是透過視覺作為傳達訊息的媒介。

為了表明你對無障礙網頁的關心，請在你的網站上發佈無障礙網頁聲明，並詳細說明你為無障礙網頁做了哪些事情、採取了哪些步驟。如果有人抱怨你的網站存在無障礙問題，請他們回報給你們，並嘗試解決。

**備註：** 我們的 Handling common accessibility problems article (en-US) 包含了應該進行詳細測試的無障礙網頁規範。

總而言之：

* 在專案初期就考量到無障礙網頁、還要測試得早而多。一如其他錯誤，無障礙網頁問題在越晚發現時，麻煩也越大。
* 謹記無障礙網頁，是對大家都有利的。例如說，精實的語意標記不只對螢幕閱讀有利，也利於快速載入與效能，從而對大家有利，尤其對行動設備、或是網速過慢的人。
* 再網站上標明支援無障礙網頁，並鼓勵身心障礙者參與。
無障礙網頁的指引與法律
-----------

有一些針對無障礙網頁的檢查清單和指引能夠用做測試，它們乍看之下可能令人眼花撩亂。我們建議你只要專注熟悉的基本領域、並理解指引裡面，與你最相關的高層次結構。

* 首先，W3C 針對無障礙網頁的一致性，發布了龐大而詳盡、並包含精確而不講明具體技術的準則文件。它們通常稱作 Web Content Accessibility Guidelines（WCAG，無障礙網頁內容指引），無論從哪個角度來說，讀起來都不是普通的長。整個準則能分為以下四大類：它指定如何實做可感知、可操作、可理解、還有健壯（perceivable, operable, understandable, robust）的內容。要取得簡易指引並開始學習，可以去參閱 WCAG at a Glance。你不需要學習 WCAG 的一切：只要了解主要的關注領域，並使用各種技術和工具來高亮任何不符 WCAG 標準的領域就型（參閱以下）。
* 你的國家或地區可能有特別立法，以管理這方面人口的需求：例如美國的 Section 508 of the Rehabilitation Act、德國的 Federal Ordinance on Barrier-Free Information Technology、英國的 Equality Act、義大利的 Accessibilità、還有澳洲的 Disability Discrimination Act……等等。
 （譯註：針對繁體中文，台灣有基於《身心障礙者權益保障法》的無障礙網頁開發規範、香港有基於《殘疾歧視條例》的無障礙設計指引）

因此，儘管 WCAG 有所指引，你的國家可能還是有管理無障礙網頁、或最少針對公眾無障礙服務（可能包含了網站、電視、物理空間……等等）的法案。最好先看看所處地區的法律管轄權。如果不好好檢查內容是否無障礙，當身心障礙者投訴時，你可能會面臨法律問題。

聽起來很可怕，但只要在開發時，如上所述地把無障礙網頁議題當作高度優先就可以了。如果真的有疑問，請諮詢合格的專業律師。我們不是律師，所以不會提供更深入的意見。

無障礙網頁 API
---------

瀏覽器使用特殊的**accessibility API**（由各自的作業系統底層提供）對輔助技術（AT）提供有用的信息：輔助技術傾向使用語義訊息，因此這種訊息不包含樣式化資訊、或是 JavaScript。這種資訊建構成一個稱為 **accessibility tree** 的訊息樹（tree of information）。

不同的作業系統有不同的 accessibility API：

* Windows: MSAA/IAccessible, UIAExpress, IAccessible2
* Mac OS X: NSAccessibility
* Linux: AT-SPI
* Android: Accessibility framework
* iOS: UIAccessibility

如果由 web app 裡面，HTML 元素提供的原生語意訊息出問題了，你可以把它用做 WAI-ARIA 規範的補充：它會把語意訊息添加到 accessibility tree 以增進無障礙功能。你可以在 WAI-ARIA 基礎文章學習 WAI-ARIA。

結論
--

本文應當使你對無障礙網頁有著概括性的認知、明白其重要性、並在知道如何在工作流程中安排它。你也該對知道如何實做無障礙網頁的細節有興趣。我們將在下個章節開始闡明為什麼 HTML 是無障礙網頁的好基礎。

* Overview: Accessibility
* 次頁  (en-US)