diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
commit | 218934fa2ed1c702a6d3923d2aa2cc6b43c48684 (patch) | |
tree | a9ef8ac1e1b8fe4207b6d64d3841bfb8990b6fd0 /files/zh-tw/learn/server-side | |
parent | 074785cea106179cb3305637055ab0a009ca74f2 (diff) | |
download | translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.gz translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.bz2 translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.zip |
initial commit
Diffstat (limited to 'files/zh-tw/learn/server-side')
46 files changed, 13171 insertions, 0 deletions
diff --git a/files/zh-tw/learn/server-side/django/admin_site/index.html b/files/zh-tw/learn/server-side/django/admin_site/index.html new file mode 100644 index 0000000000..2fce622972 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/admin_site/index.html @@ -0,0 +1,354 @@ +--- +title: 'Django Tutorial Part 4: Django admin site' +slug: Learn/Server-side/Django/Admin_site +translation_of: Learn/Server-side/Django/Admin_site +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</div> + +<p class="summary">現在,我們已經為本地圖書館網站 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 創建了模型,我們接下來使用 Django 管理網站,去添加 一些 “真實的“ 書本數據。首先,我們展示如何用管理網站註冊模型,然後展示如何登錄和創建一些數據。本文最後,我們介紹可以進一步改進管理網站的建議。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>先完成: <a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a>.</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td> + <p>了解 Django 管理站的優點與侷限,並使用它來為我們的模型新增一些資料。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>Django 管理應用程序可以使用您的模型,自動構建可用於創建,查看,更新和刪除記錄的網站區域。這可以在開發過程中,節省大量的時間,從而很容易測試您的模型,並了解您是否擁有正確的數據。根據網站的類型,管理應用程序也可用於管理生產中的數據。 Django 項目建議僅用於內部數據管理(即僅供管理員或組織內部人員使用),因為以模型為中心的方法,不一定是所有用戶最好的界面,並且暴露了大量不必要的關於模型的細節。</p> + +<p>創建基礎項目時,自動完成所有的配置文件,包含您的網站中的管理應用程序在內(有關所需實際依賴關係的信息,如有需要請看 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">Django docs here</a>)。其結果是,要將模型添加到管理應用程序,你必須做的,僅僅是註冊他們。在本文末尾,我們將簡要介紹,如何進一步配置管理區域,以更好地顯示我們的模型數據。</p> + +<p>註冊模型後,我們將展示,如何創建一個新的 “超級用戶”,登錄到該網站,並創建一些書籍,作者,書籍實例和書籍類別。這些將有助於測試我們將在下一個教程中,開始創建的視圖和模板。</p> + +<h2 id="註冊模型Registering_models">註冊模型(Registering models )</h2> + +<p>首先,我們從 catalog app 中打開 <strong>admin.py</strong> (<strong>/locallibrary/catalog/admin.py</strong>),目前它長的像下面區塊,注意它已經幫你導入 <code>django.contrib.admin</code>:</p> + +<pre class="brush: python">from django.contrib import admin + +# Register your models here.</pre> + +<p>將下方的程式碼複製貼在 <strong>admin.py</strong> 文件下方以註冊所有模型,這段程式碼簡單來說就是先將模型導入,再呼叫 <code>admin.site.register</code> 函式來註冊每個模型。</p> + +<pre class="brush: python">from .models import Author, Genre, Book, BookInstance + +admin.site.register(Book) +admin.site.register(Author) +admin.site.register(Genre) +admin.site.register(BookInstance)</pre> + +<div class="note"><strong>注意</strong>:如果你在上一章節最後有接受挑戰並建立一個書本的「語言模型」 (<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Models">查看模型教學文章</a>),你必需也要導入並註冊該模型!</div> + +<p>這是<strong>註冊模型</strong>最簡單的方式。</p> + +<p>而管理站則是高度用戶化的,我們會在接下來繼續說明其它註冊你的模型的方式。</p> + +<h2 id="創建超級用戶Creating_a_superuser">創建超級用戶(Creating a superuser)</h2> + +<p>為了能夠登入管理站,我們需要一個有啟用員工狀態(<em>Staff</em> status)的使用者帳號,另外為了要能檢視與產生資料,我們也需要讓這個使用者帳號擁有管理所有物件的權限,因此,你可以透過 <strong>manage.py</strong> 來創建一個擁有所有網站存取權限的超級用戶(superuser)。</p> + +<p>在與 <strong>manage.py</strong> 同一個資料夾中執行下方指令,建立一個超級用戶,你會被提示要輸入「使用者名稱」、「使用者 e-mail」和「強度夠高的密碼」。</p> + +<pre class="brush: bash">python3 manage.py createsuperuser</pre> + +<p>當完成指令輸入後,一個新的超級用戶就會被加進資料庫中,再來只要重新啟動開發用 server ,你便可以進行登入測試:</p> + +<pre class="brush: bash">python3 manage.py runserver +</pre> + +<h2 id="登入並開始使用網站">登入並開始使用網站</h2> + +<p>要登入網站,必須先連上 <em>/admin</em> URL (e.g. <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin</a>) 並且輸入你的超級用戶的使用者名稱與密碼(你會被重新導向登入頁面,輸入你的帳密後會再回到 <em>/admin</em> URL)。</p> + +<p>網站中的這部分羅列了所有以我們安裝的 app 分組的模型,你可以點擊模型名稱進入陳列所有與其相關連資料的頁面,而你可以進一步編輯它們,或者你也可以直接點擊模型名稱旁邊的 <strong>Add</strong> 連結來開始創建該類型的資料。</p> + +<p><img alt="Admin Site - Home page" src="https://mdn.mozillademos.org/files/13975/admin_home.png" style="display: block; height: 634px; margin: 0px auto; width: 998px;"></p> + +<p>點擊 Books 右邊的 <strong>Add</strong> 連結來新增一本新書(會產生如下方的對話方塊),可以去觀察每個字段(field)、小部件、提示文字(如果有的話)是如何對應到你的模型的。</p> + +<p>在字段中輸入值,你可以透過各個字段旁邊的 <strong>+</strong> 按鈕來新增「作者」或「書籍類別」(或者從列表中選擇你已經新增的值),當你完成後可以點選 <strong>SAVE</strong>, <strong>Save and add another</strong>, 或 <strong>Save and continue editing</strong> 來儲存該筆資料。</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<div class="note"> +<p><strong>注意:在這邊我們希望你花點時間在你的 app 中新增一些書本、作者和書及類型(例如:奇幻等)。請確保每位作者與每種書籍類型都分別關聯了一本以上的書(這在文章稍後的實作的時候,會讓你的列表與細節視圖更加豐富有趣)</strong>。</p> +</div> + +<p>當你新增完書本後,點擊上方書籤的 <strong>Home</strong> 連結回到主要管理頁面,接著點擊 <strong>Books</strong> 連結來展示目前的書本清單(你也可以點及其他連結看看其他模型的列表),現在你已經加了幾本書,畫面應該會與下方截圖類似,你可以看到下方陳列了每本書的標題,這是我們在上一篇文章所提到的 Book 模型中的 <code>__str__()</code> 方法所回傳的值。</p> + +<p><img alt="Admin Site - List of book objects" src="https://mdn.mozillademos.org/files/13935/admin_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 407px; margin: 0px auto; width: 1000px;"></p> + +<p>在列表中,如果要刪掉你不想要的書,只需要先勾選欲刪除書本的勾選方框,從動作下拉選單選擇刪除動作(delete action),接著點選 <strong>GO</strong> 按鈕即可,另外你也可以點選 <strong>ADD BOOK</strong> 按鈕來新增一本書。</p> + +<p>你可以點擊書名來編輯它,下方顯示的書本編輯頁面幾乎與 <strong>Add</strong> 頁面相同,主要差異在於頁面的標題(Change book)以及增加了 <strong>Delete</strong>, <strong>HISTORY</strong> 和 <strong>VIEW ON SITE </strong>按鈕(會有這個按鈕出現是因為我們之前在模型中有定義了 <code>get_absolute_url()</code> 的方法)</p> + +<p><img alt="Admin Site - Book Edit" src="https://mdn.mozillademos.org/files/13977/admin_book_modify.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<p>現在透過頁面上方的索引連結回到 <strong>Home</strong> 頁面,然後看看 <strong>Author</strong> 和 <strong>Genre</strong> 列表,你在新增書本的時候應該已經新增了一些資料,不過你還可以再新增更多。</p> + +<p>你還沒有任何書本實例(Book Instances),因為這不會在建立書本時就產生(但你可以在新增 <code>BookInstance</code> 資料時新增 <code>Book</code> ,這是 <code>ForeignKey</code> 字段的性質)。現在回到 Home 頁面然後點擊 Book instances 的 <strong>Add</strong> 按鈕,畫面會呈現如下圖的頁面,注意第一列有個很長、全域唯一的 id 編碼,它可以用來區分每本書在圖書館裡的每個副本。</p> + +<p><img alt="Admin Site - BookInstance Add" src="https://mdn.mozillademos.org/files/13981/admin_bookinstance_add.png" style="border-style: solid; border-width: 1px; display: block; height: 514px; margin: 0px auto; width: 863px;"></p> + +<p>幫你的每本書都新增幾筆不同的資料,有些資料的狀態(Status)請設成 <em>Available ,有些則設成 On loan,如果狀態為 </em><strong>not</strong> <em>Available,那記得需要設定到期日(Due back</em> date<em>)。</em></p> + +<p>就是這樣!你現在已經學會了如何建立與使用管理站(administration site),你也為你的 <code>Book</code>, <code>BookInstance</code>, <code>Genre</code>, 和 <code>Author</code> 模型建立了幾筆資料,再來當我們建立好視圖(Views)以及模板(Templates)後,就會開始來使用它們。</p> + +<h2 id="進階組態Advanced_configuration">進階組態(Advanced configuration)</h2> + +<p>Django 在「透過註冊模型的資訊建立管理站」這方面做得非常好:</p> + +<ul> + <li>每個模型都有各自的資料列表,每筆資料都藉由模型的 <code>__str__()</code> 方法來做分辨,而且會連結到更詳細的視圖/表格以便後續編輯,而且在預設情況下,這個視圖(View)的上方有一個「動作清單(action menu)」,你可以使用裡面的 delete 功能來執行資料的刪除作業。</li> + <li>用於編輯和新增紀錄的模型詳細紀錄表單包含了模型中的所有字段,並依照宣告順序垂直排列。</li> +</ul> + +<p>你可以進一步訂製介面讓它更好用,以下是你可以進一步做的:</p> + +<ul> + <li>列表視圖(List views): + <ul> + <li>為每一筆紀錄增加額外的字段/資訊陳列。</li> + <li>為這些紀錄列表增加篩選器(例如:使用日期、使用狀態進行過濾)</li> + <li>為動作選單(action menu)添加額外的動作,並選擇是否要讓此選單在表格中呈現。</li> + </ul> + </li> + <li>細節視圖(Detail views): + <ul> + <li>選擇那些字段要隨著「順序、分組、可否編輯、是否被模組使用、取向」而陳列(或排除)。</li> + <li>添加相關的字段來允許內聯編輯(inline editing)(例如:添加一個功能讓你可以在新增一個作者的時候也順便能夠新增或編輯他的書本記錄)。</li> + </ul> + </li> +</ul> + +<p>這部分我們將要來看幾個有助於改善 <em>LocalLibrary 介面的小變化,包含了添加更多資訊到 </em><code>Book</code> 和 <code>Author</code> 模型列表,以及改善編輯視圖的排版。我們不會改變 <code>Language</code> 和 <code>Genre</code> 的模型外貌因為他們都各只有1個字段,這樣做沒好處!</p> + +<p>你可以在 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">The Django Admin site</a> (Django Docs) 找到關於管理站訂製選擇的完整參考。</p> + +<h3 id="註冊一個_模型管理_類別_ModelAdmin_class">註冊一個 模型管理 類別 (ModelAdmin class)</h3> + +<p>為了要改變模型在管理站的陳列方式,你需要定義一個模型管理(<a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects">ModelAdmin</a>)類別 (他是用來描述排版的),並且將它與其他模型一起註冊。</p> + +<p>我們現在先從 <code>Author</code> 模型開始。打開 catalog app 中的 <strong>admin.py</strong> 檔案(<strong>/locallibrary/catalog/admin.py</strong>),並將先前註冊 <code>Author</code> 模型的程式碼註解(在程式碼前面加一個 # 前綴):</p> + +<pre class="brush: js"># admin.site.register(Author)</pre> + +<p>現在加上一個新的 <code>AuthorAdmin</code> 類別與註冊函式,如下方所示:</p> + +<pre class="brush: python"># Define the admin class +class AuthorAdmin(admin.ModelAdmin): + pass + +# Register the admin class with the associated model +admin.site.register(Author, AuthorAdmin) +</pre> + +<p>現在我們要為 <code>Book</code> 以及 <code>BookInstance</code> 模型添加 <code>ModelAdmin</code> 類別,我們一樣要先把原本的註冊程式碼註解:</p> + +<pre class="brush: js">#admin.site.register(Book) +#admin.site.register(BookInstance)</pre> + +<p>現在我們要創造並註冊新的模型;為了達到示範的目的,我們會使用 <code>@register</code> 裝飾器替代先前做法來註冊模型(這跟 <code>admin.site.register()</code> 的語法做的事情完全一樣):</p> + +<pre class="brush: 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</pre> + +<p>目前為止我們的管理類別都是空的(可以看到 "<code>pass"</code>),所以我們的管理行為都不會改變!現在我們可以來進一步定義我們的「特定模型的管理行為」。</p> + +<h3 id="配置列表視圖Configure_list_views">配置列表視圖(Configure list views)</h3> + +<p>我們的 <em>LocalLibrary 目前條列出所有作者,而他們都是使用以模型的 </em><code>__str__()</code><em> 方法產生的物件名稱。如過你只有少數幾個作者,那倒還好,但如果作者很多,你最後可能會有非常多副本。因此為了區別他們,或者你只是想呈現更多作者的有趣訊息,你可以使用「列表展示」(</em><a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display">list_display</a><em>)來位視圖添加額外的字段。</em></p> + +<p>將你的 <code>AuthorAdmin</code> 類別以下方程式碼取代。下方程式碼可以看出來,列表中被展示出來的字段名稱會被以需要的排序宣告為元組(tuple)形式。</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death')</pre> + +<p>現在把網站導向作者列表,上方所設定的字段應該會被陳列出來,如下:</p> + +<p><img alt="Admin Site - Improved Author List" src="https://mdn.mozillademos.org/files/14023/admin_improved_author_list.png" style="border-style: solid; border-width: 1px; display: block; height: 302px; margin: 0px auto; width: 941px;"></p> + +<p>至於我們的 <code>Book</code> 模型,我們將額外添加 <code>author</code> 和 <code>genre</code> 兩樣。<code>author</code> 是一個<code>ForeignKey</code> 外鍵字段(一對一)關係,因此他將會透過關聯紀錄的 <code>__str__()</code> 值來表示。</p> + +<p>將 <code>BookAdmin</code> 類別以下方區段程式碼取代:</p> + +<pre class="brush: python">class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre')</pre> + +<p>很不幸地,我們無法直接在 <code>list_display</code> 中指定「書籍類別」(<font face="Consolas, Liberation Mono, Courier, monospace">genre </font>field)字段,因為它是一個 <code>ManyToManyField</code> (多對多字段),因為如果這樣做會造成很大的資料庫讀寫「成本」,所以 Django 會預防這樣的狀況發生,因此,取而代之,我們將定義一個 <code>display_genre</code> 函式以「字串」形式得到書籍類別。(下方有定義此函式)</p> + +<div class="note"> +<p><strong>Note</strong>: Getting the <code>genre</code> 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 <em>Delete </em>link next to every item in the list.</p> +</div> + +<p>將以下程式碼添加到<code>Book</code>模型(<strong>models.py</strong>)。 這會從<code>genre</code>記錄的的頭三個值(如果有的話)創建一個字符串, 和創建一個在管理者網站中出現的<code>short_description</code>標題。</p> + +<pre class="brush: 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' +</pre> + +<p>保存模型並更新管理員後,打開您的網站並轉到“Books”列表頁面; 您應該會看到類似以下的書籍清單:</p> + +<p><img alt="Admin Site - Improved Book List" src="https://mdn.mozillademos.org/files/14025/admin_improved_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 337px; margin: 0px auto; width: 947px;"></p> + +<p><code>Genre</code> 模型(如果定義了語言模型,則還有 <code>Language</code> 模型)都有一個欄位,因此沒有必要為它們創建其他模型以顯示欄位。</p> + +<div class="note"> +<p>注意: 更新 <code>BookInstance</code> 模型列表用來顯示狀態和預期的返回日期是有價值的。 我們在本文結尾處添加了一個挑戰!</p> +</div> + +<h3 id="加入列表過濾器_List_Filter">加入列表過濾器 (List Filter)</h3> + +<p>當你的列表有很多個記錄時, 加入列表過濾器可以幫助你過濾想顯示的記錄。加入<code>list_filter</code>這個屬性就可以。請用以下的程式碼來取代原本的 <code style="font-style: normal; font-weight: normal;">BookInstanceAdmin</code> 類別</p> + +<pre class="brush: python">class BookInstanceAdmin(admin.ModelAdmin): +<strong> list_filter = ('status', 'due_back')</strong> +</pre> + +<p>現在的列表視圖右邊會多了一個過濾器。你可以選擇 dates 和 status 來做過濾:</p> + +<p><img alt="Admin Site - BookInstance List Filters" src="https://mdn.mozillademos.org/files/14037/admin_improved_bookinstance_list_filters.png" style="height: 528px; width: 960px;"></p> + +<h3 id="組織詳細視圖佈局">組織詳細視圖佈局</h3> + +<p>默認情況下,局部視圖按照模型中聲明的順序垂直排列所有字段。 您可以更改聲明的順序,顯示(或排除)哪些字段,使用分段來組織資訊,水平顯示還是垂直顯示字段,甚至管理表單中使用哪些編輯小部件。</p> + +<div class="note"> +<p>注意: <em>LocalLibrary</em> 模型相對簡單,因此我們無須更改佈局。 但我們仍然會進行一些更改,向您展示如何進行。</p> +</div> + +<h4 id="控制那些欄位顯示並佈置">控制那些欄位顯示並佈置</h4> + +<p>更新你的 <code>AuthorAdmin</code> 類別用來新增 <code>fields</code> 這行,如同下列所示 (粗體):</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +<strong> fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')]</strong> +</pre> + +<p><code>fields</code> 屬性僅按順序列出了要在表單上顯示的那些欄位。 默認情況下,字段是垂直顯示的,但是如果您進一步將它們分組到一個元組中,它們將水平顯示(如上面的“日期”字段中所示)。</p> + +<p>在您的網站上,轉到作者詳細信息視圖-現在應如下所示:</p> + +<p><img alt="Admin Site - Improved Author Detail" src="https://mdn.mozillademos.org/files/14027/admin_improved_author_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 282px; margin: 0px auto; width: 928px;"></p> + +<div class="note"> +<p><strong>注意</strong>: 您還可以使用 <code>exclude</code> 屬性來聲明要從表單中排除的屬性列表(將顯示模型中的所有其他屬性)。</p> +</div> + +<h4 id="Sectioning_the_detail_view">Sectioning the detail view</h4> + +<p>You can add "sections" to group related model information within the detail form, using the <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">fieldsets</a> attribute.</p> + +<p>In the <code>BookInstance</code> model we have information related to what the book is (i.e. <code>name</code>, <code>imprint</code>, and <code>id</code>) and when it will be available (<code>status</code>, <code>due_back</code>). We can add these in different sections by adding the text in bold to our <code>BookInstanceAdmin</code> class. </p> + +<pre class="brush: python">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_filter = ('status', 'due_back') + +<strong> fieldsets = ( + (None, { + 'fields': ('book', 'imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back') + }), + )</strong></pre> + +<p>Each section has its own title (or <code>None</code>, 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.</p> + +<p>Now navigate to a book instance view in your website; the form should appear as shown below:</p> + +<p><img alt="Admin Site - Improved BookInstance Detail with sections" src="https://mdn.mozillademos.org/files/14029/admin_improved_bookinstance_detail_sections.png" style="border-style: solid; border-width: 1px; display: block; height: 580px; margin: 0px auto; width: 947px;"></p> + +<h3 id="Inline_editing_of_associated_records">Inline editing of associated records</h3> + +<p>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.</p> + +<p>You can do this by declaring <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">inlines</a>, of type <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> (horizonal layout) or <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.StackedInline">StackedInline</a> (vertical layout, just like the default model layout). You can add the <code>BookInstance</code> information inline to our <code>Book</code> detail by adding the lines below in bold near your <code>BookAdmin</code>:</p> + +<pre class="brush: python"><strong>class BooksInstanceInline(admin.TabularInline): + model = BookInstance</strong> + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +<strong> inlines = [BooksInstanceInline]</strong> +</pre> + +<p>Now navigate to a view for a <code>Book</code> in your website — at the bottom you should now see the book instances relating to this book (immediately below the book's genre fields):</p> + +<p><img alt="Admin Site - Book with Inlines" src="https://mdn.mozillademos.org/files/14033/admin_improved_book_detail_inlines.png" style="border-style: solid; border-width: 1px; display: block; height: 889px; margin: 0px auto; width: 937px;"></p> + +<p>In this case all we've done is declare our tabular inline class, which just adds all fields from the <em>inlined</em> 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 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> for more information). </p> + +<div class="note"> +<p><strong>Note</strong>: 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 <strong>Add another Book instance</strong> link, or to be able to just list the <code>BookInstance</code>s as non-readable links from here. The first option can be done by setting the <code>extra</code> attribute to 0 in <code>BooksInstanceInline</code> model, try it by yourself.</p> +</div> + +<h2 id="自我挑戰">自我挑戰</h2> + +<p>在本節中我們學到了很多東西,所以現在該您嘗試一些事情了。</p> + +<ol> + <li>對於<code>BookInstance</code>列表視圖(list view),添加代碼以顯示<code>books</code>,<code>status</code>,<code>due back date</code> 和 <code>id</code>(而不是默認的__str __()文本)。</li> + <li>使用與<code>Book/BookInstance</code>相同的方法將<code>Book</code>項目的內聯列表添加到<code>Author</code> 的詳細視圖(detail view)中。</li> +</ol> + +<ul> +</ul> + +<h2 id="小結">小結</h2> + +<p>就是這樣! 您現在已經了解瞭如何以最簡單和改進的形式設置管理者網站,如何創建超級用戶,以及如何瀏覽管理者網站,查看,刪除和更新記錄。 在此過程中,您已經創建了許多Books,BookInstances,Genres和Authors,一旦我們創建了自己的view和templates,便可以列出和顯示這些記錄。</p> + +<h2 id="延伸閱讀">延伸閱讀</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial02/#introducing-the-django-admin">Writing your first Django app, part 2: Introducing the Django Admin</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/">The Django Admin site</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/authentication/index.html b/files/zh-tw/learn/server-side/django/authentication/index.html new file mode 100644 index 0000000000..ec15ddeffd --- /dev/null +++ b/files/zh-tw/learn/server-side/django/authentication/index.html @@ -0,0 +1,698 @@ +--- +title: 'Django Tutorial Part 8: User authentication and permissions' +slug: Learn/Server-side/Django/Authentication +translation_of: Learn/Server-side/Django/Authentication +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</div> + +<p class="summary">在本教程中,我們將會展示如何允許用戶使用自己的帳戶登入到您的網站,以及如何根據用戶是否已登入和權限的不同來控制他們可以執行和查看的內容。作為展示的一部分,我們會擴展 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,添加登入頁面和登出頁面,以及用來查看已借閱的圖書的頁面 - 分為用戶與員工兩種不同頁面。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>完成至 <a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 線上教學 7: 會話(Sessions)框架</a>為止的所有主題。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>了解如何設定與運用使用者驗證與權限機制。</td> + </tr> + </tbody> +</table> + +<h2 id="大綱">大綱</h2> + +<p>Django提供認證和授權(“ permission”)系統,該系統建立在<a href="/zh-TW/docs/Learn/Server-side/Django/Sessions">上一教程</a>中討論的會話框架的基礎上。透過它可以驗證用戶憑證並定義個別用戶能夠執行的操作。 該框架包括用於<code>Users</code> 和<code>Groups</code> 的內置模型(一般常用來一次性套用權限於一群用戶上的方式),用於指定用戶是否可以執行任務的權限/旗標,用於登入用戶的表單和視圖,以及 查看用於限制內容的工具。</p> + +<div class="note"> +<p><strong>注意</strong>: 從Django角度而言,身份驗證系統需要做到非常通用,因此不提供其他網頁身份驗證系統中提供的某些功能。 需要解決一些常見問題的話可以透過第三方軟件包。 例如,限制登錄嘗試和透過第三方進行身份驗證(例如OAuth)。</p> +</div> + +<p>在本教程中,我們將會展示如何在<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>網站中啟用用戶身份驗證,並建立自己的登入和登出頁面,為模型添加權限以及控制對頁面的訪問。 我們將根據身份驗證/權限顯示為用戶或是圖書館員設計的已借出書籍列表。</p> + +<p>身份驗證系統非常有彈性,您可以根據需要從頭開始構建URL,表單,視圖和模板,只透過提供的API來登入用戶。 但是,在本文中,我們將為登入與登出頁面使用Django的“ stock”身份驗證視圖和表單。 我們仍然需要建立一些模板,但這很簡單。</p> + +<p>我們還將向您展示如何建立權限,並在視圖和模板中檢查登入狀態和權限。</p> + +<h2 id="Enabling_authentication">Enabling authentication</h2> + +<p>當我們<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/skeleton_website">創建框架網站</a>時(在教程2中),身份驗證已自動啟用,因此您此時無需執行任何其他操作。</p> + +<div class="note"> +<p><strong>注意</strong>: 當我們使用<code>django-admin startproject</code>命令創建應用程序時,所有必要的配置都為我們完成了。 用戶和模型權限的數據庫表是在我們首次調用<code>python manage.py migrate</code>時創建的。</p> +</div> + +<p>該配置是在項目文件(<strong>locallibrary/locallibrary/settings.py</strong>)的<code>INSTALLED_APPS</code> 和<code>MIDDLEWARE</code> 部分中設置的,如下所示:</p> + +<pre class="brush: python notranslate">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.auth', </strong>#Core authentication framework and its default models. +<strong> 'django.contrib.contenttypes', #</strong>Django content type system (allows permissions to be associated with models). + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> #Manages sessions across requests + ... +<strong> 'django.contrib.auth.middleware.AuthenticationMiddleware',</strong> #Associates users with requests using sessions. + .... +</pre> + +<h2 id="Creating_users_and_groups">Creating users and groups</h2> + +<p>當我們在教程4中查看<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Admin_site">Django管理站</a>點時,您已經創建了第一個用戶(這是一個超級用戶,使用命令p<code>python manage.py createsuperuser</code>創建)。 我們的超級用戶已經通過身份驗證,並且具有所有權限,因此我們需要創建一個測試用戶來代表普通站點用戶。 我們將使用管理站點來創建本地圖書館組和網站登錄名,因為這是最快的方法之一。</p> + +<div class="note"> +<p><strong>注意</strong>: 您還可以通過編程方式創建用戶,如下所示。 例如,如果要開發一個界面以允許用戶創建自己的登錄名,則必須這樣做(您不應授予用戶訪問管理站點的權限)。</p> + +<pre class="brush: python notranslate">from django.contrib.auth.models import User + +# Create user and save to the database +user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword') + +# Update fields and then save again +user.first_name = 'John' +user.last_name = 'Citizen' +user.save() +</pre> +</div> + +<p>在下面,我們將首先創建一個組,然後創建一個用戶。 即使我們還沒有添加庫成員的任何權限,但是如果以後需要添加,將它們一次添加到組中要比分別添加到每個成員要容易得多。</p> + +<p>啟動開發服務器,然後在本地Web瀏覽器(<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>)中導航到管理站點。 使用您的超級用戶帳戶的憑據登錄到該站點。 管理站點的頂層顯示所有模型,按“ django應用程序”排序。 在“<strong>Authentication and Authorisation</strong>”部分,您可以單擊<strong>Users</strong> 或<strong>Groups</strong>鏈接以查看其現有記錄。</p> + +<p><img alt="Admin site - add groups or users" src="https://mdn.mozillademos.org/files/14091/admin_authentication_add.png" style="border-style: solid; border-width: 1px; display: block; height: 364px; margin: 0px auto; width: 661px;"></p> + +<p>首先,讓我們為圖書館成員創建一個新組。</p> + +<ol> + <li>單擊<strong>Add</strong>按鈕(在組旁邊)以創建一個新組; 輸入該組的名稱“Library Members”。<br> + <img alt="Admin site - add group" src="https://mdn.mozillademos.org/files/14093/admin_authentication_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 561px; margin: 0px auto; width: 800px;"></li> + <li>我們不需要該組的任何權限,因此只需按<strong>SAVE</strong> (您將被帶到組列表)。</li> +</ol> + +<p>現在讓我們創建一個用戶:</p> + +<ol> + <li>導航回到管理站點的主頁</li> + <li>單擊“用戶”旁邊的“添加”按鈕以打開“添加用戶”對話框。<br> + <img alt="Admin site - add user pt1" src="https://mdn.mozillademos.org/files/14095/admin_authentication_add_user_prt1.png" style="border-style: solid; border-width: 1px; display: block; height: 409px; margin: 0px auto; width: 800px;"></li> + <li>輸入適合您的測試用戶的用戶名和密碼/密碼確認</li> + <li>按<strong>SAVE</strong>創建用戶。<br> + 管理站點將創建新用戶,並立即將您帶到“更改用戶”視窗,您可以在其中更改用戶名並為用戶模型的可選字段添加信息。 這些字段包括名字,姓氏,電子郵件地址,用戶狀態和權限(僅應設置“活動”標誌)。 在更下方的位置,您可以指定用戶的組和權限,並查看與該用戶相關的重要日期(例如,他們的加入日期和上次登錄日期)。<br> + <img alt="Admin site - add user pt2" src="https://mdn.mozillademos.org/files/14097/admin_authentication_add_user_prt2.png" style="border-style: solid; border-width: 1px; display: block; height: 635px; margin: 0px auto; width: 800px;"></li> + <li>在“組”部分中,從“可用組”列表中選擇“<strong>Library Member</strong>”組,然後按框之間的右箭頭將其移至“選擇的組”框中。<img alt="Admin site - add user to group" src="https://mdn.mozillademos.org/files/14099/admin_authentication_user_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 414px; margin: 0px auto; width: 933px;"></li> + <li>我們在這裡不需要執行任何其他操作,因此只需再次選擇<strong>SAVE</strong> 即可進入用戶列表。</li> +</ol> + +<p>就是這樣而已! 現在,您將擁有一個“普通庫成員”帳戶,您將可以使用該帳戶進行測試(一旦我們實現了頁面以使其能夠登錄)。</p> + +<div class="note"> +<p><strong>注意</strong>:您應該嘗試創建另一個庫成員用戶。 另外,為圖書館員創建一個組,並為其添加用戶!</p> +</div> + +<h2 id="Setting_up_your_authentication_views">Setting up your authentication views</h2> + +<p>Django提供了創建身份驗證頁面所需的幾乎所有內容,以處理“開箱即用”的登錄,註銷和密碼管理。 這包括URL映射器,視圖和表單,但不包括模板-我們必須創建自己的模板!</p> + +<p>在本節中,我們顯示如何將默認系統集成到LocalLibrary網站中並創建模板。 我們將它們放在主項目URL中。</p> + +<div class="note"> +<p><strong>注意</strong>: 您不必使用任何代碼,但是您可能想要使用它,因為它使事情變得容易得多。 如果您更改用戶模型(一個高級主題!),幾乎可以肯定需要更改表單處理代碼,但是即使如此,您仍然可以使用庫存視圖功能。</p> +</div> + +<div class="note"> +<p><strong>注意: </strong>在這種情況下,我們可以合理地將身份驗證頁面(包括URL和模板)放入目錄應用程序中。 但是,如果我們有多個應用程序,最好將這種共享的登錄行為分開,並使其在整個站點中都可用,這就是我們在此處顯示的內容!</p> +</div> + +<h3 id="Project_URLs">Project URLs</h3> + +<p>將以下內容添加到項目urls.py文件(<strong>locallibrary/locallibrary/urls.py</strong>)文件的底部:</p> + +<pre class="brush: python notranslate">#Add Django site authentication urls (for login, logout, password management) +urlpatterns += [ + path('accounts/', include('django.contrib.auth.urls')), +] +</pre> + +<p>導航到<a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a> URL(注意尾隨斜杠!),然後Django將顯示一個錯誤,指出找不到此URL,並列出了它嘗試的所有URL。 從中您可以看到將起作用的URL,例如:</p> + +<div class="note"> +<p><strong>注意: </strong>使用上述方法會在方括號中添加以下網址,這些網址可用於反轉網址映射。 您無需執行其他任何操作-上面的url映射會自動映射以下提到的URL。</p> +</div> + +<div class="note"> +<pre class="brush: python notranslate">accounts/ login/ [name='login'] +accounts/ logout/ [name='logout'] +accounts/ password_change/ [name='password_change'] +accounts/ password_change/done/ [name='password_change_done'] +accounts/ password_reset/ [name='password_reset'] +accounts/ password_reset/done/ [name='password_reset_done'] +accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm'] +accounts/ reset/done/ [name='password_reset_complete']</pre> +</div> + +<p>現在嘗試導航到登錄URL(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>)。 這將再次失敗,但是會顯示一條錯誤消息,告訴您我們在模板搜索路徑上缺少必需的模板(<strong>registration/login.html</strong>)。 您會在頂部黃色部分看到以下幾行:</p> + +<pre class="brush: python notranslate">Exception Type: TemplateDoesNotExist +Exception Value: <strong>registration/login.html</strong></pre> + +<p>下一步是在搜索路徑上創建註冊目錄,然後添加<strong>login.html</strong>文件。</p> + +<h3 id="Template_directory">Template directory</h3> + +<p>我們剛剛添加的url(和隱式視圖)期望在模板搜索路徑中某個目錄<strong>/registration/</strong> 中找到它們的關聯模板。</p> + +<p>對於這個網站,我們將HTML頁面放在<strong>templates/registration/</strong>目錄中。 此目錄應位於您的項目根目錄中,即與<strong>catalog</strong> 和<strong>locallibrary</strong> 文件夾相同的目錄中)。 請立即創建這些文件夾。</p> + +<div class="note"> +<p><strong>Note:</strong> Your folder structure should now look like the below:<br> + locallibrary (django project folder)<br> + |_catalog<br> + |_locallibrary<br> + |_templates <strong>(new)</strong><br> + |_registration</p> +</div> + +<p>為了使這些目錄對模板加載器可見(即將該目錄放置在模板搜索路徑中),請打開項目設置(<strong>/locallibrary/locallibrary/settings.py</strong>),並更新<code>TEMPLATES</code> 部分的<code>DIRS</code>行,如圖所示。</p> + +<pre class="brush: python notranslate">TEMPLATES = [ + { + ... +<strong> 'DIRS': ['./templates',],</strong> + 'APP_DIRS': True, + ... +</pre> + +<h3 id="Login_template">Login template</h3> + +<div class="warning"> +<p><strong>重要信息</strong>:本文提供的身份驗證模板是Django演示登錄模板的非常基本/稍作修改的版本。 您可能需要自定義它們以供自己使用!</p> +</div> + +<p>創建一個名為/<strong>locallibrary/templates/registration/login.html</strong>的新HTML文件。 為其提供以下內容:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + +{% if form.errors %} + <p>Your username and password didn't match. Please try again.</p> +{% endif %} + +{% if next %} + {% if user.is_authenticated %} + <p>Your account doesn't have access to this page. To proceed, + please login with an account that has access.</p> + {% else %} + <p>Please login to see this page.</p> + {% endif %} +{% endif %} + +<form method="post" action="{% url 'login' %}"> +{% csrf_token %} + +<div> + <td>\{{ form.username.label_tag }}</td> + <td>\{{ form.username }}</td> +</div> +<div> + <td>\{{ form.password.label_tag }}</td> + <td>\{{ form.password }}</td> +</div> + +<div> + <input type="submit" value="login" /> + <input type="hidden" name="next" value="\{{ next }}" /> +</div> +</form> + +{# Assumes you setup the password_reset view in your URLconf #} +<p><a href="{% url 'password_reset' %}">Lost password?</a></p> + +{% endblock %}</pre> + +<p>該模板與我們之前看到的模板有一些相似之處-它擴展了我們的基本模板並覆蓋了內容塊。 其餘代碼是相當標準的表單處理代碼,我們將在以後的教程中進行討論。 現在您只需要知道的是,這將顯示一個表格,您可以在其中輸入用戶名和密碼,並且如果輸入無效的值,則在頁面刷新時會提示您輸入正確的值。</p> + +<p>保存模板後,導航回到登錄頁面(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>),您應該看到類似以下內容:</p> + +<p><img alt="Library login page v1" src="https://mdn.mozillademos.org/files/14101/library_login.png" style="border-style: solid; border-width: 1px; display: block; height: 173px; margin: 0px auto; width: 441px;"></p> + +<p>如果嘗試登錄將成功,並且您將被重定向到另一個頁面(默認情況下為<a href="http://127.0.0.1:8000/accounts/profile/">http://127.0.0.1:8000/accounts/profile/</a>)。 這裡的問題是,默認情況下,Django期望登錄後將您帶到個人資料頁面,情況可能與否。 由於您尚未定義此頁面,因此會出現另一個錯誤!</p> + +<p>打開項目設置(<strong>/locallibrary/locallibrary/settings.py</strong>) ,然後將下面的文本添加到底部。 現在,當您登錄時,默認情況下應將您重定向到網站主頁。</p> + +<pre class="brush: python notranslate"># Redirect to home URL after login (Default redirects to /accounts/profile/) +LOGIN_REDIRECT_URL = '/' +</pre> + +<h3 id="Logout_template">Logout template</h3> + +<p>如果您導航到登出URL (<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>) ,則會看到一些奇怪的行為-您的用戶將被確定地註銷,但是您將被帶到<strong>Admin</strong> 註銷頁面。 那不是您想要的,僅僅是因為該頁面上的登錄鏈接將您帶到<strong>Admin</strong> 登錄屏幕(並且僅對具有<code>is_staff</code> 權限的用戶可用)。</p> + +<p>創建並打開 /<strong>locallibrary/templates/registration/logged_out.html</strong>。 複製以下文本:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <p>Logged out!</p> + <a href="{% url 'login'%}">Click here to login again.</a> +{% endblock %}</pre> + +<p>這個模板非常簡單。 它僅顯示一條消息,通知您已註銷,並提供一個鏈接,您可以按此鏈接返回登錄屏幕。 如果再次進入註銷URL,您應該看到以下頁面:</p> + +<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p> + +<h3 id="Password_reset_templates">Password reset templates</h3> + +<p>默認的密碼重置系統使用電子郵件向用戶發送重置鏈接。 您需要創建表格以獲取用戶的電子郵件地址,發送電子郵件,允許他們輸入新密碼並在整個過程完成時註明。</p> + +<p>以下模板可以用作起點。</p> + +<h4 id="密碼重設表格">密碼重設表格</h4> + +<p>這是用於獲取用戶電子郵件地址(用於發送密碼重置電子郵件)的表格。 創建<strong>/locallibrary/templates/registration/password_reset_form.html</strong>,並為其提供以下內容:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <form action="" method="post"> + {% csrf_token %} + {% if form.email.errors %} + {{ form.email.errors }} + {% endif %} + <p>\{{ form.email }}</p> + <input type="submit" class="btn btn-default btn-lg" value="Reset password"> + </form> +{% endblock %} +</pre> + +<h4 id="密碼重置完成">密碼重置完成</h4> + +<p>收集您的電子郵件地址後,將顯示此表單。創建 <strong>/locallibrary/templates/registration/password_reset_done.html</strong>,並為其提供以下內容:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p> +{% endblock %} +</pre> + +<h4 id="密碼重置電子郵件">密碼重置電子郵件</h4> + +<p>該模板提供了HTML電子郵件的文本,其中包含我們將發送給用戶的重置鏈接。 創建<strong>/locallibrary/templates/registration/password_reset_email.html</strong>,並為其提供以下內容:</p> + +<pre class="brush: html notranslate">Someone asked for password reset for email \{{ email }}. Follow the link below: +\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +</pre> + +<h4 id="密碼重置確認">密碼重置確認</h4> + +<p>單擊密碼重置電子郵件中的鏈接後,即可在此頁面輸入新密碼。 創建 <strong>/locallibrary/templates/registration/password_reset_confirm.html</strong>,並為其提供以下內容:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + {% if validlink %} + <p>Please enter (and confirm) your new password.</p> + <form action="" method="post"> + <div style="display:none"> + <input type="hidden" value="\{{ csrf_token }}" name="csrfmiddlewaretoken"> + </div> + <table> + <tr> + <td>\{{ form.new_password1.errors }} + <label for="id_new_password1">New password:</label></td> + <td>\{{ form.new_password1 }}</td> + </tr> + <tr> + <td>\{{ form.new_password2.errors }} + <label for="id_new_password2">Confirm password:</label></td> + <td>\{{ form.new_password2 }}</td> + </tr> + <tr> + <td></td> + <td><input type="submit" value="Change my password" /></td> + </tr> + </table> + </form> + {% else %} + <h1>Password reset failed</h1> + <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> + {% endif %} +{% endblock %} +</pre> + +<h4 id="密碼重置完成_2">密碼重置完成</h4> + +<p>這是最後一個密碼重設模板,密碼重設成功後將顯示此模板以通知您。 創建<strong>/locallibrary/templates/registration/password_reset_complete.html</strong>,並為其提供以下內容:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>The password has been changed!</h1> + <p><a href="{% url 'login' %}">log in again?</a></p> +{% endblock %}</pre> + +<h3 id="Testing_the_new_authentication_pages">Testing the new authentication pages</h3> + +<p>現在您已經添加了URL配置並創建了所有這些模板,身份驗證頁面現在應該可以正常工作了!</p> + +<p>您可以通過嘗試使用以下URL登錄然後註銷超級用戶帳戶來測試新的身份驗證頁面:</p> + +<ul> + <li><a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a></li> + <li><a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a></li> +</ul> + +<p>您可以通過登錄頁面中的鏈接測試密碼重置功能。 <strong>請注意,Django只會將重置電子郵件發送到已經存儲在其數據庫中的地址(用戶)!</strong></p> + +<div class="note"> +<p><strong>筆記</strong>:密碼重設系統要求您的網站支持電子郵件,這不在本文的討論範圍之內,因此該部分尚無法使用。 要進行測試,請將以下行放在settings.py文件的末尾。 這將記錄發送到控制台的所有電子郵件(因此您可以從控制台複製密碼重置鏈接)。</p> + +<pre class="brush: python notranslate">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +</pre> + +<p>有關更多信息,請參閱發送電子郵件(<a href="https://docs.djangoproject.com/en/2.0/topics/email/">Sending email</a>Django文檔)。</p> +</div> + +<h2 id="針對經過身份驗證的用戶進行測試">針對經過身份驗證的用戶進行測試</h2> + +<p>本節介紹如何根據用戶是否登錄來有選擇地控制用戶看到的內容。</p> + +<h3 id="在模板中測試">在模板中測試</h3> + +<p>您可以使用 <code>\{{ user }}</code>模板變量在模板中獲取有關當前登錄用戶的信息(默認情況下,就像我們在框架中一樣設置項目時,該信息會添加到模板上下文中)。</p> + +<p>通常,您將首先針對 <code>\{{ user.is_authenticated }}</code>模板變量進行測試,以確定該用戶是否有資格查看特定內容。 為了演示這一點,接下來,我們將更新邊欄,以在用戶註銷時顯示“登錄”鏈接,在用戶登錄時顯示“註銷”鏈接。</p> + +<p>打開基礎模板。 (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) ,然後將以下文本複製到<code>sidebar</code> 塊中,緊接在<code>endblock</code> 模板標籤之前。</p> + +<pre class="brush: html notranslate"> <ul class="sidebar-nav"> + + ... + + <strong>{% if user.is_authenticated %}</strong> + <li>User: <strong>\{{ user.get_username }}</strong></li> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + <strong>{% else %}</strong> + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + <strong>{% endif %} </strong> + </ul></pre> + +<p>如您所見,我們使用 <code>if</code>-<code>else</code>-<code>endif</code> 模板標籤根據 <code>\{{ user.is_authenticated }}</code> \ {{user.is_authenticated}}是否為真來有條件地顯示文本。 如果用戶通過了身份驗證,那麼我們知道我們有一個有效的用戶,因此我們調用 <strong>\{{ user.get_username }} </strong>來顯示其名稱。</p> + +<p>我們使用<code>url</code> 模板標記和相應URL配置的名稱來創建登錄和註銷鏈接URL。 還要注意我們如何將<code>?next=\{{request.path}}</code>附加到URL的末尾。 這是在鏈接的URL的末尾添加一個URL參數,其中包含當前頁面的地址(URL)。 用戶成功登錄/註銷後,視圖將使用此``<code>next</code>''值將用戶重定向到他們首先單擊 login/logout 鏈接的頁面。</p> + +<div class="note"> +<p><strong>注意</strong>:試試看! 如果您在主頁上,然後單擊側欄中的“Login/Logout”,那麼在操作完成後,您應該回到同一頁面。</p> +</div> + +<h3 id="在視圖中測試">在視圖中測試</h3> + +<p>如果您使用的是基於函數的視圖,則限制訪問函數的最簡單方法是將<code>login_required</code> 裝飾器應用於視圖函數,如下所示。 如果用戶已登錄,則您的視圖代碼將正常執行。 如果用戶未登錄,它將重定向到項目設置(<code>settings.LOGIN_URL</code>)中定義的登錄URL,並將當前的絕對路徑作為<code>next</code> URL參數傳遞。 如果用戶成功登錄,則他們將返回此頁面,但這次已通過身份驗證。</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import login_required + +@login_required +def my_view(request): + ...</pre> + +<div class="note"> +<p><strong>注意:</strong> 您可以通過在<code>request.user.is_authenticated</code>上進行測試來手動執行相同的操作,但是裝飾器要方便得多!</p> +</div> + +<p>同樣,在基於類的視圖中限制對登錄用戶的訪問權限的最簡單方法是從 <code>LoginRequiredMixin</code>. 派生。 您需要首先在父類列表中,在主視圖類之前聲明此混合。</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class MyView(LoginRequiredMixin, View): + ...</pre> + +<p>它具有與 <code>login_required</code> 裝飾器完全相同的重定向行為。 如果用戶未通過身份驗證,也可以指定其他位置來重定向用戶 (<code>login_url</code>),並使用URL參數名稱代替“ next”來插入當前的絕對路徑(<code>redirect_field_name</code>).。</p> + +<pre class="brush: python notranslate">class MyView(LoginRequiredMixin, View): + login_url = '/login/' + redirect_field_name = 'redirect_to' +</pre> + +<p>有關更多詳細信息,請在此處查看<a href="https://docs.djangoproject.com/en/2.0/topics/auth/default/#limiting-access-to-logged-in-users">Django文檔</a>。</p> + +<h2 id="範例—列出當前用戶的書籍">範例—列出當前用戶的書籍</h2> + +<p>現在,我們知道瞭如何將頁面限制為特定用戶,讓我們創建當前用戶借閱的書籍的視圖。</p> + +<p>不幸的是,我們還沒有任何方式讓用戶借書! 因此,在創建圖書清單之前,我們將首先擴展<code>BookInstance</code> 模型以支持借用的概念,並使用Django Admin應用程序將大量圖書借給我們的測試用戶。</p> + +<h3 id="模型">模型</h3> + +<p>首先,我們將必須使用戶可以藉用<code>BookInstance</code> (我們已經具有<code>status</code> 和<code>due_back</code> ,但是在該模型和User之間還沒有任何關聯。我們將創建 一個使用<code>ForeignKey</code> (一對多)字段的方法,我們還需要一種簡單的機制來測試借出的書是否過期。<br> + <br> + 打開<strong>catalog/models.py</strong>,然後從 <code>django.contrib.auth.models</code>導入<code>User</code> 模型(將其添加到文件頂部的前一個導入行下面,因此<code>User</code> 可供使用它的後續代碼使用):</p> + +<pre class="brush: python notranslate">from django.contrib.auth.models import User +</pre> + +<p>Ne接下來,將<code>borrower</code> 字段添加到<code>BookInstance</code> 模型中:</p> + +<pre class="notranslate">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) +</pre> + +<p>當我們在這裡時,讓我們添加一個屬性,我們可以從模板中調用該屬性,以告知特定的圖書實例是否過期。 儘管我們可以在模板本身中進行計算,但是使用如下所示的<a href="https://docs.python.org/3/library/functions.html#property">屬性</a>會更加高效。</p> + +<p>將此添加到文件頂部附近:</p> + +<pre class="brush: python notranslate">from datetime import date</pre> + +<p class="brush: python">現在,在<code>BookInstance</code>類中添加以下屬性定義:</p> + +<pre class="brush: python notranslate">@property +def is_overdue(self): + if self.due_back and date.today() > self.due_back: + return True + return False</pre> + +<div class="note"> +<p><strong>Note</strong>: 在進行比較之前,我們首先要驗證<code>due_back</code>是否為空。 空的 <code>due_back</code>字段將導致Django拋出錯誤而不是顯示頁面:空值不可比。 這不是我們希望用戶體驗的東西!</p> +</div> + +<p>現在,我們已經更新了模型,我們需要在項目上進行新的遷移,然後應用這些遷移:</p> + +<pre class="brush: bash notranslate">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<h3 id="Admin">Admin</h3> + +<p>現在打開<strong>catalog/admin.py</strong>,然後將<code>list_display</code> 和<code>fieldsets</code> 中的<code>borrower</code> 字段添加到<code>BookInstanceAdmin</code> 類中,如下所示。 這將使該字段在“管理”部分中可見,以便我們可以在需要時將<code>User</code> 分配給<code>BookInstance</code> 。</p> + +<pre class="brush: python notranslate">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id') + list_filter = ('status', 'due_back') + + fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back'<strong>,'borrower'</strong>) + }), + )</pre> + +<h3 id="Loan_a_few_books">Loan a few books</h3> + +<p>現在可以將書借給特定用戶了,然後借出許多<code>BookInstance</code> 記錄。 將他們的<code>borrowed</code> 字段設置為測試用戶,<code>status</code> 為“借用”,並設置將來和將來的到期日。</p> + +<div class="note"> +<p><strong>注意</strong>:我們不會詳細說明該過程,因為您已經知道如何使用管理網站!</p> +</div> + +<h3 id="On_loan_view">On loan view</h3> + +<p>現在,我們將添加一個視圖,以獲取已借給當前用戶的所有書籍的列表。 我們將使用我們熟悉的相同的通用的基於類的列表視圖,但是這次我們還將導入並從<code>LoginRequiredMixin</code>派生,以便只有登錄的用戶才能調用此視圖。 我們還將選擇聲明<code>template_name</code>,而不使用默認值,因為我們最終可能會擁有一些不同的BookInstance記錄列表,並具有不同的視圖和模板。<br> + <br> + 將以下內容添加到<strong>catalog / views.py</strong>:</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): + """Generic class-based view listing books on loan to current user.""" + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>為了將查詢限制為僅針對當前用戶的<code>BookInstance</code> 對象,我們重新實現了 <code>get_queryset()</code>,如上所示。 請注意,“ o”是“借出”的存儲代碼,我們在<code>due_back</code> 日期之前訂購,以便最先顯示最早的項目。</p> + +<h3 id="URL_conf_for_on_loan_books">URL conf for on loan books</h3> + +<p>現在打開<strong>/catalog/urls.py</strong>並添加指向上面視圖的<code>path()</code>(您可以將下面的文本複製到文件末尾)。</p> + +<pre class="brush: python notranslate">urlpatterns += [ + path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), +]</pre> + +<h3 id="Template_for_on_loan_books">Template for on loan books</h3> + +<p>現在,我們需要為此頁面添加模板。 首先,創建模板文件 <strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong> 並為其提供以下內容:</p> + +<pre class="brush: python notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>Borrowed books</h1> + + {% if bookinstance_list %} + <ul> + + {% for bookinst in bookinstance_list %} + <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> + <a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }}) + </li> + {% endfor %} + </ul> + + {% else %} + <p>There are no books borrowed.</p> + {% endif %} +{% endblock %}</pre> + +<p>該模板與我們先前為<code>Book</code> 和<code>Author</code> 物件創建的模板非常相似。 這裡唯一的“新內容”是我們檢查在模型中添加的方法(<code>bookinst.is_overdue</code>),並使用它來更改過期項目的顏色。</p> + +<p>開發服務器運行時,現在應該可以在瀏覽器中的 <a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a> 上查看已登錄用戶的列表。 在您的用戶登錄和註銷後進行嘗試(在第二種情況下,應將您重定向到登錄頁面)。</p> + +<h3 id="Add_the_list_to_the_sidebar">Add the list to the sidebar</h3> + +<p>最後一步是將此新頁面的鏈接添加到側欄中。 我們將其放在同一部分中,在該部分中為登錄用戶顯示其他信息。</p> + +<p>打開基本模板 (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) 並將粗體顯示的行添加到側邊欄中,如圖所示。</p> + +<pre class="brush: python notranslate"> <ul class="sidebar-nav"> + {% if user.is_authenticated %} + <li>User: \{{ user.get_username }}</li> +<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + {% else %} + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + {% endif %} + </ul> +</pre> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>當任何用戶登錄後,他們將在邊欄中看到“<em>My Borrowed</em> ”,並且書的列表顯示如下(第一本書沒有截止日期,這是我們希望在以後的教程中解決的錯誤!) 。</p> + +<p><img alt="Library - borrowed books by user" src="https://mdn.mozillademos.org/files/14105/library_borrowed_by_user.png" style="border-style: solid; border-width: 1px; display: block; height: 215px; margin: 0px auto; width: 530px;"></p> + +<h2 id="Permissions">Permissions</h2> + +<p>權限與模型相關聯,並定義了具有權限的用戶可以在模型實例上執行的操作。 默認情況下,Django會自動為所有模型賦予添加,更改和刪除權限,從而允許具有權限的用戶通過管理站點執行關聯的操作。 您可以定義自己的模型權限,並將其授予特定用戶。 您還可以更改與同一模型的不同實例關聯的權限。</p> + +<p>這樣,對視圖和模板中的權限進行的測試就非常類似於對身份驗證狀態的測試(實際上,對權限的測試也對身份驗證進行了測試)。</p> + +<h3 id="Models">Models</h3> + +<p>使用<code>permissions</code> 字段在模型“<code>class Meta</code>”部分中完成權限的定義。 您可以在元組中根據需要指定任意數量的權限,每個權限本身都在嵌套的元組中定義,其中包含權限名稱和權限顯示值。 例如,我們可以定義一個權限,以允許用戶標記已退回一本書,如下所示:</p> + +<pre class="brush: python notranslate">class BookInstance(models.Model): + ... + class Meta: + ... +<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre> + +<p>然後,我們可以將權限分配給管理站點中的“圖書管理員”組。</p> + +<p>打開<strong>catalog/models.py,</strong>然後添加權限,如上所示。 您將需要重新運行遷移(調用 <code>python3 manage.py makemigrations</code> 和<code>python3 manage.py migrate</code>)以適當地更新數據庫。</p> + +<h3 id="模板">模板</h3> + +<p>當前用戶的權限存儲在名為 <code>\{{ perms }}</code>. 的模板變量中。 您可以使用關聯的Django "app"“應用”中的特定變量名稱來檢查當前用戶是否具有特定權限,例如 如果用戶具有此權限,則 <code>\{{ perms.catalog.can_mark_returned }}</code> 將為 <code>True</code> ,否則為<code>False</code>。 我們通常使用模板 <code>{% if %}</code> 標籤測試權限,如下所示:</p> + +<pre class="brush: python notranslate">{% if perms.catalog.<code>can_mark_returned</code> %} + <!-- We can mark a BookInstance as returned. --> + <!-- Perhaps add code to link to a "book return" view here. --> +{% endif %} +</pre> + +<h3 id="視圖">視圖</h3> + +<p>可以在功能視圖中使用<code>permission_required</code> 裝飾器來測試權限,或者在基於類的視圖中使用<code>PermissionRequiredMixin</code>. 來測試權限。 模式和行為與登錄身份驗證的模式和行為相同,儘管當然您可能必須合理地添加多個權限。</p> + +<p>視圖裝飾器函數:</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import permission_required + +@permission_required('catalog.<code>can_mark_returned</code>') +@permission_required('catalog.<code>can_edit</code>') +def my_view(request): + ...</pre> + +<p>基於類的視圖需要權限的混合。</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import PermissionRequiredMixin + +class MyView(PermissionRequiredMixin, View): + permission_required = 'catalog.<code>can_mark_returned</code>' + # Or multiple permissions + permission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit') + # Note that 'catalog.can_edit' is just an example + # the catalog application doesn't have such permission!</pre> + +<h3 id="範例">範例</h3> + +<p>我們不會在這裡更新LocalLibrary; 也許在下一個教程中!</p> + +<h2 id="挑戰自己">挑戰自己</h2> + +<p>在本文的前面,我們向您展示瞭如何為當前用戶創建一個頁面,列出他們所借用的書。 現在的挑戰是創建一個僅對圖書館員可見的相似頁面,該頁面顯示所有已借書的書,其中包括每個借書人的名字。</p> + +<p>您應該能夠遵循與其他視圖相同的模式。 主要區別在於您只需要將視圖限制為圖書館員即可。 您可以根據用戶是否是工作人員來執行此操作(函數裝飾器:<code>staff_member_required</code>,模板變量: <code>user.is_staff</code>),但是我們建議您改用<code>can_mark_returned</code> 權限和<code>PermissionRequiredMixin</code>,如上一節所述。</p> + +<div class="warning"> +<p><strong>重要</strong>:請記住不要將您的超級用戶用於基於權限的測試(即使尚未定義權限,權限檢查也始終對超級用戶返回true!)。 而是創建一個圖書管理員用戶,並添加所需的功能。</p> +</div> + +<p>完成後,您的頁面應類似於以下屏幕截圖。<img alt="All borrowed books, restricted to librarian" src="https://mdn.mozillademos.org/files/14115/library_borrowed_all.png" style="border-style: solid; border-width: 1px; display: block; height: 283px; margin: 0px auto; width: 500px;"></p> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p>出色的工作-您現在已經創建了一個網站,圖書館成員可以登錄並查看他們自己的內容,館員(具有正確的權限)可以用來查看所有借出的書及其借書人。 目前,我們仍在查看內容,但是當您要開始修改和添加數據時,將使用相同的原理和技術。</p> + +<p>在下一篇文章中,我們將研究如何使用Django表單來收集用戶輸入,然後開始修改一些存儲的數據。</p> + +<h2 id="也可以看看">也可以看看</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/">User authentication in Django</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/default//">Using the (default) Django authentication system</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views">Introduction to class-based views > Decorating class-based views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/deployment/index.html b/files/zh-tw/learn/server-side/django/deployment/index.html new file mode 100644 index 0000000000..752714dabb --- /dev/null +++ b/files/zh-tw/learn/server-side/django/deployment/index.html @@ -0,0 +1,675 @@ +--- +title: 'Django Tutorial Part 11: Deploying Django to production' +slug: Learn/Server-side/Django/Deployment +translation_of: Learn/Server-side/Django/Deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">現在,您已經創建(並測試)了一個令人敬畏的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,如果您希望將其安裝在公共 Web 服務器上,以便圖書館工作人員、和成員,可以通過 Internet 訪問它。本文概述如何找到主機來部署您的網站,以及您需要做什麼,才能讓您的網站準備好生產環境。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn where and how you can deploy a Django app to production.</td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>Once your site is finished (or finished "enough" to start public testing) you're going to need to host it somewhere more public and accessible than your personal development computer.</p> + +<p>Up to now you've been working in a development environment, using the Django development web server to share your site to the local browser/network, and running your website with (insecure) development settings that expose debug and other private information. Before you can host a website externally you're first going to have to:</p> + +<ul> + <li>Make a few changes to your project settings.</li> + <li>Choose an environment for hosting the Django app.</li> + <li>Choose an environment for hosting any static files.</li> + <li>Set up a production-level infrastructure for serving your website.</li> +</ul> + +<p>This tutorial provides some guidance on your options for choosing a hosting site, a brief overview of what you need to do in order to get your Django app ready for production, and a worked example of how to install the LocalLibrary website onto the <a href="https://www.heroku.com/">Heroku</a> cloud hosting service.</p> + +<h2 id="What_is_a_production_environment">What is a production environment?</h2> + +<p>The production environment is the environment provided by the server computer where you will run your website for external consumption. The environment includes:</p> + +<ul> + <li>Computer hardware on which the website runs.</li> + <li>Operating system (e.g. Linux, Windows).</li> + <li>Programming language runtime and framework libraries on top of which your website is written.</li> + <li>Web server used to serve pages and other content (e.g. Nginx, Apache).</li> + <li>Application server that passes "dynamic" requests between your Django website and the webserver.</li> + <li>Databases on which your website is dependent.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Depending on how your production is configured you might also have a reverse proxy, load balancer, etc.</p> +</div> + +<p>The server computer could be located on your premises and connected to the Internet by a fast link, but it is far more common to use a computer that is hosted "in the cloud". What this actually means is that your code is run on some remote computer (or possibly a "virtual" computer) in your hosting company's data center(s). The remote server will usually offer some guaranteed level of computing resources (e.g. CPU, RAM, storage memory, etc.) and Internet connectivity for a certain price.</p> + +<p>This sort of remotely accessible computing/networking hardware is referred to as <em>Infrastructure as a Service (IaaS)</em>. Many IaaS vendors provide options to preinstall a particular operating system, onto which you must install the other components of your production environment. Other vendors allow you to select more fully-featured environments, perhaps including a complete Django and web-server setup.</p> + +<div class="note"> +<p><strong>Note:</strong> Pre-built environments can make setting up your website very easy because they reduce the configuration, but the available options may limit you to an unfamiliar server (or other components) and may be based on an older version of the OS. Often it is better to install components yourself, so that you get the ones that you want, and when you need to upgrade parts of the system, you have some idea where to start!</p> +</div> + +<p>Other hosting providers support Django as part of a <em>Platform as a Service</em> (PaaS) offering. In this sort of hosting you don't need to worry about most of your production environment (web server, application server, load balancers) as the host platform takes care of those for you (along with most of what you need to do in order to scale your application). That makes deployment quite easy, because you just need to concentrate on your web application and not all the other server infrastructure.</p> + +<p>Some developers will choose the increased flexibility provided by IaaS over PaaS, while others will appreciate the reduced maintenance overhead and easier scaling of PaaS. When you're getting started, setting up your website on a PaaS system is much easier, and so that is what we'll do in this tutorial.</p> + +<div class="note"> +<p><strong>Tip:</strong> If you choose a Python/Django-friendly hosting provider they should provide instructions on how to set up a Django website using different configurations of webserver, application server, reverse proxy, etc (this won't be relevant if you choose a PaaS). For example, there are many step-by-step guides for various configurations in the <a href="https://www.digitalocean.com/community/tutorials?q=django">Digital Ocean Django community docs</a>.</p> +</div> + +<h2 id="Choosing_a_hosting_provider">Choosing a hosting provider</h2> + +<p>There are well over 100 hosting providers that are known to either actively support or work well with Django (you can find a fairly extensive list at <a href="http://djangofriendly.com/hosts/">Djangofriendly hosts</a>). These vendors provide different types of environments (IaaS, PaaS), and different levels of computing and network resources at different prices.</p> + +<p>Some of the things to consider when choosing a host:</p> + +<ul> + <li>How busy your site is likely to be and the cost of data and computing resources required to meet that demand.</li> + <li>Level of support for scaling horizontally (adding more machines) and vertically (upgrading to more powerful machines) and the costs of doing so.</li> + <li>Where the supplier has data centres, and hence where access is likely to be fastest.</li> + <li>The host's historical uptime and downtime performance.</li> + <li>Tools provided for managing the site — are they easy to use and are they secure (e.g. SFTP vs FTP).</li> + <li>Inbuilt frameworks for monitoring your server.</li> + <li>Known limitations. Some hosts will deliberately block certain services (e.g. email) . Others offer only a certain number of hours of "live time" in some price tiers, or only offer a small amount of storage.</li> + <li>Additional benefits. Some providers will offer free domain names and support for SSL certificates that you would otherwise have to pay for.</li> + <li>Whether the "free" tier you're relying on expires over time, and whether the cost of migrating to a more expensive tier means you would have been better off using some other service in the first place!</li> +</ul> + +<p>The good news when you're starting out is that there are quite a few sites that provide "evaluation", "developer", or "hobbyist" computing environments for "free". These are always fairly resource constrained/limited environments, and you do need to be aware that they may expire after some introductory period. They are however great for testing low traffic sites in a real environment, and can provide an easy migration to paying for more resources when your site gets busier. Popular choices in this category include <a href="https://www.heroku.com/">Heroku</a>, <a href="https://www.pythonanywhere.com/">Python Anywhere</a>, <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure</a>, etc.</p> + +<p>Many providers also have a "basic" tier that provides more useful levels of computing power and fewer limitations. <a href="https://www.digitalocean.com/">Digital Ocean</a> and <a href="https://www.pythonanywhere.com/">Python Anywhere</a> are examples of popular hosting providers that offer a relatively inexpensive basic computing tier (in the $5 to $10USD per month range).</p> + +<div class="note"> +<p><strong>Note:</strong> Remember that price is not the only selection criteria. If your website is successful, it may turn out that scalability is the most important consideration.</p> +</div> + +<h2 id="Getting_your_website_ready_to_publish">Getting your website ready to publish</h2> + +<p>The <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django skeleton website</a> created using the <em>django-admin</em> and <em>manage.py</em> tools are configured to make development easier. Many of the Django project settings (specified in <strong>settings.py</strong>) should be different for production, either for security or performance reasons.</p> + +<div class="note"> +<p><strong>Tip:</strong> It is common to have a separate <strong>settings.py</strong> file for production, and to import sensitive settings from a separate file or an environment variable. This file should then be protected, even if the rest of the source code is available on a public repository.</p> +</div> + +<p>The critical settings that you must check are:</p> + +<ul> + <li><code>DEBUG</code>. This should be set as <code>False</code> in production (<code>DEBUG = False</code>). This stops the sensitive/confidential debug trace and variable information from being displayed.</li> + <li><code>SECRET_KEY</code>. This is a large random value used for CSRF protection etc. It is important that the key used in production is not in source control or accessible outside the production server. The Django documents suggest that this might best be loaded from an environment variable or read from a serve-only file. + <pre># Read SECRET_KEY from an environment variable +import os +SECRET_KEY = os.environ['SECRET_KEY'] + +#OR + +#Read secret key from a file +with open('/etc/secret_key.txt') as f: + SECRET_KEY = f.read().strip()</pre> + </li> +</ul> + +<p>Let's change the <em>LocalLibrary</em> application so that we read our <code>SECRET_KEY</code> and <code>DEBUG</code> variables from environment variables if they are defined, but otherwise use the default values in the configuration file.</p> + +<p>Open <strong>/locallibrary/settings.py</strong>, disable the original <code>SECRET_KEY</code> configuration and add the new lines as shown below in <strong>bold</strong>. During development no environment variable will be specified for the key, so the default value will be used (it shouldn't matter what key you use here, or if the key "leaks", because you won't use it in production).</p> + +<pre class="brush: python"># SECURITY WARNING: keep the secret key used in production secret! +# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag' +<strong>import os</strong> +<strong>SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag')</strong> +</pre> + +<p>Then comment out the existing <code>DEBUG</code> setting and add the new line shown below.</p> + +<pre class="brush: python"># SECURITY WARNING: don't run with debug turned on in production! +# DEBUG = True +<strong>DEBUG = bool( os.environ.get('DJANGO_DEBUG', True) )</strong> +</pre> + +<p>The value of the <code>DEBUG</code> will be <code>True</code> by default, but will be <code>False</code> if the value of the <code>DJANGO_DEBUG</code> environment variable is set to an empty string, e.g. <code>DJANGO_DEBUG=''</code>.</p> + +<div class="note"> +<p><strong>Note</strong>: It would be more intuitive if we could just set and unset the <code>DJANGO_DEBUG</code> environment variable to <code>True</code> and <code>False</code> directly, rather than using "any string" or "empty string" (respectively). Unfortunately environment variable values are stored as Python strings, and the only string that evaluates as <code>False</code> is the empty string (e.g. <code>bool('')==False</code>).</p> +</div> + +<p>A full checklist of settings you might want to change is provided in <a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">Deployment checklist</a> (Django docs). You can also list a number of these using the terminal command below:</p> + +<pre class="brush: python">python3 manage.py check --deploy +</pre> + +<h2 id="Example_Installing_LocalLibrary_on_Heroku">Example: Installing LocalLibrary on Heroku</h2> + +<p>This section provides a practical demonstration of how to install <em>LocalLibrary</em> on the <a href="http://heroku.com">Heroku PaaS cloud</a>.</p> + +<h3 id="Why_Heroku">Why Heroku?</h3> + +<p>Heroku is one of the longest running and popular cloud-based PaaS services. It originally supported only Ruby apps, but now can be used to host apps from many programming environments, including Django!</p> + +<p>We are choosing to use Heroku for several reasons:</p> + +<ul> + <li>Heroku has a <a href="https://www.heroku.com/pricing">free tier</a> that is <em>really</em> free (albeit with some limitations).</li> + <li>As a PaaS, Heroku takes care of a lot of the web infrastructure for us. This makes it much easier to get started, because you don't worry about servers, load balancers, reverse proxies, or any of the other web infrastructure that Heroku provides for us under the hood.</li> + <li>While it does have some limitations these will not affect this particular application. For example: + <ul> + <li>Heroku provides only short-lived storage so user-uploaded files cannot safely be stored on Heroku itself.</li> + <li>The free tier will sleep an inactive web app if there are no requests within a half hour period. The site may then take several seconds to respond when it is woken up.</li> + <li>The free tier limits the time that your site is running to a certain amount of hours every month (not including the time that the site is "asleep"). This is fine for a low use/demonstration site, but will not be suitable if 100% uptime is required.</li> + <li>Other limitations are listed in <a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs).</li> + </ul> + </li> + <li>Mostly it just works, and if you end up loving it, scaling your app is very easy.</li> +</ul> + +<p>While Heroku is perfect for hosting this demonstration it may not be perfect for your real website. Heroku makes things easy to set up and scale, at the cost of being less flexible, and potentially a lot more expensive once you get out of the free tier.</p> + +<h3 id="How_does_Heroku_work">How does Heroku work?</h3> + +<p>Heroku runs Django websites within one or more "<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>", which are isolated, virtualized Unix containers that provide the environment required to run an application. The dynos are completely isolated and have an <em>ephemeral</em> file system (a short-lived file system that is cleaned/emptied every time the dyno restarts). The only thing that dynos share by default are application <a href="https://devcenter.heroku.com/articles/config-vars">configuration variables</a>. Heroku internally uses a load balancer to distribute web traffic to all "web" dynos. Since nothing is shared between them, Heroku can scale an app horizontally simply by adding more dynos (though of course you may also need to scale your database to accept additional connections).</p> + +<p>Because the file system is ephemeral you can't install services required by your application directly (e.g. databases, queues, caching systems, storage, email services, etc). Instead Heroku web applications use backing services provided as independent "add-ons" by Heroku or 3rd parties. Once attached to your web application, the dynos access the services using information contained in application configuration variables.</p> + +<p>In order to execute your application Heroku needs to be able to set up the appropriate environment and dependencies, and also understand how it is launched. For Django apps we provide this information in a number of text files:</p> + +<ul> + <li><strong>runtime.txt</strong>:<strong> </strong>the programming language and version to use.</li> + <li><strong>requirements.txt</strong>: the Python component dependencies, including Django.</li> + <li><strong>Procfile</strong>: A list of processes to be executed to start the web application. For Django this will usually be the Gunicorn web application server (with a <code>.wsgi</code> script).</li> + <li><strong>wsgi.py</strong>: <a href="http://wsgi.readthedocs.io/en/latest/what.html">WSGI</a> configuration to call our Django application in the Heroku environment.</li> +</ul> + +<p>Developers interact with Heroku using a special client app/terminal, which is much like a Unix bash script. This allows you to upload code that is stored in a git repository, inspect the running processes, see logs, set configuration variables and much more!</p> + +<p>In order to get our application to work on Heroku we'll need to put our Django web application into a git repository, add the files above, integrate with a database add-on, and make changes to properly handle static files.</p> + +<p>Once we've done all that we can set up a Heroku account, get the Heroku client, and use it to install our website.</p> + +<div class="note"> +<p><strong>Note:</strong> The instructions below reflect how to work with Heroku at time of writing. If Heroku significantly change their processes, you may wish to instead check their setup documents: <a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a>.</p> +</div> + +<p>That's all the overview you need in order to get started (see <a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> for a more comprehensive guide).</p> + +<h3 id="Creating_an_application_repository_in_Github">Creating an application repository in Github</h3> + +<p>Heroku is closely integrated with the <strong>git</strong> source code version control system, using it to upload/synchronise any changes you make to the live system. It does this by adding a new heroku "remote" repository named <em>heroku</em> pointing to a repository for your source on the Heroku cloud. During development you use git to store changes on your "master" repository. When you want to deploy your site, you sync your changes to the Heroku repository.</p> + +<div class="note"> +<p><strong>Note:</strong> If you're used to following good software development practices you are probably already using git or some other SCM system. If you already have a git repository, then you can skip this step.</p> +</div> + +<p>There are a lot of ways of to work with git, but one of the easiest is to first set up an account on <a href="https://github.com/">Github</a>, create the repository there, and then sync to it locally:</p> + +<ol> + <li>Visit <a href="https://github.com/">https://github.com/</a> and create an account.</li> + <li>Once you are logged in, click the <strong>+</strong> link in the top toolbar and select <strong>New repository</strong>.</li> + <li>Fill in all the fields on this form. While these are not compulsory, they are strongly recommended. + <ul> + <li>Enter a new repository name (e.g. <em>django_local_library</em>), and description (e.g. "Local Library website written in Django".</li> + <li>Choose <strong>Python</strong> in the <em>Add .gitignore</em> selection list.</li> + <li>Choose your preferred license in the <em>Add license</em> selection list.</li> + <li>Check <strong>Initialize this repository with a README</strong>.</li> + </ul> + </li> + <li>Press <strong>Create repository</strong>.</li> + <li>Click the green "<strong>Clone or download</strong>" button on your new repo page.</li> + <li>Copy the URL value from the text field inside the dialog box that appears (it should be something like: <strong>https://github.com/<em><your_git_user_id></em>/django_local_library.git</strong>).</li> +</ol> + +<p>Now the repository ("repo") is created we are going to want to clone it on our local computer:</p> + +<ol> + <li>Install <em>git</em> for your local computer (you can find versions for different platforms <a href="https://git-scm.com/downloads">here</a>).</li> + <li>Open a command prompt/terminal and clone your repository using the URL you copied above: + <pre class="brush: bash">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/django_local_library.git +</pre> + This will create the repository below the current point.</li> + <li>Navigate into the new repo. + <pre class="brush: bash">cd django_local_library.git</pre> + </li> +</ol> + +<p>The final step is to copy in your application and then add the files to your repo using git:</p> + +<ol> + <li>Copy your Django application into this folder (all the files at the same level as <strong>manage.py</strong> and below, <strong>not</strong> their containing locallibrary folder).</li> + <li>Open the <strong>.gitignore</strong> file, copy the following lines into the bottom of it, and then save (this file is used to identify files that should not be uploaded to git by default). + <pre># Text backup files +*.bak + +#Database +*.sqlite3</pre> + </li> + <li>Open a command prompt/terminal and use the <code>add</code> command to add all files to git. + <pre class="brush: bash">git add -A +</pre> + </li> + <li>Use the status command to check all files that you are about to add are correct (you want to include source files, not binaries, temporary files etc.). It should look a bit like the listing below. + <pre>> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + modified: .gitignore + new file: catalog/__init__.py + ... + new file: catalog/migrations/0001_initial.py + ... + new file: templates/registration/password_reset_form.html</pre> + </li> + <li>When you're satisfied commit the files to your local repository: + <pre class="brush: bash">git commit -m "First version of application moved into github"</pre> + </li> + <li>Then synchronise your local repository to the Github website, using the following: + <pre>git push origin master</pre> + </li> +</ol> + +<p>When this operation completes, you should be able to go back to the page on Github where you created your repo, refresh the page, and see that your whole application has now been uploaded. You can continue to update your repository as files change using this add/commit/push cycle.</p> + +<div class="note"> +<p><strong>Tip:</strong> This is a good point to make a backup of your "vanilla" project — while some of the changes we're going to be making in the following sections might be useful for deployment on any platform (or development) others might not.</p> + +<p>The <em>best</em> way to do this is to use <em>git</em> to manage your revisions. With <em>git</em> you can not only go back to a particular old version, but you can maintain this in a separate "branch" from your production changes and cherry-pick any changes to move between production and development branches. <a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">Learning Git</a> is well worth the effort, but is beyond the scope of this topic.</p> + +<p>The <em>easiest</em> way to do this is to just copy your files into another location. Use whichever approach best matches your knowledge of git!</p> +</div> + +<h3 id="Update_the_app_for_Heroku">Update the app for Heroku</h3> + +<p>This section explains the changes you'll need to make to our <em>LocalLibrary</em> application to get it to work on Heroku. While Heroku's <a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a> instructions assume you will use the Heroku client to also run your local development environment, our changes here are compatible with the existing Django development server and ways of working we've already learned.</p> + +<h4 id="Procfile">Procfile</h4> + +<p>Create the file <code>Procfile</code> (no extension) in the root of your GitHub repository to declare the application's process types and entry points. Copy the following text into it:</p> + +<pre>web: gunicorn locallibrary.wsgi --log-file -</pre> + +<p>The "<code>web:</code>" tells Heroku that this is a web dyno and can be sent HTTP traffic. The process to start in this dyno is <em>gunicorn</em>, which is a popular web application server that Heroku recommends. We start Gunicorn using the configuration information in the module <code>locallibrary.wsgi</code> (created with our application skeleton: <strong>/locallibrary/wsgi.py</strong>).</p> + +<h4 id="Gunicorn">Gunicorn</h4> + +<p><a href="http://gunicorn.org/">Gunicorn</a> is the recommended HTTP server for use with Django on Heroku (as referenced in the Procfile above). It is a pure-Python HTTP server for WSGI applications that can run multiple Python concurrent processes within a single dyno (see <a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> for more information).</p> + +<p>While we won't need <em>Gunicorn</em> to serve our LocalLibrary application during development, we'll install it so that it becomes part of our <a href="#requirements">requirements</a> for Heroku to set up on the remote server.</p> + +<p>Install <em>Gunicorn</em> locally on the command line using <em>pip</em> (which we installed when <a href="/en-US/docs/Learn/Server-side/Django/development_environment">setting up the development environment</a>):</p> + +<pre class="brush: bash">pip3 install gunicorn +</pre> + +<h4 id="Database_configuration">Database configuration</h4> + +<p>We can't use the default SQLite database on Heroku because it is file-based, and it would be deleted from the <em>ephemeral</em> file system every time the application restarts (typically once a day, and every time the application or its configuration variables are changed).</p> + +<p>The Heroku mechanism for handling this situation is to use a <a href="https://elements.heroku.com/addons#data-stores">database add-on</a> and configure the web application using information from an environment <a href="https://devcenter.heroku.com/articles/config-vars">configuration variable</a>, set by the add-on. There are quite a lot of database options, but we'll use the <a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#plan-tiers">hobby tier</a> of the <em>Heroku postgres</em> database as this is free, supported by Django, and automatically added to our new Heroku apps when using the free hobby dyno plan tier.</p> + +<p>The database connection information is supplied to the web dyno using a configuration variable named <code>DATABASE_URL</code>. Rather than hard-coding this information into Django, Heroku recommends that developers use the <a href="https://warehouse.python.org/project/dj-database-url/">dj-database-url</a> package to parse the <code>DATABASE_URL</code> environment variable and automatically convert it to Django’s desired configuration format. In addition to installing the <em>dj-database-url</em> package we'll also need to install <a href="http://initd.org/psycopg/">psycopg2</a>, as Django needs this to interact with Postgres databases.</p> + +<h5 id="dj-database-url_(Django_database_configuration_from_environment_variable)">dj-database-url (Django database configuration from environment variable)</h5> + +<p>Install <em>dj-database-url</em> locally so that it becomes part of our <a href="#requirements">requirements</a> for Heroku to set up on the remote server:</p> + +<pre>$ pip3 install dj-database-url +</pre> + +<h5 id="settings.py">settings.py</h5> + +<p>Open <strong>/locallibrary/settings.py</strong> and copy the following configuration into the bottom of the file:</p> + +<pre># Heroku: Update database configuration from $DATABASE_URL. +import dj_database_url +db_from_env = dj_database_url.config(conn_max_age=500) +DATABASES['default'].update(db_from_env)</pre> + +<div class="note"> +<p><strong>Note:</strong></p> + +<ul> + <li>We'll still be using SQLite during development because the <code>DATABASE_URL</code> environment variable will not be set on our development computer.</li> + <li>The value <code>conn_max_age=500</code> makes the connection persistent, which is far more efficient than recreating the connection on every request cycle. However, this is optional and can be removed if needed.</li> +</ul> +</div> + +<h5 id="psycopg2_(Python_Postgres_database_support)">psycopg2 (Python Postgres database support)</h5> + +<p>Django needs <em>psycopg2</em> to work with Postgres databases and you will need to add this to the <a href="#requirements">requirements.txt</a> for Heroku to set this up on the remote server (as discussed in the requirements section below).</p> + +<p>Django will use our SQLite database locally by default, because the <code>DATABASE_URL</code> environment variable isn't set in our local environment. If you want to switch to Postgres completely and use our Heroku free tier database for both development and production then you can. For example, to install psycopg2 and its dependencies locally on a Linux-based system you would use the following bash/terminal commands:</p> + +<pre class="brush: bash"><code>sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib</code> +pip3 install psycopg2 +</pre> + +<p>Installation instructions for the other platforms can be found on the <a href="http://initd.org/psycopg/docs/install.html">psycopg2 website here</a>.</p> + +<p>However, you don't need to do this — you don't need PostGreSQL active on the local computer, as long as you give it to Heroku as a requirement, in <code>requirements.txt</code> (see below).</p> + +<h4 id="Serving_static_files_in_production">Serving static files in production</h4> + +<p>During development we used Django and the Django development web server to serve our static files (CSS, JavaScript, etc.). In a production environment we instead typically serve static files from a content delivery network (CDN) or the web server.</p> + +<div class="note"> +<p><strong>Note:</strong> Serving static files via Django/web application is inefficient because the requests have to pass through unnecessary additional code (Django) rather than being handled directly by the web server or a completely separate CDN. While this doesn't matter for local use during development, it would have a significant performance impact if we were to use the same approach in production. </p> +</div> + +<p>To make it easy to host static files separately from the Django web application, Django provides the <em>collectstatic</em> tool to collect these files for deployment (there is a settings variable that defines where the files should be collected when <em>collectstatic</em> is run). Django templates refer to the hosting location of the static files relative to a settings variable (<code>STATIC_URL</code>), so that this can be changed if the static files are moved to another host/server.</p> + +<p>The relevant setting variables are:</p> + +<ul> + <li><code>STATIC_URL</code>: This is the base URL location from which static files will be served, for example on a CDN. This is used for the static template variable that is accessed in our base template (see <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a>).</li> + <li><code>STATIC_ROOT</code>: This is the absolute path to a directory where Django's "collectstatic" tool will gather any static files referenced in our templates. Once collected, these can then be uploaded as a group to wherever the files are to be hosted.</li> + <li><code>STATICFILES_DIRS</code>: This lists additional directories that Django's collectstatic tool should search for static files.</li> +</ul> + +<h5 id="settings.py_2">settings.py</h5> + +<p>Open <strong>/locallibrary/settings.py</strong> and copy the following configuration into the bottom of the file. The <code>BASE_DIR</code> should already have been defined in your file (the <code>STATIC_URL</code> may already have been defined within the file when it was created. While it will cause no harm, you might as well delete the duplicate previous reference).</p> + +<pre># Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +# The absolute path to the directory where collectstatic will collect static files for deployment. +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +# The URL to use when referring to static files (where they will be served from) +STATIC_URL = '/static/' +</pre> + +<p>We'll actually do the file serving using a library called <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a>, which we install and configure in the next section.</p> + +<p>For more information, see <a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku docs).</p> + +<h4 id="Whitenoise">Whitenoise</h4> + +<p>There are many ways to serve static files in production (we saw the relevant Django settings in the previous sections). Heroku recommends using the <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> project for serving of static assets directly from Gunicorn in production.</p> + +<div class="note"> +<p><strong>Note: </strong>Heroku automatically calls <em>collectstatic</em> and prepares your static files for use by WhiteNoise after it uploads your application. Check out <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> documentation for an explanation of how it works and why the implementation is a relatively efficient method for serving these files.</p> +</div> + +<p>The steps to set up <em>WhiteNoise</em> to use with the project are:</p> + +<h5 id="WhiteNoise">WhiteNoise</h5> + +<p>Install whitenoise locally using the following command:</p> + +<pre>$ pip3 install whitenoise +</pre> + +<h5 id="settings.py_3">settings.py</h5> + +<p>To install <em>WhiteNoise</em> into your Django application, open <strong>/locallibrary/settings.py</strong>, find the <code>MIDDLEWARE</code> setting and add the <code>WhiteNoiseMiddleware</code> near the top of the list, just below the <code>SecurityMiddleware</code>:</p> + +<pre>MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + <strong>'whitenoise.middleware.WhiteNoiseMiddleware',</strong> + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] +</pre> + +<p>Optionally, you can reduce the size of the static files when they are served (this is more efficient). Just add the following to the bottom of <strong>/locallibrary/settings.py</strong>:</p> + +<pre># Simplified static file serving. +# https://warehouse.python.org/project/whitenoise/ +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +</pre> + +<h4 id="Requirements">Requirements</h4> + +<p>The Python requirements of your web application must be stored in a file <strong>requirements.txt</strong> in the root of your repository. Heroku will then install these automatically when it rebuilds your environment. You can create this file using <em>pip</em> on the command line (run the following in the repo root):</p> + +<pre class="brush: bash">pip3 freeze > requirements.txt</pre> + +<p>After installing all the different dependencies above, your <strong>requirements.txt</strong> file should have <em>at least</em> these items listed (though the version numbers may be different). Please delete any other dependencies not listed below, unless you've explicitly added them for this application.</p> + +<pre>dj-database-url==0.4.1 +Django==2.0 +gunicorn==19.6.0 +<strong>psycopg2==2.6.2</strong> +whitenoise==3.2.2 +</pre> + +<div class="note"> +<p>Make sure that a <strong>psycopg2</strong> line like the one above is present! Even iIf you didn't install this locally then you should still add this to the <strong>requirements.txt</strong>.</p> +</div> + +<h4 id="Runtime">Runtime</h4> + +<p>The <strong>runtime.txt</strong> file, if defined, tells Heroku which programming language to use. Create the file in the root of the repo and add the following text:</p> + +<pre>python-3.6.4</pre> + +<div class="note"> +<p><strong>Note:</strong> Heroku only supports a small number of <a href="https://devcenter.heroku.com/articles/python-support#supported-python-runtimes">Python runtimes</a> (at time of writing, this includes the one above). Heroku will use a supported runtime irrespective of the value specified in this file.</p> +</div> + +<h4 id="Save_changes_to_Github_and_re-test">Save changes to Github and re-test</h4> + +<p>Next lets save all our changes to Github. In the terminal (whist inside our repository), enter the following commands:</p> + +<pre class="brush: python">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>Before we proceed, lets test the site again locally and make sure it wasn't affected by any of our changes above. Run the development web server as usual and then check the site still works as you expect on your browser.</p> + +<pre class="brush: bash">python3 manage.py runserver</pre> + +<p>We should now be ready to start deploying LocalLibrary on Heroku.</p> + +<h3 id="Get_a_Heroku_account">Get a Heroku account</h3> + +<p>To start using Heroku you will first need to create an account:</p> + +<ul> + <li>Go to <a href="https://www.heroku.com/">www.heroku.com</a> and click the <strong>SIGN UP FOR FREE</strong> button.</li> + <li>Enter your details and then press <strong>CREATE FREE ACCOUNT</strong>. You'll be asked to check your account for a sign-up email.</li> + <li>Click the account activation link in the signup email. You'll be taken back to your account on the web browser.</li> + <li>Enter your password and click <strong>SET PASSWORD AND LOGIN</strong>.</li> + <li>You'll then be logged in and taken to the Heroku dashboard: <a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="Install_the_client">Install the client</h3> + +<p>Download and install the Heroku client by following the <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">instructions on Heroku here</a>.</p> + +<p>After the client is installed you will be able run commands. For example to get help on the client:</p> + +<pre class="brush: bash">heroku help +</pre> + +<h3 id="Create_and_upload_the_website">Create and upload the website</h3> + +<p>To create the app we run the "create" command in the root directory of our repository. This creates a git remote ("pointer to a remote repository") named <em>heroku</em> in our local git environment.</p> + +<pre class="brush: bash">heroku create</pre> + +<div class="note"> +<p><strong>Note:</strong> You can name the remote if you like by specifying a value after "create". If you don't then you'll get a random name. The name is used in the default URL.</p> +</div> + +<p>We can then push our app to the Heroku repository as shown below. This will upload the app, package it in a dyno, run collectstatic, and start the site.</p> + +<pre class="brush: bash">git push heroku master</pre> + +<p>If we're lucky, the app is now "running" on the site, but it won't be working properly because we haven't set up the database tables for use by our application. To do this we need to use the <code>heroku run</code> command and start a "<a href="https://devcenter.heroku.com/articles/deploying-python#one-off-dynos">one off dyno</a>" to perform a migrate operation. Enter the following command in your terminal:</p> + +<pre class="brush: bash">heroku run python manage.py migrate</pre> + +<p>We're also going to need to be able to add books and authors, so lets also create our administration superuser, again using a one-off dyno:</p> + +<pre class="brush: bash">heroku run python manage.py createsuperuser</pre> + +<p>Once this is complete, we can look at the site. It should work, although it won't have any books in it yet. To open your browser to the new website, use the command:</p> + +<pre class="brush: bash">heroku open</pre> + +<p>Create some books in the admin site, and check out whether the site is behaving as you expect.</p> + +<h3 id="Managing_addons">Managing addons</h3> + +<p>You can check out the add-ons to your app using the <code>heroku addons</code> command. This will list all addons, and their price tier and state.</p> + +<pre class="brush: bash">>heroku addons + +Add-on Plan Price State +───────────────────────────────────────── ───────── ───── ─────── +heroku-postgresql (postgresql-flat-26536) hobby-dev free created + └─ as DATABASE</pre> + +<p>Here we see that we have just one add-on, the postgres SQL database. This is free, and was created automatically when we created the app. You can open a web page to examine the database add-on (or any other add-on) in more detail using the following command:</p> + +<pre class="brush: bash">heroku addons:open heroku-postgresql +</pre> + +<p>Other commands allow you to create, destroy, upgrade and downgrade addons (using a similar syntax to opening). For more information see <a href="https://devcenter.heroku.com/articles/managing-add-ons">Managing Add-ons</a> (Heroku docs).</p> + +<h3 id="Setting_configuration_variables">Setting configuration variables</h3> + +<p>You can check out the configuration variables for the site using the <code>heroku config</code> command. Below you can see that we have just one variable, the <code>DATABASE_URL</code> used to configure our database.</p> + +<pre class="brush: bash">>heroku config + +=== locallibrary Config Vars +DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3</pre> + +<p>If you recall from the section on <a href="#Getting_your_website_ready_to_publish">getting the website ready to publish</a>, we have to set environment variables for <code>DJANGO_SECRET_KEY</code> and <code>DJANGO_DEBUG</code>. Let's do this now.</p> + +<div class="note"> +<p><strong>Note:</strong> The secret key needs to be really secret! One way to generate a new key is to create a new Django project (<code>django-admin startproject someprojectname</code>) and then get the key that is generated for you from its <strong>settings.py</strong>.</p> +</div> + +<p>We set <code>DJANGO_SECRET_KEY</code> using the <code>config:set</code> command (as shown below). Remember to use your own secret key!</p> + +<pre class="brush: bash">>heroku config:set DJANGO_SECRET_KEY=eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh&= + +Setting DJANGO_SECRET_KEY and restarting locallibrary... done, v7 +DJANGO_SECRET_KEY: eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh +</pre> + +<p>We similarly set <code>DJANGO_DEBUG</code>:</p> + +<pre class="brush: bash">>heroku config:set <code>DJANGO_DEBUG= + +Setting DJANGO_DEBUG and restarting locallibrary... done, v8</code></pre> + +<p>If you visit the site now you'll get a "Bad request" error, because the <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts">ALLOWED_HOSTS</a> setting is <em>required</em> if you have <code>DEBUG=False</code> (as a security measure). Open <strong>/locallibrary/settings.py</strong> and change the <code>ALLOWED_HOSTS</code> setting to include your base app url (e.g. 'locallibrary1234.herokuapp.com') and the URL you normally use on your local development server.</p> + +<pre class="brush: python">ALLOWED_HOSTS = ['<your app URL without the https:// prefix>.herokuapp.com','127.0.0.1'] +# For example: +# ALLOWED_HOSTS = ['fathomless-scrubland-30645.herokuapp.com','127.0.0.1'] +</pre> + +<p>Then save your settings and commit them to your Github repo and to Heroku:</p> + +<pre class="brush: bash">git add -A +git commit -m 'Update ALLOWED_HOSTS with site and development server URL' +git push origin master +git push heroku master</pre> + +<div class="note"> +<p>After the site update to Heroku completes, enter an URL that does not exist (e.g. <strong>/catalog/doesnotexist/</strong>). Previously this would have displayed a detailed debug page, but now you should just see a simple "Not Found" page.</p> +</div> + +<h3 id="Debugging">Debugging</h3> + +<p>The Heroku client provides a few tools for debugging:</p> + +<pre class="brush: bash">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku config:set DEBUG_COLLECTSTATIC=1 # Add additional logging for collectstatic (this tool is run automatically during a build) +heroku ps #Display dyno status +</pre> + +<p>If you need more information than these can provide you will need to start looking into <a href="https://docs.djangoproject.com/en/2.0/topics/logging/">Django Logging</a>.</p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>That's the end of this tutorial on setting up Django apps in production, and also the series of tutorials on working with Django. We hope you've found them useful. You can check out a fully worked-through version of the <a href="https://github.com/mdn/django-locallibrary-tutorial">source code on Github here</a>.<br> + <br> + The next step is to read our last few articles, and then complete the assessment task.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/">Deploying Django</a> (Django docs) + + <ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">Deployment checklist</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/deployment/">Deploying static files</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/">How to deploy with WSGI</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/modwsgi/">How to use Django with Apache and mod_wsgi</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/gunicorn/">How to use Django with Gunicorn</a> (Django docs)</li> + </ul> + </li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/django-app-configuration">Configuring Django apps for Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-concurrency-and-database-connections">Concurrency and Database Connections in Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-python">Deploying Python and Django apps on Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/search?q=django">Other Heroku Django docs</a></li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials/how-to-serve-django-applications-with-uwsgi-and-nginx-on-ubuntu-16-04">How To Serve Django Applications with uWSGI and Nginx on Ubuntu 16.04</a></li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=django">Other Digital Ocean Django community docs</a></li> + </ul> + </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/django/development_environment/index.html b/files/zh-tw/learn/server-side/django/development_environment/index.html new file mode 100644 index 0000000000..c3d4c5c823 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/development_environment/index.html @@ -0,0 +1,429 @@ +--- +title: 架設 Django 開發環境 +slug: Learn/Server-side/Django/development_environment +translation_of: Learn/Server-side/Django/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</div> + +<p class="summary"><font><font>現在,你知道什麼是Django。</font></font><font><font>那麼我們將向你展示如何在Windows,Linux(Ubuntu)和Mac OSX上設置和測試Django開發環境—無論你常用哪種操作系統,本文應該都能讓你開始開發Django應用程序。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先備知識:</th> + <td><font>知道如何打開終端或命令行。</font><font>了解如何在計算機的操作系統上安裝軟件包。</font></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td><span style='background-color: #ffe8d4; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>在你的計算機操作系統上運行Django(2.0)開發環境。</span></td> + </tr> + </tbody> +</table> + +<h2 id="Django_開發環境概覽">Django 開發環境概覽</h2> + +<p>Django 使你輕鬆設置自己的電腦,以便開始開發網絡應用。這部分介紹在開發環境可以取得什麼,並概述了部分設置和配置選項。本文的其餘部分,介紹了在Ubuntu,Mac OS X 和 Windows 上,安裝 Django 開發環境的<em>推薦方法</em>,以及如何測試。</p> + +<h3 id="什麼是_Django_開發環境">什麼是 Django 開發環境?</h3> + +<p>開發環境是在本地計算機上安裝 Django,你可以在將 Django 部署到生產環境之前,用於開發和測試 Django 應用程序。</p> + +<p>Django 本身提供的主要工具,是一組用於創建和使用 Django 項目的 Python 腳本,以及可用於在你的計算機的瀏覽器上,測試本地(即,你的計算機,而不是外部 Web 服務器)Django 網絡應用程序的簡單開發網路服務器 。</p> + +<p>還有其他外部工具, 它們構成了開發環境的一部分, 我們將不再贅述。這些包括 文本編輯器 <a href="/en-US/docs/Learn/Common_questions/Available_text_editors">text editor</a> 或編輯代碼的 IDE,以及像 <a href="https://git-scm.com/">Git </a>這樣的源代碼控制管理工具,用於安全地管理不同版本的代碼。我們假設你已經安裝了一個文本編輯器。</p> + +<h3 id="什麼是Django設置選項"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>什麼是Django設置選項?</font></font></span></h3> + +<p><font>Django 如何在安裝和配置方面非常靈活。</font><font>Django可以:</font></p> + +<ul> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>安裝在不同的操作系統上。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>可以一起使用Python 3 和Python 2.</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>通過Python包索引(PyPi)安裝,和在許多情況下主機的包管理器應用程序。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>配置為使用幾個數據庫之一,可能需要單獨安裝和配置。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>運行在主系統Python環境中或在單獨的Python虛擬環境中運行。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>每個選項都需要略微不同的配置和設置。</font><font>以下小節解釋了你的一些選擇。</font><font>對於本文的其餘部分,我們將介紹Django在少見的操作系統上的設置,考量該模塊的其餘部分。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font>: 其他可能的安裝選項在官方Django文檔中介紹。</font></font><a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83#furtherreading" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212);'><font><font>相應文件點擊這裡</font></font></a><font><font>。</font></font></p> +</div> + +<h4 id="支持哪些操作系統" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>支持哪些操作系統?</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>幾乎任何可以運行Python編程語言的機器可以運行Django 網絡應用程序:Windows,Mac OSX,Linux/Unix,Solaris,僅舉幾例。</font><font>幾乎任何計算機都應該在開發過程中運行Django所需的性能。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在本文中。</font><font>我們將提供Windows,Mac OS X 和Linux/Unix的說明。</font></font></p> + +<h4 id="你應該使用什麼版本的Python" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你應該使用什麼版本的Python?</font></font></h4> + +<p>我們建議您使用最新版本 - 在編寫本文時,這是Python 3.7。</p> + +<p>如果需要,可以使用Python 3.4或更高版本(將來的版本中將刪除Python 3.4支持)。</p> + +<div class="note"> +<p><strong>注意</strong>: Python 2.7不能與Django 2.0一起使用(Django 1.11.x系列是最後一個支持Python 2.7的系列)。</p> +</div> + +<h4 id="我們在哪裡下載Django" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們在哪裡下載Django?</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>有三個地方可以下載Django:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>Python包含庫(PyPi),使用</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>pip</font></font></strong><font><font>工具.這是獲取最新穩定版本的Django的最佳方式.</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>使用計算機軟件包管理器中的版本。</font><font>與操作系統捆綁在一起的Django的分發提供了一種熟悉的安裝機制。</font><font>請注意,打包版本可能相當舊,只能安裝到系統Python 環境中(可能不是你想要的)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>可以從源代碼獲取並安裝的最新版本的Python。</font><font>這不是推薦給初學者,但是當你準備好開始貢獻給Django本身的時候,它是必需的。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本文介紹如何從PyPi安裝Django,從獲得最新的穩定版本。</font></font></p> + +<h4 id="哪個數據庫" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>哪個數據庫?</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>Django支持四個主要數據庫(PostgreSQL,MySQL,Oracle和SQLite),還有一些社區庫,可以為其他流行的SQL和NOSQL數據庫,提供不同級別的支持。</font><font>我們建議你為生產和開發,選擇相同的數據庫(儘管Django使用其對象關係映射器(ORM)抽像出許多數據庫差異,但是仍然存在可以避免的</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/databases/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>潛在問題</font></font></a><font><font><span> </span>).</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>對於本文(和本模塊的大部分),我們將使用將數據存放在文件中的SQLite數據庫。</font><font>SQLite旨在用作輕量級數據庫,不能支持高級並發。</font><font>然而,這確實是唯讀的應用程序的絕佳選擇。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>:當你使用標準工具(django-admin)啟動你的網站項目時,Django將默認配置為使用SQLite。</font><font>用來入門,這是一個很好的選擇,因為它不需要額外的配置和設置。</font></font></p> +</div> + +<h4 id="安裝到整個本機系統還是Python虛擬環境中" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>安裝到整個本機系統還是Python虛擬環境中?</font></font></h4> + +<p>安裝Python3時,您將獲得一個由所有Python3代碼共享的單一全局環境。雖然您可以在環境中,安裝任何您喜歡的Python軟件包,但您一次只能安裝每個軟件包的一個特定版本。</p> + +<div class="note"> +<p><strong>注意</strong>: 安裝到全局環境中的Python應用程序可能會相互衝突(即,如果它們依賴於同一程序包的不同版本)。</p> +</div> + +<p>如果您將Django安裝到默認/全局環境中,那麼您將只能在計算機上,定位一個版本的Django。如果您想要創建新網站(使用最新版本的Django)同時仍然維護依賴舊版本的網站,這可能是一個問題。</p> + +<p>因此,經驗豐富的Python / Django開發人員,通常在獨立的Python虛擬環境中,運行Python應用程序。這樣可以在一台計算機上,實現多個不同的Django環境。 Django開發團隊本身建議您使用Python虛擬環境!</p> + +<p>本模塊假設您已將Django安裝到虛擬環境中,我們將向您展示如何做。</p> + +<h2 id="安裝_Python_3">安裝 Python 3</h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>為了使用Django,你需要安裝Python3.同樣你需要</font></font><a class="external external-icon" href="https://pypi.python.org/pypi" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Python包管理工具</font></font></a><font><font><span> </span> —<span> </span></font></font><em><font><font>pip3</font></font></em><font><font><span> </span>—用來管理(安裝,更新和刪除)Django和其他Python應用程序使用的Python軟件包/庫。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本書簡要說明如何根據需要檢查什麼版本,並根據需要安裝新版本,適用於</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Ubuntu Linux 16.04, Mac OS X, and Windows 10。</font></font></strong></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>:根據你的平台,您還可以從操作系統自己的軟件包管理器或其他機制安裝Python / pip。</font><font>對於大多數平台,您可以從</font></font><a class="external external-icon" href="https://www.python.org/downloads/" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>https://www.python.org/downloads/</font></font></a><font><font>下載所需的安裝文件,並使用適當的平台特定方法進行安裝。</font></font></p> +</div> + +<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3> + +<p>Ubuntu Linux 18.04 LTS默認包含Python 3.6.5。您可以通過在bash終端中運行以下命令來確認:</p> + +<pre class="brush: bash"><span style="line-height: 1.5;">python3 -V + Python 3.6.5</span></pre> + +<p><font><font>然而,在默認情況下,為Python 3(包括Django)安裝軟件包的Python包管理工具</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>不可用。</font><font>你</font></font></strong><font><font>可以使用以下方式將</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>pip3</font></font></strong><font><font>安裝在</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>bash</font></font></strong><font><font>終端</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>:</font></font></strong></p> + +<pre class="brush: bash">sudo apt install python3-pip +</pre> + +<h3 id="macOS_X">macOS X</h3> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>Mac OS X "El Capitan" 不包括Python 3.你可以通過在bash終端中運行一下命令來確認:</span></p> + +<pre class="brush: bash"><span style="line-height: 1.5;">python3 -V + </span>-bash: python3: command not found</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你可以輕鬆從</font></font><a class="external external-icon" href="https://www.python.org/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>python.org</font></font></a><font><font>安裝Python 3(以及pip3工具):</font></font></p> + +<ol style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; list-style-type: decimal; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>下載所需的安裝程序:</font></font> + + <ol style="font-style: normal !important; margin: 0px; padding: 6px 0px 0px 40px; border: 0px; list-style-type: decimal; max-width: 42rem;"> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>點擊</font></font><a class="external external-icon" href="https://www.python.org/downloads/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>https://www.python.org/downloads/</font></font></a></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>選擇</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Download Python 3.7.0</font></font></strong><font><font>按鈕(確切的版本號可能不同).</font></font></li> + </ol> + </li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>使用Finder找到文件,然後雙擊包文件。</font><font>遵循安裝提示。</font></font><br> + <font><font>(一般能拖拽就拖拽)</font></font></li> +</ol> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你現在可以檢查Pyhon 3來確認成功安裝,如下所示:</font></font></p> + +<pre class="brush: bash"><span style="line-height: 1.5;">python3 -V + Python 3.7.0</span> +</pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>你也可以通過列出可用的軟件包來檢查pip3是否安裝:</span></p> + +<pre class="brush: bash">pip3 list</pre> + +<h3 id="Windows_10">Windows 10</h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>windows默認不安裝,但你可以從</font></font><a class="external external-icon" href="https://www.python.org/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>python.org</font></font></a><font><font>輕鬆安裝它(以及pip3工具):</font></font></p> + +<ol style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; list-style-type: decimal; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>下載所需版本:</font></font> + + <ol style="font-style: normal !important; margin: 0px; padding: 6px 0px 0px 40px; border: 0px; list-style-type: decimal; max-width: 42rem;"> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>點擊</font></font><a class="external external-icon" href="https://www.python.org/downloads/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>https://www.python.org/downloads/</font></font></a></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>選擇</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Download Python 3.7.0 </font></font></strong><font><font>按鈕(確切的版本號可能不同).</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>通過雙擊下載的文件並按照提示安裝Python</font></font></li> + </ol> + </li> +</ol> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你可以通過在命令提示符中輸入以下文本來驗證是否安裝了Python:</font></font></p> + +<pre class="brush: bash"><span style="line-height: 1.5;">py -3 -V + Python 3.7.0</span> +</pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>默認情況下,Windows安裝程序包含pip3(python包管理器,你可以列出安裝的軟件包):</span></p> + +<pre class="brush: bash"><span style="line-height: 1.5;">pip3 list</span> +</pre> + +<div class="note"> +<p><strong>注意</strong>: 安裝程序應設置上述命令工作所需的一切。但是,如果您收到無法找到Python 的消息,則可能忘記將其添加到系統路徑中。您可以通過再次運行安裝程序,選擇“修改”"Modify",然後選中第二頁上標有“將Python添加到環境變量”"Add Python to environment variables"的框來執行此操作。</p> +</div> + +<h2 id="在Python虛擬環境中使用Django"><a id="Using_a_virtual_environment" name="Using_a_virtual_environment"></a>在Python虛擬環境中使用Django</h2> + +<p>我們將用於創建虛擬環境的庫是 <a href="https://virtualenvwrapper.readthedocs.io/en/latest/index.html">virtualenvwrapper</a>(Linux和macOS X)和 <a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a> (Windows),後者又使用 <a href="https://developer.mozilla.org/en-US/docs/Python/Virtualenv">virtualenv</a>工具。包裝工具為所有平台上的接口管理創建了一致的界面。</p> + +<h3 id="安裝虛擬環境軟體">安裝虛擬環境軟體</h3> + +<h4 id="Ubuntu虛擬環境設置">Ubuntu虛擬環境設置</h4> + +<p>安裝Python和pip之後,你可以安裝 virtualenvwrapper(包括virtualenv)。可在<a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">此處</a>找到官方安裝指南,或按照以下說明操作。</p> + +<p>使用pip3安裝該工具:</p> + +<pre class="brush: bash"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>然後將以下行添加到shell啟動文件的末尾(這是主目錄中的隱藏文件名<strong>.bashrc</strong>)。這些設置了虛擬環境應該存在的位置,開發項目目錄的位置以及使用此軟件包安裝的腳本的位置 :</p> + +<pre class="brush: bash"><code>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</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>: <code>VIRTUALENVWRAPPER_PYTHON</code> 和 <code>VIRTUALENVWRAPPER_VIRTUALENV_ARGS </code>變量指向Python3的正常安裝位置,<code>source /usr/local/bin/virtualenvwrapper.sh</code>指向<code>virtualenvwrapper.sh</code>腳本的正常位置。如果virtualenv在測試時不起作用,那麼要檢查的一件事是Python和腳本位於預期的位置(然後適當地更改啟動文件)。</p> + +<p>您可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>.的命令找到系統的正確位置。</p> +</div> + +<p>然後在終端中運行以下命令重新加載啟動文件:</p> + +<pre class="brush: bash"><code>source ~/.bashrc</code></pre> + +<p>此時您應該看到一堆腳本正在運行,如下所示:</p> + +<pre class="brush: bash"><code>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</code> +</pre> + +<p>現在,您可以使用<code>mkvirtualenv</code>命令創建新的虛擬環境。</p> + +<h4 id="macOS_X_虛擬環境設置">macOS X 虛擬環境設置</h4> + +<p>在 macOS X上設置 virtualenvwrapper 與在 Ubuntu上幾乎完全相同(同樣,您可以按照<a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">官方安裝指南</a>或下面的說明進行操作。</p> + +<p>使用 pip 安裝 virtualenvwrapper(並捆綁 virtualenv),如圖所示。</p> + +<pre class="brush: bash"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>然後將以下幾行添加到 shell 啟動文件的末尾。</p> + +<pre class="brush: bash"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code></pre> + +<div class="note"> +<p><strong>注意</strong>: <code>VIRTUALENVWRAPPER_PYTHON</code>變量指向Python3的正常安裝位置,<code>source /usr/local/bin/virtualenvwrapper.sh</code>指向<code>virtualenvwrapper.sh</code>腳本的正常位置。如果virtualenv在測試時不起作用,那麼要檢查的一件事,是Python和腳本位於預期的位置(然後適當地更改啟動文件)。</p> + +<p>例如,對macOS進行的一次安裝測試,最終在啟動文件中需要以下幾行:</p> + +<pre class="brush: 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</pre> + +<p>您可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>的命令找到系統的正確位置。</p> +</div> + +<p>這幾行與Ubuntu相同,但啟動文件是主目錄中、名稱不同的隱藏文件<strong>.bash_profile</strong>。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果在查找程序中找不到要編輯的<strong>.bash-profile</strong>,也可以使用nano在終端中打開它。</p> + +<p>命令看起來像這樣:</p> + +<pre><code>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.</code> +</pre> + +<p> </p> +</div> + +<p>然後通過在終端中,進行以下調用,來重新加載啟動文件:</p> + +<pre class="brush: bash"><code>source ~/.bash_profile</code></pre> + +<p>此時,您可能會看到一堆腳本正在運行(與Ubuntu安裝相同的腳本)。您現在應該能夠使用<code>mkvirtualenv</code>命令,創建新的虛擬環境。</p> + +<h4 id="Windows_10_虛擬環境設置">Windows 10 虛擬環境設置</h4> + +<p>安裝<a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a>比設置virtualenvwrapper更簡單,因為您不需要配置工具存放虛擬環境信息的位置(有默認值)。您需要做的就是,在命令提示符中運行以下命令:</p> + +<pre><code>pip3 install virtualenvwrapper-win</code></pre> + +<p>現在,您可以使用<code>mkvirtualenv</code>命令創建新的虛擬環境</p> + +<h3 id="創建虛擬環境">創建虛擬環境</h3> + +<p>一旦你安裝了virtualenvwrapper或virtualenvwrapper-win,那麼在所有平台上使用虛擬環境都非常相似。</p> + +<p>現在,您可以使用<code>mkvirtualenv</code>命令創建新的虛擬環境。當此命令運行時,您將看到正在設置的環境(您看到的是略微特定於平台的)。當命令完成時,新的虛擬環境,將處於活動狀態 - 您可以看到這一點,因為提示的開頭,將是括號中環境的名稱(如下所示)。</p> + +<pre><code>$ 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:~$</code> +</pre> + +<p>現在,您可以在虛擬環境中,安裝Django,並開始開發。</p> + +<div class="note"> +<p><strong>注意</strong>: 從本文開始(實際上是本系列教學),請假設任何命令都在Python虛擬環境中運行,就像我們在上面設置的那樣。</p> +</div> + +<h3 id="使用虛擬環境">使用虛擬環境</h3> + +<p>您應該知道其他一些有用的命令(工具文檔中有更多,但這些是您經常使用的命令):</p> + +<ul> + <li><code>deactivate</code> — 退出當前的Python虛擬環境</li> + <li><code>workon</code> — 列出可用的虛擬環境</li> + <li><code>workon name_of_environment</code> — 激活指定的Python虛擬環境</li> + <li><code>rmvirtualenv name_of_environment</code> — 刪除指定的環境</li> +</ul> + +<h2 id="安裝_Django">安裝 Django</h2> + +<p>一旦你創建了一個虛擬環境,並調用了<code>workon</code>來輸入它,就可以使用pip3來安裝Django。</p> + +<pre class="brush: bash">pip3 install django +</pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>您可以通過運行以下命令來測試Django是否安裝(這只是測試Python可以找到Django模塊):</span></p> + +<pre class="brush: bash"># Linux/macOS X +python3 -m django --version + 2.0 + +# Windows +py -3 -m django --version + 2.0 +</pre> + +<div class="note"> +<p><strong>注意</strong>: 如果上面的Windows命令沒有顯示django模塊,請嘗試:</p> + +<pre class="brush: bash">py -m django --version</pre> +在Windows中,Python 3腳本通過在命令前面加上<code>py -3</code>來啟動,儘管這可能會因具體安裝而異。如果遇到任何命令問題,請嘗試省略<code>-3</code>修飾符。在Linux / macOS X中,命令是<code>python3</code>。</div> + +<div class="warning"> +<p><font>重要提示:本教程的其餘部分,使用Linux命令來調用Python 3(python3)。</font><font>如果您在Windows上工作,只需將此前綴替換為:<code> py -3</code></font></p> +</div> + +<h2 id="測試你的安裝"><font><font>測試你的安裝</font></font></h2> + +<p><font><font>上面的測試可以工作,但它不是很有趣。</font><font>一個更有趣的測試是創建一個骨架項目並看到它工作。</font><font>要做到這一點,先在你的命令提示符/終端導航到你想存儲你</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Django</font></font></strong><font><font>應用程序的位置。</font><font>為您的測試站點創建一個文件夾並瀏覽它。</font></font></p> + +<pre class="brush: bash">mkdir django_test +cd django_test +</pre> + +<p><font><font>然後,您可以使用<strong>django-admin</strong>工具創建一個名為“<span> </span></font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>mytestsite</font></font></strong><font><font><span> </span>”的新骨架站點,如圖所示。</font><font>創建網站後,您可以導航到文件夾,您將在其中找到管理項目的主要腳本,名為</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>manage.py</font></font></strong><font><font>。</font></font></p> + +<pre class="brush: bash">django-admin startproject mytestsite +cd mytestsite</pre> + +<p><font><font>我們可以使用</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>manage.py</font></font></strong><font><font>和 </font></font><code>runserver </code><font><font>命令,從此文件夾內運行開發Web服務器,如圖所示。</font></font></p> + +<pre class="brush: 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. +</pre> + +<div class="note"> +<p><strong>注意</strong>: 以上命令顯示Linux / macOS X命令。此時您可以忽略有關“14個未應用的遷移”的警告!("14 unapplied migration(s)" )</p> +</div> + +<p><font>一旦服務器運行,您可以通過導航到本地Web瀏覽器上的以下URL來查看該站點:<code>http://127.0.0.1:8000/</code>。</font><font>你應該看到一個如下所示的網站:</font></p> + +<p><img alt="Django Skeleton App Homepage" src="https://mdn.mozillademos.org/files/15728/Django_Skeleton_Website_Homepage.png"></p> + +<ul> +</ul> + +<h2 id="總結Summary">總結Summary</h2> + +<p>您現在已在計算機上啟動並運行Django開發環境。</p> + +<p>在測試部分,您還簡要了解了,我們如何使用<code>django-admin startproject</code>,創建一個新的Django網站,並使用開發Web服務器(<code>python3 manage.py runserver</code>)在瀏覽器中運行它。在下一篇文章中,我們將擴展此過程,構建一個簡單、但完整的Web應用程序。</p> + +<h2 id="參閱">參閱</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/install/">Quick Install Guide</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/install/">How to install Django — Complete guide</a> (Django docs) - includes information on how to remove Django</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/windows/">How to install Django on Windows</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</p> + +<h2 id="本教程連結">本教程連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/django/django_assessment_blog/index.html b/files/zh-tw/learn/server-side/django/django_assessment_blog/index.html new file mode 100644 index 0000000000..d584b8259c --- /dev/null +++ b/files/zh-tw/learn/server-side/django/django_assessment_blog/index.html @@ -0,0 +1,316 @@ +--- +title: 'Assessment: DIY Django mini blog' +slug: Learn/Server-side/Django/django_assessment_blog +tags: + - django + - 初學者 + - 部落格 +translation_of: Learn/Server-side/Django/django_assessment_blog +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">在這個評估中,您將使用您在 <a href="/en-US/docs/Learn/Server-side/Django">Django Web Framework (Python)</a> 模組中獲得的知識,來創建一個非常基本的部落格。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"> + <p>前提:</p> + </th> + <td>在開始時做這章節的任務之前,你應該已經看完這個模組的所有文章了。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td> + <p>測試Django基礎的綜合應用,包含URL設定、模型、視圖、表單和模板。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="專案簡介">專案簡介</h2> + +<p>需要顯示的頁面與對應的URLs和需求提列於下表:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">頁面</th> + <th scope="col">URL</th> + <th scope="col">需求</th> + </tr> + </thead> + <tbody> + <tr> + <td>首頁</td> + <td><code>/</code> 和 <code>/blog/</code></td> + <td>關於此站的說明。</td> + </tr> + <tr> + <td>所有部落格文章的清單</td> + <td><code>/blog/blogs/</code></td> + <td> + <p>所有部落格文章的清單。</p> + + <ul> + <li>所有使用者都能從側邊選單進入此頁。</li> + <li>清單按發布日期排序(新至舊)。</li> + <li>清單依照每頁5筆文章分頁。</li> + <li>清單內的每一筆項目顯示文章標題、發布日期與作者的名字。</li> + <li>文章標題連結至該至文章的詳細頁面。</li> + <li>作者的名字連結至該作者的詳細頁面。</li> + </ul> + </td> + </tr> + <tr> + <td>部落格作者(blogger) 詳細頁面</td> + <td><code>/blog/blogger/<em><author-id></em></code></td> + <td> + <p>特定作者(由id指定)的資訊與他所發布的部落格文章。</p> + + <ul> + <li>所有使用者都能從作者連結進入此頁(例如文章內的作者連結)。</li> + <li>包含一些關於作者本身的資訊。</li> + <li>文章清單按發布日期排序(新至舊)。</li> + <li>不用分頁。</li> + <li>文章清單只顯示文章標題與發佈日期。</li> + <li>文章標題連結至文章詳細頁面。</li> + </ul> + </td> + </tr> + <tr> + <td>部落格文章詳細頁面</td> + <td><code>/blog/<em><blog-id></em></code></td> + <td> + <p>部落格文章詳細內容。</p> + + <ul> + <li>任何使用者都能從部落格文章的清單進入此頁。</li> + <li>包含文章標題、作者、發布日期與內容。</li> + <li>文章的回覆必須呈現於底部。</li> + <li>文章的回覆必須按回覆時間排序(舊至新)。</li> + <li>已登入的使用者能看見新增回覆的連結。</li> + <li>文章與回覆需以純文字的方式顯示。不需要支援任何markup(例如連結、圖片、粗體/斜體等)。</li> + </ul> + </td> + </tr> + <tr> + <td>部落格作者清單</td> + <td><code>/blog/bloggers/</code></td> + <td> + <p>系統內的部落格作者清單。</p> + + <ul> + <li>任何使用者都可以從側邊選單進入此頁。</li> + <li>作者名字連結至該作者的詳細頁面。</li> + </ul> + </td> + </tr> + <tr> + <td>回覆表單頁</td> + <td><code>/blog/<em><blog-id></em>/create</code></td> + <td> + <p>新增回覆於特定文章。</p> + + <ul> + <li>只有登入的使用者可以由文章詳細頁面底部連結進入此頁。</li> + <li>提供能輸入回覆的表單(發布日期和文章標題不可被編輯)。</li> + <li>回覆被發表之後,頁面會轉址回該文章詳細頁。</li> + <li>使用者無法修改或是刪除他發表的回覆。</li> + <li>未登入的使用者會先被導至登入頁,登入之後才能發表回覆。一旦登入之後,他們便會被導至他們想發表回覆的文章頁。</li> + <li>回覆表單頁必須包含該文章的標題與連結。</li> + </ul> + </td> + </tr> + <tr> + <td>使用者身分認證頁</td> + <td><code>/accounts/<em><standard urls></em></code></td> + <td> + <p>標準的Django身分驗證頁面,用來登入、登出及修改密碼。</p> + + <ul> + <li>使用者能從側欄連結進入登入/登出頁面。</li> + </ul> + </td> + </tr> + <tr> + <td>管理者網頁</td> + <td><code>/admin/<em><standard urls></em></code></td> + <td> + <p>管理者網頁必須能新增/編輯/刪除部落格文章、作者及回覆。</p> + + <ul> + <li>管理者網頁的每筆文章記錄必須一併於其底下陳列出相關的回覆。</li> + <li>管理者網頁的每一筆回覆都要以75字的回覆內容作為顯示名稱。</li> + <li>其餘的紀錄使用基本的註冊即可。</li> + </ul> + </td> + </tr> + </tbody> +</table> + +<p>另外您應該要寫一些基本的測試來驗證:</p> + +<ul> + <li>所有的模型欄位都有正確的標示和長度。</li> + <li>所有的模型都有期望的物件名稱(例如<code> __str__()</code> 回傳期望的值)。</li> + <li>模型有期望的URL給每篇文章與回覆。(例如<code>get_absolute_url()</code> 回傳期望的URL)。</li> + <li>BlogListView (所有文章的頁面) 可以從期望的位址進入(例如/blog/blogs)。</li> + <li>BlogListView (所有文章的頁面) 可以從期望的位址名稱進入(例如'blogs')。</li> + <li>BlogListView (所有文章的頁面) 使用期望的模板(例如預設值)。</li> + <li>BlogListView 以每頁5筆項目分頁(至少第一頁是如此)。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 當然你也可以跑很多其他的測試。但是我們會希望您至少實作以上列出的測試項目。</p> +</div> + +<p>下一區塊顯示符合以上需求的網頁<a href="#Screenshots">截圖</a>。</p> + +<h2 id="截圖">截圖</h2> + +<p>The following screenshot provide an example of what the finished program should output.</p> + +<h3 id="列出所有的部落格文章">列出所有的部落格文章</h3> + +<p>這個頁面會列出所有部落格內的文章(可以從側邊選單的“所有文章”連結進入)。<br> + 幾項提醒:</p> + +<ul> + <li>側邊選單也要列出目前登入的使用者。</li> + <li>每篇文章與部落客都能透過連結的方式進入。</li> + <li>必須要有分頁(每頁5筆資料)。</li> + <li>文章排列順序由最新至最舊。</li> +</ul> + +<p><img alt="List of all blogs" src="https://mdn.mozillademos.org/files/14319/diyblog_allblogs.png" style="border-style: solid; border-width: 1px; display: block; height: 363px; margin: 0px auto; width: 986px;"></p> + +<h3 id="列出所有部落客(文章作者)">列出所有部落客(文章作者)</h3> + +<p>可以由側邊選單的“所有部落客”進入此頁面,並於頁面上提供連結至每一位部落客。<br> + 從截圖可以發現到,並沒有任何一位使用者登入。</p> + +<p><img alt="List of all bloggers" src="https://mdn.mozillademos.org/files/14321/diyblog_blog_allbloggers.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 493px;"></p> + +<h3 id="部落格詳細頁">部落格詳細頁</h3> + +<p>顯示某篇特定部落格文章的詳細內容。</p> + +<p><img alt="Blog detail with add comment link" src="https://mdn.mozillademos.org/files/14323/diyblog_blog_detail_add_comment.png" style="border-style: solid; border-width: 1px; display: block; height: 640px; margin: 0px auto; width: 986px;"></p> + +<p>請注意每個評論都有日期與時間,並且由最後至最新排列(與部落格文章相反)。<br> + 我們可以看見最底下有個連結連到新增評論的表單。當使用者沒有登入時,我們改以要求登入的連結代替。</p> + +<p><img alt="Comment link when not logged in" src="https://mdn.mozillademos.org/files/14325/diyblog_blog_detail_not_logged_in.png" style="border-style: solid; border-width: 1px; display: block; height: 129px; margin: 0px auto; width: 646px;"></p> + +<h3 id="新增評論表單">新增評論表單</h3> + +<p>這張表單用來新增評論,且使用者必須是登入狀態。當表單送出成功之後,我們必須回到相對應的部落格文章內容頁。</p> + +<p><img alt="Add comment form" src="https://mdn.mozillademos.org/files/14329/diyblog_comment_form.png" style="border-style: solid; border-width: 1px; display: block; height: 385px; margin: 0px auto; width: 778px;"></p> + +<h3 id="作者資料">作者資料</h3> + +<p>這頁顯示部落客的介紹資料以及列出他們所發表的部落格文章。</p> + +<p><img alt="Blogger detail page" src="https://mdn.mozillademos.org/files/14327/diyblog_blogger_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 379px; margin: 0px auto; width: 982px;"></p> + +<h2 id="一步一腳印Steps_to_complete">一步一腳印Steps to complete</h2> + +<p>以下說明實作的步驟。</p> + +<ol> + <li>建立一個此網站的專案及app骨架(可以參考<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教學2 : 建立一個網站骨架</a>)。你也許會用'diyblog'作為專案名稱,‘blog'作為app的名稱。</li> + <li>建立部落格文章、評論與其他任何所需物件的模型。當你在思考怎麼設計的時候,請記得: + <ul> + <li>每一個評論都只屬於一篇部落格文章,但每一個部落格文章可以有很多筆評論。</li> + <li>部落格文章必須要依照發布時間排序(新至舊),評論要依照發布排序(舊至新)。</li> + <li>不是每位使用者都是部落客,但是每一位使用者都可以留下評論。</li> + <li>部落客必須有介紹資訊。</li> + </ul> + </li> + <li>跑migrations以及創建一個新的超級使用者(superuser)。</li> + <li>透過admin網站新稱一些部落格文章和評論。</li> + <li>幫部落格文章列表頁與部落客列表頁建立視圖、模板及設定URL。</li> + <li>幫部落格文章詳細頁與部落客詳細頁建立視圖、模板及設定URL。</li> + <li>建立一個頁面包含可以新增評論的表單(記得只有已登入的使用者可以進入此頁!)</li> +</ol> + +<h2 id="提示與小技巧">提示與小技巧</h2> + +<p>This project is very similar to the <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> tutorial. You will be able to set up the skeleton, user login/logout behaviour, support for static files, views, URLs, forms, base templates and admin site configuration using almost all the same approaches.</p> + +<p>Some general hints:</p> + +<ol> + <li>The index page can be implemented as a basic function view and template (just like for the locallibrary).</li> + <li>The list view for blog posts and bloggers, and the detail view for blog posts can be created using the <a href="/en-US/docs/Learn/Server-side/Django/Generic_views">generic list and detail views</a>.</li> + <li>The list of blog posts for a particular author can be created by using a generic list Blog list view and filtering for blog object that match the specified author. + <ul> + <li>You will have to implement <code>get_queryset(self)</code> to do the filtering (much like in our library class <code>LoanedBooksAllListView</code>) and get the author information from the URL.</li> + <li>You will also need to pass the name of the author to the page in the context. To do this in a class-based view you need to implement <code>get_context_data()</code> (discussed below).</li> + </ul> + </li> + <li>The <em>add comment</em> form can be created using a function-based view (and associated model and form) or using a generic <code>CreateView</code>. If you use a <code>CreateView</code> (recommended) then: + <ul> + <li>You will also need to pass the name of the blog post to the comment page in the context (implement <code>get_context_data()</code> as discussed below).</li> + <li>The form should only display the comment "description" for user entry (date and associated blog post should not be editable). Since they won't be in the form itself, your code will need to set the comment's author in the<code> form_valid()</code> function so it can be saved into the model (<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user">as described here</a> — Django docs). In that same function we set the associated blog. A possible implementation is shown below (<code>pk</code> is a blog id passed in from the URL/URL configuration). + <pre class="brush: python"> def form_valid(self, form): + """ + Add author and associated blog to form data before setting it as valid (so it is saved to model) + """ + #Add logged-in user as author of comment + form.instance.author = self.request.user + #Associate comment with blog based on passed id + form.instance.blog=get_object_or_404(Blog, pk = self.kwargs['pk']) + # Call super-class form validation behaviour + return super(BlogCommentCreate, self).form_valid(form) +</pre> + </li> + <li>You will need to provide a success URL to redirect to after the form validates; this should be the original blog. To do this you will need to override <code>get_success_url()</code> and "reverse" the URL for the original blog. You can get the required blog ID using the <code>self.kwargs</code> attribute, as shown in the <code>form_valid()</code> method above.</li> + </ul> + </li> +</ol> + +<p>We briefly talked about passing a context to the template in a class-based view in the <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views#Overriding_methods_in_class-based_views">Django Tutorial Part 6: Generic list and detail views</a> topic. To do this you need to override <code>get_context_data()</code> (first getting the existing context, updating it with whatever additional variables you want to pass to the template, and then returning the updated context). For example, the code fragment below shows how you can add a blogger object to the context based on their <code>BlogAuthor</code> id.</p> + +<pre class="brush: python">class SomeView(generic.ListView): + ... + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(SomeView, self).get_context_data(**kwargs) + # Get the blogger object from the "pk" URL parameter and add it to the context + context['blogger'] = get_object_or_404(BlogAuthor, pk = self.kwargs['pk']) + return context +</pre> + +<h2 id="Assessment">Assessment</h2> + +<p>The assessment for this task is <a href="https://github.com/mdn/django-diy-blog/blob/master/MarkingGuide.md">available on Github here</a>. This assessment is primarily based on how well your application meets the requirements we listed above, though there are some parts of the assessment that check your code uses appropriate models, and that you have written at least some test code. When you're done, you can check out our <a href="https://github.com/mdn/django-diy-blog">the finished example</a> which reflects a "full marks" project.</p> + +<p>Once you've completed this module you've also finished all the MDN content for learning basic Django server-side website programming! We hope you enjoyed this module and feel you have a good grasp of the basics!</p> + +<p>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/forms/index.html b/files/zh-tw/learn/server-side/django/forms/index.html new file mode 100644 index 0000000000..a4553d2d73 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/forms/index.html @@ -0,0 +1,661 @@ +--- +title: 'Django Tutorial Part 9: Working with forms' +slug: Learn/Server-side/Django/Forms +translation_of: Learn/Server-side/Django/Forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</div> + +<p class="summary">在本教程中,我們將向您展示,如何在 Django 中使用 HTML 表單,特別是編寫表單以創建,更新和刪除模型實例的最簡單方法。作為本演示的一部分,我們將擴展 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary </a>網站,以便圖書館員,可以使用我們自己的表單(而不是使用管理員應用程序)更新圖書,創建,更新和刪除作者。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"> 前提:</th> + <td>完成先前所有的教程, 包含 <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions">Django Tutorial Part 8: User authentication and permissions</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解如何製作表單來向用戶取得資訊並更新資料庫。了解<strong>通用類別表單編輯視圖 </strong>( generic class-based form editing views ) 能夠大幅簡化用於單一模型的表單製作。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML表單</a>是網頁上的一組一個或多個字段/小組件,可用於從用戶收集信息以提交到服務器。 表單是一種用於收集用戶輸入的靈活機制,因為有合適的小部件可以輸入許多不同類型的數據,包括文本框,複選框,單選按鈕,日期選擇器等。表單也是與服務器共享數據的相對安全的方式, 因為它們允許我們在具有跨站點請求偽造保護的<code>POST</code> 請求中發送數據。</p> + +<p>儘管到目前為止,本教程中尚未創建任何表單,但我們已經在Django Admin網站中遇到過這些表單-例如,下面的屏幕截圖顯示了一種用於編輯我們的<a href="/en-US/docs/Learn/Server-side/Django/Models">Book</a> 模型的表單,該表單由許多選擇列表和 文字編輯器。</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>使用表單可能會很複雜!開發人員需要為表單編寫HTML,在服務器上(也可能在瀏覽器中)驗證並正確清理輸入的數據,使用錯誤消息重新發布表單以通知用戶任何無效字段,並在成功提交數據後處理數據,最後以某種方式回應用戶以表示成功。 Django表單通過提供一個框架使您能夠以編程方式定義表單及其字段,然後使用這些對像生成表單HTML代碼並處理許多驗證和用戶交互,從而完成了所有這些步驟中的大量工作。</p> + +<p>在本教程中,我們將向您展示創建和使用表單的幾種方法,尤其是通用編輯表單視圖如何顯著減少創建表單來操縱表單所需的工作量。楷模。在此過程中,我們將擴展本地圖書館應用程序,方法是添加一個允許圖書館員續訂圖書的表格,並創建頁面以創建,編輯和刪除圖書和作者(複製上面顯示的表格的基本版本以編輯圖書) )。</p> + +<h2 id="HTML_表單">HTML 表單</h2> + +<p>首先簡要介紹一下 <a href="/en-US/docs/Learn/HTML/Forms">HTML Forms</a>。 考慮一個簡單的 HTML 表單,其中有一個用於輸入某些“團隊”名稱的文本字段及其相關標籤:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>表單在HTML中定義為 <code><form>...</form></code> 標記內元素的集合,其中至少包含<code>type="submit"</code>.的<code>input</code>元素。</p> + +<pre class="brush: html notranslate"><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></pre> + +<p>雖然這裡只有一個用於輸入團隊名稱的文本字段,但是表單可以具有任意數量的其他輸入元素及其關聯的標籤。字段的<code>type</code> 屬性定義將顯示哪種小部件。字段的 <code>name</code> 和<code>id</code> 用於標識JavaScript / CSS / HTML中的字段,而 <code>value</code>定義該字段在首次顯示時的初始值。匹配的團隊標籤是使用<code style="font-style: normal; font-weight: normal;">label</code> 標籤指定的(請參見上面的“輸入名稱”),其中的 <code style="font-style: normal; font-weight: normal;">for</code> 字段包含相關<code style="font-style: normal; font-weight: normal;">input</code>的<code style="font-style: normal; font-weight: normal;">id</code> 值。</p> + +<p><code>submit</code> 輸入將顯示為一個按鈕(默認情況下),用戶可以按下該按鈕以將表單中所有其他輸入元素中的數據上載到服務器(在這種情況下,僅是<code>team_name</code>)。表單屬性定義用於發送數據的HTTP<code>method</code> 以及服務器上數據的目的地(<code>action</code>):<br> + </p> + +<ul> + <li><code>action</code>: 提交表單時,將數據發送到該資源/ URL進行處理。如果未設置(或設置為空字符串),則表單將被提交回當前頁面URL。</li> + <li><code>method</code>: 用於發送數據的HTTP方法:post或get。 + <ul> + <li>如果數據將導致服務器數據庫的更改,則應始終使用<code>POST</code> 方法,因為這樣可以使它更能抵抗跨站點的偽造請求攻擊。</li> + <li><code>GET</code> 方法應僅用於不更改用戶數據的表單(例如搜索表單)。建議您在希望添加書籤或共享URL時使用。</li> + </ul> + </li> +</ul> + +<p>服務器的角色是首先呈現初始表單狀態-包含空白字段,或預填充初始值。用戶按下“提交”按鈕後,服務器將從Web瀏覽器接收帶有值的表單數據,並且必須驗證信息。如果表單包含無效數據,則服務器應再次顯示該表單,這一次將在“有效”字段中顯示用戶輸入的數據,並顯示描述無效字段問題的消息。服務器收到包含所有有效表單數據的請求後,便可以執行適當的操作(例如,保存數據,返回搜索結果,上傳文件等),然後通知用戶。</p> + +<p>可以想像,創建HTML,驗證返回的數據,在需要時使用錯誤報告重新顯示輸入的數據以及對有效數據執行所需的操作都需要花費大量精力才能“正確”。 Django通過刪除一些繁瑣且重複的代碼,使此操作變得更加容易!</p> + +<h2 id="Django表單處理流程">Django表單處理流程</h2> + +<p>Django的表單處理使用了我們在以前的教程中學到的所有相同技術(用於顯示有關模型的信息):視圖獲取請求,執行所需的任何操作,包括從模型中讀取數據,然後生成並返回HTML頁面( 從模板中,我們傳遞一個包含要顯示的數據的上下文)。 使事情變得更加複雜的是,服務器還需要能夠處理用戶提供的數據,並在出現任何錯誤時重新顯示頁面。</p> + +<p>下面顯示了Django處理表單請求的過程流程圖,該流程圖從對包含表單的頁面的請求(以綠色顯示)開始。<br> + <img alt="Updated form handling process doc." src="https://mdn.mozillademos.org/files/14205/Form%20Handling%20-%20Standard.png" style="display: block; height: 569px; margin: 0px auto; width: 800px;"></p> + +<p>根據上圖,Django表單處理的主要功能是:</p> + +<ol> + <li>在用戶第一次請求時顯示默認表單。 + <ul> + <li>該表單可能包含空白字段(例如,如果您正在創建新記錄),或者可能會預先填充有初始值(例如,如果您正在更改記錄或具有有用的默認初始值)。</li> + <li>由於此表單與任何用戶輸入的數據均不相關(儘管它可能具有初始值),因此在這一點上被稱為未綁定。</li> + </ul> + </li> + <li>從提交請求中接收數據並將其綁定到表單。 + <ul> + <li>將數據綁定到表單意味著當我們需要重新顯示表單時,用戶輸入的數據和任何錯誤均可用。</li> + </ul> + </li> + <li>清理並驗證數據。 + <ul> + <li>清理數據會對輸入執行清理操作(例如,刪除可能用於向服務器發送惡意內容的無效字符),並將其轉換為一致的Python類型。</li> + <li>驗證會檢查該值是否適合該字段(例如,日期範圍正確,時間不要太短或太長等)</li> + </ul> + </li> + <li>如果任何數據無效,則這次重新顯示該表單,其中包含用戶填充的所有值和問題字段的錯誤消息。</li> + <li>如果所有數據均有效,請執行所需的操作(例如,保存數據,發送和發送電子郵件,返回搜索結果,上傳文件等)</li> + <li>完成所有操作後,將用戶重定向到另一個頁面。</li> +</ol> + +<p>Django提供了許多工具和方法來幫助您完成上述任務。 最基本的是 <code>Form</code>類,它簡化了表單HTML的生成和數據清除/驗證的過程。 在下一節中,我們將使用頁面的實際示例描述表單如何工作,以使圖書館員可以續訂書籍。</p> + +<div class="note"> +<p><strong>注意:</strong> 當我們討論Django的更多“高級”表單框架類時,了解<code>Form</code>的使用方式將對您有所幫助。</p> +</div> + +<h2 id="使用表單和功能視圖續訂表單">使用表單和功能視圖續訂表單</h2> + +<p>接下來,我們將添加一個頁面,以使圖書館員可以續借借來的書。 為此,我們將創建一個允許用戶輸入日期值的表單。 我們將從當前日期(正常藉閱期)起3週內為該字段提供初始值,並添加一些驗證以確保館員不能輸入過去的日期或將來的日期。 輸入有效日期後,我們會將其寫入當前記錄的<code>BookInstance.due_back</code> 字段中。</p> + +<p>該示例將使用基於函數的視圖和<code>Form</code> 類。 以下各節說明表單的工作方式,以及您需要對正在進行的LocalLibrary項目進行的更改。</p> + +<h3 id="Form">Form</h3> + +<p><code>Form</code>類是Django表單處理系統的核心。 它指定表單中的字段,其佈局,顯示小部件,標籤,初始值,有效值,以及(一旦驗證)與無效字段關聯的錯誤消息。 該類還提供了使用預定義格式(表,列表等)在模板中呈現自身的方法,或用於獲取任何元素的值(啟用細粒度手動呈現)的方法。</p> + +<h4 id="申報表格">申報表格</h4> + +<p><code>Form</code> 的聲明語法與聲明<code>Model</code>的語法非常相似,並且具有相同的字段類型(和一些相似的參數)。 這是有道理的,因為在兩種情況下,我們都需要確保每個字段都處理正確的數據類型,被限制為有效數據並具有顯示/文檔描述。</p> + +<p>要創建一個表單,我們導入<code>Form</code> 庫,從<code>Form</code> 類派生,並聲明表單的字段。 下面顯示了我們的圖書館圖書續訂表格的一個非常基本的表格類:</p> + +<pre class="brush: python notranslate">from django import forms + +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") +</pre> + +<h4 id="Form_fields">Form fields</h4> + +<p>In this case we have a single <code><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datefield">DateField</a></code> for entering the renewal date that will render in HTML with a blank value, the default label "<em>Renewal date:</em>", and some helpful usage text: "<em>Enter a date between now and 4 weeks (default 3 weeks).</em>" As none of the other optional arguments are specified the field will accept dates using the <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.DateField.input_formats">input_formats</a>: 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 <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: <a href="https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#django.forms.DateInput">DateInput</a>.</p> + +<p>There are many other types of form fields, which you will largely recognise from their similarity to the equivalent model field classes: <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#booleanfield"><code>BooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#charfield"><code>CharField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#choicefield"><code>ChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedchoicefield"><code>TypedChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datefield"><code>DateField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datetimefield"><code>DateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#decimalfield"><code>DecimalField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#durationfield"><code>DurationField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#emailfield"><code>EmailField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filefield"><code>FileField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filepathfield"><code>FilePathField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#floatfield"><code>FloatField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#imagefield"><code>ImageField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#integerfield"><code>IntegerField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#genericipaddressfield"><code>GenericIPAddressField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multiplechoicefield"><code>MultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedmultiplechoicefield"><code>TypedMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#nullbooleanfield"><code>NullBooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#regexfield"><code>RegexField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#slugfield"><code>SlugField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#timefield"><code>TimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#urlfield"><code>URLField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#uuidfield"><code>UUIDField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#combofield"><code>ComboField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multivaluefield"><code>MultiValueField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#splitdatetimefield"><code>SplitDateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelmultiplechoicefield"><code>ModelMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelchoicefield"><code>ModelChoiceField</code></a>.</p> + +<p>The arguments that are common to most fields are listed below (these have sensible default values):</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#required">required</a>: If <code>True</code>, the field may not be left blank or given a <code>None</code> value. Fields are required by default, so you would set <code>required=False</code> to allow blank values in the form.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a>: The label to use when rendering the field in HTML. If <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a> is not specified then Django would create one from the field name by capitalising the first letter and replacing underscores with spaces (e.g. <em>Renewal date</em>).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label-suffix">label_suffix</a>: By default a colon is displayed after the label (e.g. Renewal date<strong>:</strong>). This argument allows you to specify a different suffix containing other character(s).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#initial">initial</a>: The initial value for the field when the form is displayed.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: The display widget to use.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#help-text">help_text</a> (as seen in the example above): Additional text that can be displayed in forms to explain how to use the field.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#error-messages">error_messages</a>: A list of error messages for the field. You can override these with your own messages if needed.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#validators">validators</a>: A list of functions that will be called on the field when it is validated.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#localize">localize</a>: Enables the localisation of form data input (see link for more information).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled">disabled</a>: The field is displayed but its value cannot be edited if this is <code>True</code>. The default is <code>False</code>.</li> +</ul> + +<h4 id="Validation">Validation</h4> + +<p>Django provides numerous places where you can validate your data. The easiest way to validate a single field is to override the method <code>clean_<strong><fieldname></strong>()</code> for the field you want to check. So for example, we can validate that entered <code>renewal_date</code> values are between now and 4 weeks by implementing <code>clean_<strong>renewal_date</strong>() </code>as shown below.</p> + +<pre class="brush: python notranslate">from django import forms + +<strong>from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +import datetime #for checking renewal date range. +</strong> +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + +<strong> 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</strong></pre> + +<p>There are two important things to note. The first is that we get our data using <code>self.cleaned_data['renewal_date']</code> 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 <code>datetime.datetime</code> object).</p> + +<p>The second point is that if a value falls outside our range we raise a <code>ValidationError</code>, 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 <a href="https://docs.djangoproject.com/en/2.0/topics/i18n/translation/">Django's translation functions</a> <code>ugettext_lazy()</code> (imported as <code>_()</code>), which is good practice if you want to translate your site later.</p> + +<div class="note"> +<p><strong>Note:</strong> There are numerious other methods and examples for validating forms in <a href="https://docs.djangoproject.com/en/2.0/ref/forms/validation/">Form and field validation</a> (Django docs). For example, in cases where you have multiple fields that depend on each other, you can override the <a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.clean">Form.clean()</a> function and again raise a <code>ValidationError</code>.</p> +</div> + +<p>That's all we need for the form in this example!</p> + +<h4 id="Copy_the_Form">Copy the Form</h4> + +<p>Create and open the file <strong>locallibrary/catalog/forms.py</strong> and copy the entire code listing from the previous block into it.</p> + +<h3 id="URL_Configuration">URL Configuration</h3> + +<p>Before we create our view, let's add a URL configuration for the <em>renew-books</em> page. Copy the following configuration to the bottom of <strong>locallibrary/catalog/urls.py</strong>.</p> + +<pre class="brush: python notranslate">urlpatterns += [ + path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'), +]</pre> + +<p>The URL configuration will redirect URLs with the format <strong>/catalog/book/<em><bookinstance id></em>/renew/</strong> to the function named <code>renew_book_librarian()</code> in <strong>views.py</strong>, and send the <code>BookInstance</code> id as the parameter named <code>pk</code>. The pattern only matches if <code>pk</code> is a correctly formatted <code>uuid</code>.</p> + +<div class="note"> +<p><strong>Note</strong>: We can name our captured URL data "<code>pk</code>" 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 <code>pk</code>, short for "primary key", is a reasonable convention to use!</p> +</div> + +<h3 id="View">View</h3> + +<p>As discussed in the <a href="#django_form_handling_process">Django form handling process</a> 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. </p> + +<p>For forms that use a <code>POST</code> request to submit information to the server, the most common pattern is for the view to test against the <code>POST</code> request type (<code>if request.method == 'POST':</code>) to identify form validation requests and <code>GET</code> (using an <code>else</code> condition) to identify the initial form creation request. If you want to submit your data using a <code>GET</code> 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).</p> + +<p>The book renewal process will be writing to our database, so by convention we use the <code>POST</code> request approach. The code fragment below shows the (very standard) pattern for this sort of function view. </p> + +<pre class="brush: python notranslate">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 +<strong> if request.method == 'POST':</strong> + + # Create a form instance and populate it with data from the request (binding): + form = RenewBookForm(request.POST) + + # Check if the form is valid: + <strong>if form.is_valid():</strong> + # 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. +<strong> else:</strong> + 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})</pre> + +<p>First we import our form (<code>RenewBookForm</code>) and a number of other useful objects/methods used in the body of the view function:</p> + +<ul> + <li><code><a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#get-object-or-404">get_object_or_404()</a></code>: Returns a specified object from a model based on its primary key value, and raises an <code>Http404</code> exception (not found) if the record does not exist. </li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.HttpResponseRedirect">HttpResponseRedirect</a></code>: This creates a redirect to a specified URL (HTTP status code 302). </li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#django.urls.reverse">reverse()</a></code>: This generates a URL from a URL configuration name and a set of arguments. It is the Python equivalent of the <code>url</code> tag that we've been using in our templates.</li> + <li><code><a href="https://docs.python.org/3/library/datetime.html">datetime</a></code>: A Python library for manipulating dates and times. </li> +</ul> + +<p>In the view we first use the <code>pk</code> argument in <code>get_object_or_404()</code> to get the current <code>BookInstance</code> (if this does not exist, the view will immediately exit and the page will display a "not found" error). If this is <em>not </em>a <code>POST</code> request (handled by the <code>else</code> clause) then we create the default form passing in an <code>initial</code> value for the <code>renewal_date</code> field (as shown in bold below, this is 3 weeks from the current date). </p> + +<pre class="brush: python notranslate"> book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a GET (or any other method) create the default form + <strong>else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(<strong>weeks=3</strong>) + <strong>form = RenewBookForm(initial={'</strong>renewal_date<strong>': </strong>proposed_renewal_date<strong>,})</strong> + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>After creating the form, we call <code>render()</code> to create the HTML page, specifying the template and a context that contains our form. In this case the context also contains our <code>BookInstance</code>, which we'll use in the template to provide information about the book we're renewing.</p> + +<p>If however this is a <code>POST</code> request, then we create our <code>form</code> 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 <code>clean_renewal_date()</code> function to check the date is in the right range. </p> + +<pre class="brush: python notranslate"> 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): +<strong> form = RenewBookForm(request.POST)</strong> + + # 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})</pre> + +<p>If the form is not valid we call <code>render()</code> again, but this time the form value passed in the context will include error messages. </p> + +<p>If the form is valid, then we can start to use the data, accessing it through the <code>form.cleaned_data</code> attribute (e.g. <code>data = form.cleaned_data['renewal_date']</code>). Here we just save the data into the <code>due_back</code> value of the associated <code>BookInstance</code> object.</p> + +<div class="warning"> +<p><strong>Important</strong>: While you can also access the form data directly through the request (for example <code>request.POST['renewal_date']</code> or <code>request.GET['renewal_date']</code> (if using a GET request) this is NOT recommended. The cleaned data is sanitised, validated, and converted into Python-friendly types.</p> +</div> + +<p>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 <code>HttpResponseRedirect</code> and <code>reverse()</code> to redirect to the view named <code>'all-borrowed'</code> (this was created as the "challenge" in <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Django Tutorial Part 8: User authentication and permissions</a>). If you didn't create that page consider redirecting to the home page at URL '/').</p> + +<p>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 <code>BookInstance</code> ("<code>can_renew</code>"), but to keep things simple here we just use the <code>@permission_required</code> function decorator with our existing <code>can_mark_returned</code> permission.</p> + +<p>The final view is therefore as shown below. Please copy this into the bottom of <strong>locallibrary/catalog/views.py</strong>.</p> + +<pre class="notranslate"><strong>from django.contrib.auth.decorators import permission_required</strong> + +from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse +import datetime + +from .forms import RenewBookForm + +<strong>@permission_required('catalog.<code>can_mark_returned</code>')</strong> +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}) +</pre> + +<h3 id="The_template">The template</h3> + +<p>Create the template referenced in the view (<strong>/catalog/templates/catalog/book_renew_librarian.html</strong>) and copy the code below into it:</p> + +<pre class="brush: html notranslate">{% 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> + +<strong> <form action="" method="post"> + {% csrf_token %} + <table> + \{{ form }} + </table> + <input type="submit" value="Submit" /> + </form></strong> + +{% endblock %}</pre> + +<p>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 <code>\{{bookinst}}</code> (and its variables) because it was passed into the context object in the <code>render()</code> function, and we use these to list the book title, borrower and the original due date.</p> + +<p>The form code is relatively simple. First we declare the <code>form</code> tags, specifying where the form is to be submitted (<code>action</code>) and the <code>method</code> for submitting the data (in this case an "HTTP POST") — if you recall the <a href="#HTML_forms">HTML Forms</a> overview at the top of the page, an empty <code>action</code> 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 <code>submit</code> input, which a user can press to submit the data. The <code>{% csrf_token %}</code> added just inside the form tags is part of Django's cross-site forgery protection.</p> + +<div class="note"> +<p><strong>Note:</strong> Add the <code>{% csrf_token %}</code> to every Django template you create that uses <code>POST</code> to submit data. This will reduce the chance of forms being hijacked by malicious users.</p> +</div> + +<p>All that's left is the <code>\{{form}}</code> 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:</p> + +<pre class="brush: html notranslate"><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> +</pre> + +<div class="note"> +<p><strong>Note:</strong> 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 <code>table </code>tags above). This same rendering is provided if you reference the template variable <code>\{{ form.as_table }}</code>.</p> +</div> + +<p>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).</p> + +<pre class="brush: html notranslate"><tr> + <th><label for="id_renewal_date">Renewal date:</label></th> + <td> +<strong> <ul class="errorlist"> + <li>Invalid date - renewal in past</li> + </ul></strong> + <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></pre> + +<h4 id="Other_ways_of_using_form_template_variable">Other ways of using form template variable</h4> + +<p>Using <code>\{{form}}</code> as shown above, each field is rendered as a table row. You can also render each field as a list item (using <code>\{{form.as_ul}}</code> ) or as a paragraph (using <code>\{{form.as_p}}</code>).</p> + +<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 <code>renewal_date</code> field:</p> + +<ul> + <li><code>\{{form.renewal_date}}:</code> The whole field.</li> + <li><code>\{{form.renewal_date.errors}}</code>: The list of errors.</li> + <li><code>\{{form.renewal_date.id_for_label}}</code>: The id of the label.</li> + <li><code>\{{form.renewal_date.help_text}}</code>: The field help text.</li> + <li>etc!</li> +</ul> + +<p>For more examples of how to manually render forms in templates and dynamically loop over template fields, see <a href="https://docs.djangoproject.com/en/2.0/topics/forms/#rendering-fields-manually">Working with forms > Rendering fields manually</a> (Django docs).</p> + +<h3 id="Testing_the_page">Testing the page</h3> + +<p>If you accepted the "challenge" in <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Django Tutorial Part 8: User authentication and permissions</a> 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.</p> + +<pre class="brush: html notranslate">{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% endif %}</pre> + +<div class="note"> +<p><strong>Note</strong>: Remember that your test login will need to have the permission "<code>catalog.can_mark_returned</code>" in order to access the renew book page (perhaps use your superuser account).</p> +</div> + +<p>You can alternatively manually construct a test URL like this — <a href="http://127.0.0.1:8000/catalog/book/<bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em><bookinstance_id></em>/renew/</a> (a valid bookinstance id can be obtained by navigating to a book detail page in your library, and copying the <code>id</code> field).</p> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>If you are successful, the default form will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14209/forms_example_renew_default.png" style="border-style: solid; border-width: 1px; display: block; height: 292px; margin: 0px auto; width: 680px;"></p> + +<p>The form with an invalid value entered, will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14211/forms_example_renew_invalid.png" style="border-style: solid; border-width: 1px; display: block; height: 290px; margin: 0px auto; width: 658px;"></p> + +<p>The list of all books with renew links will look like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14207/forms_example_renew_allbooks.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 613px;"></p> + +<h2 id="ModelForms">ModelForms</h2> + +<p>Creating a <code>Form</code> 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.</p> + +<p>However if you just need a form to map the fields of a <em>single</em> 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 <a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">ModelForm</a> helper class to create the form from your model. This <code>ModelForm</code> can then be used within your views in exactly the same way as an ordinary <code>Form</code>.</p> + +<p>A basic <code>ModelForm</code> containing the same field as our original <code>RenewBookForm</code> is shown below. All you need to do to create the form is add <code>class Meta</code> with the associated <code>model</code> (<code>BookInstance</code>) and a list of the model <code>fields</code> to include in the form (you can include all fields using <code>fields = '__all__'</code>, or you can use <code>exclude</code> (instead of <code>fields</code>) to specify the fields <em>not </em>to include from the model).</p> + +<pre class="brush: python notranslate">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> class Meta: + model = BookInstance + fields = ['due_back',]</strong> +</pre> + +<div class="note"> +<p><strong>Note</strong>: This might not look like all that much simpler than just using a <code>Form</code> (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!</p> +</div> + +<p>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 <code>class Meta</code>, 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 "<em>Renewal date</em>" (rather than the default based on the field name: <em>Due date</em>), and we also want our help text to be specific to this use case. The <code>Meta</code> below shows you how to override these fields, and you can similarly set <code>widgets</code> and <code>error_messages</code> if the defaults aren't sufficient.</p> + +<pre class="brush: python notranslate">class Meta: + model = BookInstance + fields = ['due_back',] +<strong> labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } </strong> +</pre> + +<p>To add validation you can use the same approach as for a normal <code>Form</code> — you define a function named <code>clean_<em>field_name</em>()</code> and raise <code>ValidationError</code> exceptions for invalid values. The only difference with respect to our original form is that the model field is named <code>due_back</code> and not "<code>renewal_date</code>".</p> + +<pre class="brush: python notranslate">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> 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 +</strong> + 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).'), } +</pre> + +<p>The class <code>RenewBookModelForm</code> below is now functionally equivalent to our original <code>RenewBookForm</code>. You could import and use it wherever you currently use <code>RenewBookForm</code>.</p> + +<h2 id="Generic_editing_views">Generic editing views</h2> + +<p>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 <a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/">generic editing views</a> 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 <code>ModelForm</code>) for you from the model.</p> + +<div class="note"> +<p><strong>Note: </strong>In addition to the editing views described here, there is also a <a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/#formview">FormView</a> class, which lies somewhere between our function view and the other generic views in terms of "flexibility" vs "coding effort". Using <code>FormView</code> you still need to create your <code>Form</code>, 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.</p> +</div> + +<p>In this section we're going to use generic editing views to create pages to add functionality to create, edit, and delete <code>Author</code> 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).</p> + +<h3 id="Views">Views</h3> + +<p>Open the views file (<strong>locallibrary/catalog/views.py</strong>) and append the following code block to the bottom of it:</p> + +<pre class="brush: python notranslate">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')</pre> + +<p>As you can see, to create the views you need to derive from <code>CreateView</code>, <code>UpdateView</code>, and <code>DeleteView</code> (respectively) and then define the associated model.</p> + +<p>For the "create" and "update" cases you also need to specify the fields to display in the form (using in same syntax as for <code>ModelForm</code>). 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 <em>field_name</em>/<em>value</em> 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 <code>success_url</code> (as done for the <code>AuthorDelete</code> class).</p> + +<p>The <code>AuthorDelete</code> 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 <code>success_url</code>, because there is no obvious default value for Django to use. In this case we use the <code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#reverse-lazy">reverse_lazy()</a></code> function to redirect to our author list after an author has been deleted — <code>reverse_lazy()</code> is a lazily executed version of <code>reverse()</code>, used here because we're providing a URL to a class-based view attribute.</p> + +<h3 id="Templates">Templates</h3> + +<p>The "create" and "update" views use the same template by default, which will be named after your model: <em>model_name</em><strong>_form.html</strong> (you can change the suffix to something other than <strong>_form</strong> using the <code>template_name_suffix</code> field in your view, e.g. <code>template_name_suffix = '_other_suffix'</code>)</p> + +<p>Create the template file <strong>locallibrary/catalog/templates/catalog/author_form.html</strong> and copy in the text below.</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + +<form action="" method="post"> + {% csrf_token %} + <table> + \{{ form.as_table }} + </table> + <input type="submit" value="Submit" /> + +</form> +{% endblock %}</pre> + +<p>This is similar to our previous forms, and renders the fields using a table. Note also how again we declare the <code>{% csrf_token %}</code> to ensure that our forms are resistant to CSRF attacks.</p> + +<p>The "delete" view expects to find a template named with the format <em>model_name</em><strong>_confirm_delete.html</strong> (again, you can change the suffix using <code>template_name_suffix</code> in your view). Create the template file <strong>locallibrary/catalog/templates/catalog/author_confirm_delete</strong><strong>.html</strong> and copy in the text below.</p> + +<pre class="brush: html notranslate">{% 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" action="" value="Yes, delete." /> +</form> + +{% endblock %} +</pre> + +<h3 id="URL_configurations">URL configurations</h3> + +<p>Open your URL configuration file (<strong>locallibrary/catalog/urls.py</strong>) and add the following configuration to the bottom of the file:</p> + +<pre class="brush: python notranslate">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'), +]</pre> + +<p>There is nothing particularly new here! You can see that the views are classes, and must hence be called via <code>.as_view()</code>, and you should be able to recognise the URL patterns in each case. We must use <code>pk</code> as the name for our captured primary key value, as this is the parameter name expected by the view classes.</p> + +<p>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).</p> + +<div class="note"> +<p><strong>Note</strong>: 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 <code>PermissionRequiredMixin</code> and either create a new permission or reuse our <code>can_mark_returned</code> permission).</p> +</div> + +<h3 id="Testing_the_page_2">Testing the page</h3> + +<p>First login to the site with an account that has whatever permissions you decided are needed to access the author editing pages.</p> + +<p>Then navigate to the author create page: <a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a>, which should look like the screenshot below.</p> + +<p><img alt="Form Example: Create Author" src="https://mdn.mozillademos.org/files/14223/forms_example_create_author.png" style="border-style: solid; border-width: 1px; display: block; height: 184px; margin: 0px auto; width: 645px;"></p> + +<p>Enter values for the fields and then press <strong>Submit</strong> to save the author record. You should now be taken to a detail view for your new author, with a URL of something like <em>http://127.0.0.1:8000/catalog/author/10</em>.</p> + +<p>You can test editing records by appending <em>/update/</em> to the end of the detail view URL (e.g. <em>http://127.0.0.1:8000/catalog/author/10/update/</em>) — we don't show a screenshot, because it looks just like the "create" page!</p> + +<p>Last of all we can delete the page, by appending delete to the end of the author detail-view URL (e.g. <em>http://127.0.0.1:8000/catalog/author/10/delete/</em>). Django should display the delete page shown below. Press <strong>Yes, delete.</strong> to remove the record and be taken to the list of all authors.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14221/forms_example_delete_author.png" style="border-style: solid; border-width: 1px; display: block; height: 194px; margin: 0px auto; width: 561px;"></p> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Create some forms to create, edit and delete <code>Book</code> records. You can use exactly the same structure as for <code>Authors</code>. If your <strong>book_form.html</strong> template is just a copy-renamed version of the <strong>author_form.html</strong> template, then the new "create book" page will look like the screenshot below:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14225/forms_example_create_book.png" style="border-style: solid; border-width: 1px; display: block; height: 521px; margin: 0px auto; width: 595px;"></p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>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 <em>almost all</em> the work to define pages that can create, edit, and delete records associated with a single model instance.</p> + +<p>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.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/">Working with forms</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial04/#write-a-simple-form">Writing your first Django app, part 4 > Writing a simple form</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/">The Forms API</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/">Form fields</a> (Django docs) </li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/validation/">Form and field validation</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/">Form handling with class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">Creating forms from models</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/">Generic editing views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/generic_views/index.html b/files/zh-tw/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..240354cd6b --- /dev/null +++ b/files/zh-tw/learn/server-side/django/generic_views/index.html @@ -0,0 +1,612 @@ +--- +title: 'Django Tutorial Part 6: Generic list and detail views' +slug: Learn/Server-side/Django/Generic_views +translation_of: Learn/Server-side/Django/Generic_views +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">本教程擴充了 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,為書本與作者增加列表與細節頁面。此處我們將學到通用類別視圖,並演示如何降低你必須為一般使用案例撰寫的程式碼數量。我們也會更加深入 URL 處理細節,演示如何實施基本模式匹配。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>To understand where and how to use generic class-based views, and how to extract patterns from URLs and pass the information to views.</td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>本教程中,通過為書本和作者添加列表和詳細信息頁面,我們將完成第一個版本的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站(或者更準確地說,我們將向您展示如何實現書頁,並讓您自己創建作者頁面!) )</p> + +<p>該過程在創建索引頁面,我們在上一個教程中展示了該頁面。我們仍然需要創建URL地圖,視圖和模板。主要區別在於,對於詳細信息頁面,我們還有一個額外的挑戰,即從URL對於這些頁面,我們將演示一種完全不同的視圖類型:基於類別的通用列表和詳細視圖。這些可以顯著減少所需的視圖代碼量,有助於更容易編寫和維護。</p> + +<p>本教程的最後一部分,將演示在使用基於類別的通用列表視圖時,如何對數據進行分頁。</p> + +<h2 id="Book_list_page">Book list page</h2> + +<p>該書將顯示每條記錄的標題和作者,標題是指向相關圖書詳細信息頁面的超鏈接。該頁面將具有與站點中,所有其他頁面相同的結構和導航,因此,我們可以擴展在上一個教程中創建的基本模板 (<strong>base_generic.html</strong>)。</p> + +<h3 id="URL_mapping">URL mapping</h3> + +<p>開啟/catalog/urls.py,並複製加入下面粗體顯示的代碼。就像索引頁面的方式,這個path()函數,定義了一個與URL匹配的模式('books /'),如果URL匹配,將調用視圖函數(views.BookListView.as_view())和一個對應的特定映射的名稱。</p> + +<pre class="brush: python notranslate">urlpatterns = [ + path('', views.index, name='index'), +<strong> </strong>path<strong>('books/', views.BookListView.as_view(), name='books'),</strong> +]</pre> + +<p>正如前一個教程中所討論的,URL必須已經先匹配了/ catalog,因此實際上將為URL調用的視圖是:/ catalog / books /。</p> + +<p>我們將繼承現有的泛型視圖函數,該函數已經完成了我們希望此視圖函數執行的大部分工作,而不是從頭開始編寫自己的函數。對於基於Django類的視圖,我們通過調用類方法as_view(),來訪問適當的視圖函數。由此可以創建類的實例,並確保為HTTP請求正確的處理程序方法。</p> + +<h3 id="View_class-based">View (class-based)</h3> + +<p>我們可以很容易地,將書本列表列表編寫為常規函數(就像我們之前的索引視圖一樣),進入查詢數據庫中的所有書本,然後調用render(),將列表傳遞給指定的模板。然而,我們用另一種方法取代,我們將使用基於類的通用列表視圖(ListView)-一個繼承自現有視圖的類。因為通用視圖,已經實現了我們需要的大部分功能,並且遵循Django最佳實踐,我們將能夠創建更強大的列表視圖,代碼更多,重複次數最多,最終維護所需。</p> + +<p>開啟catalog / views.py,將以下代碼複製到文件的底部:</p> + +<pre class="brush: python notranslate">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>就是這樣!通用view將查詢數據庫,以獲取指定模型(Book)的所有記錄,然後呈現/locallibrary/catalog/templates/catalog/book_list.html的模板(我們將在下面創建)。在模板中,您可以使用所謂的object_list或book_list的模板變量(即通常為“ the_model_name_list”),以訪問書本列表。</p> + +<div class="note"> +<p><strong>Note</strong>: This awkward path for the template location isn't a misprint — the generic views look for templates in <code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code> (<code>catalog/book_list.html</code> in this case) inside the application's <code>/<em>application_name</em>/templates/</code> directory (<code>/catalog/templates/)</code>.</p> +</div> + +<p>您可以添加屬性,以更改上面的某種行為。例如,如果需要使用同一模型的多個視圖,則可以指定另一個模板文件,或者如果book_list對於特定模板用例不直觀,則可能需要使用不同的模板變量名稱。可能最有用的變更,是更改/過濾返回的結果子集-因此,您可能會列出其他用戶閱讀的前5本書,而不是列出所有書本。</p> + +<pre class="brush: python notranslate">class BookListView(generic.ListView): + model = Book + context_object_name = 'my_book_list' # your own name for the list as a template variable + queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war + template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location</pre> + +<h4 id="Overriding_methods_in_class-based_views">Overriding methods in class-based views</h4> + +<p>雖然我們不需要在這裡執行此操作,但您也可以覆寫某些類別方法。</p> + +<p>例如,我們可以覆寫get_queryset()方法,來更改返回的記錄列表。這比單獨設置queryset屬性更靈活,就像我們在前面的代碼片段中進行的那樣(儘管在這案例中沒有太大用處):</p> + +<pre class="brush: python notranslate">class BookListView(generic.ListView): + model = Book + + def get_queryset(self): + return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war +</pre> + +<p>我們還可以重寫<code>get_context_data()</code> 以便將其他上下文變數傳遞給模組 (例如,默認情況下傳遞書籍列表). 下面的片段顯示瞭如何向上下文添加名為"<code>some_data</code>" 的變數(然後它將用作模組變數)</p> + +<pre class="brush: python notranslate">class BookListView(generic.ListView): + model = Book + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(BookListView, self).get_context_data(**kwargs) + # Create any data and add it to the context + context['some_data'] = 'This is just some data' + return context</pre> + +<p>執行此操作時,務必遵循上面使用的模式:</p> + +<ul> + <li>首先從我們的superclass中獲取現有內文。</li> + <li>然後添加新的內文信息。</li> + <li>然後返回新的(更新後)內文。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Check out <a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs) for many more examples of what you can do.</p> +</div> + +<h3 id="Creating_the_List_View_template">Creating the List View template</h3> + +<p>建立HTML及複製以下文字串到<strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> , 這是基於通用類的列表視圖所期望的默認模板文件 (默認在<code>catalog中名稱為Book</code> 的模組).</p> + +<p>通用的views模板跟其他的模板沒有不同 (儘管傳遞給模板的內文/訊息當然可以不同). 與index模板一樣,我們在第一行中擴展了基本模板,然後更替名為 <code>content</code>的區塊。</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + <strong>{% if book_list %}</strong> + <ul> + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>該視圖默認將上下文(書籍列表)作為<code>object_list</code> 和 <code>book_list</code> 別名傳遞;兩者都會起作用.</p> + +<h4 id="Conditional_execution">Conditional execution</h4> + +<p>我們使用 <code><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a></code>, <code>else</code> 和 <code>endif</code> 模組標籤,以檢查<code>book_list</code> 是否已定義並且不為空。 如果 <code>book_list</code> 為空值, 則 <code>else</code> 子句回傳text 說明沒有書可以列出. 如果<code>book_list</code>不是空值, 然後我們遍曆書籍清單。</p> + +<pre class="brush: html notranslate"><strong>{% if book_list %}</strong> + <!-- code here to list the books --> +<strong>{% else %}</strong> + <p>There are no books in the library.</p> +<strong>{% endif %}</strong> +</pre> + +<p>The condition above only checks for one case, but you can test on additional conditions using the <code>elif</code> template tag (e.g. <code>{% elif var2 %}</code> ). For more information about conditional operators see: <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a>, and <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifchanged">ifchanged</a> in <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django Docs).</p> + +<h4 id="For_loops">For loops</h4> + +<p>The template uses the <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for">for</a> and <code>endfor</code> template tags to loop through the book list, as shown below. Each iteration populates the <code>book</code> template variable with information for the current list item.</p> + +<pre class="brush: html notranslate">{% for <strong>book</strong> in book_list %} + <li> <!-- code here get information from each <strong>book</strong> item --> </li> +{% endfor %} +</pre> + +<p>While not used here, within the loop Django will also create other variables that you can use to track the iteration. For example, you can test the <code>forloop.last</code> variable to perform conditional processing the last time that the loop is run.</p> + +<h4 id="Accessing_variables">Accessing variables</h4> + +<p>The code inside the loop creates a list item for each book that shows both the title (as a link to the yet-to-be-created detail view) and the author.</p> + +<pre class="brush: html notranslate"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>We access the <em>fields</em> of the associated book record using the "dot notation" (e.g. <code>book.title</code> and <code>book.author</code>), where the text following the <code>book</code> item is the field name (as defined in the model).</p> + +<p>We can also call <em>functions</em> in the model from within our template — in this case we call <code>Book.get_absolute_url()</code> to get an URL you could use to display the associated detail record. This works provided the function does not have any arguments (there is no way to pass arguments!)</p> + +<div class="note"> +<p><strong>Note</strong>: We have to be a little careful of "side effects" when calling functions in templates. Here we just get a URL to display, but a function can do pretty much anything — we wouldn't want to delete our database (for example) just by rendering our template!</p> +</div> + +<h4 id="Update_the_base_template">Update the base template</h4> + +<p>Open the base template (<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>) and insert <strong>{% url 'books' %} </strong>into the URL link for <strong>All books</strong>, as shown below. This will enable the link in all pages (we can successfully put this in place now that we've created the "books" url mapper).</p> + +<pre class="brush: python notranslate"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="What_does_it_look_like">What does it look like?</h3> + +<p>You won't be able to build book list yet, because we're still missing a dependency — the URL map for the book detail pages, which is needed to create hyperlinks to individual books. We'll show both list and detail views after the next section.</p> + +<h2 id="Book_detail_page">Book detail page</h2> + +<p>The book detail page will display information about a specific book, accessed using the URL <code>catalog/book/<em><id></em></code> (where <code><em><id></em></code> is the primary key for the book). In addition to fields in the <code>Book</code> model (author, summary, ISBN, language, and genre), we'll also list the details of the available copies (<code>BookInstances</code>) including the status, expected return date, imprint, and id. This will allow our readers not just to learn about the book, but also to confirm whether/when it is available.</p> + +<h3 id="URL_mapping_2">URL mapping</h3> + +<p>Open <strong>/catalog/urls.py</strong> and add the '<strong>book-detail</strong>' URL mapper shown in bold below. This <code>path()</code> function defines a pattern, associated generic class-based detail view, and a name.</p> + +<pre class="brush: python notranslate">urlpatterns = [ + path('', views.index, name='index'), + path('books/', views.BookListView.as_view(), name='books'), +<strong> path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p>For the <em>book-detail</em> path the URL pattern uses a special syntax to capture the specific id of the book that we want to see. The syntax is very simple: angle brackets define the part of the URL to be captured, enclosing the name of the variable that the view can use to access the captured data. For example, <strong><something></strong> , will capture the marked pattern and pass the value to the view as a variable "something". You can optionally precede the variable name with a <a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters">converter specification</a> that defines the type of data (int, str, slug, uuid, path).</p> + +<p>In this case we use <code>'<int:pk>'</code><strong> </strong> to capture the book id, which must be an integer, and pass it to the view as a parameter named <code>pk</code> (short for primary key).</p> + +<div class="note"> +<p><strong>Note</strong>: As discussed previously, our matched URL is actually <code>catalog/book/<digits></code> (because we are in the <strong>catalog</strong> application, <code>/catalog/</code> is assumed).</p> +</div> + +<div class="warning"> +<p><strong>Important</strong>: The generic class-based detail view <em>expects</em> to be passed a parameter named <strong>pk</strong>. If you're writing your own function view you can use whatever parameter name you like, or indeed pass the information in an unnamed argument.</p> +</div> + +<h4 id="Advanced_path_matchingregular_expression_primer">Advanced path matching/regular expression primer</h4> + +<div class="note"> +<p><strong>Note</strong>: You won't need this section to complete the tutorial! We provide it because knowing this option is likely to be useful in your Django-centric future.</p> +</div> + +<p>The pattern matching provided by <code>path()</code> is simple and useful for the (very common) cases where you just want to capture <em>any</em> string or integer. If you need more refined filtering (for example, to filter only strings that have a certain number of characters) then you can use the <a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path">re_path()</a> method.</p> + +<p>This method is used just like <code>path()</code> except that it allows you to specify a pattern using a <a href="https://docs.python.org/3/library/re.html">Regular expression</a>. For example, the previous path could have been written as shown below:</p> + +<pre class="brush: python notranslate"><strong>re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +</pre> + +<p><em>Regular expressions</em> are an incredibly powerful pattern mapping tool. They are, frankly, quite unintuitive and scary for beginners. Below is a very short primer!</p> + +<p>The first thing to know is that regular expressions should usually be declared using the raw string literal syntax (i.e. they are enclosed as shown: <strong>r'<your regular expression text goes here>'</strong>).</p> + +<p>The main parts of the syntax you will need to know for declaring the pattern matches are:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Symbol</th> + <th scope="col">Meaning</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>Match the beginning of the text</td> + </tr> + <tr> + <td>$</td> + <td>Match the end of the text</td> + </tr> + <tr> + <td>\d</td> + <td>Match a digit (0, 1, 2, ... 9)</td> + </tr> + <tr> + <td>\w</td> + <td>Match a word character, e.g. any upper- or lower-case character in the alphabet, digit or the underscore character (_)</td> + </tr> + <tr> + <td>+</td> + <td>Match one or more of the preceding character. For example, to match one or more digits you would use <code>\d+</code>. To match one or more "a" characters, you could use <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>Match zero or more of the preceding character. For example, to match nothing or a word you could use <code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>Capture the part of the pattern inside the brackets. Any captured values will be passed to the view as unnamed parameters (if multiple patterns are captured, the associated parameters will be supplied in the order that the captures were declared).</td> + </tr> + <tr> + <td>(?P<<em>name</em>>...)</td> + <td>Capture the pattern (indicated by ...) as a named variable (in this case "name"). The captured values are passed to the view with the name specified. Your view must therefore declare an argument with the same name!</td> + </tr> + <tr> + <td>[ ]</td> + <td>Match against one character in the set. For example, [abc] will match on 'a' or 'b' or 'c'. [-\w] will match on the '-' character or any word character.</td> + </tr> + </tbody> +</table> + +<p>Most other characters can be taken literally!</p> + +<p>Lets consider a few real examples of patterns:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Pattern</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>This is the RE used in our url mapper. It matches a string that has <code>book/</code> at the start of the line (<strong>^book/</strong>), then has one or more digits (<code>\d+</code>), and then ends (with no non-digit characters before the end of line marker).</p> + + <p>It also captures all the digits <strong>(?P<pk>\d+)</strong> and passes them to the view in a parameter named 'pk'. <strong>The captured values are always passed as a string!</strong></p> + + <p>For example, this would match <code>book/1234</code> , and send a variable <code>pk='1234'</code> to the view.</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>This matches the same URLs as the preceding case. The captured information would be sent as an unnamed argument to the view.</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>This matches a string that has <code>book/</code> at the start of the line (<strong>^book/</strong>), then has one or more characters that are <em>either</em> a '-' or a word character (<strong>[-\w]+</strong>), and then ends. It also captures this set of characters and passes them to the view in a parameter named 'stub'.</p> + + <p>This is a fairly typical pattern for a "stub". Stubs are URL-friendly word-based primary keys for data. You might use a stub if you wanted your book URL to be more informative. For example <code>/catalog/book/the-secret-garden</code> rather than <code>/catalog/book/33</code>.</p> + </td> + </tr> + </tbody> +</table> + +<p>You can capture multiple patterns in the one match, and hence encode lots of different information in a URL.</p> + +<div class="note"> +<p><strong>Note</strong>: As a challenge, consider how you might encode an url to list all books released in a particular year, month, day, and the RE that could be used to match it.</p> +</div> + +<h4 id="Passing_additional_options_in_your_URL_maps">Passing additional options in your URL maps</h4> + +<p>One feature that we haven't used here, but which you may find valuable, is that you can declare and pass <a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#views-extra-options">additional options</a> to the view. The options are declared as a dictionary that you pass as the third un-named argument to the <code>path()</code> function. This approach can be useful if you want to use the same view for multiple resources, and pass data to configure its behaviour in each case (below we supply a different template in each case).</p> + +<pre class="brush: python notranslate">path('url/', views.my_reused_view, <strong>{'my_template_name': 'some_path'}</strong>, name='aurl'), +path('anotherurl/', views.my_reused_view, <strong>{'my_template_name': 'another_path'}</strong>, name='anotherurl'), +</pre> + +<div class="note"> +<p><strong>Note:</strong> Both extra options and named captured patterns are passed to the view as <em>named</em> arguments. If you use the <strong>same name</strong> for both a captured pattern and an extra option then only the captured pattern value will be sent to the view (the value specified in the additional option will be dropped). </p> +</div> + +<h3 id="View_class-based_2">View (class-based)</h3> + +<p>Open <strong>catalog/views.py</strong>, and copy the following code into the bottom of the file:</p> + +<pre class="brush: python notranslate">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>That's it! All you need to do now is create a template called <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>, and the view will pass it the database information for the specific <code>Book</code> record extracted by the URL mapper. Within the template you can access the list of books with the template variable named <code>object</code> OR <code>book</code> (i.e. generically "<code><em>the_model_name</em></code>").</p> + +<p>If you need to, you can change the template used and the name of the context object used to reference the book in the template. You can also override methods to, for example, add additional information to the context.</p> + +<h4 id="What_happens_if_the_record_doesnt_exist">What happens if the record doesn't exist?</h4> + +<p>If a requested record does not exist then the generic class-based detail view will raise an <code>Http404</code> exception for you automatically — in production this will automatically display an appropriate "resource not found" page, which you can customise if desired.</p> + +<p>Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-based view as a function, if you were <strong>not</strong> using the generic class-based detail view.</p> + +<pre class="brush: python notranslate">def book_detail_view(request, primary_key): + try: + book = Book.objects.get(pk=primary_key) + except Book.DoesNotExist: + raise Http404('Book does not exist') + + # from django.shortcuts import get_object_or_404 + # book = get_object_or_404(Book, pk=primary_key) + + return render(request, 'catalog/book_detail.html', context={'book': book}) +</pre> + +<p>The view first tries to get the specific book record from the model. If this fails the view should raise an <code>Http404</code> exception to indicate that the book is "not found". The final step is then, as usual, to call <code>render()</code> with the template name and the book data in the <code>context</code> parameter (as a dictionary).</p> + +<div class="note"> +<p><strong>Note</strong>: The <code>get_object_or_404()</code> (shown commented out above) is a convenient shortcut to raise an <code>Http404</code> exception if the record is not found.</p> +</div> + +<h3 id="Creating_the_Detail_View_template">Creating the Detail View template</h3> + +<p>Create the HTML file <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong> and give it the below content. As discussed above, this is the default template file name expected by the generic class-based <em>detail</em> view (for a model named <code>Book</code> in an application named <code>catalog</code>).</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + <h1>Title: \{{ book.title }}</h1> + + <p><strong>Author:</strong> <a href="">\{{ book.author }}</a></p> <!-- author detail link not yet defined --> + <p><strong>Summary:</strong> \{{ book.summary }}</p> + <p><strong>ISBN:</strong> \{{ book.isbn }}</p> + <p><strong>Language:</strong> \{{ book.language }}</p> + <p><strong>Genre:</strong> {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> + + <div style="margin-left:20px;margin-top:20px"> + <h4>Copies</h4> + + {% for copy in book.bookinstance_set.all %} + <hr> + <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">\{{ copy.get_status_display }}</p> + {% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> \{{copy.due_back}}</p>{% endif %} + <p><strong>Imprint:</strong> \{{copy.imprint}}</p> + <p class="text-muted"><strong>Id:</strong> \{{copy.id}}</p> + {% endfor %} + </div> +{% endblock %}</pre> + +<ul> +</ul> + +<div class="note"> +<p>The author link in the template above has an empty URL because we've not yet created an author detail page. Once that exists, you should update the URL like this:</p> + +<pre class="notranslate"><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>Though a little larger, almost everything in this template has been described previously:</p> + +<ul> + <li>We extend our base template and override the "content" block.</li> + <li>We use conditional processing to determine whether or not to display specific content.</li> + <li>We use <code>for</code> loops to loop through lists of objects.</li> + <li>We access the context fields using the dot notation (because we've used the detail generic view, the context is named <code>book</code>; we could also use "<code>object</code>")</li> +</ul> + +<p>The one interesting thing we haven't seen before is the function <code>book.bookinstance_set.all()</code>. This method is "automagically" constructed by Django in order to return the set of <code>BookInstance</code> records associated with a particular <code>Book</code>.</p> + +<pre class="brush: python notranslate">{% for copy in book.bookinstance_set.all %} +<!-- code to iterate across each copy/instance of a book --> +{% endfor %}</pre> + +<p>需要這方法是因為我們僅在“一”那側model(Book)定義一個<code>ForeignKey</code> (一對多)字段的關聯,也因為沒有任何的關聯被定義在“多”那側model(BookInstance),故無法透過字段來取得相關的紀錄。為了克服這個問題,Django建立一個function取名為“reverse lookup”供使用。function的名字以一對多關係中該 <code>ForeignKey</code> 被定義在的那個模型名稱小寫,再在字尾加上<code>_set</code>(因此在 <code>Book</code> 創建的function名是 <code>bookinstance_set()</code>)。</p> + +<div class="note"> +<p><strong>Note</strong>: 在這我們使用 <code>all()</code> 取得所有紀錄 (預設),你無法直接在template做是因為你無法指定引數到function,但你可用 <code>filter()</code> 方法取得一個紀錄的子集 。</p> + +<p>順帶一提,若你不再基於類的view或model定義順序(order),開發伺服器會將會報錯類似的訊息:</p> + +<pre class="notranslate">[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 +/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> + allow_empty_first_page=allow_empty_first_page, **kwargs) +</pre> + +<p>That happens because the <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">paginator object</a> expects to see some ORDER BY being executed on your underlying database. Without it, it can't be sure the records being returned are actually in the right order!<strong> </strong></p> + +<p>This tutorial didn't reach <strong>Pagination</strong> (yet, but soon enough), but since you can't use <code>sort_by()</code> and pass a parameter (the same with <code>filter()</code> described above) you will have to choose between three choices:</p> + +<ol> + <li>Add a <code>ordering</code> inside a <code>class Meta</code> declaration on your model.</li> + <li>Add a <code>queryset</code> attribute in your custom class-based view, specifying a <code>order_by()</code>.</li> + <li>Adding a <code>get_queryset</code> method to your custom class-based view and also specify the <code>order_by()</code>.</li> +</ol> + +<p>If you decide to go with a <code>class Meta</code> for the <code>Author</code> model (probably not as flexible as customizing the class-based view, but easy enough), you will end up with something like this:</p> + +<pre class="notranslate">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return f'{self.last_name}, {self.first_name}' + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>Of course, the field doesn't need to be <code>last_name</code>: it could be any other.</p> + +<p>And last, but not least, you should sort by an attribute/column that actually has a index (unique or not) on your database to avoid performance issues. Of course, this will not be necessary here (and we are probably getting ourselves too much ahead) if such small amount of books (and users!), but it is something to keep in mind for future projects.</p> +</div> + +<h2 id="What_does_it_look_like_2">What does it look like?</h2> + +<p>At this point we should have created everything needed to display both the book list and book detail pages. Run the server (<code>python3 manage.py runserver</code>) and open your browser to <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</p> + +<div class="warning"> +<p><strong>Warning:</strong> Don't click any author or author detail links yet — you'll create those in the challenge!</p> +</div> + +<p>Click the <strong>All books</strong> link to display the list of books. </p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>Then click a link to one of your books. If everything is set up correctly, you should see something like the following screenshot.</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="Pagination">Pagination</h2> + +<p>If you've just got a few records, our book list page will look fine. However, as you get into the tens or hundreds of records the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this problem is to add pagination to your list views, reducing the number of items displayed on each page. </p> + +<p>Django has excellent in-built support for pagination. Even better, this is built into the generic class-based list views so you don't have to do very much to enable it!</p> + +<h3 id="Views">Views</h3> + +<p>Open <strong>catalog/views.py</strong>, and add the <code>paginate_by</code> line shown in bold below.</p> + +<pre class="brush: python notranslate">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the template. The different pages are accessed using GET parameters — to access page 2 you would use the URL: <code>/catalog/books/<strong>?page=2</strong></code>.</p> + +<h3 id="Templates">Templates</h3> + +<p>Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might want to do this in all list views, we'll do this in a way that can be added to the base template. </p> + +<p>Open <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> and copy in the following pagination block below our content block (highlighted below in bold). The code first checks if pagination is enabled on the current page. If so then it adds next and previous links as appropriate (and the current page number). </p> + +<pre class="brush: python notranslate">{% block content %}{% endblock %} + +<strong>{% block pagination %} + {% if is_paginated %} + <div class="pagination"> + <span class="page-links"> + {% if page_obj.has_previous %} + <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a> + {% endif %} + <span class="page-current"> + <p>Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.</p> + </span> + {% if page_obj.has_next %} + <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a> + {% endif %} + </span> + </div> + {% endif %} +{% endblock %} </strong></pre> + +<p>The <code>page_obj</code> is a <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">Paginator</a> object that will exist if pagination is being used on the current page. It allows you to get all the information about the current page, previous pages, how many pages there are, etc. </p> + +<p>We use <code>\{{ request.path }}</code> to get the current page URL for creating the pagination links. This is useful, because it is independent of the object that we're paginating.</p> + +<p>Thats it!</p> + +<h3 id="What_does_it_look_like_3">What does it look like?</h3> + +<p>The screenshot below shows what the pagination looks like — if you haven't entered more than 10 titles into your database, then you can test it more easily by lowering the number specified in the <code>paginate_by</code> line in your <strong>catalog/views.py</strong> file. To get the below result we changed it to <code>paginate_by = 2</code>.</p> + +<p>The pagination links are displayed on the bottom, with next/previous links being displayed depending on which page you're on.</p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>The challenge in this article is to create the author detail and list views required to complete the project. These should be made available at the following URLs:</p> + +<ul> + <li><code>catalog/authors/</code> — The list of all authors.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— The detail view for the specific author with a primary key field named <em><code><id></code></em></li> +</ul> + +<p>The code required for the URL mappers and the views should be virtually identical to the <code>Book</code> list and detail views we created above. The templates will be different, but will share similar behaviour.</p> + +<div class="note"> +<p><strong>Note</strong>:</p> + +<ul> + <li>Once you've created the URL mapper for the author list page you will also need to update the <strong>All authors</strong> link in the base template. Follow the <a href="#Update_the_base_template">same process</a> as we did when we updated the <strong>All books</strong> link.</li> + <li>Once you've created the URL mapper for the author detail page, you should also update the <a href="#Creating_the_Detail_View_template">book detail view template</a> (<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>) so that the author link points to your new author detail page (rather than being an empty URL). The line will change to add the template tag shown in bold below. + <pre class="brush: html notranslate"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>When you are finished, your pages should look something like the screenshots below.</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Congratulations, our basic library functionality is now complete! </p> + +<p>In this article we've learned how to use the generic class-based list and detail views and used them to create pages to view our books and authors. Along the way we've learned about pattern matching with regular expressions, and how you can pass data from URLs to your views. We've also learned a few more tricks for using templates. Last of all we've shown how to paginate list views, so that our lists are managable even when we have many records.</p> + +<p>In our next articles we'll extend this library to support user accounts, and thereby demonstrate user authentication, permissons, sessions, and forms.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/">Generic display views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/">Introduction to class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/pagination/">Pagination</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/home_page/index.html b/files/zh-tw/learn/server-side/django/home_page/index.html new file mode 100644 index 0000000000..a01d71608e --- /dev/null +++ b/files/zh-tw/learn/server-side/django/home_page/index.html @@ -0,0 +1,383 @@ +--- +title: 'Django Tutorial Part 5: Creating our home page' +slug: Learn/Server-side/Django/Home_page +translation_of: Learn/Server-side/Django/Home_page +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</div> + +<p class="summary">我們現在可以添加代碼,來顯示我們的第一個完整頁面 - <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站的主頁,顯示每個模型類型有多少條記錄,並提供我們其他頁面的側邊欄導航鏈接。一路上,我們將獲得編寫基本URL 地圖和視圖、從數據庫獲取記錄、以及使用模板的實踐經驗。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>讀 the <a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Introduction">Django Introduction</a>. 完成上章節 (including <a href="/zh-TW/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a>).</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解如何創建簡單的URL映射和視圖(沒有數據編碼在URL中)以及如何從模型中獲取數據並創建模版。<br> + 概要</td> + </tr> + </tbody> +</table> + +<h2 id="總覽">總覽</h2> + +<p>在定義了模型並創建了一些可以使用的初始庫記錄之後,是時候編寫將這些信息呈現給用戶的代碼了。 我們要做的第一件事是確定我們要在頁面中顯示的信息,並定義用於返回這些資源的URL。 然後,我們將創建一個URL映射器,視圖和模板來顯示頁面。</p> + +<p>下圖描述了主要數據流,以及處理HTTP請求和響應時所需的組件。 當我們已經實現了模型時,我們將創建的主要組件是:</p> + +<ul> + <li>URL映射器將支持的URL(以及URL中編碼的任何信息)轉發到適當的視圖功能。</li> + <li>查看函數可從模型中獲取所需的數據,創建顯示數據的HTML頁面,並將頁面返回給用戶以在瀏覽器中查看。</li> + <li>在視圖中呈現數據時要使用的模板。</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="display: block; margin: 0px auto;"></p> + +<p>正如您將在下一節中看到的那樣,我們將顯示5頁,這是太多信息,無法在一篇文章中進行記錄。 因此,本文將重點介紹如何實現主頁,我們將在後續文章中介紹其他頁面。 這應該使您對URL映射器,視圖和模型在實踐中如何工作有很好的端到端理解。</p> + +<h2 id="定義資源URL">定義資源URL</h2> + +<p>由於此版本的LocalLibrary對於最終用戶基本上是只讀的,因此我們只需要提供該網站的登錄頁面(主頁),以及顯示書籍和作者的列表和詳細視圖的頁面。</p> + +<p>我們頁面所需的URL是:</p> + +<ul> + <li><code>catalog/</code> — 主頁/索引頁面。</li> + <li><code>catalog/books/</code> — 所有書籍的清單。</li> + <li><code>catalog/authors/</code> — 所有作者列表。</li> + <li><code>catalog/book/<em><id></em></code> — 特定書的詳細信息視圖,其主鍵為 <code><em><id></em></code> (the default)。 因此,例如<code>/catalog/book/3</code>,作為添加的第三本書。</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— 特定作者的詳細信息視圖,其主鍵字段名為<em><code><id></code></em>。 例如,為第11位作者添加了<code>/catalog/author/11</code>。</li> +</ul> + +<p>前三個URL用於列出索引,書籍和作者。 它們不對任何其他信息進行編碼,並且雖然返回的結果將取決於數據庫中的內容,但為獲取信息而運行的查詢將始終相同。</p> + +<p>相比之下,最後兩個URL用於顯示有關特定書籍或作者的詳細信息-這些URL編碼要顯示在URL中的項目的標識(如上顯示為<id>)。 URL映射器可以提取編碼信息並將其傳遞給視圖,然後將動態確定從數據庫中獲取哪些信息。 通過在我們的URL中編碼信息,我們只需要一個URL映射,視圖和模板即可處理每本書(或作者)。</p> + +<div class="note"> +<p>注意: Django允許您以自己喜歡的任何方式來構造URL-您可以如上所示在URL主體中編碼信息或使用URL <code>GET</code> 參數(例如<code>/book/?id=6</code>)。 無論使用哪種方法,都應保持URL的整潔,邏輯和可讀性(<a href="https://www.w3.org/Provider/Style/URI">在此處查看W3C建議</a>).</p> + +<p>Django文檔傾向於建議在URL正文中編碼信息,他們認為這種做法鼓勵更好的URL設計。</p> +</div> + +<p>如概述中所述,本文的其餘部分描述瞭如何構造索引頁。</p> + +<h2 id="創建索引頁面">創建索引頁面</h2> + +<p>我們將創建的第一頁是索引頁 (<code>catalog/</code>)。 這將顯示一些靜態HTML,以及數據庫中不同記錄的一些計算出的“計數”。 為了完成這項工作,我們必須創建一個URL映射,視圖和模板。</p> + +<div class="note"> +<p><strong>注意:值得在本節中多加註意。 大多數材料是所有頁面共有的。</strong></p> +</div> + +<h3 id="URL_mapping">URL mapping</h3> + +<p>創建<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">skeleton website</a> 時,我們更新了<strong>locallibrary/urls.py</strong>文件,以確保每當收到以 <code>catalog/</code> 開頭的URL時, <em>URLConf </em>模組 <code>catalog.urls</code> 都將處理其餘的子字符串。</p> + +<p>來自 <strong>locallibrary/urls.py</strong>的以下代碼片段包括<code>catalog.urls</code> 模塊:</p> + +<pre>urlpatterns += [ + path('catalog/', include('catalog.urls')), +] +</pre> + +<div class="note"> +<p><strong>注意</strong>: 每當Django遇到導入函數 <code><a href="https://docs.djangoproject.com/en/2.1/ref/urls/#django.urls.include" title="django.conf.urls.include">django.urls.include()</a></code>時,它都會在指定的結束字符處分割URL字符串,並將剩餘的子字符串發送到所包含的<em>URLconf</em> 模塊以進行進一步處理。</p> +</div> + +<p>我們還為<em>URLConf </em>模塊創建了一個佔位符文件,名為 <strong>/catalog/urls.py</strong>。 將以下行添加到該文件:</p> + +<pre class="brush: python">urlpatterns = [ +<strong> path('', views.index, name='index'),</strong> +]</pre> + +<p><code>path()</code>函數定義以下內容:</p> + +<ul> + <li>URL模式,它是一個空字符串:<code>''</code>。 處理其他視圖時,我們將詳細討論URL模式。</li> + <li>如果檢測到URL模式,將調用一個視圖函數:<code>views.index</code>, 它是<strong>views.py</strong>文件中名為<code>index()</code> 的函數。 </li> +</ul> + +<p><code>path()</code> 函數還指定一個<code>name</code>參數,它是此特定URL映射的唯一標識符。 您可以使用該名稱來“反向”映射器,即,動態創建指向映射器旨在處理的資源的URL。 例如,通過在模板中添加以下鏈接,我們可以使用name參數從任何其他頁面鏈接到我們的主頁:</p> + +<pre class="brush: html"><a href="<strong>{% url 'index' %}</strong>">Home</a>.</pre> + +<div class="note"> +<p><strong>注意</strong>: 我們可以對上面的鏈接進行硬編碼 (例如<code><a href="<strong>/catalog/</strong>">Home</a></code>), 但是如果我們更改主頁的模式 (例如更改為 <code>/catalog/index</code>) 則模板將不再 正確鏈接。 使用反向URL映射更加靈活和健壯!</p> +</div> + +<h3 id="View_function-based">View (function-based)</h3> + +<p>View是一個用來處理 HTTP 請求的函式,根據需求從資料庫取得資料,通過使用 HTML 模板呈現此數據來生成 HTML , 並且在一個 HTTP 回應中返回 HTML 來呈現給用戶。Index view 遵循這個模型 — 獲取有關數據庫中有多少 <code>Book</code>, <code>BookInstance</code>, 可用的 <code>BookInstance</code> 還有 <code>Author</code> 的訊息, 然後把他們傳遞給模板進行顯示。</p> + +<p>打開<strong>catalog/views.py</strong>, 並且注意該文件已經導入 <a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#django.shortcuts.render">render()</a> 快捷功能已使用模板和數據生成HTML文件。 </p> + +<pre class="brush: python">from django.shortcuts import render + +# Create your views here. +</pre> + +<p>將以下代碼複製到文件底部。 第一行導入將用於訪問所有視圖中的數據的模型類。</p> + +<pre class="brush: 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)</pre> + +<p>視圖函數的第一部分使用模型類上的 <code>objects.all()</code> 屬性獲取記錄數。 它還獲取具有狀態字段值為“ a”(可用)的<code>BookInstance</code> 物件列表。 在上一教程(<a href="/en-US/docs/Learn/Server-side/Django/Models#Searching_for_records">Django Tutorial Part 3: Using models > Searching for records</a>)中,您可以找到更多有關如何從模型進行訪問的信息。.</p> + +<p>在函數的最後,我們調用 <code>render()</code> 函數來創建並返回HTML頁面作為響應(此快捷功能包裝了許多其他函數,從而簡化了這種非常常見的用例)。它以原始 <code>request</code> 物件 (一個 <code>HttpRequest</code>), 帶有數據佔位符的HTML模板以及上下文 <code>context</code> 變量包含將插入到這些佔位符中的數據的Python字典)為參數。</p> + +<p>在下一節中,我們將詳細討論模板和上下文變量。 讓我們開始創建模板,以便實際上可以向用戶顯示內容!</p> + +<h3 id="Template">Template</h3> + +<p>模板是一個文本文件,用於定義文件(例如HTML頁面)的結構或佈局,並使用佔位符表示實際內容。 Django會在您的應用程序名為'<strong>templates</strong>'的目錄中自動查找模板。 因此,例如,在我們剛剛添加的索引視圖中, <code>render()</code> 函數將有望能夠找到文件 <strong>/locallibrary/catalog/templates/<em>index.html</em></strong>,如果找不到該文件,則會引發錯誤。 如果您保存以前的更改並返回瀏覽器,則可以看到此信息-訪問<code>127.0.0.1:8000</code>現在將為您提供一個相當直觀的錯誤消息"<code>TemplateDoesNotExist at /catalog/</code>"以及其他詳細信息。</p> + +<div class="note"> +<p><strong>注意</strong>: Django將根據項目的設置文件在許多位置查找模板(搜索已安裝的應用程序是默認設置!)。 您可以在 <a href="https://docs.djangoproject.com/en/2.0/topics/templates/">Templates</a> (Django docs)中找到有關Django如何查找模板及其支持的模板格式的更多信息。</p> +</div> + +<h4 id="Extending_templates">Extending templates</h4> + +<p>索引模板的頭部和身體將需要標準的HTML標記,以及用於導航的部分(到我們尚未創建的站點中的其他頁面)以及用於顯示一些介紹性文本和我們的書籍數據的部分。 對於我們網站上的每個頁面,大部分文本(HTML和導航結構)都是相同的。 Django模板語言允許您聲明一個基本模板,然後擴展它,而不是強迫開發人員在每個頁面中都複製此"樣板" ,只需替換每個特定頁面上不同的部分即可。</p> + +<p>例如,基本模板 <strong>base_generic.html</strong> 可能類似於以下文本。 如您所見,其中包含一些"通用" HTML以及標題,側邊欄和內容的部分,這些部分使用命名的<code>block</code> 和<code>endblock</code> 模板標記進行了標記(以粗體顯示)。 區塊可以為空,或包含將在默認情況下用於派生頁面的內容。</p> + +<div class="note"> +<p><strong>注意</strong>:模板<em>tags</em> 類似於可以在模板中使用的功能,可以在模板中循環使用列表,基於變量的值執行條件操作等。除了模板標記之外,模板語法還允許您引用模板變量(傳遞給 模板),並使用<em>template filters</em>,該過濾器可重新格式化變量(例如,將字符串設置為小寫)。</p> +</div> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + <strong>{% block title %}</strong><title>Local Library</title><strong>{% endblock %}</strong> +</head> + +<body> + <strong>{% block sidebar %}</strong><!-- insert default navigation text for every page --><strong>{% endblock %}</strong> + <strong>{% block content %}</strong><!-- default content text (typically empty) --><strong>{% endblock %}</strong> +</body> +</html> +</pre> + +<p>當我們想為特定視圖定義模板時,我們首先指定基本模板(帶有<code>extends</code> 模板標籤-請參見下一個代碼清單)。 如果我們要在模板中替換任何節,則使用與基本模板中相同的<code>block</code>/<code>endblock</code>節來聲明這些節。</p> + +<p>例如,下面的代碼片段顯示了我們如何使用<code>extends</code> 模板標籤並覆蓋<code>content</code> 區塊。 生成的最終HTML將具有基本模板中定義的所有HTML和結構(包括您在<code>title</code> 區塊中定義的默認內容),但是將新的<code>content</code> 區塊插入到默認模板中。</p> + +<pre class="brush: html">{% 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 %}</pre> + +<h4 id="The_LocalLibrary_base_template">The LocalLibrary base template</h4> + +<p>下面列出了我們計劃用於<em>LocalLibrary</em> 網站的基本模板。 如您所見,其中包含一些HTML以及 <code>title</code>, <code>sidebar</code>, 和 <code>content</code>。 我們有一個默認標題(我們可能想要更改)和一個默認側邊欄,其中帶有指向所有書籍和作者列表的鏈接(我們可能不想更改,但是如果需要的話,我們允許範圍通過將其放在 在一個區塊中)。</p> + +<div class="note"> +<p><strong>注意</strong>: 我們還引入了兩個附加的模板標籤:<code>url</code> 和<code>load static</code>。 這些將在以下各節中討論。</p> +</div> + +<p>創建一個新文件<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> ,並為其提供以下內容:</p> + +<pre class="brush: html"><!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></pre> + +<p>該模板包括來自<a href="http://getbootstrap.com/">Bootstrap</a>的CSS,以改進HTML頁面的佈局和表示方式。 使用Bootstrap或其他客戶端Web框架是創建吸引人的頁面的快速方法,該頁面可以在不同的瀏覽器大小上很好地擴展。</p> + +<p>基本模板還引用了本地CSS文件 (<strong>styles.css</strong>) ,該文件提供了一些其他樣式。 創建 <strong>/locallibrary/catalog/static/css/styles.css</strong>並為其提供以下內容:</p> + +<pre class="brush: css">.sidebar-nav { + margin-top: 20px; + padding: 0; + list-style: none; +}</pre> + +<h4 id="The_index_template">The index template</h4> + +<p>創建HTML文件 <strong>/locallibrary/catalog/templates/<em>index.html</em></strong> 並為其提供以下內容。 如您所見,我們在第一行中擴展了基本模板,然後使用該模板的新內容塊替換默認<code>content</code> 區塊。</p> + +<pre class="brush: html">{% 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> <strong>\{{ num_books }}</strong></li> + <li><strong>Copies:</strong> <strong>\{{ num_instances }}</strong></li> + <li><strong>Copies available:</strong> <strong>\{{ num_instances_available }}</strong></li> + <li><strong>Authors:</strong> <strong>\{{ num_authors }}</strong></li> + </ul> +{% endblock %}</pre> + +<p>在 <em>Dynamic content </em>部分中,我們聲明了要從視圖中包含的信息的佔位符(<em>template variables</em>)。 變量使用“雙括號”或“把手”語法標記(請參見上面的粗體)。</p> + +<div class="note"> +<p><strong>注意</strong>:因為變量具有雙括號 (<code>\{{ num_books }}</code>),而標籤則用百分號括在單括號中擴展為 (<code>{% extends "base_generic.html" %}</code>),所以您可以輕鬆識別是要處理模板變量還是模板標籤(函數)。</p> +</div> + +<p>這裡要注意的重要一點是,這些變量是使用我們在視圖的<code>render()</code> 函數中傳遞給<code>context</code> 字典的鍵命名的(請參見下文); 呈現模板時,這些將被其<em>values</em> 替換。</p> + +<pre class="brush: python">context = { + '<strong>num_books</strong>': num_books, + '<strong>num_instances</strong>': num_instances, + '<strong>num_instances_available</strong>': num_instances_available, + '<strong>num_authors</strong>': num_authors, +} + +return render(request, 'index.html', context=context)</pre> + +<h4 id="Referencing_static_files_in_templates">Referencing static files in templates</h4> + +<p>您的項目可能會使用靜態資源,包括JavaScript,CSS和圖像。 由於這些文件的位置可能未知(或可能會更改),因此Django允許您相對於<code>STATIC_URL</code> 全局設置在模板中指定這些文件的位置(默認框架網站將<code>STATIC_URL</code> 的值設置為'<code>/static/</code>',但您可以選擇將其託管在內容分發網絡或其他地方)。</p> + +<p>在模板中,您首先調用指定為“ static”的<code>load</code> 模板標籤以添加此模板庫(如下所示)。 加載靜態文件後,您可以使用<code>static</code> 模板標籤,指定感興趣文件的相對URL。</p> + +<pre class="brush: html"><!-- Add additional CSS in static file --> +{% load static %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"></pre> + +<p>如果需要,您可以以相同的方式將圖像添加到頁面中。 例如:</p> + +<pre class="brush: html">{% load static %} +<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="UML diagram" style="width:555px;height:540px;"> +</pre> + +<div class="note"> +<p><strong>注意</strong>:上面的更改指定了文件的位置,但是Django默認不提供文件。創建網站框架時 (<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">created the website skeleton</a>),雖然我們在全局URL映射器(<strong>/locallibrary/locallibrary/urls.py</strong>)中啟用了由開發Web服務器提供的服務,但您仍需要安排它們在生產中提供。 我們待會再看。</p> +</div> + +<p>有關使用靜態文件的更多信息,請參閱管理靜態文件 <a href="https://docs.djangoproject.com/en/2.0/howto/static-files/">Managing static files</a> (Django docs)。</p> + +<h4 id="Linking_to_URLs">Linking to URLs</h4> + +<p>上面的基本模板引入了<code>url</code> 模板標籤。</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +</pre> + +<p>此標記採用在 <strong>urls.py</strong>中調用的 <code>path()</code>函數的名稱以及關聯視圖將從該函數接收的任何參數的值,並返回可用於鏈接到資源的URL。</p> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>此時,我們應該已經創建了顯示索引頁面所需的所有內容。 運行服務器(<code>python3 manage.py runserver</code>),然後打開瀏覽器到<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>。 如果一切設置正確,則您的站點應類似於以下螢幕截圖。</p> + +<p><img alt="Index page for LocalLibrary website" src="https://mdn.mozillademos.org/files/14045/index_page_ok.png" style="border-style: solid; border-width: 1px; display: block; height: 356px; margin: 0px auto; width: 874px;"></p> + +<div class="note"> +<p><strong>注意</strong>:您將無法使用<strong>All books</strong>和<strong>All authors</strong>鏈接,因為尚未定義這些頁面的路徑,視圖和模板(當前我們僅在<code>base_generic.html</code> html模板中插入了這些鏈接的佔位符)。</p> +</div> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>這裡有兩個任務可以測試您對模型查詢,視圖和模板的熟悉程度。</p> + +<ol> + <li>LocalLibrary <a href="#The_LocalLibrary_base_template">base template</a> 已定義<code>title</code> 欄。 在 <a href="#The_index_template">index template</a>中覆蓋此塊並為頁面創建一些新標題。 + + <div class="note"> + <p><strong>提示</strong> :<a href="#Extending_templates">Extending templates</a> 部分介紹瞭如何創建塊並將其擴展到另一個模板中。<br> + </p> + </div> + </li> + <li>修改 <a href="#View_(function-based)">view</a>以生成包含特定單詞(不區分大小寫)的流派計數和書籍計數,並將其傳遞給<code>context</code> (這與我們創建並使用<code>num_books</code> 和<code>num_instances_available</code>的方式大致相同)。 然後更新 <a href="#The_index_template">index template</a> 以使用這些變量。</li> +</ol> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>現在,我們已經為網站創建了主頁-一個HTML頁面,該頁面顯示了數據庫中的一些記錄計數,並具有指向其他尚待創建頁面的鏈接。 在此過程中,我們學習了很多有關url映射器,視圖,使用我們的模型查詢數據庫,如何從視圖中將信息傳遞到模板以及如何創建和擴展模板的基本信息。</p> + +<p>在下一篇文章中,我們將基於我們的知識來創建其他四個頁面。</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial03/">Writing your first Django app, part 3: Views and Templates</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/">URL dispatcher</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/views/">View functions</a> (DJango docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/templates/">Templates</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/">Managing static files</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#django.shortcuts.render">Django shortcut functions</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/index.html b/files/zh-tw/learn/server-side/django/index.html new file mode 100644 index 0000000000..7bb4840e06 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/index.html @@ -0,0 +1,115 @@ +--- +title: Django 網站框架 (Python) +slug: Learn/Server-side/Django +translation_of: Learn/Server-side/Django +--- +<div>{{LearnSidebar}}</div> + +<p>Django 使用 Python 語言編寫,是一個廣受歡迎、且功能完整的服務器端網站框架。本模塊將為您展示,為什麼 Django 能夠成為一個廣受歡迎的服務器端框架,如何設置開發環境,以及如何開始創建你自己的網絡應用。</p> + +<h2 id="先決條件">先決條件</h2> + +<p>開始學習本模塊,並不需要任何 Django 知識. 但您要理解什麼是服務器端網絡編程、什麼是網絡框架,最好能夠閱讀我們的<a href="/zh-TW/docs/Learn/Server-side/First_steps">服務端網站編程的第一步模塊</a>。</p> + +<p>最好能有基本的編程概念、並了解 <a href="/zh-TW/docs/Glossary/Python">Python</a> 語言,但並不是理解本教程的核心概念的必然條件。</p> + +<div class="note"> +<p><strong>Note</strong>: 對於初學者來說,Python 是最容易閱讀和理解的編程語言之一。也就是說,如果您想更好的理解本教程,網上有很多免費書籍及免費教程可供參考學習(建議初學者查看 Python 官網的 <a href="https://wiki.python.org/moin/BeginnersGuide/NonProgrammers">Python for Non Programmers</a> )。</p> +</div> + +<h2 id="指引">指引</h2> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Introduction">Django 簡介</a></dt> + <dd>在第一篇關於 Django 的文章裡,我們會回答"什麼是Django?",並概述這個網絡框架的特殊之處。我們會列出主要的功能,包括一些高級的功能特性,這些高級特性我們在這部分教程裡沒有時間詳細說明。在你設置好 Django 應用、並開始把玩它之前,我們會展示 Django 應用的一些主要模塊,讓你明白 Django 應用能做什麼。 </dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/development_environment">架設 Django 開發環境</a></dt> + <dd>現在你知道 Django 是做什麼的,我們會展示怎樣在 Windows、Linux(Ubuntu)、和 Mac OS X上,創建和測試 Django 的開發環境—不管你是用什麼操作系統,這篇文章會教給你能夠開發 Django 應用所需要的開發環境。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教學 1: 本地圖書館網站</a></dt> + <dd>我們實用教程系列的第一篇文章,會解釋你將學習到什麼,並提供 "本地圖書館" 網站這個例子的概述。我們會在接下來的文章裡,完成並不斷的改進這個網站。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/skeleton_website">Django 教學 2: 創建骨架網站</a></dt> + <dd>這篇文章會教你,怎樣創建一個網站的 "框架" 。以這個網站為基礎,你可以填充網站特定的 settings、urls、models、views 和 templates。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Models">Django 教學 3: 使用模型</a></dt> + <dd>這篇文章會為 “本地圖書館網站” 定義數據模板—數據模板是我們為應用存儲的數據結構。並且允許 Django 在資料庫中存儲數據(以後可以修改)。此文章解釋了什麼是數據模板、怎樣聲明它、和一些主要的數據種類。文章還簡要的介紹了一些,你可以獲得數據模板的方法。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Admin_site">Django 教學 4: Django 管理員頁面</a></dt> + <dd>現在我們已經為本地圖書館網站,創建了模型,我們將使用 Django 管理員頁面添加一些 ‘真實的’ 的圖書數據。首先,我們將向你介紹,如何使用管理員頁面註冊模型,然後我們介紹如何登錄和創建一些數據。最後我們展示一些,進一步改進管理員頁面呈現的方法。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Home_page">Django 教學 5: 創建我們的首頁</a></dt> + <dd>我們現在可以添加代碼,來展示我們的第一個完整頁面—本地圖書館主頁,來顯示我們對每個模型類型有多少條記錄,並提供我們其他頁面的側邊欄導航鏈接。一路上,我們將獲得編寫基本 URL 地圖和視圖、從數據庫獲取記錄、以及使用模版的實踐經驗。.</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Generic_views">Django 教學 6: 通用列表與詳細視圖</a></dt> + <dd>本教學課程擴展了我們的本地圖書館網站,添加書籍和作者和詳細頁面。在這裡,我們將了解基於類別的通用視圖,並展示如何減少常用代碼用例的代碼量。我們還將更詳細地深入理解 URL 處理,展示如何執行基本模式匹配。 </dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Sessions">Django 教學 7: 會話框架</a></dt> + <dd>本教學擴展本地圖書館網站,向首頁添加了一個基於會話的訪問計數器。這是個比較簡單的例子,但它顯示如何使用會話框架,為你自己的網站中的匿名用戶,提供一致的行為。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Authentication">Django 教學 8: 使用者身份驗証和權限</a></dt> + <dd>本教程,我們將向你展示,如何允許使用者用自己的賬戶,登錄到你的網站,以及如何根據他們是否登錄、及其權限,來控制他們可以做什麼、和看到什麼。作為此次演示的一部分,我們將擴展本地圖書館網站,添加登錄和登出頁面,以及使用者和工作人員特定頁面,以查看已借用的書籍。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Forms">Django 教學 9: 使用表單</a></dt> + <dd>本教程,我們將向你展示如何使用 Django 中的 <a href="https://developer.mozilla.org/zh-TW/docs/Web/Guide/HTML/Forms">HTML Forms</a> 表單,特別是編寫表單以創建、更新、和刪除模型實例的最簡單方法。作為此次演示的一部分,我們將擴展本地圖書館網站,以便圖書館員,可以使用我們自己的表單 (而不是使用管理應用程序) 來更新書籍,創建、更新、刪除作者。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Testing">Django 教學 10: 測試 Django 網頁應用</a></dt> + <dd>隨著網站的的發展,手工測試越來越難測試—不僅要測試更多,而且隨著組件之間的相互作用變得越來越複雜,一個領域的一個小的變化,可能需要許多額外的測試,來驗證其對其他領域的影響。減輕這些問題的一種方法,是編寫自動化測試,每次更改時,都可以輕鬆可靠地運行。本教程將介紹如何使用 Django 的測試框架,對你的網站進行單元測試自動化。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Deployment">Django 教學 11: 部署 Django 到生產環境</a></dt> + <dd>現在,你已創建(並測試)一個很酷的 “本地圖書館網站”,你將要把它安裝在公共 Web 服務器上,以便圖書館員工和成員,可以通過 Internet 訪問。本文概述如何找到主機,來部署你的網站,以及你需要做什麼,才能使你的網站準備好投入生產環境。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/web_application_security">Django 網頁應用安全</a></dt> + <dd>保護用戶數據,是任何網站設計的重要組成部分,我們以前解釋了Web 安全文章中,一些更常見的安全威脅—本文提供了 Django 內置、如何保護處理這種危險的實際演示。</dd> +</dl> + +<h2 id="評估">評估</h2> + +<p>以下評估,將測試你對如何使用 Django 創建網站的理解,如上述指南中所列出的項目。</p> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博客</a></dt> + <dd>在這個評估中,你將使用你從本單元中學到的一些知識,來創建自己的博客。</dd> +</dl> +<div>{{LearnSidebar}}</div> + +<p>Django 使用 Python 語言編寫,是一個廣受歡迎、且功能完整的服務器端網站框架。本模塊將為您展示,為什麼 Django 能夠成為一個廣受歡迎的服務器端框架,如何設置開發環境,以及如何開始創建你自己的網絡應用。</p> + +<h2 id="先決條件_2">先決條件</h2> + +<p>開始學習本模塊,並不需要任何 Django 知識. 但您要理解什麼是服務器端網絡編程、什麼是網絡框架,最好能夠閱讀我們的<a href="/zh-TW/docs/Learn/Server-side/First_steps">服務端網站編程的第一步模塊</a>。</p> + +<p>最好能有基本的編程概念、並了解 <a href="/zh-TW/docs/Glossary/Python">Python</a> 語言,但並不是理解本教程的核心概念的必然條件。</p> + +<div class="note"> +<p><strong>Note</strong>: 對於初學者來說,Python 是最容易閱讀和理解的編程語言之一。也就是說,如果您想更好的理解本教程,網上有很多免費書籍及免費教程可供參考學習(建議初學者查看 Python 官網的 <a href="https://wiki.python.org/moin/BeginnersGuide/NonProgrammers">Python for Non Programmers</a> )。</p> +</div> + +<h2 id="指引_2">指引</h2> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Introduction">Django 簡介</a></dt> + <dd>在第一篇關於 Django 的文章裡,我們會回答"什麼是Django?",並概述這個網絡框架的特殊之處。我們會列出主要的功能,包括一些高級的功能特性,這些高級特性我們在這部分教程裡沒有時間詳細說明。在你設置好 Django 應用、並開始把玩它之前,我們會展示 Django 應用的一些主要模塊,讓你明白 Django 應用能做什麼。 </dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/development_environment">架設 Django 開發環境</a></dt> + <dd>現在你知道 Django 是做什麼的,我們會展示怎樣在 Windows、Linux(Ubuntu)、和 Mac OS X上,創建和測試 Django 的開發環境—不管你是用什麼操作系統,這篇文章會教給你能夠開發 Django 應用所需要的開發環境。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教學 1: 本地圖書館網站</a></dt> + <dd>我們實用教程系列的第一篇文章,會解釋你將學習到什麼,並提供 "本地圖書館" 網站這個例子的概述。我們會在接下來的文章裡,完成並不斷的改進這個網站。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/skeleton_website">Django 教學 2: 創建骨架網站</a></dt> + <dd>這篇文章會教你,怎樣創建一個網站的 "框架" 。以這個網站為基礎,你可以填充網站特定的 settings、urls、models、views 和 templates。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Models">Django 教學 3: 使用模型</a></dt> + <dd>這篇文章會為 “本地圖書館網站” 定義數據模板—數據模板是我們為應用存儲的數據結構。並且允許 Django 在資料庫中存儲數據(以後可以修改)。此文章解釋了什麼是數據模板、怎樣聲明它、和一些主要的數據種類。文章還簡要的介紹了一些,你可以獲得數據模板的方法。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Admin_site">Django 教學 4: Django 管理員頁面</a></dt> + <dd>現在我們已經為本地圖書館網站,創建了模型,我們將使用 Django 管理員頁面添加一些 ‘真實的’ 的圖書數據。首先,我們將向你介紹,如何使用管理員頁面註冊模型,然後我們介紹如何登錄和創建一些數據。最後我們展示一些,進一步改進管理員頁面呈現的方法。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Home_page">Django 教學 5: 創建我們的首頁</a></dt> + <dd>我們現在可以添加代碼,來展示我們的第一個完整頁面—本地圖書館主頁,來顯示我們對每個模型類型有多少條記錄,並提供我們其他頁面的側邊欄導航鏈接。一路上,我們將獲得編寫基本 URL 地圖和視圖、從數據庫獲取記錄、以及使用模版的實踐經驗。.</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Generic_views">Django 教學 6: 通用列表與詳細視圖</a></dt> + <dd>本教學課程擴展了我們的本地圖書館網站,添加書籍和作者和詳細頁面。在這裡,我們將了解基於類別的通用視圖,並展示如何減少常用代碼用例的代碼量。我們還將更詳細地深入理解 URL 處理,展示如何執行基本模式匹配。 </dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Sessions">Django 教學 7: 會話框架</a></dt> + <dd>本教學擴展本地圖書館網站,向首頁添加了一個基於會話的訪問計數器。這是個比較簡單的例子,但它顯示如何使用會話框架,為你自己的網站中的匿名用戶,提供一致的行為。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Authentication">Django 教學 8: 使用者身份驗証和權限</a></dt> + <dd>本教程,我們將向你展示,如何允許使用者用自己的賬戶,登錄到你的網站,以及如何根據他們是否登錄、及其權限,來控制他們可以做什麼、和看到什麼。作為此次演示的一部分,我們將擴展本地圖書館網站,添加登錄和登出頁面,以及使用者和工作人員特定頁面,以查看已借用的書籍。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Forms">Django 教學 9: 使用表單</a></dt> + <dd>本教程,我們將向你展示如何使用 Django 中的 <a href="https://developer.mozilla.org/zh-TW/docs/Web/Guide/HTML/Forms">HTML Forms</a> 表單,特別是編寫表單以創建、更新、和刪除模型實例的最簡單方法。作為此次演示的一部分,我們將擴展本地圖書館網站,以便圖書館員,可以使用我們自己的表單 (而不是使用管理應用程序) 來更新書籍,創建、更新、刪除作者。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Testing">Django 教學 10: 測試 Django 網頁應用</a></dt> + <dd>隨著網站的的發展,手工測試越來越難測試—不僅要測試更多,而且隨著組件之間的相互作用變得越來越複雜,一個領域的一個小的變化,可能需要許多額外的測試,來驗證其對其他領域的影響。減輕這些問題的一種方法,是編寫自動化測試,每次更改時,都可以輕鬆可靠地運行。本教程將介紹如何使用 Django 的測試框架,對你的網站進行單元測試自動化。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/Deployment">Django 教學 11: 部署 Django 到生產環境</a></dt> + <dd>現在,你已創建(並測試)一個很酷的 “本地圖書館網站”,你將要把它安裝在公共 Web 服務器上,以便圖書館員工和成員,可以通過 Internet 訪問。本文概述如何找到主機,來部署你的網站,以及你需要做什麼,才能使你的網站準備好投入生產環境。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/web_application_security">Django 網頁應用安全</a></dt> + <dd>保護用戶數據,是任何網站設計的重要組成部分,我們以前解釋了 Web 安全文章中,一些更常見的安全威脅—本文提供了 Django 內置、如何保護處理這種危險的實際演示。</dd> +</dl> + +<h2 id="評估_2">評估</h2> + +<p>以下評估,將測試你對如何使用 Django 創建網站的理解,如上述指南中所列出的項目。</p> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django/django_assessment_blog">Django 小部落格 DIY</a></dt> + <dd>在這個評估中,你將使用你從本單元中學到的一些知識,來創建自己的部落格。</dd> +</dl> diff --git a/files/zh-tw/learn/server-side/django/introduction/index.html b/files/zh-tw/learn/server-side/django/introduction/index.html new file mode 100644 index 0000000000..f0a9e2caa5 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/introduction/index.html @@ -0,0 +1,306 @@ +--- +title: Django 介紹 +slug: Learn/Server-side/Django/Introduction +translation_of: Learn/Server-side/Django/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> + +<p class="summary"> 在這第一篇Django文章中,我們將回答“什麼是Django”這個問題,並概述這個網絡框架有什麼特性。我們將描述主要功能,包括一些高級功能,但我們並不會在本單元中詳細介紹。我們還會展示一些Django應用程序的主要構建模塊(儘管此時你還沒有要測試的開發環境)。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先備知識:</th> + <td>基本的電腦知識.對<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">服務器端網站編程的一般了解</a> ,特別是<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">網站中客戶端-服務器交互的機制</a> .</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>了解Django是什麼,它提供了哪些功能,以及Django應用程序的主要構建塊。</td> + </tr> + </tbody> +</table> + +<h2 id="什麼是_Django">什麼是 Django?</h2> + +<p>Django 是一個高級的 Python 網路框架,可以快速開發安全和可維護的網站。由經驗豐富的開發者構建,Django 負責處理網站開發中麻煩的部分,因此你可以專注於編寫應用程序,而無需重新開發。</p> + +<p>它是免費和開源的,有活躍繁榮的社區、豐富的文檔、以及很多免費和付費的解決方案。</p> + +<p>Django 可以使你的應用具有以下優點:</p> + +<dl> + <dt>完備</dt> + <dd>Django 遵循 “功能完備” 的理念,提供開發人員可能想要 “開箱即用” 的幾乎所有功能。因為你需要的一切,都是一個 ”產品“ 的一部分,它們都可以無縫結合在一起,遵循一致性設計原則,並且具有廣泛、和<a href="https://docs.djangoproject.com/en/2.0/">最新的文檔</a>。</dd> + <dt>通用</dt> + <dd> + <p>Django 可以(並已經)用於構建幾乎任何類型的網站—從內容管理系統和維基,到社交網絡和新聞網站。它可以與任何客戶端框架一起工作,並且可以提供幾乎任何格式(包括 HTML、RSS、JSON、XML等)的內容。你正在閱讀的網站就是基於 Django。</p> + + <p>在內部,儘管它為幾乎所有可能需要的功能(例如幾個流行的資料庫,模版引擎等)提供了選擇,但是如果需要,它也可以擴展到使用其他組件。</p> + </dd> + <dt>安全</dt> + <dd> + <p>Django 幫助開發人員,通過提供一個被設計為 “做正確的事情” 來自動保護網站的框架,來避免許多常見的安全錯誤。例如,Django 提供了一種安全的方式,來管理用戶帳號和密碼,避免了常見的錯誤,比如將 session 放在 cookie 中這種易受攻擊的做法(取而代之的是,cookies 只包含一個密鑰,實際數據存儲在數據庫中),或直接存儲密碼,而不是密碼的 hash 值。</p> + + <p>密碼 hash ,是讓密碼通過加密 hash 函數,而創建的固定長度值。 Django 能通過運行 hash 函數,來檢查輸入的密碼 - 就是將輸出的 hash 值,與存儲的 hash 值進行比較是否正確。然而由於功能的 “單向” 性質,假使存儲的 hash 值受到威脅,攻擊者也難以解出原始密碼。 (但其實有彩虹表-譯者觀點)</p> + + <p>默認情況下,Django 可以防範許多漏洞,包括 SQL 注入,跨站點腳本,跨站點請求偽造,和點擊劫持 (請參閱 網站安全 相關信息,如有興趣)。</p> + </dd> + <dt>可擴展</dt> + <dd>Django 使用基於組件的 “無共享” 架構 (架構的每一部分獨立於其他架構,因此可以根據需要進行替換或更改)。在不同部分之間,有明確的分隔,意味著它可以通過在任何級別添加硬件,來擴展服務:緩存服務器,數據庫服務器,或應用程序服務器。一些最繁忙的網站,已經在 Django 架構下成功地縮放了網站的規模大小,以滿足他們的需求(例如 Instagram 和 Disqus,僅舉兩個例子,可自行添加)。 </dd> + <dt>可維護</dt> + <dd>Django 代碼編寫,是遵照設計原則和模式,鼓勵創建可維護和可重複使用的代碼。特別是,它使用了不要重複自己(DRY)原則,所以沒有不必要的重複,減少了代碼的數量。 Django 還將相關功能,分組到可重用的 “應用程序” 中,並且在較低級別,將相關代碼分組或模塊( 模型視圖控制器 <a href="/en-US/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller (MVC)</a> 模式)。</dd> + <dt>可移植</dt> + <dd>Django 是用 Python 編寫的,它在許多平台上運行。這意味著,你不受任務特定的服務器平台的限制,並且可以在許多種類的 Linux,Windows 和 Mac OS X 上運行應用程序。此外,Django 得到許多網路託管提供商的好評,他們經常提供特定的基礎設施,和託管 Django 網站的文檔。</dd> +</dl> + +<h2 id="Django的起源">Django的起源?</h2> + +<p>Django 最初在 2003 年到 2005 年間,由負責創建和維護報紙網站的網絡團隊開發。在創建了許多網站後,團隊開始考慮、並重用許多常見的代碼和設計模式。這個共同的代碼,演變一個通用的網絡開發框架,2005 年 7 月,被開源為 “Django” 項目。</p> + +<p>Django 不斷發展壯大 — 從 2008 年 9 月的第一個里程碑版本(1.0),到最近發布的(2.0)-(2018)版本。每個版本都添加了新功能,和錯誤修復,從支持新類型的數據庫,模版引擎和緩存,到添加 “通用” 視圖函數和類別(這減少了開發人員在一些編程任務必須編寫的代碼量)。</p> + +<div class="note"> +<p><strong>注意</strong>: 查看 Django 網站上的發行說明 <a href="https://docs.djangoproject.com/en/2.0/releases/">release notes</a>,看看最近版本發生了什麼變化,以及 Django 能做多少工作</p> +</div> + +<p>Django 現在是一個蓬勃發展的合作開源項目,擁有數千個用戶和貢獻者。雖然它仍然具有反映其起源的一些功能,但 Django 已經發展成為,能夠開發任何類型的網站的多功能框架。</p> + +<h2 id="Django有多受歡迎">Django有多受歡迎?</h2> + +<p>服務器端框架的受歡迎程度沒有任何可靠和明確的測量(儘管<a class="external external-icon" href="http://hotframeworks.com/" rel="noopener">Hot Frameworks</a>網站嘗試使用諸如計算每個平台的GitHub項目數量和StackOverflow問題的機制來評估流行度)。一個更好的問題是Django是否“足夠流行”,以避免不受歡迎的平台的問題。它是否繼續發展?如果您需要幫助,可以幫您嗎?如果您學習Django,有機會獲得付費工作嗎?</p> + +<p>基於使用Django的流行網站數量,為代碼庫貢獻的人數以及提供免費和付費支持的人數,那麼是的,Django是一個流行的框架!</p> + +<p>使用Django的流行網站包括:Disqus,Instagram,騎士基金會,麥克阿瑟基金會,Mozilla,國家地理,開放知識基金會,Pinterest和開放棧(來源:<a class="external external-icon" href="https://www.djangoproject.com/" rel="noopener">Django home page</a> ).</p> + +<h2 id="Django_是特定用途的">Django 是特定用途的?</h2> + +<p>Web框架通常將自己稱為“特定”或“無限制”。</p> + +<p>特定框架是對處理任何特定任務的“正確方法”有意見的框架。他們經常支持特定領域的快速發展(解決特定類型的問題),因為正確的做法是通常被很好地理解和記錄在案。然而,他們在解決其主要領域之外的問題時可能不那麼靈活,並且傾向於為可以使用哪些組件和方法提供較少的選擇。</p> + +<p>相比之下,无限制的框架对于将组件粘合在一起以实现目标或甚至应使用哪些组件的最佳方式的限制较少。它们使开发人员更容易使用最合适的工具来完成特定任务,尽管您需要自己查找这些组件。</p> + +<p>Django“有點有意義”,因此提供了“兩個世界的最佳”。它提供了一組組件來處理大多數Web開發任務和一個(或兩個)首選的使用方法。然而,Django的解耦架構意味著您通常可以從多個不同的選項中進行選擇,也可以根據需要添加對全新的支持。</p> + +<h2 id="Django_代碼是什麼樣子">Django 代碼是什麼樣子?</h2> + +<p>在傳統的數據驅動網站中,Web應用程序會等待來自Web瀏覽器(或其他客戶端)的HTTP 請求。當接收到請求時,應用程序根據URL 和可能的<code>POST</code> 數據或<code>GET</code> 數據中的信息確定需要的內容。根據需要,可以從數據庫讀取或寫入信息,或執行滿足請求所需的其他任務。然後,該應用程序將返回對Web瀏覽器的響應,通常通過將檢索到的數據插入HTML模板中的佔位符來動態創建用於瀏覽器顯示的HTML 頁面。</p> + +<p>Django 網絡應用程序通常將處理每個步驟的代碼分組到單獨的文件中:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png"></p> + +<ul> + <li><strong>URLs: </strong>雖然可以通過單個功能來處理來自每個URL的請求,但是編寫單獨的視圖函數來處理每個資源是更加可維護的。URL映射器用於根據請求URL將HTTP請求重定向到相應的視圖。URL映射器還可以匹配出現在URL中的字符串或數字的特定模式,並將其作為數據傳遞給視圖功能。<br> + </li> + <li><strong>View:</strong> 視圖是一個請求處理函數,它接收HTTP請求並返回HTTP響應。視圖通過模型訪問滿足請求所需的數據,並將響應的格式委託給模板。<br> + </li> + <li><strong>Models:</strong> 模型是定義應用程序數據結構的Python對象,並提供在數據庫中管理(添加,修改,刪除)和查詢記錄的機制。<br> + </li> + <li><strong>Templates:</strong> 模板是定義文件(例如HTML頁面)的結構或佈局的文本文件,用於表示實際內容的佔位符。一個視圖可以使用HTML模板,從數據填充它動態地創建一個HTML頁面模型。可以使用模板來定義任何類型的文件的結構;它不一定是HTML!</li> +</ul> + +<div class="note"> +<p><strong>注意</strong> : Django將此組織稱為“模型視圖模板(MVT)”架構。它與更加熟悉的<a href="https://developer.mozilla.org/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller</a>架構有許多相似之處. </p> +</div> + +<ul> +</ul> + +<p>以下部分將為您提供Django應用程序的這些主要部分的想法(稍後我們將在進一步詳細介紹後,我們將在開發環境中進行更詳細的介紹)。</p> + +<h3 id="將請求發送到正確的視圖(urls.py)">將請求發送到正確的視圖(urls.py)</h3> + +<p>URL映射器通常存儲在名為urls.py的文件中。在下面的示例中,mapper(<code>urlpatterns</code>)定義了特定URL 模式和相應視圖函數之間的映射列表。如果接收到具有與指定模式匹配的URL(例如r'^$',下面)的HTTP請求,則將調用相關聯的視圖功能(例如 views.index)並傳遞請求。</p> + +<pre>urlpatterns = [ + <strong>path('admin/', admin.site.urls), + </strong>path('book/<int:id>/', views.book_detail, name='book_detail'), + path('catalog/', include('catalog.urls')), + re_path(r'^([0-9]+)/$', views.best), +] +</pre> + +<p><code>urlpatterns</code>對像是<code>path()</code>和/或<code>re_path()</code>函數的列表(Python列表使用方括號定義,其中項目用逗號分隔,可以有一個<a href="https://docs.python.org/2/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples">可選的尾隨逗號</a>。例如:[<code>item1, item2, item3,</code> ])。</p> + +<p>兩種方法的第一個參數,是將要匹配的路由(模式)。<code> path()</code>方法使用尖括號,來定義將被捕獲、並作為命名參數傳遞給視圖函數的 URL 的部分。<code> re_path()</code>函數使用靈活的模式匹配方法,稱為正則表達式。我們將在後面的文章中討論這些內容!</p> + +<p>第二個參數,是在匹配模式時將調用的另一個函數。註釋 <code>views.book_detail</code>表示該函數名為<code>book_detail()</code>,可以在名為<code>views</code>的模塊中找到(即在名為<code>views.py</code>的文件中)</p> + +<h3 id="處理請求(views.py)">處理請求(views.py)</h3> + + + +<p>視圖是Web應用程序的核心,從Web客戶端接收HTTP請求並返回HTTP響應。在兩者之間,他們編制框架的其他資源來訪問數據庫,渲染模板等。</p> + +<p>下面的例子顯示了一個最小的視圖功能index(),這可以通過我們的URL映射器在上一節中調用。像所有視圖函數一樣,它接收一個HttpRequest對像作為參數(request)並返回一個HttpResponse對象。在這種情況下,我們對請求不做任何事情,我們的響應只是返回一個硬編碼的字符串。我們會向您顯示一個請求,在稍後的部分中會提供更有趣的內容。</p> + + + +<pre class="brush: python">## filename: views.py (Django view functions) + +from django.http import HttpResponse + +def index(request): + # Get an HttpRequest - the request parameter + # perform operations using information from the request. + # Return HttpResponse + return HttpResponse('Hello from Django!') +</pre> + +<div class="note"> +<p><strong>注意</strong> :一點點Python:</p> + +<ul> + <li><a class="external external-icon" href="https://docs.python.org/3/tutorial/modules.html" rel="noopener">Python模塊</a>是函數的“庫”,存放在單獨的文件中,我們可能希望在代碼中使用。在這裡,我們只導入了<code>HttpResponse</code>從對象<code>django.http</code>模塊,使我們可以在視圖中使用<br> + <code>from django.http import HttpResponse</code>。<br> + 還有其他方法可以從模塊導入一些或所有對象</li> + <li>使用<code>def</code>,如上所示的關鍵字聲明函數,命參數在函數名稱後面的括號中列出:整行以冒號結尾。注意下一行是否都<strong>縮進</strong>。縮進很重要,因為它指定代碼行在該特定塊內(強制縮進是Python的一個關鍵特徵,而且是Python代碼很容易閱讀的一個原因)。</li> +</ul> + + +</div> + +<ul> +</ul> + +<p>視圖通常存放在一個名為<strong>views.py</strong>的文件中。</p> + +<h3 id="定義數據模型(models.py)">定義數據模型(models.py)</h3> + + + +<p>Django Web應用程序,通過被稱為模型的Python對象,來管理和查詢數據。模型定義存儲數據的結構,包括字段類型 以及字段可能的最大值,默認值,選擇列表選項,文檔幫助文本,表單的標籤文本等。模型的定義與底層數據庫無關-您可以選擇其中一個,作為項目設置的一部分。一旦您選擇了要使用的數據庫,您就不需要直接與之交談- 只需編寫模型結構和其他代碼,Django可以處理與數據庫通信的所有辛苦的工作。</p> + +<p>下面的代碼片段為<code>Team</code>對象,展示了一個非常簡單的Django模型。本<code>Team</code>類別是從Django的類別派生<code>models.Model</code>。它將團隊名稱和團隊級別,定義為字符字段,並為每個記錄指定了要存放的最大字符數。<code>team_level</code>可以是幾個值中的一個,因此,我們將其定義為一個選擇字段,並在被展示的數據、和被儲存的數據之間,建立映射,並設置一個默認值。</p> + + + +<pre class="brush: python"># filename: models.py + +from django.db import models + +class Team(models.Model): + team_name = models.CharField(max_length=40) + + TEAM_LEVELS = ( + ('U09', 'Under 09s'), + ('U10', 'Under 10s'), + ('U11', 'Under 11s'), + ... #list other team levels + ) + team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') +</pre> + +<div class="note"> +<p><strong>注意</strong> : Python小知識:</p> + +<ul> + <li>Python支持“面向對象編程”,這是一種編程風格,我們將代碼組織到對像中,其中包括用於對該對象進行操作的相關數據和功能。對像也可以從其他對象繼承/擴展/派生,允許相關對象之間的共同行為被共享。在Python中,我們使用關鍵字<strong>Class</strong>定義對象的“藍圖”。我們可以根據類中的模型創建類型的多個特定實例。</li> + <li>例如,我們有個<strong>Team</strong>類,它來自於<strong>Model</strong>類。這意味著它是一個模型,並且將包含模型的所有方法,但是我們也可以給它自己的專門功能。在我們的模型中,我們定義了我們的數據庫需要存儲我們的數據字段,給出它們的具體名稱。Django使用這些定義(包括字段名稱)來創建底層數據庫。</li> +</ul> + + +</div> + +<h3 id="查詢數據(views.py)">查詢數據(views.py)</h3> + + + +<p>Django模型提供了一個,用於搜索數據庫的簡單查詢API。這可以使用不同的標準(例如,精確,不區分大小寫,大於等等)來匹配多個字段,並且可以支持複雜語句(例如,您可以在擁有一個團隊的<strong>U11</strong>團隊上指定搜索名稱以“Fr ”開頭或以“al”結尾)。</p> + +<p>代碼片段顯示了一個視圖函數(資源處理程序),用於顯示我們所有的<strong>U09</strong>團隊。<strong>粗體</strong>顯示如何使用模型查詢API,過濾所有記錄,其中該 <strong>team_level</strong>字段,具有正確的文本“ <strong>U09</strong> ”(請注意,該條件如何filter()作為參數傳遞給該函數,該字段名稱和匹配類型由雙下劃線: <strong>team_level__exact</strong>)</p> + + + +<pre class="brush: python">## filename: views.py + +from django.shortcuts import render +from .models import Team + +def index(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, '/best/index.html', context) +</pre> + +<dl> +</dl> + +<p>此功能使用<strong>render</strong> ()功能創建<strong>HttpResponse</strong>發送回瀏覽器的功能。這個函數是一個快捷方式;它通過組合指定的HTML模版和一些數據來插入模版(在名為“ <strong>content</strong> ”的變量中提供)來創建一個<strong>HTML</strong>文件。在下一節中,我們將介紹如何在其中插入數據以創建<strong>HTML</strong>。</p> + +<h3 id="呈現數據(HTML模版)">呈現數據(HTML模版)</h3> + +<p>模板系統允許您使用佔位符指定輸出文檔的結構,以便在生成頁面時填充數據。模板通常用於創建HTML,但也可以創建其他類型的文檔。 Django支持其本機模板系統,和另一個流行的Python庫,名為 Jinja2(如果需要,它也可以支持其他系統)。</p> + +<p>代碼片段,顯示了上一節中<code>render()</code>函數調用的HTML模板的外觀。這個模板的編寫假設它在渲染時可以訪問名為<code>youngest_teams</code>的列表變量(包含在上面<code>render()</code>函數中的上下文變量<code>context</code>中)。在HTML框架內部,我們有一個表達式,首先檢查<code>youngest_teams</code>變量是否存在,然後在<code>for</code>循環中迭代它。在每次迭代中,模板在{{htmlelement(“li”)}}元素中顯示每個團隊的<code>team_name</code>值。</p> + +<pre class="brush: python">## filename: best/templates/best/index.html + +<!DOCTYPE html> +<html lang="en"> +<body> + + {% if youngest_teams %} + <ul> + {% for team in youngest_teams %} + <li>\{\{ team.team_name \}\}</li> + {% endfor %} + </ul> +{% else %} + <p>No teams are available.</p> +{% endif %} + +</body> +</html></pre> + +<h2 id="你還能做什麼?">你還能做什麼?</h2> + + + +<p>前面的部分,展示了幾乎每個Web應用程序將使用的主要功能:URL映射,視圖,模型和模版。Django提供的其他內容包括:</p> + +<ul> + <li><strong>表單</strong> : HTML表單用於收集用戶數據,以便在服務器上進行處理。Django簡化了表單創建,驗證和處理。</li> + <li><strong>用戶身份驗證和權限</strong> : Django包含了一個強大的用戶身份驗證和權限系統,該系統已經構建了安全性。</li> + <li><strong>緩存</strong> :與提供靜態內容相比,動態創建內容需要更大的計算強度(也更緩慢)。Django提供靈活的緩存,以便你可以存儲所有或部分的頁面。如無必要,不會重新呈現網頁。</li> + <li><strong>管理網站</strong> :當你使用基本骨架創建應用時,就已經默認包含了一個Django管理站點。它十分輕鬆地創建了一個管理頁面,使網站管理員能夠創建、編輯和查看站點中的任何數據模型。</li> + <li><strong>序列化數據</strong> : Django可以輕鬆地將數據序列化,並支持XML或JSON格式。這會有助於創建一個Web服務(Web服務指數據純粹為其他應用程序或站點所用,並不會在自己的站點中顯示),或是有助於創建一個由客戶端代碼處理、和呈現所有數據的網站。</li> +</ul> + + + +<h2 id="總結">總結</h2> + + + +<p>恭喜,您已經完成了Django之旅的第一步!您現在應該了解Django的主要優點,一些關於它的歷史,以及Django應用程序的每個主要部分可能是什麼樣子。您還應該了解Python編程語言的一些內容,包括列表,函數和類別的語法。</p> + +<p>您已經看到上面的一些真正的Django代碼,但與客戶端代碼不同,您需要設置一個開發環境來運行它。這是我們的下一步。</p> + + + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> + +<h2 id="本教學連結">本教學連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/models/index.html b/files/zh-tw/learn/server-side/django/models/index.html new file mode 100644 index 0000000000..c075d8d35a --- /dev/null +++ b/files/zh-tw/learn/server-side/django/models/index.html @@ -0,0 +1,475 @@ +--- +title: 'Django Tutorial Part 3: Using models' +slug: Learn/Server-side/Django/Models +translation_of: Learn/Server-side/Django/Models +--- +<div>,{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</div> + +<p class="summary"></p> + +<p class="summary">本文介紹如何為 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站定義模型。它解釋了模型是什麼、聲明的方式以及一些主要字段類型。它還簡要展示了您可以訪問模型數據的幾個主要方法。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教學 2: 創建骨架網站。</a></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td> + <p>能夠設計和創建自己的模型,選擇適當的欄位。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>Django Web 應用程序通過被稱為模型的 Python 對象,訪問和管理數據。模型定義儲存數據的結構,包括欄位類型、以及可能還有最大大小,默認值,選擇列表選項,幫助文檔,表單的標籤文本等。模型的定義與底層數據庫無關 — 你可以選擇其中一個,作為項目設置的一部分。一旦你選擇了要使用的數據庫,你就不需要直接與之交談 — 只需編寫模型結構和其他代碼,Django 可以處理與數據庫通信的所有繁瑣工作。</p> + +<p>本教程將介紹如何定義和訪問 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 範例網站的模型。</p> + +<h2 id="設計LocalLibrary模型"><font><font>設計LocalLibrary模型</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在你投入開始編寫模型之前,花幾分鐘時間考慮我們需要存放的數據、以及不同物件之間的關係。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們知道,我們需要存放書籍的信息(標題,摘要,作者,語言,類別,ISBN),並且我們可能有多個副本(具有全域唯一的ID,可用狀態等)。</font><font>我們可以存放更多關於作者的信息,而不僅僅是他的名字,或多個作者的相同或相似的名稱。</font><font>我們希望能根據書名,作者名,語言和類別對信息進行排序。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在設計模型時,為每個“物件”分別設置模型(相關信息分組)是有意義的。在這種情況下,明顯的物件是書籍,書本實例和作者。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你可能想要使用模型,來表示選擇列表選項(例如:選擇下拉列表),而不是硬編碼,將選項編寫進網站—這是當所有選項面臨未知、或改變時候的建議。在本網站,模型的明顯候選,包括書籍類型(例如:科幻小說,法國詩歌等)和語言(英語,法語,日語)。</font></font></p> + +<p><font><font>一旦我們已經決定了我們的模型和字段,我們需要考慮它們的關聯性。</font><font>Django允許你來定義一對一的關聯(</font></font><code>OneToOneField</code><font><font>),一對多(</font></font><code>ForeignKey</code><font><font>)和多對多(</font></font><code>ManyToManyField</code><font><font>)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>思考一下,在網站中,我們將定義模型展示在下面UML關聯圖中(下圖)。</font><font>如以上,我們創建了書的模型(書的通用細節),書本實例(系統中特定物理副本的書籍狀態),和作者。</font><font>我們也決定了各類型模型,以便通過管理界面創建/選擇值。</font><font>我們決定不給</font></font><code>BookInstance:status</code>一個模型 <font><font>—我們硬編碼了(</font></font><code>LOAN_STATUS</code><font><font>)的值,因為我們不希望其改變。</font><font>在每個框中,你可以看到模型名稱,字段名稱和類型,以及方法和返回類型。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該圖顯示模型之間的關係,包括它們的多重性。</font><font>多重性是圖中的數字,顯示可能存在於關係中的每個模型的數量(最大值和最小值)。</font><font>例如,盒子之間的連接線,顯示書和類型相關。</font><font>書模型中數字表明,一本書必須有一個或多個類型(想要多少就多少),而類型(</font></font>Genres<font><font>)模型線的另一端的數字(0..*),表明它可以有零個或多個關聯書本(可以有這個書籍類別,也有對應的書;也可以是有這個書籍類別,但沒有對應的書)。</font></font></p> + +<p><img alt="LocalLibrary Model UML" src="https://mdn.mozillademos.org/files/15646/local_library_model_uml.png" style="height: 660px; width: 977px;"></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>:下一節提供一個基本解釋模型的定義與使用,當你在讀的時候,也需要一邊考慮如何構建上圖中的每個模型。</font></font><br> + </p> +</div> + +<h2 id="模型入門"><font><font>模型入門</font></font></h2> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>本節簡要概述了模型定義,和一些重要的字段、和字段參數。</span></p> + +<h3 id="模型定義">模型定義</h3> + +<p><font><font>模型通常在 app 中的 </font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>models.py</font></font></strong><font><font><span> 檔案</span>中定義。</font><font>它們是繼承自 </font></font> <code>django.db.models.Model</code>的子類, 可以包括屬性,方法和描述性資料(metadata)。下面區段為一個名為<code>MyModelName</code>的「典型」模型範例碼:</p> + +<pre class="notranslate">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</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在下面章節中,我們將更詳細解釋模型的每個功能。</font></font></p> + +<h4 id="字段" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>字段</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>模型可以有任意數量的字段、任何類型的字段 — 每個字段都表示我們要存放在我們的一個資料庫中的一欄數據(a column of data)。</font><font>每筆資料庫記錄(列 row)將由每個字段值之一組成。</font><font>我們來看看上面看到的例子。</font></font></p> + +<pre class="syntaxbox notranslate">my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')</pre> + +<div class="hidden"> +<p>Our above example has a single field called <code>my_field_name</code>, of type <code>models.CharField</code> — which means that this field will contain strings of alphanumeric characters. The field types are assigned using specific classes, which determine the type of record that is used to store the data in the database, along with validation criteria to be used when values are received from an HTML form (i.e. what constitutes a valid value). The field types can also take arguments that further specify how the field is stored or can be used. In this case we are giving our field two arguments:</p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在上面例子中,有個叫 </font></font><code>my_field_name</code> 的單一字段<font><font>,其類型為 </font></font><code>models.CharField</code> <font><font><span> </span>— 這意味著這個字段將會包含字母、數字字符串。</font><font>使用特定的類別分配字段類型,這些類別,決定了用於將數據存放在資料庫中的記錄的類型,以及從HTML表單接收到值(即構成有效值)時使用的驗證標準。</font><font>字段類型還可以獲取參數,進一步指定字段如何存放或如何被使用。</font><font>在這裡的情況下,我們給了字段兩個參數:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>max_length=20</code><font><font><span> </span>— 表示此字段中值的最大長度為20個字符的狀態。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>help_text="Enter field documentation"</code><font><font><span> </span>— 提供一個幫助用戶的文本標籤,讓用戶知道當前透過HTML表單輸入時要提供什麼值。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>字段名稱用於在視圖和模版中引用它。</font><font>字段還有一個標籤,它被指定一個參數(</font></font><code>verbose_name</code><font><font>),或者通過大寫字段的變量名的第一個字母,並用空格替換下劃線(例如</font></font><code>my_field_name</code><font><font> 的默認標籤為 My field name )。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>如果模型在表單中呈現(例如:在管理站點中),則聲明該字段的順序,將影響其默認順序,但可能會被覆蓋。</font></font></p> + +<h5 id="常用字段參數">常用字段參數</h5> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>當聲明很多/大多數不同的字段類型時,可以使用以下常用參數:</span></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#help-text" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>help_text</font></font></a><font><font><span> </span>:提供HTML表單文本標籤(eg i在管理站點中),如上所述。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#verbose-name" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>verbose_name</font></font></a><font><font><span> </span>:字段標籤中的可讀性名稱,如果沒有被指定,Django將從字段名稱推斷默認的詳細名稱。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#default" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>default</font></font></a><font><font><span> </span>:該字段的默認值。</font><font>這可以是值或可呼叫物件(callable object),在這種情況下,每次創建新紀錄時都將呼叫該物件。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#null" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>null</font></font></a><font><font>:如為 <code>True</code>,即允許 Django 於資料庫該欄位寫入 <code>NULL</code>(但欄位型態如為 <code>CharField</code> 則會寫入空字串)。預設值是 <code>False</code>。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#blank" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>blank</font></font></a><font><font><span> </span>:如果</font></font><code><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>True</font></font></strong></code><font><font>,表單中的字段被允許為空白。</font><font>默認是<code>False</code>,這意味著Django的表單驗證將強制你輸入一個值。</font><font>這通常搭配 <code>NULL=True</code> 使用,因為如果要允許空值,你還希望數據庫能夠適當地表示它們。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#choices" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>choices</font></font></a><font><font><span> </span>:這是給此字段的一組選項。</font><font>如果提供這一項,預設對應的表單部件是「該組選項的列表」,而不是原先的標准文本字段。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#primary-key" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>primary_key</font></font></a><font><font><span> </span>:如果是True,將當前字段設置為模型的主鍵(主鍵是被指定用來唯一辨識所有不同表記錄的特殊數據庫欄位(column))。</font><font>如果沒有指定字段作為主鍵,則Django將自動為此添加一個字段。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>還有許多其他選項 — 你可以在</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-options" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>這裡看到完整的字段選項</font></font></a><font><font>。</font></font></p> + +<h5 id="常用字段類型">常用字段類型</h5> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>以下列表描述了一些更常用的字段類型。</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.CharField" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">CharField</a> </font></font><font><font>是用來定義短到中等長度的字段字符串。</font><font>你必須指定<code>max_length</code>要存儲的數據。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.TextField" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>TextField </font></font></a><font><font>用於大型任意長度的字符串。</font><font>你可以<code>max_length</code>為該字段指定一個字段,但僅當該字段以表單顯示時才會使用(不會在數據庫級別強制執行)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.IntegerField" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;" title="django.db.models.IntegerField">IntegerField</a> </font></font><font><font>是一個用於存儲整數(整數)值的字段,用於在表單中驗證輸入的值為整數。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datefield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">DateField</a> </font></font><font><font>和</font></font><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datetimefield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">DateTimeField</a> </font></font><font><font>用於存儲/表示日期和日期/時間信息(分別是<code>Python.datetime.date</code> 和 <code>datetime.datetime</code> 對象)。這些字段可以另外表明(互斥)參數 <code>auto_now=Ture</code> (在每次保存模型時將該字段設置為當前日期),<code>auto_now_add</code>(僅設置模型首次創建時的日期)和 <code>default</code>(設置默認日期,可以被用戶覆蓋)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#emailfield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">EmailField</a> </font></font><font><font>用於存儲和驗證電子郵件地址。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#filefield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">FileField</a> </font></font><font><font>和</font></font><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#imagefield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">ImageField</a> </font></font><font><font>分別用於上傳文件和圖像(<code>ImageField</code> 只需添加上傳的文件是圖像的附加驗證)。</font><font>這些參數用於定義上傳文件的存儲方式和位置。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#autofield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">AutoField</a> </font></font><font><font>是一種 </font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>IntegerField </font></font></strong><font><font>自動遞增的特殊類型。</font><font>如果你沒有明確指定一個主鍵,則此類型的主鍵將自動添加到模型中。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">ForeignKey</a> </font></font><font><font>用於指定與另一個數據庫模型的一對多關係(例如,汽車有一個製造商,但製造商可以製作許多汽車)。</font><font>關係的“一”側是包含密鑰的模型。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">ManyToManyField</a> </font></font><font><font>用於指定</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>多對多</font></font></a><font><font>關係(例如,一本書可以有幾種類型,每種類型可以包含幾本書)。</font><font>在我們的圖書館應用程序中,我們將非常類似地使用它們 ForeignKeys,但是可以用更複雜的方式來描述組之間的關係。</font><font>這些具有參數 <code>on_delete</code> 來定義關聯記錄被刪除時會發生什麼(例如,值 <code>models.SET_NULL</code> 將簡單地設置為值 NULL )。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>還有許多其他類型的字段,包括不同類型數字的字段(大整數,小整數,浮點數),布林值,URLs,唯一 ids 和其他 “時間相關” 的信息(持續時間,時間等)。</font><font>你可以查閱</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-types" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>完整列表</font></font></a><font><font><span> </span>.</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'></p> + +<h4 id="元數據Metadata"><font><font>元數據(Metadata)</font></font></h4> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>你可以通過宣告 class Meta 來宣告模型級別的元數據,如圖所示:</span></p> + +<pre class="brush: python notranslate">class Meta: + ordering = ['-my_field_name'] +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>此元數據最有用的功能之一是控制在查詢模型類型時返回之記錄的默認排序。</font><font>你可以透過在</font></font><code>ordering</code><font><font> 屬性的字段名稱列表中指定匹配順序來執行此操作,如上所示。排序將依賴字段的類型(字符串字段按字母順序排序,而日期字段按時間順序排序)。如上所示,你可以使用減號(-)前綴字段名稱以反轉排序順序。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>例如,如果我們選擇依照此預設來排列書單:</font></font></p> + +<pre class="brush: python notranslate">ordering = ['title', '-pubdate']</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>書單通過標題依據--字母排序--排列,從A到Z,然後再依每個標題的出版日期,從最新到最舊排列。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>另一個常見的屬性是 </font></font><code>verbose_name</code><font><font> ,一個 </font></font><code>verbose_name</code><font><font> 說明單數和複數形式的類別。</font></font></p> + +<pre class="brush: python notranslate">verbose_name = 'BetterName'</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>其他有用的屬性允許你為模型創建和應用新的“訪問權限”(預設權限會被自動套用),允許基於其他的字段排序,或聲明該類是”抽象的“(你無法創建的記錄基類,並將由其他型號派生)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>許多其他元數據選項控制模型中必須使用哪些數據庫以及數據的存儲方式。</font><font>(如果你需要模型映射一個現有數據庫,這會有用)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>完整有用的元數據選項在這裡</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/models/options/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Model metadata options</font></font></a><font><font><span> </span>(Django docs).</font></font></p> + +<h4 id="方法Methods" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>方法(Methods)</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>一個模型也可以有方法。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><strong><font><font>最起碼,在每個模型中,你應該定義標準的Python 類方法</font></font><code>__str__()</code> </strong><font><font> ,<strong>來為每個物件返回一個人類可讀的字符串</strong>。</font><font>此字符用於表示管理站點的各個記錄(以及你需要引用模型實例的任何其他位置)。</font><font>通常這將返回模型中的標題或名稱字段。</font></font></p> + +<pre class="brush: python notranslate">def __str__(self): + return self.field_name</pre> + +<p><font>Django 方法中另一個常用方法是</font> <code>get_absolute_url()</code> <font>,這函數返回一個在網站上顯示個人模型記錄的 URL(如果你定義了該方法,那麼 Django 將自動在“管理站點”中添加“在站點中查看“按鈕在模型的記錄編輯欄)。</font><code>get_absolute_url()</code><font>的典型示例如下:</font></p> + +<pre class="brush: python notranslate">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)]) +</pre> + +<div class="note"> +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><strong style="border: 0px; font-style: normal; font-weight: 700; margin: 0px; padding: 0px;"><font><font>注意</font></font></strong><font><font><span> </span>:假設你將使用URL</font></font><code>/myapplication/mymodelname/2</code> <font><font>來顯示模型的單個記錄(其中“2”是id特定記錄),則需要創建一個URL映射器來將響應和id傳遞給“模型詳細視圖” (這將做出顯示記錄所需的工作)。</font><font>以上示例中,</font></font><code>reverse()</code><font><font>函數可以“反轉”你的url映射器(在上訴命名為“model-detail-view”的案例中,以創建正確格式的URL。</font></font></p> + +<p style='font-style: normal; margin: 0px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><font><font>當然要做這個工作,你還是要寫URL映射,視圖和模版!</font></font></p> +</div> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>你可以定義一些你喜歡的其他方法,並從你的代碼或模版調用它們(只要它們不帶任何參數)。</span></p> + +<h3 id="模型管理">模型管理</h3> + +<p><font>一旦你定義了模型類,你可以使用它們來創建,更新或刪除記錄,並運行查詢獲取所有記錄或特定的記錄子集。</font><font>當我們定義我們的視圖,我們將展示給你在這個教程如何去做。</font></p> + +<h4 id="創建和修改記錄"><font><font>創建和修改記錄</font></font></h4> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>要創建一個記錄,你可以定義一個模型實例,然後呼叫 </span><code>save()</code><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>。</span></p> + +<pre class="brush: python notranslate"># Create a new record using the model's constructor. +record = MyModelName(my_field_name="Instance #1") + +# Save the object into the database. +record.save()</pre> + +<div class="note"> +<p><strong>註:</strong>如果沒有任何的欄位被宣告為<code>主鍵</code>,這筆新的紀錄會被自動的賦予一個主鍵並將主鍵欄命名為 <code>id</code>。上例的那筆資料被儲存後,試著查詢這筆紀錄會看到它被自動賦予 1 的編號。</p> +</div> + +<p>你可以透過「點(dot)的語法」取得或變更這筆新資料的欄位(字段)。你需要呼叫 <code>save()</code> 將變更過的資料存進資料庫。</p> + +<pre class="brush: python notranslate"># 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() +</pre> + +<h4 id="搜尋紀錄">搜尋紀錄</h4> + +<p>你可以使用模型的 <code>objects</code> 屬性(由 base class 提供)搜尋符合某個條件的紀錄。You can search for records that match a certain criteria using the model's attribute (provided by the base class).</p> + +<div class="note"> +<p><strong>Note</strong>: 要用"抽象的"模型還有欄位說明怎麼搜尋紀錄可能會有點令人困惑。我們會以一個Book模型,其包含<code>title</code>與<code>genre</code>字段,而genre 也是一個僅有<code>name</code>一個字段的模型。</p> +</div> + +<p>我們可以取得一個模型的所有紀錄,為一個 <code>QuerySet</code> 使用<code>objects.all()。</code> <code>QuerySet</code> 是一個可迭代的物件,表示他含有多個物件,而我們可以藉由迭代/迴圈取得每個物件。</p> + +<pre class="brush: python notranslate">all_books = Book.objects.all() +</pre> + +<p>Django的 <code>filter()</code> 方法讓我們可以透過符合特定文字或數值的字段篩選回傳的<code>QuerySet</code>。例如篩選書名裡有 "wild" 的書並且計算總數,如下面所示。</p> + +<pre class="brush: python notranslate">wild_books = Book.objects.filter(title__contains='wild') +number_wild_books = Book.objects.filter(title__contains='wild').count() +</pre> + +<p>要比對的字段與比對方法都要被定義在篩選的參數名稱裡,並且使用這個格式:<code>比對字段__比對方法</code> (請注意上方範例中的 <code>title</code> 與 <code>contains</code> 中間隔了兩個底線唷)。在上面我們使用大小寫區分的方式比對<code>title</code> 。還有很多比對方式可以使用: <code>icontains</code> (不區分大小寫), <code>iexact</code> (大小寫區分且完全符合), <code>exact</code> (不區分大小寫但完全符合) 還有 <code>in</code>, <code>gt</code> (大於), <code>startswith</code>, 之類的。<a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#field-lookups">全部的用法在這裡。</a></p> + +<p>有時候你會須要透過某個一對多的字段來篩選(例如一個 <code>外鍵</code>)。 這樣的狀況下,你可以使用兩個底線來指定相關模型的字段。例如透過某個特定的genre名稱篩選書籍,如下所示:</p> + +<pre class="brush: python notranslate"># 會比對到: Fiction, Science fiction, non-fiction etc. +books_containing_genre = Book.objects.filter(genre<strong>__</strong>name<strong>__</strong>icontains='fiction') +</pre> + +<div class="note"> +<p><strong>Note</strong>: 你可隨心地使用雙底線 (__) 來探索更多層的關係 (<code>ForeignKey</code>/<code>ManyToManyField</code>). 例如, 一本 <code>Book</code> 有許多不同的 types, 其進一步定義有參數 name 關聯的"cover":<code>type__cover__name__exact='hard'.</code></p> +</div> + +<p>還有很多是你可以用索引(queries)來做的,包含從相關的模型做向後查詢(backwards searches)、連鎖過濾器(chaining filters)、回傳「值的小集合」等。更多資訊可以到 <a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/">Making queries</a> (Django Docs) 查詢。</p> + +<h2 id="定義_LocalLibrary_模型">定義 LocalLibrary 模型</h2> + +<p>這部份我們會開始定義圖書館的模型。</p> + +<p>先打開 models.py (在 <em>/locallibrary/catalog/</em>),頁面的最上方可以看到樣板導入了 models 模組,其包含了模型的基本類別 <code>models.Model</code> ,能使我們的模型能夠繼承。</p> + +<pre class="brush: python notranslate">from django.db import models + +# Create your models here.</pre> + +<h3 id="書籍類型模型_Genre_model">書籍類型模型 (Genre model)</h3> + +<p>複製下方 <code>Genre</code> 模型的程式碼,並貼在你的 <code>models.py</code> 檔案底部,這個模型是用來儲存書籍類型的資訊 — 例如:該本書是否為科幻小說、羅曼史、軍事歷史等。</p> + +<p>就像先前提到的,我們以「模型」的方式建立一個書籍類型模型,而非以自由文本(free text)或者選擇列表(selection list)的方式,這樣做讓我們可以透過資料庫的形式而非硬編碼(hard coded)的方式來管理所有可能的值。</p> + +<pre class="brush: python notranslate">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</pre> + +<p>此模型有一個單一的 <code>CharField</code> 字段(<code>name</code>) 被用來描述書籍類別(限制輸入字元長度最多200個,同時也有提示文本(help_text) )。</p> + +<p>在模型最下方我們宣告一個 <code>__str__()</code> 方法來簡單回傳被特定一筆紀錄定義的書籍類別名稱。</p> + +<p>因為詳細名稱(verbose name)沒有被定義,所以字段在形式上會被稱為 <code>Name</code> 。</p> + +<h3 id="書本模型_Book_model">書本模型 (Book model)</h3> + +<p>複製下方 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">Book</span></font> 模型的程式碼,並貼在你的 <code>models.py</code> 檔案底部,這個 <code>Book</code> 模型一般來說代表一個可用書本的所有資訊,但並非包含特定的物理實例(physical instance)或者副本資訊(copy),此模型使用 <code>CharField</code> 來表示書的 <code>title</code> 和 <code>isbn</code> (國際標準書號)(note how the <code>isbn</code> specifies its label as "ISBN" using the first unnamed parameter because the default label would otherwise be "Isbn").,另外此模型使用 <code>TextField</code> 來存 <code>summary</code> ,因為此文本可能會很長。</p> + +<pre class="brush: python notranslate">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)]) + +</pre> + +<p>「書籍類別」(<code>genre</code>)是一個 <code>ManyToManyField</code> ,因此一本書可以有很多書籍類別,而一個書結類別也能夠對應到很多本書。作者(<code>author</code>)被宣告為外鍵(<code>ForeignKey</code>),因此每本書只會有一名作者,但一名作者可能會有多本書(實際上,一本書可能會有多名作者,不過這個案例不會有,所以在別的例子這種作法可能會有問題)</p> + +<p>在上面兩個宣告關聯性模型的敘述句內,關聯的對象都是用對象的模型類或字串的方式作為首個未具名參數的方式傳入句內做宣告。在關聯對象尚未被定義前,若要參照到該對象,必須使用該對象名稱字串的方式來宣告關聯性!還有一些 <code>author</code> 欄位的其它值得一提的參數:<code>null=True</code> 表示如果沒有作者的話,允許在資料庫中存入 <code>Null</code> 值;<code>on_delete=models.SET_NULL</code> 表示如果某筆作者紀錄被刪除的話,與該作者相關連的欄位都會被設成 <code>Null</code>。</p> + +<p>這個模型也定義了 <code>__str__()</code> ,使用書本的 <code>title</code> 字段來表示一筆 <code>Book</code> 的紀錄。而最後一個方法,<code>get_absolute_url()</code> ,則會回傳一個可以被用來存取該模型細節紀錄的 URL (要讓其有效運作,我們必須定義一個 URL 的映射,我們將其命名為 <code>book-detail</code> ,另外還得定義一個關聯示圖(view)與模板(template) )。</p> + +<h3 id="書本詳情模型_BookInstance_model">書本詳情模型 (BookInstance model)</h3> + +<p>接下來,複製下方 <code>BookInstance</code> 的模型,貼在其他模型下面,這個 <code>BookInstance</code> 模型表示一個特定的書籍副本(可會被某人借走),並且包含如「副本是否可用」、「預計歸還日期」、「版本說明」或「版本細節」等資訊,還有一個在圖書館中唯一的 id 。</p> + +<p>有些字段(fields)和方法(methods)現在你也熟悉了。此模型使用了:</p> + +<ul> + <li><code>ForeignKey</code> 用來辨識相關的 <code>Book</code> (每本書可以有很多副本,但每個副本只能有一個<code>Book</code>)</li> + <li><code>CharField</code> 用來表示該本書的版本說明(特定版本)</li> +</ul> + +<pre class="brush: python notranslate">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})'</pre> + +<p>我們額外宣告了一些新的字段(field)類別(types):</p> + +<ul> + <li><code>UUIDField</code> 被用來將 <code>id</code> 字段再這個模型中設定為 <code>primary_key</code> ,這類別的字段會分配一個全域唯一的值給每一個實例(instance),也就是任何一本你能在圖書館找到的書。</li> + <li><code>DateField</code> 會被用來設定 <code>due_back</code> 的日期(紀錄書本何時會被歸還,可再被使用,或者是否正在保養期),這個字段允許 <code>blank</code> 或 <code>null</code> 值,而當元數據模型 (<code>Class Meta</code>)收到請求(query)時也會使用此字段來做資料排序。</li> + <li> <code>status</code> 是一個 <code>CharField</code> 字段,用來定義一個選項列表。你可以看到,我們定義了一個包含「鍵-值對元組」的元組(tuple) 並使其成為選項的參數,鍵-值對中的值會陳列出來並可以被使用者選擇,當選項被選定後,鍵(key)也會被儲存下來。我們也設定了預設的鍵值為 "m" (maintenance) 用來表示當每本書在初始創造還未放上書架時是不可被使用的。</li> +</ul> + +<p>而 <code>__str__()</code> 模型用來表示 <code>BookInstance</code> 這個物件的「唯一 ID」和「相關之 <code>Book</code> 書本名稱(title)」的組合。</p> + +<div class="note"> +<p><strong>Note</strong>: 關於 Python 的小提醒:</p> + +<ul> + <li>從 Python3.6 開始,你可以使用「字串插值語法」(又稱做 f-string):<br> + <code>f'{self.id} ({self.book.title})'</code></li> + <li>在舊版 Python 這部分的教學中,我們則使用了另一種有效的 <a href="https://www.python.org/dev/peps/pep-3101/">formatted string</a> 語法<br> + (e.g. <code>'{0} ({1})'.format(self.id,self.book.title)</code>)</li> +</ul> +</div> + +<h3 id="作者模型Author_model">作者模型(Author model)</h3> + +<p>複製下方 <code>Author</code> 的模型程式碼並貼在 <strong>models.py</strong> 文件的最下方。</p> + +<p>現在所有的字段(fields)與方法(methods)你應該都熟悉了,此模型定義了作者的「名」、「姓」、「出生年月日」、「死亡日期(非必填)」。該模型也指定,預設情況下,<code>__str__()</code> 方法會回傳作者姓名(按照姓、名排序)。而 <code>get_absolute_url()</code> 方法會反轉 author-detail 的URL映射,來獲得顯示單個作者的URL。</p> + +<pre class="brush: python notranslate">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}' + +</pre> + +<h2 id="再次執行資料庫遷移database_migrations">再次執行資料庫遷移(database migrations)</h2> + +<p>你的所有模型都建立好了,現在必須再次執行你的資料庫 migrations 指令來將這些修改內容更信到資料庫中。</p> + +<pre class="notranslate"><code>python3 manage.py makemigrations +python3 manage.py migrate</code></pre> + +<h2 id="語言模型Language_model_—_挑戰">語言模型(Language model) — 挑戰</h2> + +<p>請想像一下,現在來了一位善心人士捐了一堆用不同語言寫的書(例如:波斯語),而你的挑戰是必須制定一個最好在我們的圖說館網站呈現的方式,並把它做成模組。</p> + +<p>幾件事情需要思考:</p> + +<ul> + <li>「語言」需要與 <code>Book</code> 、<code>BookInstance</code> 或其他物件(Object)相關聯嗎?</li> + <li>「不同語言」能以什麼形式來表示?<br> + 模型?自由文本字段(free text field)?硬編碼選擇列表(hard-coded selection list)?</li> +</ul> + +<p>當你決定好了,就開始動手吧!你可以在<a href="https://github.com/mdn/django-locallibrary-tutorial/blob/master/catalog/models.py">Github的這裡</a>看到我們是怎麼思考的。</p> + +<ul> +</ul> + +<ul> +</ul> + +<h2 id="小結">小結</h2> + +<p>在這篇文章我們學到如何定義模型,並且利用這些資訊來設計與實作適合的模型給 <em>LocalLibrary 網站。</em></p> + +<p><em>再來我們要稍微撇開建立網站,先來看看 Django 的管理站(Django Administration site),這個管理站能讓我們加入一些資料到圖書館中,讓我們再來能夠透過「示圖(views)與模板(templates)」(當然我們現在都還沒建立)來展示。</em></p> + +<h2 id="延伸閱讀">延伸閱讀</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial02/">Writing your first Django app, part 2</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/">Making queries</a> (Django Docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/">QuerySet API Reference</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/sessions/index.html b/files/zh-tw/learn/server-side/django/sessions/index.html new file mode 100644 index 0000000000..86b534adaf --- /dev/null +++ b/files/zh-tw/learn/server-side/django/sessions/index.html @@ -0,0 +1,185 @@ +--- +title: 'Django Tutorial Part 7: Sessions framework' +slug: Learn/Server-side/Django/Sessions +translation_of: Learn/Server-side/Django/Sessions +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">本教程擴展了我們的<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,為主頁添加了一個基於會話的訪問計數器。這是一個相對簡單的例子,但它確實顯示了,如何使用會話框架,為匿名用戶提供持久的行為。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand how sessions are used.</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>我們在之前的教程中創建的<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站允許用戶瀏覽目錄中的書籍和作者。 雖然內容是從數據庫動態生成的,但每個用戶在使用該網站時基本上都可以訪問相同的頁面和信息類型。</p> + +<p>在一個"真實"的庫中,您可能希望根據用戶以前對網站的使用,首選項等為單個用戶提供定制的體驗。例如,您可以隱藏或存儲用戶下次訪問網站時之前已確認的警告消息,或尊重他們的偏好(例如,他們希望在每個頁面上顯示的搜索結果的數量)。</p> + +<p>會話框架允許您實現這種行為,從而允許您基於每個站點訪問者存儲和檢索任意數據。</p> + +<h2 id="What_are_sessions">What are sessions?</h2> + +<p>Web瀏覽器和服務器之間的所有通信都是通過HTTP協議進行的,該協議是無狀態的。該協議是無狀態的事實意味著客戶端和服務器之間的消息是完全相互獨立的-沒有基於先前消息的“序列”或行為的概念。因此,如果您想擁有一個跟踪與客戶之間正在進行的關係的站點,則需要自己實施。</p> + +<p>會話是Django(以及大多數Internet)使用的機制,用於跟踪站點與特定瀏覽器之間的“狀態”。會話允許您在每個瀏覽器中存儲任意數據,並且只要瀏覽器連接,該數據就可用於站點。然後,與會話相關聯的單個數據項被一個``鍵''引用,該鍵既用於存儲又用於檢索數據。</p> + +<p>Django使用包含特殊會話ID的cookie來標識每個瀏覽器及其與站點的關聯會話。默認情況下,實際會話數據默認存儲在站點數據庫中(這比將數據存儲在cookie中更安全,因為cookie在cookie中更容易受到惡意用戶的攻擊)。您可以將Django配置為將會話數據存儲在其他位置(緩存,文件或是“安全” Cookie),但是默認位置是一個很好且相對安全的選擇。</p> + +<h2 id="Enabling_sessions">Enabling sessions</h2> + +<p>當我們<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/skeleton_website">創建框架網站</a>時(在教程2中),將自動啟用會話。</p> + +<p>在項目文件的<code>INSTALLED_APPS</code> 和<code>MIDDLEWARE</code> 部分中進行配置(<strong>locallibrary/locallibrary/settings.py</strong>),如下所示:</p> + +<pre class="brush: python notranslate">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.sessions',</strong> + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> + ....</pre> + +<h2 id="Using_sessions">Using sessions</h2> + +<p>您可以從<code>request</code> 參數(作為視圖的第一個參數傳入的<code>HttpRequest</code> )中訪問視圖中的<code>session</code> 屬性。 此會話屬性表示與當前用戶的特定連接(或更確切地說,與當前瀏覽器的連接,由該站點的瀏覽器cookie中的會話ID標識)。</p> + +<p><code>session</code> 屬性是一個類似於字典的對象,您可以在視圖中隨意讀取和寫入多次,並根據需要對其進行修改。 您可以執行所有正常的字典操作,包括清除所有數據,測試是否存在鍵,循環訪問數據等。儘管如此,在大多數情況下,您只會使用標準的``字典''API來獲取和設置值。</p> + +<p>下面的代碼片段顯示瞭如何獲取,設置和刪除與當前會話(瀏覽器)相關的鍵“ <code>my_car</code>”的某些數據。</p> + +<div class="note"> +<p><strong>注意</strong>: Django的一大優點是,您無需考慮將會話綁定到視圖中當前請求的機制。 如果我們在視圖中使用以下片段,我們將知道有關<code>my_car</code> 的信息僅與發送當前請求的瀏覽器相關聯。</p> +</div> + +<pre class="brush: python notranslate"># Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present +my_car = request.session['my_car'] + +# Get a session value, setting a default if it is not present ('mini') +my_car = request.session.get('my_car', 'mini') + +# Set a session value +request.session['my_car'] = 'mini' + +# Delete a session value +del request.session['my_car'] +</pre> + +<p>該API還提供了許多其他方法,這些方法主要用於管理關聯的會話cookie。 例如,有一些方法可以測試客戶端瀏覽器是否支持cookie,設置和檢查cookie到期日期以及從數據存儲中清除過期的會話。 您可以在如 <a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a> 找到完整的API(Django文檔)。</p> + +<h2 id="Saving_session_data">Saving session data</h2> + +<p>默認情況下,當會話已被修改(分配)或刪除時,Django僅保存到會話數據庫並將會話cookie發送給客戶端。 如果您要使用上一節中所示的會話密鑰更新某些數據,則無需擔心! 例如:</p> + +<pre class="brush: python notranslate"># This is detected as an update to the session, so session data is saved. +request.session['my_car'] = 'mini'</pre> + +<p>如果您要更新會話數據中的某些信息,則Django將不會識別您已對會話進行了更改並保存了數據(例如,如果要在“ <code>my_car</code>”數據中更改“ <code>wheels</code>”數據, 如下所示)。 在這種情況下,您需要將會話明確標記為已修改。</p> + +<pre class="brush: python notranslate"># Session object not directly modified, only data within the session. Session changes not saved! +request.session['my_car']['wheels'] = 'alloy' + +# Set session as modified to force data updates/cookie to be saved. +<code>request.session.modified = True</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:您可以更改行為,以便站點可以通過在您的項目設置(<strong>locallibrary/locallibrary/settings.py</strong>)中添加<code>SESSION_SAVE_EVERY_REQUEST = True</code> 來更新每個請求的數據庫/發送cookie。</p> +</div> + +<h2 id="Simple_example_—_getting_visit_counts">Simple example — getting visit counts</h2> + +<p>作為一個簡單的真實示例,我們將更新我們的庫以告知當前用戶他們訪問LocalLibrary主頁的次數。</p> + +<p>打開/ <strong>/locallibrary/catalog/views.py</strong>,然後進行以下粗體顯示的更改。</p> + +<pre class="brush: python notranslate">def index(request): + ... + + num_authors = Author.objects.count() # The 'all()' is implied by default. + +<strong> # Number of visits to this view, as counted in the session variable. + num_visits = request.session.get('num_visits', 0) + request.session['num_visits'] = num_visits + 1</strong> + +<strong> context = { + 'num_books': num_books, + 'num_instances': num_instances, + 'num_instances_available': num_instances_available, + 'num_authors': num_authors, + 'num_visits': num_visits, + }</strong> + + # Render the HTML template index.html with the data in the context variable. + return render(request, 'index.html', context=context)</pre> + +<p>在這裡,我們首先獲取<code>'num_visits'</code>會話密鑰的值,如果之前未設置,則將其設置為0。 每次接收到請求時,我們都將增加該值並將其存儲回會話中(對於下一次用戶訪問該頁面)。 然後將<code>num_visits</code> 變量傳遞到我們的上下文變量中的模板。</p> + +<div class="note"> +<p><strong>注意:</strong>我們也可能會在此處測試瀏覽器是否甚至支持cookie(例如,請參閱<a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a>)或設計我們的UI,以便無論是否支持cookie都無關緊要。</p> +</div> + +<p>將以下區塊底部看到的行添加到``動態內容''部分底部的主HTML模板(<strong>/locallibrary/catalog/templates/index.html</strong>)中以顯示上下文變量:</p> + +<pre class="brush: html notranslate"><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> + +<strong><p>You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.</p></strong> +</pre> + +<p>保存更改,然後重新啟動測試服務器。 每次刷新頁面時,數字都會更新。</p> + +<h2 id="總結">總結</h2> + +<p>現在,您知道使用會話來改善與匿名用戶的交互是多麼容易。</p> + +<p>在接下來的文章中,我們將說明身份驗證和授權(權限)框架,並向您展示如何支持用戶帳戶。</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">How to use sessions</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/skeleton_website/index.html b/files/zh-tw/learn/server-side/django/skeleton_website/index.html new file mode 100644 index 0000000000..b57b351eae --- /dev/null +++ b/files/zh-tw/learn/server-side/django/skeleton_website/index.html @@ -0,0 +1,388 @@ +--- +title: 'Django 教學 2: 創建一個骨架網站' +slug: Learn/Server-side/Django/skeleton_website +translation_of: Learn/Server-side/Django/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</div> + +<p class="summary">Django 教學的第二篇文章,會展示怎樣創建一個網站的"框架",在這個框架的基礎上,你可以繼續填充整站使用的 settings, urls,模型(models),視圖(views)和模板(templates )。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>創建 Django 的開發環境。複習 Django 教學。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>能夠使用 Django 提供的工具,搭建你自己的網站。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>這篇文章會展示怎樣創建一個網站的"框架",在這個框架的基礎上,你可以繼續填充整站使用的settings, urls,模型(models),視圖(views)和模板(templates)(我們會在接下來的文章裡討論)。</p> + +<p>搭建 “框架” 的過程很直接:</p> + +<ol> + <li>使用 <code style="font-style: normal; font-weight: normal; line-height: 1.5;">django-admin</code>工具創建工程的文件夾,基本的文件模板和工程管理腳本(<strong style="line-height: 1.5;">manage.py</strong>)。</li> + <li>用 <strong>manage.py</strong> 創建一個或多個<em>應用</em>。 + <div class="note"> + <p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 14px; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>一個網站可能由多個部分組成,比如,主要頁面,博客,wiki,下載區域等。</font><font>Django鼓勵將這些部分作為分開的應用開發。</font><font>如果這樣的話,在需要可以在不同的工程中復用這些應用。</font></font></p> + </div> + </li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>工程裡註冊新的應用。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>為每個應用分配url。</font></font></li> +</ol> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>為 <span> </span></font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>locallibrary</font></font></a><font><font><span> </span> 這個項目創建的網站文件夾和它的工程文件夾都命名為</font></font><em><font><font>locallibrary</font></font></em><font><font>。</font><font>我們只創建一個名為</font></font><em><font><font>catalog</font></font></em><font><font>的應用。</font><font>最高層的項目文件結構如下所示:</font></font></p> + +<pre class="brush: bash"><em>locallibrary/ # Website folder</em> + <strong>manage.py </strong># Script to run Django tools for this project (created using django-admin) + <em>locallibrary/ # Website/project folder </em>(created using django-admin) + <em>catalog/ # Application folder </em>(created using manage.py) +</pre> + +<p><font>接下來的部分,會詳細討論創建網站框架的過程,並會展示怎麼測試這些變化。</font><font>最後,我們會討論在這個階段裡,你可以設置的全站配置。</font></p> + +<h2 id="創建專案項目">創建專案項目</h2> + +<p>首先打開命令提示符/終端,確保您在<a href="/zh-TW/docs/Learn/Server-side/Django/development_environment#Using_a_virtual_environment">虛擬環境</a>中,導航到您要存放Django應用程序的位置(在文檔文件夾中,輕鬆找到它的位置),並為您的新網站,創建一個文件夾(在這種情況下:locallibrary)。然後使用cd命令進入該文件夾:</p> + +<pre class="brush: bash">mkdir locallibrary +cd locallibrary</pre> + +<p><font><font>用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>django-admin startproject</code><font><font>命令創建新項目,並進入該文件夾。</font></font></p> + +<pre class="brush: bash">django-admin startproject locallibrary +cd locallibrary</pre> + +<p><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>django-admin</code><font><font>工具會創建如下所示的文件夾結構</font></font></p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash"><em>locallibrary/</em> + manage.py + <em>locallibrary/</em> + __init__.py + settings.py + urls.py + wsgi.py</code></pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>locallibrary項目的子文件夾是整個網站的進入點:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><strong>__init__.py</strong> 是一個空文件,指示 Python 將此目錄視為 Python 套件。</li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>settings.py</font></font></strong><font><font><span> </span> 包含所有的網站設置。</font><font>這是可以註冊所有創建的應用的地方,也是靜態文件,數據庫配置的地方,等等。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>urls.py</font></font></strong><font><font>定義了網站url到view的映射</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>。</font></font></strong><font><font>雖然這裡可以包含所有的url,但是更常見的做法是把應用相關的url包含在相關應用中,你可以在接下來的教程裡看到。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><strong style="border: 0px; font-style: normal !important; line-height: 1.5; margin: 0px; padding: 0px;"><font><font>wsgi.py<span> </span></font></font></strong><span style="border: 0px; font-style: normal !important; line-height: 1.5; margin: 0px; padding: 0px;"><font><font> 幫助Django應用和網絡服務器間的通訊。</font><font>你可以把這個當作模板。</font></font></span></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>manage.py</font></font></strong><font><font>腳本可以創建應用,和資料庫通訊,啟動開發用網絡服務器。</font></font></p> + +<h2 id="創建_catalog_應用">創建 catalog 應用</h2> + +<p><font><font>接下來,在locallibrary項目裡,使用下面的命令創建catalog應用(和您項目的</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>manage.py</font></font></strong><font><font>在同一個文件夾下)</font></font></p> + +<pre class="brush: bash">python3 manage.py startapp catalog</pre> + +<div class="note"> +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><span style="border: 0px; font-size: 14px; font-style: normal !important; margin: 0px; padding: 0px;"><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>注意:</font></font></strong><font><font><span> </span>Linux/Mac OS X應用可以使用上面的命令。</font><font>在windows平台下應該改為:</font></font></span> <code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>py -3 manage.py startapp catalog</code></p> + +<p style='font-style: normal; margin: 0px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><font><font>如果你是windows系統,在這個部分用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>py -3</code><font><font>替代</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>python3</code><font><font>。</font></font></p> + +<p>如果您使用的是Python 3.7.0,則應使用<code>py manage.py startapp catalog</code></p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>這個工具創建了一個新的文件夾,並為該應用創建了不同的文件(下面黑體所示)。</font><font>絕大多數文件的命名和它們的目的有關(比如視圖函數就是</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>views.py,</font></font></strong><font><font>模型就是</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>models.py,</font></font></strong><font><font>測試是</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>tests.py,</font></font></strong><font><font>網站管理設置是</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>admin.py,</font></font></strong><font><font>註冊應用是</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>apps.py)</font></font></strong><font><font>,並且還包含了為項目所用的最小模板。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>執行命令後的文件夾結構如下所示:</font></font></p> + +<pre class="brush: bash"><em>locallibrary/</em> + manage.py + <em>locallibrary/ +</em><strong> <em>catalog/</em> + admin.py + apps.py + models.py + tests.py + views.py + __init__.py + <em>migrations/</em></strong> +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>除上面所說的文件外,我們還有:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>一個</font></font><em><font><font>migration</font></font></em><font><font>文件夾,用來存放 “migrations” ——當你修改你的數據模型時,這個文件會自動升級你的資料庫。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>__init__.py</font></font></strong><font><font><span> </span>—一個空文件,Django/Python會將這個文件作為</font></font><a class="external external-icon" href="https://docs.python.org/3/tutorial/modules.html#packages" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Python套件包</font></font></a><font><font>並允許你在項目的其他部分使用它。</font></font></li> +</ul> + +<div class="note"> +<p><span style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 14px; font-style: normal; font-weight: 400; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>注意</font></font></strong></span><font><font><span> </span>:你注意到上面的文件裡有些缺失嘛?</font><font>儘管有了 views 和 models 的文件,可是 url 映射,網站模板,靜態文件在哪裡呢?</font><font>我們會在接下來的部分展示如何創建它們(並不是每個網站都需要,不過這個例子需要)。</font></font></p> +</div> + +<h2 id="註冊catalog應用"><font><font>註冊catalog應用</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>既然應用已經創建好了,我們還必須在項目裡註冊它,以便工具在運行時它會包括在裡面(比如在數據庫裡添加模型時)。</font><font>在項目的settings裡,把應用添加進</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>INSTALLED_APPS</code><font><font> ,就完成了註冊。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開項目設置文件 <span> </span></font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>locallibrary/locallibrary/settings.py</font></font></strong><font><font>找到 <span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>INSTALLED_APPS</code><font><font> 列表裡的定義。</font><font>如下所示,在列表的最後添加新的一行。</font></font></p> + +<pre class="brush: bash">INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +<strong> 'catalog.apps.CatalogConfig', </strong> +]</pre> + +<p><font><font>新的這行,詳細說明了應用配置文件在(<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>CatalogConfig</code><font><font>) </font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>/locallibrary/catalog/apps.py</font></font></strong><font><font><span> </span> 裡,當你創建應用時就完成了這個過程。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>:注意到</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>INSTALLED_APPS已经有许多其他的应用了</code><font><font> (還有 </font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>MIDDLEWARE</code><font><font>,在settings的下面)。</font><font>這些應用為 <span> </span></font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Admin_site" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212);'><font><font>Django administration site</font></font></a><font><font><span> </span> 提供了支持和許多功能(包括會話,認證系統等)。</font></font></p> +</div> + +<h2 id="配置資料庫"><font><font>配置資料庫</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>現在可以為項目配置資料庫了——為了避免性能上的差異,最好在生產和開發中使用同一種資料庫。你可以在</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/settings/#databases" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>資料庫</font></font></a><font><font><span> </span> 裡找到不同的設置方法(Django文檔)。 </font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在這個項目裡,我們使用SQLite。</font><font>因為在展示用的數據庫中,我們不會有很多並發存取的行為。</font><font>同時,也因為SQLite不需要額外的配置工作。</font><font>你可以在</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>settings.py</font></font></strong><font><font>裡看到這個數據庫怎樣配置的。</font><font>(更多信息如下所示)</font></font></p> + +<pre class="brush: python">DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +</pre> + +<p><font>因為我們使用SQLite,不需要其他的設置了。</font><font>我們繼續吧!</font></p> + +<h2 id="其他項目設置"><font><font>其他項目設置</font></font></h2> + +<p><font><font>settings.py裡還包括其他的一些設置,現在只需要改變</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-TIME_ZONE" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>時區</font></font></a><font><font><span> </span>—改為和標準</font></font><a class="external external-icon" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>tz時區數據表</font></font></a><font><font><span> </span> 裡的字符串相同就可以了(數據表裡的TZ列有你想要的時區)。</font><font>把</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>TIME_ZONE</code><font><font>的值改為你的時區,比如</font></font></p> + +<pre class="brush: python">TIME_ZONE = 'Europe/London'</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>有兩個設置你現在不會用到,不過你應該留意:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>SECRET_KEY</code><font><font>. 這個密匙值,是Django網站安全策略的一部分。</font><font>如果在開發環境中,沒有保護好這個密匙,把代碼投入生產環境時,最好用不同的密匙代替。</font><font>(可能從環境變量或文件中讀取)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>DEBUG</code><font><font>. 這個會在debug日誌裡輸出錯誤信息,而不是輸入HTTP的返回碼。</font><font>在生產環境中,它應設置為false,因為輸出的錯誤信息,會幫助想要攻擊網站的人。</font></font></li> +</ul> + +<h2 id="鏈接URL映射器"><font><font>鏈接URL映射器</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在項目文件夾裡,創建網站時同時生成了URL映射器(</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>urls.py</font></font></strong><font><font>)。</font><font>儘管你可以用它來管理所有的URL映射,但是更常用的做法是把URL映射留到它們相關的應用中。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>locallibrary/locallibrary/urls.py</font></font></strong><font><font><span> </span> 注意指導文字解釋了一些使用URL映射器的方法。</font></font></p> + +<pre class="brush: python">"""locallibrary URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>URL映射通過</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>urlpatterns</code><font><font> 變量管理,它是一個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>path()</code><font><font>函數的Python列表。</font><font>每個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>path()</code><font><font>函數,要么將URL式樣(URL pattern)關聯到特定視圖(<span> </span></font></font><em><font><font>specific view)</font></font></em><font><font>,當模式匹配時將會顯示,要么關聯到某個URL式樣列表的測試代碼。</font><font>(第二種情況下,URL式樣是目標模型裡的“基本URL”).<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>urlpatterns</code><font><font> 列表初始化定義了單一函數,把所有帶有 </font></font>'admin/' 模式的 <font><font>URL,映射到<code>admin.site.urls</code>。這個函數,包含了Administration應用自己的URL映射定義。</font></font></p> + +<div class="note"> +<p><strong>注意</strong>: <code>path()</code>中的路由是一個字符串,用於定義要匹配的URL模式。該字符串可能包括一個命名變量(在尖括號中),例如<code>'catalog/<id>/'</code>。此模式將匹配<strong> /catalog/</strong><em>any_chars</em><strong>/</strong> 等URL,並將any_chars 作為參數名稱為<code>id</code> 的字符串,傳遞給視圖。我們將在後面的主題中,進一步討論路徑方法和路由模式</p> +</div> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>在</span><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>urlpatterns</code><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'> 列表的下面一行,插入下面的代码。</span>這個新項目包括一個 <code>path()</code> ,它使用模式 <code>catalog/</code> 轉發請求到模塊 <code>catalog.urls</code>(具有相對 URL <strong>/catalog/urls.py </strong>的文件)。</p> + +<pre class="brush: python"># Use include() to add paths from the catalog application +from django.conf.urls import include +from django.urls import path + +urlpatterns += [ + path('catalog/', include('catalog.urls')), +] +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>現在我們把我們網站的根URL(例如</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>127.0.0.1:8000</code><font><font>),重新導向URL<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>127.0.0.1:8000/catalog/</code><font><font>;這是項目中唯一的應用,所以我們最好這樣做。</font><font>為了完成這個目標,我們使用一個特別的視圖函數(<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>RedirectView</code><font><font>),當</font></font><code>path()</code><font><font>函數中的 url 式樣被識別以後(在這個例子中是根 url),就會把第一個參數,也就是新的相對 URL ,重定向到(</font></font><code>/catalog/</code><font><font>)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>把下面的代碼加到文件最後:</font></font></p> + +<pre class="brush: python">#Add URL maps to redirect the base URL to our application +from django.views.generic import RedirectView +urlpatterns += [ + path('', RedirectView.as_view(url='/catalog/')), +]</pre> + +<p>將路徑函數的第一個參數留空,用以表示'/'。如果您將第一個參數寫為'/',Django會在您啟動開發服務器時給出以下警告:</p> + +<pre class="brush: python">System check identified some issues: + +WARNINGS: +?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'. +Remove this slash as it is unnecessary. +If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'. +</pre> + +<p>Django默認不提供CSS,JavaScript和圖像等靜態文件,但在創建站點時,開發Web服務器這樣做是有用的。作為此URL映射器的最終添加,您可以通過附加以下幾行,在開發期間啟用靜態文件的提供。</p> + +<p>現在將以下最終區塊,添加到文件的底部:</p> + +<pre><code># Use static() to add url mapping to serve static files during development (only) +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>: 有許多方法可以擴充<code>urlpatterns</code>列表(上面我們只是使用<code>+= </code>運算符,附加一個新的列表項,來清楚地分隔舊代碼和新代碼)。我們可以改為在原始列表定義中,包含這個新的模式映射:</p> + +<pre class="brush: python">urlpatterns = [ + path('admin/', admin.site.urls), + path('catalog/', include('catalog.urls')), + path('', RedirectView.as_view(url='/catalog/', permanent=True)), +] + <code>static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code> +</pre> + +<p>此外,我們將導入行(<code>from django.urls import include</code>)包含在使用它的代碼中(因此很容易看到我們添加的內容),但通常將所有導入行包含在一個Python文件的頂部。</p> +</div> + +<p>最後一步,在<strong>catalog</strong>文件夾中,創建一個名為<strong>urls.py</strong>的文件,並添加以下文本,以定義(空)導入的<code>urlpatterns</code>。這是我們在構建應用程序時,添加模式的地方。</p> + +<pre class="brush: python">from django.urls import path +from . import views + + +urlpatterns = [ + +] +</pre> + +<h2 id="測試網站框架"><font><font>測試網站框架</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>現在我們有了一個完整的框架項目。</font><font>這個網站現在還什麼都不能做,但是我們仍然要運行,以確保我們的更改是有效的。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在運行前,我們應該先運行</font></font><em><font><font>數據庫遷移</font></font></em><font><font>。</font><font>這會更新我們的數據庫並且包含所有安裝的應用(同時去除一些警告)。</font></font></p> + +<h3 id="運行資料庫遷移">運行資料庫遷移</h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>Django使用對象關係映射器(ORM),將Django代碼中的模型定義,映射到底層資料庫使用的數據結構。</font><font>當我們更改模型定義時,Django會跟踪更改,並創建資料庫遷移腳本(位於<span> </span></font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/locallibrary/catalog/migrations/</font></font></strong><font><font><span> </span>),來自動遷移資料庫中的底層數據結構。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>當我們創建網站時,Django會自動添加一些模型,供網站的管理部分使用(稍後我們會解釋)。</font><font>運行以下命令,來定義資料庫中這些模型的表(確認你位於包含 </font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>manage.py </font></font></strong><font><font>的目錄中):</font></font></p> + +<pre class="brush: bash">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<div class="warning"> +<p><strong>重要</strong>: <span style='background-color: #ffe7e8; color: #333333; display: inline !important; float: none; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>每次模型改變,都需要運行以上命令,來影響需要存放的數據結構(包括添加和刪除整個模型和單個字段)。</span></p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>makemigrations</code></strong><font><font>命令,創建(但不實施)項目中安裝的所有應用程序的遷移(你可以指定應用程序名稱,也可以為單個項目運行遷移)。</font><font>這讓你有機會在應用這些遷移之前,檢查這些遷移代碼—當你是Django專家時,你可以選擇稍微調整它們。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>這個 </font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>migrate</code></strong><font><font>命令,真正對你的資料庫實施遷移(Django跟踪哪些已添加到當前資料庫)。</font></font></p> + +<div class="note"> +<p><strong>注意</strong>: 參見 <a href="https://docs.djangoproject.com/en/2.0/topics/migrations/">Migrations</a> (Django 文件) <span style='background-color: #fff3d4; color: #333333; display: inline !important; float: none; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>,了解較少使用的遷移命令的其他信息。</span></p> +</div> + +<h3 id="運行網站">運行網站</h3> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>在開發期間,你首先要使用開發網頁服務器,然後用你本機的瀏覽器觀看,來測試你的網站。</span></p> + +<div class="note"> +<p><strong>注意</strong>: <font><font>這個開發網頁服務器並不夠強大,不足以用於生產使用,但是它使你在開發期間,能非常容易獲得你的 Django 網站和運行它,以此來進行快速測試。默認情況下,服務器會開通(http://127.0.0.1:8000/),但你也可以選擇其他端口。有關更多信息,查閱(</font></font><a class="external external-icon" href="https://docs.djangoproject.com/en/1.10/ref/django-admin/#runserver" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>django-admin and manage.py: runserver</font></font></a><font><font>)(Django docs).</font></font></p> +</div> + +<p><font><font>通過如下</font></font><code>runserver</code><font><font>命令,運行開發網頁服務器。(同樣的要在</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>manage.py</font></font></strong><font><font>的目錄)</font></font></p> + +<pre class="brush: bash">python3 manage.py runserver + + Performing system checks... + + System check identified no issues (0 silenced). + September 22, 2016 - 16:11:26 + Django version 1.10, using settings 'locallibrary.settings' + Starting development server at http://127.0.0.1:8000/ + Quit the server with CTRL-BREAK. +</pre> + +<p><font><font>一旦服務器運行,你可以用你的瀏覽器導航到</font></font><a class="external external-icon" href="http://127.0.0.1:8000/" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>http://127.0.0.1:8000/</font></font></a><font><font> 查看。你應該會看到一個錯誤頁面,如下。</font></font></p> + +<p><img alt="Django Debug page for Django 2.0" src="https://mdn.mozillademos.org/files/15729/django_404_debug_page.png"></p> + +<p><font><font>別擔心,這個錯誤頁面是預期的結果。</font><font>因為我們沒有在</font></font> <code>catalogs.urls</code><font><font>模塊中,定義任何頁面或網址(即是當我們使用一個指向根目錄的URL時,會被重新定向的地方)。</font></font></p> + +<div class="note"> +<p><strong>注意</strong>: 上面的頁面,演示了一個很棒的Django功能 - 自動除錯日誌記錄。只要找不到頁面,或者代碼引發任何錯誤,就會顯示錯誤畫面,其中包含有用的信息。在這種情況下,我們可以看到我們提供的URL,與我們的任何URL模式都不匹配(如列出的那樣)。在生產期間(當我們將網站放在網上時),日誌記錄將被關閉,在這種情況下,將提供信息量較少、但用戶友好的頁面。</p> +</div> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>這個時候,我們知道Django正在工作!</span></p> + +<div class="note"> +<p><strong>注意</strong>: <font>在進行重大更改時,你應該重新運行遷移,並重新測試站點。這不需要很長時間!</font></p> +</div> + +<h2 id="挑戰自我"><font><font>挑戰自我</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該 </font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>catalog/ <span> </span></font></font></strong><font><font>目錄包含應用程序的視圖、模型、和應用的其他部分,你可以打開這些文件並查看樣板。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>如上所述,管理站點的URL映射,已經添加到項目的</font></font> <strong>urls.py</strong><font><font>。</font><font>在瀏覽器中查看管理區域,看看會發生什麼(你可以從上面映射,推斷正確的URL)。</font></font></p> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>你現在已經創建了一個完整的骨架網站項目,你可以繼續加入網址、模型、視圖、和模版。</font></font></p> + +<p><font><font>現在,</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>Local Library website</font></font></a><font><font>的骨架已經完成並運行了,是時候開始</font></font>寫些代碼,讓網站做些它應該做的事了。</p> + +<h2 id="參見">參見</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial01/">Writing your first Django app - part 1</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/applications/#configuring-applications">Applications</a> (Django Docs). Contains information on configuring applications.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</p> + + + +<h2 id="本教程連結">本教程連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/testing/index.html b/files/zh-tw/learn/server-side/django/testing/index.html new file mode 100644 index 0000000000..d559585a50 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/testing/index.html @@ -0,0 +1,907 @@ +--- +title: 'Django Tutorial Part 10: Testing a Django web application' +slug: Learn/Server-side/Django/Testing +translation_of: Learn/Server-side/Django/Testing +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</div> + +<p class="summary">隨著網站的增長,他們越來越難以手動測試。不僅要進行更多的測試,而且隨著組件之間的互動,變得越來越複雜,一個區域的小改變,可能會影響到其他區域,所以需要做更多的改變,來確保一切正常運行,並且在進行更多更改時,不會引入錯誤。減輕這些問題的一種方法,是編寫自動化測試,每當您進行更改時,都可以輕鬆可靠地運行測試。本教程演示如何使用 Django 的測試框架,自動化您的網站的單元測試。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Complete all previous tutorial topics, including <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand how to write unit tests for Django-based websites.</td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>The <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> currently has pages to display lists of all books and authors, detail views for <code>Book</code> and <code>Author</code> items, a page to renew <code>BookInstance</code>s, and pages to create, update, and delete <code>Author</code> items (and <code>Book</code> records too, if you completed the <em>challenge</em> in the <a href="/en-US/docs/Learn/Server-side/Django/Forms">forms tutorial</a>). Even with this relatively small site, manually navigating to each page and <em>superficially</em> checking that everything works as expected can take several minutes. As we make changes and grow the site, the time required to manually check that everything works "properly" will only grow. If we were to continue as we are, eventually we'd be spending most of our time testing, and very little time improving our code.</p> + +<p>Automated tests can really help with this problem! The obvious benefits are that they can be run much faster than manual tests, can test to a much lower level of detail, and test exactly the same functionality every time (human testers are nowhere near as reliable!) Because they are fast, automated tests can be executed more regularly, and if a test fails, they point to exactly where code is not performing as expected.</p> + +<p>In addition, automated tests can act as the first real-world "user" of your code, forcing you to be rigorous about defining and documenting how your website should behave. Often they are basis for your code examples and documentation. For these reasons, some software development processes start with test definition and implementation, after which the code is written to match the required behavior (e.g. <a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven</a> and <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">behaviour-driven</a> development).</p> + +<p>This tutorial shows how to write automated tests for Django, by adding a number of tests to the <em>LocalLibrary</em> website.</p> + +<h3 id="Types_of_testing">Types of testing</h3> + +<p>There are numerous types, levels, and classifications of tests and testing approaches. The most important automated tests are:</p> + +<dl> + <dt>Unit tests</dt> + <dd>Verify functional behavior of individual components, often to class and function level.</dd> + <dt>Regression tests</dt> + <dd>Tests that reproduce historic bugs. Each test is initially run to verify that the bug has been fixed, and then re-run to ensure that it has not been reintroduced following later changes to the code.</dd> + <dt>Integration tests</dt> + <dd>Verify how groupings of components work when used together. Integration tests are aware of the required interactions between components, but not necessarily of the internal operations of each component. They may cover simple groupings of components through to the whole website.</dd> +</dl> + +<div class="note"> +<p><strong>Note: </strong>Other common types of tests include black box, white box, manual, automated, canary, smoke, conformance, acceptance, functional, system, performance, load, and stress tests. Look them up for more information.</p> +</div> + +<h3 id="What_does_Django_provide_for_testing">What does Django provide for testing?</h3> + +<p>Testing a website is a complex task, because it is made of several layers of logic – from HTTP-level request handling, queries models, to form validation and processing, and template rendering.</p> + +<p>Django provides a test framework with a small hierarchy of classes that build on the Python standard <code><a href="https://docs.python.org/3/library/unittest.html#module-unittest" title="(in Python v3.5)">unittest</a></code> library. Despite the name, this test framework is suitable for both unit and integration tests. The Django framework adds API methods and tools to help test web and Django-specific behaviour. These allow you to simulate requests, insert test data, and inspect your application's output. Django also provides an API (<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>) and tools for <a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/#other-testing-frameworks">using different testing frameworks</a>, for example you can integrate with the popular <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> framework to simulate a user interacting with a live browser.</p> + +<p>To write a test you derive from any of the Django (or <em>unittest</em>) test base classes (<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#simpletestcase">SimpleTestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#transactiontestcase">TransactionTestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>) and then write separate methods to check that specific functionality works as expected (tests use "assert" methods to test that expressions result in <code>True</code> or <code>False</code> values, or that two values are equal, etc.) When you start a test run, the framework executes the chosen test methods in your derived classes. The test methods are run independently, with common setup and/or tear-down behaviour defined in the class, as shown below.</p> + +<pre class="brush: python">class YourTestClass(TestCase): + + def setUp(self): + #Setup run before every test method. + pass + + def tearDown(self): + #Clean up run after every test method. + pass + + def test_something_that_will_pass(self): + self.assertFalse(False) + + def test_something_that_will_fail(self): + self.assertTrue(False) +</pre> + +<p>The best base class for most tests is <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a>. This test class creates a clean database before its tests are run, and runs every test function in its own transaction. The class also owns a test <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client" title="django.test.Client">Client</a> that you can use to simulate a user interacting with the code at the view level. In the following sections we're going to concentrate on unit tests, created using this <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a> base class.</p> + +<div class="note"> +<p><strong>Note:</strong> The <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a> class is very convenient, but may result in some tests being slower than they need to be (not every test will need to set up its own database or simulate the view interaction). Once you're familiar with what you can do with this class, you may want to replace some of your tests with the available simpler test classes.</p> +</div> + +<h3 id="What_should_you_test">What should you test?</h3> + +<p>You should test all aspects of your own code, but not any libraries or functionality provided as part of Python or Django.</p> + +<p>So for example, consider the <code>Author</code> model defined below. You don't need to explicitly test that <code>first_name</code> and <code>last_name</code> have been stored properly as <code>CharField</code> in the database because that is something defined by Django (though of course in practice you will inevitably test this functionality during development). Nor do you need to test that the <code>date_of_birth</code> has been validated to be a date field, because that is again something implemented in Django.</p> + +<p>However you should check the text used for the labels (<em>First name, Last_name, Date of birth, Died</em>), and the size of the field allocated for the text (<em>100 chars</em>), because these are part of your design and something that could be broken/changed in future.</p> + +<pre class="brush: python">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name)</pre> + +<p>Similarly, you should check that the custom methods <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> and <code style="font-style: normal; font-weight: normal;">__str__()</code> behave as required because they are your code/business logic. In the case of <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> you can trust that the Django <code>reverse()</code> method has been implemented properly, so what you're testing is that the associated view has actually been defined.</p> + +<div class="note"> +<p><strong>Note:</strong> Astute readers may note that we would also want to constrain the date of birth and death to sensible values, and check that death comes after birth. In Django this constraint would be added to your form classes (although you can define validators for the fields these appear to only be used at the form level, not the model level).</p> +</div> + +<p>With that in mind lets start looking at how to define and run tests.</p> + +<h2 id="Test_structure_overview">Test structure overview</h2> + +<p>Before we go into the detail of "what to test", let's first briefly look at <em>where</em> and <em>how</em> tests are defined.</p> + +<p>Django uses the unittest module’s <a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery" title="(in Python v3.5)">built-in test discovery</a>, which will discover tests under the current working directory in any file named with the pattern <strong>test*.py</strong>. Provided you name the files appropriately, you can use any structure you like. We recommend that you create a module for your test code, and have separate files for models, views, forms, and any other types of code you need to test. For example:</p> + +<pre>catalog/ + /tests/ + __init__.py + test_models.py + test_forms.py + test_views.py +</pre> + +<p>Create a file structure as shown above in your <em>LocalLibrary</em> project. The <strong>__init__.py</strong> should be an empty file (this tells Python that the directory is a package). You can create the three test files by copying and renaming the skeleton test file <strong>/catalog/tests.py</strong>.</p> + +<div class="note"> +<p><strong>Note:</strong> The skeleton test file <strong>/catalog/tests.py</strong> was created automatically when we <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">built the Django skeleton website</a>. It is perfectly "legal" to put all your tests inside it, but if you test properly, you'll quickly end up with a very large and unmanageable test file.</p> + +<p>Delete the skeleton file as we won't need it.</p> +</div> + +<p>Open <strong>/catalog/tests/test_models.py</strong>. The file should import <code>django.test.TestCase</code>, as shown:</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. +</pre> + +<p>Often you will add a test class for each model/view/form you want to test, with individual methods for testing specific functionality. In other cases you may wish to have a separate class for testing a specific use case, with individual test functions that test aspects of that use-case (for example, a class to test that a model field is properly validated, with functions to test each of the possible failure cases). Again, the structure is very much up to you, but it is best if you are consistent.</p> + +<p>Add the test class below to the bottom of the file. The class demonstrates how to construct a test case class by deriving from <code>TestCase</code>.</p> + +<pre class="brush: python">class YourTestClass(TestCase): + + @classmethod + def setUpTestData(cls): + print("setUpTestData: Run once to set up non-modified data for all class methods.") + pass + + def setUp(self): + print("setUp: Run once for every test method to setup clean data.") + pass + + def test_false_is_false(self): + print("Method: test_false_is_false.") + self.assertFalse(False) + + def test_false_is_true(self): + print("Method: test_false_is_true.") + self.assertTrue(False) + + def test_one_plus_one_equals_two(self): + print("Method: test_one_plus_one_equals_two.") + self.assertEqual(1 + 1, 2)</pre> + +<p>The new class defines two methods that you can use for pre-test configuration (for example, to create any models or other objects you will need for the test):</p> + +<ul> + <li><code>setUpTestData()</code> is called once at the beginning of the test run for class-level setup. You'd use this to create objects that aren't going to be modified or changed in any of the test methods.</li> + <li><code>setUp()</code> is called before every test function to set up any objects that may be modified by the test (every test function will get a "fresh" version of these objects).</li> +</ul> + +<div class="note"> +<p>The test classes also have a <code>tearDown()</code> method which we haven't used. This method isn't particularly useful for database tests, since the <code>TestCase</code> base class takes care of database teardown for you.</p> +</div> + +<p>Below those we have a number of test methods, which use <code>Assert</code> functions to test whether conditions are true, false or equal (<code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code>). If the condition does not evaluate as expected then the test will fail and report the error to your console.</p> + +<p>The <code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code> are standard assertions provided by <strong>unittest</strong>. There are other standard assertions in the framework, and also <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#assertions">Django-specific assertions</a> to test if a view redirects (<code>assertRedirects</code>), to test if a particular template has been used (<code>assertTemplateUsed</code>), etc.</p> + +<div class="note"> +<p>You should <strong>not </strong>normally include <strong>print()</strong> functions in your tests as shown above. We do that here only so that you can see the order that the setup functions are called in the console (in the following section).</p> +</div> + +<h2 id="How_to_run_the_tests">How to run the tests</h2> + +<p>The easiest way to run all the tests is to use the command:</p> + +<pre class="brush: bash">python3 manage.py test</pre> + +<p>This will discover all files named with the pattern <strong>test*.py</strong> under the current directory and run all tests defined using appropriate base classes (here we have a number of test files, but only <strong>/catalog/tests/test_models.py</strong> currently contains any tests.) By default the tests will individually report only on test failures, followed by a test summary.</p> + +<div class="note"> +<p>If you get errors similar to: <code>ValueError: Missing staticfiles manifest entry ...</code> this may be because testing does not run <em>collectstatic</em> by default and your app is using a storage class that requires it (see <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict">manifest_strict</a> for more information). There are a number of ways you can overcome this problem - the easiest is to simply run <em>collectstatic</em> before running the tests:</p> + +<pre class="brush: bash">python3 manage.py collectstatic +</pre> +</div> + +<p>Run the tests in the root directory of <em>LocalLibrary</em>. You should see an output like the one below.</p> + +<pre class="brush: bash">>python3 manage.py test + +Creating test database for alias 'default'... +<strong>setUpTestData: Run once to set up non-modified data for all class methods. +setUp: Run once for every test method to setup clean data. +Method: test_false_is_false. +.setUp: Run once for every test method to setup clean data. +Method: test_false_is_true. +FsetUp: Run once for every test method to setup clean data. +Method: test_one_plus_one_equals_two.</strong> +. +====================================================================== +FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true + self.assertTrue(False) +AssertionError: False is not true + +---------------------------------------------------------------------- +Ran 3 tests in 0.075s + +FAILED (failures=1) +Destroying test database for alias 'default'...</pre> + +<p>Here we see that we had one test failure, and we can see exactly what function failed and why (this failure is expected, because <code>False</code> is not <code>True</code>!).</p> + +<div class="note"> +<p><strong>Tip: </strong>The most important thing to learn from the test output above is that it is much more valuable if you use descriptive/informative names for your objects and methods.</p> +</div> + +<p>The text shown in <strong>bold</strong> above would not normally appear in the test output (this is generated by the <code>print()</code> functions in our tests). This shows how the <code>setUpTestData()</code> method is called once for the class and <code>setUp()</code> is called before each method.</p> + +<p>The next sections show how you can run specific tests, and how to control how much information the tests display.</p> + +<h3 id="Showing_more_test_information">Showing more test information</h3> + +<p>If you want to get more information about the test run you can change the <em>verbosity</em>. For example, to list the test successes as well as failures (and a whole bunch of information about how the testing database is set up) you can set the verbosity to "2" as shown:</p> + +<pre class="brush: bash">python3 manage.py test --verbosity 2</pre> + +<p>The allowed verbosity levels are 0, 1, 2, and 3, with the default being "1".</p> + +<h3 id="Running_specific_tests">Running specific tests</h3> + +<p>If you want to run a subset of your tests you can do so by specifying the full dot path to the package(s), module, <code>TestCase</code> subclass or method:</p> + +<pre class="brush: bash">python3 manage.py test catalog.tests # Run the specified module +python3 manage.py test catalog.tests.test_models # Run the specified module +python3 manage.py test catalog.tests.test_models.YourTestClass # Run the specified class +python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two # Run the specified method +</pre> + +<h2 id="LocalLibrary_tests">LocalLibrary tests</h2> + +<p>Now we know how to run our tests and what sort of things we need to test, let's look at some practical examples.</p> + +<div class="note"> +<p><strong>Note: </strong>We won't write every possible test, but this should give you and idea of how tests work, and what more you can do.</p> +</div> + +<h3 id="Models">Models</h3> + +<p>As discussed above, we should test anything that is part of our design or that is defined by code that we have written, but not libraries/code that is already tested by Django or the Python development team.</p> + +<p>For example, consider the <code>Author</code> model below. Here we should test the labels for all the fields, because even though we haven't explicitly specified most of them, we have a design that says what these values should be. If we don't test the values, then we don't know that the field labels have their intended values. Similarly while we trust that Django will create a field of the specified length, it is worthwhile to specify a test for this length to ensure that it was implemented as planned.</p> + +<pre class="brush: python">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name)</pre> + +<p>Open our <strong>/catalog/tests/test_models.py</strong>, and replace any existing code with the following test code for the <code>Author</code> model.</p> + +<p>Here you'll see that we first import <code>TestCase</code> and derive our test class (<code>AuthorModelTest</code>) from it, using a descriptive name so we can easily identify any failing tests in the test output. We then call <code>setUpTestData()</code> to create an author object that we will use but not modify in any of the tests.</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +from catalog.models import Author + +class AuthorModelTest(TestCase): + + @classmethod + def setUpTestData(cls): + #Set up non-modified objects used by all test methods + Author.objects.create(first_name='Big', last_name='Bob') + + def test_first_name_label(self): + author=Author.objects.get(id=1) + field_label = author._meta.get_field('first_name').verbose_name + self.assertEquals(field_label,'first name') + + def test_date_of_death_label(self): + author=Author.objects.get(id=1) + field_label = author._meta.get_field('date_of_death').verbose_name + self.assertEquals(field_label,'died') + + def test_first_name_max_length(self): + author=Author.objects.get(id=1) + max_length = author._meta.get_field('first_name').max_length + self.assertEquals(max_length,100) + + def test_object_name_is_last_name_comma_first_name(self): + author=Author.objects.get(id=1) + expected_object_name = '%s, %s' % (author.last_name, author.first_name) + self.assertEquals(expected_object_name,str(author)) + + def test_get_absolute_url(self): + author=Author.objects.get(id=1) + #This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(),'/catalog/author/1')</pre> + +<p>The field tests check that the values of the field labels (<code>verbose_name</code>) and that the size of the character fields are as expected. These methods all have descriptive names, and follow the same pattern:</p> + +<pre class="brush: python">author=Author.objects.get(id=1) # Get an author object to test +field_label = author._meta.get_field('first_name').verbose_name # Get the metadata for the required field and use it to query the required field data +self.assertEquals(field_label,'first name') # Compare the value to the expected result</pre> + +<p>The interesting things to note are:</p> + +<ul> + <li>We can't get the <code>verbose_name</code> directly using <code>author.first_name.verbose_name</code>, because <code>author.first_name</code> is a <em>string</em> (not a handle to the <code>first_name</code> object that we can use to access its properties). Instead we need to use the author's <code>_meta</code> attribute to get an instance of the field and use that to query for the additional information.</li> + <li>We chose to use <code>assertEquals(field_label,'first name')</code> rather than <code>assertTrue(field_label == 'first name')</code>. The reason for this is that if the test fails the output for the former tells you what the label actually was, which makes debugging the problem just a little easier.</li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> Tests for the <code>last_name</code> and <code>date_of_birth</code> labels, and also the test for the length of the <code>last_name</code> field have been omitted. Add your own versions now, following the naming conventions and approaches shown above.</p> +</div> + +<p>We also need to test our custom methods. These essentially just check that the object name was constructed as we expected using "Last Name", "First Name" format, and that the URL we get for an <code>Author</code> item is as we would expect.</p> + +<pre class="brush: python">def test_object_name_is_last_name_comma_first_name(self): + author=Author.objects.get(id=1) + expected_object_name = '%s, %s' % (author.last_name, author.first_name) + self.assertEquals(expected_object_name,str(author)) + +def test_get_absolute_url(self): + author=Author.objects.get(id=1) + #This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(),'/catalog/author/1')</pre> + +<p>Run the tests now. If you created the Author model as we described in the models tutorial it is quite likely that you will get an error for the <code>date_of_death</code> label as shown below. The test is failing because it was written expecting the label definition to follow Django's convention of not capitalising the first letter of the label (Django does this for you).</p> + +<pre class="brush: bash">====================================================================== +FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label + self.assertEquals(field_label,'died') +AssertionError: 'Died' != 'died' +- Died +? ^ ++ died +? ^</pre> + +<p>This is a very minor bug, but it does highlight how writing tests can more thoroughly check any assumptions you may have made.</p> + +<div class="note"> +<p><strong>Note: </strong>Change the label for the date_of_death field (/catalog/models.py) to "died" and re-run the tests.</p> +</div> + +<p>The patterns for testing the other models are similar so we won't continue to discuss these further. Feel free to create your own tests for the our other models.</p> + +<h3 id="Forms">Forms</h3> + +<p>The philosophy for testing your forms is the same as for testing your models; you need to test anything that you've coded or your design specifies, but not the behaviour of the underlying framework and other third party libraries.</p> + +<p>Generally this means that you should test that the forms have the fields that you want, and that these are displayed with appropriate labels and help text. You don't need to verify that Django validates the field type correctly (unless you created your own custom field and validation) — i.e. you don't need to test that an email field only accepts emails. However you would need to test any additional validation that you expect to be performed on the fields and any messages that your code will generate for errors.</p> + +<p>Consider our form for renewing books. This has just one field for the renewal date, which will have a label and help text that we will need to verify.</p> + +<pre class="brush: python">class RenewBookForm(forms.Form): + """ + Form for a librarian to renew books. + """ + 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</pre> + +<p>Open our <strong>/catalog/tests/test_forms.py</strong> file and replace any existing code with the following test code for the <code>RenewBookForm</code> form. We start by importing our form and some Python and Django libraries to help test time-related functionality. We then declare our form test class in the same way as we did for models, using a descriptive name for our <code>TestCase</code>-derived test class.</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +import datetime +from django.utils import timezone +from catalog.forms import RenewBookForm + +class RenewBookFormTest(TestCase): + + def test_renew_form_date_field_label(self): + form = RenewBookForm() + self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date') + + def test_renew_form_date_field_help_text(self): + form = RenewBookForm() + self.assertEqual(form.fields['renewal_date'].help_text,'Enter a date between now and 4 weeks (default 3).') + + def test_renew_form_date_in_past(self): + date = datetime.date.today() - datetime.timedelta(days=1) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_too_far_in_future(self): + date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_today(self): + date = datetime.date.today() + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertTrue(form.is_valid()) + + def test_renew_form_date_max(self): + date = timezone.now() + datetime.timedelta(weeks=4) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertTrue(form.is_valid()) +</pre> + +<p>The first two functions test that the field's <code>label</code> and <code>help_text</code> are as expected. We have to access the field using the fields dictionary (e.g. <code>form.fields['renewal_date']</code>). Note here that we also have to test whether the label value is <code>None</code>, because even though Django will render the correct label it returns <code>None</code> if the value is not <em>explicitly</em> set.</p> + +<p>The rest of the functions test that the form is valid for renewal dates just inside the acceptable range and invalid for values outside the range. Note how we construct test date values around our current date (<code>datetime.date.today()</code>) using <code>datetime.timedelta()</code> (in this case specifying a number of days or weeks). We then just create the form, passing in our data, and test if it is valid.</p> + +<div class="note"> +<p><strong>Note:</strong> Here we don't actually use the database or test client. Consider modifying these tests to use <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.SimpleTestCase">SimpleTestCase</a>.</p> + +<p>We also need to validate that the correct errors are raised if the form is invalid, however this is usually done as part of view processing, so we'll take care of that in the next section.</p> +</div> + +<p>That's all for forms; we do have some others, but they are automatically created by our generic class-based editing views, and should be tested there! Run the tests and confirm that our code still passes!</p> + +<h3 id="Views">Views</h3> + +<p>To validate our view behaviour we use the Django test <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">Client</a>. This class acts like a dummy web browser that we can use to simulate <code>GET</code> and <code>POST</code> requests on a URL and observe the response. We can see almost everything about the response, from low-level HTTP (result headers and status codes) through to the template we're using to render the HTML and the context data we're passing to it. We can also see the chain of redirects (if any) and check the URL and status code at each step. This allows us to verify that each view is doing what is expected.</p> + +<p>Let's start with one of our simplest views, which provides a list of all Authors. This is displayed at URL <strong>/catalog/authors/</strong> (an URL named 'authors' in the URL configuration).</p> + +<pre class="brush: python">class AuthorListView(generic.ListView): + model = Author + paginate_by = 10 +</pre> + +<p>As this is a generic list view almost everything is done for us by Django. Arguably if you trust Django then the only thing you need to test is that the view is accessible at the correct URL and can be accessed using its name. However if you're using a test-driven development process you'll start by writing tests that confirm that the view displays all Authors, paginating them in lots of 10.</p> + +<p>Open the <strong>/catalog/tests/test_views.py</strong> file and replace any existing text with the following test code for <code>AuthorListView</code>. As before we import our model and some useful classes. In the <code>setUpTestData()</code> method we set up a number of <code>Author</code> objects so that we can test our pagination.</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +from catalog.models import Author +from django.urls import reverse + +class AuthorListViewTest(TestCase): + + @classmethod + def setUpTestData(cls): + #Create 13 authors for pagination tests + number_of_authors = 13 + for author_num in range(number_of_authors): + Author.objects.create(first_name='Christian %s' % author_num, last_name = 'Surname %s' % author_num,) + + def test_view_url_exists_at_desired_location(self): + resp = self.client.get('/catalog/authors/') + self.assertEqual(resp.status_code, 200) + + def test_view_url_accessible_by_name(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + + def test_view_uses_correct_template(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + + self.assertTemplateUsed(resp, 'catalog/author_list.html') + + def test_pagination_is_ten(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + self.assertTrue('is_paginated' in resp.context) + self.assertTrue(resp.context['is_paginated'] == True) + self.assertTrue( len(resp.context['author_list']) == 10) + + def test_lists_all_authors(self): + #Get second page and confirm it has (exactly) remaining 3 items + resp = self.client.get(reverse('authors')+'?page=2') + self.assertEqual(resp.status_code, 200) + self.assertTrue('is_paginated' in resp.context) + self.assertTrue(resp.context['is_paginated'] == True) + self.assertTrue( len(resp.context['author_list']) == 3)</pre> + +<p>All the tests use the client (belonging to our <code>TestCase</code>'s derived class) to simulate a <code>GET</code> request and get a response (<code>resp</code>). The first version checks a specific URL (note, just the specific path without the domain) while the second generates the URL from its name in the URL configuration.</p> + +<pre class="brush: python">resp = self.client.get('/catalog/authors/') +resp = self.client.get(reverse('authors')) +</pre> + +<p>Once we have the response we query it for its status code, the template used, whether or not the response is paginated, the number of items returned, and the total number of items.</p> + +<p>The most interesting variable we demonstrate above is <code>resp.context</code>, which is the context variable passed to the template by the view. This is incredibly useful for testing, because it allows us to confirm that our template is getting all the data it needs. In other words we can check that we're using the intended template and what data the template is getting, which goes a long way to verifying that any rendering issues are solely due to template.</p> + +<h4 id="Views_that_are_restricted_to_logged_in_users">Views that are restricted to logged in users</h4> + +<p>In some cases you'll want to test a view that is restricted to just logged in users. For example our <code>LoanedBooksByUserListView</code> is very similar to our previous view but is only available to logged in users, and only displays <code>BookInstance</code> records that are borrowed by the current user, have the 'on loan' status, and are ordered "oldest first".</p> + +<pre class="brush: python">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): + """ + Generic class-based view listing books on loan to current user. + """ + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>Add the following test code to <strong>/catalog/tests/test_views.py</strong>. Here we first use <code>SetUp()</code> to create some user login accounts and <code>BookInstance</code> objects (along with their associated books and other records) that we'll use later in the tests. Half of the books are borrowed by each test user, but we've initially set the status of all books to "maintenance". We've used <code>SetUp()</code> rather than <code>setUpTestData()</code> because we'll be modifying some of these objects later.</p> + +<div class="note"> +<p><strong>Note:</strong> The <code>setUp()</code> code below creates a book with a specified <code>Language</code>, but <em>your</em> code may not include the <code>Language</code> model as this was created as a <em>challenge</em>. If this is the case, simply comment out the parts of the code that create or import Language objects. You should also do this in the <code>RenewBookInstancesViewTest</code> section that follows.</p> +</div> + +<pre class="brush: python">import datetime +from django.utils import timezone + +from catalog.models import BookInstance, Book, Genre, Language +from django.contrib.auth.models import User #Required to assign User as a borrower + +class LoanedBookInstancesByUserListViewTest(TestCase): + + def setUp(self): + #Create two users + test_user1 = User.objects.create_user(username='testuser1', password='12345') + test_user1.save() + test_user2 = User.objects.create_user(username='testuser2', password='12345') + test_user2.save() + + #Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language) + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) #Direct assignment of many-to-many types not allowed. + test_book.save() + + #Create 30 BookInstance objects + number_of_book_copies = 30 + for book_copy in range(number_of_book_copies): + return_date= timezone.now() + datetime.timedelta(days=book_copy%5) + if book_copy % 2: + the_borrower=test_user1 + else: + the_borrower=test_user2 + status='m' + BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=the_borrower, status=status) + + def test_redirect_if_not_logged_in(self): + resp = self.client.get(reverse('my-borrowed')) + self.assertRedirects(resp, '/accounts/login/?next=/catalog/mybooks/') + + def test_logged_in_uses_correct_template(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Check we used correct template + self.assertTemplateUsed(resp, 'catalog/bookinstance_list_borrowed_user.html') +</pre> + +<p>To verify that the view will redirect to a login page if the user is not logged in we use <code>assertRedirects</code>, as demonstrated in <code>test_redirect_if_not_logged_in()</code>. To verify that the page is displayed for a logged in user we first log in our test user, and then access the page again and check that we get a <code>status_code</code> of 200 (success). </p> + +<p>The rest of the tests verify that our view only returns books that are on loan to our current borrower. Copy the (self-explanatory) code at the end of the test class above.</p> + +<pre class="brush: python"> def test_only_borrowed_books_in_list(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Check that initially we don't have any books in list (none on loan) + self.assertTrue('bookinstance_list' in resp.context) + self.assertEqual( len(resp.context['bookinstance_list']),0) + + #Now change all books to be on loan + get_ten_books = BookInstance.objects.all()[:10] + + for copy in get_ten_books: + copy.status='o' + copy.save() + + #Check that now we have borrowed books in the list + resp = self.client.get(reverse('my-borrowed')) + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + self.assertTrue('bookinstance_list' in resp.context) + + #Confirm all books belong to testuser1 and are on loan + for bookitem in resp.context['bookinstance_list']: + self.assertEqual(resp.context['user'], bookitem.borrower) + self.assertEqual('o', bookitem.status) + + def test_pages_ordered_by_due_date(self): + + #Change all books to be on loan + for copy in BookInstance.objects.all(): + copy.status='o' + copy.save() + + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Confirm that of the items, only 10 are displayed due to pagination. + self.assertEqual( len(resp.context['bookinstance_list']),10) + + last_date=0 + for copy in resp.context['bookinstance_list']: + if last_date==0: + last_date=copy.due_back + else: + self.assertTrue(last_date <= copy.due_back)</pre> + +<p>You could also add pagination tests, should you so wish!</p> + +<h4 id="Testing_views_with_forms">Testing views with forms</h4> + +<p>Testing views with forms is a little more complicated than in the cases above, because you need to test more code paths: initial display, display after data validation has failed, and display after validation has succeeded. The good news is that we use the client for testing in almost exactly the same way as we did for display-only views.</p> + +<p>To demonstrate, let's write some tests for the view used to renew books (<code>renew_book_librarian()</code>):</p> + +<pre class="brush: python">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})</pre> + +<p>We'll need to test that the view is only available to users who have the <code>can_mark_returned </code>permission, and that users are redirected to an HTTP 404 error page if they attempt to renew a <code>BookInstance</code> that does not exist. We should check that the initial value of the form is seeded with a date three weeks in the future, and that if validation succeeds we're redirected to the "all-borrowed books" view. As part of checking the validation-fail tests we'll also check that our form is sending the appropriate error messages.</p> + +<p>Add the first part of the test class (shown below) to the bottom of <strong>/catalog/tests/test_views.py</strong>. This creates two users and two book instances, but only gives one user the permission required to access the view. The code to grant permissions during tests is shown in bold:</p> + +<pre class="brush: python">from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned. + +class RenewBookInstancesViewTest(TestCase): + + def setUp(self): + #Create a user + test_user1 = User.objects.create_user(username='testuser1', password='12345') + test_user1.save() + + test_user2 = User.objects.create_user(username='testuser2', password='12345') + test_user2.save() +<strong> permission = Permission.objects.get(name='Set book as returned') + test_user2.user_permissions.add(permission) + test_user2.save()</strong> + + #Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language,) + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed. + test_book.save() + + #Create a BookInstance object for test_user1 + return_date= datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance1=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user1, status='o') + + #Create a BookInstance object for test_user2 + return_date= datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance2=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user2, status='o')</pre> + +<p>Add the following tests to the bottom of the test class. These check that only users with the correct permissions (<em>testuser2</em>) can access the view. We check all the cases: when the user is not logged in, when a user is logged in but does not have the correct permissions, when the user has permissions but is not the borrower (should succeed), and what happens when they try to access a <code>BookInstance</code> that doesn't exist. We also check that the correct template is used.</p> + +<pre class="brush: python"> def test_redirect_if_not_logged_in(self): + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual( resp.status_code,302) + self.assertTrue( resp.url.startswith('/accounts/login/') ) + + def test_redirect_if_logged_in_but_not_correct_permission(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + + #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual( resp.status_code,302) + self.assertTrue( resp.url.startswith('/accounts/login/') ) + + def test_logged_in_with_permission_borrowed_book(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance2.pk,}) ) + + #Check that it lets us login - this is our book and we have the right permissions. + self.assertEqual( resp.status_code,200) + + def test_logged_in_with_permission_another_users_borrowed_book(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + + #Check that it lets us login. We're a librarian, so we can view any users book + self.assertEqual( resp.status_code,200) + + def test_HTTP404_for_invalid_book_if_logged_in(self): + import uuid + test_uid = uuid.uuid4() #unlikely UID to match our bookinstance! + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid,}) ) + self.assertEqual( resp.status_code,404) + + def test_uses_correct_template(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + self.assertEqual( resp.status_code,200) + + #Check we used correct template + self.assertTemplateUsed(resp, 'catalog/book_renew_librarian.html') +</pre> + +<p>Add the next test method, as shown below. This checks that the initial date for the form is three weeks in the future. Note how we are able to access the value of the initial value of the form field (shown in bold).</p> + +<pre class="brush: python"> def test_form_renewal_date_initially_has_date_three_weeks_in_future(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + self.assertEqual( resp.status_code,200) + + date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3) + self.assertEqual(<strong>resp.context['form'].initial['renewal_date']</strong>, date_3_weeks_in_future ) +</pre> + +<p>The next test (add this to the class too) checks that the view redirects to a list of all borrowed books if renewal succeeds. What differs here is that for the first time we show how you can <code>POST</code> data using the client. The post <em>data</em> is the second argument to the post function, and is specified as a dictionary of key/values.</p> + +<pre class="brush: python"> def test_redirects_to_all_borrowed_book_list_on_success(self): + login = self.client.login(username='testuser2', password='12345') + valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2) + resp = <strong>self.client.<em>post</em>(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future} )</strong> + self.assertRedirects(resp, reverse('all-borrowed') ) +</pre> + +<div class="warning"> +<p>The <em>all-borrowed</em> view was added as a <em>challenge</em>, and your code may instead redirect to the home page '/'. If so, modify the last two lines of the test code to be like the code below. The <code>follow=True</code> in the request ensures that the request returns the final destination URL (hence checking <code>/catalog/</code> rather than <code>/</code>).</p> + +<pre class="brush: python"> resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future},<strong>follow=True</strong> ) + <strong>self.assertRedirects(resp, '/catalog/')</strong></pre> +</div> + +<p>Copy the last two functions into the class, as seen below. These again test <code>POST</code> requests, but in this case with invalid renewal dates. We use <code>assertFormError() </code>to verify that the error messages are as expected.</p> + +<pre class="brush: python"> def test_form_invalid_renewal_date_past(self): + login = self.client.login(username='testuser2', password='12345') + date_in_past = datetime.date.today() - datetime.timedelta(weeks=1) + resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':date_in_past} ) + self.assertEqual( resp.status_code,200) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal in past')</strong> + + def test_form_invalid_renewal_date_future(self): + login = self.client.login(username='testuser2', password='12345') + invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5) + resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':invalid_date_in_future} ) + self.assertEqual( resp.status_code,200) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')</strong> +</pre> + +<p>The same sorts of techniques can be used to test the other view.</p> + +<h3 id="Templates">Templates</h3> + +<p>Django provides test APIs to check that the correct template is being called by your views, and to allow you to verify that the correct information is being sent. There is however no specific API support for testing in Django that your HTML output is rendered as expected.</p> + +<h2 id="Other_recommended_test_tools">Other recommended test tools</h2> + +<p>Django's test framework can help you write effective unit and integration tests — we've only scratched the surface of what the underlying <strong>unittest</strong> framework can do, let alone Django's additions (for example, check out how you can use <a href="https://docs.python.org/3.5/library/unittest.mock-examples.html">unittest.mock</a> to patch third party libraries so you can more thoroughly test your own code).</p> + +<p>While there are numerous other test tools that you can use, we'll just highlight two:</p> + +<ul> + <li><a href="http://coverage.readthedocs.io/en/latest/">Coverage</a>: This Python tool reports on how much of your code is actually executed by your tests. It is particularly useful when you're getting started, and you are trying to work out exactly what you should test.</li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> is a framework to automate testing in a real browser. It allows you to simulate a real user interacting with the site, and provides a great framework for system testing your site (the next step up from integration testing).</li> +</ul> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>There are a lot more models and views we can test. As a simple task, try to create a test case for the <code>AuthorCreate</code> view.</p> + +<pre class="brush: python">class AuthorCreate(PermissionRequiredMixin, CreateView): + model = Author + fields = '__all__' + initial={'date_of_death':'12/10/2016',} + permission_required = 'catalog.can_mark_returned'</pre> + +<p>Remember that you need to check anything that you specify or that is part of the design. This will include who has access, the initial date, the template used, and where the view redirects on success.</p> + +<h2 id="Summary">Summary</h2> + +<p>Writing test code is neither fun nor glamorous, and is consequently often left to last (or not at all) when creating a website. It is however an essential part of making sure that your code is safe to release after making changes, and cost-effective to maintain.</p> + +<p>In this tutorial we've shown you how to write and run tests for your models, forms, and views. Most importantly we've provided a brief summary of what you should test, which is often the hardest thing to work out when you're getting started. There is a lot more to know, but even with what you've learned already you should be able to create effective unit tests for your websites.</p> + +<p>The next and final tutorial shows how you can deploy your wonderful (and fully tested!) Django website.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/overview/">Writing and running tests</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial05/">Writing your first Django app, part 5 > Introducing automated testing</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/">Testing tools reference</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/">Advanced testing topics</a> (Django docs)</li> + <li><a href="http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/">A Guide to Testing in Django</a> (Toast Driven Blog, 2011)</li> + <li><a href="http://test-driven-django-development.readthedocs.io/en/latest/index.html">Workshop: Test-Driven Web Development with Django</a> (San Diego Python, 2014)</li> + <li><a href="https://realpython.com/blog/python/testing-in-django-part-1-best-practices-and-examples/">Testing in Django (Part 1) - Best Practices and Examples</a> (RealPython, 2013)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/django/tutorial_local_library_website/index.html b/files/zh-tw/learn/server-side/django/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..3e2cae3be5 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/tutorial_local_library_website/index.html @@ -0,0 +1,92 @@ +--- +title: 'Django 教學 1: 本地圖書館網站' +slug: Learn/Server-side/Django/Tutorial_local_library_website +tags: + - django + - 初學者 +translation_of: Learn/Server-side/Django/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">我們實戰教學系列的第一篇,會解釋你將學到什麼。並提供一個“本地圖書館” 的例子,作為概述。在接下來的教學裡,我們會不斷完善和改進這個網站。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>閱讀 Django 介紹。在接下來的文章裡,你需要創建 Django 開發環境.</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>介紹教學裡使用的網站應用,讓讀者明白要討論的主題。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>歡迎來到 MDN 的 ”本地圖書館“ Django 教學。在教學裡,我們會開發一個網站,用來管理本地圖書館的目錄。</p> + +<p>在這一系列的教學裡,你將:</p> + +<ul> + <li>運用 Django 的工具,創建網站和應用的框架。</li> + <li>啟動和停止開發用的服務器。</li> + <li>創建模型(models)用來表示應用裡的數據。</li> + <li>運用 Django 的 admin 網站,以填充網站數據。</li> + <li>面對不同的網路請求,創建視圖函數(views)取回相應的數據。並把數據用模板(templates ),渲染成 HTML ,展示在瀏覽器裡。</li> + <li>創建網路映射,將不同的 URL 模式,分發給特定的視圖函數(views)。</li> + <li>添加用戶認證和會話(sessions),管理網站行為和進入權限。</li> + <li>使用表單。</li> + <li>為應用編寫測試。</li> + <li>有效運用 Django 的安全系統。</li> + <li>把應用佈置到生產環境中。</li> +</ul> + +<p>關於這些主題,你已經學會了一些,並對其他的也有了簡單的了解。在這系列教學的最後,你會學到足夠多,而可以自己開發簡單的Django 應用了。</p> + +<h2 id="本地圖書館網站">本地圖書館網站</h2> + +<p>本地圖書館,是我們在本系列教學裡,創建和不斷改善的網站。跟你期望的一樣,這個網站的目標,是為一個小型的圖書館,提供一個線上目錄。在這個小型圖書館裡,用戶能瀏覽書籍,和管理他們的帳號。</p> + +<p>這個例子是精心挑選出來的,因為它可以根據我們的需要,增加或多或少的細節。也能用來展示,幾乎所有的 Django 特性。更重要的是,它提供了一條指南式的路線,在這條路線中,我們會用到 Django 網路框架最重要的功能:</p> + +<ul> + <li>在第一篇教學裡,我們會定義一個,簡單到只能瀏覽的圖書館。圖書館的會員,可以查找哪些書可以借閱。我們得以探索那些,幾乎所有網站都會運用的操作:閱讀和展示數據庫裡的內容。</li> + <li>接下來,圖書館會慢慢擴展,以展示更高級的 Django 特性。例如,我們會擴展功能,讓會員能夠保留圖書。這個特性會展示如何使用表單,並支持用戶認證。</li> +</ul> + +<p>儘管這是一個非常容易擴展的例子,它被稱為本地圖書館是有原因的——我們希望用最少的訊息,幫助你快速創建、和運用 Django。最後,我們會存儲圖書訊息,圖書數量,作者和其他重要訊息。我們不會儲存圖書館可能會儲存的其他訊息,或是提供一個支持多個圖書館、或是 ”大型圖書館“ 功能的建構。</p> + +<h2 id="我卡住了,從哪裡獲得源程式碼呢?">我卡住了,從哪裡獲得源程式碼呢?</h2> + +<p>在學習本系列教程時,我們會提供合適的代碼片段,你可以粘貼複製,但是有些代碼我們希望你能自己擴展(在提示下)。</p> + +<p>如果你卡在某個地方,你可以在 <a href="https://github.com/mdn/django-locallibrary-tutorial">Github </a>裡找到網站的完整代碼。</p> + +<h2 id="總結">總結</h2> + +<p>現在你對本地圖書館網站有了一些了解並知道你會學到什麼。是時候創建我們例子會用到的<a href="/zh-TW/docs/Learn/Server-side/Django/skeleton_website">網站框架</a>了。</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</p> + +<h2 id="本系列教學">本系列教學</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django 介紹</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">設定Django開發環境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教學: 本地圖書館網站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教學 第2部分: 建立網站骨架</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django 教學 第3部分: 使用模型</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django 教學 第4部分: Django的管理員頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 教學 第5部分: 創建我們的首頁</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django 教學 第6部分: 通用列表與詳細視圖</a><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教學 第7部分: 會話 (Sessions) 框架 </a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django 教學 第8部分: 使用者的身分驗證與權限</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 教學 第9部分: 使用表單</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 教學 第10部分: 測試Django 網頁應用</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django 教學 第11部分: 部署 Django 到生產環境(production)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django 網頁應用安全</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 迷你部落格</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/django/web_application_security/index.html b/files/zh-tw/learn/server-side/django/web_application_security/index.html new file mode 100644 index 0000000000..f644f400b9 --- /dev/null +++ b/files/zh-tw/learn/server-side/django/web_application_security/index.html @@ -0,0 +1,180 @@ +--- +title: Django web application security +slug: Learn/Server-side/Django/web_application_security +translation_of: Learn/Server-side/Django/web_application_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</div> + +<p class="summary">保護用戶數據是任何網站設計的重要部分。我們之前在文章 web 安全中,解釋了一些更常見的安全威脅 -- 本文提供了 Django 的內置保護如何處理這些威脅的實際演示。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Read the Server-side progamming "<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a>" topic. Complete the Django tutorial topics up to (and including) at least <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand the main things you need to do (or not do) to secure your Django web application.</td> + </tr> + </tbody> +</table> + +<h2 id="Overview">Overview</h2> + +<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Website security</a> topic provides an overview of what website security means for server-side design, and some of the more common threats that you may need to protect against. One of the key messages in that article is that almost all attacks are successful when the web application trusts data from the browser.</p> + +<div class="warning"> +<p><strong>Important:</strong> The single most important lesson you can learn about website security is to <strong>never trust data from the browser</strong>. This includes <code>GET</code> request data in URL parameters, <code>POST</code> data, HTTP headers and cookies, user-uploaded files, etc. Always check and sanitize all incoming data. Always assume the worst.</p> +</div> + +<p>The good news for Django users is that many of the more common threats are handled by the framework! The <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> (Django docs) article explains Django's security features and how to secure a Django-powered website.</p> + +<h2 id="Common_threatsprotections">Common threats/protections</h2> + +<p>Rather than duplicate the Django documentation here, in this article we'll demonstrate just a few of the security features in the context of our Django <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> tutorial.</p> + +<h3 id="Cross_site_scripting_(XSS)">Cross site scripting (XSS)</h3> + +<p>XSS is a term used to describe a class of attacks that allow an attacker to inject client-side scripts <em>through</em> the website into the browsers of other users. This is usually achieved by storing malicious scripts in the database where they can be retrieved and displayed to other users, or by getting users to click a link that will cause the attacker’s JavaScript to be executed by the user’s browser.</p> + +<p>Django's template system protects you against the majority of XSS attacks by <a href="https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping">escaping specific characters</a> that are "dangerous" in HTML. We can demonstrate this by attempting to inject some JavaScript into our LocalLibrary website using the Create-author form we set up in <a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a>.</p> + +<ol> + <li>Start the website using the development server (<code>python3 manage.py runserver</code>).</li> + <li>Open the site in your local browser and login to your superuser account.</li> + <li>Navigate to the author-creation page (which should be at URL: <code><a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a></code>).</li> + <li>Enter names and date details for a new user, and then append the following text to the Last Name field:<br> + <code><script>alert('Test alert');</script></code>.<br> + <img alt="Author Form XSS test" src="https://mdn.mozillademos.org/files/14305/author_create_form_alert_xss.png" style="border-style: solid; border-width: 1px; height: 245px; width: 594px;"> + <div class="note"> + <p><strong>Note:</strong> This is a harmless script that, if executed, will display an alert box in your browser. If the alert is displayed when you submit the record then the site is vulnerable to XSS threats.</p> + </div> + </li> + <li>Press <strong>Submit</strong> to save the record.</li> + <li>When you save the author it will be displayed as shown below. Because of the XSS protections the <code>alert()</code> should not be run. Instead the script is displayed as plain text.<img alt="Author detail view XSS test" src="https://mdn.mozillademos.org/files/14307/author_detail_alert_xss.png" style="border-style: solid; border-width: 1px; height: 248px; width: 986px;"></li> +</ol> + +<p>If you view the page HTML source code, you can see that the dangerous characters for the script tags have been turned into their harmless escape code equivalents (e.g. <code>></code> is now <code>&gt;</code>)</p> + +<pre class="brush: html"><h1>Author: Boon&lt;script&gt;alert(&#39;Test alert&#39;);&lt;/script&gt;, David (Boonie) </h1> +</pre> + +<p>Using Django templates protects you against the majority of XSS attacks. However it is possible to turn off this protection, and the protection isn't automatically applied to all tags that wouldn't normally be populated by user input (for example, the <code>help_text</code> in a form field is usually not user-supplied, so Django doesn't escape those values).</p> + +<p>It is also possible for XSS attacks to originate from other untrusted source of data, such as cookies, Web services or uploaded files (whenever the data is not sufficiently sanitized before including in a page). If you're displaying data from these sources, then you may need to add your own sanitisation code.</p> + +<h3 id="Cross_site_request_forgery_(CSRF)_protection">Cross site request forgery (CSRF) protection</h3> + +<p>CSRF attacks allow a malicious user to execute actions using the credentials of another user without that user’s knowledge or consent. For example consider the case where we have a hacker who wants to create additional authors for our LocalLibrary.</p> + +<div class="note"> +<p><strong>Note:</strong> Obviously our hacker isn't in this for the money! A more ambitious hacker could use the same approach on other sites to perform much more harmful tasks (e.g. transfer money to their own accounts, etc.)</p> +</div> + +<p>In order to do this, they might create an HTML file like the one below, which contains an author-creation form (like the one we used in the previous section) that is submitted as soon as the file is loaded. They would then send the file to all the Librarians and suggest that they open the file (it contains some harmless information, honest!). If the file is opened by any logged in librarian, then the form would be submitted with their credentials and a new author would be created.</p> + +<pre class="brush: html"><html> +<body onload='document.EvilForm.submit()'> + +<form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name='EvilForm'> + <table> + <tr><th><label for="id_first_name">First name:</label></th><td><input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /></td></tr> + <tr><th><label for="id_last_name">Last name:</label></th><td><input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /></td></tr> + <tr><th><label for="id_date_of_birth">Date of birth:</label></th><td><input id="id_date_of_birth" name="date_of_birth" type="text" /></td></tr> + <tr><th><label for="id_date_of_death">Died:</label></th><td><input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /></td></tr> + </table> + <input type="submit" value="Submit" /> +</form> + +</body> +</html> +</pre> + +<p>Run the development web server, and log in with your superuser account. Copy the text above into a file and then open it in the browser. You should get a CSRF error, because Django has protection against this kind of thing!</p> + +<p>The way the protection is enabled is that you include the <code>{% csrf_token %}</code> template tag in your form definition. This token is then rendered in your HTML as shown below, with a value that is specific to the user on the current browser.</p> + +<pre class="brush: html"><input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' /> +</pre> + +<p>Django generates a user/browser specific key and will reject forms that do not contain the field, or that contain an incorrect field value for the user/browser.</p> + +<p>To use this type of attack the hacker now has to discover and include the CSRF key for the specific target user. They also can't use the "scattergun" approach of sending a malicious file to all librarians and hoping that one of them will open it, since the CSRF key is browser specific.</p> + +<p>Django's CSRF protection is turned on by default. You should always use the <code>{% csrf_token %}</code> template tag in your forms and use <code>POST</code> for requests that might change or add data to the database.</p> + +<h3 id="Other_protections">Other protections</h3> + +<p>Django also provides other forms of protection (most of which would be hard or not particularly useful to demonstrate):</p> + +<dl> + <dt>SQL injection protection</dt> + <dd>SQL injection vulnerabilities enable malicious users to execute arbitrary SQL code on a database, allowing data to be accessed, modified, or deleted irrespective of the user's permissions. In almost every case you'll be accessing the database using Django’s querysets/models, so the resulting SQL will be properly escaped by the underlying database driver. If you do need to write raw queries or custom SQL then you'll need to explicitly think about preventing SQL injection.</dd> + <dt>Clickjacking protection</dt> + <dd>In this attack a malicious user hijacks clicks meant for a visible top level site and routes them to a hidden page beneath. This technique might be used, for example, to display a legitimate bank site but capture the login credentials in an invisible <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="The HTML Inline Frame Element (<iframe>) represents a nested browsing context, effectively embedding another HTML page into the current page. In HTML 4.01, a document may contain a head and a body or a head and a frameset, but not both a body and a frameset. However, an <iframe> can be used within a normal document body. Each browsing context has its own session history and active document. The browsing context that contains the embedded content is called the parent browsing context. The top-level browsing context (which has no parent) is typically the browser window."><code><iframe></code></a> controlled by the attacker. Django contains <a href="https://docs.djangoproject.com/en/2.0/ref/clickjacking/#clickjacking-prevention">clickjacking protection</a> in the form of the <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#django.middleware.clickjacking.XFrameOptionsMiddleware" title="django.middleware.clickjacking.XFrameOptionsMiddleware"><code>X-Frame-Options middleware</code></a> which, in a supporting browser, can prevent a site from being rendered inside a frame.</dd> + <dt>Enforcing SSL/HTTPS</dt> + <dd>SSL/HTTPS can be enabled on the web server in order to encrypt all traffic between the site and browser, including authentication credentials that would otherwise be sent in plain text (enabling HTTPS is highly recommended). If HTTPS is enabled then Django provides a number of other protections you can use:</dd> +</dl> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER"><code>SECURE_PROXY_SSL_HEADER</code></a> can be used to check whether content is secure, even if it is incoming from a non-HTTP proxy.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT"><code>SECURE_SSL_REDIRECT</code></a> is used to redirect all HTTP requests to HTTPS.</li> + <li>Use <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#http-strict-transport-security">HTTP Strict Transport Security</a> (HSTS). This is an HTTP header that informs a browser that all future connections to a particular site should always use HTTPS. Combined with redirecting HTTP requests to HTTPS, this setting ensures that HTTPS is always used after a successful connection has occurred. HSTS may either be configured with <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS"><code>SECURE_HSTS_SECONDS</code></a> and <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> or on the Web server.</li> + <li>Use ‘secure’ cookies by setting <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE"><code>SESSION_COOKIE_SECURE</code></a> and <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE"><code>CSRF_COOKIE_SECURE</code></a> to <code>True</code>. This will ensure that cookies are only ever sent over HTTPS.</li> +</ul> + +<dl> + <dt>Host header validation</dt> + <dd>Use <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-ALLOWED_HOSTS"><code>ALLOWED_HOSTS</code></a> to only accept requests from trusted hosts.</dd> +</dl> + +<p>There are many other protections, and caveats to the usage of the above mechanisms. While we hope that this has given you an overview of what Django offers, you should still read the Django security documentation.</p> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>Django has effective protections against a number of common threats, including XSS and CSRF attacks. In this article we've demonstrated how those particular threats are handled by Django in our <em>LocalLibrary</em> website. We've also provided a brief overview of some of the other protections.</p> + +<p>This has been a very brief foray into web security. We strongly recommend that you read <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> to gain a deeper understanding.</p> + +<p>The next and final step in this module about Django is to complete the <a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">assessment task</a>.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> (Django docs)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Server side website security</a> (MDN)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Web security</a> (MDN)</li> + <li><a href="/en-US/docs/Web/Security/Securing_your_site">Securing your site</a> (MDN)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/deployment/index.html b/files/zh-tw/learn/server-side/express_nodejs/deployment/index.html new file mode 100644 index 0000000000..d7c2089cd1 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/deployment/index.html @@ -0,0 +1,521 @@ +--- +title: 'Express 教學 7: 佈署到生產環境' +slug: Learn/Server-side/Express_Nodejs/deployment +translation_of: Learn/Server-side/Express_Nodejs/deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">現在你已經創建(並測試)了一個不錯的 本地圖書館 網站了,你打算把它發佈到一個公共網絡服務器,這樣圖書館管理員和網路上的其他成員就可以訪問它了。這篇文章總結了你可以怎樣找到一台主機部署你的網站,以及你需要為網站準備好佈署到生產環境該做什麼。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">預備知識:</th> + <td>完成前面所有的指南主題,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>學習你可以怎樣以及在哪裡部署一個 Express 應用到生產環境。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>一旦您的站點完成(或完成 “足夠” 以開始公共測試),您將需要將其託管在比您的個人開發計算機,更公開和可訪問的地方。</p> + +<p>到目前為止,您一直在開發環境中工作,使用Express / Node 作為 Web 服務器,將您的站點共享到本地瀏覽器/網路,並使用(不安全的)開發設置運行您的網站,以顯示調試和其他私人信息。在您可以在外部託管網站之前,您首先必須:</p> + +<ul> + <li>選擇託管 Express 應用程序的環境。</li> + <li>對項目設置進行一些更改。</li> + <li>設置生產級別的基礎架構,以服務您的網站。</li> +</ul> + +<p>本教程提供了,有關選擇託管站點的選項的一些指導,簡要概述了為使您的Express 應用程序準備好生產,所需執行的操作,以及一個工作示例,演示如何將 LocalLibrary 網站安裝到 <a href="https://www.heroku.com/">Heroku</a> 雲託管上的服務。</p> + +<p>請記住,您不必使用 Heroku - 還有其他託管服務可用。我們還提供了一個單獨的教程,以展示如何在 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry </a>上安裝 LocalLibrary。</p> + +<h2 id="什麼是生產環境?">什麼是生產環境?</h2> + +<p>生產環境是服務器計算機提供的環境,您可以在其中運行網站,以供外部使用。環境包括:</p> + +<ul> + <li>網站運行的計算機硬件。</li> + <li>操作系統(例如 Linux 或 Windows)。</li> + <li>編程語言運行庫和框架庫,在其上編寫您的網站。</li> + <li>Web 服務器基礎結構,可能包含 Web 服務器,反向代理,負載平衡器等。</li> + <li>您的網站所依賴的數據庫。</li> +</ul> + +<p>服務器計算機,可以位於您的場所,並通過快速鏈接,連接到 Internet,但使用 “託管在雲上” 的計算機更為常見。這實際上意味著,您的代碼運行在託管公司的數據中心的某台遠程計算機(或可能是“虛擬”計算機)。遠程服務器,通常會以特定價格提供互聯網連接,和一些保證級別的計算資源(例如CPU,RAM,存儲器等)。</p> + +<p>這種可遠程訪問的計算/網絡硬件,稱為基礎架構即服務(IaaS)。許多 IaaS 供應商,提供預安裝特定操作系統的選項,您必須在其上,安裝生產環境的其他組件。其他供應商,允許您選擇功能更全面的環境,可能包括完整的 node 設置。</p> + +<div class="note"> +<p><strong>注意:</strong> 預構建環境,可以使您的網站設置變得非常簡單,因為它們會減少配置,但可用選項可能會限制您使用不熟悉的服務器(或其他組件),並且可能基於較舊版本的操作系統。通常最好自己安裝組件,以便獲得所需的組件,並且當您需要升級系統的某些部分時,您可以知道從哪裡開始!</p> +</div> + +<p>其他託管服務提供商,支持 Express 作為平台即服務(PaaS)產品的一部分。使用此類託管時,您無需擔心大多數生產環境(服務器,負載平衡器等),因為主機平台會為您處理這些問題。這使得部署非常簡單,因為您只需要專注於 Web 應用程序,而不是任何其他服務器基礎結構。</p> + +<p>一些開發人員選擇 IaaS ,相對於 PaaS ,IaaS 提供更高靈活性,而其他開發人員偏好 PaaS 的降低維護開銷,和更輕鬆的擴展性。當您在一開始使用時,在 PaaS 系統上設置您的網站,要容易得多,因此我們將在本教程中使用 PaaS。</p> + +<div class="note"> +<p><strong>提示:</strong> 如果您選擇 Node/Express 友好的託管服務提供商,他們應該提供,有關如何使用 Web 服務器,應用程序服務器,反向代理等不同配置,來設置 Express 網站的說明。例如,在<a href="https://www.digitalocean.com/community/tutorials?q=node"> Digital Ocean</a> 的node 社區文檔中,有許多各種配置的手把手指南。</p> +</div> + +<h2 id="選擇一個主機供應商">選擇一個主機供應商</h2> + +<p>眾所周知,眾多託管服務提供商,都積極支持或與 Node(和Express)合作。這些供應商提供不同類型的環境(IaaS,PaaS),以及不同價格的不同級別的計算和網絡資源。</p> + +<div class="note"> +<p><strong>提示:</strong> 有很多託管解決方案,他們的服務和定價,可能會隨著時間而改變。雖然我們在下面介紹幾個選項,但在選擇託管服務提供商之前,有必要自己進行互聯網搜索。</p> +</div> + +<p>選擇主機時需要考慮的一些事項:</p> + +<ul> + <li>您的網站可能有多忙,以及滿足該需求所需的數據,和計算資源的成本。</li> + <li>水平擴展(添加更多機器)和垂直擴展(升級到更強大的機器)的支持級別,以及這樣做的成本。</li> + <li>供應商有數據中心的地方,因此訪問可能是最快的。</li> + <li>主機正常運行時間和停機時間的歷史表現。</li> + <li>用於管理站點的工具 - 易於使用且安全(例如 SFTP 與 FTP)。</li> + <li>用於監控服務器的內置框架。</li> + <li>已知限制。有些主機會故意阻止某些服務(例如電子郵件)。其他在某些價格層中,僅提供一定數小時的 “實時時間”,或者僅提供少量存儲空間。</li> + <li>額外的好處。一些提供商將提供免費域名和 SSL 證書支持,否則您將不得不為此另外支付費用。</li> + <li>您所依賴的“免費”等級,是否會隨著時間的推移而過期,以及遷移到更昂貴等級的成本,是否意味著您最好在一開始就使用其他服務!</li> +</ul> + +<p>當你剛開始時,好消息是有很多網站提供“免費”的計算環境,儘管有一些條件。例如, <a href="https://www.heroku.com/">Heroku</a> “永遠” 提供免費但資源有限的 PaaS 環境,而 <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure</a> 和開源選項 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry</a> 在您第一次加入時,提供免費信用額度。</p> + +<p>許多提供商還擁有“基本”層,可提供更多有用的計算能力,和更少的限制。舉例來說, <a href="https://www.digitalocean.com/">Digital Ocean</a> 是一個流行的託管服務提供商,它提供了一個相對便宜的基本計算層(在本教程寫作時,是每月5美元的較低範圍)。</p> + +<div class="note"> +<p><strong>注意:</strong> 請記住,價格不是唯一的選擇標準。如果您的網站成功,可能會發現可擴展性是最重要的考慮因素。</p> +</div> + +<h2 id="準備好發布你的網站">準備好發布你的網站</h2> + +<p>發佈網站時,要考慮的主要問題是網絡安全性和性能。至少,您需要刪除開發期間,錯誤頁面上包含的堆棧跟踪,整理日誌記錄,並設置適當的標頭,以避免許多常見的安全威脅。</p> + +<p>在以下小節中,我們概述了您應該對應用進行的、最重要的更改。</p> + +<div class="note"> +<p><strong>提示:</strong> Express文檔中還有其他有用的提示 - 請參閱“<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生產最佳實踐:性能和可靠性</a>”,以及“<a href="https://expressjs.com/en/advanced/best-practice-security.html">生產最佳實踐:安全性</a>”。</p> +</div> + +<h3 id="設置_NODE_ENV_為_'production'">設置 NODE_ENV 為 'production'</h3> + +<p>我們可以通過將 <code>NODE_ENV</code> 環境變量,設置為 production ,來刪除錯誤頁面中的堆棧跟踪(默認設置為 “development” )。除了生成較為不詳細的錯誤消息之外,還要將變量設置為生產緩存視圖模板,和從 CSS 擴展生成的 CSS 文件。測試表明,將<code>NODE_ENV</code>設置為生產,可以將應用程序性能提高三倍!</p> + +<p>可以使用導出或環境文件,或使用 OS 初始化系統,以進行此更改。</p> + +<div class="note"> +<p><strong>注意:</strong> 這實際上是在環境設置,而不是應用中所做的更改,但重要的是,要注意這裡!我們將在下面,展示我們的託管示例要如何設置。</p> +</div> + +<h3 id="Log_appropriately">Log appropriately</h3> + +<p>記錄呼叫會對高流量網站產生影響。在生產環境中,您可能需要記錄網站活動(例如,跟踪流量,或記錄API調用),但您應嘗試最小化為調試目的而添加的日誌記錄量。</p> + +<p>在生產環境中,最小化“調試”日誌記錄的一種方法,是使用類似調試 <a href="https://www.npmjs.com/package/debug">debug </a> 的模塊,允許您通過設置環境變量,來控制執行的日誌記錄。例如,下面的代碼片段,顯示如何設置 “author” 日誌記錄。調試變量使用名稱 “author” 聲明,並且將自動顯示,來自此對象的所有日誌的前綴 “author”。</p> + +<pre class="brush: js"><strong>var debug = require('debug')('author');</strong> + +// Display Author update form on GET +exports.author_update_get = function(req, res, next) { + + req.sanitize('id').escape().trim(); + Author.findById(req.params.id, function(err, author) { + if (err) { +<strong> debug('update error:' + err);</strong> + return next(err); + } + //On success + res.render('author_form', { title: 'Update Author', author: author }); + }); + +};</pre> + +<p>然後,您可以通過在 <code>DEBUG </code>環境變量中,將它們指定為逗號分隔列表,來啟用特定日誌集。您可以設置顯示作者和書籍日誌的變量,如圖所示(也支持通配符)。</p> + +<pre class="brush: bash">#Windows +set DEBUG=author,book + +#Linux +export DEBUG="author,book" +</pre> + +<div class="note"> +<p><strong>挑戰:</strong> 調用<code>debug</code>可以替換您以前使用 <code>console.log()</code>或<code>console.error()</code>執行的日誌記錄。通過調試模塊 <a href="https://www.npmjs.com/package/debug">debug </a>進行日誌記錄,替換代碼中的所有<code>console.log()</code>調用。通過設置 DEBUG 變量,並在其中記錄對日誌記錄的影響,在開發環境中,打開和關閉日誌記錄。</p> +</div> + +<p>如果您需要記錄網站活動,可以使用 Winston 或 Bunyan 等日誌庫。有關此主題的更多信息,請參閱:<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生產最佳實踐:性能和可靠性</a>。</p> + +<h3 id="使用_gzipdeflate_壓縮響應">使用 gzip/deflate 壓縮響應</h3> + +<p>Web 服務器,通常可以壓縮發送回客戶端的 HTTP 響應,從而顯著減少客戶端獲取和加載頁面所需的時間。使用的壓縮方法,取決於客戶端在請求中支持的解壓縮方法(如果不支持壓縮方法,則響應將以未壓縮的方式發送)。</p> + +<p>您可以使用壓縮中間件 <a href="https://www.npmjs.com/package/compression">compression</a>,將其添加到您的站點。通過在項目的根目錄下,運行以下命令,將其安裝到項目中。</p> + +<pre class="brush: bash">npm install compression</pre> + +<p>打開<strong>./app.js</strong>,並導入壓縮庫,如圖所示。使用<code> use()</code>方法,將壓縮庫添加到中間件鏈(這應該出現在您想要壓縮的任何路由之前 - 在本教程這種情況下,全部都是!)</p> + +<pre class="brush: js">var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site +<strong>var compression = require('compression');</strong> + +// Create the Express application object +var app = express(); + +... + +<strong>app.use(compression()); //Compress all routes</strong> + +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', indexRouter); +app.use('/users', usersRouter); +app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain. + +... +</pre> + +<div class="note"> +<p><strong>注意</strong>: 對於生產中流量較大的網站,您不會使用此中間件。相反,你會使用像 Nginx 這樣的反向代理。</p> +</div> + +<h3 id="使用_Helmet_避免被常見漏洞侵襲">使用 Helmet 避免被常見漏洞侵襲</h3> + +<p><a href="https://www.npmjs.com/package/helmet">Helmet</a> 是一個中間件包,可以通過設置適當的 HTTP 標頭,來幫助保護您的應用,免受一些眾所周知的 Web 漏洞的影響(有關它設置的標頭/防護漏洞的詳細信息,請參閱文檔 <a href="https://helmetjs.github.io/docs/">docs</a>) 。</p> + +<p>通過在項目的根目錄下,運行以下命令,將其安裝到項目中。</p> + +<pre class="brush: bash">npm install helmet +</pre> + +<p>打開<strong>./app.js</strong>,並導入如圖所示的 helmet 庫。然後使用<code>use()</code>方法,將模塊添加到中間件鏈。</p> + +<pre class="brush: js">var compression = require('compression'); +<strong>var helmet = require('helmet'); +</strong> +// Create the Express application object +var app = express(); + +<strong>app.use(helmet())</strong>; +...</pre> + +<div class="note"> +<p id="production-best-practices-performance-and-reliability"><strong>注意:</strong> 上面的命令,添加了對大多數站點有意義的可用標頭子集。您可以按照 <a href="https://www.npmjs.com/package/helmet">npm </a>上的說明,根據需要添加/禁用特定標頭。</p> +</div> + +<h2 id="例子:在_Heroku_上安裝本地圖書館">例子:在 Heroku 上安裝本地圖書館</h2> + +<p>本節提供如何在 <a href="http://heroku.com">Heroku PaaS cloud</a> 雲上安裝 LocalLibrary 的實際演示。</p> + +<h3 id="為什麼選擇_Heroku">為什麼選擇 Heroku?</h3> + +<p>Heroku 是運行時間最長,且最受歡迎的基於雲的 PaaS 服務之一。它最初只支持 Ruby 應用程序,但現在可用於託管來自許多編程環境的應用程序,包括 Node(以及Express)!</p> + +<p>我們選擇使用 Heroku 有以下幾個原因:</p> + +<ul> + <li>Heroku 有一個免費套餐 <a href="https://www.heroku.com/pricing">free tier</a>(儘管有一些限制)。</li> + <li>作為 PaaS,Heroku 為我們提供了大量的 Web 基礎架構。這使得入門更加容易,因為您不必擔心服務器,負載平衡器,反向代理,崩潰時重新啟動網站,或者 Heroku 為我們提供的任何其他 Web 基礎結構。</li> + <li>雖然它確實有一些限制,但這些不會影響這個特定的應用程序。例如: + <ul> + <li>Heroku 只提供短期存儲,因此用戶上傳的文件無法安全地存儲在 Heroku本身。</li> + <li>如果半小時內沒有請求,免費套餐將使不活動的網絡應用程序進入睡眠。然後,該網站可能需要幾秒鐘才能被喚醒。</li> + <li>免費套餐將您網站運行的時間,限制為每月一定的小時數(不包括網站“睡著”的時間)。這對於低使用/演示站點來說很好,但如果需要100%的正常運行時間,則不適用。</li> + <li>Heroku 官方文檔 <a href="https://devcenter.heroku.com/articles/limits">Limits </a>中列出的其他限制。</li> + </ul> + </li> + <li>大多數情況下,它只是可以工作,如果你最終喜歡它,並希望升級,那麼擴展你的應用程序非常容易。</li> +</ul> + +<p>雖然 Heroku 非常適合舉辦此演示,但它可能並不適合您的真實網站。 Heroku 可以輕鬆設置和擴展,但代價是靈活性較低,而且一旦退出免費套餐,可能會花費更多。</p> + +<h3 id="Heroku_如何工作">Heroku 如何工作?</h3> + +<p>Heroku在一個或多個 "<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>" 中運行網站,這些 “Dynos” 是獨立的虛擬化Unix容器,提供運行應用程序所需的環境。 Dynos 是完全隔離的,並且有一個短暫的文件系統(一個短暫的文件系統,每次dyno重新啟動時都會清理/清空)。 dynos 默認共享的唯一內容,是應用程序配置變量 <a href="https://devcenter.heroku.com/articles/config-vars">configuration variables</a>。 Heroku 內部使用負載均衡器,將Web流量分配給所有 “web” dynos。由於它們之間沒有任何共享,Heroku 可以通過添加更多 dynos,來水平擴展應用程序(當然,您可能還需要擴展數據庫,以接受其他連接)。</p> + +<p>由於文件系統是短暫的,因此無法直接安裝應用程序所需的服務(例如數據庫,隊列,緩存系統,存儲,電子郵件服務等)。相反,Heroku Web應用程序使用 Heroku 或第三方作為獨立“附加組件”提供的支持服務。連接到Web應用程序後,可以通過環境變量,在Web應用程序中訪問附加服務。</p> + +<p>為了執行您的應用程序,Heroku 需要能夠設置適當的環境和依賴關係,並了解它是如何啟動的。對於 Node 應用程序,它所需的所有信息都是從 <strong>package.json</strong>文件中獲取的。</p> + +<p>開發人員使用特殊的客戶端應用程序/終端,與 Heroku 交互,這很像 Unix bash 腳本。這允許您上傳存儲在 git 儲存庫中的代碼,檢查正在運行的進程,查看日誌,設置配置變量等等!</p> + +<p>為了讓我們的應用程序在 Heroku 上工作,我們需要將我們的 Express Web 應用程序放入 git 儲存庫,並對 package.json 進行一些小的更改。完成後,我們可以設置Heroku 帳戶,獲取 Heroku 客戶端,並使用它來安裝我們的網站。</p> + +<p>這是您開始教程所需的全部概述(有關更全面的指南,請參閱帶有 Node.js 的<a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">Heroku</a> 入門)。</p> + +<h3 id="在_Github_上創建一個應用倉庫">在 Github 上創建一個應用倉庫</h3> + +<p>Heroku 與 <strong>git</strong> 源代碼版本控制系統緊密集成,使用它來上傳/同步您對實時運行系統所做的任何更改。它通過添加一個名為 heroku 的新 Heroku“遠程”儲存庫,來指向您在Heroku雲上的源儲存庫。在開發期間,您使用 git 在“主”儲存庫 master 中儲存更改。如果要部署站點,請將更改同步到 Heroku 存儲庫。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果您習慣於遵循良好的軟件開發實踐,那麼您可能已經在使用 git 或其他一些 SCM 系統。如果您已有 git 儲存庫,則可以跳過此步驟。</p> +</div> + +<p>有很多方法可以使用git,但最簡單的方法之一,是首先在 <a href="https://github.com/">GitHub </a>上建立一個帳戶,在那裡創建儲存庫,然後在本地同步它:</p> + +<ol> + <li>訪問 <a href="https://github.com/">https://github.com/</a> 並創建一個帳戶。</li> + <li>登錄後,單擊頂部工具欄中的 + 號鏈接,然後選擇新建儲存庫 <strong>New repository</strong>。</li> + <li>填寫此表單上的所有字段。雖然這些不是強制性的,但強烈建議使用它們。 + <ul> + <li>輸入新的存儲庫名稱(例如,express-locallibrary-tutorial)和描述(例如 “以Express(node)編寫的本地圖書館網站”)。</li> + <li>在 Add .gitignore 選擇列表中選擇 <strong>Node</strong>。</li> + <li>在添加許可證 Add license 選擇列表中,選擇您偏好的許可證。</li> + <li>點選 使用自述文件初始化此儲存庫 <strong>Initialize this repository with a README</strong>.</li> + </ul> + </li> + <li>按 <strong>Create repository</strong>.</li> + <li>單擊新倉庫頁面上的綠色“克隆或下載”按鈕 "<strong>Clone or download</strong>" 。</li> + <li>從顯示的對話框的文本字段,複製 URL值(它應該類似於:<strong>https://github.com/<em><your_git_user_id></em>/express-locallibrary-tutorial.git</strong>)。</li> +</ol> + +<p>現在創建了儲存庫(“repo”),我們將要在本地計算機上克隆它:</p> + +<ol> + <li>為您的本地計算機安裝 git(您可以在<a href="https://git-scm.com/downloads">此處找到不同平台的版本</a>)。<br> + </li> + <li>打開命令提示符/終端,並使用您在上面複製的 URL ,克隆儲存庫: + <pre class="brush: bash">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/express-locallibrary-tutorial.git +</pre> + 這將在當前時間點之後,創建儲存庫。</li> + <li>到新的儲存庫。 + <pre class="brush: bash">cd express-locallibrary-tutorial</pre> + </li> +</ol> + +<p>最後一步,是複制你的應用程序,然後使用 git ,將文件添加到你的倉庫:</p> + +<ol> + <li>將Express應用程序,複製到此文件夾中(不包括 <strong>/node_modules</strong>,其中包含您應根據需要,從 NPM 獲取的依賴項文件)。</li> + <li>打開命令提示符/終端,並使用 <code>add </code>命令,將所有文件添加到 git。</li> + <li> + <pre class="brush: bash">git add -A +</pre> + </li> + <li>使用 status 命令,檢查要添加的所有文件是否正確(您希望包含源文件,而不是二進製文件,臨時文件等)。它應該看起來有點像下面的列表。 + <pre>> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + new file: ...</pre> + </li> + <li>如果您滿意,請將文件提交到本地儲存庫: + <pre class="brush: bash">git commit -m "First version of application moved into github"</pre> + </li> + <li>然後使用以下內容,將本地儲存庫同步到 Github 網站: + <pre>git push origin master</pre> + </li> +</ol> + +<p>完成此操作後,您應該可以返回創建儲存庫的 Github 上的頁面,刷新頁面,並查看您的整個應用程序現已上傳。使用此添加/提交/推送循環,您可以在文件更改時,繼續更新儲存庫。</p> + +<div class="note"> +<p><strong>提示:</strong> 這是備份你的“vanilla”項目的好時機 - 雖然我們將在以下部分中進行的一些更改,可能對任何平台(或開發)上的部署有用,而一些其他的更改可能沒有用。</p> + +<p>執行此操作的最佳方法,是使用 git 來管理您的修訂。使用 git,您不僅可以回到特定的舊版本,而且可以在生產變更的單獨“分支”中進行維護,並選擇在生產和開發分支之間移動的任何更改。<a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">學習Git</a>非常值得,但超出了本主題的範圍。</p> + +<p>最簡單的方法,是將文件複製到另一個位置。以您對 git 了解,使用最符合的方法!</p> +</div> + +<h3 id="更新_Heroku_的應用程序">更新 Heroku 的應用程序</h3> + +<p>本節介紹了您需要對 LocalLibrary 應用程序進行的更改,以使其在 Heroku 上運行。</p> + +<h4 id="設置_node_版本">設置 node 版本</h4> + +<p><strong>package.json</strong> 包含解決應用程序依賴項所需的所有內容,以及啟動站點時,應啟動的文件。 Heroku 檢測到此文件的存在,並將使用它來配置您的應用程序環境。</p> + +<p>我們當前的 <strong>package.json</strong> 中,缺少的唯一有用信息,是 node 的版本。我們可以通過輸入命令,找到我們用於開發的 node 版本:</p> + +<pre class="brush: bash">>node --version +v8.9.1</pre> + +<p>打開 <strong>package.json</strong>,並將此信息添加為 <strong>engines > node</strong> 部分,如圖所示(使用系統的版本號)。</p> + +<pre class="brush: json">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", +<strong> "engines": { + "node": "8.9.1" + },</strong> + "private": true, + ... +</pre> + +<h4 id="數據庫配置">數據庫配置</h4> + +<p>到目前為止,在本教程中,我們使用了一個硬編碼到 <strong>app.js</strong> 的單個數據庫。通常我們希望,能夠為生產和開發創建不同的數據庫,接下來我們將修改 LocalLibrary 網站,以從 OS 環境獲取數據庫 URI(如果已定義),否則使用我們的開發數據庫。</p> + +<p>打開 <strong>app.js</strong>,並找到設置 mongoDB 連接變量的行。它看起來像這樣:</p> + +<pre class="brush: js">var mongoDB = 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library';</pre> + +<p>使用以下代碼替換該行,該代碼使用 <code>process.env.MONGODB_URI </code>從名為 <code>MONGODB_URI </code>的環境變量中,獲取連接字符串(如果已設置)(使用您自己的數據庫URL,而不是下面的佔位符。)</p> + +<pre class="brush: js">var mongoDB = <strong>process.env.MONGODB_URI</strong> || 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library'; +</pre> + +<h4 id="安裝依賴並重新測試">安裝依賴並重新測試</h4> + +<p>在我們繼續之前,讓我們再次測試該網站,並確保它不受我們的任何更改的影響。</p> + +<p>首先,我們需要獲取我們的依賴項(你會記得,我們沒有將 <strong>node_modules</strong>文件夾,複製到我們的 git 樹中)。您可以通過在項目根目錄的終端中,運行以下命令來執行此操作:</p> + +<pre class="brush: bash">npm install +</pre> + +<p>現在運行該站點(請參閱<a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">測試路由</a>的相關命令),並檢查該站點,是否仍按預期運行。</p> + +<h4 id="將更改保存到_Github">將更改保存到 Github</h4> + +<p>接下來,讓我們將所有更改保存到 Github。在終端中(在我們的儲存庫中),輸入以下命令:</p> + +<pre class="brush: bash">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>我們現在應該準備開始在 Heroku 上,部署 LocalLibrary。</p> + +<h3 id="獲取一個_Heroku_帳戶">獲取一個 Heroku 帳戶</h3> + +<p>要開始使用 Heroku,您首先需要創建一個帳戶(如果您已經擁有一個帳戶,並安裝了 Heroku 客戶端,請跳過創建並上傳網站):</p> + +<ul> + <li>訪問 <a href="https://www.heroku.com/">www.heroku.com</a> 並單擊免費註冊按鈕 <strong>SIGN UP FOR FREE</strong></li> + <li>輸入您的詳細信息,然後按 <strong>CREATE FREE ACCOUNT</strong>。系統會要求您,檢查帳戶中是否有註冊電子郵件。</li> + <li>單擊註冊電子郵件中的帳戶激活鏈接。您將在網絡瀏覽器上收回您的帳戶。</li> + <li>輸入您的密碼,然後單擊 <strong>SET PASSWORD AND LOGIN</strong>.</li> + <li>然後,您將登錄並進入Heroku儀表板: <a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="安裝客戶端">安裝客戶端</h3> + +<p>按照 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku 上的說明</a>,下載並安裝 Heroku 客戶端。</p> + +<p>安裝客戶端后,您將能夠運行命令。例如,要獲得客戶端的幫助說明:</p> + +<pre class="brush: bash">heroku help +</pre> + +<h3 id="創建並上傳網站">創建並上傳網站</h3> + +<p>要創建應用程序,我們在儲存庫的根目錄中,運行 “create” 命令。這將在我們的本地git 環境中,創建一個名為 heroku 的 git remote(“指向遠程儲存庫的指針”)。</p> + +<pre class="brush: bash">heroku create</pre> + +<div class="note"> +<p><strong>注意:</strong> 如果您願意,可以在“創建”create 之後指定遠程儲存庫的命名。如果你不這樣做,你會得到一個隨機的名字。該名稱用於默認 URL。</p> +</div> + +<p>然後,我們可以將我們的應用程序,推送到 Heroku 儲存庫,如下所示。這將上傳應用程序,獲取所有依賴項,將其打包到 dyno 中,然後啟動該站點。</p> + +<pre class="brush: bash">git push heroku master</pre> + +<p>如果我們很幸運,該應用程序現在正在網站上“運行”。要打開瀏覽器並運行新網站,請使用以下命令:</p> + +<pre class="brush: bash">heroku open</pre> + +<div class="note"> +<p><strong>注意</strong>: 該站點將使用我們的開發數據庫運行。創建一些書本和其他對象,並檢查該網站是否按預期運行。在下一節中,我們將其設置為使用我們的新數據庫。</p> +</div> + +<h3 id="設定配置變量">設定配置變量</h3> + +<p>您將從前一節回憶起,我們需要將 NODE_ENV 設置為 'production',以便提高性能,並生成更簡潔的錯誤消息。我們通過輸入以下命令,來完成此操作:</p> + +<pre class="brush: bash">>heroku config:set NODE_ENV='production' +Setting NODE_ENV and restarting limitless-tor-18923... done, v13 +NODE_ENV: production +</pre> + +<p>我們還應該使用單獨的數據庫進行生產,在<strong>MONGODB_URI</strong>環境變量中,設置其URI。您可以完全按照<a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose#Setting_up_the_MongoDB_database">我們原來的方式</a>,設置新數據庫和數據庫用戶,並獲取其URI。您可以如下圖所示設置URI(顯然,要使用您自己的URI!)</p> + +<pre class="brush: bash">>heroku config:set <strong>MONGODB_URI</strong>='mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production' +Setting MONGODB_URI and restarting limitless-tor-18923... done, v13 +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +</pre> + +<p>您可以使用 <code>heroku config </code>命令,隨時檢查配置變量 - 立即嘗試:</p> + +<pre class="brush: bash">>heroku config +=== limitless-tor-18923 Config Vars +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +NODE_ENV: production +</pre> + +<p>Heroku 會在更新變量時,重新啟動應用程序。如果您現在檢查主頁,它應該顯示對象計數的零值,因為上面的更改,意味著我們現在正在使用新的(空)數據庫。</p> + +<h3 id="管理附加組件">管理附加組件</h3> + +<p>Heroku 使用獨立的附加組件,為應用程序提供支持服務 - 例如電子郵件或數據庫服務。我們不在本網站中使用任何插件,但它們是使用 Heroku 的重要部分,因此您可能需要查看主題 - <a href="https://devcenter.heroku.com/articles/managing-add-ons">管理插件(Heroku 官方文件)</a>。</p> + +<h3 id="除錯">除錯</h3> + +<p>Heroku 客戶端提供了一些除錯工具:</p> + +<pre class="brush: bash">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku ps #Display dyno status +</pre> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p>本教程介紹在生產環境中,如何配置 Express 應用。是Express系列教程的最後一個。我們希望你覺得這些教程有用。你可以在 <a href="https://github.com/mdn/express-locallibrary-tutorial">Github </a>上取得完整的源碼。</p> + +<h2 id="相關鏈接">相關鏈接</h2> + +<ul> + <li id="production-best-practices-performance-and-reliability"><a href="https://expressjs.com/en/advanced/best-practice-performance.html">Production best practices: performance and reliability</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/advanced/best-practice-security.html">Production Best Practices: Security</a> (Express docs)</li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">Getting Started on Heroku with Node.js</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-nodejs">Deploying Node.js Applications on Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/nodejs-support">Heroku Node.js Support</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/node-concurrency">Optimizing Node.js Application Concurrency</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs)</li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials?q=express">Express</a> tutorials</li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=node.js">Node.js</a> tutorials </li> + </ul> + </li> +</ul> + +<p>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="本教學鏈接">本教學鏈接</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/development_environment/index.html b/files/zh-tw/learn/server-side/express_nodejs/development_environment/index.html new file mode 100644 index 0000000000..3e556ada3a --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/development_environment/index.html @@ -0,0 +1,385 @@ +--- +title: Setting up a Node development environment +slug: Learn/Server-side/Express_Nodejs/development_environment +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">現在你已經了解Express的目的了,接下來繼續說明如何設定和測試 Windows、Linux (Ubuntu)和Mac OS X上的Node/Express開發環境。不管你用的是什麼作業系統,你都能在本文中找到開發Express應用的入門需知。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置需求:</th> + <td>了解如何開啟terminal / command line. 了解如何在開發系統上安裝套件。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>在你的電腦上設定Express(X.XX)開發環境。</td> + </tr> + </tbody> +</table> + +<h2 id="Express_開發環境概覽">Express 開發環境概覽</h2> + +<p>為了使你能快速的開發web應用,<em>Node</em> 和 <em>Express</em> 非常容易安裝,這個部分說明哪些工具是需要的、在Ubuntu、macOS和Windows中安裝Node和Express的最簡單方法、展示如何測試安裝成功與否。</p> + +<h3 id="什麼是Express開發環境">什麼是Express開發環境?</h3> + +<p><em>Express</em> 開發環境包含 <em>Nodejs、</em><em>NPM </em>套件管理器的安裝, 還有 <em>Express Application </em>產生器(可選)<em>。</em></p> + +<p><em>Node </em>和 <em>NPM</em> 套件管理器會從準備好的 binary package、安裝檔、 作業系統的套件管理器或是從源檔一起安裝。接著 <em>Express </em>會透過 NPM 進行安裝,成為你所有個別 Express web 應用的依賴項(以及其他函式庫,如模板引擎,資料庫驅動程式,身份驗證中間層,用於提供靜態文件的中間件等)</p> + +<p>NPM 也可用來安裝 Express 應用程式產生器(全域用),一個方便的工具幫助你創造符合 <a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC模式</a>的 Express web app 骨架。你不一定要使用應用程式產生器,因為每個Express應用程式不需要擁有同樣的檔案結構或依賴項。但為了專注於學習本身以及習慣模組化架構,我們會在接下來的教學中使用它。</p> + +<div class="note"> +<p><strong>注意:</strong> 與其他不包含單獨的web開發伺服器的Web框架不同。 在Node / Express中,Web應用程式創建並運行自己的Web伺服器!</p> +</div> + +<p>典型的開發環境還包含其他工具,例如:編輯程式碼使用的<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Available_text_editors">文字編輯器</a>、IDE,進行版本控置管理不同版本程式碼的<a href="/zh-TW/docs/Glossary/Git">Git</a>。這邊假設你已經有這種工具了(尤其是文字編輯器)</p> + +<h3 id="哪些作業系統有支援">哪些作業系統有支援?</h3> + +<p>Node 可以執行在 Windows、macOS、各種 Linux、Docker 等等(nodejs 的<a href="https://nodejs.org/en/download/">下載</a>頁面有完整的列表),在開發階段中個人電腦應該都有足夠的效能來執行 Node 。Express 執行在 Node 環境中,所以也能所有有安裝Node的平台上執行。</p> + +<p>在這份教學中我們提供 Windows、macOS 和 Ubuntu Linux 的 Node 安裝教學。</p> + +<h3 id="該用什麼版本的_NodeExpress">該用什麼版本的 Node/Express?</h3> + +<p>Node 有許多<a href="https://nodejs.org/en/blog/release/">版本</a>,更新的版本代表著 bug 的修復、支援更新版本的 ECMAScript(JavaScript)標準和更好的 Node APIs 。</p> + +<p>基本上你應該使用最新的 LTS 版本(<em>long-term supported,</em>長期維護版)。這種版本比『Current』版本更穩定而且還擁有最新的功能及持續性的更新維護。除非LTS不支援你需要的功能才使用『Current』版本。</p> + +<p>而 Express ?永遠使用最新版!</p> + +<h3 id="關於資料庫和其他依賴項呢">關於資料庫和其他依賴項呢?</h3> + +<p>諸如資料庫、模版引擎、驗證引擎等等都屬於應用程式的一部分,這些依賴項會透過NPM導入應用程式環境中,在後續的章節將會進一步探討。</p> + +<h2 id="安裝Node">安裝Node</h2> + +<p>為了使用Express,首先要在你的電腦上安裝Node和<a href="https://docs.npmjs.com/">Node Package Manager (NPM)</a>。接下來用最簡單的方法在 Ubuntu Linux 16.04、 macOS和 Windows 10上安裝Nodejs的 Long Term Supported (LTS)版本吧</p> + +<div class="note"> +<p>以下的部分用最簡單的方法在上述的作業系統中安裝Node和NPM。如果你使用其他作業系統或想看看其他平台的安裝方式,請查閱<a href="https://nodejs.org/en/download/package-manager/">透過套件管理器安裝Node.js</a> (nodejs.org)。</p> +</div> + +<h3 id="Windows_和macOS">Windows 和macOS</h3> + +<p>直接使用安裝檔吧!</p> + +<ol> + <li>下載需要的安裝檔: + <ol> + <li>開啟 <a href="https://nodejs.org/en/">https://nodejs.org/en/</a></li> + <li>對於大部分的使用者來說,直接下載LTS版本</li> + </ol> + </li> + <li>下載完成後雙擊安裝檔,並照著安裝流程繼續。</li> +</ol> + +<h3 id="Ubuntu_16.04">Ubuntu 16.04</h3> + +<p>安裝Node 8.x LTS版本最簡單的方法是使用<a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions">套件管理器</a>,只要在terminal上執行兩行指令</p> + +<pre class="brush: bash notranslate"><code>curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - +sudo apt-get install -y nodejs</code> + +</pre> + +<div class="warning"> +<p><strong>警告:</strong> 不要直接從普通的Ubuntu repositories 安裝,那邊只有很舊的版本。</p> +</div> + +<ol> +</ol> + +<h3 id="測試_Nodejs_和NPM_的安裝">測試 Nodejs 和NPM 的安裝</h3> + +<p>測試Node安裝最簡單的方法是在terminal/command上執行"version"這個指令,它會顯示當前的Node版本:</p> + +<pre class="brush: bash notranslate">>node -v +v8.9.4</pre> + +<p>NPM應該會隨著Node一起安裝,可以用相同的方法進行測試:</p> + +<pre class="brush: bash notranslate">>npm -v +5.6.0</pre> + +<p>接著用稍為令人興奮的方法來測試吧!讓我們創件一個非常基本的『純Node』伺服器,當你開啟正確的網頁時它會在瀏覽器上顯示"Hello World"</p> + +<ol> + <li>複製以下的文字到名為<strong>hellonode.js</strong>的檔案中,目前我們只用到Node而已。 + + <pre class="brush: js notranslate">//載入HTTP模組 +var http = require("http"); + +//創建HTTP 伺服器並監聽8000埠 +http.createServer(function (request, response) { + + // Set the response HTTP header with HTTP status and Content type + response.writeHead(200, {'Content-Type': 'text/plain'}); + + // Send the response body "Hello World" + response.end('Hello World\n'); +}).listen(8000); + +// Print URL for accessing server +console.log('Server running at http://127.0.0.1:8000/') +</pre> + + <p>這段程式載入『http』模組,並創建一個伺服器 (<code>createServer()</code>,並在8000埠上監聽HTTP requests。 The script then prints a message to the console about what browser URL you can use to test the server. The <code>createServer()</code> function takes as an argument a callback function that will be invoked when an HTTP request is received — this simply returns a response with an HTTP status code of 200 ("OK") and the plain text "Hello World".</p> + </li> + <li> + <div class="note"> + <p><strong>Note:</strong> Don't worry if you don't understand exactly what this code is doing yet! We'll explain our code in greater detail once we start using Express!</p> + </div> + </li> + <li>Start the server by navigating into the same directory as your <code>hellonode.js</code> file in your command prompt, and calling <code>node</code> along with the script name, like so: + <pre class="brush: bash notranslate">>node hellonode.js +Server running at http://127.0.0.1:8000/ +</pre> + </li> + <li>Navigate to the URL (<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>). If everything is working, the browser should simply display the string "Hello World".</li> +</ol> + +<h2 id="Using_NPM">Using NPM</h2> + +<p>Next to <em>Node</em> itself, <a href="https://docs.npmjs.com/">NPM</a> is the most important tool for working with<em> Node </em>applications. NPM is used to fetch any packages (JavaScript libraries) that an application needs for development, testing, and/or production, and may also be used to run tests and tools used in the development process. </p> + +<div class="note"> +<p><strong>Note:</strong> From Node's perspective, <em>Express</em> is just another package that you need to install using NPM and then require in your own code.</p> +</div> + +<p>You can manually use NPM to separately fetch each needed package. Typically we instead manage dependencies using a plain-text definition file named <a href="https://docs.npmjs.com/files/package.json">package.json</a>. This file lists all the dependencies for a specific JavaScript "package", including the package's name, version, description, initial file to execute, production dependencies, development dependencies, versions of <em>Node</em> it can work with, etc. The <strong>package.json</strong> file should contain everything NPM needs to fetch and run your application (if you were writing a reusable library you could use this definition to upload your package to the npm respository and make it available for other users).</p> + +<h3 id="Adding_dependencies">Adding dependencies</h3> + +<p>The following steps show how you can use NPM to download a package, save it into the project dependencies, and then require it in a Node application.</p> + +<div class="note"> +<p><strong>Note:</strong> Here we show the instructions to fetch and install the <em>Express</em> package. Later on we'll show how this package, and others, are already specified for us using the <em>Express Application Generator</em>. This section is provided because it is useful to understand how NPM works and what is being created by the application generator.</p> +</div> + +<ol> + <li>First create a directory for your new application and navigate into it: + <pre class="brush: bash notranslate">mkdir myapp +cd myapp</pre> + </li> + <li>Use the npm <code>init</code> command to create a <strong>package.json</strong> file for your application. This command prompts you for a number of things, including the name and version of your application and the name of the initial entry point file (by default this is <strong>index.js</strong>). For now, just accept the defaults: + <pre class="brush: bash notranslate">npm init</pre> + + <p>If you display the <strong>package.json</strong> file (<code>cat package.json</code>), you will see the defaults that you accepted, ending with the license.</p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li>Now install the <em>Express</em> library in the <strong>myapp</strong> directory. The package will automatically be saved to the dependencies list in your <strong>package.json</strong> file. + <pre class="brush: bash notranslate">npm install express</pre> + + <p>The dependencies section of your <strong>package.json</strong> will now appear at the end of the <strong>package.json</strong> file and will include <em>Express</em>.</p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.2" + }</strong> +} +</pre> + </li> + <li>To use the library you call the <code>require()</code> function as shown below. + <pre class="notranslate"><code><strong>var express = require('express')</strong> +var app = express() + +app.get('/', function (req, res) { + res.send('Hello World!') +}) + +app.listen(</code>8000<code>, function () { + console.log('Example app listening on port </code>8000<code>!') +})</code> +</pre> + + <p>This code shows a minimal "HelloWorld" Express web application. This imports the "express" module and uses it to create a server (<code>app</code>) that listens for HTTP requests on port 8000 and prints a message to the console explaining what browser URL you can use to test the server. The <code>app.get()</code> function only responds to HTTP <code>GET</code> requests with the specified URL path ('/'), in this case by calling a function to send our <em>Hello World!</em> message. <br> + <br> + Create a file named <strong>index.js</strong> in the root of the "myapp" application directory and give it the contents shown above.</p> + </li> + <li>You can start the server by calling node with the script in your command prompt: + <pre class="brush: bash notranslate">>node index.js +Example app listening on port 8000 +</pre> + </li> + <li>Navigate to the URL (<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>). If everything is working, the browser should simply display the string "Hello World!".</li> +</ol> + +<h3 id="Development_dependencies">Development dependencies</h3> + +<p>If a dependency is only used during development, you should instead save it as a "development dependency" (so that your package users don't have to install it in production). For example, to use the popular JavaScript Linting tool <a href="http://eslint.org/">eslint</a> you would call NPM as shown:</p> + +<pre class="brush: bash notranslate"><code>npm install eslint --save-dev</code></pre> + +<p>The following entry would then be added to your application's <strong>package.json</strong>:</p> + +<pre class="brush: js notranslate"> "devDependencies": { + "eslint": "^4.12.1" + } +</pre> + +<div class="note"> +<p><strong>Note:</strong> "<a href="https://en.wikipedia.org/wiki/Lint_(software)">Linters</a>" are tools that perform static analysis on software in order to recognise and report adherence/non-adherance to some set of coding best practice.</p> +</div> + +<h3 id="Running_tasks">Running tasks</h3> + +<p>In addition to defining and fetching dependencies you can also define <em>named</em> scripts in your <strong>package.json</strong> files and call NPM to execute them with the <a href="https://docs.npmjs.com/cli/run-script">run-script</a> command. This approach is commonly used to automate running tests and parts of the development or build toolchain (e.g., running tools to minify JavaScript, shrink images, LINT/analyse your code, etc).</p> + +<div class="note"> +<p><strong>Note:</strong> Task runners like <a href="http://gulpjs.com/">Gulp</a> and <a href="http://gruntjs.com/">Grunt</a> can also be used to run tests and other external tools.</p> +</div> + +<p>For example, to define a script to run the <em>eslint</em> development dependency that we specified in the previous section we might add the following script block to our <strong>package.json</strong> file (assuming that our application source is in a folder /src/js):</p> + +<pre class="brush: js notranslate">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p>To explain a little further, <code>eslint src/js</code> is a command that we could enter in our terminal/command line to run <code>eslint</code> on JavaScript files contained in the <code>src/js</code> directory inside our app directory. Including the above inside our app's package.json file provides a shortcut for this command — <code>lint</code>.</p> + +<p>We would then be able to run <em>eslint</em> using NPM by calling:</p> + +<pre class="brush: bash notranslate"><code>npm run-script lint +# OR (using the alias) +npm run lint</code> +</pre> + +<p>This example may not look any shorter than the original command, but you can include much bigger commands inside your npm scripts, including chains of multiple commands. You could identify a single npm script that runs all your tests at once.</p> + +<h2 id="Installing_the_Express_Application_Generator">Installing the Express Application Generator</h2> + +<p>The <a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> tool generates an Express application "skeleton". Install the generator using NPM as shown (the <code>-g</code> flag installs the tool globally so that you can call it from anywhere):</p> + +<pre class="notranslate"><code>npm install express-generator -g</code></pre> + +<p>To create an <em>Express</em> app named "helloworld" with the default settings, navigate to where you want to create it and run the app as shown:</p> + +<pre class="brush: bash notranslate">express helloworld</pre> + +<div class="note"> +<p><strong>Note: </strong>You can also specify the template library to use and a number of other settings. Use the <code>help</code> command to see all the options:</p> + +<pre class="brush: bash notranslate">express --help +</pre> +</div> + +<p>NPM will create the new Express app in a sub folder of your current location, displaying build progress on the console. On completion, the tool will display the commands you need to enter to install the Node dependencies and start the app.</p> + +<div class="note"> +<p>The new app will have a <strong>package.json</strong> file in its root directory. You can open this to see what dependencies are installed, including Express and the template library Jade:</p> + +<pre class="brush: js notranslate">{ + "name": "helloworld", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.18.2", + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.15.5", + "jade": "~1.11.0", + "morgan": "~1.9.0", + "serve-favicon": "~2.4.5" + } +}</pre> +</div> + +<p>Install all the dependencies for the helloworld app using NPM as shown:</p> + +<pre class="brush: bash notranslate">cd helloworld +npm install +</pre> + +<p>Then run the app (the commands are slightly different for Windows and Linux/macOS), as shown below:</p> + +<pre class="brush: bash notranslate"># Run the helloworld on Windows +SET DEBUG=helloworld:* & npm start + +# Run helloworld on Linux/macOS +DEBUG=helloworld:* npm start +</pre> + +<p>The DEBUG command creates useful logging, resulting in an output like that shown below.</p> + +<pre class="brush: bash notranslate">>SET DEBUG=helloworld:* & npm start + +> helloworld@0.0.0 start D:\Github\expresstests\helloworld +> node ./bin/www + + helloworld:server Listening on port 3000 +0ms</pre> + +<p>Open a browser and navigate to <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> to see the default Express welcome page.</p> + +<p><img alt="Express - Generated App Default Screen" src="https://mdn.mozillademos.org/files/14331/express_default_screen.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 675px;"></p> + +<p>We'll talk more about the generated app when we get to the article on generating a skeleton application.</p> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p>你現在有一個 Node 開發環境在你的電腦上運行,可以用來創造 Express 網頁應用。你也看到如何用 NPM 來加載 Express到一個應用中,以及看到如何使用 Express 應用產生器,創建應用,然後執行它們。</p> + +<p>下一篇文章,我們開始跟著教程一步一步實作,使用這個開發環境與搭配工具,建立一個完整的網頁應用。</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://nodejs.org/en/download/">Downloads</a> page (nodejs.org)</li> + <li><a href="https://nodejs.org/en/download/package-manager/">Installing Node.js via package manager</a> (nodejs.org)</li> + <li><a href="http://expressjs.com/en/starter/installing.html">Installing Express</a> (expressjs.com)</li> + <li><a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> (expressjs.com)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html new file mode 100644 index 0000000000..df7a5180e5 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html @@ -0,0 +1,89 @@ +--- +title: 作者詳情頁面 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +--- +<p>作者細節頁面,需要呈現指定作者 <code>Author </code>的信息,使用 <code>_id</code> 欄位的值(自動產生)識別,接著是這個作者 <code>Author </code>的所有書本物件 <code>Book </code>的列表。</p> + +<h2 id="Controller_控制器">Controller 控制器</h2> + +<p>打開 <strong>/controllers/authorController.js</strong>.</p> + +<p>在檔案最上方,加入底下幾行,引入 <em>async</em> 和 <em>Book</em> 模組(作者細節頁面需要它們)。</p> + +<pre class="brush: js">var async = require('async'); +var Book = require('../models/book');</pre> + +<p>找到 exported <code>author_detail()</code> 控制器方法,並用底下代碼置換。</p> + +<pre class="brush: js">// Display detail page for a specific Author. +exports.author_detail = function(req, res, next) { + +<strong> async.parallel({ + author: function(callback) { + Author.findById(req.params.id) + .exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id },'title summary') + .exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } // Error in API usage. + if (results.author==null) { // No results. + var err = new Error('Author not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('author_detail', { title: 'Author Detail', author: results.author, author_books: results.authors_books } ); + });</strong> + +}; +</pre> + +<p>此處的控制器方法使用<code> async.parallel()</code>,用平行的方式,查詢作者 <code>Author</code>和相應的書本實例,並附加上繪製本頁面的回調,如果 2 個要求都成功完成,就運行回調。這個方式,就跟前面的<em>種類細節頁面</em>所說明的完全相同。</p> + +<h2 id="View_視圖">View 視圖</h2> + +<p>創建 <strong>/views/author_detail.pug</strong> ,並複制貼上底下的文字。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 Author: #{author.name}</strong> + p #{author.date_of_birth} - #{author.date_of_death} + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This author has no books. +</pre> + +<p>本模板裡的所有事物,都在先前的章節演示過了。</p> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>運行本應用,並打開瀏覽器訪問 <a href="http://localhost:3000/">http://localhost:3000/</a>。選擇 <em>All Authors</em> 連結,然後選擇一個作者。如果每個東西都設定正確了,你的網站看起來應該會像底下的截圖。</p> + +<p><img alt="Author Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14466/LocalLibary_Express_Author_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 422px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 作者的出生與死亡日期的外觀很醜!我們將在本文最後的自我挑戰處理它。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> + <li>繼續教學 5 的最後一個部分: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">書本實例詳情頁面與自我挑戰</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..f207126ed1 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,85 @@ +--- +title: Author list page and Genre list page challenge +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +<p>作者列表頁面,需要呈現數據庫中所有作者的列表,有每位作者的名字,並連結到作者詳細內容頁面。出生與死亡日期應該在名字後面,並且在同一列。</p> + +<h2 class="highlight-spanned" id="Controller_控制器"><span class="highlight-span">Controller </span>控制器</h2> + +<p>作者列表控制器函數,需要獲取所有作者實例的列表,然後將這些實例傳遞給模板進行渲染。</p> + +<p>打開 <strong>/controllers/authorController.js</strong>。在文件頂部附近,找到導出的 <code>author_list() </code>控制器方法,並將其替換為以下代碼(更改後的代碼以<strong>粗體</strong>顯示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Authors.</span> +exports<span class="punctuation token">.</span>author_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">sort</span><span class="punctuation token">(</span><span class="punctuation token">[</span><span class="punctuation token">[</span><span class="string token">'family_name'</span><span class="punctuation token">,</span> <span class="string token">'ascending'</span><span class="punctuation token">]</span><span class="punctuation token">]</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_authors<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Author List'</span><span class="punctuation token">,</span> author_list<span class="punctuation token">:</span> list_authors <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>The method uses the model's <code>find()</code>, <code>sort()</code> and <code>exec()</code> functions to return all <code>Author</code> objects sorted by <code>family_name</code> in alphabetic order. The callback passed to the <code>exec()</code> method is called with any errors (or <code>null</code>) as the first parameter, or a list of all authors on success. If there is an error it calls the next middleware function with the error value, and if not it renders the <strong>author_list</strong>(.pug) template, passing the page <code>title</code> and the list of authors (<code>author_list</code>).</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Create <strong>/views/author_list.pug</strong> and replace its content with the text below.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each author <span class="keyword token">in</span> author_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">}</span> <span class="operator token">-</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_death<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no authors<span class="punctuation token">.</span></code></pre> + +<p>The view follows exactly the same pattern as our other templates.</p> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application and open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Then select the <em>All authors </em>link. If everything is set up correctly, the page should look something like the following screenshot.</p> + +<p><img alt="Author List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14468/LocalLibary_Express_Author_List.png" style="display: block; height: 453px; margin: 0px auto; width: 1200px;"></p> + +<div class="note"> +<p><strong>Note:</strong> The appearance of the author <em>lifespan </em>dates is ugly! You can improve this using the <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">same approach</a> as we used for the <code>BookInstance</code> list (adding the virtual property for the lifespan to the <code>Author</code> model). This time, however, there are missing dates, and references to nonexistent properties are ignored unless strict mode is in effect. <code>moment()</code> returns the current time, and you don't want missing dates to be formatted as if they were today. One way to deal with this is to define the body of the function that returns a formatted date so it returns a blank string unless the date actually exists. For example:</p> + +<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p> +</div> + +<h2 id="Genre_list_page—challenge!Edit">Genre list page—challenge!<a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data$edit#Genre_list_page—challenge!" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>In this section you should implement your own genre list page. The page should display a list of all genres in the database, with each genre linked to its associated detail page. A screenshot of the expected result is shown below.</p> + +<p><img alt="Genre List - Express Local Library site" src="https://mdn.mozillademos.org/files/14460/LocalLibary_Express_Genre_List.png" style="border-style: solid; border-width: 1px; display: block; height: 346px; margin: 0px auto; width: 600px;"></p> + +<p>The genre list controller function needs to get a list of all <code>Genre</code> instances, and then pass these to the template for rendering.</p> + +<ol> + <li>You will need to edit <code>genre_list()</code> in <strong>/controllers/genreController.js</strong>. </li> + <li>The implementation is almost exactly the same as the <code>author_list()</code> controller. + <ul> + <li>Sort the results by name, in ascending order.</li> + </ul> + </li> + <li>The template to be rendered should be named <strong>genre_list.pug</strong>.</li> + <li>The template to be rendered should be passed the variables <code>title</code> ('Genre List') and <code>genre_list</code> (the list of genres returned from your <code>Genre.find()</code> callback.</li> + <li>The view should match the screenshot/requirements above (this should have a very similar structure/format to the Author list view, except for the fact that genres do not have dates).</li> +</ol> + +<h2 id="Next_steps">Next steps</h2> + +<p>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</p> + +<p>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">Genre detail page</a>.</p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html new file mode 100644 index 0000000000..31f3d65284 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html @@ -0,0 +1,112 @@ +--- +title: 書本詳情頁面 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +--- +<p><em>書本細節頁面</em>需要呈現一本指定書本(<code>Book</code>)的信息, 使用它的 <code>_id</code> 字段值(自動產生)做為識別,接著是圖書館中書本實例(<code>BookInstance</code>)的信息。無論我們在哪裡呈現一個作者、種類、或書本實例,都應該連結到它的細節頁面。</p> + +<h2 id="Controller_控制器">Controller 控制器</h2> + +<p>打開 <strong>/controllers/bookController.js.</strong> ,找到 exported <code>book_detail()</code> 控制器方法,用底下的代碼置換。</p> + +<pre class="brush: js">// Display detail page for a specific book. +exports.book_detail = function(req, res, next) { + +<strong> async.parallel({ + book: function(callback) { + + Book.findById(req.params.id) + .populate('author') + .populate('genre') + .exec(callback); + }, + book_instance: function(callback) { + + BookInstance.find({ 'book': req.params.id }) + .exec(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.book==null) { // No results. + var err = new Error('Book not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('book_detail', { title: 'Title', book: results.book, book_instances: results.book_instance } ); + });</strong> + +}; + +</pre> + +<div class="note"> +<p><strong>注意:</strong> 我們不需要用 require 導入 <em>async</em> 和 <em>BookInstance</em>,當我們實作主頁面控制器的時候,我們就已經引入這些模組。</p> +</div> + +<p>此處的控制器方法使用 <code>async.parallel()</code>,用平行的方式找到 <code>Book</code> 以及它的相應複本 (<code>BookInstances</code>) 。這樣的處理方式,就跟上面的 <em>種類細節頁面</em> 所說明的完全相同。</p> + +<h2 id="View_視圖">View 視圖</h2> + +<p>創建 <strong>/views/book_detail.pug </strong>並加入底下文字。</p> + +<pre class="brush: js">extends layout + +block content + h1 #{title}: #{book.title} + + p #[strong Author:] + a(href=book.author.url) #{book.author.name} + p #[strong Summary:] #{book.summary} + p #[strong ISBN:] #{book.isbn} + p #[strong Genre:]&nbsp; + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, + + div(style='margin-left:20px;margin-top:20px') + h4 Copies + + each val in book_instances + hr + if val.status=='Available' + <strong>p.text-success</strong> #{val.status} + else if val.status=='Maintenance' + p.text-danger #{val.status} + else + p.text-warning #{val.status} + p #[strong Imprint:] #{val.imprint} + if val.status!='Available' + p #[strong Due back:] #{val.due_back} + p #[strong Id:]&nbsp; + a(href=val.url) #{val._id} + + else + p There are no copies of this book in the library. +</pre> + +<p>在這個模板裡,幾乎每個東西都在先前的章節演示過了。</p> + +<div class="note"> +<p><strong>注意:</strong> 與該書相關的種類列表,在模板中的實作,如以下代碼。除了最後一本書之外,在與本書相關的每個種類之後,都會添加一個逗號。</p> + +<pre> p #[strong Genre:] + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, </pre> +</div> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>運行本應用,並打開瀏覽器訪問 <a href="http://localhost:3000/">http://localhost:3000/</a>。選擇 <em>All books</em> 連結,然後選擇其中一本書。如果每個東西都設定正確了,你的頁面看起來應該像是底下的截圖。</p> + +<p><img alt="Book Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14470/LocalLibary_Express_Book_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 616px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> + <li>繼續教學 5 下一個部分: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者詳情頁面</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..a35b31767d --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,72 @@ +--- +title: 書本清單頁面 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +<p>接下做我們將實作書本列表頁面。這個頁面需要呈現數據庫中所有書本的列表,包含每本書的作者、標題,標題將成為一個超連結,連到書本詳細內容頁面。</p> + +<h2 class="highlight-spanned" id="控制器">控制器</h2> + +<p>書本列表控制器函數,需要獲取數據庫中所有 <code>Book</code>對象的列表,然後將這些對像傳給模板進行呈現。</p> + +<p>打開 <strong>/controllers/bookController.js</strong>. 找到導出的 <code>book_list()</code>控制器方法,並替換為下面的代碼。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Books.</span> +exports<span class="punctuation token">.</span>book_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="string token">'title author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book List'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> list_books <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>該方法使用模型的<code>find()</code>函數,返回所有 <code>Book </code>對象,選擇僅返回標題 <code>title </code>和作者 <code>author</code>,因為我們不需要其他字段(它也會返回 <code>_id </code>和虛擬欄位字段)。這裡我們還調用 <code>Book </code>上的 <code>populate()</code>,指定作者 <code>author</code>欄位字段 — 這將用完整的作者信息,替換儲存的書本作者 id。</p> + +<p>成功時,傳遞給查詢的回調,將呈現 <strong>book_list</strong>(.pug) 模板,將標題 <code>title </code>和<code>book_list</code>(包含作者的書本列表)作為變量傳遞。</p> + +<h2 class="highlight-spanned" id="View視圖"><span class="highlight-span">View</span>視圖</h2> + +<p>創建<strong> /views/book_list.pug</strong> 並複制底下的文字。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each book <span class="keyword token">in</span> book_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>book<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>book<span class="punctuation token">.</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no books<span class="punctuation token">.</span></code></pre> + +<p>這個視圖擴展了<strong> layout.pug</strong> 基本模板,並覆蓋了名為 '<strong>content</strong>' 的 <code>block </code>區塊 。它顯示我們從控制器傳入的標題 <code>title</code>(通過 <code>render()</code>方法),然後使用 <code>each</code>-<code>in</code>-<code>else </code>語法,遍歷 <code>book_list</code>變量。為每本圖書創建一個列表項,以顯示書名,並作為書的詳細信息頁面的鏈接,後面跟著作者姓名。如果 <code>book_list</code>中沒有書,則執行 <code>else</code> 子句,並顯示文字 “沒有書本” 'There are no books'。</p> + +<p> </p> + +<div class="note"> +<p><strong>注意:</strong> 我們使用 <code>book.url</code>,為每本書提供詳細記錄鏈接(我們已經實現了此路由,但尚未實現此頁面)。這是 <code>Book</code> 模型的一個虛擬屬性,它使用模型實例的 <code>_id</code> 字段,生成唯一的 URL 路徑。</p> +</div> + +<p>在這裡,我們感興趣的是,每本書被定義為兩行,第二行使用管道(上面高亮顯示)。這種方法是必要的,因為如果作者姓名位於上一行,那麼它將成為超鏈接的一部分。</p> + +<h2 class="highlight-spanned" id="它看起來像是">它看起來像是?</h2> + +<p>運行本應用 (參見 <a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">測試路由</a> 有相關的命令) ,並打開你的瀏覽器,訪問 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然後選擇所有書本連結 <em>All books</em>。如果每樣東西都設定正確了,你的網站看起來應該像底下的截圖。</p> + +<p> </p> + +<p><img alt="Book List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14464/LocalLibary_Express_Book_List.png" style="border-style: solid; border-width: 1px; display: block; height: 387px; margin: 0px auto; width: 918px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> + <li>繼續教學 5 下個部分: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">書本實例清單頁面</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html new file mode 100644 index 0000000000..e04981411c --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html @@ -0,0 +1,91 @@ +--- +title: 書本實例詳情頁面與自我挑戰 +slug: >- + Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +translation_of: >- + Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +--- +<h2 id="書本實例詳情頁面">書本實例詳情頁面</h2> + +<p><code>BookInstance</code> 詳情頁面,需要呈現每一個 <code>BookInstance </code>的信息,用 <code>_id</code> 欄位字段值(自動產生)做識別。它包含了 <code>Book</code> 名稱 (也是一個連結,連到 <em>書本細節</em>頁面),接著是紀錄中的其它的信息。</p> + +<h3 id="Controller_控制器">Controller 控制器</h3> + +<p>打開 <strong>/controllers/bookinstanceController.js</strong>. 找到 exported <code>bookinstance_detail()</code> 控制器方法,並用底下代碼置換。</p> + +<pre class="brush: js">// Display detail page for a specific BookInstance. +exports.bookinstance_detail = function(req, res, next) { + +<strong> BookInstance.findById(req.params.id) + .populate('book') + .exec(function (err, bookinstance) { + if (err) { return next(err); } + if (bookinstance==null) { // No results. + var err = new Error('Book copy not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('bookinstance_detail', { title: 'Book:', bookinstance: bookinstance}); + })</strong> + +}; +</pre> + +<p>該方法使用從 URL(使用路由)中提取的特定書本實例的ID,調用<code>BookInstance.findById()</code>,並通過請求參數(<code style="font-style: normal; font-weight: normal;">req.params.id</code>),在控制器中訪問。然後調用<code>populate()</code>來獲取相關 <code>Book </code>的詳細信息。</p> + +<h3 id="View_視圖">View 視圖</h3> + +<p>創建 <strong>/views/bookinstance_detail.pug</strong> ,並複製下面的內容。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 ID: #{bookinstance._id}</strong> + + p #[strong Title:] + a(href=bookinstance.book.url) #{bookinstance.book.title} + p #[strong Imprint:] #{bookinstance.imprint} + + p #[strong Status:] + if bookinstance.status=='Available' + span.text-success #{bookinstance.status} + else if bookinstance.status=='Maintenance' + span.text-danger #{bookinstance.status} + else + span.text-warning #{bookinstance.status} + + if bookinstance.status!='Available' + p #[strong Due back:] #{bookinstance.due_back} +</pre> + +<p>本模組中的所有東西,都在先前的章節演示過了。</p> + +<h3 id="它看起來像是">它看起來像是?</h3> + +<p>運行本應用,並打開瀏覽器訪問 <a href="http://localhost:3000/">http://localhost:3000/</a>/。選擇 <em>All book-instances</em> 連結,然後選擇其中一本。如果每個東西都設定正確了,你的網站看起來應該像是底下的截圖。</p> + +<p><img alt="BookInstance Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14472/LocalLibary_Express_BookInstance_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 362px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="自我挑戰">自我挑戰</h2> + +<p>目前,我們網站上顯示的大多數日期,都使用默認的 JavaScript 格式(例如 <em>Tue Dec 06 2016 15:49:58 GMT+1100 </em>(AUS東部夏令時間))。本文的挑戰,是改善作者<code>Author</code>生命週期日期顯示的外觀信息(死亡/誔生日期)和<em>BookInstance詳細信息</em>頁面,使用格式:December 6th, 2016。</p> + +<div class="note"> +<p><strong>注意:</strong> 您可以使用與我們用於 <em>Book Instance List</em> 的相同方法(將生命週期的虛擬屬性,添加到<code>Author</code>模型,並使用 <a href="https://www.npmjs.com/package/moment">moment </a>來設置日期字符串的格式)。</p> +</div> + +<p>這個挑戰的要求: </p> + +<ol> + <li>用 BookInstance 詳細信息頁面中的 <code>due_back_formatted</code> 替換 <code>due_back</code>。</li> + <li>更新作者模塊以添加壽命虛擬屬性。壽命應該有兩個值: <em>date_of_birth - date_of_death</em>,這兩個值的格式與 <code>BookInstance.due_back_formatted</code>的日期格式相同。</li> + <li>在當前使用 <code>date_of_birth</code> 和 <code>date_of_death</code>的所有視圖中,使用 <code>Author.lifespan</code> 。</li> +</ol> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..1b1656258e --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,71 @@ +--- +title: 書本實例清單頁面 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +<p>接下來,我們將實作圖書館中所有書本實例 (<code>BookInstance</code>) 的列表頁面。這個頁面需要包含與每個 <code>BookInstance</code> (鏈接到其詳細信息頁面) 關聯的書本 <code>Book</code> 標題,以及 <code>BookInstance</code>模型中的其他信息,包含每個副本的狀態,印記和唯一ID。唯一ID的文字,應該鏈接到 <code>BookInstance</code> 詳細信息頁面。</p> + +<h2 class="highlight-spanned" id="Controller_控制器"><span class="highlight-span">Controller</span> 控制器</h2> + +<p><code>BookInstance</code>列表控制器函數,需要獲取所有書本實例的列表,填充關聯的書本信息,然後將列表傳遞給模板以進行呈現。</p> + +<p>打開 <strong>/controllers/bookinstanceController.js</strong>。找到導出的 <code>bookinstance_list()</code>控制器方法,並用以下代碼替換它(更改後的代碼以<strong>粗體</strong>顯示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all BookInstances.</span> +exports<span class="punctuation token">.</span>bookinstance_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + BookInstance<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_bookinstances<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book Instance List'</span><span class="punctuation token">,</span> bookinstance_list<span class="punctuation token">:</span> list_bookinstances <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>此方法使用模型的<code>find()</code>函數,返回所有 <code>BookInstance</code>對象。然後它將一個調用,以菊花鏈方式連接到 <code>populate()</code>,附加書本 <code>book</code>欄位字段,這將使用完整的 <code>Book</code>文檔,替換每個 <code>BookInstance</code>儲存的書本 ID。</p> + +<p>成功時,傳遞給查詢的回調,會呈現 <strong>bookinstance_list</strong> (.pug)模板,並將標題<code>title</code>和書籍實例列表 <code>bookinstance_list</code>作為變量傳遞。</p> + +<h2 class="highlight-spanned" id="View_視圖"><span class="highlight-span">View</span> 視圖</h2> + +<p>創建 <strong>/views/bookinstance_list.pug </strong>,並複制貼上底下的文字。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each val <span class="keyword token">in</span> bookinstance_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>val<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> <span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>imprint<span class="punctuation token">}</span> <span class="operator token">-</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Available'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>success #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Maintenance'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>danger #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>warning #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back<span class="punctuation token">}</span> <span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no book copies <span class="keyword token">in</span> <span class="keyword token">this</span> library<span class="punctuation token">.</span></code></pre> + +<p>這個視圖與其他視圖非常相似。它擴展了佈局,替換內容區塊,顯示從控制器傳入的標題 <code>title</code>,並遍歷 <code>bookinstance_list</code> 中的所有書籍副本。對於每個副本,我們都會顯示它的狀態(用顏色編碼),如果書本不可用,則顯示其預期返回日期。這裡引入了一個新功能 — 我們可以在標籤之後使用點符號表示法,來指定一個類別。因此,<code>span.text-success </code>將被編譯為 <<code>span class="text-success"</code>> (也可以用 Pug 編寫為 <code>span(class="text-success"</code>)。</p> + +<p> </p> + +<h2 class="highlight-spanned" id="它看起來像是">它看起來像是?</h2> + +<p>運行本應用,打開瀏覽器訪問 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然後選擇 All book-instances 連結。假如每個東西都設定正確了,你的網站看起來應該像是底下的截圖。</p> + +<p><img alt="BookInstance List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14474/LocalLibary_Express_BookInstance_List.png" style="border-style: solid; border-width: 1px; display: block; height: 322px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> + <li>繼續教學 5下一個部分: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">格式化日期 - 使用 moment</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..ecd3ee7f0d --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,60 @@ +--- +title: 使用 moment 做日期格式化 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +--- +<p>我們模型的日期預設呈現很難看: <em>Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)</em>。在本節中,我們將展示如何更新上一節中的 <em>書本實例 BookInstance 列表頁面</em>,以更友好的格式顯示<code>due_date</code>欄位字段:December 6th, 2016。</p> + +<p>我們將使用的方法,是在我們的<code>BookInstance</code>模型中,創建一個返回格式化日期的虛擬屬性。我們將使用 <a class="external external-icon" href="https://www.npmjs.com/package/moment" rel="noopener">moment</a> 來做實際的格式化,這是一個輕量級 JavaScript日期庫,用於解析,驗證,操作和格式化日期。</p> + +<div class="note"> +<p><strong>注意:</strong> 我們可以直接在 Pug 模板中,使用 moment 格式化字符串,或者可以在許多其它地方格式化字符串。使用虛擬屬性,可以使我們獲得格式化的日期,這與我們當前獲取 <code>due_date</code> 的方式完全相同。</p> +</div> + +<h2 class="highlight-spanned" id="安裝_moment"><span class="highlight-span">安裝 moment</span></h2> + +<p>在項目的根目錄,輸入下列命令</p> + +<p> </p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install moment</code></pre> + +<h2 class="highlight-spanned" id="創建虛擬屬性">創建虛擬屬性</h2> + +<ol> + <li>打開 <strong>./models/bookinstance.js</strong>.</li> + <li>在此頁面最上方,引入 <em>moment</em>. + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> moment <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'moment'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> +</ol> + +<p>在 url 屬性後面,加入虛擬屬性 <code>due_back_formatted</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">BookInstanceSchema +<span class="punctuation token">.</span><span class="function token">virtual</span><span class="punctuation token">(</span><span class="string token">'due_back_formatted'</span><span class="punctuation token">)</span> +<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">return</span> <span class="function token">moment</span><span class="punctuation token">(</span><span class="keyword token">this</span><span class="punctuation token">.</span>due_back<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">format</span><span class="punctuation token">(</span><span class="string token">'MMMM Do, YYYY'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>注意:</strong> 格式化方法可以使用幾乎任何模式顯示日期。 <a class="external external-icon" href="http://momentjs.com/docs/#/displaying/" rel="noopener">moment</a>文檔中,可以找到表示不同日期組件的語法。</p> +</div> + +<h2 class="highlight-spanned" id="更新視圖">更新視圖</h2> + +<p>打開 <strong>/views/bookinstance_list.pug</strong> ,然後用 <code>due_back_formatted</code> 取代 <code>due_back</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + <span class="comment token">//span (Due: #{val.due_back} )</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back_formatted<span class="punctuation token">}</span> <span class="punctuation token">)</span> </code></pre> + +<p>這就是本章節的全部了。如果你訪問側邊欄的 <em>All book-instances</em> ,你應該會看到所有的歸還日期都更吸引人了!</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學 5: 呈現圖書館資料</a></li> + <li>繼續教學 5 的下一個部分: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">Author list page and Genre list page challenge</a>.</li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..5271bd6722 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,137 @@ +--- +title: 使用 async 進行非同步流控制 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +<p>有些<em>本地圖書館</em>網頁的控制器代碼,會依賴多重非同步要求的結果,可能會需要以某種特定次序運行,或者以平行方式運行。為了管理流控制,並在我們所有需要用到的信息,都已經可以取用的時候,再繪製網頁,我們將使用許多人採用的 node <a class="external external-icon" href="https://www.npmjs.com/package/async" rel="noopener">async</a> 模組。</p> + +<div class="note"> +<p><strong>注意:</strong> 在 JavaScript 中有許多其他方法,可以管理異步行為和流控制,包括相對較新的 JavaScript 語言功能,如 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Techniques/Promises">Promises</a>。</p> +</div> + +<p>Async 有很多有用的方法(請查看<a href="http://caolan.github.io/async/docs.html">文檔</a>)。一些最重要的功能是:</p> + +<p> </p> + +<ul> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> 執行必須並行執行的任何操作。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> 用於當需要確保異步操作是序列執行的。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> 用於必須序列運行的操作,每個操作取決於前面操作的結果。</li> +</ul> + +<h2 class="highlight-spanned" id="為什麼需要這麼做">為什麼需要這麼做?</h2> + +<p>我們在 <em>Express</em> 中使用的大多數方法,都是異步的 - 您指定要執行的操作,傳遞回調。該方法立即返回,並在請求的操作完成時,調用回調。按照 <em>Express</em> 中的慣例,回調函數將<em>錯誤值</em>作為第一個參數傳遞(或成功時為 <code>null</code>),並將函數的結果(如果有的話)作為第二個參數傳遞。</p> + +<p>如果控制器只需要<em>執行<strong>一個</strong>異步操作</em>,來獲取呈現頁面所需的信息,那麼實現很簡單 - 我們只需在回調中呈現模板。下面的代碼片段,顯示了一個函數,該函數呈現模型 <code>SomeModel</code> 的計數(使用Mongoose <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code>方法):</p> + +<pre class="brush: js"><code>exports.some_model_count = function(req, res, next) { + +</code> SomeModel.count({ a_model_field: 'match_value' }, function (err, count) { + // ... do something if there is an err + + // On success, render the result by passing count into the render function (here, as the variable 'data'). + res.render('the_template', { data: count } ); + }); +<code>}</code> +</pre> + +<p>但是,如果您需要進行<strong>多個</strong>異步查詢,並且在完成所有操作之前,無法呈現頁面,該怎麼辦?一個單純的實現可以用 “菊花鏈” 連接請求,在先前請求的回調中,啟動後續請求,並在最終回調中呈現響應。這種方法的問題,是我們的請求必須串行運行,即使並行運行它們可能更有效。這也可能導致複雜的嵌套代碼,通常稱為<a href="http://callbackhell.com/">回調地獄</a>。</p> + +<p>一個更好的解決方案,是並行執行所有請求,然後在所有查詢完成後執行單個回調。這是 <em>Async</em> 模塊簡化的流操作!</p> + +<h2 class="highlight-spanned" id="Asynchronous_operations_in_parallel"><span class="highlight-span">Asynchronous operations in parallel</span></h2> + +<p>The method <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> is used to run multiple asynchronous operations in parallel.</p> + +<p>The first argument to <code>async.parallel()</code> is a collection of the asynchronous functions to run (an array, object or other iterable). Each function is passed a <code>callback(err, result)</code> which it must call on completion with an error <code>err</code> (which can be <code>null</code>) and an optional <code>results</code> value.</p> + +<p>The optional second argument to <code>async.parallel()</code> is a callback that will be run when all the functions in the first argument have completed. The callback is invoked with an error argument and a result collection that contains the results of the individual asynchronous operations. The result collection is of the same type as the first argument (i.e. if you pass an array of asynchronous functions, the final callback will be invoked with an array of results). If any of the parallel functions reports an error the callback is invoked early (with the error value).</p> + +<p>The example below shows how this works when we pass an object as the first argument. As you can see, the results are <em>returned</em> in an object with the same property names as the original functions that were passed in.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equal to: {one: 1, two: 2, ..., something_else: some_value}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>If you instead pass an array of functions as the first argument, the results will be an array (the array order results will match the original order that the functions were declared—not the order in which they completed).</p> + +<h2 class="highlight-spanned" id="Asynchronous_operations_in_series"><span class="highlight-span">Asynchronous operations in series</span></h2> + +<p>The method <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> is used to run multiple asynchronous operations in sequence, when subsequent functions do not depend on the output of earlier functions. It is essentially declared and behaves in the same way as <code>async.parallel()</code>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback after the last asynchronous function completes.</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>Note:</strong> The ECMAScript (JavaScript) language specification states that the order of enumeration of an object is undefined, so it is possible that the functions will not be called in the same order as you specify them on all platforms. If the order really is important, then you should pass an array instead of an object, as shown below.</p> +</div> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some stuff ...</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some more stuff ... </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">]</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// results is now equal to ['one', 'two'] </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Dependent_asynchronous_operations_in_series"><span class="highlight-span">Dependent asynchronous operations in series</span></h2> + +<p>The method <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> is used to run multiple asynchronous operations in sequence when each operation is dependent on the result of the previous operation.</p> + +<p>The callback invoked by each asynchronous function contains <code>null</code> for the first argument and results in subsequent arguments. Each function in the series takes the results arguments of the previous callback as the first parameters, and then a callback function. When all operations are complete, a final callback is invoked with the result of the last operation. The way this works is more clear when you consider the code fragment below (this example is from the <em>async</em> documentation):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">waterfall</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> arg2<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'one' and arg2 now equals 'two' </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'three'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'three'</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'done'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> result<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// result now equals 'done'</span> +<span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Installing_async"><span class="highlight-span">Installing async</span></h2> + +<p>Install the async module using the NPM package manager so that we can use it in our code. You do this in the usual way, by opening a prompt in the root of the <em>LocalLibrary</em> project and enter the following command:</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of Part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">Template primer</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..2c7f1e938b --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,123 @@ +--- +title: Genre detail page +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +<p>種類<em>細節</em>頁面,需要利用<code>_id</code> 字段值 (自動生成) ,以呈現特定種類實例的信息。此頁面應該呈現種類名稱,各個種類的所有書本列表(每本書都連結到書本的細節頁面)。</p> + +<p> </p> + +<h2 id="Controller_控制器">Controller 控制器</h2> + +<p>打開 <strong>/controllers/genreController.js</strong> ,並在檔案最上方引用 <em>async</em> 和 <em>Book</em> 模組。</p> + +<pre class="brush: js">var Book = require('../models/book'); +var async = require('async'); +</pre> + +<p>Find the exported <code>genre_detail</code><code>()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js">// Display detail page for a specific Genre. +exports.genre_detail = function(req, res, next) { + +<strong> async.parallel({ + genre: function(callback) { + Genre.findById(req.params.id) + .exec(callback); + }, + + genre_books: function(callback) { + Book.find({ 'genre': req.params.id }) + .exec(callback); + }, + + }, function(err, results) { + if (err) { return next(err); } + if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); + } + // Successful, so render + res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } ); + });</strong> + +}; +</pre> + +<p>The method uses <code>async.parallel()</code> to query the genre name and its associated books in parallel, with the callback rendering the page when (if) both requests complete successfully.</p> + +<p>The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (<strong>/genre/:id</strong>). The ID is accessed within the controller via the request parameters: <code style="font-style: normal; font-weight: normal;">req.params.id</code>. It is used in <code style="font-style: normal; font-weight: normal;">Genre.findById()</code> to get the current genre. It is also used to get all <code>Book</code> objects that have the genre ID in their <code>genre</code> field: <code>Book.find({ 'genre': req.params.id })</code>.</p> + +<div class="note"> +<p><strong>Note:</strong> If the genre does not exist in the database (i.e. it may have been deleted) then <code>findById()</code> will return successfully with no results. In this case we want to display a "not found" page, so we create an <code>Error</code> object and pass it to the <code>next</code> middleware function in the chain. </p> + +<pre class="brush: js"><strong>if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); +}</strong> +</pre> + +<p>The message will then propagate through to our error handling code (this was set up when we <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">generated the app skeleton</a> - for more information see <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">Handling Errors</a>).</p> +</div> + +<p>The rendered view is <strong>genre_detail</strong> and it is passed variables for the <code>title</code>, <code>genre</code> and the list of books in this genre (<code>genre_books</code>).</p> + +<h2 id="View">View</h2> + +<p>Create <strong>/views/genre_detail.pug</strong> and fill it with the text below:</p> + +<pre class="brush: js">extends layout + +block content + + <strong>h1 Genre: #{genre.name}</strong> + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in genre_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This genre has no books +</pre> + +<p>The view is very similar to all our other templates. The main difference is that we don't use the <code>title</code> passed in for the first heading (though it is used in the underlying <strong>layout.pug</strong> template to set the page title).</p> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application and open your browser to <a href="http://localhost:3000/">http://localhost:3000/</a>. Select the <em>All genres</em> link, then select one of the genres (e.g. "Fantasy"). If everything is set up correctly, your page should look something like the following screenshot.</p> + +<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p>You might get an error similar to this:</p> + +<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" +</pre> + +<p>This is a mongoose error coming from the <strong>req.params.id</strong>. To solve this problem, first you need to require mongoose on the <strong>genreController.js</strong> page like this:</p> + +<pre class="brush: js"> var mongoose = require('mongoose'); +</pre> + +<p>Then use <strong>mongoose.Types.ObjectId() </strong>to convert the id to a that can be used. For example:</p> + +<pre class="brush: js">exports.genre_detail = function(req, res, next) { + var id = mongoose.Types.ObjectId(req.params.id); + ... +</pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Book detail page</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..8adc4b11f9 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,133 @@ +--- +title: 主頁 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +<p>我們創建的第一個頁面,是網站的主頁面,可以從網站的根目錄 (<code>'/'</code>) ,或者 catalog 的根目錄 (<code>catalog/</code>) 訪問。這將呈現一些網站的靜態文字描述,以及動態計算數據庫中不同記錄類型的“計數”。</p> + +<p>我們已經為主頁創建了一個路由。為了完成頁面,我們需要更新控制器函數,以從數據庫中提取記錄的“計數”,並創建一個可用於呈現頁面的視圖(模板)。</p> + +<h2 id="路由">路由</h2> + +<p>在<a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes">前面的教程</a>,我們創建 index 頁面路由。此處要提醒的是,所有的路由函式,都定義在<strong> /routes/catalog.js</strong>:</p> + +<pre class="brush: js ">// GET catalog home page. +router.get('/', book_controller.index); //This actually maps to /catalog/ because we import the route with a /catalog prefix</pre> + +<p>Where the callback function parameter (<code>book_controller.index</code>) is defined in <strong>/controllers/bookController.js</strong>:</p> + +<pre class="brush: js">exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>It is this controller function that we extend to get information from our models and then render it using a template (view).</p> + +<h2 id="Controller">Controller</h2> + +<p>The index controller function needs to fetch information about how many <code>Book</code>, <code>BookInstance</code>, available <code>BookInstance</code>, <code>Author</code>, and <code>Genre</code> records we have in the database, render this data in a template to create an HTML page, and then return it in an HTTP response.</p> + +<div class="note"> +<p><strong>Note:</strong> We use the <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.countDocuments" rel="noopener">countDocuments()</a></code> method to get the number of instances of each model. This is called on a model with an optional set of conditions to match against in the first argument and a callback in the second argument (as discussed in <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Using a Database (with Mongoose)</a>, and you can also return a <code>Query</code> and then execute it with a callback later. The callback will be returned when the database returns the count, with an error value (or <code>null</code>) as the first parameter and the count of records (or null if there was an error) as the second parameter.</p> + +<pre class="brush: js ">SomeModel.countDocuments({ a_model_field: 'match_value' }, function (err, count) { + // ... do something if there is an err + // ... do something with the count if there was no error + });</pre> +</div> + +<p>Open <strong>/controllers/bookController.js</strong>. Near the top of the file you should see the exported <code>index()</code> function.</p> + +<pre class="brush: python ">var Book = require('../models/book') + +exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>Replace all the code above with the following code fragment. The first thing this does is import (<code>require()</code>) all the models (highlighted in bold). We need to do this because we'll be using them to get our counts of records. It then imports the <em>async</em> module.</p> + +<pre class="brush: js ">var Book = require('../models/book'); +var Author = require('../models/author'); +var Genre = require('../models/genre'); +var BookInstance = require('../models/bookinstance'); + +var async = require('async'); + +exports.index = function(req, res) { + + async.parallel({ + book_count: function(callback) { + Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection + }, + book_instance_count: function(callback) { + BookInstance.countDocuments({}, callback); + }, + book_instance_available_count: function(callback) { + BookInstance.countDocuments({status:'Available'}, callback); + }, + author_count: function(callback) { + Author.countDocuments({}, callback); + }, + genre_count: function(callback) { + Genre.countDocuments({}, callback); + }, + }, function(err, results) { + res.render('index', { title: 'Local Library Home', error: err, data: results }); + }); +};</pre> + +<p>The <code>async.parallel()</code> method is passed an object with functions for getting the counts for each of our models. These functions are all started at the same time. When all of them have completed the final callback is invoked with the counts in the results parameter (or an error).</p> + +<p>On success the callback function calls <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>, specifying a view (template) named '<strong>index</strong>' and an object containing the data that is to be inserted into it (this includes the results object that contains our model counts). The data is supplied as key-value pairs, and can be accessed in the template using the key.</p> + +<div class="note"> +<p><strong>Note:</strong> The callback function from <code>async.parallel()</code> above is a little unusual in that we render the page whether or not there was an error (normally you might use a separate execution path for handling the display of errors).</p> +</div> + +<h2 id="View">View</h2> + +<p>Open <strong>/views/index.pug</strong> and replace its content with the text below.</p> + +<pre class="brush: js ">extends layout + +block content + h1= title + p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network. + + h1 Dynamic content + + if error + p Error getting dynamic content. + else + p The library has the following record counts: + + ul + li #[strong Books:] !{data.book_count} + li #[strong Copies:] !{data.book_instance_count} + li #[strong Copies available:] !{data.book_instance_available_count} + li #[strong Authors:] !{data.author_count} + li #[strong Genres:] !{data.genre_count}</pre> + +<p>The view is straightforward. We extend the <strong>layout.pug</strong> base template, overriding the <code>block</code> named '<strong>content</strong>'. The first <code>h1</code> heading will be the escaped text for the <code>title</code> variable that was passed into the <code>render()</code> function—note the use of the '<code>h1=</code>' so that the following text is treated as a JavaScript expression. We then include a paragraph introducing the LocalLibrary.</p> + +<p>Under the <em>Dynamic content</em> heading we check whether the error variable passed in from the <code>render()</code> function has been defined. If so, we note the error. If not, we get and list the number of copies of each model from the <code>data</code> variable.</p> + +<div class="note"> +<p><strong>Note:</strong> We didn't escape the count values (i.e. we used the <code>!{}</code> syntax) because the count values are calculated. If the information was supplied by end-users then we'd escape the variable for display.</p> +</div> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>At this point we should have created everything needed to display the index page. Run the application and open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. If everything is set up correctly, your site should look something like the following screenshot.</p> + +<p><img alt="Home page - Express Local Library site" src="https://mdn.mozillademos.org/files/14458/LocalLibary_Express_Home.png" style="display: block; height: 440px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>Note:</strong> You won't be able to use the sidebar links yet because the urls, views, and templates for those pages haven't been defined. If you try you'll get errors like "NOT IMPLEMENTED: Book list" for example, depending on the link you click on. These string literals (which will be replaced with proper data) were specified in the different controllers that live inside your "controllers" file.</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">Book list page</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..2073a02bc8 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,87 @@ +--- +title: 'Express 教程 5: 呈現圖書館數據' +slug: Learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">我們現在準備好要新增網頁,以顯示本地圖書館網站的書本與其它資料。這些網頁將包括一個主頁 ,顯示我們每個模型的型態有多少筆紀錄,以及我們所有模型的清單與細節頁面。藉此,我們將得到從數據庫取得紀錄、以及使用樣版的實務經驗。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置條件:</th> + <td>完成先前教程主題 (包含 Express 教程 4: 路由與控制器)。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>了解如何使用非同步模組與 Pug 樣版語言,以及如何從我們的控制器函式中的 URL 得取資料。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>在我們先前的教程中,定義了可以用來跟資料庫互動的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose models</a> ,並創建了一些初始的圖書館紀錄。我們接著<a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes">創建本地圖書館網站需要的所有路由</a>,但僅使用"空殼控制器" 函式(這些是骨架控制器函式,當一個網頁被存取時,只回傳一個"未實作" 信息)。</p> + +<p>下一步,是為這些顯示圖書館信息的網頁,提供充分的實作(我們將在後面的文章,檢視網頁表單的實作,像是創建、更新、刪除信息)。這包含了更新控制器函式,以利用我們的模型取得紀錄,並定義模板,為使用者顯示這些信息。</p> + +<p>我們在一開始,提供概略的總覽/重點主題,解釋在控制器函式中,如何管理非同步操作,以及如何使用 Pug 撰寫模板。然後我們將為每一個主要的 "唯讀" 網頁提供實作步驟,並且在使用到任何特別的、或新的特性時,會附上簡短的解釋說明。</p> + +<p>本教程的最後,你對路由、非同步函式、視圖、模型如何實際運作,應該有了更好的理解。</p> + +<h2 id="本教程的章節">本教程的章節</h2> + +<p>本教程分為下列章節,說明為了顯示圖書館網站頁面,如何新增各種特性 。在進入下一個教程之前,你需要閱讀並逐一實作下列章節。</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">使用 async 控制非同步流</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模板入門</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">本地圖書館基礎模板</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主頁</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">書本清單頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">書本實例清單頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化 - 使用 moment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者清單頁面、分類清單頁面、與自我挑戰</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">分類詳情頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">書本詳情頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者詳情頁面</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">書本實例詳情頁面與自我挑戰</a></li> +</ol> + +<h2 id="總結">總結</h2> + +<p>我們現在已經為我們的網站,創建了所有 "唯讀" 的頁面: 一個主頁,可以顯示每一個模組的實例數量,書本的列表與詳細信息頁面,書本的實例、作者、分類。沿著目前的學習路徑,我們學到了許多基本知識,有控制器、在非同步作業時管理流控制、使用 Pug 創建視圖、使用模型查詢數據庫、如何從視圖傳送信息到模板、如何創建並擴展模板。而完成挑戰的人,還會學到如何用 moment 處理日期。</p> + +<p>在下一篇文章,我們將依據目前為止學到的知識,創建HTML 表單以及表單管理代碼,開始修改儲存在網站中的資料。</p> + +<h2 id="參閱">參閱</h2> + +<ul> + <li><a href="http://caolan.github.io/async/docs.html">Async module</a> (Async docs)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Using Template engines with Express</a> (Express docs)</li> + <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug docs)</li> + <li><a href="http://momentjs.com/docs/">Moment</a> (Moment docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="本教學連結">本教學連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..c67e82f07e --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,71 @@ +--- +title: 本地圖書館基礎模板 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +<p> </p> + +<p>現在我們了解如何使用 Pug 拓展模板,讓我們開始項目,創建一個基礎模板。這個模板會有一個側邊欄,連結到本教程中將要創建的各個頁面(例如,呈現並創建書本、種類、作者等等),以及一個主要內容區域,我們將在每個頁面中進行覆寫。</p> + +<p>開啟 <strong>/views/layout.pug </strong>,並以下列代碼,置換其內容。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang='en') + head + title= title + meta(charset='utf-8') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js') + script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') + link(rel='stylesheet', href='/stylesheets/style.css') + body + div(class='container-fluid') + div(class='row') + div(class='col-sm-2') + block sidebar + ul(class='sidebar-nav') + li + a(href='/catalog') Home + li + a(href='/catalog/books') All books + li + a(href='/catalog/authors') All authors + li + a(href='/catalog/genres') All genres + li + a(href='/catalog/bookinstances') All book-instances + li + hr + li + a(href='/catalog/author/create') Create new author + li + a(href='/catalog/genre/create') Create new genre + li + a(href='/catalog/book/create') Create new book + li + a(href='/catalog/bookinstance/create') Create new book instance (copy) + + div(class='col-sm-10') + block content</code></pre> + +<p>此模板使用(並包含)來自 <a class="external external-icon" href="http://getbootstrap.com/" rel="noopener">Bootstrap</a> 的 JavaScript 和 CSS ,以改進 HTML 頁面的佈局和呈現方式。使用 Bootstrap 或其它客戶端網頁框架,是一種快速的方式,可以創建吸引人的網頁,能夠良好地適應不同的瀏覽器尺寸,並且允許我們處理頁面的呈現,而不需要糾纒於任何不同尺寸的細節—此處我們只想專注於伺服端代碼!</p> + +<p>佈局的安排應該相當明白,假如你已經閱讀了之前的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#Template_primer">模板入門</a>。注意,使用 <code>block content</code> 當做定位符號,放到頁面內容將要放置的地方。</p> + +<p>基礎模板也參考了一個本地 css 檔 (<strong>style.css</strong>) ,此檔提供了一些額外的樣式。打開 <strong>/public/stylesheets/style.css</strong> ,並用底下的 CSS 代碼,取代它的內容:</p> + +<pre class="brush: css line-numbers language-css"><code class="language-css"><span class="selector token"><span class="class token">.sidebar-nav</span> </span><span class="punctuation token">{</span> + <span class="property token">margin-top</span><span class="punctuation token">:</span> <span class="number token">20</span>px<span class="punctuation token">;</span> + <span class="property token">padding</span><span class="punctuation token">:</span> <span class="number token">0</span><span class="punctuation token">;</span> + <span class="property token">list-style</span><span class="punctuation token">:</span> none<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<p>當我們開始運行網站時,我們應該看到側邊欄出現!在本教程的下個部分,我們將使用以上的佈局,來定義各個頁面。</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>繼續教學 5 下個章節: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">Home page</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..af976b7155 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: 模板入門 +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +<p>模板是一個文字檔,定義了一個輸出檔的<em>結構</em>或者排版,使用定位符號表示,當模板被繪製時,資料將插入到何處(在<em>Express</em>,模板被稱為<em>視圖</em>)。</p> + +<h2 id="Express_模板選擇">Express 模板選擇</h2> + +<p>Express 可以與許多不同的<a href="https://expressjs.com/zh-tw/guide/using-template-engines.html">模板渲染引擎</a>一起使用。在本教程中,我們使用 <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>(以前稱為 <em>Jade</em>)作為模板。這是最流行的 Node 模板語言,並且官方將自身描述為 “用於編寫 HTML,語法乾淨且空格敏感,受 <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml </a>影響很大”。</p> + +<p>不同的模板語言使用不同的方法,來定義佈局和標記數據的佔位符 — 一些使用 HTML 來定義佈局,而另一些則使用可以編譯為 HTML 的不同標記格式。 Pug 是第二種類型;它使用 HTML 的<em>表示形式</em>,其中任何行中的第一個單詞,通常表示HTML元素,後續行中的縮進,用於表示嵌套在這些元素中的任何內容。結果是一個頁面定義直接轉換為 HTML,但可以說更簡潔,更容易閱讀。</p> + +<div class="note"> +<p><strong>Note:</strong> The downside of using <em>Pug</em> is that it is sensitive to indentation and whitespace (if you add an extra space in the wrong place you may get an unhelpful error code). However once you have your templates in place, they are very easy to read and maintain.</p> +</div> + +<h2 class="highlight-spanned" id="Template_configuration"><span class="highlight-span">Template configuration</span></h2> + +<p>The <em>LocalLibrary</em> was configured to use <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a> when we <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">created the skeleton website</a>. You should see the pug module included as a dependency in the website's <strong>package.json</strong> file, and the following configuration settings in the <strong>app.js</strong> file. The settings tell us that we're using pug as the view engine, and that <em>Express</em> should search for templates in the <strong>/views</strong> subdirectory.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// View engine setup.</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'views'</span><span class="punctuation token">,</span> path<span class="punctuation token">.</span><span class="function token">join</span><span class="punctuation token">(</span>__dirname<span class="punctuation token">,</span> <span class="string token">'views'</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'view engine'</span><span class="punctuation token">,</span> <span class="string token">'pug'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>If you look in the views directory you will see the .pug files for the project's default views. These include the view for the home page (<strong>index.pug</strong>) and base template (<strong>layout.pug</strong>) that we will need to replace with our own content.</p> + +<pre><code>/express-locallibrary-tutorial //the project root + /views + error.pug + <strong>index.pug</strong> + layout.pug</code> +</pre> + +<h2 class="highlight-spanned" id="Template_syntax"><span class="highlight-span">Template syntax</span></h2> + +<p>The example template file below shows off many of Pug's most useful features.</p> + +<p>The first thing to notice is that the file maps the structure of a typical HTML file, with the first word in (almost) every line being an HTML element, and indentation being used to indicate nested elements. So for example, the <code>body</code> element is inside an <code>html</code> element, and paragraph elements (<code>p</code>) are within the <code>body</code> element, etc. Non-nested elements (e.g. individual paragraphs) are on separate lines.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang="en") + head + title= title + script(type='text/javascript'). + body + h1= title + + p This is a line with #[em some emphasis] and #[strong strong text] markup. + p This line has un-escaped data: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}. + | This line follows on. + p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title + + <span class="comment token"><!-- You can add HTML comments directly --></span> + // You can add single line JavaScript comments and they are generated to HTML comments + //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML + + p A line with a link + a(href='/catalog/authors') Some link text + | and some extra text. + + #container.col + if title + p A variable named "title" exists. + else + p A variable named "title" does not exist. + p. + Pug is a terse and simple template language with a + strong focus on performance and powerful features. + + h2 Generate a list + + ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>Element attributes are defined in parentheses after their associated element. Inside the parentheses, the attributes are defined in comma- or whitespace- separated lists of the pairs of attribute names and attribute values, for example:</p> + +<ul> + <li><code>script(type='text/javascript')</code>, <code>link(rel='stylesheet', href='/stylesheets/style.css')</code></li> + <li><code>meta(name='viewport' content='width=device-width initial-scale=1')</code></li> +</ul> + +<p>The values of all attributes are <em>escaped</em> (e.g. characters like "<code>></code>" are converted to their HTML code equivalents like "<code>&gt;"</code>) to prevent injection of JavaScript/cross-site scripting attacks.</p> + +<p>If a tag is followed by the equals sign, the following text is treated as a JavaScript <em>expression</em>. So for example, in the first line below, the content of the <code>h1</code> tag will be <em>variable</em> <code>title</code> (either defined in the file or passed into the template from Express). In the second line the paragraph content is a text string concatented with the <code>title</code> variable. In both cases the default behaviour is to <em>escape</em> the line.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">h1= title +p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title</code></pre> + +<p>If there is no equals symbol after the tag then the content is treated as plain text. Within the plain text you can insert escaped and unescaped data using the <code>#{}</code> and<code> !{}</code> syntax, as shown below. You can also add raw HTML within the plain text.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">p This is a line with #[em some emphasis] and #[strong strong text] markup. +p This line has an un-escaped string: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, an escaped string: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, and escaped variables: #{title}.</code></pre> + +<div class="note"> +<p><strong>Tip:</strong> You will almost always want to escape data from users (via the <strong><code>#{}</code></strong> syntax). Data that can be trusted (e.g. generated counts of records, etc.) may be displayed without escaping the values.</p> +</div> + +<p>You can use the pipe ('<strong>|</strong>') character at the beginning of a line to indicate "<a class="external external-icon" href="https://pugjs.org/language/plain-text.html" rel="noopener">plain text</a>". For example, the additional text shown below will be displayed on the same line as the preceding anchor, but will not be linked.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">a(href='http://someurl/') Link text +| Plain text</code></pre> + +<p>Pug allows you to perform conditional operations using <code>if</code>, <code>else</code> , <code>else if</code> and <code>unless</code>—for example:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">if title + p A variable named "title" exists +else + p A variable named "title" does not exist</code></pre> + +<p>You can also perform loop/iteration operations using <code>each-in</code> or <code>while</code> syntax. In the code fragment below we've looped through an array to display a list of variables (note the use of the 'li=' to evaluate the "val" as a variable below. The value you iterate across can also be passed into the template as a variable!</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>The syntax also supports comments (that can be rendered in the output—or not—as you choose), mixins to create reusable blocks of code, case statements, and many other features. For more detailed information see <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">The Pug docs</a>.</p> + +<h2 class="highlight-spanned" id="Extending_templates"><span class="highlight-span">Extending templates</span></h2> + +<p>Across a site, it is usual for all pages to have a common structure, including standard HTML markup for the head, footer, navigation, etc. Rather than forcing developers to duplicate this "boilerplate" in every page, <em>Pug</em> allows you to declare a base template and then extend it, replacing just the bits that are different for each specific page.</p> + +<p>For example, the base template <strong>layout.pug</strong> created in our <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">skeleton project</a> looks like this:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content</code></pre> + +<p>The <code>block</code> tag is used to mark up sections of content that may be replaced in a derived template (if the block is not redefined then its implementation in the base class is used).</p> + +<p>The default <strong>index.pug</strong> (created for our skeleton project) shows how we override the base template. The <code>extends</code> tag identifies the base template to use, and then we use <code>block <em>section_name</em></code> to indicate the new content of the section that we will override.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">extends layout + +block content + h1= title + p Welcome to #{title}</code></pre> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">The LocalLibrary base template</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/create_author_form/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/create_author_form/index.html new file mode 100644 index 0000000000..9d4563376e --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/create_author_form/index.html @@ -0,0 +1,155 @@ +--- +title: Create Author form +slug: Learn/Server-side/Express_Nodejs/forms/Create_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_author_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_author_form" rel="nofollow, noindex"><span>Edi</span></a>本章節演示,如何為創建作者對象 <code>Author</code>定義一個頁面。</p> + +<h2 class="highlight-spanned" id="導入驗證和清理方法">導入驗證和清理方法</h2> + +<p>為了在種類表單使用 <em>express</em> 驗證器,我們必須用 <em>require</em> 導入我們想用的函式。</p> + +<p>打開 <strong>/controllers/authorController.js</strong>,並在檔案最上方,加入底下幾行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>Find the exported <code>author_create_get()</code> controller method and replace it with the following code. This simply renders the <strong>author_form.pug</strong> view, passing a <code>title</code> variable.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display Author create form on GET.</span> +exports<span class="punctuation token">.</span>author_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Author'</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Find the exported <code>author_create_post()</code> controller method, and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle Author create on POST.</span> +exports<span class="punctuation token">.</span>author_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'first_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'First name must be specified.'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">isAlphanumeric</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'First name has non-alphanumeric characters.'</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'family_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'Family name must be specified.'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">isAlphanumeric</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'Family name has non-alphanumeric characters.'</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date of birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'date_of_death'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date of death'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'first_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'family_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_death'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values/errors messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Author'</span><span class="punctuation token">,</span> author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + + <span class="comment token">// Create an Author object with escaped and trimmed data.</span> + <span class="keyword token">var</span> author <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Author</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> + first_name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>first_name<span class="punctuation token">,</span> + family_name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>family_name<span class="punctuation token">,</span> + date_of_birth<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">,</span> + date_of_death<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>date_of_death + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + author<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new author record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>The structure and behaviour of this code is almost exactly the same as for creating a <code>Genre</code> object. First we validate and sanitize the data. If the data is invalid then we re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid then we save the new author record and redirect the user to the author detail page.</p> + +<div class="note"> +<p><strong>Note:</strong> Unlike with the <code>Genre</code> post handler, we don't check whether the <code>Author</code> object already exists before saving it. Arguably we should, though as it is now we can have multiple authors with the same name.</p> +</div> + +<p>The validation code demonstrates several new features:</p> + +<ul> + <li>We can daisy chain validators, using <code>withMessage()</code> to specify the error message to display if the previous validation method fails. This makes it very easy to provide specific error messages without lots of code duplication. + + <pre class="brush: js">// Validate fields. +body('first_name').isLength({ min: 1 }).trim().withMessage('First name must be specified.') + .isAlphanumeric().withMessage('First name has non-alphanumeric characters.'),<code> +</code></pre> + </li> + <li>We can use the <code>optional()</code> function to run a subsequent validation only if a field has been entered (this allows us to validate optional fields). For example, below we check that the optional date of birth is an ISO8601-compliant date (the <code>checkFalsy</code> flag means that we'll accept either an empty string or <code>null</code> as an empty value). + <pre class="line-numbers language-html"><code class="language-html">body('date_of_birth', 'Invalid date of birth').optional({ checkFalsy: true }).isISO8601(),</code></pre> + </li> +</ul> + +<ul> + <li>Parameters are recieved from the request as strings. We can use <code>toDate()</code> (or <code>toBoolean()</code>, etc.) to cast these to the proper JavaScript types. + + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span></code></pre> + </li> +</ul> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Create <strong>/views/author_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1=title + + form(method='POST' action='') + div.form-group + label(for='first_name') First Name: + input#first_name.form-control(type='text' placeholder='First name (Christian) last' name='first_name' required='true' value=(undefined===author ? '' : author.first_name) ) + label(for='family_name') Family Name: + input#family_name.form-control(type='text' placeholder='Family name (surname)' name='family_name' required='true' value=(undefined===author ? '' : author.family_name)) + div.form-group + label(for='date_of_birth') Date of birth: + input#date_of_birth.form-control(type='date' name='date_of_birth' value=(undefined===author ? '' : author.date_of_birth) ) + button.btn.btn-primary(type='submit') Submit + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>The structure and behaviour for this view is exactly the same as for the <strong>genre_form.pug</strong> template, so we won't describe it again.</p> + +<div class="note"> +<p><strong>Note:</strong> Some browsers don’t support the input <code>type=“date”</code>, so you won’t get the datepicker widget or the default <em><code>dd/mm/yyyy</code></em> placeholder, but will instead get an empty plain text field. One workaround is to explicitly add the attribute <code>placeholder='dd/mm/yyyy'</code> so that on less capable browsers you will still get information about the desired text format.</p> +</div> + +<h3 id="Challenge_Adding_the_date_of_death">Challenge: Adding the date of death</h3> + +<p>The template above is missing a field for entering the <code>date_of_death</code>. Create the field following the same pattern as the date of birth form group!</p> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>Create new author </em>link. If everything is set up correctly, your site should look something like the following screenshot. After you enter a value, it should be saved and you'll be taken to the author detail page.</p> + +<p><img alt="Author Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14482/LocalLibary_Express_Author_Create_Empty.png" style="display: block; height: 426px; margin: 0px auto; width: 800px;"></p> + +<div class="note"> +<p><strong>Note:</strong> If you experiment with various input formats for the dates, you may find that the format <code>yyyy-mm-dd</code> misbehaves. This is because JavaScript treats date strings as including the time of 0 hours, but additionally treats date strings in that format (the ISO 8601 standard) as including the time 0 hours UTC, rather than the local time. If your time zone is west of UTC, the date display, being local, will be one day before the date you entered. This is one of several complexities (such as multi-word family names and multi-author books) that we are not addressing here.</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">Create Book form</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/create_book_form/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/create_book_form/index.html new file mode 100644 index 0000000000..c15b2ca385 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/create_book_form/index.html @@ -0,0 +1,214 @@ +--- +title: Create Book form +slug: Learn/Server-side/Express_Nodejs/forms/Create_book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_book_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_book_form" rel="nofollow, noindex">Edit</a>此章節展示如何定義頁面/表單以創建<code>Book</code>對象。這比相同的作者<code>Author</code>或種類<code>Genre</code>頁面稍微複雜一點,因為我們需要在我們的書本表單中,獲取並顯示可用的作者和種類記錄。</p> + +<p> </p> + +<h2 id="導入驗證和清理方法">導入驗證和清理方法</h2> + +<p>打開 <strong>/controllers/bookController.js</strong>,並在文件頂部添加以下幾行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter');</code></pre> + +<h2 id="Controller—get_route">Controller—get route</h2> + +<p>Find the exported <code>book_create_get()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Display book create form on GET. +exports.book_create_get = function(req, res, next) { + + // Get all authors and genres, which we can use for adding to our book. + async.parallel({ + authors: function(callback) { + Author.find(callback); + }, + genres: function(callback) { + Genre.find(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + res.render('book_form', { title: 'Create Book', authors: results.authors, genres: results.genres }); + }); + +};</code></pre> + +<p>This uses the async module (described in <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>) to get all <code>Author</code> and <code>Genre</code> objects. These are then passed to the view <code><strong>book_form.pug</strong></code> as variables named <code>authors</code> and <code>genres</code> (along with the page <code>title</code>).</p> + +<h2 id="Controller—post_route">Controller—post route</h2> + +<p>Find the exported <code>book_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Handle book create on POST. +exports.book_create_post = [ + // Convert the genre to an array. + (req, res, next) => { + if(!(req.body.genre instanceof Array)){ + if(typeof req.body.genre==='undefined') + req.body.genre=[]; + else + req.body.genre=new Array(req.body.genre); + } + next(); + }, + + // Validate fields. + body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(), + body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(), + body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(), + body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(), + + // Sanitize fields (using wildcard). + sanitizeBody('*').trim().escape(), + + // Process request after validation and sanitization. + (req, res, next) => { + + // Extract the validation errors from a request. + const errors = validationResult(req); + + // Create a Book object with escaped and trimmed data. + var book = new Book( + { title: req.body.title, + author: req.body.author, + summary: req.body.summary, + isbn: req.body.isbn, + genre: req.body.genre + }); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/error messages. + + // Get all authors and genres for form. + async.parallel({ + authors: function(callback) { + Author.find(callback); + }, + genres: function(callback) { + Genre.find(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + + // Mark our selected genres as checked. + for (let i = 0; i < results.genres.length; i++) { + if (book.genre.indexOf(results.genres[i]._id) > -1) { + results.genres[i].checked='true'; + } + } + res.render('book_form', { title: 'Create Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() }); + }); + return; + } + else { + // Data from form is valid. Save book. + book.save(function (err) { + if (err) { return next(err); } + //successful - redirect to new book record. + res.redirect(book.url); + }); + } + } +];</code></pre> + +<p>The structure and behaviour of this code is almost exactly the same as for creating a <code>Genre</code> or <code>Author</code> object. First we validate and sanitize the data. If the data is invalid then we re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid, we then save the new <code>Book</code> record and redirect the user to the book detail page.</p> + +<p>The first main difference with respect to the other form handling code is that we use a wildcard to trim and escape all fields in one go (rather than sanitising them individually):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">sanitizeBody('*').trim().escape(),</code></pre> + +<p>The next main difference with respect to the other form handling code is how we sanitize the genre information. The form returns an array of <code>Genre</code> items (while for other fields it returns a string). In order to validate the information we first convert the request to an array (required for the next step).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Convert the genre to an array. +(req, res, next) => { + if(!(req.body.genre instanceof Array)){ + if(typeof req.body.genre==='undefined') + req.body.genre=[]; + else + req.body.genre=new Array(req.body.genre); + } + next(); +},</code></pre> + +<p>We then use a wildcard (<code>*</code>) in the sanitiser to individually validate each of the genre array entries. The code below shows how - this translates to "sanitise every item below key <code>genre</code>".</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">sanitizeBody('genre.*').trim().escape(),</code></pre> + +<p>The final difference with respect to the other form handling code is that we need to pass in all existing genres and authors to the form. In order to mark the genres that were checked by the user we iterate through all the genres and add the <code>checked='true'</code> parameter to those that were in our post data (as reproduced in the code fragment below).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Mark our selected genres as checked. +for (let i = 0; i < results.genres.length; i++) { + if (book.genre.indexOf(results.genres[i]._id) > -1) { + // Current genre is selected. Set "checked" flag. + results.genres[i].checked='true'; + } +}</code></pre> + +<h2 id="View">View</h2> + +<p>Create <strong>/views/book_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1= title + + form(method='POST' action='') + div.form-group + label(for='title') Title: + input#title.form-control(type='text', placeholder='Name of book' name='title' required='true' value=(undefined===book ? '' : book.title) ) + div.form-group + label(for='author') Author: + select#author.form-control(type='select', placeholder='Select author' name='author' required='true' ) + for author in authors + if book + option(value=author._id selected=(author._id.toString()==book.author ? 'selected' : false) ) #{author.name} + else + option(value=author._id) #{author.name} + div.form-group + label(for='summary') Summary: + input#summary.form-control(type='textarea', placeholder='Summary' name='summary' value=(undefined===book ? '' : book.summary) required='true') + div.form-group + label(for='isbn') ISBN: + input#isbn.form-control(type='text', placeholder='ISBN13' name='isbn' value=(undefined===book ? '' : book.isbn) required='true') + div.form-group + label Genre: + div + for genre in genres + div(style='display: inline; padding-right:10px;') + input.checkbox-input(type='checkbox', name='genre', id=genre._id, value=genre._id, checked=genre.checked ) + label(for=genre._id) #{genre.name} + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>The view structure and behaviour is almost the same as for the <strong>genre_form.pug</strong> template.</p> + +<p>The main differences are in how we implement the selection-type fields: <code>Author</code> and <code>Genre</code>.</p> + +<ul> + <li>The set of genres are displayed as checkboxes, using the <code>checked</code> value we set in the controller to determine whether or not the box should be selected.</li> + <li>The set of authors are displayed as a single-selection drop-down list. In this case we determine what author to display by comparing the id of the current author option with the value previously entered by the user (passed in as the <code>book</code> variable). This is highlighted above! + <div class="note"> + <p><strong>Note:</strong> If there is an error in the submitted form, then, when the form is to be re-rendered, the new book's author is identified only with a string (the value of the selected option in the list of authors). By contrast, the existing books' authors have <code>_id</code> properties that are not strings. So to compare the new with the existing we must cast each existing book's author's <code>_id</code> to a string, as shown above.</p> + </div> + </li> +</ul> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>Create new book </em>link. If everything is set up correctly, your site should look something like the following screenshot. After you submit a valid book, it should be saved and you'll be taken to the book detail page.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14484/LocalLibary_Express_Book_Create_Empty.png" style="display: block; height: 498px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<p>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</p> + +<p>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">Create BookInstance form</a>.</p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html new file mode 100644 index 0000000000..14288f4678 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html @@ -0,0 +1,150 @@ +--- +title: Create BookInstance form +slug: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_BookInstance_form" rel="nofollow, noindex"><span>Edi</span></a>本章節演示如何定義一個頁面/表單,以創建 <code>BookInstance</code> 物件。這很像我們用來創建書本 <code>Book</code> 物件的表單。</p> + +<h2 class="highlight-spanned" id="導入驗證和清理方法">導入驗證和清理方法</h2> + +<p>打開 <strong>/controllers/bookinstanceController.js</strong>,並在檔案最上方,加入以下幾行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>At the top of the file, require the <em>Book</em> module (needed because each <code>BookInstance</code> is associated with a particular <code>Book</code>).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Find the exported <code>bookinstance_create_get()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display BookInstance create form on GET.</span> +exports<span class="punctuation token">.</span>bookinstance_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span>title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span>books<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>The controller gets a list of all books (<code>book_list</code>) and passes it to the view <code><strong>bookinstance_form.pug</strong></code> (along with the <code>title</code>)</p> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Find the exported <code>bookinstance_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle BookInstance create on POST.</span> +exports<span class="punctuation token">.</span>bookinstance_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">,</span> <span class="string token">'Book must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">,</span> <span class="string token">'Imprint must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'status'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a BookInstance object with escaped and trimmed data.</span> + <span class="keyword token">var</span> bookinstance <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">BookInstance</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> book<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>book<span class="punctuation token">,</span> + imprint<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>imprint<span class="punctuation token">,</span> + status<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>status<span class="punctuation token">,</span> + due_back<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>due_back + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values and error messages.</span> + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list <span class="punctuation token">:</span> books<span class="punctuation token">,</span> selected_book <span class="punctuation token">:</span> bookinstance<span class="punctuation token">.</span>book<span class="punctuation token">.</span>_id <span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> bookinstance<span class="punctuation token">:</span>bookinstance <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + bookinstance<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>bookinstance<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>The structure and behaviour of this code is the same as for creating our other objects. First we validate and sanitize the data. If the data is invalid, we then re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid, we save the new <code>BookInstance</code> record and redirect the user to the detail page.</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Create <strong>/views/bookinstance_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1=title + + form(method='POST' action='') + div.form-group + label(for='book') Book: + select#book.form-control(type='select' placeholder='Select book' name='book' required='true') + for book in book_list + if bookinstance + option(value=book._id selected=(bookinstance.book.toString()==book._id.toString() ? 'selected' : false)) #{book.title} + else + option(value=book._id) #{book.title} + + div.form-group + label(for='imprint') Imprint: + input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint)) + div.form-group + label(for='due_back') Date when book available: + input#due_back.form-control(type='date' name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back)) + + div.form-group + label(for='status') Status: + select#status.form-control(type='select' placeholder='Select status' name='status' required='true') + option(value='Maintenance') Maintenance + option(value='Available') Available + option(value='Loaned') Loaned + option(value='Reserved') Reserved + + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>The view structure and behaviour is almost the same as for the <strong>book_form.pug</strong> template, so we won't go over it again.</p> + +<div class="note"> +<p><strong>Note:</strong> The above template hard-codes the <em>Status</em> values (Maintenance, Available, etc.) and does not "remember" the user's entered values. Should you so wish, consider reimplementing the list, passing in option data from the controller and setting the selected value when the form is re-displayed.</p> +</div> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application and open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Then select the <em>Create new book instance (copy) </em>link. If everything is set up correctly, your site should look something like the following screenshot. After you submit a valid <code>BookInstance</code>, it should be saved and you'll be taken to the detail page.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14490/LocalLibary_Express_BookInstance_Create_Empty.png" style="display: block; height: 554px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">Delete Author form</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/create_genre_form/index.html new file mode 100644 index 0000000000..3e648e48ea --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/create_genre_form/index.html @@ -0,0 +1,294 @@ +--- +title: 創建種類表單 +slug: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +--- +<p>本章節演示如何定義我們的頁面,創建<code>Genre</code> 物件(這是一個很好的起點,因為類型<code>Genre</code>只有一個欄位,就是它的名稱 <code>name</code>,沒有依賴項)。像任何其他頁面一樣,我們需要設置路由,控制器和視圖。</p> + +<p> </p> + +<h2 class="highlight-spanned" id="引入驗證與無害化方法">引入驗證與無害化方法</h2> + +<p>在我們的控制器中使用 <em>express-validator</em> 驗證器,我們必須導入我們想要從 '<strong>express-validator/check</strong>' 和 '<strong>express-validator/filter</strong>' 模塊中使用的函數。</p> + +<p>打開 <strong>/controllers/genreController.js</strong>,並在文件頂部添加以下幾行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>Find the exported <code>genre_create_get()</code> controller method and replace it with the following code. This simply renders the <strong>genre_form.pug</strong> view, passing a title variable.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display Genre create form on GET.</span> +exports<span class="punctuation token">.</span>genre_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Find the exported <code>genre_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle Genre create on POST.</span> +exports<span class="punctuation token">.</span>genre_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate that the name field is not empty.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">,</span> <span class="string token">'Genre name required'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize (trim and escape) the name field.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a genre object with escaped and trimmed data.</span> + <span class="keyword token">var</span> genre <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Genre</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span> + <span class="punctuation token">)</span><span class="punctuation token">;</span> + + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render the form again with sanitized values/error messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + <span class="comment token">// Check if Genre with same name already exists.</span> + Genre<span class="punctuation token">.</span><span class="function token">findOne</span><span class="punctuation token">(</span><span class="punctuation token">{</span> <span class="string token">'name'</span><span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span>found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// Genre exists, redirect to its detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>found_genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + + genre<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Genre saved. Redirect to genre detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="punctuation token">}</span> + + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>The first thing to note is that instead of being a single middleware function (with arguments <code>(req, res, next)</code>) the controller specifies an <em>array</em> of middleware functions. The array is passed to the router function and each method is called in order.</p> + +<ul> +</ul> + +<div class="note"> +<p><strong>Note:</strong> This approach is needed, because the sanitisers/validators are middleware functions.</p> +</div> + +<p>The first method in the array defines a validator (<code>body</code>) to check that the <em>name</em> field is not empty (calling <code>trim()</code> to remove any trailing/leading whitespace before performing the validation). The second method in the array (<code>sanitizeBody()</code>) creates a sanitizer to <code>trim()</code> the <em>name</em> field and <code>escape()</code> any dangerous HTML characters.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Validate that the name field is not empty.</span> +<span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">,</span> <span class="string token">'Genre name required'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + +<span class="comment token">// Sanitize (trim and escape) the name field.</span> +<span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span></code></pre> + +<ul> +</ul> + +<div class="note"> +<p><strong>Note:</strong> Sanitizers run during validation do not modify the request. That is why we have to call <code>trim()</code> in both steps above!</p> +</div> + +<p>After specifying the validators and sanitizers we create a middleware function to extract any validation errors. We use <code>isEmpty()</code> to check whether there are any errors in the validation result. If there are then we render the form again, passing in our sanitised genre object and the array of error messages (<code>errors.array()</code>).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Process request after validation and sanitization.</span> +<span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a genre object with escaped and trimmed data.</span> + <span class="keyword token">var</span> genre <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Genre</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span> + <span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render the form again with sanitized values/error messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="operator token"><</span>save the result<span class="operator token">></span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + <span class="punctuation token">}</span> +<span class="punctuation token">}</span></code></pre> + +<p>If the genre name data is valid then we check if a <code>Genre</code> with the same name already exists (as we don't want to create duplicates). If it does we redirect to the existing genre's detail page. If not, we save the new <code>Genre</code> and redirect to its detail page.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Check if Genre with same name already exists.</span> +Genre<span class="punctuation token">.</span><span class="function token">findOne</span><span class="punctuation token">(</span><span class="punctuation token">{</span> <span class="string token">'name'</span><span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// Genre exists, redirect to its detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>found_genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + genre<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Genre saved. Redirect to genre detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>This same pattern is used in all our post controllers: we run validators, then sanitisers, then check for errors and either re-render the form with error information or save the data. </p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>The same view is rendered in both the <code>GET</code> and <code>POST</code> controllers/routes when we create a new <code>Genre</code> (and later on it is also used when we <em>update</em> a <code>Genre</code>). In the <code>GET</code> case the form is empty and we just pass a title variable. In the <code>POST</code> case the user has previously entered invalid data—in the <code>genre</code> variable we pass back a sanitized version of the entered data and in the <code>errors</code> variable we pass back an array of error messages.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Create <strong>/views/genre_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1 #{title} + + form(method='POST' action='') + div.form-group + label(for='name') Genre: + input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name)) + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>Much of this template will be familiar from our previous tutorials. First we extend the <strong>layout.pug</strong> base template and override the <code>block</code> named '<strong>content</strong>'. We then have a heading with the <code>title</code> we passed in from the controller (via the <code>render()</code> method).</p> + +<p>Next we have the pug code for our HTML form that uses the <code>POST</code> <code>method</code> to send the data to the server, and because the <code>action</code> is an empty string, will send the data to the same URL as the page.</p> + +<p>The form defines a single required field of type "text" called "name". The default <em>value</em> of the field depends on whether the <code>genre</code> variable is defined. If called from the <code>GET</code> route it will be empty as this is a new form. If called from a <code>POST</code> route it will contain the (invalid) value originally entered by the user.</p> + +<p>The last part of the page is the error code. This simply prints a list of errors, if the error variable has been defined (in other words, this section will not appear when the template is rendered on the <code>GET</code> route).</p> + +<div class="note"> +<p><strong>Note:</strong> This is just one way to render the errors. You can also get the names of the affected fields from the error variable, and use these to control where the error messages are rendered, whether to apply custom CSS, etc.</p> +</div> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>Create new genre </em>link. If everything is set up correctly, your site should look something like the following screenshot. After you enter a value, it should be saved and you'll be taken to the genre detail page.</p> + +<p><img alt="Genre Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14476/LocalLibary_Express_Genre_Create_Empty.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 800px;"></p> + +<p>The only error we validate against server-side is that the genre field must not be empty. The screenshot below shows what the error list would look like if you didn't supply a genre (highlighted in red).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14480/LocalLibary_Express_Genre_Create_Error.png" style="border-style: solid; border-width: 1px; display: block; height: 249px; margin: 0px auto; width: 400px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Our validation uses <code>trim()</code> to ensure that whitespace is not accepted as a genre name. We can also validate that the field is not empty on the client side by adding the value <code>required='true'</code> to the field definition in the form:</p> + +<pre class="brush: js"><code>input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), <strong>required='true'</strong> )</code></pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms.</a></li> + <li>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">Create Author form</a>.</li> +</ul> + +<div id="SL_balloon_obj" style="display: block;"> +<div class="SL_ImTranslatorLogo" id="SL_button" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; display: none; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important; opacity: 1;"> </div> + +<div id="SL_shadow_translation_result2" style="display: none;"> </div> + +<div id="SL_shadow_translator" style="display: none;"> +<div id="SL_planshet"> +<div id="SL_arrow_up" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_Bproviders"> +<div class="SL_BL_LABLE_ON" id="SL_P0" title="Google">G</div> + +<div class="SL_BL_LABLE_ON" id="SL_P1" title="Microsoft">M</div> + +<div class="SL_BL_LABLE_ON" id="SL_P2" title="Translator">T</div> +</div> + +<div id="SL_alert_bbl" style="display: none;"> +<div id="SLHKclose" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_alert_cont"> </div> +</div> + +<div id="SL_TB"> +<table id="SL_tables"> + <tbody> + <tr> + <td class="SL_td"><input></td> + <td class="SL_td"><select><option value="auto">Detect language</option><option value="af">Afrikaans</option><option value="sq">Albanian</option><option value="ar">Arabic</option><option value="hy">Armenian</option><option value="az">Azerbaijani</option><option value="eu">Basque</option><option value="be">Belarusian</option><option value="bn">Bengali</option><option value="bs">Bosnian</option><option value="bg">Bulgarian</option><option value="ca">Catalan</option><option value="ceb">Cebuano</option><option value="ny">Chichewa</option><option value="zh-CN">Chinese (Simplified)</option><option value="zh-TW">Chinese (Traditional)</option><option value="hr">Croatian</option><option value="cs">Czech</option><option value="da">Danish</option><option value="nl">Dutch</option><option value="en">English</option><option value="eo">Esperanto</option><option value="et">Estonian</option><option value="tl">Filipino</option><option value="fi">Finnish</option><option value="fr">French</option><option value="gl">Galician</option><option value="ka">Georgian</option><option value="de">German</option><option value="el">Greek</option><option value="gu">Gujarati</option><option value="ht">Haitian Creole</option><option value="ha">Hausa</option><option value="iw">Hebrew</option><option value="hi">Hindi</option><option value="hmn">Hmong</option><option value="hu">Hungarian</option><option value="is">Icelandic</option><option value="ig">Igbo</option><option value="id">Indonesian</option><option value="ga">Irish</option><option value="it">Italian</option><option value="ja">Japanese</option><option value="jw">Javanese</option><option value="kn">Kannada</option><option value="kk">Kazakh</option><option value="km">Khmer</option><option value="ko">Korean</option><option value="lo">Lao</option><option value="la">Latin</option><option value="lv">Latvian</option><option value="lt">Lithuanian</option><option value="mk">Macedonian</option><option value="mg">Malagasy</option><option value="ms">Malay</option><option value="ml">Malayalam</option><option value="mt">Maltese</option><option value="mi">Maori</option><option value="mr">Marathi</option><option value="mn">Mongolian</option><option value="my">Myanmar (Burmese)</option><option value="ne">Nepali</option><option value="no">Norwegian</option><option value="fa">Persian</option><option value="pl">Polish</option><option value="pt">Portuguese</option><option value="pa">Punjabi</option><option value="ro">Romanian</option><option value="ru">Russian</option><option value="sr">Serbian</option><option value="st">Sesotho</option><option value="si">Sinhala</option><option value="sk">Slovak</option><option value="sl">Slovenian</option><option value="so">Somali</option><option value="es">Spanish</option><option value="su">Sundanese</option><option value="sw">Swahili</option><option value="sv">Swedish</option><option value="tg">Tajik</option><option value="ta">Tamil</option><option value="te">Telugu</option><option value="th">Thai</option><option value="tr">Turkish</option><option value="uk">Ukrainian</option><option value="ur">Urdu</option><option value="uz">Uzbek</option><option value="vi">Vietnamese</option><option value="cy">Welsh</option><option value="yi">Yiddish</option><option value="yo">Yoruba</option><option value="zu">Zulu</option></select></td> + <td class="SL_td"> + <div id="SL_switch_b" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Switch languages"> </div> + </td> + <td class="SL_td"><select><option value="af">Afrikaans</option><option value="sq">Albanian</option><option value="ar">Arabic</option><option value="hy">Armenian</option><option value="az">Azerbaijani</option><option value="eu">Basque</option><option value="be">Belarusian</option><option value="bn">Bengali</option><option value="bs">Bosnian</option><option value="bg">Bulgarian</option><option value="ca">Catalan</option><option value="ceb">Cebuano</option><option value="ny">Chichewa</option><option value="zh-CN">Chinese (Simplified)</option><option value="zh-TW">Chinese (Traditional)</option><option value="hr">Croatian</option><option value="cs">Czech</option><option value="da">Danish</option><option value="nl">Dutch</option><option selected value="en">English</option><option value="eo">Esperanto</option><option value="et">Estonian</option><option value="tl">Filipino</option><option value="fi">Finnish</option><option value="fr">French</option><option value="gl">Galician</option><option value="ka">Georgian</option><option value="de">German</option><option value="el">Greek</option><option value="gu">Gujarati</option><option value="ht">Haitian Creole</option><option value="ha">Hausa</option><option value="iw">Hebrew</option><option value="hi">Hindi</option><option value="hmn">Hmong</option><option value="hu">Hungarian</option><option value="is">Icelandic</option><option value="ig">Igbo</option><option value="id">Indonesian</option><option value="ga">Irish</option><option value="it">Italian</option><option value="ja">Japanese</option><option value="jw">Javanese</option><option value="kn">Kannada</option><option value="kk">Kazakh</option><option value="km">Khmer</option><option value="ko">Korean</option><option value="lo">Lao</option><option value="la">Latin</option><option value="lv">Latvian</option><option value="lt">Lithuanian</option><option value="mk">Macedonian</option><option value="mg">Malagasy</option><option value="ms">Malay</option><option value="ml">Malayalam</option><option value="mt">Maltese</option><option value="mi">Maori</option><option value="mr">Marathi</option><option value="mn">Mongolian</option><option value="my">Myanmar (Burmese)</option><option value="ne">Nepali</option><option value="no">Norwegian</option><option value="fa">Persian</option><option value="pl">Polish</option><option value="pt">Portuguese</option><option value="pa">Punjabi</option><option value="ro">Romanian</option><option value="ru">Russian</option><option value="sr">Serbian</option><option value="st">Sesotho</option><option value="si">Sinhala</option><option value="sk">Slovak</option><option value="sl">Slovenian</option><option value="so">Somali</option><option value="es">Spanish</option><option value="su">Sundanese</option><option value="sw">Swahili</option><option value="sv">Swedish</option><option value="tg">Tajik</option><option value="ta">Tamil</option><option value="te">Telugu</option><option value="th">Thai</option><option value="tr">Turkish</option><option value="uk">Ukrainian</option><option value="ur">Urdu</option><option value="uz">Uzbek</option><option value="vi">Vietnamese</option><option value="cy">Welsh</option><option value="yi">Yiddish</option><option value="yo">Yoruba</option><option value="zu">Zulu</option></select></td> + <td class="SL_td"> + <div id="SL_TTS_voice" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Listen to the translation"> </div> + </td> + <td class="SL_td"> + <div class="SL_copy" id="SL_copy" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Copy translation"> </div> + </td> + <td class="SL_td"> + <div id="SL_bbl_font_patch"> </div> + + <div class="SL_bbl_font" id="SL_bbl_font" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Font size"> </div> + </td> + <td class="SL_td"> + <div id="SL_bbl_help" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Help"> </div> + </td> + <td class="SL_td"> + <div class="SL_pin_off" id="SL_pin" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Pin pop-up bubble"> </div> + </td> + </tr> + </tbody> +</table> +</div> +</div> + +<div id="SL_shadow_translation_result" style=""> </div> + +<div class="SL_loading" id="SL_loading" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_player2"> </div> + +<div id="SL_alert100">Text-to-speech function is limited to 200 characters</div> + +<div id="SL_Balloon_options" style="background: rgb(0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> +<div id="SL_arrow_down" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<table id="SL_tbl_opt" style="width: 100%;"> + <tbody> + <tr> + <td><input></td> + <td> + <div id="SL_BBL_IMG" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Show Translator's button 3 second(s)"> </div> + </td> + <td><a class="SL_options" title="Show options">Options</a> : <a class="SL_options" title="Translation History">History</a> : <a class="SL_options" title="ImTranslator Feedback">Feedback</a> : <a class="SL_options" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GD9D8CPW8HFA2" title="Make a small contribution">Donate</a></td> + <td><span id="SL_Balloon_Close" title="Close">Close</span></td> + </tr> + </tbody> +</table> +</div> +</div> +</div> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/delete_author_form/index.html new file mode 100644 index 0000000000..f26b87bce7 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/delete_author_form/index.html @@ -0,0 +1,167 @@ +--- +title: Delete Author form +slug: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +--- +<p> </p> + +<p>此子文檔展示,如何定義頁面以刪除 <code>Author</code>對象。</p> + +<p>正如<a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs/forms#form_design">表單設計</a>部分所討論的那樣,我們的策略是,只允許刪除“未被其他對象引用” 的對象(在這種情況下,這意味著如果作者<code>Author</code>被一本書<code>Book</code>引用,我們將不允許刪除作者)。在實現方面,這意味著,表單需要在刪除作者之前,先確認沒有關聯的書籍。如果存在關聯的書籍,則應顯示它們,並說明在刪除<code>Author</code>對象之前,必須刪除它們。</p> + +<h2 class="highlight-spanned" id="Controller—get_route">Controller—get route</h2> + +<p>Open <strong>/controllers/authorController.js</strong>. Find the exported <code>author_delete_get()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Display Author delete form on GET. +exports.author_delete_get = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.params.id).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors'); + } + // Successful, so render. + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + }); + +};</code></pre> + +<p>The controller gets the id of the <code>Author</code> instance to be deleted from the URL parameter (<code>req.params.id</code>). It uses the <code>async.parallel()</code> method to get the author record and all associated books in parallel. When both operations have completed it renders the <code><strong>author_delete</strong></code><strong>.pug</strong> view, passing variables for the <code>title</code>, <code>author</code>, and <code>author_books</code>.</p> + +<div class="note"> +<p><strong>Note:</strong> If <code>findById()</code><strong> </strong>returns no results the author is not in the database. In this case there is nothing to delete, so we immediately render the list of all authors. </p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">}, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors') + }</code></pre> +</div> + +<h2 class="highlight-spanned" id="Controller—post_route">Controller—post route</h2> + +<p>Find the exported <code>author_delete_post()</code> controller method, and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Handle Author delete on POST. +exports.author_delete_post = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.body.authorid).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.body.authorid }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + // Success + if (results.authors_books.length > 0) { + // Author has books. Render in same way as for GET route. + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + return; + } + else { + // Author has no books. Delete object and redirect to the list of authors. + Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) { + if (err) { return next(err); } + // Success - go to author list + res.redirect('/catalog/authors') + }) + } + }); +};</code></pre> + +<p>First we validate that an id has been provided (this is sent via the form body parameters, rather than using the version in the URL). Then we get the author and their associated books in the same way as for the <code>GET</code> route. If there are no books then we delete the author object and redirect to the list of all authors. If there are still books then we just re-render the form, passing in the author and list of books to be deleted.</p> + +<div class="note"> +<p><strong>Note:</strong> We could check if the call to <code>findById()</code><strong> </strong>returns any result, and if not, immediately render the list of all authors. We've left the code as it is above for brevity (it will still return the list of authors if the id is not found, but this will happen after <code>findByIdAndRemove()</code>).</p> +</div> + +<h2 class="highlight-spanned" id="View">View</h2> + +<p>Create <strong>/views/author_delete.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1 #{title}: #{author.name} + p= author.lifespan + + if author_books.length + + p #[strong Delete the following books before attempting to delete this author.] + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p Do you really want to delete this Author? + + form(method='POST' action='') + div.form-group + input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id ) + + button.btn.btn-primary(type='submit') Delete</code></pre> + +<p>The view extends the layout template, overriding the block named <code>content</code>. At the top it displays the author details. It then includes a conditional statement based on the number of <code><strong>author_books</strong></code> (the <code>if</code> and <code>else</code> clauses).</p> + +<ul> + <li>If there <em>are</em> books associated with the author then the page lists the books and states that these must be deleted before this <code>Author</code> may be deleted.</li> + <li>If there <em>are no</em> books then the page displays a confirmation prompt. If the <strong>Delete</strong> button is clicked then the author id is sent to the server in a <code>POST</code> request and that author's record will be deleted.</li> +</ul> + +<h2 class="highlight-spanned" id="Add_a_delete_control">Add a delete control</h2> + +<p>Next we will add a <strong>Delete</strong> control to the<em> Author detail</em> view (the detail page is a good place from which to delete a record).</p> + +<div class="note"> +<p><strong>Note:</strong> In a full implementation the control would be made visible only to authorised users. However at this point we haven't got an authorisation system in place!</p> +</div> + +<p>Open the <strong>author_detail.pug</strong> view and add the following lines at the bottom.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">hr +p + a(href=author.url+'/delete') Delete author</code></pre> + +<p>The control should now appear as a link, as shown below on the <em>Author detail</em> page.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14492/LocalLibary_Express_Author_Detail_Delete.png" style="border-style: solid; border-width: 1px; display: block; height: 202px; margin: 0px auto; width: 500px;"></p> + +<h2 class="highlight-spanned" id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application and open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Then select the <em>All authors </em>link, and then select a particular author. Finally select the <em>Delete author</em> link.</p> + +<p>If the author has no books, you'll be presented with a page like this. After pressing delete, the server will delete the author and redirect to the author list.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14494/LocalLibary_Express_Author_Delete_NoBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 342px; margin: 0px auto; width: 600px;"></p> + +<p>If the author does have books, then you'll be presented with a view like the following. You can then delete the books from their detail pages (once that code is implemented!).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14496/LocalLibary_Express_Author_Delete_WithBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 327px; margin: 0px auto; width: 500px;"></p> + +<div class="note"> +<p><strong>Note:</strong> The other pages for deleting objects can be implemented in much the same way. We've left that as a challenge.</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the final subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">Update Book form</a>.</li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/forms/index.html b/files/zh-tw/learn/server-side/express_nodejs/forms/index.html new file mode 100644 index 0000000000..008d7ae4e8 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/forms/index.html @@ -0,0 +1,274 @@ +--- +title: 'Express 教學 6: 使用表單' +slug: Learn/Server-side/Express_Nodejs/forms +translation_of: Learn/Server-side/Express_Nodejs/forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">在此教程中,我們會教你如何使用 Express ,並且結合 Pug 來實現 HTML 表單,並且如何從數據庫中創建、更新、和刪除文檔。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提條件:</th> + <td>完成前面所有的教程,包括 Express 教程第5章: 展示圖書館數據。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>了解如何編寫表單獲取用戶信息,並且將這些數據更新到數據庫中。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p><a href="/zh-TW/docs/Web/Guide/HTML/Forms">HTML 表單</a>是網頁中由一個、或多個字段/小工具形成的一個組合,它被用來收集用戶的信息,並將信息上傳到服務器。表單作為一種用來收集用戶的機制,非常的靈活,因為有各種合適的輸入框,來接受各種類型的數據——文本框,複選框,單選按鈕,時間選擇器等。表單和服務器交互數據也相對安全,因為它使用<code>POST</code>請求發送數據,保護不受跨站點請求偽造攻擊(cross-site request forgery)的威脅。</p> + +<p>但是表單同樣也很複雜!開發者需要編寫給表單編寫 HTML,在服務器上驗證,並且正確去除有害的數據(瀏覽器上也可能需要),對於任何不合法的字段,需要傳給用戶相應的錯誤信息,當數據成功提交後,處理數據,並設法通知用戶提交成功。</p> + +<p>此教程將展示上述的操作,如何在 Express 中實現。在此過程中,我們將擴展 LocalLibrary 網站,以允許用戶創建、編輯、和刪除圖書館中的項目。</p> + +<div class="note"> +<p><strong>注意:</strong> 我們還沒有考慮如何將特定路由,限制為經過身份驗證或授權的用戶,因此在這個時間點,任何用戶都可以對數據庫進行更改。</p> +</div> + +<h3 id="HTML_表單">HTML 表單</h3> + +<p>首先簡要概述 <a href="/zh-TW/docs/Web/Guide/HTML/Forms">HTML 表單</a>。考慮一個簡單的 HTML 表單,其中包含一個文本字段,用於輸入某些 “團隊” 的名稱,及其相關標籤:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>表單在HTML中,定義為 <code><form>...</form></code>標記內的元素集合,包含至少一個<code>type="submit"</code>的 <code>input</code>輸入元素。</p> + +<pre class="brush: 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></pre> + +<p>雖然這裡,我們只包含一個(文本)字段,用於輸入團隊名稱,但表單可能包含任意數量的其他輸入元素,及其相關標籤。字段的 <code>type</code> 屬性,定義將顯示哪種窗口小部件。該字段的名稱<code>name</code>和<code>id</code> ,用於標識JavaScript/CSS/HTML 中的字段,而 <code>value</code>定義字段首次顯示時的初始值。匹配團隊標籤使用 <code style="font-style: normal; font-weight: normal;">label </code>標籤,指定(請參閱上面的“輸入名稱” "Enter name"),其中 <code style="font-style: normal; font-weight: normal;">for</code> 字段,包含 <code style="font-style: normal; font-weight: normal;">input </code>相關輸入的 <code style="font-style: normal; font-weight: normal;">id</code>值。</p> + +<p>提交輸入(<code>submit</code>)將顯示為按鈕(默認情況下) - 用戶可以按此按鈕,將其他輸入元素包含的數據,上傳到服務器(在本例中,只有 <code>team_name</code>)。表單屬性,定義用於發送數據的HTTP <code>method</code>方法,和服務器上數據的目標(<code>action</code>):</p> + +<ul> + <li><code>action</code>: 提交表單時,要發送數據以進行處理的資源/ URL。如果未設置(或設置為空字符串),則表單將提交回當前頁面 URL。</li> + <li><code>method</code>: 用於發送數據的 HTTP 方法:<code>POST</code> 或 <code>GET</code>。 + <ul> + <li><code>POST</code> 方法。如果數據將導致服務器數據庫的更改,則始終應該使用 <code>POST</code>方法,因為這更加可以抵抗跨站點偽造請求攻擊。</li> + <li><code>GET</code> 方法只應用於不更改用戶數據的表單(例如,搜索表單)。當您希望能夠為URL添加書籤或共享時,建議使用此選項。</li> + </ul> + </li> +</ul> + +<h3 id="表單處理流程">表單處理流程</h3> + +<p>表單處理使用的技術,與我們學習過、用來顯示有關模型的信息的所有技術,是相同的:路由將我們的請求發送到控制器函數,該函數執行所需的任何數據庫操作,包括從模型中讀取數據,然後生成並返回 HTML 頁面。使事情變得更複雜的是,服務器還需要能夠處理用戶提供的數據,並在出現任何問題時,重新顯示帶有錯誤信息的表單。</p> + +<p>下面顯示了處理表單請求的流程圖,從包含表單的頁面請求開始(以綠色顯示):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p> + +<p>如上圖所示,構成處理代碼所需要做的主要是: </p> + +<ol> + <li>在用戶第一次請求時顯示默認表單。 + <ul> + <li>表單可能包含空白字段(例如,如果您正在創建新記錄),或者可能預先填充了初始值(例如,如果您要更改記錄,或者俱有有用的默認初始值)。</li> + </ul> + </li> + <li>接收用戶提交的數據,通常是在HTTP <code>POST</code>請求中。</li> + <li>驗證並清理數據。</li> + <li>如果任何數據無效,請重新顯示表單 - 這次使用用戶填寫的任何值,和問題字段的錯誤消息。</li> + <li>如果所有數據都有效,請執行所需的操作(例如,將數據保存在數據庫中,發送通知電子郵件,返回搜索結果,上傳文件等)</li> + <li>完成所有操作之後,將用戶重定向到另一個頁面。</li> +</ol> + +<p>表格處理代碼,通常使用 <code>GET</code>路由,以實現表單的初始顯示,以及 <code>POST</code>路由到同一路徑,以處理表單數據的驗證和處理。這是將在本教程中使用的方法!</p> + +<p>Express 本身不提供表單處理操作的任何特定支持,但它可以使用中間件,以處理表單中的 <code>POST</code>和 <code>GET</code>參數,並驗證/清理它們的值。</p> + +<h3 id="驗證和清理">驗證和清理</h3> + +<p>在儲存表單中的數據之前,必須對其進行驗證和清理:</p> + +<ul> + <li>驗證檢查輸入的值,適用於每個字段(範圍,格式等),並且已為所有必填字段提供了值。</li> + <li>數據中的字符,可能用於將惡意內容送到服務器,為其進行清理刪除/替換。</li> +</ul> + +<p>在本教程中,我們將使用流行的 <a href="https://www.npmjs.com/package/express-validator">express-validator </a>模塊,來執行表單數據的驗證和清理。</p> + +<h4 id="安裝">安裝</h4> + +<p>通過在項目的根目錄中,運行以下命令,來安裝模塊。</p> + +<pre class="brush: bash">npm install express-validator +</pre> + +<h4 id="使用_express-validator">使用 express-validator</h4> + +<div class="note"> +<p><strong>注意:</strong> Github上的 <a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a> 指南,提供了API的良好概述。我們建議您閱讀該內容,以了解其所有功能(包括創建自定義驗證程序)。下面我們只介紹一個對 LocalLibrary 有用的子集。</p> + +<p> </p> + +<p> </p> +</div> + +<p>要在我們的控制器中使用驗證器,我們必須從 'e<strong>xpress-validator/check</strong>'和'<strong>express-validator/filter</strong>'模塊中,導入我們想要使用的函數,如下所示:</p> + +<pre class="brush: js">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter'); +</pre> + +<p>有許多可用的功能,允許您一次檢查和清理請求參數,正文,標頭,cookie 等數據,或所有數據。對於本教程,我們主要使用 <code>body</code>, <code>sanitizeBody</code>,和 <code>validationResult</code>(如上面 required 導入的 )。</p> + +<p>功能定義如下:</p> + +<ul> + <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: 指定請求本文中的一組字段(<code>POST</code>參數)以及可選的錯誤消息,如果測試失敗,則可以顯示該字段。驗證標準以菊花鏈形式連接到 <code>body()</code>方法。例如,下面的第一個檢查測試,“name”字段不為空,如果不是,則設置錯誤消息“Empty name”。第二個測試,檢查age字段是否為有效日期,並使用<code>optional()</code>指定null和空字符串不會驗證失敗。 + + <pre class="brush: js">body('name', 'Empty name').isLength({ min: 1 }), +body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(), +</pre> + 您還可以用菊花鍊式連接不同的驗證器,並添加前面驗證器為真時顯示的消息。 + + <pre class="brush: js">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.') + .isAlpha().withMessage('Name must be alphabet letters.'), +</pre> + + <div class="note"> + <p><strong>注意:</strong> 您還可以添加內聯清理器,如<code>trim()</code>,如上所示。但是,此處應用清理器,僅適用於驗證步驟。如果要對最終輸出進行消毒,則需要使用單獨的清理器方法,如下所示。</p> + </div> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: 指定一個正文要清理的字段。然後將清理操作,以菊花鏈形式連接到此方法。例如,下面的 <code>escape()</code>清理操作,會從名稱變量中,刪除可能在 JavaScript 跨站點腳本攻擊中使用的 HTML 字符。 + <pre class="brush: js">sanitizeBody('name').trim().escape(), +sanitizeBody('date').toDate(),</pre> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: 運行驗證,以 <code>validation </code>驗證結果對象的形式,提供錯誤。這是在單獨的回調中調用的,如下所示: + <pre class="brush: js">(req, res, next) => { + // Extract the validation errors from a request. + const errors = validationResult(req); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/errors messages. + // Error messages can be returned in an array using `errors.array()`. + } + else { + // Data from form is valid. + } +}</pre> + 我們使用驗證結果的<code>isEmpty()</code>方法,來檢查是否存在錯誤,並使用其 <code>array()</code> 方法,來獲取錯誤消息集合。有關更多信息,請參閱驗證結果的 <a href="https://github.com/ctavan/express-validator#validation-result-api">Validation Result API</a>。</li> +</ul> + +<p>驗證和清理鏈,是應該傳遞給 Express 路由處理程序的中間件(我們通過控制器,間接地執行此操作)。中間件運行時,每個驗證器/清理程序都按指定的順序運行。</p> + +<p>當我們實現下面的LocalLibrary表單時,我們將介紹一些真實的例子。</p> + +<h3 id="表單設計">表單設計</h3> + +<p>圖書館中的許多模型都是相關/依賴的 - 例如,一本書需要一個作者,也可能有一個或多個種類。這提出了一個問題,即我們應該如何處理用戶希望的情況:</p> + +<ul> + <li>在其相關對象尚不存在時,創建對象(例如,尚未定義作者對象的書)。<br> + </li> + <li>刪除另一個對象仍在使用的對象(例如,刪除仍有書本 <code>Book </code>使用的種類 <code>Genre</code>)。</li> +</ul> + +<p>在這個項目,我們為了簡化實作,將聲明表單只能:</p> + +<ul> + <li>使用已存在的對象創建對象(因此用戶在嘗試創建任何 <code>Book</code>對象之前,必須創建任何所需的 <code>Author</code>和 <code>Genre</code>實例)。</li> + <li>如果對象未被其他對象引用,則刪除該對象(例如,在刪除所有關聯的 <code>BookInstance</code>對象之前,您將無法刪除該書)。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong> 更“牢固”的實現,可能允許您在創建新對象時,創建依賴對象,並隨時刪除任何對象(例如,通過刪除依賴對象,或從數據庫中,刪除對已刪除對象的引用) 。</p> +</div> + +<h3 id="路由">路由</h3> + +<p>為了實現我們的表單處理代碼,我們需要兩個具有相同 URL 模式的路由。</p> + +<p>第一個(<code>GET</code>)路由,用於顯示用於創建對象的新空表單。第二個路由(<code>POST</code>),用於驗證用戶輸入的數據,然後保存信息,並重定向到詳細信息頁面(如果數據有效),或重新顯示有錯誤的表單(如果數據無效)。</p> + +<p>我們已經在 <strong>/routes/catalog.js</strong>(在之前的教程中)為我們所有模型的創建頁面,創建了路徑。例如,種類路由如下所示:</p> + +<pre class="brush: js">// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id). +router.get('/genre/create', genre_controller.genre_create_get); + +// POST request for creating Genre. +router.post('/genre/create', genre_controller.genre_create_post); +</pre> + +<h2 id="Express_表單子文件">Express 表單子文件</h2> + +<p>以下子文件,將帶我們完成向示例應用程序添加所需表單的過程。在進入下一個文件之前,您需要依次閱讀並解決每個問題。</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">創建種類表單</a> — 定義我們的頁面以創建種類對象 <code>Genre </code>。</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">創建作者表單</a> — 定義用於創建作者對象 <code>Author </code>的頁面。</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">創建書本表單</a> — 定義頁面/表單以創建書本對象 <code>Book</code> 。</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">創建書本實例表單</a> — 定義頁面/表單以創建書本實例對象 <code>BookInstance</code> 。</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">刪除作者表單 </a>— 定義要刪除作者對象 <code>Author </code>的頁面。</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新書本表單</a> — 定義頁面以更新書本對象 <code>Book</code> 。</li> +</ol> + +<h2 id="挑戰自我">挑戰自我</h2> + +<p>實現 <code>Book</code>, <code>BookInstance</code>, 和 <code>Genre</code>模型的刪除頁面,用跟我們的作者刪除頁面相同的方式,將它們與關聯的詳細信息頁面,鏈接起來。頁面應遵循相同的設計方法:</p> + +<ul> + <li>如果有來自其他對象的、對於對象的引用,則應顯示註釋,列出這些對象,並說明在刪除列出的對象之前,無法刪除此記錄。</li> + <li>如果沒有對該對象的其他引用,則視圖應提示刪除它。如果用戶按下“刪除”按鈕 <strong>Delete</strong>,則應刪除該記錄。</li> +</ul> + +<p>一些提示:</p> + +<ul> + <li>刪除種類 <code>Genre</code>就像刪除作者<code>Author</code>一樣,因為兩個對像都是 <code>Book</code>的依賴項(因此在這兩種情況下,只有在刪除相關書本時,才能刪除對象)。</li> + <li>刪除書本 <code>Book</code>也很相似,但您需要檢查是否沒有關聯的書本實例 <code>BookInstances</code>。</li> + <li>刪除書本實例 <code>BookInstances</code>是最簡單的,因為沒有依賴對象。在這種情況下,您只需找到相關記錄並將其刪除即可。</li> +</ul> + +<p>實現 <code>BookInstance</code>, <code>Author</code>, 和 <code>Genre</code>模型的更新頁面,以與我們的書本更新頁面相同的方式,將它們與關聯的詳細信息頁面,鏈接起來。</p> + +<p>一些提示:</p> + +<ul> + <li>我們剛剛實施的圖書更新頁面是最難的!相同的模式可用於其他對象的更新頁面。</li> + <li>作者<code>Author</code>的死亡日期和出生日期字段,以及書本實例<code>BookInstance</code> 的 due_date 字段,是輸入到表單上日期輸入字段的錯誤格式(它需要 “YYYY-MM-DD” 形式的數據)。解決此問題的最簡單方法,是為適當格式化的日期,定義新的虛擬屬性,然後在關聯的視圖模板中,使用此字段。</li> + <li>如果您遇到困難,此處示例中的更新頁面有一些示例<a href="https://github.com/mdn/express-locallibrary-tutorial">的連結。</a></li> +</ul> + +<h2 id="總結">總結</h2> + +<p>Express, node, 與NPM上面的第三方套件,提供你需要的每樣東西 ,可用於新增表單到你的網站上。在本文中,您學習如何使用 Pug 創建表單,使用 express-validator 驗證和清理輸入,以及添加,刪除和修改數據庫中的記錄。</p> + +<p>你現在應該了解如何新增基本表單,以及表單處理代碼到你的 node 網站!</p> + +<h2 id="請參閱">請參閱</h2> + +<ul> + <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm docs).</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本教程連結">本教程連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/index.html b/files/zh-tw/learn/server-side/express_nodejs/index.html new file mode 100644 index 0000000000..c1c6e11ee5 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/index.html @@ -0,0 +1,73 @@ +--- +title: Express web framework (Node.js/JavaScript) +slug: Learn/Server-side/Express_Nodejs +tags: + - Express + - Express.js + - Node + - node.js + - 介紹 + - 伺服器端程式 + - 初學者 + - 學習 +translation_of: Learn/Server-side/Express_Nodejs +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Express 是一個流行的web框架,使用JavsScript實現,執行在node.js環境上。本系列解釋Express的優點、如何設定開發環境、完成常見的web開發和佈署。</p> + +<h2 id="前置需求">前置需求</h2> + +<p>在開始前你需要了解什麼是伺服器端web程式和什麼是web框架,推薦閱讀<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/First_steps">伺服器端網站開發第一步</a>。建議了解基本的程式知識和<a href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript">JavaScript</a>,但不需要知道核心概念。</p> + +<div class="note"> +<p><strong>注意</strong>: 本網站有許多學習JavaScript應用在客戶端開發的有用資源,如:<a href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript">JavaScript</a>、<a href="https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide">JavaScript 指南</a>、<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基礎</a>、<a href="https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript">JavaScript</a> (learning)。使用Node.js開發伺服器端使用的JavaScript語言與概念和客戶端是一樣的。Node.js提供<a href="https://nodejs.org/dist/latest-v6.x/docs/api/">額外的APIs</a>以支援無瀏覽器環境,例如:建立HTTP服務和讀取檔案系統。但不支援DOM及瀏覽器相關的 JavaScript API。</p> + +<p>這份指南將提供一些使用Node.js和Express的資訊以及數個優秀的學習資源。部分連結由 <a href="http://stackoverflow.com/a/5511507/894359">How do I get started with Node.js</a>(StackOverflow) 與 <a href="https://www.quora.com/What-are-the-best-resources-for-learning-Node-js?">What are the best resources for learning Node.js?</a>(Quora) 提供。</p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介紹</a></dt> + <dd>第一篇的系列文章中回答了「什麼是Node」和「什麼是Express?」並概略的說明為什麼Express web框架如此特別。此文章將重點放在主要的功能上,並展示一些Express應用常見的建構模塊(儘管此時你還沒有可供測試的開發環境)</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/development_environment">設定 Node (Express) 開發環境</a></dt> + <dd>現在你已經了解Express的目的了,接下來繼續說明如何設定和測試 Windows、Linux (Ubuntu)和Mac OS X上的Node/Express開發環境。不管你用的是什麼作業系統,你都能在本文中找到開發Express應用的入門需知。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教學(1): The Local Library website</a></dt> + <dd>在第一篇實務教學系列文章中將說明你將會學到什麼?以及提供範例網站local library的概覽,我們將在後續的文章中繼續改進它。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教學(2): 建構網站骨架</a></dt> + <dd>本文章展示如何建構網站的骨架,接著你可以自己添加路由、模板/畫面和資料庫。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教學(3): 使用資料庫(以Mongoose為例)</a></dt> + <dd>本文簡短的介紹Node/Express如何使用資料庫。接下來展示LocalLibray網站如何透過<a href="http://mongoosejs.com/">Mongoose</a>進行資料庫的存取。說明物件綱要(object schema)和模型(models)如何宣告、the main field types和基本驗證。同時簡單的展示幾個讀取資料的主要方法。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/routes">Express 教學(4): 路由和控制器</a></dt> + <dd>在本教學中,我們將為LocalLibrary網站中的所有資源終端設定“虛擬”處理函數的路由(URL處理代碼)。 完成後,我們將為我們的路由處理程式提供模組化結構,以便我們可以在後續的教學中擴展真正的處理函數。 我們也將了解如何使用Express創建模組化路由。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教學(5): 顯示圖書館的資料</a></dt> + <dd>現在已經準備好新增頁面來展示館藏和其他資料了。這些頁面包括一個展示我們有多少種model 型態的首頁、所有models的列表和詳細資料頁面。透過本教學你可以得到從資料庫取得紀錄和使用模板的實務經驗。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/forms">Express 教學(6): 使用表單</a></dt> + <dd>本教學中展示如何使用Express的插件-Pug來使用HTML Forms,以及如何編寫表單來創造、更新和刪除資料庫的文件。</dd> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教學(7): 網站佈署</a></dt> + <dd>現在你完成了很棒的<em>LocalLibrary</em> 網站,你希望圖書館的員工和會員可以透過網路讀取它。本教學概略說明如何找到主機來佈署你的網站以及為了使你的網站正式上線所需做的準備。</dd> +</dl> + +<h2 id="或許你也想看">或許你也想看</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">在 PWS/Cloud Foundry上安裝LocalLibrary</a></dt> + <dd>本文展示如何在<a href="http://run.pivotal.io">Pivotal Web Services PaaS cloud</a>上安裝<em>LocalLibrary</em> ,PWS/Cloud Foundry是一個完整且開源的Heroku替代品,可使用於教學(7)。如果你正在尋找Heroku或其他PaaS的替代品或只是想玩點不同的東西,那PWS/Cloud Foundry絕對值得一試。</dd> +</dl> + +<h2 id="新增其他教學">新增其他教學</h2> + +<div> +<p>現在已經有了很多教學,但你可能會想寫其他有趣主題的模塊,包括:</p> + +<ul> + <li>Using sessions</li> + <li>使用者認證</li> + <li>使用者授權與權限</li> + <li>測試Express web應用</li> + <li>Express web 應用之安全性</li> +</ul> + +<p>當然,如果能作個評估模塊就更好了!</p> +</div> diff --git a/files/zh-tw/learn/server-side/express_nodejs/introduction/index.html b/files/zh-tw/learn/server-side/express_nodejs/introduction/index.html new file mode 100644 index 0000000000..6fc3f0a98c --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/introduction/index.html @@ -0,0 +1,522 @@ +--- +title: Express/Node introduction +slug: Learn/Server-side/Express_Nodejs/Introduction +tags: + - Express + - Node + - nodejs + - 伺服器端 + - 初學者 + - 學習 +translation_of: Learn/Server-side/Express_Nodejs/Introduction +--- +<div> + + + + +<p>{{LearnSidebar}}</p> + +<p>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</p> +</div> + +<p class="summary">在這篇文章中回答了「什麼是Node?」和「什麼是Express」,同時概述是什麼讓Express框架如此特別。本文將概述主要特性、展示一些Express應用的主要建構模塊(雖然此時你還沒有能測試它的開發環境)</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置需求:</th> + <td>基本的電腦知識。 對<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">伺服器端網站程式設計</a>的基本了解,特別是網站中<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">客戶端 - 伺服器交互的機制</a>。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>提升對Express的了解、如何與Node搭配使用、提供的功能和Express應用的主要建構模塊。</td> + </tr> + </tbody> +</table> + +<h2 id="什麼是Express和Node">什麼是Express和Node?</h2> + +<p><a href="https://nodejs.org/">Node</a> (或者說<em>Node.js</em>) 是一個開源、跨平台和允許開發者使用<a href="https://developer.mozilla.org/en-US/docs/Glossary/JavaScript">Javascript</a>創造伺服器端工具和應用的執行環境。運行的目的是為了能在瀏覽器外使用,例如:直接執行在電腦或伺服器上。所以該環境捨棄了瀏覽器限定的JavaScript APIs並增加更多傳統OS APIs的支援,例如:HTTP和檔案系統的程式庫。</p> + + + +<p>從網站伺服器開發的觀點來看Node有幾項優點:</p> + +<ul> + <li>高效能!Node 旨在提升生產率和網頁應用的可擴充性。而且它非常適合網站開發常見的問題,例如:即時網站應用</li> + <li>使用舊版本的JavaScript進行程式編寫,這表示不用多花力氣在轉換瀏覽器和伺服器上的程式碼</li> + <li>與其他傳統的Web伺服器語言(例如Python,PHP等)相比,JavaScript是一種相對新的程式語言,它受益於語言設計的改進。許多其他新的和流行的語言都可以編譯/轉換成JavaScript,因此你還可以使用CoffeeScript, ClojureScript,Scala,LiveScript等</li> + <li>Node Package Manager(NPM) 提供數十萬個第三方套件,是最佳的依賴解決方案也可以用來自動化大部分構建工具鏈。</li> + <li>它是可移植的,能夠在Windows, OS x, Linux, Solaris, FreeBSD, OpenBSD, WebOS和NonStop OS上執行。許多web主機提供方也支援使用Node,通常會提供特定的基礎設施和文件</li> + <li>擁有非常活耀的第三方生態系統和開發者社群,許多人樂意提供幫助</li> +</ul> + +<p>你可以只用Node的HTTP模組創造一個簡單的web伺服器來回應任何請求,如下所示。此教學不會告訴建議的檔案名稱或如何執行該檔案 ;-)</p> + +<p>這將創造一個伺服器並會監聽<code>http://127.0.0.1:8000/</code>上任何種類的HTTP請求,當接收到任何請求時回傳一個「Hello World」的純文字回應。</p> + +<pre class="brush: js">// 載入 HTTP 模組 +var http = require("http"); + +// 創建 HTTP 伺服器並監聽8000 port +http.createServer(function(request, response) { + + // Set the response HTTP header with HTTP status and Content type + response.writeHead(200, {'Content-Type': 'text/plain'}); + + // Send the response body "Hello World" + response.end('Hello World\n'); +}).listen(8000); + +// Print URL for accessing server +console.log('Server running at http://127.0.0.1:8000/');</pre> + +<p>Node並不原生支持其他常見的web開發任務,如果你想為不同的HTTP方法(例如:<code>GET</code>, <code>POST</code>, <code>DELETE</code>等)增加特定的處理、替不同的URL路徑提供靜態檔案、使用樣板或動態性的產生response,你需要自己完成相關的程式<strong>或者是</strong>避免重新造輪子 - 使用web框架!</p> + +<p><a href="https://expressjs.com/">Express</a> 是最受歡迎的Node web框架,還是其他許多流行的<a href="https://expressjs.com/en/resources/frameworks.html">Node web框架</a>的底層庫,它提供:</p> + +<ul> + <li>替不同HTTP Method、不同URL路徑的requests編寫不同的處理方法</li> + <li>透過整合「畫面」的渲染引擎來達到插入資料到樣板中產生response</li> + <li>設定常見的web應用設定,例如:連線用的port和產生response的樣板位置</li> + <li>在request的處理流程中增加額外的「中間層」進行處理</li> +</ul> + +<p>雖然Express本身非常簡單,但開發者們已經創造相容的中間層套件來解決大部份web開發的問題,這些套件能處理cookies, sessions,登入,URL參數,POST資料,安全標頭等等,你能在<a href="http://expressjs.com/en/resources/middleware.html">Express Middleware</a>中找到這些套件的列表(以及其他流行的第三方套件)</p> + +<div class="note"> +<p><strong>注意:</strong> 這種靈活性是一把雙刃劍。有一些中間層套件能解決大部份的問題或需求,但使用正確的套件有時會是一個問題。也沒有「正確的方法」來創建應用,你在網路上找到的範例也並非都是最佳解或是只有開發上所需要做的一小部份。</p> +</div> + +<h2 id="歷史">歷史</h2> + +<p>2009年Node在Linux平台上初次發佈. 2010年NPM套件管利器發佈, 2012年增加Windows的原生支援. 現在的LTS版本為Node v8.11.2,最新版本為Node v10.1.0。這只是它深厚歷史的一小片斷,欲知更多詳情請洽 <a href="https://en.wikipedia.org/wiki/Node.js#History">Wikipedia</a>。</p> + +<p>2010年11月Express初次發佈,現在的API版本為 4.16。你可以查閱<a href="https://expressjs.com/en/changelog/4x.html">更新紀錄</a>來了解此版本做了甚麼更改或是從<a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a>中了解詳細的歷史紀錄。</p> + +<h2 id="NodeExpress有多流行">Node/Express有多流行?</h2> + +<p>對於web 框架而言流行度很重要,這代表他會不會被繼續更新、文件、附加套件和技術支援方面有多少資源</p> + +<p>現在沒有一個明確的指標來評斷伺服器端框架的流行度,雖然有 <a href="http://hotframeworks.com/">Hot Frameworks</a>透過計算GitHub的專案數量和StackOverflow的問題來衡量流行度。更好的問題是,Node和Express是否「夠流行」以避免成為不流行的平台。有沒有持續進步?需要時是否能得到幫助?能不能找到Express相關的工作?</p> + +<p>從眾多使用Express的<a href="https://expressjs.com/en/resources/companies-using-express.html">公司</a>、貢獻程式碼的人數和那些提供免費/收費支援的人員來看,是的!Express是一個流行的框架。</p> + +<h2 id="Is_Express_opinionated">Is Express opinionated?</h2> + +<p>Web 框架通常自稱為 "opinionated" 或 "unopinionated".</p> + +<p>Opinionated指的是那些有「正確」方法解決特定問題的框架。在特定的需求上他們通常能快速開發,因為正確的方法通常易懂且有良好的文件,然而在面對其他問題時則會失去靈活性。這類型的框架通常傾向於提供較少的選擇和套件來解決問題。</p> + +<p>反過來說Unopinionated 框架,對於如何組合套件來解決問題尚有較少的限制,開發者可以更輕易的使用適當的套件來解決特定問題,儘管代價是你需要自己找到適合的套件。</p> + +<p>Express是Unopinionated 框架,你可以在request處理流程中使用任何相容套件,使用單一或複數個檔案來建構應用,有時候甚至會覺得擁有太多選擇了。</p> + +<h2 id="Express的程式碼長怎樣">Express的程式碼長怎樣?</h2> + +<p>傳統的資料驅動網站中,web應用程式會等待來自瀏覽器(或其他客戶端)的HTTP Request,接收到Request後根據URL和可能夾帶的<code>POST</code>/<code>GET</code>資料來決定需要回應什麼動作,根據需要可能對資料庫進行讀寫或執行滿足Request所需的其他任務。web應用程式會回應Response給瀏覽器,通常是藉由插入檢所到的資料到HTML 模板中動態產生HTML頁面讓瀏覽器顯示。</p> + + + +<p>Express provides methods to specify what function is called for a particular HTTP verb (<code>GET</code>, <code>POST</code>, <code>SET</code>, etc.) and URL pattern ("Route"), and methods to specify what template ("view") engine is used, where template files are located, and what template to use to render a response. You can use Express middleware to add support for cookies, sessions, and users, getting <code>POST</code>/<code>GET</code> parameters, etc. You can use any database mechanism supported by Node (Express does not define any database-related behaviour).</p> + +<p>The following sections explain some of the common things you'll see when working with <em>Express</em> and <em>Node</em> code.</p> + +<h3 id="Helloworld_Express">Helloworld Express</h3> + +<p>First lets consider the standard Express <a href="https://expressjs.com/en/starter/hello-world.html">Hello World</a> example (we discuss each part of this below, and in the following sections).</p> + +<div class="note"> +<p><strong>Tip:</strong> If you have Node and Express already installed (or if you install them as shown in the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">next article</a>), you can save this code in a text file called <strong>app.js</strong> and run it in a bash command prompt by calling: </p> + +<p><strong><code>./node ./app.js</code></strong></p> +</div> + +<pre class="brush: js">var express = require('express'); +var app = express(); + +<strong>app.get('/', function(req, res) { + res.send('Hello World!'); +});</strong> + +app.listen(3000, function() { + console.log('Example app listening on port 3000!'); +}); +</pre> + +<p>The first two lines <code>require()</code> (import) the express module and create an <a href="https://expressjs.com/en/4x/api.html#app">Express application</a>. This object, which is traditionally named <code>app</code>, has methods for routing HTTP requests, configuring middleware, rendering HTML views, registering a template engine, and modifying <a href="https://expressjs.com/en/4x/api.html#app.settings.table">application settings</a> that control how the application behaves (e.g. the environment mode, whether route definitions are case sensitive, etc.)</p> + +<p>The middle part of the code (the three lines starting with <code>app.get</code>) shows a <em>route definition</em>. The <code>app.get()</code> method specifies a callback function that will be invoked whenever there is an HTTP <code>GET</code> request with a path (<code>'/'</code>) relative to the site root. The callback function takes a request and a response object as arguments, and simply calls <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> on the response to return the string "Hello World!"</p> + +<p>The final block starts up the server on port '3000' and prints a log comment to the console. With the server running, you could go to <code>localhost:3000</code> in your browser to see the example response returned.</p> + +<h3 id="Importing_and_creating_modules">Importing and creating modules</h3> + +<p>A module is a JavaScript library/file that you can import into other code using Node's <code>require()</code> function. <em>Express</em> itself is a module, as are the middleware and database libraries that we use in our <em>Express</em> applications.</p> + +<p>The code below shows how we import a module by name, using the <em>Express</em> framework as an example. First we invoke the <code style="font-style: normal; font-weight: normal;">require()</code> function, specifying the name of the module as a string (<code>'express'</code>), and calling the returned object to create an <a href="https://expressjs.com/en/4x/api.html#app">Express application</a>. We can then access the properties and functions of the application object.</p> + +<pre class="brush: js">var express = require('express'); +var app = express(); +</pre> + +<p>You can also create your own modules that can be imported in the same way.</p> + +<div class="note"> +<p><strong>Tip:</strong> You will <em>want </em>to create your own modules, because this allows you to organise your code into managable parts — a monolithic single-file application is hard to understand and maintain. Using modules also helps you manage your namespace, because only the variables you explicitly export are imported when you use a module.</p> +</div> + +<p>To make objects available outside of a module you just need to assign them to the <code>exports</code> object. For example, the <strong>square.js</strong> module below is a file that exports <code>area()</code> and <code>perimeter()</code> methods:</p> + +<pre class="brush: js">exports.area = function(width) { return width * width; }; +exports.perimeter = function(width) { return 4 * width; }; +</pre> + +<p>We can import this module using <code>require()</code>, and then call the exported method(s) as shown:</p> + +<pre class="brush: js">var square = require('./square'); // Here we require() the name of the file without the (optional) .js file extension +console.log('The area of a square with a width of 4 is ' + square.area(4));</pre> + +<div class="note"> +<p><strong>Note:</strong> You can also specify an absolute path to the module (or a name, as we did initially).</p> +</div> + +<p>If you want to export a complete object in one assignment instead of building it one property at a time, assign it to <code>module.exports</code> as shown below (you can also do this to make the root of the exports object a constructor or other function):</p> + +<pre class="brush: js">module.exports = { + area: function(width) { + return width * width; + }, + + perimeter: function(width) { + return 4 * width; + } +}; +</pre> + +<p>For a lot more information about modules see <a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs).</p> + +<h3 id="Using_asynchronous_APIs">Using asynchronous APIs</h3> + +<p>JavaScript code frequently uses asynchronous rather than synchronous APIs for operations that may take some time to complete. A synchronous API is one in which each operation must complete before the next operation can start. For example, the following log functions are synchronous, and will print the text to the console in order (First, Second).</p> + +<pre class="brush: js">console.log('First'); +console.log('Second'); +</pre> + +<p>By contrast, an asynchronous API is one in which the API will start an operation and immediately return (before the operation is complete). Once the operation finishes, the API will use some mechanism to perform additional operations. For example, the code below will print out "Second, First" because even though <code>setTimeout()</code> method is called first, and returns immediately, the operation doesn't complete for several seconds.</p> + +<pre class="brush: js">setTimeout(function() { + console.log('First'); + }, 3000); +console.log('Second'); +</pre> + +<p>Using non-blocking asynchronous APIs is even more important on Node than in the browser, because <em>Node</em> is a single threaded event-driven execution environment. "single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources, but it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not just the current request, but every other request being handled by your web application.</p> + +<p>There are a number of ways for an asynchronous API to notify your application that it has completed. The most common way is to register a callback function when you invoke the asynchronous API, that will be called back when the operation completes. This is the approach used above.</p> + +<div class="note"> +<p><strong>Tip:</strong> Using callbacks can be quite "messy" if you have a sequence of dependent asynchronous operations that must be performed in order, because this results in multiple levels of nested callbacks. This problem is commonly known as "callback hell". This problem can be reduced by good coding practices (see <a href="http://callbackhell.com/">http://callbackhell.com/</a>), using a module like <a href="https://www.npmjs.com/package/async">async</a>, or even moving to ES6 features like <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a>.</p> +</div> + +<div class="note"> +<p><strong>Note:</strong> A common convention for Node and Express is to use error-first callbacks. In this convention the first value in your <em>callback functions</em> is an error value, while subsequent arguments contain success data. There is a good explanation of why this approach is useful in this blog: <a href="http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js">The Node.js Way - Understanding Error-First Callbacks</a> (fredkschott.com).</p> +</div> + +<h3 id="Creating_route_handlers">Creating route handlers</h3> + +<p>In our <em>Hello World</em> Express example (see above), we defined a (callback) route handler function for HTTP <code>GET</code> requests to the site root (<code>'/'</code>).</p> + +<pre class="brush: js">app.<strong>get</strong>('/', function(req, res) { + res.send('Hello World!'); +}); +</pre> + +<p>The callback function takes a request and a response object as arguments. In this case the method simply calls <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> on the response to return the string "Hello World!" There are a <a href="https://expressjs.com/en/guide/routing.html#response-methods">number of other response methods</a> for ending the request/response cycle, for example you could call <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> to send a JSON response or <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> to send a file.</p> + +<div class="note"> +<p><strong>JavaScript tip:</strong> You can use any argument names you like in the callback functions; when the callback is invoked the first argument will always be the request and the second will always be the response. It makes sense to name them such that you can identify the object you're working with in the body of the callback.</p> +</div> + +<p>The <em>Express application</em> object also provides methods to define route handlers for all the other HTTP verbs, which are mostly used in exactly the same way: <code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, and <code>connect()</code>.</p> + +<p>There is a special routing method, <code>app.all()</code>, which will be called in response to any HTTP method. This is used for loading middleware functions at a particular path for all request methods. The following example (from the Express documentation) shows a handler that will be executed for requests to <code>/secret</code> irrespective of the HTTP verb used (provided it is supported by the <a href="https://nodejs.org/api/http.html#http_http_methods">http module</a>).</p> + +<pre class="brush: js">app.all('/secret', function(req, res, next) { + console.log('Accessing the secret section ...'); + next(); // pass control to the next handler +});</pre> + +<p>Routes allow you to match particular patterns of characters in a URL, and extract some values from the URL and pass them as parameters to the route handler (as attributes of the request object passed as a parameter).</p> + +<p>Often it is useful to group route handlers for a particular part of a site together and access them using a common route-prefix (e.g. a site with a Wiki might have all wiki-related routes in one file and have them accessed with a route prefix of <em>/wiki/</em>). In <em>Express</em> this is achieved by using the <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> object. For example, we can create our wiki route in a module named <strong>wiki.js</strong>, and then export the <code>Router</code> object, as shown below:</p> + +<pre class="brush: js">// wiki.js - Wiki route module + +var express = require('express'); +var router = express.Router(); + +// Home page route +router.get('/', function(req, res) { + res.send('Wiki home page'); +}); + +// About page route +router.get('/about', function(req, res) { + res.send('About this wiki'); +}); + +module.exports = router; +</pre> + +<div class="note"> +<p><strong>Note:</strong> Adding routes to the <code>Router</code> object is just like adding routes to the <code>app</code> object (as shown previously).</p> +</div> + +<p>To use the router in our main app file we would then <code>require()</code> the route module (<strong>wiki.js</strong>), then call <code>use()</code> on the <em>Express</em> application to add the Router to the middleware handling path. The two routes will then be accessible from <code style="font-style: normal; font-weight: normal;">/wiki/</code> and <code style="font-style: normal; font-weight: normal;">/wiki/about/</code>.</p> + +<pre class="brush: js">var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</pre> + +<p>We'll show you a lot more about working with routes, and in particular about using the <code>Router</code>, later on in the linked section<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes"> Routes and controllers .</a></p> + +<h3 id="Using_middleware">Using middleware</h3> + +<p>Middleware is used extensively in Express apps, for tasks from serving static files to error handling, to compressing HTTP responses. Whereas route functions end the HTTP request-response cycle by returning some response to the HTTP client, middleware functions <em>typically</em> perform some operation on the request or response and then call the next function in the "stack", which might be more middleware or a route handler. The order in which middleware is called is up to the app developer.</p> + +<div class="note"> +<p><strong>Note:</strong> The middleware can perform any operation, execute any code, make changes to the request and response object, and it can<em> also end the request-response cycle</em>. If it does not end the cycle then it must call <code>next()</code> to pass control to the next middleware function (or the request will be left hanging).</p> +</div> + +<p>Most apps will use <em>third-party</em> middleware in order to simplify common web development tasks like working with cookies, sessions, user authentication, accessing request <code>POST</code> and JSON data, logging, etc. You can find a <a href="http://expressjs.com/en/resources/middleware.html">list of middleware packages maintained by the Express team</a> (which also includes other popular 3rd party packages). Other Express packages are available on the NPM package manager.</p> + +<p>To use third party middleware you first need to install it into your app using NPM. For example, to install the <a href="http://expressjs.com/en/resources/middleware/morgan.html">morgan</a> HTTP request logger middleware, you'd do this:</p> + +<pre class="brush: bash"><code>$ npm install morgan +</code></pre> + +<p>You could then call <code>use()</code> on the <em>Express application object</em> to add the middleware to the stack:</p> + +<pre class="brush: js">var express = require('express'); +<strong>var logger = require('morgan');</strong> +var app = express(); +<strong>app.use(logger('dev'));</strong> +...</pre> + +<div class="note"> +<p><strong>Note:</strong> Middleware and routing functions are called in the order that they are declared. For some middleware the order is important (for example if session middleware depends on cookie middleware, then the cookie handler must be added first). It is almost always the case that middleware is called before setting routes, or your route handlers will not have access to functionality added by your middleware.</p> +</div> + +<p>You can write your own middleware functions, and you are likely to have to do so (if only to create error handling code). The <strong>only</strong> difference between a middleware function and a route handler callback is that middleware functions have a third argument <code>next</code>, which middleware functions are expected to call if they are not that which completes the request cycle (when the middleware function is called, this contains the <em>next</em> function that must be called).</p> + +<p>You can add a middleware function to the processing chain with either <code>app.use()</code> or <code>app.add()</code>, depending on whether you want to apply the middleware to all responses or to responses with a particular HTTP verb (<code>GET</code>, <code>POST</code>, etc). You specify routes the same in both cases, though the route is optional when calling <strong>app.use()</strong>.</p> + +<p>The example below shows how you can add the middleware function using both methods, and with/without a route.</p> + +<pre class="brush: js">var express = require('express'); +var app = express(); + +// An example middleware function +var a_middleware_function = function(req, res, <em>next</em>) { + // ... perform some operations + next(); // Call next() so Express will call the next middleware function in the chain. +} + +// Function added with use() for all routes and verbs +app.use(a_middleware_function); + +// Function added with use() for a specific route +app.use('/someroute', a_middleware_function); + +// A middleware function added for a specific HTTP verb and route +app.get('/', a_middleware_function); + +app.listen(3000);</pre> + +<div class="note"> +<p><strong>JavaScript Tip:</strong> Above we declare the middleware function separately and then set it as the callback. In our previous route handler function we declared the callback function when it was used. In JavaScript, either approach is valid.</p> +</div> + +<p>The Express documentation has a lot more excellent documentation about <a href="https://expressjs.com/en/guide/using-middleware.html">using</a> and <a href="http://expressjs.com/en/guide/writing-middleware.html">writing</a> Express middleware.</p> + +<h3 id="Serving_static_files">Serving static files</h3> + +<p>You can use the <a href="http://expressjs.com/en/4x/api.html#express.static">express.static</a> middleware to serve static files, including your images, CSS and JavaScript (<code>static()</code> is the only middleware function that is actually <strong>part</strong> of <em>Express</em>). For example, you would use the line below to serve images, CSS files, and JavaScript files from a directory named '<strong>public'</strong> at the same level as where you call node:</p> + +<pre class="brush: js">app.use(express.static('public')); +</pre> + +<p>Any files in the public directory are served by adding their filename (<em>relative</em> to the base "public" directory) to the base URL. So for example:</p> + +<pre><code>http://localhost:3000/images/dog.jpg +http://localhost:3000/css/style.css +http://localhost:3000/js/app.js +http://localhost:3000/about.html +</code></pre> + +<p>You can call <code>static()</code> multiple times to serve multiple directories. If a file cannot be found by one middleware function then it will simply be passed on to the subsequent middleware (the order that middleware is called is based on your declaration order).</p> + +<pre class="brush: js">app.use(express.static('public')); +app.use(express.static('media')); +</pre> + +<p>You can also create a virtual prefix for your static URLs, rather than having the files added to the base URL. For example, here we <a href="http://expressjs.com/en/4x/api.html#app.use">specify a mount path</a> so that the files are loaded with the prefix "/media":</p> + +<pre class="brush: js">app.use('/media', express.static('public')); +</pre> + +<p>Now, you can load the files that are in the <code>public</code> directory from the <code>/media</code> path prefix.</p> + +<pre><code>http://localhost:3000/media/images/dog.jpg +http://localhost:3000/media/video/cat.mp4 +http://localhost:3000/media/cry.mp3</code> +</pre> + +<p>For more information, see <a href="Serving static files in Express">Serving static files in Express</a>.</p> + +<h3 id="Handling_errors">Handling errors</h3> + +<p>Errors are handled by one or more special middleware functions that have four arguments, instead of the usual three: <code>(err, req, res, next)</code>. For example:</p> + +<pre class="brush: js">app.use(function(err, req, res, next) { + console.error(err.stack); + res.status(500).send('Something broke!'); +}); +</pre> + +<p>These can return any content required, but must be called after all other <code>app.use()</code> and routes calls so that they are the last middleware in the request handling process!</p> + +<p>Express comes with a built-in error handler, which takes care of any remaining errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack. If you pass an error to <code>next()</code> and you do not handle it in an error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace.</p> + +<div class="note"> +<p><strong>Note:</strong> The stack trace is not included in the production environment. To run it in production mode you need to set the the environment variable <code>NODE_ENV</code> to '<code>production'</code>.</p> +</div> + +<div class="note"> +<p><strong>Note:</strong> HTTP404 and other "error" status codes are not treated as errors. If you want to handle these, you can add a middleware function to do so. For more information see the <a href="http://expressjs.com/en/starter/faq.html#how-do-i-handle-404-responses">FAQ</a>.</p> +</div> + +<p>For more information see <a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs).</p> + +<h3 id="Using_databases">Using databases</h3> + +<p><em>Express</em> apps can use any database mechanism supported by <em>Node</em> (<em>Express</em> itself doesn't define any specific additional behaviour/requirements for database management). There are many options, including PostgreSQL, MySQL, Redis, SQLite, MongoDB, etc.</p> + +<p>In order to use these you have to first install the database driver using NPM. For example, to install the driver for the popular NoSQL MongoDB you would use the command:</p> + +<pre class="brush: bash"><code>$ npm install mongodb +</code></pre> + +<p>The database itself can be installed locally or on a cloud server. In your Express code you require the driver, connect to the database, and then perform create, read, update, and delete (CRUD) operations. The example below (from the Express documentation) shows how you can find "mammal" records using MongoDB.</p> + +<pre class="brush: js">//this works with older versions of mongodb version ~ 2.2.33 +var MongoClient = require('mongodb').MongoClient; + +MongoClient.connect('mongodb://localhost:27017/animals', function(err, db) { + if (err) throw err; + + db.collection('mammals').find().toArray(function (err, result) { + if (err) throw err; + + console.log(result); + }); +}); + + +//for mongodb version 3.0 and up +let MongoClient = require('mongodb').MongoClient; +MongoClient.connect('mongodb://localhost:27017/animals', function(err, client){ + if(err) throw err; + + let db = client.db('animals'); + db.collection('mammals').find().toArray(function(err, result){ + if(err) throw err; + console.log(result); + client.close(); + }); +} +</pre> + + + + + + + +<p>Another popular approach is to access your database indirectly, via an Object Relational Mapper ("ORM"). In this approach you define your data as "objects" or "models" and the ORM maps these through to the underlying database format. This approach has the benefit that as a developer you can continue to think in terms of JavaScript objects rather than database semantics, and that there is an obvious place to perform validation and checking of incoming data. We'll talk more about databases in a later article.</p> + +<p>For more information see <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="Rendering_data_(views)">Rendering data (views)</h3> + +<p>Template engines (referred to as "view engines" by <em>Express</em>) allow you to specify the <em>structure</em> of an output document in a template, using placeholders for data that will be filled in when a page is generated. Templates are often used to create HTML, but can also create other types of documents. Express has support for <a href="https://github.com/expressjs/express/wiki#template-engines">a number of template engines</a>, and there is a useful comparison of the more popular engines here: <a href="https://strongloop.com/strongblog/compare-javascript-templates-jade-mustache-dust/">Comparing JavaScript Templating Engines: Jade, Mustache, Dust and More</a>.</p> + +<p>In your application settings code you set the template engine to use and the location where Express should look for templates using the 'views' and 'view engines' settings, as shown below (you will also have to install the package containing your template library too!)</p> + +<pre class="brush: js">var express = require('express'); +var app = express(); + +// Set directory to contain the templates ('views') +app.set('views', path.join(__dirname, 'views')); + +// Set view engine to use, in this case 'some_template_engine_name' +app.set('view engine', 'some_template_engine_name'); +</pre> + +<p>The appearance of the template will depend on what engine you use. Assuming that you have a template file named "index.<template_extension>" that contains placeholders for data variables named 'title' and "message", you would call <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> in a route handler function to create and send the HTML response:</p> + +<pre class="brush: js">app.get('/', function(req, res) { + res.render('index', { title: 'About dogs', message: 'Dogs rock!' }); +});</pre> + +<p>For more information see <a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs).</p> + +<h3 id="File_structure">File structure</h3> + +<p>Express makes no assumptions in terms of structure or what components you use. Routes, views, static files, and other application-specific logic can live in any number of files with any directory structure. While it is perfectly possible to have the whole <em>Express</em> application in one file, typically it makes sense to split your application into files based on function (e.g. account management, blogs, discussion boards) and architectural problem domain (e.g. model, view or controller if you happen to be using an <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC architecture</a>).</p> + +<p>In a later topic we'll use the <em>Express Application Generator</em>, which creates a modular app skeleton that we can easily extend for creating web applications.</p> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p>恭喜,您已完成 Express / Node之旅的第一步!您現在應該了解 Express 和 Node 的主要優點,以及 Express 應用程序的主要部分(路由,中間件,錯誤處理和模板代碼)。您還應該明白,Express 是一個不固執己見的框架,您將這些組件組合在一起的方式以及您使用的函式庫,在很大程度上取決於您!</p> + +<p>當然,Express是一個非常輕量級的 Web 應用程序框架,它的許多好處和潛力來自第三方函式庫和功能。我們將在以下文章中更詳細地介紹這些內容。在下一篇文章中,我們將介紹如何設置 Node 開發環境,以便您可以開始查看一些 Express 代碼。</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://medium.com/@ramsunvtech/manage-multiple-node-versions-e3245d5ede44">Venkat.R - Manage Multiple Node versions</a></li> + <li><a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs)</li> + <li><a href="https://expressjs.com/">Express</a> (home page)</li> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-middleware.html">Using middleware</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/writing-middleware.html">Writing middleware for use in Express apps</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/starter/static-files.html">Serving static files in Express</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs)</li> +</ul> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/mongoose/index.html b/files/zh-tw/learn/server-side/express_nodejs/mongoose/index.html new file mode 100644 index 0000000000..8541c1c37c --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/mongoose/index.html @@ -0,0 +1,792 @@ +--- +title: 'Express 教學 3: 使用資料庫 ( Mongoose)' +slug: Learn/Server-side/Express_Nodejs/mongoose +translation_of: Learn/Server-side/Express_Nodejs/mongoose +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">本文簡短介紹數據庫,以及如何搭配 Node / Express 應用,使用數據庫。接下來會演示我們如何使用 <a href="http://mongoosejs.com/">Mongoose</a>,為本地圖書館提供數據庫存取。本文說明物件要求與模型如何宣告,主要的欄位型態,以及基本驗證。本文也簡短演示一些存取模型數據的主要方法。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置條件:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教學 2: 創建一個骨架網站</a></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>能夠使用Mongoose設計並創造自己的模型。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>圖書館職員會使用本地圖書館網站,存放書本和借書者<font><font>訊息</font></font>。圖書館使用者會用網站瀏覽與尋找書本,看看是否有可以藉閱的書本複本,然後預約或者藉閱。為了有效率地存放與取用<font><font>訊息</font></font>,我們將把它存放到數據庫。</p> + +<p>Express 應用可以使用許多不同的數據庫,並且有好幾種方法可以執行創建 <strong>C</strong>reate、讀取 <strong>R</strong>ead、更新 <strong>U</strong>pdate 和刪除 <strong>D</strong>elete (CRUD) 操作。本教程為一些可用的選項,提供簡短的概覽,然後接著詳細演示該選項的特定運行機制。</p> + +<h3 id="我可以使用什麼數據庫">我可以使用什麼數據庫?</h3> + +<p>Express 應用程序可以使用 Node 支持的任何數據庫(Express 本身不會為數據庫管理,定義任何特定的附加行為/要求)。有許多<a href="https://expressjs.com/en/guide/database-integration.html">流行的選項</a>,包括 PostgreSQL,MySQL,Redis,SQLite 和 MongoDB。</p> + +<p>在選擇數據庫時,您應該考慮時間 - 生產力/學習曲線,性能,易複製/備份,成本,社區支持等等。雖然沒有單一的 “最佳” 數據庫,但幾乎任何流行的解決方案,我們的本地圖書館這樣的中小型網站,應該都可以接受。</p> + +<p>有關選項的更多<font><font>訊息</font></font>,請參閱:<a href="https://expressjs.com/en/guide/database-integration.html">數據庫集成(Express docs)</a>。</p> + +<h3 id="與數據庫互動的最好方式是什麼">與數據庫互動的最好方式是什麼?</h3> + +<p>有兩種與數據庫互動的方法:</p> + +<ul> + <li>使用數據庫的原生查詢語言(例如SQL)</li> + <li>使用對像數據模型(“ODM”)/對象關係模型(“ORM”)。 ODM / ORM將網站的數據表示為JavaScript對象,然後將其映射到底層數據庫。一些ORM綁定到特定的數據庫,而另一些則提供了一個不特定數據庫的後端。</li> +</ul> + +<p>通過使用 SQL 或數據庫支持的任何查詢語言,都可以獲得最佳性能。 ODM通常比較慢,因為它們使用翻譯代碼,在對象和數據庫格式之間進行映射,這可能不會使用最有效的數據庫查詢(尤其是如果ODM支持不同的數據庫後端,並且必須在各個數據庫所支持的功能方面,做出更大的折衷)。</p> + +<p>使用 ORM 的好處是,程序員可以繼續用 JavaScript 對象而不是數據庫語義來思考 — 如果您需要使用不同數據庫(在相同或不同的網站上),那麼尤其如此。他們還提供了一個明顯的地方來執行數據驗證和檢查。</p> + +<div class="note"> +<p><strong>提示:</strong> 使用ODM / ORM通常可以降低開發和維護成本!除非您非常熟悉本地查詢語言,或者性能對您至關重要,否則您應該強烈考慮使用 ODM。</p> +</div> + +<h3 id="我應該使用哪個_ORMODM">我應該使用哪個 ORM/ODM ?</h3> + +<p>NPM 套件管理器站點上,有許多ODM / ORM 解決方案(查看 <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> 和 <a href="https://www.npmjs.com/browse/keyword/orm">orm</a> 標籤的子集合!)。</p> + +<p>在撰寫本文時,受歡迎的幾種解決方案是: </p> + +<ul> + <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>: Mongoose 是一個 <a href="https://www.mongodb.org/">MongoDB</a>對象建模工具,用於在異步環境中工作。</li> + <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>: 它是從基於Express的 Sails web 框架中提取的 ORM。它提供了一個統一的 API,來訪問眾多不同的數據庫,包括Redis,mySQL,LDAP,MongoDB 和 Postgres。</li> + <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>: 提供基於 promise 和傳統回調的接口,提供事務支持,eager/嵌套 eager 關係加載,多態關聯以及對一對一,一對多和多對多關係的支持。適用於PostgreSQL,MySQL 和 SQLite3。</li> + <li><a href="https://www.npmjs.com/package/objection">Objection</a>: 以盡可能簡單的方式,使用 SQL 的全部功能,和底層數據庫引擎(支持SQLite3,Postgres 和 MySQL)。</li> + <li> + <p><a href="https://www.npmjs.com/package/sequelize">Sequelize</a> 是 Node.js 和 io.js 基於 promise 的 ORM。它支持以下數據庫方言,PostgreSQL,MySQL,MariaDB,SQLite 和 MSSQL,並具有可靠的事務支持,關係,唯讀複本等功能。</p> + </li> +</ul> + +<p>一般來說,在選擇解決方案時,您應該考慮提供的功能和 “社區活動” (下載,貢獻,錯誤報告,文檔質量等)。在撰寫本文時,Mongoose 是迄今為止最受歡迎的 ODM,如果您將MongoDB 用於你的數據庫,那麼它是一個合理的選擇。</p> + +<h3 id="在本地圖書館使用_Mongoose_和_MongoDb">在本地圖書館使用 Mongoose 和 MongoDb</h3> + +<p>對於本地圖書館示例(以及本主題的其餘部分),我們將使用 <a href="https://www.npmjs.com/package/mongoose">Mongoose ODM </a>來訪問我們的圖書館數據。 Mongoose 是 <a href="https://www.mongodb.com/what-is-mongodb">MongoDB</a> 的前端,MongoDB 是一個使用面向文檔數據模型的開源 <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> 數據庫。在 MongoDB 數據庫中,“文檔” 的 “集合” ,<a href="https://docs.mongodb.com/manual/core/databases-and-collections/#collections">類似於</a>關係數據庫中 “行” 的 “表”。</p> + +<p>這種 ODM 和數據庫的結合在 Node 社區中非常流行,部分原因是文檔存儲和查詢系統,看起來非常像 JSON,因此對 JavaScript 開發人員來說很熟悉。</p> + +<div class="note"> +<p><strong>提示:</strong> 使用 Mongoose 時,您不需要事先了解 MongoDB,但是如果您已經熟悉 MongoDB,<a href="http://mongoosejs.com/docs/guide.html">Mongoose documentation </a>文檔的一部分會更易於使用和理解。</p> +</div> + +<p>本教程的其餘部分,將介紹如何為 本地圖書館網站示例,定義和訪問Mongoose 模式和模型。</p> + +<h2 id="設計本地圖書館的模型"><font><font>設計本地圖書館的模型</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在您開始編寫模型之前,花幾分鐘的時間思考,我們需要儲存的數據以及不同對象之間的關係。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們知道,我們需要儲存有關書籍的訊息(標題,摘要,作者,種類,國際標準書號),以及我們可能有多個副本可用(具有全域唯一ID,可用狀態等)。我們可能需要存儲有關作者的更多訊息,而不僅僅是他們的名字,並且可能有多個作者,具有相同或相似的名稱。我們希望能夠根據書名,作者,種類和類別對訊息進行分類。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在設計模型時,對於每個“對象”(相關訊息組)都有獨立的模型,是有意義的。在這種情況下,明顯的對像是書籍,書籍實例和作者。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>您可能還希望,使用模型來表示選擇列表選項(例如,選擇的下拉列表),而不是將選項硬編碼到網站本身— 在無法預先知道所有選項,或者可能更改時,更建議使用模型來表示。</font><font>很明顯的,書本類型是這種模型的可能人選(例如科幻小說,法國詩歌等)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>一旦我們決定了我們的模型和字段,我們就需要考慮它們之間的關係。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>考慮到這一點,下面的UML關聯圖,顯示了我們在這種情況下定義的模型(一個框對應一個模型)。</font><font>如上所述,我們創建了以下模型,圖書(本書的通用細節),書本實例(系統中可用圖書的特定實際副本的狀態)和作者。</font><font>我們還決定建立一個種類模型,以便可以動態創建它的值,而不是將下拉選項硬編碼。</font><font>我們已經決定不為書本實例:狀態</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>BookInstance:status</code><font><font>建立模型—我們將硬編碼可接受的值,因為我們不希望這些值發生變化。</font><font>在下圖每個框中,您可以看到模型名稱,字段名稱和類型,以及方法及其返回類型。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下圖還顯示了模型之間的關係,包括它們的多重性。</font><font>多重性是圖中顯示可能存在於關係中的每個模型的數量(最大值和最小值)的數字。</font><font>例如,框之間的連接線,顯示書本</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Book</code><font><font>和種類</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Genre</code><font><font>是相關的。</font><font>靠近書本</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Book</code><font><font>模型的數字,表明一本書必須有零個或多個種類(您想要多少都可以),而種類</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Genre</code><font><font>旁邊一行的數字,表明它可以有零個或多個相關書籍。</font></font></p> + +<div class="note"> +<p><strong>注意</strong>: <font><font>正如我們在下面的</font></font><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Mongoose入門</font></font></strong><font><font>中所討論的那樣,通常只需要在一個模型中定義文檔/模型之間關係的字段(通過在另一個模型中搜索相關的</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>_id</code><font><font>仍然可以找到反向關係)。</font><font>下面我們選擇在書本綱要(Book schema)中定義Book/Genre和Book/Author之間的關係,以及書本實例綱要(BookInstance Schema)中Book/BookInstance之間的關係。</font><font>這種選擇有點武斷—我們同樣可以在其他綱要中擁有該字段。</font></font></p> +</div> + +<p><img alt="Mongoose Library Model with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>:下一節提供了一個基本的入門知識,解釋如何定義和使用模型。</font><font>在您閱讀它時,請想想我們將如何構建上圖中的每個模型。</font></font></p> +</div> + +<h2 id="Mongoose入門"><font><font>Mongoose入門</font></font></h2> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>本節概述如何將Mongoose 連接到MongoDB 數據庫,如何定義模型綱要和模型,以及如何進行基本查詢。</span></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>本入門受到npm上的</font></font><a class="external external-icon" href="https://www.npmjs.com/package/mongoose" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>Mongoose快速入門</font></font></a><font><font>和</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/guide.html" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>Mongoose官方文檔</font></font></a><font><font>的“深度影響”。</font></font></p> +</div> + +<h3 id="安裝Mongoose和MongoDB"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>安裝Mongoose和MongoDB</font></font></span></h3> + +<p><font><font>Mongoose像任何其他依賴項一樣,安裝在您的項目(</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>package.json</font></font></strong><font><font>)中—使用NPM。</font><font>要安裝它,請在項目文件夾中,使用以下命令:</font></font></p> + +<pre class="brush: bash notranslate"><code>npm install mongoose</code> +</pre> + +<p><font><font>安裝Mongoose會添加所有依賴項,包括MongoDB數據庫驅動程序,但它不會安裝MongoDB 。</font><font>如果你想安裝一個MongoDB服務器,那麼你可以</font></font><a class="external external-icon" href="https://www.mongodb.com/download-center" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>從這裡下載</font></font></a><font><font>各種操作系統的安裝程序,並在本地安裝。</font><font>您還可以使用基於雲端的MongoDB實例。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>對於本教程,我們將使用基於mLab雲的數據庫,作為服務</font></font><a class="external external-icon" href="https://mlab.com/plans/pricing/" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>沙箱層</font></font></a><font><font>來提供數據庫。</font><font>這適用於開發,也對於本教程很有意義,因為它使“安裝”與操作系統無關(數據庫即服務,也是您可能會用於生產環境數據庫的一種方法)。</font></font></p> +</div> + +<h3 id="連接到MongoDB"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>連接到MongoDB</font></font></span></h3> + +<p><font><font>Mongoose需要連接到MongoDB數據庫。</font><font>您可以</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>require()</code><font><font>並使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>mongoose.connect()</code><font><font>,以連接到本地託管的數據庫,如下所示。</font></font></p> + +<pre class="brush: js notranslate">//Import the mongoose module +var mongoose = require('mongoose'); + +//Set up default mongoose connection +var mongoDB = 'mongodb://127.0.0.1/my_database'; +mongoose.connect(mongoDB); +// Get Mongoose to use the global promise library +mongoose.Promise = global.Promise; +//Get the default connection +var db = mongoose.connection; + +//Bind connection to error event (to get notification of connection errors) +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p><font><font>您可以使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>mongoose.connection</code><font><font>獲取默認的</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Connection</code><font><font>對象。</font><font>一旦連接,在</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Connection</code><font><font>實例上,將觸發打開事件。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>提示:</font></font></strong><font><font>如果需要創建其他連接,可以使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>mongoose.createConnection()</code><font><font>。</font><font>這與</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>connect()</code><font><font>採用相同形式的數據庫URI(包含主機,數據庫,端口,選項等),並返回</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Connection</code><font><font>對象。</font></font></p> +</div> + +<h3 id="定義並創建模型"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>定義並創建模型</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>模型使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Schema</code><font><font>接口進行定義。</font><font>Schema允許您定義存儲在每個文檔中的字段,及其驗證要求和默認值。</font><font>此外,您可以定義靜態和實例助手方法,以更輕鬆地處理數據類型,以及可以像其他任何字段一樣使用的虛擬屬性,但實際上並不存儲在數據庫中(我們稍後將討論)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>然後,綱要Schemas被</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>mongoose.model()</code><font><font>方法“編譯”為模型。</font><font>擁有模型後,您可以使用它來查找,創建,更新和刪除給定類型的對象。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>每個模型都映射到MongoDB數據庫中的文檔集合。</font><font>這些文檔將包含模型綱要</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Schema</code><font><font>中定義的字段/綱要型態。</font></font></p> +</div> + +<h4 id="定義綱要Schemas" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>定義綱要Schemas</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面的代碼片段,顯示了您可以如何定義一個簡單的綱要。</font><font>首先</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>require()</code><font><font>mongoose,然後使用Schema構造函數,創建一個新的Schema實例,在構造函數的對象參數中,定義其中的各個字段。</font></font></p> + +<pre class="brush: js notranslate">//Require Mongoose +var mongoose = require('mongoose'); + +//Define a schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在上面的例子中,我們只有兩個字段,一個字符串和一個日期。</font><font>在接下來的部分中,我們將展示一些其他的字段類型,驗證和其他方法。</font></font></p> + +<h4 id="創建模型" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>創建模型</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>mongoose.model()</code><font><font>方法從綱要創建模型:</font></font></p> + +<pre class="brush: js notranslate">// Define schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); + +<strong>// Compile model from schema +var SomeModel = mongoose.model('SomeModel', SomeModelSchema );</strong></pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>第一個參數,是將為模型創建的集合的單數名稱(Mongoose將為上面的SomeModel模型,創建數據庫集合),第二個參數,是您要在創建模型時使用的綱要Shema。</span></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>定義模型類後,可以使用它們來創建,更新或刪除記錄,並運行查詢,以獲取記錄的所有記錄,或特定子集。</font><font>我們將在以下“使用模型”部分,向您展示如何執行上述操作,以及當創建視圖時,如何執行此操作。</font></font></p> +</div> + +<h4 id="綱要型態(字段)" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>綱要型態(字段)</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>綱要schema可以有任意數量的字段 — 每個字段代表存儲在MongoDB 文檔中的字段。</font><font>如下的示例綱要,顯示許多常見字段類型及其聲明方式。</font></font></p> + +<pre class="brush: js notranslate">var schema = new Schema( +{ + name: <strong>String</strong>, + binary: <strong>Buffer</strong>, + living: <strong>Boolean</strong>, + updated: { type: <strong>Date</strong>, default: Date.now }, + age: { type: <strong>Number</strong>, min: 18, max: 65, required: true }, + mixed: <strong>Schema.Types.Mixed</strong>, + _someId: <strong>Schema.Types.ObjectId</strong>, + array: <strong>[]</strong>, + ofString: [<strong>String</strong>], // You can also have an array of each of the other types too. + nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } } +})</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>大多數綱要型態</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/schematypes.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>SchemaTypes</font></font></a><font><font>(“type:”之後或字段名稱之後的描述符)都是自解釋的。</font><font>例外情況是:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>ObjectId</code><font><font>:表示數據庫中模型的特定實例。</font><font>例如,一本書可能會使用它來表示其作者對象。</font><font>這實際上將包含指定對象的唯一ID (<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>_id</code><font><font>) 。</font><font>我們可以使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>populate()</code><font><font>方法,在需要時提取相關訊息。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="http://mongoosejs.com/docs/schematypes.html#mixed" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Mixed</font></font></a><font><font><span> </span>:任意綱要型態。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font face="Consolas, Liberation Mono, Courier, monospace"><font><font>[]</font></font></font><font><font><span> </span>:一個數組的項目。</font><font>您可以在這些模型上執行JavaScript數組操作(push,pop,unshift等)。</font><font>上面的例子,顯示了一個沒有指定類型的對像數組,和一個String對像數組,但是你可以有任何類型的對像數組。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該代碼還顯示了聲明一個字段的兩種方式:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>字段名稱和類型作為鍵值對(即是,像上面的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>name</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>binary<span> </span></code><font><font>and<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>living</code><font><font>)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>字段名稱後跟一個定義類型</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>type</code><font><font>的對象,以及該字段的任何其他選項。</font><font>選項包括如下內容:</font></font> + <ul style="font-style: normal !important; margin: 0px; padding: 6px 0px 0px 40px; border: 0px; max-width: 42rem;"> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>默認值。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>內置驗證器(例如最大/最小值)和自定義驗證功能。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>該字段是否為必要</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>是否應將字符串</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>String</code><font><font>字段自動設置為小寫,大寫或修剪(例如</font><font>)</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>{ type:<strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;">String</strong>, lowercase: true, trim: true }</code></li> + </ul> + </li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>有關選項的更多訊息,請參閱</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/schematypes.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>SchemaTypes</font></font></a><font><font>(Mongoose docs)。</font></font></p> + +<h4 id="驗證" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>驗證</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>Mongoose 提供內置和自定義驗證器,以及同步和異步驗證器。</font><font>它允許您在所有情況下,指定可接受的範圍或值,以及驗證失敗的錯誤消息。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>內置的驗證器包括:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>所有</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/schematypes.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>SchemaTypes</font></font></a><font><font>都具有內置的</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>必需</font></font></a><font><font>驗證器。</font><font>這用於指定,是否必須提供該字段才能保存文檔。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema-number-js" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Numbers </font></font></a><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema-number-js" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>數字</font></font></a><font><font>有最小</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>min</font></font></a><font><font>和最大</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>max</font></font></a><font><font>驗證器。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema-string-js" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Strings</font></font></a><font><font>字符串有:</font></font> + <ul style="font-style: normal !important; margin: 0px; padding: 6px 0px 0px 40px; border: 0px; max-width: 42rem;"> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>enum</font></font></a><font><font>枚舉:指定該字段的允許值集合。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>match</font></font></a><font><font><span> </span>:指定字符串必須匹配的正則表達式。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>字符串的最大長度</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>maxlength</font></font></a><font><font>和最小長度</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>minlength</font></font></a></li> + </ul> + </li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面的示例(從Mongoose文檔稍微修改)顯示瞭如何指定一些驗證器類型和錯誤消息:</font></font></p> + +<pre class="brush: js notranslate"><code> + var breakfastSchema = new Schema({ + eggs: { + type: Number, + min: [6, 'Too few eggs'], + max: 12 + required: [true, 'Why no eggs?'] + }, + drink: { + type: String, + enum: ['Coffee', 'Tea', 'Water',] + } + }); +</code></pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>有關字段驗證的完整訊息,請參閱</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/validation.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>驗證</font></font></a><font><font>(Mongoose docs)。</font></font></p> + +<h4 id="虛擬屬性" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>虛擬屬性</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>虛擬屬性是您可以獲取和設置的文檔屬性,但不會持久保存到MongoDB。</font><font>getter 對格式化或組合字段非常有用,而setter 可用於將單個值分解為多個值,以進行存儲。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>文檔中的示例,從名字和姓氏字段構造(並解構)一個全名虛擬屬性,這比每次在模板中使用全名更簡單,更清晰。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>我們將使用庫中的虛擬屬性,來為每個使用路徑和記錄的<code>_id</code>值的模型記錄,定義唯一的URL。</font></font></p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>欲了解更多訊息,請參閱</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/guide.html#virtuals" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>虛擬</font></font></a><font><font>(Mongoose文檔)。</font></font></p> + +<h4 id="方法和查詢幫助" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>方法和查詢幫助</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>綱要schema也可以有</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/guide.html#methods" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>實例方法</font></font></a><font><font>,</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/guide.html#statics" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>靜態方法</font></font></a><font><font>和</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/guide.html#query-helpers" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>查詢助手</font></font></a><font><font>。</font><font>實例和靜態方法很相似,但有明顯的區別,即實例方法與特定記錄相關聯,並且可以訪問當前對象。</font><font>查詢助手允許您擴展mongoose的</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/queries.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>鍊式查詢構建器API</font></font></a><font><font>(例如,除了</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>find()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>findOne()</code><font><font>和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>findById()</code><font><font>方法外,還允許您添加一個“byName”查詢。</font></font></p> + +<h3 id="使用模型"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>使用模型</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>一旦創建了綱要,就可以使用它來創建模型。</font><font>該模型代表數據庫中可以搜索的文檔集合,而模型的實例代表您可以保存和檢索的單個文檔。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們在下面簡要介紹一下。有關更多訊息,請參閱:</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/models.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>模型</font></font></a><font><font>(Mongoose docs)。</font></font></p> + +<h4 id="創建和修改文檔" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>創建和修改文檔</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>要創建記錄,您可以定義模型的實例,然後調用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>save()</code><font><font>。</font><font>下面的例子假設,SomeModel是我們從綱要創建的模型(帶有單一字段“name” )。</font></font></p> + +<pre class="brush: js notranslate"><code>// Create an instance of model SomeModel +var awesome_instance = new </code>SomeModel<code>({ name: 'awesome' }); + +// Save the new model instance, passing a callback +awesome_instance.save(function (err) { + if (err) return handleError(err); + // saved! +}); +</code></pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>創建記錄(以及更新,刪除和查詢)是異步操作— 您提供在操作完成時調用的回調。</font><font>API使用錯誤優先參數約定,因此回調的第一個參數將始終為錯誤值(或null)。</font><font>如果API返回一些結果,則將作為第二個參數提供。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>您還可以使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>create()</code><font><font>,同時定義模型實例,並保存模型實例。</font><font>回調將為第一個參數返回錯誤,為第二個參數返回新創建的模型實例。</font></font></p> + +<pre class="brush: js notranslate">SomeModel<code>.create({ name: 'also_awesome' }, function (err, awesome_instance) { + if (err) return handleError(err); + // saved! +});</code></pre> + +<p><font><font>每個模型都有一個關聯的連接(當您使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>mongoose.model()</code><font><font>時,這將成為默認連接)。</font><font>您創建一個新連接並調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>.model()</code><font><font>,以在另一個數據庫上創建文檔。</font></font></p> + +<p><font><font>您可以使用點語法訪問此新記錄中的字段,並更改值。</font><font>您必須調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>save()</code><font><font>或</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>update()</code><font><font>,將修改的值存回數據庫。</font></font></p> + +<pre class="brush: js notranslate">// Access model field values using dot notation +console.log(<code>awesome_instance.name</code>); //should log '<code>also_awesome</code>' + +// Change record by modifying the fields, then calling save(). +<code>awesome_instance</code>.name="New cool name"; +<code>awesome_instance.save(function (err) { + if (err) return handleError(err); // saved! + });</code> +</pre> + +<h4 id="尋找紀錄" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>尋找紀錄</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>可以使用查詢方法搜索記錄,將查詢條件指定為JSON 文檔。</font><font>下面的代碼片段,顯示瞭如何在數據庫中,找到所有參加網球運動的運動員,只返回運動員姓名和年齡的字段。</font><font>這裡我們只指定一個匹配的字段(運動 sport),但您可以添加更多條件,指定正則表達式標準,或完全刪除條件以返回所有運動員。</font></font></p> + +<pre class="brush: js notranslate"><code>var Athlete = mongoose.model('Athlete', yourSchema); + +// find all athletes who play tennis, selecting the 'name' and 'age' fields +Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) { + if (err) return handleError(err); + // 'athletes' contains the list of athletes that match the criteria. +})</code></pre> + +<p><font>如果您指定回調,如上所示,查詢將立即執行。</font><font>搜索完成後將調用回調。</font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font><span> </span>Mongoose中的所有回調,都使用此回調模式</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>callback(error, result)</code><font><font>。</font><font>如果執行查詢時發生錯誤,錯誤參數</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>error</code><font><font>將包含錯誤文檔,並且結果</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>result</code><font><font>將為null。</font><font>如果查詢成功,則</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>error</code><font><font>參數將為null,並且結果</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>result<span> </span></code><font><font>將被填充到查詢結果。</font></font></p> +</div> + +<p><font><font>如果您未指定回調,則API將返回</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query-js" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>Query</font></font></a><font><font>類型的變量。</font><font>您可以使用此查詢對象來構建查詢,然後稍後使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>exec()</code><font><font>方法執行(使用回調)。</font></font></p> + +<pre class="brush: js notranslate"><code>// find all athletes that play tennis +var query = Athlete.find({ 'sport': 'Tennis' }); + +// selecting the 'name' and 'age' fields +query.select('name age'); + +// limit our results to 5 items +query.limit(5); + +// sort by age +query.sort({ age: -1 }); + +// execute the query at a later time +query.exec(function (err, athletes) { + if (err) return handleError(err); + // athletes contains an ordered list of 5 athletes who play Tennis +})</code></pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>上面我們在</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>find()</code><font><font>方法中,定義了查詢條件。</font><font>我們也可以使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>where()</code><font><font>函數來執行此操作,並且我們可以使用點運算符( . )將查詢的所有部分鏈接在一起,而不是分別添加它們。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面的代碼片段,與我們上面的查詢相同,並有年齡的附加條件。</font></font></p> + +<pre class="notranslate"><code>Athlete. + find(). + where('sport').equals('Tennis'). + where('age').gt(17).lt(50). //Additional where query + limit(5). + sort({ age: -1 }). + select('name age'). + exec(callback); // where callback is the name of our callback function.</code></pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query_Query-find" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>find()</font></font></a><font><font><span> </span> 方法獲取所有匹配的記錄,但通常你只想獲得一個匹配。</font><font>以下方法可以查詢單個記錄:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.findById" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findById()</a></code><font><font>:用指定的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>id<span> </span></code><font><font>查找文檔(每個文檔都有一個唯一的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>id</code><font><font>)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query_Query-findOne" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findOne()</a></code><font><font>: 查找與指定條件匹配的單個文檔。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findByIdAndRemove()</a></code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findByIdAndUpdate()</a></code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove" rel="noopener" style="font-style: normal !important; text-decoration: underline; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findOneAndRemove()</a></code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">findOneAndUpdate()</a></code><font><font>:通過</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>id<span> </span></code><font><font>或條件查找單個文檔,並更新或刪除它。</font><font>這些是用於更新和刪除記錄的有用便利功能。</font></font></li> +</ul> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>還有一個</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">count()</a></code><font><font>方法,您可以使用它來獲取與條件匹配的項目數。</font><font>如果您想要在不實際提取記錄的情況下執行計數,這非常有用。</font></font></p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>查詢可以做更多的事情。有關更多訊息,請參閱:</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/queries.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>查詢</font></font></a><font><font>(Mongoose文檔)。</font></font></p> + +<h4 id="運用相關文檔—_population方法" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>運用相關文檔— population方法</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>您可以使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>ObjectId</code><font><font>綱要字段,從一個文檔/模型實例,創建一對一引用,或者使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>ObjectIds</code><font><font>數組,從一個文檔創建一對多的引用。</font><font>該字段存儲相關模型的ID。</font><font>如果需要關聯文檔的實際內容,可以在查詢中使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#query_Query-populate" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">populate()</a></code><font><font>方法,將id替換為實際數據。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>例如,以下綱要定義作者和故事。</font><font>每個作者可以有多個故事,我們將其表示為一個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>ObjectId</code><font><font>數組。</font><font>每個故事可以有一個作者。</font><font>綱要從“ref”(以粗體突出顯示)得知,可以分配給該字段的模型。</font></font></p> + +<pre class="brush: js notranslate"><code>var mongoose = require('mongoose') + , Schema = mongoose.Schema + +var authorSchema = Schema({ + name : String, + stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }] +}); + +var storySchema = Schema({ + author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' }, + title : String +}); + +var Story = mongoose.model('Story', storySchema); +var Author = mongoose.model('Author', authorSchema);</code></pre> + +<p><font><font>我們可以通過分配</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>_id</code><font><font>值,來保存對相關文檔的引用。</font><font>下面我們創建一個作者,然後創建一個故事,並將作者ID分配給我們的故事作者字段。</font></font></p> + +<pre class="brush: js notranslate"><code>var bob = new Author({ name: 'Bob Smith' }); + +bob.save(function (err) { + if (err) return handleError(err); + + //Bob now exists, so lets create a story + var story = new Story({ + title: "Bob goes sledding", + author: bob._id // assign the _id from the our author Bob. This ID is created by default! + }); + + story.save(function (err) { + if (err) return handleError(err); + // Bob now has his story + }); +});</code></pre> + +<p><font><font>我們的故事文檔,現在有作者文檔ID引用的作者。為了在我們的故事結果中,獲取作者訊息,我們使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>populate()</code><font><font>,如下所示。</font></font></p> + +<pre class="brush: js notranslate"><code>Story +.findOne({ title: 'Bob goes sledding' }) +.populate('author') //This populates the author id with actual author information! +.exec(function (err, story) { + if (err) return handleError(err); + console.log('The author is %s', story.author.name); + // prints "The author is Bob Smith" +});</code></pre> + +<div class="note"> +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><strong style="border: 0px; font-style: normal; font-weight: 700; margin: 0px; padding: 0px;"><font><font>注意:</font></font></strong><font><font>敏銳的讀者會注意到,我們在故事中添加了作者,但我們沒有做任何事情,來將我們的故事添加到作者的故事</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>stories</code><font><font>數組中。</font><font>那麼我們怎樣才能得到特定作者的所有故事?</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><font><font>一種方法,是將作者添加到故事數組中,但這會導致我們需要在兩個地方,維護與作者和故事有關的訊息。更好的方法是獲取作者的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>_id</code><font><font>,然後使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>find()</code><font><font>,在所有故事的作者字段中搜索此內容。</font></font></p> + +<pre class="brush: js notranslate"><code>Story +.find({ author : bob._id }) +.exec(function (err, stories) { + if (err) return handleError(err); + // returns all stories that have Bob's id as their author. +});</code> +</pre> +</div> + +<p><font><font>這幾乎是您在本教程中,使用相關項目時,需要了解的所有內容。有關更多詳細訊息,請參閱</font></font><a class="external external-icon" href="http://mongoosejs.com/docs/populate.html" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>Population</font></font></a><font><font>(Mongoose docs)。</font></font></p> + +<h3 id="一個檔案對應一個綱要模型"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>一個檔案對應一個綱要/模型</font></font></span></h3> + +<p><font>雖然您可以使用任何喜歡的文件結構創建綱要和模型,但我們強烈建議在每個模型模塊(文件)中,定義每個模型綱要,導出方法以創建模型。</font><font>如下所示:</font></p> + +<pre class="brush: js notranslate"><code>// File: ./models/somemodel.js + +//Require Mongoose +var mongoose = require('mongoose'); + +//Define a schema +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string : String, + a_date : Date, +}); + +<strong>//Export function to create "SomeModel" model class +module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre> + +<p><font>然後,您可以在其他文件中,立即要求並使用該模型。</font><font>下面我們展示如何使用它,來獲取模型的所有實例。</font></p> + +<pre class="brush: js notranslate"><code>//Create a SomeModel model just by requiring the module +var SomeModel = require('../models/somemodel') + +// Use the SomeModel object (model) to find all SomeModel records +SomeModel.find(callback_function);</code></pre> + +<h2 id="架設MongoDB數據庫"><font><font>架設MongoDB數據庫</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>現在我們了解了Mongoose能做什麼,以及我們想如何設計我們的模型,現在該開始在LocalLibrary網站上工作了。我們想要做的第一件事,就是設置一個MongoDb數據庫,我們可以使用它來儲存我們的圖書館數據。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本教程,我們將使用</font></font><a class="external external-icon" href="https://mlab.com/welcome/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>mLab</font></font></a><font><font>免費的雲託管的“<span> </span></font></font><a class="external external-icon" href="https://mlab.com/plans/pricing/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>沙盒</font></font></a><font><font><span> </span>”數據庫。</font><font>這個數據庫層不適合生產環境的網站,因為它沒有冗餘設計,但它對於開發和原型設計來說非常有用。我們在這裡使用它,是因為它免費且易於設置,並且因為作為數據庫服務供應商來說,mLab是流行的數據庫選擇之一,您可能會合理選擇您的生產環境數據庫(撰寫本文時,其他流行的選擇包括</font></font><a class="external external-icon" href="https://www.compose.com/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Compose</font></font></a><font><font>、</font></font><a class="external external-icon" href="https://scalegrid.io/pricing.html" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>ScaleGrid</font></font></a><font><font>和</font></font><a class="external external-icon" href="https://www.mongodb.com/cloud/atlas" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>MongoDB Atlas</font></font></a><font><font>)。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>如果您願意,可以下載並安裝</font></font><a class="external external-icon" href="https://www.mongodb.com/download-center" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>與系統相對應的二進製文件</font></font></a><font><font>,在本地設置MongoDb數據庫。</font><font>除了您在連接時指定的數據庫URL之外,本文中的其餘指令將很類似。</font></font></p> +</div> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>您首先需要</font></font><a class="external external-icon" href="https://mlab.com/signup/" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>使用mLab創建一個賬戶</font></font></a><font><font>(這是免費的,只需要輸入基本聯繫訊息,並確認其服務條款)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>登錄後,您將進入</font></font><font><font><a class="external external-icon" href="https://mlab.com/home" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">mLab主</a>畫面</font></font><font><font>:</font></font></p> + +<ol> + <li><font><font>單擊</font></font><em><font><font>MongoDB Deployments</font></font></em><font><font>部分中的</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Create New。</font></font></strong><img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li> + <li><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>這將打開“雲提供商”Cloud Provider 選擇畫面。</span><br> + <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"><br> + + <ul> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>從“計劃類型”Plan Type 部分中,選擇“SANDBOX(免費)”計劃。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>從“雲提供商”<span> </span></font></font><em><font><font>Cloud Provider</font></font></em><font><font>部分,選擇任意提供商。</font><font>不同的提供商,提供不同的地區(顯示在選定的計劃類型下面)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>單擊“繼續”<span> </span></font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Continue</font></font></strong><font><font>按鈕。</font></font></li> + </ul> + </li> + <li><font><font>這將打開“選擇區域”<span> </span></font></font><em><font><font>Select Region </font></font></em><font><font>畫面。</font></font> + <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p> + + <ul> + <li> + <p><font><font>選擇離您最近的地區,然後選擇繼續</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Continue</font></font></strong><font><font><span> </span>.</font></font></p> + </li> + </ul> + </li> + <li> + <p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>這將打開 Final Details </span><font><font>畫面</font></font><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>。</span><br> + <img alt="New deployment database name" src="https://mdn.mozillademos.org/files/15663/mLab_new_deployment_final_details.png" style="height: 569px; width: 1293px;"></p> + + <ul> + <li> + <p><font><font>輸入新數據庫的名稱</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>local_library</code><font><font>,然後選擇繼續</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Continue</font></font></strong><font><font>。</font></font></p> + </li> + </ul> + </li> + <li> + <p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>這將打開訂單確認畫面。</span><br> + <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p> + + <ul> + <li> + <p><font><font>單擊“提交訂單”<span> </span></font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Submit Order</font></font></strong><font><font>以創建數據庫。</font></font></p> + </li> + </ul> + </li> + <li> + <p><font>您將返回到主</font><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>畫面</span><font>。單擊剛剛創建的新數據庫,以打開其詳細<font>訊息</font></font><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>畫面</span><font>。正如你所看到的,數據庫沒有集合(數據)。</font><br> + <img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br> + <font>您需要用來訪問數據庫的URL,顯示在上面的表單中(如上圖所示)。</font><font>為了使用它,您需要創建一個可以在URL中指定的數據庫用戶。</font></p> + </li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>單擊用戶</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Users</font></font></strong><font><font>選項卡,並選擇添加數據庫用戶按鈕</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Add database user</font></font></strong><font><font>。</font></font></li> + <li><font><font>輸入用戶名和密碼(兩次),然後按創建</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Create</font></font></strong><font><font>。</font><font>不要選擇只讀</font></font><em><font><font>read-only</font></font></em><font><font>。</font></font><br> + <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li> +</ol> + +<p><font><font>您現在已經創建了數據庫,並且有一個可以用來訪問它的URL(帶有用戶名和密碼)。</font><font>這看起來像是這樣的:</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library</code><font><font>.</font></font></p> + +<h2 id="安裝_Mongoose" style='font-style: normal; line-height: 1.2; margin: 103px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 2.5rem; position: relative; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>安裝 Mongoose</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開命令提示符,並到您創建</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>本地圖書館骨架網站</font></font></a><font><font>的目錄。</font><font>輸入以下命令,安裝Mongoose(及其依賴項),並將其添加到您的</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>package.json</font></font></strong><font><font>文件中,除非您在閱讀上述</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>Mongoose入門</font></font></strong><font><font>時,已經這樣做了。</font></font></p> + +<pre class="brush: bash notranslate">npm install mongoose +</pre> + +<h2 id="連接到_MongoDB" style='font-style: normal; line-height: 1.2; margin: 103px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 2.5rem; position: relative; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>連接到 MongoDB</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/app.js</font></font></strong><font><font>(位於項目的根目錄),並在宣告Express應用程序對象的位置(在</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>var app = express();</code><font><font>之後)複製以下文本。</font><font>將數據庫url字符串('insert_your_database_url_here')替換為表示您自己的數據庫的位置URL(即是使用</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>來自上面mLab</font></font></strong><font><font>的訊息)。</font></font></p> + +<pre class="brush: js notranslate">//Set up mongoose connection +var mongoose = require('mongoose'); +var mongoDB = '<em>insert_your_database_url_here</em>'; +mongoose.connect(mongoDB); +mongoose.Promise = global.Promise; +var db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p><font><font>正如上面的</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Mongoose入門</font></font></strong><font><font>中所討論的,此代碼創建了與數據庫的默認連接,並綁定到錯誤事件(以便將錯誤打印到控制台)。</font></font></p> + +<h2 id="定義本地圖書館綱要" style='font-style: normal; line-height: 1.2; margin: 103px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 2.5rem; position: relative; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>定義本地圖書館綱要</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>如上所述,我們將為每個模型定義一個單獨的模塊。</font><font>首先在項目根目錄(</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/models</font></font></strong><font><font>)中,為我們的模型創建一個文件夾,然後為每個模型創建單獨的文件:</font></font></p> + +<pre class="notranslate">/express-locallibrary-tutorial //the project root + <strong>/models</strong> + <strong>author.js</strong> + <strong>book.js</strong> + <strong>bookinstance.js</strong> + <strong>genre.js</strong> +</pre> + +<h3 id="作者模型"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>作者模型</font></font></span></h3> + +<p><font><font>複製下面顯示的</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Author</code><font><font>作者綱要代碼,並將其粘貼到</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>./models/author.js</font></font></strong><font><font>文件中。</font><font>該綱要定義了一個作者,具有</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>String</code><font><font>SchemaTypes的第一個名稱和家族名稱,這是必需的,最多有100個字符,</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Date</code><font><font>字段為出生和死亡日期。</font></font></p> + +<pre class="brush: js notranslate">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var AuthorSchema = new Schema( + { + first_name: {type: String, required: true, max: 100}, + family_name: {type: String, required: true, max: 100}, + date_of_birth: {type: Date}, + date_of_death: {type: Date}, + } +); + +<strong>// Virtual for author's full name +AuthorSchema +.virtual('name') +.get(function () { + return this.family_name + ', ' + this.first_name; +});</strong> + +// Virtual for author's URL +AuthorSchema +.virtual('url') +.get(function () { + return '/catalog/author/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Author', AuthorSchema); + +</pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>我們還為AuthorSchema,聲明了一個名為“url”的虛擬屬性,它返回獲取模型的特定實例所需的絕對URL — 每當我們需要獲取指向特定作者的鏈接時,我們將在模板中使用該屬性。</span></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>在綱要中聲明我們的URL是虛擬的,這是一個好主意,因為一個項目的URL只需要在一個地方更改。</font><font>此時,使用此URL的鏈接將不起作用,因為我們還沒有任何路由,可以處理個別模型實例的代碼。</font><font>我們將在後面的文章中介紹這些內容!</font></font></p> +</div> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>在模塊的最後,我們導出了模型。</span></p> + +<h3 class="highlight-spanned" id="書本模型" style='font-style: normal; line-height: 1.25; margin: 20px 0px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.5rem; background-color: rgb(255, 255, 255); color: rgb(51, 51, 51); font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>書本模型</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>複製下面顯示的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Book</code><font><font>綱要代碼,並將其粘貼到</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>./models/book.js</font></font></strong><font><font>文件中。</font><font>其中大部分與作者模型相似—我們已經聲明了一個具有多個字符串字段的綱要,以及一個虛擬屬性,用於獲取特定書籍記錄的URL,並且我們已經導出了模型。</font></font></p> + +<pre class="brush: js notranslate">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookSchema = new Schema( + { + title: {type: String, required: true}, + <strong> author: {type: Schema.Types.ObjectId, ref: 'Author', required: true},</strong> + summary: {type: String, required: true}, + isbn: {type: String, required: true}, + <strong> genre: [{type: Schema.Types.ObjectId, ref: 'Genre'}]</strong> + } +); + +// Virtual for book's URL +BookSchema +.virtual('url') +.get(function () { + return '/catalog/book/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Book', BookSchema); +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>這裡的主要區別,是我們已經創建了兩個對其他模型的引用:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>作者是對單個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Author</code><font><font>作者模型對象的引用,並且是必要的。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>種類是對</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Genre</code><font><font>種類模型對像數組的引用。</font><font>我們還沒有宣告這個對象!</font></font></li> +</ul> + +<h3 id="書本實例模型"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>書本實例模型</font></font></span></h3> + +<p><font><font>最後,複製下面顯示的</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>BookInstance</code><font><font>綱要代碼,並將其粘貼到</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>./models/bookinstance.js</font></font></strong><font><font>文件中。</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>BookInstance</code><font><font>表示某人可能藉閱的書籍的特定副本,並包含有關該副本是否可用,或預期返回日期的訊息,“印記”或版本詳細訊息。</font></font></p> + +<pre class="brush: js notranslate">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookInstanceSchema = new Schema( + { + book: { type: Schema.Types.ObjectId, ref: 'Book', required: true }, //reference to the associated book + imprint: {type: String, required: true}, + status: {type: String, required: true, <strong>enum: ['Available', 'Maintenance', 'Loaned', 'Reserved']</strong>, <strong>default: 'Maintenance'</strong>}, + due_back: {type: Date, <strong>default: Date.now</strong>} + } +); + +// Virtual for bookinstance's URL +BookInstanceSchema +.virtual('url') +.get(function () { + return '/catalog/bookinstance/' + this._id; +}); + +//Export model +module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們在這裡展示的新東西,是字段選項:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>枚舉</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>enum</code><font><font>:這允許我們設置字符串的允許值。</font><font>在這種情況下,我們用它來指定我們書籍的可用性狀態(使用枚舉,意味著我們可以防止錯誤拼寫和任意值,成為我們的狀態)</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>默認值</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>default</code><font><font>:我們使用默認值,將新創 建的書本實例的默認狀態,設置為維護,並將默認的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>due_back</code><font><font>日期,設置為現在</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>now</code><font><font>(請注意在設置日期時,如何調用Date函數!)</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>其他所有內容,大夥應該在前面教程裡邊已經熟悉了。</font></font></p> + +<h3 id="種類模型-自我挑戰!"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>種類模型-自我挑戰!</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開你的</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>./models/genre.js</font></font></strong><font><font>文件,並創建一個存儲類型的綱要(書本的類別,例如它是小說還是非小說,浪漫史或軍事歷史等)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該定義將與其他模型非常相似:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>該模型應該有一個名為</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>name</code><font><font>的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>String</code><font><font>SchemaType ,來描述種類。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>這個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>name</code><font><font>字段應該是必要的,並且有3到100個字符。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>為類型的URL聲明虛擬,名為</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>url</code><font><font>。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>導出模型。</font></font></li> +</ul> + +<h2 id="測試—創建一些項目"><font><font>測試—創建一些項目</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>就是這樣。</font><font>我們現在已經為該網站建立了所有模型!</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>為了測試這些模型(並創建一些示例書籍,和其他項目以便於我們在後面文章使用),現在我們將運行一個獨立的腳本來創建每種類型的項目:</font></font></p> + +<ol> + <li><font><font>在express-locallibrary-tutorial目錄下(與</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>package.json</code><font><font>處於同一級別),下載(或以其他方式創建)文件</font></font><a class="external external-icon" href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>populatedb.js</font></font></a><font><font>。</font></font> + + <div class="note"> + <p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>您不需要知道</font></font><a class="external external-icon" href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>populatedb.js</font></font></a><font><font>的工作原理;它只是將示例數據添加到數據庫中。</font></font></p> + </div> + </li> + <li><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>在項目根目錄中,輸入以下命令,以安裝腳本所需的異步模塊(我們將在後面的教程中討論這一點)</span> + <pre class="brush: bash notranslate">npm install async</pre> + </li> + <li><font><font>在命令提示符下,使用node運行此腳本,傳遞MongoDB數據庫的URL(與之前在</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>app.js</code><font><font>中替換insert_your_database_url_here佔位符的那個相同):</font></font> + <pre class="brush: bash notranslate">node populatedb <your mongodb url></pre> + </li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>該腳本應一路運行至完成,並在終端中創建它們時顯示各項目。</font></font></li> +</ol> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>提示:</font></font></strong><font><font>至</font></font><a class="external external-icon" href="https://mlab.com/home" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>mLab</font></font></a><font><font>上的數據庫。</font><font>您現在應該可以深入到書本籍,作者,種類和書本實例的各個集合中,並查看單個文檔。</font></font></p> +</div> + +<h2 id="總結"><font><font>總結</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本文中我們學到了一點數據庫和Node/Express的ORMs,更多的是關於如何定義Mongoose綱要與模型。</font><font>然後我們使用這些知識,為本地圖書館網站設計並實作出書本</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Book</code><font><font>,書本實例</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>BookInstance</code><font><font>,作者</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Author</code><font><font>和種類</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Genre</code><font><font>模型。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>最後,我們創建一些實例,以測試模型(使用獨立運作的命令稿)。</font><font>下一篇文章,我們將關注於如何創建一些網頁,以呈現這些物件。</font></font></p> + +<h2 id="參閱">參閱</h2> + +<ul> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="http://mongoosejs.com/">Mongoose website</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose Guide</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/validation.html">Validation</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html">Schema Types</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/models.html">Models</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/queries.html">Queries</a> (Mongoose docs)</li> + <li><a href="http://mongoosejs.com/docs/populate.html">Population</a> (Mongoose docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本教程連結"><font><font>本教程連結</font></font></h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/routes/index.html b/files/zh-tw/learn/server-side/express_nodejs/routes/index.html new file mode 100644 index 0000000000..f4549ec598 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/routes/index.html @@ -0,0 +1,646 @@ +--- +title: 'Express 教學 4: 路由與控制器' +slug: Learn/Server-side/Express_Nodejs/routes +translation_of: Learn/Server-side/Express_Nodejs/routes +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">在本教程中,我們將為最終在 本地圖書館 網站中需要的所有資源端點,搭配 "空殼" 處理函式來配置路由 (URL handling code) 。完成後,我們的路由處理源碼將會有模組化結構,在接下來的文章中,我們可以用真實的處理函式加以擴充。我們也會對如何使用Express 創建模組化路由,有更好的理解。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先備知識:</th> + <td>閱讀 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介紹</a>。 完成先前教學主題 (包含 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教學 3: 使用資料庫 (Mongoose)</a>).</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td><font>理解如何創建簡易路由配置。</font><font>我們所有的URL端點。</font></td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>上一篇教程文章</font></font></a><font><font>中,我們定義了Mongoose模型,以與數據庫互動,並使用(獨立)腳本創建一些初始庫記錄。</font><font>現在我們可以編寫代碼,向用戶展示這些信息。</font><font>我們需要做的第一件事,是確定我們希望能夠在頁面中顯示哪些信息,然後定義適當的URL,以返回這些資源。</font><font>然後我們將需要創建路由(URL處理程序)和視圖(模板)來顯示這些頁面。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下圖是作為處理HTTP請求/響應時,需要實現的主要數據流和事項的提醒。</font><font>除了視圖和路線之外,圖表還顯示“控制器” — 實際處理請求的函數,那些與路由請求分開的代碼。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>由於我們已經創建了模型,我們需要創建的主要內容是:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>“路由”將支持的請求(以及請求URL中編碼的任何信息)轉發到適當的控制器功能。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>控制器用於從模型中獲取請求的數據,創建一個顯示數據的HTML頁面,並將其返回給用戶,以在瀏覽器中查看。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>視圖(模板)則由控制器用來呈現數據。</font></font></li> +</ul> + +<p> </p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14456/MVC%20Express.png" style="height: 460px; width: 800px;"></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>最終,我們可能會有頁面顯示書籍,流派,作者和書籍的列表和詳細信息,以及用於創建,更新和刪除記錄的頁面。</font><font>對一篇文章來說,這是很多的內容。</font><font>因此,本文的大部分內容,都將集中在設置我們的路由和控制器,以返回“虛擬”內容。</font><font>我們將在後續文章中,擴展控制器方法,以使用模型數據。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面的第一部分,提供了關於如何使用</font></font><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#router" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;"><font><font>Express Router</font></font></a><font><font>中間件的簡要“入門”。</font><font>當我們設置LocalLibrary路由時,我們將在後面的章節中使用這些知識。</font></font></p> + +<h2 id="路由入門" style='font-style: normal; line-height: 1.2; margin: 103px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 2.5rem; position: relative; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由入門</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由是Express代碼的一部分,它將HTTP動詞(</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>GET</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>POST</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>PUT</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>DELETE</code><font><font>等),URL路徑/模式和被調用來處理該模式的函數,相關聯起來。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>有幾種方法可以創建路線。</font><font>本教程將使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><a class="external external-icon" href="http://expressjs.com/en/guide/routing.html#express-router" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">express.Router</a></code><font><font>中間件,因為它允許我們將站點的特定部分的路由處理程序組合在一起,並使用通用的路由前綴訪問它們。</font><font>我們會將所有與圖書館有關的路由,保存在“目錄”模塊中,如果我們添加路由來處理用戶帳戶或其他功能,我們可以將它們分開保存。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>我們在</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Creating_route_handlers" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212);'><font><font>Express簡介>創建路由處理程序</font></font></a><font><font>中,簡要討論了Express應用程序路由。</font><font>除了為模塊化提供更好的支持之外(如下面第一小節所述),使用Router非常類似於直接在Express應用程序對像上定義路由。</font></font></p> +</div> + +<p><font><font>本節的其餘部分,概述瞭如何使用路由器</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Router</code><font><font>來定義路由。</font></font></p> + +<h3 id="定義和使用單獨的路由模塊"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>定義和使用單獨的路由模塊</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面的代碼提供了一個具體示例,說明我們如何創建路由模塊,然後在Express應用程序中使用它。</font><font>首先,我們在一個名為</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>wiki.js</font></font></strong><font><font>的模塊中創建一個wiki的路由。</font><font>代碼首先導入Express應用程序對象,使用它獲取一個</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Router</code><font><font>對象,然後使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>get()</code><font><font>方法向其添加一對路由。</font><font>所有模塊的最後一個導出路由器</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Router</code><font><font>對象。</font></font></p> + +<pre class="brush: js"><code>// wiki.js - Wiki route module. + +var express = require('express'); +var router = express.Router(); + +// Home page route. +router.get('/', function (req, res) { + res.send('Wiki home page'); +}) + +// About page route. +router.get('/about', function (req, res) { + res.send('About this wiki'); +}) + +module.exports = router;</code> + +</pre> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>上面我們直接在路由器函數中定義路由處理程序回調。</font><font>在LocalLibrary中,我們將在一個單獨的控制器模塊中,定義這些回調。</font></font></p> +</div> + +<p><font><font>要在主應用程序文件中使用路由器模塊,我們首先</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>require()</code><font><font>路由模塊(</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>wiki.js</font></font></strong><font><font>)。</font><font>然後,我們在Express應用程序上調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>use()</code><font><font>,將路由器添加到中間件處理路徑,並指定一個'wiki'的URL路徑。</font></font></p> + +<pre class="brush: js"><code>var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</code></pre> + +<p><font><font>然後可以從</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/wiki/</code><font><font>和</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/wiki/about/</code><font><font>,訪問我們的wiki路由模塊中定義的兩個路由。</font></font></p> + +<h3 id="路由函數"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>路由函數</font></font></span></h3> + +<p><font><font>我們上面的模塊,定義了幾個典型的路由功能。</font><font>使用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>Router.get()</code><font><font>方法定義“about”路由(在下面),該方法僅響應HTTP GET請求。</font><font>此方法的第一個參數是URL路徑,而第二個參數是一個回調函數,如果收到帶有路徑的HTTP GET請求,將會調用該函數。</font></font></p> + +<pre class="brush: js"><code>router.get('/about', function (req, res) { + res.send('About this wiki'); +})</code> +</pre> + +<p><font><font>回調函數接受三個參數(通常如下所示命名:</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>req</code><font><font>,<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>res</code><font><font>,<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>next</code><font><font>),它將包含HTTP請求對象,HTTP響應,以及中間件鏈中的下一個函數。</font></font></p> + +<div class="note"> +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><strong style="border: 0px; font-style: normal; font-weight: 700; margin: 0px; padding: 0px;"><font><font>注意:</font></font></strong><font><font>路由器功能是</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>Express中間件</font></font></a><font><font>,這意味著它們必須完成(響應)請求或調用鏈中的下一個功能</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>next</code><font><font>。</font><font>在上面的例子中,我們使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>send()</code><font><font>完成了請求,所以下一個參數</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem;'>next</code><font><font>沒有被使用(我們選擇不指定它)。</font></font></p> + +<p style='font-style: normal; margin: 0px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><font><font>上面的路由器函數只需要一次回調,但您可以根據需要指定任意數量的回調參數,或一組回調函數。</font><font>每個函數都是中間件鏈的一部分,並且將按照添加到鏈中的順序調用(除非前面的函數完成請求)。</font></font></p> + +<p> </p> +</div> + +<p><font><font>這裡的回調函數,在響應中調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><a class="external external-icon" href="https://expressjs.com/en/4x/api.html#res.send" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">send()</a></code><font><font>,當我們收到帶有路徑('<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/about'</code><font><font>)的GET請求時,返回字符串“About this wiki”。</font><font>有</font></font><a class="external external-icon" href="https://expressjs.com/en/guide/routing.html#response-methods" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>許多其他響應方法</font></font></a><font><font>,可以結束請求/響應週期。</font><font>例如,您可以調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><a class="external external-icon" href="https://expressjs.com/en/4x/api.html#res.json" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">res.json()</a></code><font><font>,來發送JSON響應,或調用</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: medium; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'><a class="external external-icon" href="https://expressjs.com/en/4x/api.html#res.sendFile" rel="noopener" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line;">res.sendFile()</a></code><font><font>來發送文件。</font><font>構建庫時,我們最常使用的響應方法是</font></font><a class="external external-icon" href="https://expressjs.com/en/4x/api.html#res.render" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>render()</font></font></a><font><font>,它使用模板和數據創建並返回HTML文件—我們將在後面的文章中,進一步討論這個問題!</font></font></p> + +<h3 id="HTTP_動詞"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font><span> </span>HTTP 動詞</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>上面的示例路由使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Router.get()</code><font><font>方法,響應具有特定路徑的HTTP GET請求。</font><font>路由器</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Router</code><font><font>還為所有其他HTTP動詞提供路由方法,這些方法多數以完全相同的方式使用:</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>post()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>put()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>delete()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>options()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>trace()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>copy()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>lock()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>mkcol()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>move()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>purge()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>propfind()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>proppatch()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>unlock()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>report()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>mkactivity()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>checkout()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>merge()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>m-</code><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>search()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>notify()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>subscribe()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>unsubscribe()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>patch()</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>search()</code><font><font>,和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>connect()</code><font><font>。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>例如,下面的代碼就像上一個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/about</code><font><font>路由一樣,但只響應HTTP POST請求。</font></font></p> + +<pre class="brush: js"><code>router.post('/about', function (req, res) { + res.send('About this wiki'); +})</code></pre> + +<h3 id="路由路徑"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>路由路徑</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由路徑定義可以進行請求的端點。</font><font>我們到目前為止看到的例子,都是字符串,並且完全按照字符串的寫法使用:'/','/ about','/ book','/any-random.path'。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由路徑也可以是字符串模式。</font><font>字符串模式使用正則表達式語法的子集,來定義將匹配的端點模式。</font><font>下面列出了子集(請注意,連字符(</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>-</code><font><font>)和點(</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>.</code><font><font>)由字符串路徑字面解釋):</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>? :端點在?號前面的那個字符,必須為0個或1個。</font><font>例如。</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>'/ab?cd'</code><font><font>的路徑路徑將匹配端點</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>acd<span> </span></code><font><font>或</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abcd</code><font><font>。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>+ :端點在+號前面的那個字符,必須為1個或多個。</font><font>例如,</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>'/ab+cd'</code><font><font>的路徑路徑將與端點</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abcd</code><font><font>,</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abbcd</code><font><font>,</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abbbcd</code><font><font>等匹配。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>* :端點在放置*字符的地方,可以代換為任意字符串。</font><font>例如。</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>'ab\*cd'</code><font><font>的路由路徑,將匹配端點</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abcd</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abXcd</code><font><font>,<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abSOMErandomTEXTcd</code><font><font>等。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><font><font>() :將一組字符進行匹配,以執行上面三個操作。</font><font>例如。</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>'/ab(cd)?e'</code><font><font>,表示以?</font><font>號對(cd)進行匹配-它會匹配</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abe</code><font><font>和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abcde</code><font><font>。</font><font>(譯註:即(cd)必須為0個或1個。若為0,匹配</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abe</code><font><font>。若為1,匹配</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>abcde</code><font><font>)</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由路徑也可以是</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions" style="font-style: normal !important; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px;"><font><font>JavaScript正則表達式</font></font></a><font><font>。</font><font>例如,下面的路由路徑將與鯰魚</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catfish<span> </span></code><font><font>和角鯊魚</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>dogfish</code><font><font>相匹配,但不包括鯰魚</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catflap</code><font><font>、鯰魚頭</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catfishhead</code><font><font>等。</font><font>請注意,正則表達式的路徑使用正則表達式語法(它不像以前那樣,是帶引號的字符串)。</font></font></p> + +<pre class="brush: js"><code>app.get(/.*fish$/, function (req, res) { + ... +})</code></pre> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font><span> </span>LocalLibrary的大部分路由,都只使用字符串,而不是字符串模式和正則表達式。</font><font>我們還將使用下一節中討論的路由參數。</font></font></p> +</div> + +<h3 id="路由參數"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>路由參數</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路徑參數是命名的URL段,用於捕獲在URL中的位置指定的值。</font><font>命名段以冒號為前綴,然後是名稱(例如</font><font>。捕獲的值,使用參數名稱作為鍵,存在</font><font>對像中(例如</font><font>)。</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/<strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;">:</strong>your_parameter_name/</code><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>req.params</code><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>req.params.your_parameter_name</code></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>例如,考慮一個編碼的URL,其中包含有關用戶和書本的信息:</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>http://localhost:3000/users/34/books/8989</code><font><font>。</font><font>我們可以使用</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>userId</code><font><font>和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>bookId</code><font><font>路徑參數,提取如下所示的信息:</font></font></p> + +<pre><code>app.get('/users/:userId/books/:bookId', function (req, res) { + // Access userId via: req.params.userId + // Access bookId via: req.params.bookId + res.send(req.params); +}) +</code></pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>路由參數的名稱,必須由“單詞字符”(AZ,az,0-9和_)組成。</span></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font><span> </span>URL<span> </span></font></font><em><font><font>/book/create</font></font></em><font><font>將與</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/book/:bookId</code><font><font> 之類的路由匹配(它將提取要創建'<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>create</code><font><font>'的“bookId”值)。</font><font>將使用與傳入URL匹配的第一個路由,因此,如果要單獨處理</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/book/create</code><font><font>URL,則必須在</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/book/:bookId</code><font><font>路由之前,先定義其路由處理程序。</font></font></p> +</div> + +<p><font><font>這就是您開始使用路由所需的全部內容-如果需要,您可以在Express文檔中找到更多信息:</font></font><a class="external external-icon" href="http://expressjs.com/en/starter/basic-routing.html" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>基本路由</font></font></a><font><font>和</font></font><a class="external external-icon" href="http://expressjs.com/en/guide/routing.html" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 255, 255);'><font><font>路由指南</font></font></a><font><font>。</font><font>以下部分顯示了我們如何為LocalLibrary設置路由和控制器。</font></font></p> + +<h2 id="本地圖書館需要的路由"><font><font>本地圖書館需要的路由</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下面列出了我們最終需要用於頁面的URL,其中object被替換為每個模型的名稱(book,bookinstance,genre,author),objects是對象的複數,id是默認情況下,為每個Mongoose模型實例指定的唯一實例字段(</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>_id</code><font><font>)。</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/</code><font><font><span> </span>— 主頁/索引頁面。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/<objects>/</code><font><font>—所有書本,書本實例,種類或作者的列表(例如/<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/books/</code><font><font>, /<span> </span></font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/genres/</code><font><font>等)</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/<object>/<em><id></em></code><font><font>—具有給定</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><em>_id</em></code><font><font>字段值的特定書本,書本實例,種類或作者的詳細信息頁面(例如</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/catalog/book/584493c1f4887f06c0e67d37</code><font><font>)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/<object>/create</code><font><font>—用於創建新的書本,書本實例,種類或作者的表單(例如</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/catalog/book/create</code><font><font>)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/<object>/<em><id></em>/update</code><font><font>—使用給定的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><em>_id</em></code><font><font>字段值更新特定書本,書本實例,種類或作者的表單(例如</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/catalog/book/584493c1f4887f06c0e67d37/update</code><font><font>)。</font></font></li> + <li style="font-style: normal !important; margin: 0px 0px 6px; padding: 0px; border: 0px;"><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>catalog/<object>/<em><id></em>/delete</code><font><font>—刪除具有給定</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><em>_id</em></code><font><font>字段值的特定書本,書本實例,種類或作者的表單(例如</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>/catalog/book/584493c1f4887f06c0e67d37/delete</code><font><font>)。</font></font></li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>第一個主頁和列表頁面,不編碼任何其他信息。</font><font>雖然返回的結果,將取決於模型類型和數據庫中的內容,但為了獲取信息所運行的查詢,將始終相同(類似地,用於創建對象的代碼將始終類似)。</font><font>相反的,其他URL用於處理特定文檔/模型實例—這些將項目的標識編碼在URL中(如上面的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'><em><id></em></code><font><font>)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們將使用路徑參數,來提取編碼信息,並將其傳遞給路由處理程序(在稍後的文章中,我們將使用它來動態確定從數據庫獲取的信息)。</font><font>通過對我們的URL中的信息進行編碼,我們只需要一個路由,用於特定類型的每個資源(例如,一個路由來處理每個書本項目的顯示)。</font></font></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font><span> </span>: Express允許您以任何方式構建URL -您可以在URL正文中編碼信息,就像上面一樣,或使用URL<span> </span></font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>GET</code><font><font>參數(例如</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/book/?id=6</code><font><font>)。</font><font>無論您使用哪種方法,URL都應保持乾淨,合理且可讀(請在此處查看</font></font><a class="external external-icon" href="https://www.w3.org/Provider/Style/URI" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>W3C建議</font></font></a><font><font>)。</font></font></p> +</div> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>接下來,我們為所有上述URL,創建路由處理程序回調函數和路由代碼。</span></p> + +<h2 id="創建路由-handler回調函式"><font><font>創建路由-handler回調函式</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>在我們定義路由之前,我們將首先創建它們將調用的所有虛擬/骨架回調函數。</font><font>回調將存在Books,BookInstances,Genres 和Authors 的單獨“控制器” 模塊中(您可以使用任何文件/模塊結構,但這似乎是該項目的適當粒度)。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>首先在項目根目錄(</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/controllers</font></font></strong><font><font>)中,為我們的控制器創建一個文件夾,然後創建單獨的控制器文件/模塊,來處理每個模型:</font></font></p> + +<pre>/express-locallibrary-tutorial //the project root + <strong>/controllers</strong> + <strong>authorController.js</strong> + <strong>bookController.js</strong> + <strong>bookinstanceController.js</strong> + <strong>genreController.js</strong></pre> + +<h3 id="作者控制器"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>作者控制器</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/controllers/authorController.js</font></font></strong><font><font>文件,並複制以下代碼:</font></font></p> + +<pre class="brush: js">var Author = require('../models/author'); + +// Display list of all Authors. +exports.author_list = function(req, res) { + res.send('NOT IMPLEMENTED: Author list'); +}; + +// Display detail page for a specific Author. +exports.author_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Author detail: ' + req.params.id); +}; + +// Display Author create form on GET. +exports.author_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author create GET'); +}; + +// Handle Author create on POST. +exports.author_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author create POST'); +}; + +// Display Author delete form on GET. +exports.author_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete GET'); +}; + +// Handle Author delete on POST. +exports.author_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete POST'); +}; + +// Display Author update form on GET. +exports.author_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author update GET'); +}; + +// Handle Author update on POST. +exports.author_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author update POST'); +}; +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該模塊首先導入我們稍後將使用的模型,來訪問和更新我們的數據。</font><font>然後它為我們希望處理的每個URL,導出函數(創建,更新和刪除操作使用表單,因此還有其他方法,來處理表單發布請求- 我們將在稍後的“表單文章” 中討論這些方法) 。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>所有函數都具有Express中間件函數的標準形式,如果方法沒有完成請求週期,則會調用請求,響應和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>next</code><font><font>下一個函數的參數(在所有這些情況下,它都會執行!)。</font><font>這些方法只返回一個字符串,表明尚未創建關聯的頁面。</font><font>如果期望控制器函數接收路徑參數,則在消息字符串中,輸出這些參數(參見上面的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>req.params.id</code><font><font>)。</font></font></p> + +<h4 id="書本實例控制器" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>書本實例控制器</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/controllers/bookinstanceController.js</font></font></strong><font><font>文件,並將其複製到以下代碼中(它遵循與</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Author</code><font><font>控制器模塊相同的模式):</font></font></p> + +<pre class="brush: js">var BookInstance = require('../models/bookinstance'); + +// Display list of all BookInstances. +exports.bookinstance_list = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance list'); +}; + +// Display detail page for a specific BookInstance. +exports.bookinstance_detail = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance detail: ' + req.params.id); +}; + +// Display BookInstance create form on GET. +exports.bookinstance_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create GET'); +}; + +// Handle BookInstance create on POST. +exports.bookinstance_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create POST'); +}; + +// Display BookInstance delete form on GET. +exports.bookinstance_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete GET'); +}; + +// Handle BookInstance delete on POST. +exports.bookinstance_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete POST'); +}; + +// Display BookInstance update form on GET. +exports.bookinstance_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update GET'); +}; + +// Handle bookinstance update on POST. +exports.bookinstance_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update POST'); +}; +</pre> + +<h4 id="種類控制器" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>種類控制器</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/controllers/genreController.js</font></font></strong><font><font>文件,並複制以下文本(這與</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Author</code><font><font>和</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>BookInstance</code><font><font>文件的模式相同):</font></font></p> + +<pre class="brush: js">var Genre = require('../models/genre'); + +// Display list of all Genre. +exports.genre_list = function(req, res) { + res.send('NOT IMPLEMENTED: Genre list'); +}; + +// Display detail page for a specific Genre. +exports.genre_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Genre detail: ' + req.params.id); +}; + +// Display Genre create form on GET. +exports.genre_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create GET'); +}; + +// Handle Genre create on POST. +exports.genre_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create POST'); +}; + +// Display Genre delete form on GET. +exports.genre_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete GET'); +}; + +// Handle Genre delete on POST. +exports.genre_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete POST'); +}; + +// Display Genre update form on GET. +exports.genre_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update GET'); +}; + +// Handle Genre update on POST. +exports.genre_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update POST'); +}; +</pre> + +<h4 id="書本控制器" style='font-style: normal; line-height: 1.2; margin: 30px 0px 20px; padding: 0px; border: 0px; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 1.375rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>書本控制器</font></font></h4> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/controllers/bookController.js</font></font></strong><font><font>文件,並複制以下代碼。</font><font>它遵循與其他控制器模塊相同的模式,但另外還有一個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>index()</code><font><font>函數,用於顯示站點歡迎頁面:</font></font></p> + +<pre class="brush: js">var Book = require('../models/book'); + +<strong>exports.index = function(req, res) { + res.send('NOT IMPLEMENTED: Site Home Page'); +};</strong> + +// Display list of all books. +exports.book_list = function(req, res) { + res.send('NOT IMPLEMENTED: Book list'); +}; + +// Display detail page for a specific book. +exports.book_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Book detail: ' + req.params.id); +}; + +// Display book create form on GET. +exports.book_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book create GET'); +}; + +// Handle book create on POST. +exports.book_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book create POST'); +}; + +// Display book delete form on GET. +exports.book_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete GET'); +}; + +// Handle book delete on POST. +exports.book_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete POST'); +}; + +// Display book update form on GET. +exports.book_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book update GET'); +}; + +// Handle book update on POST. +exports.book_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book update POST'); +}; +</pre> + +<h2 id="創建目錄路由模組"><font><font>創建目錄路由模組</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>接下來,我們為LocalLibrary 網站,創建所需全部URL 的路由,這將調用我們在上一節中定義的控制器功能。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>骨架網站已經有一個</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>./routes</font></font></strong><font><font>文件夾,其中包含索引和用戶的路由。</font><font>在此文件夾中,創建另一個路徑文件—<span> </span></font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>catalog.js</font></font></strong><font><font><span> </span>—如下圖所示。</font></font></p> + +<pre>/express-locallibrary-tutorial //the project root + /routes + index.js + users.js + <strong>catalog.js</strong></pre> + +<p><font><font>打開</font></font><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>/routes/<span> </span></font></font></strong><strong style='background-color: #ffffff; border: 0px; color: #333333; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>catalog.js</font></font></strong><font><font>,複製下面的代碼:</font></font></p> + +<pre class="brush: js"><strong>var express = require('express'); +var router = express.Router(); +</strong> +// Require controller modules. +var book_controller = require('../controllers/bookController'); +var author_controller = require('../controllers/authorController'); +var genre_controller = require('../controllers/genreController'); +var book_instance_controller = require('../controllers/bookinstanceController'); + +/// BOOK ROUTES /// + +// GET catalog home page. +router.get('/', book_controller.index); + +// GET request for creating a Book. NOTE This must come before routes that display Book (uses id). +router.get('/book/create', book_controller.book_create_get); + +// POST request for creating Book. +router.post('/book/create', book_controller.book_create_post); + +// GET request to delete Book. +router.get('/book/:id/delete', book_controller.book_delete_get); + +// POST request to delete Book. +router.post('/book/:id/delete', book_controller.book_delete_post); + +// GET request to update Book. +router.get('/book/:id/update', book_controller.book_update_get); + +// POST request to update Book. +router.post('/book/:id/update', book_controller.book_update_post); + +// GET request for one Book. +router.get('/book/:id', book_controller.book_detail); + +// GET request for list of all Book items. +router.get('/books', book_controller.book_list); + +/// AUTHOR ROUTES /// + +// GET request for creating Author. NOTE This must come before route for id (i.e. display author). +router.get('/author/create', author_controller.author_create_get); + +// POST request for creating Author. +router.post('/author/create', author_controller.author_create_post); + +// GET request to delete Author. +router.get('/author/:id/delete', author_controller.author_delete_get); + +// POST request to delete Author. +router.post('/author/:id/delete', author_controller.author_delete_post); + +// GET request to update Author. +router.get('/author/:id/update', author_controller.author_update_get); + +// POST request to update Author. +router.post('/author/:id/update', author_controller.author_update_post); + +// GET request for one Author. +router.get('/author/:id', author_controller.author_detail); + +// GET request for list of all Authors. +router.get('/authors', author_controller.author_list); + +/// GENRE ROUTES /// + +// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id). +router.get('/genre/create', genre_controller.genre_create_get); + +//POST request for creating Genre. +router.post('/genre/create', genre_controller.genre_create_post); + +// GET request to delete Genre. +router.get('/genre/:id/delete', genre_controller.genre_delete_get); + +// POST request to delete Genre. +router.post('/genre/:id/delete', genre_controller.genre_delete_post); + +// GET request to update Genre. +router.get('/genre/:id/update', genre_controller.genre_update_get); + +// POST request to update Genre. +router.post('/genre/:id/update', genre_controller.genre_update_post); + +// GET request for one Genre. +router.get('/genre/:id', genre_controller.genre_detail); + +// GET request for list of all Genre. +router.get('/genres', genre_controller.genre_list); + +/// BOOKINSTANCE ROUTES /// + +// GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id). +router.get('/bookinstance/create', book_instance_controller.bookinstance_create_get); + +// POST request for creating BookInstance. +router.post('/bookinstance/create', book_instance_controller.bookinstance_create_post); + +// GET request to delete BookInstance. +router.get('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_get); + +// POST request to delete BookInstance. +router.post('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_post); + +// GET request to update BookInstance. +router.get('/bookinstance/:id/update', book_instance_controller.bookinstance_update_get); + +// POST request to update BookInstance. +router.post('/bookinstance/:id/update', book_instance_controller.bookinstance_update_post); + +// GET request for one BookInstance. +router.get('/bookinstance/:id', book_instance_controller.bookinstance_detail); + +// GET request for list of all BookInstance. +router.get('/bookinstances', book_instance_controller.bookinstance_list); + +<strong>module.exports = router;</strong> +</pre> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>該模塊導入Express,然後使用它來創建一個</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>Router</code><font><font>對象。</font><font>路由都在路由器上設置完成,然後導出。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>路由是使用路由器對像上的</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>.get()</code><font><font>或</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>.post()</code><font><font>方法定義的。</font><font>所有路徑都是使用字符串定義的(我們不使用字符串模式或正則表達式)。</font><font>作用於某些特定資源(如書籍)的路由,則使用路徑參數從URL中獲取對象標識id。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>處理程序函數,都是從我們在上一節中,創建的控制器模塊導入的。</font></font></p> + +<h3 id="更新_index_路由模組"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;">更新 index 路由模組</span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們已經設置了所有新路由,但我們仍然有一個到原始頁面的路由。</font><font>讓我們將其重定向,到我們在路徑'/ catalog' 創建的新索引頁面。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>/routes/index.js</font></font></strong><font><font>並使用下面的函數,替換現有路由。</font></font></p> + +<pre class="brush: js">// GET home page. +router.get('/', function(req, res) { + res.redirect('/catalog'); +});</pre> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>這是我們第一次使用</font></font><a class="external external-icon" href="https://expressjs.com/en/4x/api.html#res.redirect" rel="noopener" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; white-space: pre-line; font-family: x-locale-heading-primary, zillaslab, Palatino, "Palatino Linotype", x-locale-heading-secondary, serif; font-size: 18px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; background-color: rgb(255, 243, 212);'><font><font>redirect()</font></font></a><font><font>響應方法。</font><font>這會重定向到指定的頁面,默認情況下會發送HTTP狀態代碼“302 Found”。</font><font>您可以根據需要,更改返回的狀態代碼,並提供絕對路徑或相對路徑。</font></font></p> +</div> + +<h3 id="更新app.js"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>更新app.js</font></font></span></h3> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>最後一步,是將路由,添加到中間件鏈。</font><font>我們在</font></font><code style='font-style: inherit; margin: 0px; padding: 0px 2px; border: 0px; font-weight: inherit; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word;'>app.js</code><font><font>這樣做。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>打開</font></font><strong style="border: 0px; font-style: normal !important; margin: 0px; padding: 0px;"><font><font>app.js</font></font></strong><font><font>,並要求其他路由下方的目錄路由(添加下面顯示的第三行,在其他兩個路由下面):</font></font></p> + +<pre class="brush: js">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +<strong>var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site</strong></pre> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>接下來,將目錄路由,添加到其他路由下面的中間件堆棧(添加下面顯示的第三行,在其他兩行下面):</span></p> + +<pre class="brush: js">app.use('/', indexRouter); +app.use('/users', usersRouter); +<strong>app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain.</strong></pre> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>Note:</font></font></strong><font><font><span> </span> 我們已在路徑</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>'/catalog'</code><font><font>中添加了目錄模塊。</font><font>它預先添加到目錄模塊中定義的所有路徑。</font><font>例如,要訪問書本列表,URL將為:</font></font><code style='font-style: normal; margin: 0px; padding: 0px 2px; border: 0px; font-weight: 400; background-color: rgba(220, 220, 220, 0.5); border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; color: rgb(51, 51, 51); letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>/catalog/books/</code><font><font>。</font></font></p> +</div> + +<p><font>就是這樣。</font><font>現在應該為我們最終在LocalLibrary 網站上支持的所有URL,啟用路由和框架功能。</font></p> + +<h3 id="測試路由"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-style: normal !important; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px 0px 40px;"><font><font>測試路由</font></font></span></h3> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>要測試路由,首先使用您通常的方法啟動網站</span></p> + +<ul> + <li>預設方法 + <pre class="brush: bash"><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm start + +// macOS or Linux +DEBUG=express-locallibrary-tutorial:* npm start</code> +</pre> + </li> + <li><font><font>如果您先前設置了</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website" style='font-style: normal; text-decoration: none; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>nodemon</font></font></a><font><font><span> </span>,則可以使用:</font></font> + <pre><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong> + +// macOS or Linux +</code>DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong> +</pre> + </li> +</ul> + +<p><font>然後瀏覽一些上面的LocalLibrary URL,並驗證您沒有收到錯誤頁面(HTTP 404)。</font><font>為方便起見,下面列出了一小組網址:</font></p> + +<ul> + <li><a href="http://localhost:3000/">http://localhost:3000/</a></li> + <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li> + <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li> + <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances/</a></li> + <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors/</a></li> + <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres/</a></li> + <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li> + <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li> +</ul> + +<h2 id="總結"><font><font>總結</font></font></h2> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>我們現在為網站創建了所有的路由,在稍後的教程,我們可以將實作完成的代碼,填入到空殼控制器函式。</font><font>以這樣的方式,我們學到了許多關於Express 路由的基本信息,以及一些組織路由和控制器的方式。</font></font></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>下一篇文章,我們將使用視圖(模板) 和存在模型裡的信息,為網站創建一個合適的歡迎頁面。</font></font></p> + +<h2 id="參閱">參閱</h2> + +<ul> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="本教程連結">本教程連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/express_nodejs/skeleton_website/index.html b/files/zh-tw/learn/server-side/express_nodejs/skeleton_website/index.html new file mode 100644 index 0000000000..0139a30dd9 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/skeleton_website/index.html @@ -0,0 +1,506 @@ +--- +title: 'Express 教學 2: 創建一個骨架網站' +slug: Learn/Server-side/Express_Nodejs/skeleton_website +translation_of: Learn/Server-side/Express_Nodejs/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<p class="summary">在 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程</a>的第二篇文章,演示如何創建一個 "骨架" 網站項目,你可以接著在裡面加入網站特定的路由、模板/視圖、和數据庫調用。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置條件:</th> + <td><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">架設一個Node 開發環境</a>。回顧Express 教程。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>能夠使用<em>Express 應用產生器,</em>創建自己新的網頁項目。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>本文演示如何使用 <a class="external external-icon" href="https://expressjs.com/en/starter/generator.html" rel="noopener">Express 應用產生器</a> 工具,創建一個 "骨架" 網站,然後您可以使用特定於站點的路由,視圖/模板和數據庫調用來填充它們。在這個教程,我們將使用該工具,為我們的本地圖書館網站創建框架,我們稍後將添加該網站所需的所有其他代碼。該過程非常簡單,只需要在命令行上,用新項目名稱調用生成器,還可以指定站點的模板引擎和 CSS 生成器。</p> + +<p>以下部分向您展示如何調用應用程序生成器,並提供關於視圖或CSS的不同選項的一些解釋。我們還將解釋骨架網站的結構。最後,我們將展示如何運行網站,來驗證它是否有效。</p> + +<div class="note"> +<p><span style="line-height: 1.5;"><strong>注意</strong>: </span>Express Application Generator並非 Express 應用程序的唯一生成器,生成的項目不是構建文件和目錄的唯一可行方式。然而,生成的網站具有易於擴展和理解的模塊化結構。有關最小 Express 應用程序的信息,請參閱 <span style="line-height: 1.5;"><a href="https://expressjs.com/en/starter/hello-world.html">Hello world </a></span>示例(Express docs)。</p> +</div> + +<h2 id="使用應用產生器">使用應用產生器</h2> + +<p>您應該已經安裝了生成器,作為設置 Node 開發環境的一部分。作為快速提醒,您可以使用 NPM 軟件包管理器,在整個站點安裝生成器工具,如下所示:</p> + +<pre class="brush: bash notranslate"><code>npm install express-generator -g</code></pre> + +<p>生成器有許多選項,您可以使用<code>--help</code>(或<code>-h</code>)命令,在命令行上查看它們:</p> + +<pre class="brush: bash notranslate">> express --help + + Usage: express [options] [dir] + + Options: + + -h, --help output usage information + --version output the version number + -e, --ejs add ejs engine support + --pug add pug engine support + --hbs add handlebars engine support + -H, --hogan add hogan.js engine support + -v, --view <engine> add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) + -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css) + --git add .gitignore + -f, --force force on non-empty directory +</pre> + +<p>您可以使用 Jade 視圖引擎和純 CSS 來指定 <code>express</code>,以在當前目錄中創建項目(如果指定目錄名,則項目將創建在具有該名稱的子文件夾中)。</p> + +<pre class="brush: bash notranslate"><code>express</code></pre> + +<p>您還可以使用<code>--view</code>選擇視圖(模板)引擎,並且/或者使用<code>--css</code>選擇 CSS 生成引擎。</p> + +<div class="note"> +<p><strong>注意:</strong> 選擇模板引擎的其他選項(例如 <code>--hogan</code>, <code>--ejs</code>, <code>--hbs</code>等)已被棄用。請用 <code>--view</code> (或 <code>-v</code>)!</p> +</div> + +<h3 id="我應該用哪個視圖引擎">我應該用哪個視圖引擎?</h3> + +<p>Express Application Generator 允許您配置許多流行的視圖/模板引擎,包括 <a href="https://www.npmjs.com/package/ejs">EJS</a>, <a href="http://github.com/donpark/hbs">Hbs</a>, <a href="https://pugjs.org/api/getting-started.html">Pug</a> (Jade), <a href="https://www.npmjs.com/package/twig">Twig</a>, 和 <a href="https://www.npmjs.com/package/vash">Vash</a>,但如果您沒有指定視圖選項,它會默認選擇Jade。 Express 本身也可以支持大量其他模板語言,是「<a href="https://github.com/expressjs/express/wiki#template-engines">開箱即可使用</a>」的。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果要使用生成器不支持的模板引擎,請參閱<a href="https://expressjs.com/en/guide/using-template-engines.html">在Express中使用模板引擎(Express文檔)</a>,並參閱目標視圖引擎的文檔。</p> +</div> + +<p>一般來說,您應該選擇一種「可以提供您所需的所有功能」的模板引擎,<br> + 並使您能夠儘早提高生產力 - 換句話說,就像您選擇其他組件一樣!比較模板引擎時需要考慮的一些事項如下:</p> + +<ul> + <li>具備生產力之前所需要花費的時間 — 如果您的團隊已經有模板語言的經驗,那麼使用該語言可能會更快地提高生產力。如果不是,那麼你應該考慮候選模板引擎的相對學習曲線(即以投入時間與得到的生產力為XY座標所繪製的曲線)。</li> + <li>人氣和活躍 — 回顧引擎的普及程度以及它是否擁有活躍的社區。當您在網站的生命週期中遇到問題時,能夠獲得對引擎的支持非常重要。</li> + <li>風格 — 某些模板引擎使用特定的標記,來指示在 “普通” HTML內插入的內容,而其他模板引擎使用不同的語法(例如,使用縮進和區塊名稱)構造HTML。</li> + <li>性能/渲染時間。</li> + <li>特點 — 你應該考慮你看中的引擎是否具有以下功能: + <ul> + <li>佈局繼承:允許您定義基本模板,然後 “繼承” 它的一部分,使特定頁面可以有不同的呈現。比起通過包含大量必需組件,或每次從頭開始構建模板,這通常是更好的方式。</li> + <li>"Include" 支持:允許您通過包含其他模板,來構建模板。</li> + <li>簡潔的變量和循環控制語法。</li> + <li>能夠在模板級別過濾變量值(例如,將變量設置為大寫,或格式化日期值)。</li> + <li>能夠生成HTML以外的輸出格式(例如 JSON 或 XML)。</li> + <li>支持異步操作和流媒體。</li> + <li>可以在客戶端和服務器上使用。如果可以在客戶端上使用模板引擎,則允許提供數據,並有可能在客戶端完成所有渲染,或大部分渲染。</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>提示:</strong> 互聯網上有許多資源,可幫助您比較不同的視圖/模板引擎選擇!</p> +</div> + +<p>對於這個項目,我們將使用 <a href="https://pugjs.org/api/getting-started.html">Pug</a> 模板引擎(這是最近更名的 Jade 引擎),因為這是最流行的 Express / JavaScript 模板語言之一,並且應用發生器支持開箱即用。</p> + +<h3 id="我應該用哪個CSS樣式引擎">我應該用哪個CSS樣式引擎?</h3> + +<p>Express 應用生成器允許您創建一個項目,並配置最常見的 CSS 樣式表引擎:<a href="http://lesscss.org/">LESS</a>, <a href="http://sass-lang.com/">SASS</a>, <a href="http://compass-style.org/">Compass</a>, <a href="http://stylus-lang.com/">Stylus</a>。</p> + +<div class="note"> +<p><strong>注意: </strong>CSS有一些限制,使某些任務變得困難。 CSS 樣式表引擎允許您使用更強大的語法來定義您的 CSS,然後將定義編譯為純粹的舊式 CSS,以供瀏覽器使用。</p> +</div> + +<p>與模板引擎一樣,您應該使用樣式表引擎,這樣可以讓您的團隊獲得最高生產力。對於這個項目,我們將使用普通的 CSS(默認值),因為我們的 CSS 要求不夠複雜,沒有必要使用其他任何東西。</p> + +<h3 id="我應該用哪個數據庫">我應該用哪個數據庫?</h3> + +<p>生成的代碼不使用/包含任何數據庫。 Express 應用程序可以使用 Node支持的任何<a href="https://expressjs.com/en/guide/database-integration.html">數據庫機制</a>(Express 本身並未針對數據庫管理,定義任何特定的附加行為/要求)。<br> + 我們將在後面的文章中,討論如何與數據庫集成。</p> + +<h2 id="創建項目">創建項目</h2> + +<p>對於我們要構建的示例 Local Library 應用程序,我們將使用 Pug 模板庫,創建一個名為 express-locallibrary-tutorial 的項目,並且不使用 CSS樣式表引擎。</p> + +<p>首先到要創建項目的位置,然後在命令提示符下,運行 Express 應用生成器,如下所示:</p> + +<pre class="brush: bash notranslate">express express-locallibrary-tutorial --view=pug +</pre> + +<p>成器將創建(並列出)項目的文件。</p> + +<pre class="brush: bash notranslate"> create : express-locallibrary-tutorial + create : express-locallibrary-tutorial/package.json + create : express-locallibrary-tutorial/app.js + create : express-locallibrary-tutorial/public/images + create : express-locallibrary-tutorial/public + create : express-locallibrary-tutorial/public/stylesheets + create : express-locallibrary-tutorial/public/stylesheets/style.css + create : express-locallibrary-tutorial/public/javascripts + create : express-locallibrary-tutorial/routes + create : express-locallibrary-tutorial/routes/index.js + create : express-locallibrary-tutorial/routes/users.js + create : express-locallibrary-tutorial/views + create : express-locallibrary-tutorial/views/index.pug + create : express-locallibrary-tutorial/views/layout.pug + create : express-locallibrary-tutorial/views/error.pug + create : express-locallibrary-tutorial/bin + create : express-locallibrary-tutorial/bin/www + + install dependencies: + > cd express-locallibrary-tutorial && npm install + + run the app: + > SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + +<p>在輸出結束時,生成器提供關於「如何安裝依賴關係」的指示信息(如 <strong>package.json </strong>文件中所列),以及如何運行應用程序(上述說明適用於 Windows;在 Linux / macOS上,它們會略有不同)。</p> + +<h2 id="運行骨架網站">運行骨架網站</h2> + +<p>在這一時間點上,我們有一個完整的骨架項目。該網站實際上並沒有做太多工作,但運行它,能夠展示它是如何工作的。</p> + +<ol> + <li>首先安裝依賴項(<code>install</code>安裝命令,將獲取項目的 <strong>package.json </strong>文件中列出的所有依賴項包)。 + + <pre class="brush: bash notranslate">cd express-locallibrary-tutorial +npm install</pre> + </li> + <li>然後運行該應用程序。 + <ul> + <li>在Windows上,使用此命令: + <pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + </li> + <li>在macOS 或 Linux,使用此命令: + <pre class="brush: bash notranslate">DEBUG=express-locallibrary-tutorial:* npm start +</pre> + </li> + </ul> + </li> + <li>然後在瀏覽器中加載 <a href="http://localhost:3000/">http://localhost:3000/</a> ,以訪問該應用程序。</li> +</ol> + +<p>你應該會看到一個瀏覽器頁面,就像這樣:</p> + +<p><img alt="Browser for default Express app generator website" src="https://mdn.mozillademos.org/files/14375/ExpressGeneratorSkeletonWebsite.png" style="display: block; height: 403px; margin: 0px auto; width: 576px;"></p> + +<p>你有一個能工作的 Express 應用了,讓它在 <a href="http://localhost:3000/">http://localhost:3000/ </a>服務。</p> + +<div class="note"> +<p><strong>注意:</strong> 您也可以使用 <code>npm start</code>命令啟動應用程序。如下圖所示,指定 DEBUG 變量可啟用控制台日誌記錄/調試。例如,當你訪問上面的頁面時,你會看到像這樣的調試輸出:</p> + +<pre class="brush: bash notranslate">>SET DEBUG=express-locallibrary-tutorial:* & npm start + +> express-locallibrary-tutorial@0.0.0 start D:\express-locallibrary-tutorial +> node ./bin/www + + express-locallibrary-tutorial:server Listening on port 3000 +0ms +GET / 200 288.474 ms - 170 +GET /stylesheets/style.css 200 5.799 ms - 111 +GET /favicon.ico 404 34.134 ms - 1335</pre> +</div> + +<h2 id="讓伺服器在檔案更改時重新啟動">讓伺服器在檔案更改時重新啟動</h2> + +<p>在您重新啟動服務器之前,您對 Express 網站所做的任何更改,目前都不可見。每次進行更改時,必須停止並重新啟動服務器,很快變得非常煩人,因此值得花時間使服務器在需要時,自動重新啟動。</p> + +<p>這種工具中,最簡單的之一就是 <a href="https://github.com/remy/nodemon">nodemon</a>。這通常是全局安裝的(因為它是一個“工具”),但在這裡,我們將在本地安裝和使用它,作為開發人員依賴項,以便任何使用該項目的開發人員,在安裝應用程序時自動獲取它。在骨架項目的根目錄中,使用以下命令:</p> + +<pre class="brush: bash notranslate">npm install --save-dev nodemon</pre> + +<p>如果您打開項目的 <strong>package.json</strong> 文件,您現在將看到一個具有此依賴關係的新區段:</p> + +<pre class="brush: json notranslate"> "devDependencies": { + "nodemon": "^1.14.11" + } +</pre> + +<p>由於該工具沒有全局安裝,我們無法從命令行啟動它(除非我們將其添加到路徑中),但是我們可以從 NPM 腳本中調用它,因為 NPM 知道所有關於安裝的軟件包的信息。找到你的 package.json 的腳本 scripts 區塊。我們更新 <code>scripts </code>區塊,最初的一行,以"<code>start</code>"開頭,在該行的末尾添加逗號,並添加 "<code>devstart</code>" 開頭的一行,如下所示:</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www"<strong>,</strong> +<strong> "devstart": "nodemon ./bin/www"</strong> + }, +</pre> + + + +<p>現在我們可以用與前面幾乎完全相同的方式,啟動服務器,但使用指定的 devstart 命令:</p> + +<ul> + <li>在 Windows,使用此命令: + <pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong></pre> + </li> + <li>在 macOS or Linux,使用此命令: + <pre class="brush: bash notranslate">DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong> +</pre> + </li> +</ul> + +<div class="note"> +<p><strong>注意:</strong> 現在,如果您編輯項目中的任何文件,服務器將重新啟動(或者您可以隨時在命令提示符下,鍵入<code>rs</code>來重新啟動它)。您仍需要重新加載瀏覽器,以刷新頁面。</p> + +<p>我們現在必須調用“<code>npm run <scriptname></code>”而不是 <code>npm start</code>,因為“start”實際上是映射到指定腳本的 NPM 命令。我們可以在啟動腳本中替換該命令,但我們只想在開發期間使用 nodemon,因此創建新的腳本命令是有意義的。</p> +</div> + +<h2 id="從產生器得到的項目">從產生器得到的項目</h2> + +<p>現在我們來看看我們剛剛創建的項目。</p> + +<h3 id="目錄結構">目錄結構</h3> + +<p>從產生器得到的生成項目,現在已經安裝了依賴項,具有以下文件結構 (<strong>不帶</strong>前綴 “/” 的項目,表示文件)。 <strong>package.json </strong>文件定義了應用程序依賴項,和其他信息。它還定義了一個啟動腳本,它將調用應用程序入口點 JavaScript 文件 <strong>/bin/www</strong>。這設置了一些應用程序的錯誤處理,然後加載 <strong>app.js</strong> ,來完成剩下的工作。應用程序路徑,存儲在 <strong>/routes </strong>目錄下的單獨模塊中。模板存儲在 <strong>/views</strong> 目錄下。</p> + +<pre class="notranslate">/express-locallibrary-tutorial + <strong>app.js</strong> + /bin + <strong>www</strong> + <strong>package.json</strong> + /node_modules + [about 4,500 subdirectories and files] + /public + /images + /javascripts + /stylesheets + <strong>style.css</strong> + /routes + <strong>index.js</strong> + <strong>users.js</strong> + /views + <strong>error.pug</strong> + <strong>index.pug</strong> + <strong>layout.pug</strong> + +</pre> + +<p>以下各節將詳細介紹這些文件。</p> + +<h3 id="package.json">package.json</h3> + +<p><strong>package.json </strong>文件定義了應用程序依賴關係,和其他訊息:</p> + +<pre class="brush: json notranslate">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, + "dependencies": { + "body-parser": "~1.18.2", + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.2", + "morgan": "~1.9.0", + "pug": "~2.0.0-rc.4", + "serve-favicon": "~2.4.5" + }, + "devDependencies": { + "nodemon": "^1.14.11" + } +} +</pre> + +<p>依賴關係包括 express 套件,和我們所選視圖引擎(pug)的套件。另外,我們還有以下的套件,在許多 Web 應用程序中很有用:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/body-parser">body-parser</a>: 這解析傳入HTTP請求的正文 body 部分,並更容易提取包含信息的不同部分。例如,您可以使用它來讀取 <code>POST</code> 參數。</li> + <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>: 用於解析 cookie header 並填充 <code>req.cookies</code>(本質上提供了訪問 cookie 信息的便捷方法)。</li> + <li><a href="https://www.npmjs.com/package/debug">debug</a>: 一個小型 node 調試程序,仿照 node 核心的調試技術建立的。</li> + <li><a href="https://www.npmjs.com/package/morgan">morgan</a>: 搭配 node 使用的 HTTP 請求記錄器中間層軟件。</li> + <li><a href="https://www.npmjs.com/package/serve-favicon">serve-favicon</a>: 用於提供收藏圖標 <a href="https://en.wikipedia.org/wiki/Favicon">favicon</a> 的 node 中間層軟件(這是用於表示瀏覽器選項卡、書籤等網站內的圖標)。</li> +</ul> + +<p>腳本部分,定義了一個“開始” "start" 腳本,當我們調用 <code>npm start</code> 來啟動服務器時,這就是我們所調用的腳本。從腳本定義中,您可以看到這實際上用 node 啟動了 JavaScript 文件 <strong>./bin/www</strong>。它還定義了一個“devstart” 腳本,我們在調用 <code>npm run devstart </code>時調用它。這將啟動相同的 <strong>./bin/www </strong>文件,但使用 nodemon 調用而不是 node 。</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, +</pre> + +<h3 id="www_文件">www 文件</h3> + +<p>文件 <strong>/bin/www</strong> 是應用程序入口點!它做的第一件事是 <code>require()</code> “真正的” 應用程序入口點(即項目根目錄中的 app.js ),<strong>app.js </strong>會設置並返回 <code><a href="http://expressjs.com/en/api.html">express()</a></code>應用程序的對象。</p> + +<pre class="brush: js notranslate">#!/usr/bin/env node + +/** + * Module dependencies. + */ + +<strong>var app = require('../app');</strong> +</pre> + +<div class="note"> +<p><strong>注意:</strong> <code>require()</code> 是一個全局 node 函數,用於將模塊導入當前文件。這裡我們使用相對路徑指定 <strong>app.js</strong> 模塊,並省略可選的(<strong>.js</strong>)文件擴展名。</p> +</div> + +<p>此文件中的其餘代碼,將設置一個node 運行的HTTP 服務器,並將應用app 設置為特定的端口(在環境變量中定義,如果變量未定義,則定義為3000),並開始監聽和報告服務器錯誤和連接。現在你並不需要知道代碼的其他內容(這個文件中的所有內容都是 “樣板文件” ),但如果你感興趣,可以隨時查看它。</p> + +<h3 id="app.js">app.js</h3> + +<p>此文件創建一個 <code>express</code> 應用程序對象(按傳統命名為 <code>app</code>),使用各種設置和中間件,以設置應用程序,然後從模塊導出應用程序。下面的代碼只顯示了文件的一部分,創建和導出應用程序對象的部分:</p> + +<pre class="brush: js notranslate"><code>var express = require('express'); +var app = express(); +... +</code>module.exports = app; +</pre> + +<p>回到上面的 <strong>www</strong> 入口點文件,它是在導入該文件時,提供給調用者的這個 <code>module.exports</code> 對象。</p> + +<p>讓我們詳細了解 <strong>app.js</strong> 文件。首先,我們使用 <code>require()</code>將一些有用的 node 庫導入到文件中,其中包括我們先前使用 NPM 為應用程序下載的 express,serve-favicon,morgan,cookie-parser 和body-parser;和path 庫,它是解析文件和目錄路徑的核心 node 庫。</p> + +<pre class="brush: js notranslate">var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +</pre> + +<p>然後我們用 <code>require()</code>導入來自我們的路由目錄的模塊。這些模塊/文件包含用於處理特定的相關“路由”集合(URL路徑)的代碼。當我們擴展骨架應用程序,例如列出圖書館中的所有書籍時,我們將添加一個新文件,來處理與書籍相關的路由。</p> + +<pre class="brush: js notranslate">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +</pre> + +<div class="note"> +<p><strong>注意:</strong> 此時我們剛剛導入了模塊;我們還沒有真正使用過它的路由(在文件的更下方一點將使用到路由)。</p> +</div> + +<p>接下來,我們使用導入的 express 模塊,創建應用程序 <code>app</code> 對象,然後使用它來設置視圖(模板)引擎。引擎的設置有兩個部分。首先我們設置 '<code>views</code>' 值,來指定模板將被存儲的文件夾(在這種情況下是子文件夾<strong> /views</strong>)。然後我們設置 '<code>view engine</code>' 的值,來指定模板庫(在本例中為 “pug” )。</p> + +<pre class="brush: js notranslate">var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +</pre> + +<p>下一組函數調用 <code>app.use()</code>,將中間件的庫,添加到請求處理鏈中。除了我們之前導入的第三方庫之外,我們還使用 <code>express.static</code> 中間件,來使 Express 提供在項目根目錄下,<strong>/public</strong> 目錄中的所有靜態文件。</p> + +<pre class="brush: js notranslate">// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +<strong>app.use(express.static(path.join(__dirname, 'public')));</strong> +</pre> + +<p>現在所有其他中間件都已設置完畢,我們將(先前導入的)路由處理代碼,添加到請求處理鏈中。導入的代碼,將為網站的不同部分定義特定路由:</p> + +<pre class="brush: js notranslate">app.use('/', indexRouter); +app.use('/users', usersRouter); +</pre> + +<div class="note"> +<p><strong>注意:</strong> 上面指定的路徑 ('/' and '<code>/users</code>'),被視為定義在導入文件中的路由前綴。因此,例如,如果導入的用戶模塊 <strong>users</strong>為<code>/profile</code>定義了路由,則可以在 <code>/users/profile</code>中訪問該路由。我們將在後面的文章中,詳細討論路由。</p> +</div> + +<p>文件中的最後一個中間件,為錯誤和 HTTP 404 響應添加了處理程序方法。</p> + +<pre class="brush: js notranslate">// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); +</pre> + +<p>Express 應用程序對象(app)現已完全完成配置。最後一步,是將其添加到模塊導出(這允許它通過 <strong>/bin/www</strong> 導入)。</p> + +<pre class="brush: js notranslate">module.exports = app;</pre> + +<h3 id="路由">路由</h3> + +<p>路由文檔 <strong>/routes/users.js</strong> 如下所示(路由文件共享一個類似的結構,所以我們不需要也顯示 <strong>index.js</strong>)。首先加載 express 模塊,並使用它獲取<code> express.Router</code>對象。然後它在該對像上指定一個路由,最後從模塊中導出路由器(這就是允許將文件導入到 <strong>app.js</strong> 中的路由)。</p> + +<pre class="brush: js notranslate">var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +<strong>router.get('/', function(req, res, next) { + res.send('respond with a resource'); +});</strong> + +module.exports = router; +</pre> + +<p>該路由定義了一個回調,只要檢測到具有正確模式的HTTP <code>GET</code> 請求,就會調用該回調。匹配模式是模塊導入時指定的路由('<code>/users</code>'),加上('<code>/</code>')文件中定義的任何內容。換句話說,當收到<code>/users/</code>的 URL 時,將使用此路由。</p> + +<div class="note"> +<p><strong>提示:</strong> 嘗試運行帶有 node 的服務器,並在瀏覽器中訪問以下 URL: <a href="http://localhost:3000/users/">http://localhost:3000/users/</a>。您應該看到一條消息:'respond with a resource'。</p> +</div> + +<p>上面有趣的事情是,回調函數有第三個參數 '<code>next</code>',因此是一個中間件函數,而不是簡單的路由回調。雖然代碼當前不使用 <code>next</code> 參數,但如果要在'<code>/</code>'根路由路徑中,添加多個路由處理程序,將來可能會有用。</p> + +<h3 id="視圖模板">視圖(模板)</h3> + +<p>視圖(模板)存儲在 <strong>/views</strong> 目錄中(如<strong> app.js</strong> 中指定的)並且被賦予文件擴展名<strong>.pug</strong>。方法 <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code>用於呈現指定的模板,以及在對像中傳遞的命名變量的值,然後將結果作為響應發送。在來自<strong> /routes/index.js </strong>的以下代碼中,您可以看到,該路由如何使用模板 "index" 傳遞模板變量 "title" ,以呈現響應。</p> + +<pre class="brush: js notranslate">/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { title: 'Express' }); +}); +</pre> + +<p>上面路由的相應模板在下面給出(<strong>index.pug</strong>)。我們稍後會詳細討論這個語法。您現在需要知道的是,標題變量 <code>title</code>(值為 '<code>Express</code>')將插入模板中指定的位置。</p> + +<pre class="notranslate">extends layout + +block content + h1= title + p Welcome to #{title} +</pre> + +<h2 id="挑戰自己">挑戰自己</h2> + +<p>在 <strong>/routes/users.js </strong>中創建一個新路由,它將在 <code>/users/cool/</code>上顯示文本 “You're so cool”。通過運行服務器,並在瀏覽器中訪問 <a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/</a> 來測試它。</p> + +<ul> +</ul> + +<h2 id="總結">總結</h2> + +<p>你現在為 本地圖書館 創建了一個骨架網站項目,並且用 node 驗證了它能夠運行。最重要的,你也理解了項目的結構,因此你也明白了我們需要為本地圖書館加上路由和視圖。</p> + +<p>接下來我們將開始修改骨架,讓它能像一個圖書館網站一樣運作。</p> + +<h2 id="參閱">參閱</h2> + +<ul> + <li><a href="https://expressjs.com/en/starter/generator.html">Express application generator</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="本教程連結">本教程連結</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/express_nodejs/tutorial_local_library_website/index.html b/files/zh-tw/learn/server-side/express_nodejs/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..6804ef3742 --- /dev/null +++ b/files/zh-tw/learn/server-side/express_nodejs/tutorial_local_library_website/index.html @@ -0,0 +1,91 @@ +--- +title: 'Express 教學 1: 本地圖書館網站' +slug: Learn/Server-side/Express_Nodejs/Tutorial_local_library_website +translation_of: Learn/Server-side/Express_Nodejs/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">我們實作教程系列的第一篇文章,會說明將學到什麼東西,並提供「本地圖書館」範例網站的概述 。我們將在接下來的文章中一步一步完成這個網站。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置條件:</th> + <td>閱讀 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express 介紹。</a> 在底下的教程,你將需要 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">架設一個 Node 開發環境。</a></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>介紹本教程的範例應用,讓讀者理解包含哪些主題。</td> + </tr> + </tbody> +</table> + +<h2 id="概覽">概覽</h2> + +<p>歡迎來到 MDN "本地圖書館" Express (Node) 教程,我們將開發一個網站,用於管理本地圖書館的目錄。</p> + +<p>本系列教程文章中,你將會:</p> + +<ul> + <li>使用 <em>Express 應用產生器工具,創建一個骨架網站與應用</em></li> + <li>起動和停止Node 網頁伺服器</li> + <li>使用數据庫存放應用的數据</li> + <li>創建路由用以要求不同的信息,創建模板 ("視圖") 以HTML的形式在瀏覽器中呈現數据</li> + <li>使用表單</li> + <li>部署應用到生產環境</li> +</ul> + +<p>這些主題中,有一部分你可能已經學過了,或者曾經簡短的接觸過。在本列系教程的最後,你應該知道的夠多,能夠自己開發簡單的 Express 應用。</p> + +<h2 id="本地圖書館網站">本地圖書館網站</h2> + +<p><em>我們接下來將創建,並隨著本系列教程發展的網站,名字是本地圖書館。如同你的預測,此網站的目的,是為一間小型本地圖書館,提供一個線上目錄,使用者能夠瀏覽可取得的書本</em>,並管理他們的帳号。</p> + +<p>本範例經過細心地考慮,因為它的規模可以放大或縮小,以配合我們的需要,演示盡可能多或少的細節。並且可以用來演示幾乎所有的 Express 特性。更重要的,它允許我們提供一條引導路徑,演示你在任何網站都會需要的功能:</p> + +<ul> + <li>在教程一開始,我們將定義一個簡單的、只能瀏覽的圖書館,圖書館成員能夠用來找到可以借的書。這允許我們找出,幾乎每個網站都會使用的共同操作: 從數据庫讀取並呈現內容。</li> + <li>跟隨教程的進展,圖書館的例子會一步一步擴充,以演示更高級的網站特征。比如我們會擴充圖書館,允許新書能夠被創建,並用這個來演示如何使用表單,並支持使用者授權。</li> +</ul> + +<p>即使這是一個具備相當擴充性的範例,它被叫做<strong>本地</strong>圖書館是有原因的 — 我們希望呈現給你最少的信息,能夠盡快幫助你上手並運行Express。因此,我們將會存放書本、複本、作者、和其它關鍵信息。然而,我們不會存放其它圖書館可能用到的有關信息,或者提供支持多個圖書館網站的架構,又或者其它 "大型圖書館" 的特性。</p> + +<h2 id="我被卡住了,哪裡可以得到原始碼">我被卡住了,哪裡可以得到原始碼?</h2> + +<p>當你使用本教程,我們將在每個知識點,提供適當的代碼片段,讓你複制貼上,同時有些代碼,我們希望你能自己擴充 (會有一些指引)。</p> + +<p>如果你被卡住了,你可以在 <a class="external external-icon" href="https://github.com/mdn/express-locallibrary-tutorial" rel="noopener">Github 的這裡</a>,找到本地圖書館網站已經開發完成的版本。</p> + +<div class="note"> +<p><strong>注意:</strong> 在本教程中,指定版本的 node、Express、還有其它模組,都經過測試,並列出在專案項目的 <a class="external external-icon" href="https://github.com/mdn/express-locallibrary-tutorial/blob/master/package.json" rel="noopener">package.json</a> 檔案中。</p> +</div> + +<h2 id="總結Edit">總結<a class="button section-edit only-icon" href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Express_Nodejs/Tutorial_local_library_website$edit#總結" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>現在,你對本地圖書館網站以及將要學習的東西,有更多一點的認識,是時候開始創建一個 <a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/skeleton_website">骨架項目</a>,以存放我們的範例。</p> + +<p> </p> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<p> </p> + +<h2 id="本系列教學">本系列教學</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/zh-tw/learn/server-side/first_steps/index.html b/files/zh-tw/learn/server-side/first_steps/index.html new file mode 100644 index 0000000000..2afd7bb1d4 --- /dev/null +++ b/files/zh-tw/learn/server-side/first_steps/index.html @@ -0,0 +1,41 @@ +--- +title: 伺服器端程式設計起步走 +slug: Learn/Server-side/First_steps +translation_of: Learn/Server-side/First_steps +--- +<div>{{LearnSidebar}}</div> + +<p>在我們的伺服器端程式設計模組內,我們會回答一些關於伺服器端編程的問題──「那是什麼?」、「它和用戶端程式設計有何不同?」、還有「為什麼它有用?」。接著,我們會比較各大熱門框架、並佐以一些如何選擇最適合框架的指引。最後,我們還會提供關於伺服器安全的進階介紹性文章。</p> + +<p>(譯者 iigmir 註:你可能常常聽到網路開發有前端與後端。在網路開發的脈落下,這裡講的伺服器端程式設計,就是俗稱的後端。)</p> + +<h2 id="先決條件">先決條件</h2> + +<p>在開始本模組前,你不需要擁有任何與伺服器端、或其他種類的程式設計相關知識。</p> + +<p>你必須知道「網路如何運作」。關於此,我們推薦以下主題:</p> + +<ul> + <li><a href="/zh-TW/docs/Learn/Common_questions/What_is_a_web_server">何謂網路伺服器</a></li> + <li><a href="/zh-TW/docs/Learn/Common_questions/What_software_do_I_need">我需要什麼軟體來建立網站?</a></li> + <li><a href="/zh-TW/docs/Learn/Common_questions/Upload_files_to_a_web_server">如何把檔案上傳到網路伺服器?</a></li> +</ul> + +<p>有了基本理解後,你就可以透過本章節的模組來完成工作。</p> + +<h2 id="指引">指引</h2> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/First_steps/Introduction">介紹伺服器端</a></dt> + <dd>歡迎來到 MDN 初學者的伺服器端程式設計課程!在這首篇文章中,我們將以很高的角度回答諸如「這是什麼?」、「它和用戶端程式設計有何不同?」、還有「為什麼它有用?」之類的問題。讀完以後,你會明白很多關於伺服器端程式設計的知識。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/First_steps/Client-Server_overview">用戶端概覽</a></dt> + <dd>現在你知道了伺服器端程式設計的目標與益處,而我們現在要檢驗一些細節:當伺服器從瀏覽器那邊收到「動態請求」的時候,究竟發生了什麼事。因為大多數網站都用相近的方法處理請求與回應,所以這一點會幫助你理解自己在撰寫程式碼的時候要幹什麼。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/First_steps/Web_frameworks">伺服器端網路框架</a></dt> + <dd>最後一篇文章介紹了伺服器端網路程式,為了回應來自瀏覽器的請求,究竟需要些什麼。現在,我們會告訴你網路框架如何簡化那些工作,並幫助你選定自己的第一個網路程式,要用上什麼樣的框架。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/First_steps/Website_security">網站安全</a></dt> + <dd>網站安全,有賴網頁設計時的高度警覺。這篇概要性的文章不是要讓你變成網站安全大神,而是幫你理解在強化網路程式免受大多數威脅時,第一要務為何。</dd> +</dl> + +<h2 id="評估">評估</h2> + +<p>這份「概覽」模組不做任何評估,因為我們還沒有給你看過任何程式碼。我們希望到了這裡,你可以對伺服器端程式設計能提供什麼東西,有者良好的理解;我們也希望你能在建立第一個網站時要用什麼框架的方面,能夠下好決定。</p> diff --git a/files/zh-tw/learn/server-side/first_steps/介紹/index.html b/files/zh-tw/learn/server-side/first_steps/介紹/index.html new file mode 100644 index 0000000000..a0919697ee --- /dev/null +++ b/files/zh-tw/learn/server-side/first_steps/介紹/index.html @@ -0,0 +1,225 @@ +--- +title: 伺服器端的介紹 +slug: Learn/Server-side/First_steps/介紹 +translation_of: Learn/Server-side/First_steps/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</div> + +<p class="summary"><span class="seoSummary">歡迎來到MDN伺服器端程式設計的初學者課程 !在第一篇文章中,我們會用較為抽象的角度來探討 server-side programming,並且為你解答「這是什麼?」「這個和用戶端的程式有什麼不同?」以及「這個有什麼用?」 。在讀完這篇文章後,你將能明白如何透過 server-side coding 來為你的網站增添力量。</span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先決條件:</th> + <td>基本電腦知識、對網路伺服器有基本了解。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>認識伺服器端的程式設計、它可以做什麼、它和用戶端的程式有什麼不一樣?</td> + </tr> + </tbody> +</table> + +<p>大多數的大型網站使用伺服器端程式(server-side code)來動態地顯示各種所需的資料,普遍的做法為從伺服端的資料庫中取出資料,並送至用戶端,再透過一些 code 來顯示它們(例如:HTML 與 JavaScript)。</p> + +<p>也許,使用伺服器端程式的最大好處是為不同的瀏覽者量身打造網頁內容。動態網站根據使用者的偏好設定及興趣提供更為相關的內容,也可以儲存個人設定及資訊讓網站更易於使用 — 例如重複使用已儲存的信用卡資料來使付款流程更為順暢。</p> + +<p>它也能讓網站透過信件或其他方式來和使用者互動,如發送通知與更新。這一切的一切都讓網站更能牢牢抓住使用者的心。</p> + +<h2 id="何謂伺服器端網站程式開發?">何謂伺服器端網站程式開發?</h2> + +<p>網頁瀏覽器使用超文本傳輸協定(<strong>H</strong>yper<strong>T</strong>ext <strong>T</strong>ransfer <strong>P</strong>rotocol, {{glossary("HTTP")}})與網頁伺服器(<a href="/zh-TW/docs/Learn/Common_questions/What_is_a_web_server">web servers</a>)溝通。當您點選網頁上的連結、送出表單,或者執行搜尋,一段<strong> HTTP 請求</strong>(<strong>request</strong>)會由您的瀏覽器送至目標伺服器。</p> + +<p>該請求(request)包含一個用來指定受影響資源的 URL、一個定義行為的請求方法(例如對資源進行get、delete或post)與當進行<a href="/zh-TW/docs/Web/HTTP/Methods/POST">HTTP POST方法</a>時可能包含編碼於URL參數中的額外資訊(經由一段<a href="https://en.wikipedia.org/wiki/Query_string">查詢字串</a>送出的各個鍵值對),或是在關聯的{{glossary("Cookie", "cookies")}}中。</p> + +<p>網頁伺服器等待用戶端的請求訊息、獲得後處理它們,並以一個<strong>HTTP回應</strong>(<strong>response</strong>)訊息回覆至網頁瀏覽器。該回應包含一個狀態訊息說明本次請求是否達成(例如:"HTTP/1.1 200 OK"表示成功)。</p> + +<p>成功對應於一個請求的回應主體(response body)應包含請求的資源(例如:一份新的HTML頁面或一張圖片等),這些可能將被用來顯示在網頁瀏覽器中。</p> + +<h3 id="靜態網站">靜態網站</h3> + +<p>以下的靜態網站(static site)圖展示一個基本的網頁伺服器架構,其中靜態網站意謂當無論何時有個特定資源的請求,伺服器始終回傳相同的硬編碼內容(hard-coded content)。當一個使用者想要引導到一個網頁時,瀏覽器送出的HTTP "GET" 請求指的就是該資源的URL。</p> + +<p>此伺服器從它的檔案系統取回被請求的文件,並回傳一個包含此文件以及<a href="/zh-TW/docs/Web/HTTP/Status#成功回應">成功狀態碼</a>(通常為200 OK)的HTTP回應。若檔案因某些原因無法被取回,則回傳一個錯誤狀態(參見 <a href="/zh-TW/docs/Web/HTTP/Status#用戶端錯誤回應">用戶端錯誤回應</a> 與 <a href="/zh-TW/docs/Web/HTTP/Status#伺服器端錯誤回應">伺服器端錯誤回應</a>)。</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png" style="height: 223px; width: 800px;"></p> + +<h3 id="動態網站">動態網站</h3> + +<p>一個動態網站的回應內容是當需要時<em>動態</em>產生的。在一個動態網站的HTML網頁通常是經由資料庫取得並插入資料至HTML範本的佔位符(placeholders)中而創造出來(相較於靜態網站,這對於儲存大量內容而言,這是一種相當有效率的做法)。 </p> + +<p>一個動態網站可以根據使用者或已存偏好設定提供的URL資訊回傳不同的資料,也可以以其他的作用方式呈現回應(例如:發送通知)。</p> + +<p>用來支援一個動態網站的大部分的程式碼必須在伺服器執行。建立程式碼的方式稱為"<strong>伺服端程式設計(server-side programming)</strong>"或"<strong>後端腳本(back-end scripting)</strong>"。</p> + +<p>下圖為<em>動態網站</em>(<em>dynamic website</em>)的基本架構。如同先前的圖說,瀏覽器發送HTTP請求至伺服器,接著伺服器處理請求後,回傳合適的HTTP回應。</p> + +<p>對於<em>靜態</em>資源的請求處理方式如同靜態網站的方式(靜態資源為任何不會改變的檔案 — 通常為CSS、JavaScript、圖片、預產生的PDF檔案等)。 </p> + +<p><img alt="A simplified diagram of a web server that uses server-side programming to get information from a database and construct HTML from templates. This is the same diagram as is in the Client-Server overview." src="https://mdn.mozillademos.org/files/13839/Web%20Application%20with%20HTML%20and%20Steps.png"></p> + +<p>對於動態資源的請求方式則為轉送(2)至伺服端程式碼(如圖中的<em>網頁應用程式 Web Application</em>)。對於"動態請求",伺服器解釋該請求、從資料庫讀取所需資訊(3)、與HTML範本結合取得的資料(4),最後送回一個包含已產生HTML的回應(5,6)。</p> + +<div> +<h2 id="伺服端與用戶端程式設計是相同的嗎?">伺服端與用戶端程式設計是相同的嗎?</h2> +</div> + +<p>讓我們把注意力集中在伺服端與用戶端的程式設計吧!在以下的每個案例中,程式碼完全不一樣:</p> + +<ul> + <li>他們有不同的目的與考量。</li> + <li>一般而言,他們不使用相同的程式語言(除了JavaScript可以使用在伺服端與用戶端以外)。</li> + <li>他們在不同的作業系統環境下執行。</li> +</ul> + +<p>執行在瀏覽器的程式碼被稱為<strong>用戶端程式碼</strong>(<strong>client-side code</strong>),它主要用來改善一個渲染的網頁外觀與行為。這包含選取與設定UI元件樣式、建立佈局、導覽、表格驗證等。相對的,伺服端網站程式設計大量涉及要回傳<em>哪些內容</em>給瀏覽器做為對請求的回應。伺服端程式碼處理驗證已提交的資料與請求、使用資料庫儲存及取得資料,和按需求發送正確的資料給用戶等任務。</p> + +<p>用戶端程式碼以<a href="/zh-TW/docs/Learn/HTML">HTML</a>、<a href="/zh-TW/docs/Learn/CSS">CSS</a>與<a href="/zh-TW/docs/Learn/JavaScript">JavaScript</a>撰寫 — 它執行在網頁瀏覽器內,並且僅有或無訪問底層的作業系統(包含對檔案系統的有限存取)。</p> + +<p>網頁開發者不能控制使用者可能使用何種瀏覽器來檢視一個網站 — 瀏覽器與用戶端程式有著不同層度的相容性,並且用戶端程式的挑戰之一是如何妥善地處理瀏覽器支援的差異。</p> + +<p>伺服端程式碼可以為任何程式語言 — 例如有名的伺服端網頁語言包括PHP、Python、Ruby、C#與NodeJS(JavaScript)。該伺服端程式碼擁有完整的作業系統存取權限,而且開發者能夠選擇他們想要的程式語言(以及特定版本)。</p> + +<p>開發者們通常使用<strong>網頁框架</strong>(<strong>web frameworks</strong>)撰寫程式碼。網頁框架為功能函式、物件、規則與其他程式碼的集合,旨在解決常見問題、加速開發並簡化在特定域中面臨到的不同類型的任務。</p> + +<p>再者,儘管用戶端與伺服端程式碼都使用框架,但會因為非常不同的域,而使得框架也不同。用戶端網頁框架簡化佈局與呈現的任務,而伺服端網頁框架則提供大量"通用"的網頁伺服器功能,否則你可能必須要自己實現(例如:對sessions的支援、對使用者認證的支援、簡易資料庫存取、樣板庫等)。</p> + +<div class="note"> +<p><strong>Note</strong>: Client-side frameworks are often used to help speed up development of client-side code, but you can also choose to write all the code by hand; in fact, writing your code by hand can be quicker and more efficient if you only need a small, simple web site UI.</p> + +<p>In contrast, you would almost never consider writing the server-side component of a web app without a framework — implementing a vital feature like an HTTP server is really hard to do from scratch in say Python, but Python web frameworks like Django provide one out of the box, along with other very useful tools.</p> +</div> + +<div> +<h2 id="在伺服端,你能做什麼?">在伺服端,你能做什麼?</h2> + +<p>伺服端程式設計是非常有用的,因為它讓我們有效地遞送替單個使用者量身訂做的資訊,從而創造更棒的使用者體驗。</p> +</div> + +<p>如Amazon這樣的公司使用伺服端程式設計來建構產品搜尋結果、根據顧客偏好與過往購物習慣提供針對性的產品建議、簡化購物過程等。</p> + +<p>銀行使用伺服端程式設計來儲存帳號資訊,並讓已授權用戶檢視與進行交易。其他服務如Facebook、Twitter、Instagram與Wikipedia使用伺服端程式設計來突顯、分享與控制使用者存取到感興趣的內容。</p> + +<p>一些常見的伺服端程式設計使用案例與效益列舉如下。您將會注意到這當中會有些重疊的部分!</p> + +<h3 id="高效率資訊儲存與遞送">高效率資訊儲存與遞送</h3> + +<p>想像一下,在Amazon可以找到多少產品,或者說在Facebook上有多少文章?對各個產品或文章建立各別的靜態網頁完全是不切實際的。</p> + +<p>伺服端程式設計反而是可以讓我們將資訊儲存至資料庫,並且動態建構及回傳HTML與其他型態的檔案(例如:PDF、圖片等)。它也可以藉由合適的用戶端網頁框架(利用這個方式可以降低在伺服器的處理負擔,亦減少需要被送出的大量資料)僅回傳資料({{glossary("JSON")}}、{{glossary("XML")}}等)來進行畫面渲染。</p> + +<p>伺服器並不侷限於從資料庫發送資訊,還可以回傳軟體工具的結果或是來自通訊服務的資料。這些內容甚至可以針對到收到它的用戶裝置類型。</p> + +<p>由於資訊存在於資料庫中,它可以輕易地與其他商業系統進行分享與更新(例如:當產品在線上或在店家中售完,店家可能會更新該產品的庫存資料庫)。</p> + +<div class="note"> +<p><strong>Note</strong>: Your imagination doesn't have to work hard to see the benefit of server-side code for efficient storage and delivery of information:</p> + +<ol> + <li>Go to <a href="https://www.amazon.com">Amazon</a> or some other e-commerce site.</li> + <li>Search for a number of keywords and note how the page structure doesn't change, even though the results do. </li> + <li>Open two or three different products. Note again how they have a common structure and layout, but the content for different products has been pulled from the database.</li> +</ol> + +<p>For a common search term ("fish", say) you can see literally millions of returned values. Using a database allows these to be stored and shared efficiently, and it allows the presentation of the information to be controlled in just one place.</p> +</div> + +<h3 id="客製化的使用者體驗">客製化的使用者體驗</h3> + +<p>伺服器能保存及使用關於用戶的資訊,來提供一個方便且量身訂做的使用者體驗。例如,許多網站儲存信用卡資料讓這些資料無須再重新輸入。網站如Google Maps能使用已儲存或目前位置來提供導航資訊與搜尋或旅行歷史紀錄,以便於搜尋結果中突顯在地店家。</p> + +<p>一個使用者習慣更深層的分析,可以使用在預測他的興趣以及更進一步客製回應與提醒,例如在地圖中提供你可能想去看得過去遊歷過的或是熱門的地點列表。</p> + +<div class="note"> +<p><strong>Note: </strong><a href="https://maps.google.com/">Google Maps</a> saves your search and visit history. Frequently visited or frequently searched locations are highlighted more than others.</p> + +<p>Google search results are optimized based on previous searches.</p> + +<ol> + <li> Go to <a href="https:\\google.com">Google search</a>.</li> + <li> Search for "football".</li> + <li> Now try typing "favourite" in the search box and observe the autocomplete search predictions.</li> +</ol> + +<p>Coincidence? Nada!</p> +</div> + +<h3 id="控制內容存取">控制內容存取</h3> + +<p>伺服器端程式設計允許網站限制僅能由已授權的使用者存取,並提供資訊給那些只被允許觀看的使用者。</p> + +<p>真實世界案例包括:</p> + +<ul> + <li>社交網路如Facebook允許使用者全權控制他們自己的資料,但是只有允許他們的朋友檢視或評論資料。該使用者決定誰能看到他們的資料,並推廣到他們的動態中會呈現誰的資料 — 授權是使用者體驗的核心部分!</li> + <li> + <p>你正所在的網站控制內容的存取:文章可被任何人看見,但是只有使用者登入才能編輯該內容。若要試試看,點擊本頁面上方的<strong>編輯</strong>按鈕 — 如果你已登入,你將會顯示編輯視窗;如果你沒有登入,你將會被導到註冊頁面。</p> + </li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Consider other real examples where access to content is controlled. For example, what can you see if you go to the online site for your bank? Log in to your account — what additional information can you see and modify? What information can you see that only the bank can change?</p> +</div> + +<h3 id="儲存sessionstate資訊">儲存session/state資訊</h3> + +<p>伺服器端程式設計允許開發者利用<strong>sessions</strong> — 基本上,就是一個機制讓伺服器儲存目前的使用者資訊,並且基於這些資訊發送不同的回應。</p> + +<p>例如,這允許網站了解一個使用者先前已登入過,以及將訂購歷史紀錄在他們的電子郵件中顯示連結,或者也許會儲存一個基本的遊戲狀態,讓使用者能再次回到網站的同時,拿回他們留在網站的資訊。</p> + +<div class="note"> +<p><strong>Note</strong>: Visit a newspaper site that has a subscription model and open a bunch of tabs (e.g. <a href="http://www.theage.com.au/">The Age</a>). Continue to visit the site over a few hours/days. Eventually, you will start to be redirected to pages explaining how to subscribe, and you will be unable to access articles. This information is an example of session information stored in cookies.</p> +</div> + +<h3 id="提醒與溝通">提醒與溝通</h3> + +<p>伺服器能透過網站本身或經由電子郵件、SMS、即時通訊、影像或其他通訊服務,發送提醒訊息給一般或特定使用者。</p> + +<p>一些範例包括:</p> + +<ul> + <li>Facebook與Twitter發送電子郵件與SMS訊息等新的通訊來提醒你。</li> + <li>Amazon定期發送產品電子郵件來提供相近於曾經已購買或你可能有興趣瀏覽的產品。</li> + <li>一個網頁伺服器可能會發送警告訊息給網站管理員,以警示伺服器的記憶體過低或有嫌疑的使用者型為。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: The most common type of notification is a "confirmation of registration". Pick almost any large site that you are interested in (Google, Amazon, Instagram, etc.) and create a new account using your email address. You will shortly receive an email confirming your registration, or requiring acknowledgment to activate your account.</p> +</div> + +<h3 id="資料分析">資料分析</h3> + +<p>一個網站可能會收集很多包括使用者的資料:他們所搜尋的、他們所買的、他們所推薦的、他們在每個網頁停留的時間。伺服器端程式設計能根據資料分析以完善回應。</p> + +<p>例如,Amazon與Google都根據過往搜尋(與購買)紀錄來廣告產品。</p> + +<div class="note"> +<p><strong>Note</strong>: If you're a Facebook user, go to your main feed and look at the stream of posts. Note how some of the posts are out of numerical order - in particular, posts with more "likes" are often higher on the list than more recent posts.</p> + +<p>Also look at what kind of ads you are being shown — you might see ads for things you looked at on other sites. Facebook's algorithm for highlighting content and advertising can be a bit of a mystery, but it is clear that it does depend on your likes and viewing habits!</p> +</div> + +<h2 id="總結">總結</h2> + +<p>恭喜,你已經到達關於伺服器端程式設計的第一篇文章的結尾。 </p> + +<p>現在你已經學到伺服器端程式碼運作於網頁伺服器,他的主要任務是控制<em>哪些</em>資訊要發送給使用者(而用戶端程式碼主要掌握資料的結構與呈現給使用者)。</p> + +<p>你也應該了解這是很有用的,當你身為伺服器端開發者時,因為它允許我們創建<em>有效</em>散播客製訊息與有些你可能會去做的好點子給單個使用者的網站。</p> + +<p>最後,你應該了解伺服器端程式碼可以用很多種程式語言來撰寫,以及你應該使用網頁框架來讓整個程序變得更簡便。 </p> + +<p>在未來的文章,我們將協助你選擇最佳的網頁框架,做為你的第一個網站;接著,我們將帶你更詳細了解主要的用戶端-伺服端的互動。</p> + +<p>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li> +</ul> diff --git a/files/zh-tw/learn/server-side/index.html b/files/zh-tw/learn/server-side/index.html new file mode 100644 index 0000000000..c62f4e8aba --- /dev/null +++ b/files/zh-tw/learn/server-side/index.html @@ -0,0 +1,59 @@ +--- +title: 伺服端網站程式設計 +slug: Learn/Server-side +tags: + - Beginner + - CodingScripting + - Intro + - Landing + - Learn + - NeedsTranslation + - Server + - Server-side programming + - Topic + - TopicStub +translation_of: Learn/Server-side +--- +<div>{{LearnSidebar}}</div> + +<p class="summary"><strong><em>動態網站</em></strong>–<em><strong>伺服端網站程式設計</strong></em>是一連串有關如何建立動態網站的模塊:動態網站可以針對 HTTP 請求,發送客製化的資訊。這些模塊將介紹伺服端網站程式設計:還有以初學者的角度,來教你怎麼使用 Django (Python) 與 Express (Node.js/JavaScript) 來架設基本的動態網路程式。</p> + +<p>大多數主流網站會使用伺服端技術,以根據需要呈現動態資料。例如說,來想想亞馬遜(Amazon)上架多少商品、還有臉書(Facebook)貼了多少動態。如果都用靜態頁面來呈現這些內容,開發就會毫無效率可言。因此,我們會使用靜態技術(<a href="/zh-TW/docs/Learn/HTML">HTML</a>、<a href="/zh-TW/docs/Learn/CSS">CSS</a>、<a href="/zh-TW/docs/Learn/JavaScript">JavaScript</a>)來顯示靜態模板;並在需要時,動態更新模板內的資料。一如你在逛亞馬遜時,看著五花八門的產品一般。</p> + +<p>在當今的 Web development 的世界,我們強烈建議學習怎麼開發伺服端網站程式。</p> + +<h2 id="學習路徑">學習路徑</h2> + +<p>學習伺服端網站程式設計通常比用戶端網站程式設計簡單,因為動態網站比較傾向執行多次相似的操作(像是從資料庫擷取資料並放到頁面上、驗證用戶輸入的資料並存到資料庫、檢查登入用戶權限之類的)、使用框架建立網站能讓上述操作、以及其他常見操作變得簡單許多。</p> + +<p>基本的程式概念(或是理解特定的語言)會很有用,但不是必須的。同樣地,精於用戶端網站程式設計不是必須,但它能在前端開發時,幫你做得更好。</p> + +<p>首先你要知道「web 是怎麼作動的」。我們建議先看看這些文章:</p> + +<ul> + <li><a href="/zh-TW/docs/Learn/Common_questions/What_is_a_web_server">何謂網路伺服器?</a></li> + <li><a href="/zh-TW/docs/Learn/Common_questions/What_software_do_I_need">建立網站需要什麼軟體?</a></li> + <li><a href="/zh-TW/docs/Learn/Common_questions/Upload_files_to_a_web_server">如何把檔案傳到伺服器?</a></li> +</ul> + +<p>有了基本觀念後,就可以開始去學習模塊章節的東西了。</p> + +<h2 id="模塊">模塊</h2> + +<p>本章節包含了以下模塊。你首先要從第一個模塊開始,再循序漸進,學習接下來的模塊。這些模塊將告訴你如何與訪間最熱門的其中兩個伺服器端框架共事。</p> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/First_steps">伺服器端程式設計起步走</a></dt> + <dd>本模塊會提供與技術無關的伺服器資訊,像是「那什麼?」、「和用戶端有啥不同?」、「有用嗎?」之類的。本模塊也會概述一些比較熱門的伺服器端 web 框架、並告訴你如何選擇。最後,我們還會概述有關伺服器服務的安全性問題。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Django">Django 網站框架 (Python)</a></dt> + <dd>Django 以 Python 寫成,是個非常熱門的伺服器端 web 框架。本模塊會講解 Django 是好框架的理由、如何建立開發環境、還有如何處理常見工作。</dd> + <dt><a href="/zh-TW/docs/Learn/Server-side/Express_Nodejs">Express web framework (Node.js/JavaScript)</a></dt> + <dd>Express 以 JavaScript 寫成、並在 node.js 執行環境執行。它也是個非常熱門的伺服器端 web 框架。本模塊會講解一些有關本框架的重要優點、也同樣會講解如何建立開發環境、還有如何處理常見工作。</dd> +</dl> + +<h2 id="參見">參見</h2> + +<dl> + <dt><a href="/zh-TW/docs/Learn/Server-side/Node_server_without_framework">不用框架的 Node 伺服器</a></dt> + <dd>如果不想用框架的話,這篇文章會告訴你如何使用純 Node.js 提供簡易的靜態檔案。</dd> +</dl> |