aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/server-side
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/learn/server-side
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/learn/server-side')
-rw-r--r--files/zh-cn/learn/server-side/django/authentication/index.html710
-rw-r--r--files/zh-cn/learn/server-side/django/deployment/index.html675
-rw-r--r--files/zh-cn/learn/server-side/django/django_assessment_blog/index.html313
-rw-r--r--files/zh-cn/learn/server-side/django/forms/index.html671
-rw-r--r--files/zh-cn/learn/server-side/django/generic_views/index.html630
-rw-r--r--files/zh-cn/learn/server-side/django/index.html60
-rw-r--r--files/zh-cn/learn/server-side/django/introduction/index.html268
-rw-r--r--files/zh-cn/learn/server-side/django/models/index.html449
-rw-r--r--files/zh-cn/learn/server-side/django/sessions/index.html190
-rw-r--r--files/zh-cn/learn/server-side/django/skeleton_website/index.html372
-rw-r--r--files/zh-cn/learn/server-side/django/testing/index.html917
-rw-r--r--files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html70
-rw-r--r--files/zh-cn/learn/server-side/django/web_application_security/index.html180
-rw-r--r--files/zh-cn/learn/server-side/django/主页构建/index.html358
-rw-r--r--files/zh-cn/learn/server-side/django/开发环境/index.html406
-rw-r--r--files/zh-cn/learn/server-side/django/管理站点/index.html339
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/deployment/index.html526
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html390
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html89
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html89
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html112
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html70
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html91
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html69
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html58
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html139
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html120
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html134
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html87
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html69
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html149
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html155
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html212
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html150
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html298
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html165
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/index.html286
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html189
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/index.html72
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html242
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/introduction/index.html525
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html831
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/routes/index.html425
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html476
-rw-r--r--files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html93
-rw-r--r--files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html317
-rw-r--r--files/zh-cn/learn/server-side/first_steps/index.html46
-rw-r--r--files/zh-cn/learn/server-side/first_steps/introduction/index.html239
-rw-r--r--files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html298
-rw-r--r--files/zh-cn/learn/server-side/first_steps/website_security/index.html165
-rw-r--r--files/zh-cn/learn/server-side/index.html41
51 files changed, 14025 insertions, 0 deletions
diff --git a/files/zh-cn/learn/server-side/django/authentication/index.html b/files/zh-cn/learn/server-side/django/authentication/index.html
new file mode 100644
index 0000000000..c6536c3309
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/authentication/index.html
@@ -0,0 +1,710 @@
+---
+title: 'Django 教程 8: 用户授权与许可'
+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">在<font><font>本教程中,我们将向您展示如何允许用户使用自己的帐户登录到您的网站,以及如何根据用户是否已登录及其</font></font><em><font><font>权限</font></font></em><font><font>来控制他们可以执行和查看的内容</font><font>。</font><font>作为演示的一部分,我们将扩展</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,添加登录页面和注销页面,以及用户和员工特定的页面以查看已借阅的图书</font></font>。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>完成之前的所有教程主题,包括<a href="/zh-CN/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 提供了一个身份验证和授权(“权限”)系统,该系统构建在<a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">上一个教程</a>中讨论的会话框架之上,允许您验证用户凭据,并定义每个用户可允许执行的操作。该框架包括用户<code>Users</code>和分组<code>Groups</code>的内置模型(一次向多个用户应用权限的通用方法),用于登录用户的权限/标志,以指定用户是否可以执行任务,表单和视图,以及查看限制内容的工具。</p>
+
+<div class="note">
+<p><strong>注意</strong>: Django身份验证系统的目标非常通用,因此不提供其他Web身份验证系统中,所提供的某些功能。某些常见问题的解决方案,可作为第三方软件包提供。例如,限制登录尝试,和针对第三方的身份验证(例如 OAuth)。</p>
+</div>
+
+<p>在本教程中,我们将向您展示,如何在<a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>网站中,启用用户身份验证,创建您自己的登录和注销页面,为模型添加权限,以及控制对页面的访问。我们将使用身份验证/权限,来显示用户和图书馆员借用图书的列表。</p>
+
+<p>身份验证系统非常灵活,您可以根据需要,从头开始构建 URLs,表单,视图和模板,只需调用提供的API,即可登录用户。但是,在本文中,我们将在登录和注销页面,使用 Django 的“库存” 身份验证视图和表单。我们仍然需要创建一些模板,但这很简单。</p>
+
+<p>我们还将向您展示如何创建权限,以及检查视图和模板中的登录状态和权限。</p>
+
+<h2 id="启用身份验证">启用身份验证</h2>
+
+<p>我们在<a href="/zh-CN/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">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="创建用户和分组">创建用户和分组</h2>
+
+<p>在教程 4 中,当我们查看 <a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django 管理站点</a>时,您已经创建了第一个用户(这是一个超级用户,使用命令 <code>python manage.py createsuperuser </code>创建)。我们的超级用户已经过身份验证,并拥有所有权限,因此我们需要创建一个测试用户,来代表普通网站用户。我们将使用管理站点,来创建我们的 locallibrary 组別和网站登录,因为这是最快的方法之一。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 您还可以用编程方式创建用户,如下所示。您会必须这样做,例如,如果要开发一个界面,能允许用户创建自己的登录(您不应该授予用户访问管理站点的权限)。</p>
+
+<pre class="brush: python">from django.contrib.auth.models import User
+
+# Create user and save to the database
+user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')
+
+# Update fields and then save again
+user.first_name = 'John'
+user.last_name = 'Citizen'
+user.save()
+</pre>
+</div>
+
+<p>下面,我们首先创建一个分组,然后创建一个用户。即使我们还没有为我们的图书馆成员添加任何权限,如果我们以后需要,将它们添加到分组中,要比单独添加到每个成员要容易得多。</p>
+
+<p>启动开发服务器,并到本地 Web 浏览器中的管理站点(<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>)。使用超级用户帐户的凭据,登录该站点。 Admin 站点的最上级显示所有模型,按 “django application” 排序。在 “身份验证和授权” <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>按钮(“分组” Group 旁边)以创建新的分组;在分组的名称<strong> Name</strong> ,输入“Library Members”。<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>单击“用户”旁边的“添加”按钮 <strong>Add</strong>,以打开“添加用户”对话框。<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>为测试用户输入适当的用户名(<strong>Username)</strong>和密码/密码确认<strong>(Password/Password confirmation</strong> )</li>
+ <li>按 <strong>SAVE</strong> 创建用户。
+ <p> </p>
+
+ <p>管理站点将创建新用户,并立即转到 “更改用户” 屏幕,您可以在其中更改用户名(<strong>username</strong>),并添加用户模型的可选字段的信息。这些字段包括名字,姓氏,电子邮件地址,用户状态和权限(仅应设置活动标志<strong> Active</strong>)。再往下,您可以指定用户的分组和权限,并查看与用户相关的重要日期(例如,他们的加入日期和上次登录日期)。</p>
+ <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>在“分组”(<em>Groups</em>)部分中,从“可用分组”(<em>Available groups</em>)列表中,选择“图书馆成员”分组 <strong>Library Member</strong>,然后点击这些框之间的<strong>右箭头</strong>,将其移动到“选择的分组”(<em>Chosen groups</em>)框中。<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="设置身份验证视图">设置身份验证视图</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="项目网址">项目网址</h3>
+
+<p>将以下内容,添加到项目 urls.py(<strong>locallibrary/locallibrary/urls.py</strong>)文件的底部:</p>
+
+<pre class="brush: python"># Use include() to add URLS from the catalog application and authentication system
+from django.urls import include
+
+#Add Django site authentication urls (for login, logout, password management)
+urlpatterns += [
+ path('accounts/', include('django.contrib.auth.urls')),
+]
+</pre>
+
+<p>打开 URL <a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a> (注意前面的斜杠!),Django将显示一个错误,它无法找到此URL,并列出它尝试过的所有URL。从中您可以看到可以使用的URL,例如:</p>
+
+<div class="note">
+<p><strong>注意: </strong>使用上面的方法,添加以下带有方括号中的名称的 URL,可用于反转 URL 映射。您不必实现任何其他内容 - 上面的 url 映射,会自动映射下面提到的URL。</p>
+</div>
+
+<div class="note">
+<pre class="brush: python">accounts/ login/ [name='login']
+accounts/ logout/ [name='logout']
+accounts/ password_change/ [name='password_change']
+accounts/ password_change/done/ [name='password_change_done']
+accounts/ password_reset/ [name='password_reset']
+accounts/ password_reset/done/ [name='password_reset_done']
+accounts/ reset/&lt;uidb64&gt;/&lt;token&gt;/ [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">Exception Type: TemplateDoesNotExist
+Exception Value: <strong>registration/login.html</strong></pre>
+
+<p>下一步是在搜索路径上创建注册目录,然后添加 <strong>login.html </strong>文件。</p>
+
+<h3 id="模板目录">模板目录</h3>
+
+<p>我们希望在模板搜索路径中的目录 <strong>/registration/</strong> 某处,找到刚刚添加的 url(以及隐式视图)的关联模板。</p>
+
+<p>对于此站点,我们将 HTML 页面,放在 <strong>templates/registration/ </strong>目录中。此目录应该位于项目的根目录中,即与 <strong>catalog </strong>和 <strong>locallibrary </strong>文件夹相同的目录)。请立即创建这些文件夹。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 您的文件夹结构,现在应如下所示:<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">TEMPLATES = [
+ {
+ ...
+<strong> 'DIRS': ['./templates',],</strong>
+ 'APP_DIRS': True,
+ ...
+</pre>
+
+<h3 id="登录模板">登录模板</h3>
+
+<div class="warning">
+<p><strong>重要说明</strong>: 本文提供的身份验证模板,是 Django 演示登录模板的基本/略微修改版本。您可能需要自定义它们,以供自己使用!</p>
+</div>
+
+<p>创建一个名为 <strong>/locallibrary/templates/registration/login.html</strong> 的新HTML文件。为它加入以下内容:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+
+{% if form.errors %}
+&lt;p&gt;Your username and password didn't match. Please try again.&lt;/p&gt;
+{% endif %}
+
+{% if next %}
+    {% if user.is_authenticated %}
+    &lt;p&gt;Your account doesn't have access to this page. To proceed,
+    please login with an account that has access.&lt;/p&gt;
+    {% else %}
+    &lt;p&gt;Please login to see this page.&lt;/p&gt;
+    {% endif %}
+{% endif %}
+
+&lt;form method="post" action="{% url 'login' %}"&gt;
+{% csrf_token %}
+
+&lt;div&gt;
+  &lt;td&gt;\{{ form.username.label_tag }}&lt;/td&gt;
+  &lt;td&gt;\{{ form.username }}&lt;/td&gt;
+&lt;/div&gt;
+&lt;div&gt;
+  &lt;td&gt;\{{ form.password.label_tag }}&lt;/td&gt;
+  &lt;td&gt;\{{ form.password }}&lt;/td&gt;
+&lt;/div&gt;
+
+&lt;div&gt;
+  &lt;input type="submit" value="login" /&gt;
+  &lt;input type="hidden" name="next" value="\{{ next }}" /&gt;
+&lt;/div&gt;
+&lt;/form&gt;
+
+{# Assumes you setup the password_reset view in your URLconf #}
+&lt;p&gt;&lt;a href="{% url 'password_reset' %}"&gt;Lost password?&lt;/a&gt;&lt;/p&gt;
+
+{% endblock %}</pre>
+
+<p>此模板与我们之前看到的模板,有一些相似之处 - 它扩展了我们的基本模板,并覆盖了内容区块 <code>content</code>。其余代码,是相当标准的表单处理代码,我们将在后面的教程中讨论。您现在需要知道的是,这将显示一个表单,您可以在其中输入您的用户名和密码,如果您输入的值无效,则会在页面刷新时,提示您输入正确的值。</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"># Redirect to home URL after login (Default redirects to /accounts/profile/)
+LOGIN_REDIRECT_URL = '/'
+</pre>
+
+<h3 id="登出模板">登出模板</h3>
+
+<p>如果您打开登出网址(<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>),那么您会看到一些奇怪的行为 - 您所属的用户肯定会被登出,但您将被带到管理员登出页面。这不是您想要的,只是因为该页面上的登录链接,将您带到管理员登录屏幕(并且仅对具有<code>is_staff</code>权限的用户可用)。</p>
+
+<p>创建并打开 /<strong>locallibrary/templates/registration/logged_out.html</strong>。将下面的文字,复制到文档中:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+&lt;p&gt;Logged out!&lt;/p&gt;
+
+&lt;a href="{% url 'login'%}"&gt;Click here to login again.&lt;/a&gt;
+{% 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="密码重置模板">密码重置模板</h3>
+
+<p>默认密码重置系统,使用电子邮件向用户发送重置链接。您需要创建表单,以获取用户的电子邮件地址,发送电子邮件,允许他们输入新密码,以及记录整个过程的完成时间。</p>
+
+<p>以下模板可作为起点。</p>
+
+<h4 id="密码重置表单">密码重置表单</h4>
+
+<p>这是用于获取用户电子邮件地址的表单(用于发送密码重置电子邮件)。创建 <strong>/locallibrary/templates/registration/password_reset_form.html</strong>,并为其提供以下内容:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+{% block content %}
+
+&lt;form action="" method="post"&gt;{% csrf_token %}
+ {% if form.email.errors %} \{{ form.email.errors }} {% endif %}
+ &lt;p&gt;\{{ form.email }}&lt;/p&gt;
+ &lt;input type="submit" class="btn btn-default btn-lg" value="Reset password" /&gt;
+&lt;/form&gt;
+
+{% endblock %}
+</pre>
+
+<h4 id="密码重置完成">密码重置完成</h4>
+
+<p>收集您的电子邮件地址后,会显示此表单。创建 <strong>/locallibrary/templates/registration/password_reset_done.html</strong>,并为其提供以下内容:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+{% block content %}
+&lt;p&gt;We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.&lt;/p&gt;
+{% endblock %}
+</pre>
+
+<h4 id="密码重置电子邮件">密码重置电子邮件</h4>
+
+<p>此模板提供 HTML 电子邮件的文本,其中包含我们将发送给用户的重置链接。创建 <strong>/locallibrary/templates/registration/password_reset_email.html</strong>,并为其提供以下内容:</p>
+
+<pre class="brush: html">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">{% extends "base_generic.html" %}
+
+{% block content %}
+
+ {% if validlink %}
+ &lt;p&gt;Please enter (and confirm) your new password.&lt;/p&gt;
+ &lt;form action="" method="post"&gt;
+ &lt;div style="display:none"&gt;
+ &lt;input type="hidden" value="\{{ csrf_token }}" name="csrfmiddlewaretoken"&gt;
+ &lt;/div&gt;
+ &lt;table&gt;
+ &lt;tr&gt;
+ &lt;td&gt;\{{ form.new_password1.errors }}
+ &lt;label for="id_new_password1"&gt;New password:&lt;/label&gt;&lt;/td&gt;
+ &lt;td&gt;\{{ form.new_password1 }}&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;\{{ form.new_password2.errors }}
+ &lt;label for="id_new_password2"&gt;Confirm password:&lt;/label&gt;&lt;/td&gt;
+ &lt;td&gt;\{{ form.new_password2 }}&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;&lt;/td&gt;
+ &lt;td&gt;&lt;input type="submit" value="Change my password" /&gt;&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/table&gt;
+ &lt;/form&gt;
+ {% else %}
+ &lt;h1&gt;Password reset failed&lt;/h1&gt;
+ &lt;p&gt;The password reset link was invalid, possibly because it has already been used. Please request a new password reset.&lt;/p&gt;
+ {% endif %}
+
+{% endblock %}
+</pre>
+
+<h4 id="密码重置完成_2">密码重置完成</h4>
+
+<p>这是最后一个密码重置模板,显示该模板,以在密码重置成功时通知您。创建 <strong>/locallibrary/templates/registration/password_reset_complete.html</strong>,并为其提供以下内容:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+{% block content %}
+
+&lt;h1&gt;The password has been changed!&lt;/h1&gt;
+&lt;p&gt;&lt;a href="{% url 'login' %}"&gt;log in again?&lt;/a&gt;&lt;/p&gt;
+
+{% endblock %}</pre>
+
+<h3 id="测试新的身份验证页面">测试新的身份验证页面</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>: 密码重置系统,要求您的网站支持电子邮件,这超出了本文的范围,因此该部分<strong>将无法使用</strong>。要测试此功能,请将以下一行放在 settings.py 文件的末尾。这会记录发送到命令行控制台的所有电子邮件(因此您可以从命令行控制台,复制密码重置链接)。</p>
+
+<pre class="brush: python">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+</pre>
+
+<p>有关更多信息,请参阅<a href="https://docs.djangoproject.com/en/2.0/topics/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"> &lt;ul class="sidebar-nav"&gt;
+
+ ...
+
+ <strong>{% if user.is_authenticated %}</strong>
+ &lt;li&gt;User: <strong>\{{ user.get_username }}</strong>&lt;/li&gt;
+ &lt;li&gt;&lt;a href="{% url 'logout'%}?next=\{{request.path}}"&gt;Logout&lt;/a&gt;&lt;/li&gt;
+ <strong>{% else %}</strong>
+ &lt;li&gt;&lt;a href="{% url 'login'%}?next=\{{request.path}}"&gt;Login&lt;/a&gt;&lt;/li&gt;
+ <strong>{% endif %} </strong>
+ &lt;/ul&gt;</pre>
+
+<p>如您所见,我们使用 <code>if</code>-<code>else</code>-<code>endif</code>模板标签,根据 <code>\{{ user.is_authenticated }}</code> 是否为 true ,来有条件地显示文本。如果用户已通过身份验证,那么我们知道,我们拥有有效用户,因此我们会调用 <strong>\{{ user.get_username }} </strong>,来显示其名称。</p>
+
+<p>我们使用 <code>url</code>模板标记,和相应 URL 配置的名称,创建登录和登出链接 URL。另外请注意,我们如何将 “<code>?next=\{{request.path}}</code>附加到URL的末尾。这样做,是将包含当前页面地址(URL)的URL参数,添加到链接URL的末尾。用户成功登录/登出后,视图将使用此“下一个”值,将用户重定向,回到他们首次单击登录/登出链接的页面。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 试试吧!如果您在主页上,并单击侧栏中的“登录/登出”,在操作完成后,您应该返回到同一页面。</p>
+</div>
+
+<h3 id="在视图中测试">在视图中测试</h3>
+
+<p>如果您正在使用基于函数的视图,则限制访问函数的最简单方法,是将<code>login_required</code>装饰器,应用于您的视图函数,如下所示。如果用户已登录,则您的视图代码将正常执行。</p>
+
+<p>如果用户未登录,则会重定向到项目设置 (<code>settings.LOGIN_URL</code>)中定义的登录URL,并将当前绝对路径,作为URL参数("下一个"<code>next</code>)来传递。如果用户成功登录,则会返回到此页面,但这次会进行身份验证。</p>
+
+<pre class="brush: python">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>派生。您需要在主视图类之前的超类列表中,首先声明此 mixin。</p>
+
+<pre class="brush: python">from django.contrib.auth.mixins import LoginRequiredMixin
+
+class MyView(LoginRequiredMixin, View):
+ ...</pre>
+
+<p>这与<code>login_required</code>装饰器,具有完全相同的重定向行为。如果用户未经过身份验证(<code>login_url</code>),还可以指定一个替代位置,以将用户重定向到该位置,并使用URL参数名称,而不是“<code>next</code>”,来插入当前绝对路径(<code>redirect_field_name</code>)。</p>
+
+<pre class="brush: python">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>,但这个模型和用户之间,没有任何关联。我们将使用<code>ForeignKey</code>(一对多)字段,来创建一个。我们还需要一个简单的机制,来测试借出的书是否过期。</p>
+
+<p>打开 <strong>catalog/models.py</strong>,然后从 <code>django.contrib.auth.models</code>  导入 <code>User</code>模型(在文件顶部的上一个导入行的正下方添加它,好让后续代码可以使用 <code>User</code>):</p>
+
+<pre class="brush: python">from django.contrib.auth.models import User
+</pre>
+
+<p>接下来将借用者字段<code>borrower</code>,添加到<code>BookInstance</code>模型:</p>
+
+<pre class="brush: python">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>
+
+<pre class="brush: python">from datetime import date
+
+@property
+def is_overdue(self):
+ if self.due_back and date.today() &gt; self.due_back:
+ return True
+ return False</pre>
+
+<div class="note">
+<p><strong>注意</strong>: 在进行比较之前,我们首先验证<code>due_back</code>是否为空。空的<code>due_back</code>字段,会导致Django抛出错误,而不是显示页面:空值不具有可比性。这不是我们希望用户体验到的东西!</p>
+</div>
+
+<p>现在我们已经更新了模型,我们需要对项目进行新的迁移,然后应用这些迁移:</p>
+
+<pre class="brush: bash">python3 manage.py makemigrations
+python3 manage.py migrate
+</pre>
+
+<h3 id="管理员">管理员</h3>
+
+<p>现在打开 <strong>catalog/admin.py</strong>,并将<code>borrower</code>字段,添加到<code>BookInstanceAdmin</code>类别中的<code>list_display</code>和<code>fieldsets</code>,如下所示。这将使该字段在Admin部分中可见,以便我们可以在需要时将<code>User</code>分配给<code>BookInstance</code>。</p>
+
+<pre class="brush: python">@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="借几本书">借几本书</h3>
+
+<p>现在可以将书本借给特定用户,然后借出一些<code>BookInstance</code>记录。将他们的借用字段<code>borrowed</code>,设置为您的测试用户,将状态<code>status</code>设置为 “On loan”,并在将来和过去设置截止日期。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 我们不会一步一步说明这个流程,因为您已经知道如何使用管理站点!</p>
+</div>
+
+<h3 id="在借书视图">在借书视图</h3>
+
+<p>现在我们将添加一个视图,以获取已经借给当前用户的所有书本列表。我们将使用我们熟悉的、基于类的通用类列表视图,但这次我们还将导入并派生自<code>LoginRequiredMixin</code>,以便只有登录用户才能调用此视图。我们还将选择声明<code>template_name</code>,而不是使用默认值,因为我们最终可能会有几个不同的 BookInstance 记录列表,其中包含不同的视图和模板。</p>
+
+<p>将以下内容添加到 catalog/views.py:</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>为了将查询,限制为当前用户的<code>BookInstance</code>对象,我们重新实现了<code>get_queryset()</code>,如上所示。请注意,“o”是表示借出当中“on loan”的存储代码,我们按<code>due_back</code>日期排序,以便首先显示最旧的项目。</p>
+
+<h3 id="借书的_URL_设置">借书的 URL 设置</h3>
+
+<p>现在打开<strong>/catalog/urls.py</strong>,并添加指向上面视图的<code>path()</code>(您只需将下面的文本复制到文件末尾)。</p>
+
+<pre class="brush: python">urlpatterns += [
+ path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
+]</pre>
+
+<h3 id="借书的模板">借书的模板</h3>
+
+<p>现在,我们需要为此页面添加一个模板。首先,创建模板文件<strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong>,并为其提供以下内容:</p>
+
+<pre class="brush: python">{% extends "base_generic.html" %}
+
+{% block content %}
+ &lt;h1&gt;Borrowed books&lt;/h1&gt;
+
+ {% if bookinstance_list %}
+ &lt;ul&gt;
+
+ {% for bookinst in bookinstance_list %}
+ &lt;li class="{% if bookinst.is_overdue %}text-danger{% endif %}"&gt;
+ &lt;a href="{% url 'book-detail' bookinst.book.pk %}"&gt;\{{bookinst.book.title}}&lt;/a&gt; (\{{ bookinst.due_back }})
+ &lt;/li&gt;
+ {% endfor %}
+ &lt;/ul&gt;
+
+ {% else %}
+ &lt;p&gt;There are no books borrowed.&lt;/p&gt;
+ {% 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>
+
+<p> </p>
+
+<h3 id="将列表添加到侧栏">将列表添加到侧栏</h3>
+
+<p>最后一步,是将这个新页面的链接,添加到侧边栏中。我们将把它放在我们为登录用户显示其他信息的同一部分。</p>
+
+<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>),并将粗体标识的那一行,添加到侧边栏区块,如下所示。</p>
+
+<pre class="brush: python"> &lt;ul class="sidebar-nav"&gt;
+ {% if user.is_authenticated %}
+ &lt;li&gt;User: \{{ user.get_username }}&lt;/li&gt;
+<strong> &lt;li&gt;&lt;a href="{% url 'my-borrowed' %}"&gt;My Borrowed&lt;/a&gt;&lt;/li&gt;</strong>
+ &lt;li&gt;&lt;a href="{% url 'logout'%}?next=\{{request.path}}"&gt;Logout&lt;/a&gt;&lt;/li&gt;
+ {% else %}
+ &lt;li&gt;&lt;a href="{% url 'login'%}?next=\{{request.path}}"&gt;Login&lt;/a&gt;&lt;/li&gt;
+ {% endif %}
+ &lt;/ul&gt;
+</pre>
+
+<h3 id="它看起来是什么样子的?">它看起来是什么样子的?</h3>
+
+<p>当任何用户登录时,他们会在侧栏中看到 My Borrowed 链接,并显示如下所示的书本列表(第一本书没有截止日期,这是我们希望在以后的教程中修复的错误!) 。</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="权限">权限</h2>
+
+<p>权限与模型相关联,并定义可以由具有权限的用户,在模型实例上执行的操作。默认情况下,Django会自动为所有模型提供添加,更改和删除权限,这允许具有权限的用户,通过管理站点执行相关操作。您可以为模型定义自己的权限,并将其授予特定用户。您还可以更改与同一模型的不同实例关联的权限。</p>
+
+<p>对于视图和模板中的权限测试,非常类似于对身份验证状态的测试(实际上,测试权限也会测试身份验证)。</p>
+
+<h3 id="模型_2">模型</h3>
+
+<p>在模型“<code>class Meta</code>”部分上,使用 <code>permissions</code>字段,完成权限定义。您可以在元组中指定所需的权限,每个权限本身,都在包含权限名称和权限显示值的嵌套元组中被定义。例如,我们可能会定义一个权限,允许用户标记已归还的图书,如下所示:</p>
+
+<pre class="brush: python">class BookInstance(models.Model):
+ ...
+  class Meta:
+  ...
+<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre>
+
+<p>然后,我们可以将权限分配给管理站点中的图书管理员“Librarian”分组。打开 <strong>catalog/models.py</strong>,然后添加权限,如上所示。您需要重新运行迁移(调用 <code>python3 manage.py makemigrations</code> 和 <code>python3 manage.py migrate</code>),以适当地更新数据库。</p>
+
+<p> </p>
+
+<h3 id="模板">模板</h3>
+
+<p>当前用户的权限,存在名为 <code>\{{ perms }}</code>的模板变量中。您可以使用关联的Django “app” 中的特定变量名,来检查当前用户是否具有特定权限 - 例如,如果用户具有此权限,则 <code>\{{ perms.catalog.can_mark_returned }}</code>将为True,否则为False。我们通常使用模板标记 <code>{% if %}</code> 测试权限,如下所示:</p>
+
+<pre class="brush: python">{% if perms.catalog.<code>can_mark_returned</code> %}
+ &lt;!-- We can mark a BookInstance as returned. --&gt;
+  &lt;!-- Perhaps add code to link to a "book return" view here. --&gt;
+{% endif %}
+</pre>
+
+<h3 id="视图">视图</h3>
+
+<p>在功能视图中,可以使用 <code>permission_required</code>装饰器,或在基于类别的视图中,使用 <code>PermissionRequiredMixin</code>测试权限。模式和行为与登录身份验证相同,但当然您可能需要添加多个权限。</p>
+
+<p>功能视图装饰器:</p>
+
+<pre class="brush: python">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>基于类别视图的权限要求 mixin。</p>
+
+<pre class="brush: python">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="挑战自己"><a id="Challenge_yourself" name="Challenge_yourself"></a>挑战自己</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>完成后,您的页面应该类似于下面的屏幕截图。</p>
+
+<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/">Django中的用户授权</a> (Django 文档)</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/default//">使用 Django 授权系统(默认) </a>(Django 文档)</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views">介绍从基于类别的视图 &gt; 到使用装饰器的基于类别的视图</a> (Django 文档)</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p>
+
+<p> </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 教程: The Local Library website</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: Creating a skeleton website</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django 教程 3: Using models</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django admin site</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 教程 5: Creating our home page</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: Generic list and detail views</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: Sessions framework</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django 教程 8: User authentication and permissions</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 教程 9: Working with forms</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 教程 10: Testing a Django web application</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django 教程 11: Deploying Django to 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>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/django/deployment/index.html b/files/zh-cn/learn/server-side/django/deployment/index.html
new file mode 100644
index 0000000000..fe582da51f
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/deployment/index.html
@@ -0,0 +1,675 @@
+---
+title: 'Django 教程 11: 部署 Django 到生产环境'
+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">现<font><font>在,您已经创建(并测试)了一个令人敬畏的</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,如果您希望将其安装在公共Web服务器上,以便图书馆工作人员和成员可以通过Internet访问它。</font><font>本文概述了如何找到主机来部署您的网站,以及您需要做什么才能让您的网站准备好生产</font></font>。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row"><strong 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: 700; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>先决条件</strong>:</th>
+ <td>完成所有先前的教程,包括:
+ <p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Testing">在线教学 10:测试 Django 的 Web 应用</a></p>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row"><strong 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: 700; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>目的</strong>:</th>
+ <td>了解您可以在何处以及如何将Django应用程序部署到生产环境。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概述">概述</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>
+
+<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开发Web服务器将您的站点共享到本地浏览器/网络,并使用暴露调试和其他私人信息的(不安全)开发设置运行您的网站。</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;'>
+</ul>
+
+<ul>
+ <li>对你的项目设置(project settings)做一定的修改</li>
+ <li>选择一个用来托管Django app的环境</li>
+ <li>选择一个用来托管所有静态文件的环境</li>
+ <li>设置一个产品级的设施来为你的网站服务</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>本教程为您选择托管站点提供了一些指导,简要概述了为了让您的Django应用程序可用于生产需要做什么以及如何将LocalLibrary网站安装到</font></font><a class="external external-icon" href="https://www.heroku.com/" rel="noopener" style="font-style: normal !important; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; white-space: pre-line;"><font><font>Heroku</font></font></a><font><font>云托管上服务。</font></font></p>
+
+<h2 id="什么是生产环境">什么是生产环境?</h2>
+
+<p>生产环境是一个由服务器电脑提供的环境,你将在这里运行你的网站,为外部使用提供服务。生产环境包括:</p>
+
+<ul>
+ <li>网站运行所需要的电脑硬件</li>
+ <li>操作系统 (例如 Linux, Windows).</li>
+ <li>编程语言运行库和框架库,在其上编写您的网站。</li>
+ <li>用于提供页面和其他内容的Web服务器(例如Nginx,Apache)。</li>
+ <li>在 Django 网站和 Web 服务器之间,传递“动态”请求的应用程序服务器。</li>
+ <li>您的网站所依赖的数据库。</li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>: 根据您的生产配置方式,您可能还有反向代理,负载均衡器等。</p>
+</div>
+
+<p>服务器计算机可以位于您的场所,并通过快速链接连接到 Internet,但使用托管“在云中”的计算机更为常见。这实际上意味着,您的代码在托管公司的数据中心的某台远程计算机(或可能是“虚拟”计算机)上运行。远程服务器通常会以特定价格,提供一些保证级别的计算资源(例如CPU,RAM,存储器等)和互联网连接。</p>
+
+<p>这种可远程访问的计算/网络硬件,称为基础架构即服务(IaaS)。许多IaaS供应商,提供预安装特定操作系统的选项,您必须在其上安装生产环境的其他组件。其他供应商允许您选择功能更全面的环境,可能包括完整的 Django ,和 Web 服务器设置。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 预构建环境可以使您的网站设置变得非常简单,因为它们会减少配置,但可用选项可能会限制您使用不熟悉的服务器(或其他组件),并且可能基于较旧版本的操作系统。通常最好自己安装组件,以便获得所需的组件,当您需要升级系统的某些部分时,您就知道从哪里开始!</p>
+</div>
+
+<p>其他托管服务提供商,支持 Django 作为平台即服务(PaaS)产品的一部分。在这种托管中,您不必担心大多数生产环境(Web 服务器,应用程序服务器,负载平衡器),因为主机平台会为您处理这些(以及为了扩展您的应用程序,而需要做的大部分工作)。这使得部署非常简单,因为您只需要专注于 Web 应用程序,而不是所有其他服务器的基础结构。</p>
+
+<p>相对于 PaaS,一些开发人员会选择 IaaS 所提供的更高灵活性,而其他开发人员,则欣赏 PaaS 降低的维护开销,和更轻松地扩展。当您开始使用时,在 PaaS 系统上设置您的网站,要容易得多,因此我们将在本教程中这么做。</p>
+
+<div class="note">
+<p><strong>提示:</strong> 如果您选择一个 Python/Django 友好的托管服务提供商,他们应该提供有关如何使用不同配置的网络服务器,应用服务器,反向代理等设置 Django 网站的说明(如果您选择 PaaS,这就没有关系了)。例如,<a href="https://www.digitalocean.com/community/tutorials?q=django">Digital Ocean Django 社区文档 </a>中的各种配置,有许多手把手指南。</p>
+</div>
+
+<h2 id="选择托管服务提供商">选择托管服务提供商</h2>
+
+<p>已知有超过100个托管服务提供商,积极支持或与 Django 合作(您可以在 <a href="http://djangofriendly.com/hosts/">Djangofriendly hosts </a>主机上,找到相当广泛的列表)。这些供应商提供不同类型的环境(IaaS,PaaS),以及不同价格、不同级别的计算和网络资源。</p>
+
+<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>, <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> 等。</p>
+
+<p>许多提供商还有“基本”层,可提供更多有用的计算能力和更少的限制。<a href="https://www.digitalocean.com/">Digital Ocean</a> 和 <a href="https://www.pythonanywhere.com/">Python Anywhere</a> ,就是流行的托管服务提供商的例子,提供相对便宜的基本计算等级(每月 5 美元到 10 美元不等)。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 请记住,价格不是唯一的选择标准。如果您的网站成功,可能会发现,可扩展性是最重要的考虑因素。</p>
+</div>
+
+<h2 id="让您的网站准备好发布">让您的网站准备好发布</h2>
+
+<p>使用 django-admin 和 manage.py 工具创建的 Django 骨架网站,是为了使开发更容易而配置的。出于安全性或性能原因,许多 Django 项目设置(在<strong>settings.py</strong>中指定),在生产应该是不同的。</p>
+
+<div class="note">
+<p><strong>提示:</strong> 通常有一个单独的 <strong>settings.py</strong> 文件用于生产环境,并从单独的文件或环境变量,导入敏感设置。即使其他源代码在公共存储库中可用,也应保护此文件。</p>
+</div>
+
+<p>您必须检查的关键设置是:</p>
+
+<ul>
+ <li><code>DEBUG</code>. 这应该在生产环境中设置为 <code>False</code>(<code>DEBUG = False</code>)。这将停止显示敏感/机密调试跟踪和变量信息。</li>
+ <li><code>SECRET_KEY</code>. 这是用于CRSF保护等的大随机值。重要的是,生产中使用的密钥,不应在源代码管理中、或在生产服务器外部可访问。 Django文档表明,可能最好从环境变量加载,或从仅供服务的文件中读取。
+ <pre class="notranslate"># 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>让我们更改 LocalLibrary 应用程序,以便我们从环境变量中,读取<code>SECRET_KEY</code> 和 <code>DEBUG</code>变量(如果已定义),否则使用配置文件中的默认值。</p>
+
+<p>打开 <strong>/locallibrary/settings.py</strong>,禁用原始的<code>SECRET_KEY</code>配置,并加入如下以<strong>粗体</strong>显示的几行。在开发过程中,不会为密钥指定环境变量,因此将使用默认值(在此处使用的密钥,或密钥“泄漏”无关紧要,因为您不会在生产环境中使用它)。</p>
+
+<pre class="brush: python notranslate"># SECURITY WARNING: keep the secret key used in production secret!
+# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&amp;k3q+pmu)5%asj6yjpkag'
+<strong>import os</strong>
+<strong>SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&amp;k3q+pmu)5%asj6yjpkag')</strong>
+</pre>
+
+<p>然后注释掉现有的<code>DEBUG</code>设置,并如下所示,添加新的一行。</p>
+
+<pre class="brush: python notranslate"># 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>默认情况下,<code>DEBUG</code>的值为<code>True</code>,但如果<code>DJANGO_DEBUG</code>环境变量的值,设置为空字符串,则为<code>False</code>,例如,<code>DJANGO_DEBUG=''</code>。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 如果我们可以直接将<code>DJANGO_DEBUG</code>环境变量设置为<code>True</code>或<code>False</code>,而不是分别使用“any string”或“empty string”,那将更直观。不幸的是,环境变量值存储为 Python 字符串,计算结果为 <code>False</code> 的唯一字符串,是空字符串(例如<code>bool('')==False</code>)。</p>
+</div>
+
+<p><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">部署清单(Django文档)</a>中,提供了您可能要更改的完整设置清单。您还可以使用下面的终端命令,列出其中的一些:</p>
+
+<pre class="brush: python notranslate">python3 manage.py check --deploy
+</pre>
+
+<h2 id="示例:在Heroku上安装LocalLibrary">示例:在Heroku上安装LocalLibrary</h2>
+
+<p>本节提供了如何在 <a href="http://heroku.com">Heroku PaaS cloud </a>云上安装 LocalLibrary 的实际演示。</p>
+
+<h3 id="为何选择Heroku?">为何选择Heroku?</h3>
+
+<p>Heroku 是运行时间最长,且最受欢迎的基于云的 PaaS 服务之一。它最初只支持 Ruby 应用程序,但现在可用于托管来自许多编程环境的应用程序,包括 Django!</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>在 <a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku 文档) 中列出了其他限制。</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>” 中,运行Django网站,这是一个独立的虚拟化 Unix 容器,提供运行应用程序所需的环境。<a href="https://devcenter.heroku.com/articles/dynos">Dynos </a>是完全隔离的,并且有一个短暂的文件系统(一个短暂的文件系统,每次dyno 重新启动时,都会清理/清空)。 Dynos 默认共享的唯一内容,是应用程序配置变量。 Heroku 内部使用负载均衡器,将 Web 流量分配给所有 “web” dynos。由于他们之间没有任何共享,Heroku可以通过添加更多 dynos ,来水平扩展应用程序(当然,您可能还需要扩展数据库,以接受其他连接)。</p>
+
+<p>由于文件系统是暂时的,因此无法直接安装应用程序所需的服务(例如数据库,队列,缓存系统,存储,电子邮件服务等)。取代的是,Heroku Web 应用程序,使用 Heroku 或第三方作为独立“附加组件”提供的支持服务。一旦连接到 Web 应用程序,dynos 就会使用应用程序配置变量中包含的信息,来访问服务。</p>
+
+<p>为了执行您的应用程序,Heroku 需要能够设置适当的环境,和依赖关系,并了解它是如何启动的。对于 Django 应用程序而言,我们在一些文本文件中提供此信息:</p>
+
+<ul>
+ <li><strong>runtime.txt</strong>:<strong> </strong>要使用的编程语言和版本。</li>
+ <li><strong>requirements.txt</strong>: Python组件依赖项,包括Django。</li>
+ <li><strong>Procfile</strong>: 启动 Web 应用程序要执行的进程列表。对于Django,这通常是 Gunicorn Web 应用程序服务器(带有 <code>.wsgi </code>脚本)。</li>
+ <li><strong>wsgi.py</strong>: 在 Heroku 环境中,调用我们的Django 应用程序的 <a href="http://wsgi.readthedocs.io/en/latest/what.html">WSGI </a>配置。</li>
+</ul>
+
+<p>开发人员使用特殊的客户端应用程序/终端与 Heroku 交互,这很像Unix bash 脚本。这允许您上传存在 git 储存库中的代码,检查正在运行的进程,查看日志,设置配置变量等等!</p>
+
+<p>为了让我们的应用程序在Heroku上工作,我们需要将我们的 Django Web 应用程序,放入git储存库,添加上面的文件,集成数据库附加组件,并进行更改,以正确处理静态文件。</p>
+
+<p>完成所有操作后,我们可以设置 Heroku 帐户,获取 Heroku 客户端,并使用它来安装我们的网站。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 以下说明反映了在撰写本书时,如何使用 Heroku。如果 Heroku 显着改变了他们的操作过程,您可能希望检查他们的设置文档:<a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">在Heroku上开始使用 Django</a>。</p>
+</div>
+
+<p>就是您一开始所需的所有概述(请参阅 <a href="https://devcenter.heroku.com/articles/how-heroku-works">Heroku 如何工作</a>,以获取更全面的指南)。</p>
+
+<h3 id="在_Github_中创建应用程序储存库">在 Github 中创建应用程序储存库</h3>
+
+<p>Heroku 与 <strong>git</strong> 源代码版本控制系统紧密集成,使用它来上传/同步您对实时系统所做的任何更改。它通过添加一个名为 heroku 的新的heroku “远程” 储存库,来指向 Heroku 云上的源储存库。在开发期间,您使用 git 在“主”储存库中储存更改。如果要部署站点,请将更改同步到 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>输入新的储存库名称(例如 django_local_library)和描述(例如 “用 Django编写的本地图书馆网站”)。</li>
+ <li>在Add .gitignore 选择列表中,选择 <strong>Python</strong>。</li>
+ <li>在添加许可证选择列表中,选择您想要的许可证。</li>
+ <li>选中使用自述文件初始化此储存库(<strong>Initialize this repository with a README)</strong>。</li>
+ </ul>
+ </li>
+ <li>点击 <strong>Create repository</strong>.</li>
+ <li>点击新仓库页面上的绿色 “克隆或下载”<strong> </strong>(<strong>Clone or download</strong>)按钮 。</li>
+ <li>从显示的对话框中的文本字段中复制URL值(它应该类似于: <strong>https://github.com/<em>&lt;your_git_user_id&gt;</em>/django_local_library.git</strong>)。</li>
+</ol>
+
+<p>现在创建了储存库(“repo”),我们将要在本地计算机上克隆它:</p>
+
+<ol>
+ <li>为您的本地计算机安装 git(您可以在<a href="https://git-scm.com/downloads">此处</a>找到不同平台的版本)。</li>
+ <li>打开命令提示符/终端,并使用您在上面复制的 URL 克隆储存库:
+ <pre class="brush: bash notranslate">git clone https://github.com/<strong><em>&lt;your_git_user_id&gt;</em></strong>/django_local_library.git
+</pre>
+ 这将在当前目录下方创建储存库。</li>
+ <li>切换目录,到新的仓库。
+ <pre class="brush: bash notranslate">cd django_local_library</pre>
+ </li>
+</ol>
+
+<p>最后一步是复制你的应用程序,然后使用 git ,将文件添加到你的仓库:</p>
+
+<ol>
+ <li>将您的 Django 应用程序,复制到此文件夹(与 <strong>manage.py</strong> 级别相同的、和以下级别的所有文件,而<strong>不是</strong>包含 locallibrary 文件夹的文件)。</li>
+ <li>打开<strong>.gitignore</strong>文件,将以下几行复制到其底部,然后保存(此文件用于标识默认情况下,不应上传到 git 的文件)。
+ <pre class="notranslate"># Text backup files
+*.bak
+
+#Database
+*.sqlite3</pre>
+ </li>
+ <li>打开命令提示符/终端,并使用<code>add</code>命令,将所有文件添加到 git。
+ <pre class="brush: bash notranslate">git add -A
+</pre>
+ </li>
+ <li>使用 status 命令,检查要添加的所有文件是否正确(您希望包含源文件,而不是二进制文件,临时文件等)。它应该看起来有点像下面的列表。
+ <pre class="notranslate">&gt; git status
+On branch master
+Your branch is up-to-date with 'origin/master'.
+Changes to be committed:
+  (use "git reset HEAD &lt;file&gt;..." 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>如果您满意,请将文件提交到本地储存库:
+ <pre class="brush: bash notranslate">git commit -m "First version of application moved into github"</pre>
+ </li>
+ <li>然后使用以下内容,将本地储存库同步到 Github 网站:
+ <pre class="notranslate">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上运行。虽然 Heroku 的<a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">在 Heroku 使用 Django 入门教程</a>,假设您将使用 Heroku 客户端,来运行您的本地开发环境,但我们的更改,与现有的 Django 开发服务器,以及我们已经学习的工作方式兼容。</p>
+
+<h4 id="Procfile">Procfile</h4>
+
+<p>在 GitHub 储存库的根目录中,创建文件<code>Procfile</code>(无扩展名),以声明应用程序的进程类型和入口点。将以下文本复制到其中:</p>
+
+<pre class="notranslate">web: gunicorn locallibrary.wsgi --log-file -</pre>
+
+<p>“<code>web:</code>”告诉 Heroku ,这是一个 web dyno,可以发送 HTTP 流量。在这个 dyno 中启动的进程,是 gunicorn,这是 Heruko 推荐的一种流行的 Web 应用程序服务器。我们使用模块 <code>locallibrary.wsgi</code>(使用我们的应用程序框架创建:<strong>/locallibrary/wsgi.py </strong>)中的配置信息启动 Gunicorn。</p>
+
+<h4 id="Gunicorn">Gunicorn</h4>
+
+<p><a href="http://gunicorn.org/">Gunicorn</a> 是推荐的 HTTP 服务器,用于 Heroku 上的 Django(如上面的 Procfile 中所述)。它是一个用于 WSGI 应用程序的纯 Python HTTP 服务器,可以在一个 dyno 中,运行多个 Python 并发进程(有关更多信息,请参阅<a href="https://devcenter.heroku.com/articles/python-gunicorn">使用 Gunicorn 部署 Python 应用程序</a>)。</p>
+
+<p>虽然在开发期间,我们不需要 Gunicorn 为我们的 LocalLibrary 应用程序提供服务,但我们将安装它,以便它成为我们在远程服务器上设置 Heroku 的 <a href="#requirements">requirements</a> 的一部分。</p>
+
+<p>使用 pip(我们在<a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">设置开发环境</a>时安装)在命令行上,将 Gunicorn 安装到本地:</p>
+
+<pre class="brush: bash notranslate">pip3 install gunicorn
+</pre>
+
+<h4 id="数据库配置">数据库配置</h4>
+
+<p>我们不能在 Heroku 上使用默认的 SQLite 数据库,因为它是基于文件的,并且每次应用程序重新启动时,都会从暂时的文件系统中删除它(通常每天一次,每次应用程序或其配置变量被更改时)。</p>
+
+<p>处理这种情况的 Heroku 机制,是使用<a href="https://elements.heroku.com/addons#data-stores">数据库加载项</a>,并使用来自加载项设置的环境<a href="https://devcenter.heroku.com/articles/config-vars">配置变量</a>的信息,来配置 Web 应用程序。有很多数据库选项,但我们将使用 Heroku postgres 数据库的<a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#plan-tiers">爱好者等级</a>,因为它是免费的,被 Django 所支持,并在使用免费的爱好者 dyno 计划等级时,会自动添加到新的 Heroku 应用程序。</p>
+
+<p>使用名为<code>DATABASE_URL</code>的配置变量,将数据库连接信息提供给 Web dyno。Heroku 建议开发人员使用 <a href="https://warehouse.python.org/project/dj-database-url/">dj-database-url </a>套件包,以解析<code>DATABASE_URL</code>环境变量,并自动将其转换为 Django 所需的配置格式,而不是将此信息硬编码到 Django 中。除了安装 dj-database-url 套件包之外,我们还需要安装<a href="http://initd.org/psycopg/">psycopg2</a>,因为 Django 需要它与 Postgres 数据库进行交互。</p>
+
+<h5 id="dj-database-url_Django_database_configuration_from_environment_variable">dj-database-url (Django database configuration from environment variable)</h5>
+
+<p>在本地安装 dj-database-url,使其成为我们在远程服务器上设置 Heroku 的 <a href="#requirements">requirements</a> 的一部分:</p>
+
+<pre class="notranslate">$ pip3 install dj-database-url
+</pre>
+
+<h5 id="settings.py">settings.py</h5>
+
+<p>打开<strong>/locallibrary/settings.py</strong>,并将以下配置复制到文件的底部:</p>
+
+<pre class="notranslate"># 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>注意:</strong></p>
+
+<ul>
+ <li>我们仍然会在开发期间使用SQLite,因为我们的开发计算机上不会设置<code>DATABASE_URL</code>环境变量。</li>
+ <li><code>conn_max_age=500</code>的值使连接持久,这比在每个请求周期重新创建连接更有效。但是,这是可选的,如果需要可以删除。</li>
+</ul>
+</div>
+
+<h5 id="psycopg2_Python_Postgres_database_support">psycopg2 (Python Postgres database support)</h5>
+
+<p>Django 需要 psycopg2 来处理 Postgres 数据库,你需要将它添加到<a href="#requirements">requirements.txt </a>中,以便 Heroku 在远程服务器上进行设置(如下面的 requirements 部分所述)。</p>
+
+<p>Django 默认会在本地使用我们的 SQLite 数据库,因为我们的本地环境中,没有设置<code>DATABASE_URL</code>环境变量。如果您想完全切换到Postgres ,并使用我们的 Heroku 免费等级数据库,进行开发和生产,那么您可以这么做。例如,要在基于 Linux 的系统上,本地安装psycopg2 及其依赖项,您将使用以下 bash / terminal 命令:</p>
+
+<pre class="brush: bash notranslate"><code>sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib</code>
+pip3 install psycopg2
+</pre>
+
+<p>有关其他平台的安装说明,请访问 <a href="http://initd.org/psycopg/docs/install.html">psycopg2 </a>网站。</p>
+
+<p>但是,您不需要这样做 - 您不需要在本地计算机上激活 PostGreSQL,只要将其作为要求(requirement)提供给 Heroku,请参阅<code>requirements.txt</code>(见下文)。</p>
+
+<h4 id="在生产环境中提供静态文件">在生产环境中提供静态文件</h4>
+
+<p>在开发过程中,我们使用 Django 和 Django 开发 Web 服务器,来提供静态文件(CSS,JavaScript 等)。在生产环境中,我们通常提供来自内容传送网络(CDN)或 Web 服务器的静态文件。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 通过 Django/web 应用程序提供静态文件是低效的,因为请求必须通过不必要的附加代码(Django),而不是由 Web 服务器或完全独立的 CDN 直接处理。虽然这对于开发期间的本地使用无关紧要,但如果我们在生产环境中使用相同的方法,则会对性能产生重大影响。</p>
+</div>
+
+<p>为了便于将静态文件与 Django Web 应用程序分开托管,Django 提供了 collectstatic 工具,来收集这些文件以进行部署(有一个设置变量,用于定义在运行 collectstatic 时,应该收集文件的位置)。 Django 模板是指相对于设置变量(<code>STATIC_URL</code>)的静态文件的托管位置,因此如果将静态文件移动到另一个主机/服务器,则可以更改此位置。</p>
+
+<p>相关的设置变量是:</p>
+
+<ul>
+ <li><code>STATIC_URL</code>:  这是将提供静态文件的基本 URL 位置,例如,在CDN上。这用于在我们的基本模板中访问的静态模板变量(请参阅 <a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5:创建我们的主页</a>)。</li>
+ <li><code>STATIC_ROOT</code>: 这是 Django 的 “collectstatic” 工具将收集模板中引用的任何静态文件的目录的绝对路径。收集完成后,可以将这些文件,作为一个组上载到托管文件的任何位置。</li>
+ <li><code>STATICFILES_DIRS</code>: 这列出了 Django 的 collectstatic 工具应该搜索静态文件的其他目录。</li>
+</ul>
+
+<h5 id="settings.py_2">settings.py</h5>
+
+<p>打开<strong>/locallibrary/settings.py</strong>,并将以下配置,复制到文件的底部。 <code>BASE_DIR </code>应该已经在您的文件中定义了(<code>STATIC_URL</code>可能已经在文件创建时已经定义。虽然它不会造成任何伤害,但您也可以删除重复的先前引用)。</p>
+
+<pre class="notranslate"># 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>我们实际上,将使用名为 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 的库来提供文件,我们将在下一节中安装和配置。</p>
+
+<p>有关更多信息,请参阅 <a href="https://devcenter.heroku.com/articles/django-assets">Django和静态资产</a>(Heroku 文档)。<br>
+ <br>
+ Whitenoise</p>
+
+<p>有许多方法可以在生产环境中提供静态文件(我们在前面的部分中看到了相关的 Django 设置)。 Heroku 建议在生产环境中使用 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 项目,直接从 Gunicorn 提供静态资产。</p>
+
+<div class="note">
+<p><strong>注意: </strong>Heroku 会在上传您的应用程序后,自动调用collectstatic 并准备静态文件,以供 WhiteNoise 使用。查看 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 文档,了解其工作原理以及实现,为什么是提供这些文件的相对有效方法。</p>
+</div>
+
+<p>设置 WhiteNoise 以便在项目中使用的步骤如下:</p>
+
+<h5 id="WhiteNoise">WhiteNoise</h5>
+
+<p>使用以下命令在本地安装 whitenoise:</p>
+
+<pre class="notranslate">$ pip3 install whitenoise
+</pre>
+
+<h5 id="settings.py_3">settings.py</h5>
+
+<p>要将 WhiteNoise 安装到您的 Django 应用程序中,请打开<strong>/locallibrary/settings.py</strong>,找到<code>MIDDLEWARE</code>设置,并在<code>SecurityMiddleware</code>正下方的列表顶部附近,添加<code>WhiteNoiseMiddleware</code>:</p>
+
+<pre class="notranslate">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>这是可选的,您可以在提供静态文件时,减小它们的大小(这样更有效)。只需将以下内容添加到<strong>/locallibrary/settings.py</strong>的底部:</p>
+
+<pre class="notranslate"># Simplified static file serving.
+# https://warehouse.python.org/project/whitenoise/
+STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
+</pre>
+
+<h4 id="Requirements">Requirements</h4>
+
+<p>Web 应用程序的 Python requirements ,必须放在储存库根目录中的文件 <strong>requirements.txt</strong> 中。然后 Heroku 将在重建您的环境时,自动安装它们。您可以在命令行上,使用 pip 创建此文件(在 repo 根目录中,运行以下命令):</p>
+
+<pre class="brush: bash notranslate">pip3 freeze &gt; requirements.txt</pre>
+
+<p>安装上面所有不同的依赖项后,您的 <strong>requirements.txt</strong> 文件,应至少列出这些项目(尽管版本号可能不同)。请删除下面未列出的任何其他依赖项,除非您已为此应用程序明确添加它们。</p>
+
+<pre class="notranslate">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><strong>注意</strong>:确保存在类似上面的 <strong>psycopg2 </strong>的那一行!即使你没有在本地安装它,你仍然应该将它添加到<strong>requirements.txt</strong>。</p>
+</div>
+
+<h4 id="Runtime">Runtime</h4>
+
+<p><strong>runtime.txt </strong>文件(如果已定义)告诉 Heroku 使用哪种编程语言。在 repo 的根目录中,创建文件并添加以下文本:</p>
+
+<pre class="notranslate">python-3.6.4</pre>
+
+<div class="note">
+<p><strong>注意:</strong> Heroku 只支持少量的 <a href="https://devcenter.heroku.com/articles/python-support#supported-python-runtimes">Python 运行库</a>(在编写本文时,包括上面的那个)。 Heroku 将使用受支持的运行库,而不管此文件中指定的值。</p>
+</div>
+
+<h4 id="将更改保存到_Github_并重新测试">将更改保存到 Github 并重新测试</h4>
+
+<p>接下来,我们将所有更改保存到 Github。在终端(我们的存储库中的 whist)中,输入以下命令:</p>
+
+<pre class="brush: python notranslate">git add -A
+git commit -m "Added files and changes required for deployment to heroku"
+git push origin master</pre>
+
+<p>在我们继续之前,让我们在本地,再次测试网站,并确保它不受上述任何更改的影响。像往常一样运行开发 Web 服务器,然后检查站点,是否仍然按预期在浏览器上运行。</p>
+
+<pre class="brush: bash notranslate">python3 manage.py runserver</pre>
+
+<p>我们现在应该准备开始在 Heroku 上部署 LocalLibrary。</p>
+
+<h3 id="获取_Heroku_帐户">获取 Heroku 帐户</h3>
+
+<p>要开始使用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 notranslate">heroku help
+</pre>
+
+<h3 id="创建并上传网站">创建并上传网站</h3>
+
+<p>要创建应用程序,我们在储存库的根目录中,运行“create”命令。这将在我们的本地 git 环境中,创建一个名为 heroku 的 git remote(“指向远程储存库的指标”)。</p>
+
+<pre class="brush: bash notranslate">heroku create</pre>
+
+<div class="note">
+<p><strong>注意:</strong> 如果您愿意,可以通过在“创建”之后指定值来命名远程。如果你不这样做,你会得到一个随机的名字。该名称用于默认URL。</p>
+</div>
+
+<p>然后我们可以将应用程序,推送到 Heroku 储存库,如下所示。这将上传应用程序,将其打包到 dyno 中,运行 collectstatic,然后启动该站点。</p>
+
+<pre class="brush: bash notranslate">git push heroku master</pre>
+
+<p>如果我们很幸运,该应用程序现在在网站上“运行”,但它将无法正常工作,因为我们尚未设置数据库表,以供我们的应用程序使用。为此,我们需要使用 heroku run命令,并启动 “<a href="https://devcenter.heroku.com/articles/deploying-python#one-off-dynos">one off dyno</a>” 来执行迁移操作。在终端中输入以下命令:</p>
+
+<pre class="brush: bash notranslate">heroku run python manage.py migrate</pre>
+
+<p>我们还需要能够添加书本和作者,所以我们再次使用一次性dyno,创建我们的管理超级用户:</p>
+
+<pre class="brush: bash notranslate">heroku run python manage.py createsuperuser</pre>
+
+<p>完成后,我们可以查看该网站。它应该有用,虽然它还没有任何书本。要打开浏览器访问新网站,请使用以下命令:</p>
+
+<pre class="brush: bash notranslate">heroku open</pre>
+
+<p>在管理站点中创建一些书本,并检查该站点是否按预期运行。</p>
+
+<h3 id="管理附加组件(插件)">管理附加组件(插件)</h3>
+
+<p>您可以使用 <code>heroku addons</code>命令,查看应用程序的附加组件。这将列出所有附加组件,以及它们的价格等级和状态。</p>
+
+<pre class="brush: bash notranslate">&gt;heroku addons
+
+Add-on Plan Price State
+───────────────────────────────────────── ───────── ───── ───────
+heroku-postgresql (postgresql-flat-26536) hobby-dev free created
+ └─ as DATABASE</pre>
+
+<p>在这里,我们看到我们只有一个附加组件,即 postgres SQL数据库。这是免费的,并且是在我们创建应用时,自动创建的。您可以使用以下命令,更详细地打开网页,以检查数据库附加组件(或任何其他附加组件):</p>
+
+<pre class="brush: bash notranslate">heroku addons:open heroku-postgresql
+</pre>
+
+<p>其他命令允许您创建,销毁,升级和降级附加组件(使用类似的语法打开)。有关更多信息,请参阅<a href="https://devcenter.heroku.com/articles/managing-add-ons">管理附加组件</a>(Heroku 文档)。</p>
+
+<h3 id="设定配置变量">设定配置变量</h3>
+
+<p>您可以使用 <code>heroku config</code>命令,检查站点的配置变量。下面你可以看到,我们只有一个变量,<code>DATABASE_URL</code>用于配置我们的数据库。</p>
+
+<pre class="brush: bash notranslate">&gt;heroku config
+
+=== locallibrary Config Vars
+DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3</pre>
+
+<p>如果您回想起来<strong>准备发布网站</strong>的部分,我们必须为<code>DJANGO_SECRET_KEY</code> 和 <code>DJANGO_DEBUG</code>设置环境变量。我们现在就这样做。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 密钥需要真正的保密!生成新密钥的一种方法,是创建一个新的 Django 项目(<code>django-admin startproject someprojectname</code>),然后从 <strong>settings.py </strong>中,获取为您生成的密钥。</p>
+</div>
+
+<p>我们使用 <code>config:set</code>命令,设置<code>DJANGO_SECRET_KEY</code>(如下所示)。记得使用自己的密钥!</p>
+
+<pre class="brush: bash notranslate">&gt;heroku config:set DJANGO_SECRET_KEY=eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh&amp;=
+
+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>我们以同样的方式设置<code>DJANGO_DEBUG</code>:</p>
+
+<pre class="brush: bash notranslate">&gt;heroku config:set <code>DJANGO_DEBUG=''
+
+Setting DJANGO_DEBUG and restarting locallibrary... done, v8</code></pre>
+
+<p>如果您现在访问该站点,您将收到“错误请求”(Bad request)错误,因为如果您有 <code>DEBUG=False</code>(作为安全措施),则需要<a href="https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts">ALLOWED_HOSTS</a> 设置。打开 <strong>/locallibrary/settings.py</strong>,并更改<code>ALLOWED_HOSTS</code>设置,以包含您的基本应用程序 URL(例如'locallibrary1234.herokuapp.com'),以及您通常在本地开发服务器上使用的 URL。</p>
+
+<pre class="brush: python notranslate">ALLOWED_HOSTS = ['&lt;your app URL without the https:// prefix&gt;.herokuapp.com','127.0.0.1']
+# For example:
+# ALLOWED_HOSTS = ['fathomless-scrubland-30645.herokuapp.com','127.0.0.1']
+</pre>
+
+<p>然后保存您的设置,并将它们提交到您的 Github 仓库和 Heroku:</p>
+
+<pre class="brush: bash notranslate">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>完成 Heroku 的站点更新后,输入一个不存在的 URL(例如,<strong>/catalog/doesnotexist/</strong>)。以前这会显示一个详细的调试页面,但现在,您应该只看到一个简单的“未找到”页面。</p>
+</div>
+
+<h3 id="除错调试">除错调试</h3>
+
+<p>Heroku客户端提供了一些调试工具:</p>
+
+<pre class="brush: bash notranslate">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>如果您需要比这些更多的信息,您将需要开始研究 <a href="https://docs.djangoproject.com/en/2.0/topics/logging/">Django Logging</a>。</p>
+
+<h2 id="总结"><strong><span style='font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 2.5rem;'>总结</span></strong></h2>
+
+<p>本教程讲述如何在生产环境中配置Django应用,也是本系列Django教程的结尾。我们希望你觉得教程有用。你可以在Github上取得一个完全可工作版本的<a href="https://github.com/mdn/django-locallibrary-tutorial">源码(用力点击此处)</a>。</p>
+
+<p>下一步是阅读我们此前的一些文章,然后完成评估任务。</p>
+
+<h2 id="也可以参考">也可以参考</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/">Deploying Django</a> (Django 文档)
+
+ <ul>
+ <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">Deployment checklist</a> (Django 文档)</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/deployment/">Deploying static files</a> (Django 文档)</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/">How to deploy with WSGI</a> (Django 文档)</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 文档)</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/gunicorn/">How to use Django with Gunicorn</a> (Django 文档)</li>
+ </ul>
+ </li>
+ <li>Heroku
+ <ul>
+ <li><a href="https://devcenter.heroku.com/articles/django-app-configuration">Configuring Django apps for Heroku</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/python-concurrency-and-database-connections">Concurrency and Database Connections in Django</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> (Heroku 文档)</li>
+ <li><a href="https://devcenter.heroku.com/articles/deploying-python">Deploying Python and Django apps on Heroku</a> (Heroku 文档)</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>
+
+
+
+<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-cn/learn/server-side/django/django_assessment_blog/index.html b/files/zh-cn/learn/server-side/django/django_assessment_blog/index.html
new file mode 100644
index 0000000000..a3ca28e160
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/django_assessment_blog/index.html
@@ -0,0 +1,313 @@
+---
+title: 评估:DIY Django 微博客
+slug: learn/Server-side/Django/django_assessment_blog
+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">先决条件:</th>
+ <td>在尝试此评估之前,您应该已经完成​​了本模块中的所有文章。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>测试对Django基础知识的理解,包括URL配置,模型,视图,表单和模板。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="項目概要">項目概要</h2>
+
+<p>下面列出了需要显示的页面、URL和其它要求:</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> and <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 篇文章 1 个分页。</li>
+ <li>列表项显示博客标题,发布日期和作者。</li>
+ <li>博客帖子名称,链接到博客详细信息页面。</li>
+ <li>博主(作者姓名)链接到博客作者详细信息页面。</li>
+ </ul>
+ </td>
+ </tr>
+ <tr>
+ <td>博客作者(博主)详细信息页面</td>
+ <td><code>/blog/blogger/<em>&lt;author-id&gt;</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>&lt;blog-id&gt;</em></code></td>
+ <td>
+ <p>博客帖子详情。</p>
+
+ <ul>
+ <li>可从博客帖子列表访问所有用户。</li>
+ <li>页面包含博客文章:姓名,作者,发布日期和内容。</li>
+ <li>博客文章的评论应显示在底部。</li>
+ <li>评论应按顺序排序:从最旧到最近。</li>
+ <li>包含为登录用户添加注释的链接(请参阅注释表单页面)</li>
+ <li>博客帖子和评论,只需显示纯文本。不需要支持任何类型的HTML标记(例如链接,图像,粗体/斜体等)。</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>&lt;blog-id&gt;</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>&lt;standard urls&gt;</em></code></td>
+ <td>
+ <p>用于登录,注销和设置密码的标准Django身份验证页面:</p>
+
+ <p> </p>
+
+ <ul>
+ <li>应该可以通过侧边栏链接,访问登录/退出页面。</li>
+ </ul>
+ </td>
+ </tr>
+ <tr>
+ <td>管理站点</td>
+ <td><code>/admin/<em>&lt;standard urls&gt;</em></code></td>
+ <td>
+ <p>应启用管理站点,以允许创建/编辑/删除博客帖子、博客作者、和博客评论(这是博客创建新博客帖子的机制):</p>
+
+ <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 个记录为 1 个分页(至少在第一页上)</li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>: 当然还有许多其他测试可以运行。请谨慎使用,但我们希望您至少进行上述测试。</p>
+</div>
+
+<p>以下部分,显示了实现上述要求的站点的屏幕截图。</p>
+
+<h2 id="截图">截图</h2>
+
+<p>以下屏幕截图,提供了完成的程序应输出的示例。</p>
+
+<h3 id="所有博客文章列表">所有博客文章列表</h3>
+
+<p>这将显示所有博客帖子的列表(可从侧栏中的“所有博客” All blogs 链接访问)。注意事项:</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>这提供了到所有博客的链接,如同来自侧栏中的“所有博客” All bloggers 链接。在这种情况下,我们可以从侧边栏看到,并没有用户登录。</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>请注意,注释具有日期和时间,并且从最旧到最新排序(与博客次序相反)。最后,我们有一个链接,用于访问表单,以添加新评论。如果用户未登录,我们会看到登录的建议。</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="完成的步骤">完成的步骤</h2>
+
+<p>以下部分,描述了您需要执行的操作。</p>
+
+<ol>
+ <li>为站点创建骨架项目和Web应用程序(如<a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django教程 2:创建骨架网站</a>中所述)。您可以使用 'diyblog' 作为项目名称,使用 'blog' 作为应用程序名称。</li>
+ <li>为博客帖子,评论和所需的任何其他对象创建模型。在考虑您的设计时,请记住:
+ <ul>
+ <li>每个评论只有一个博客,但博客可能有很多评论。</li>
+ <li>博客帖子和评论,必须按发布日期排序。</li>
+ <li>并非每个用户都必须是博客作者,尽管任何用户都可能是评论者。</li>
+ <li>博客作者还必须包含个人信息。</li>
+ </ul>
+ </li>
+ <li>为新模型运行迁移,并创建超级用户。</li>
+ <li>使用管理站点,创建一些示例博客帖子,和博客评论。</li>
+ <li>为博客帖子、和博客列表页面,创建视图、模板、和 URL 配置。</li>
+ <li>为博客帖子、和博客详细信息页面,创建视图、模板、和 URL 配置。</li>
+ <li>创建一个页面,其中包含用于添加新评论的表单(请记住,这仅适用于已登录的用户!)</li>
+</ol>
+
+<h2 id="提示和技巧">提示和技巧</h2>
+
+<p>该项目与 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 教程非常相似。您将能够使用几乎所有相同的方法,包含设置框架,用户登录/注销行为,对静态文件,视图,URL,表单,基本模板和管理站点配置的支持。</p>
+
+<p>一些一般提示:</p>
+
+<ol>
+ <li>索引页面可以实现为基本功能视图和模板(就像locallibrary一样)。</li>
+ <li>T可以使用<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">通用列表和详细信息视图</a>,以创建博客帖子和博主的列表视图,以及博客帖子的详细信息视图。</li>
+ <li>可以使用通用列表的博客列表视图,并对指定作者匹配的博客对象进行过滤,来创建特定作者的博客帖子列表。
+ <ul>
+ <li>您将必须实现<code>get_queryset(self)</code>来进行过滤(很像我们的图书馆类<code>LoanedBooksAllListView</code>),并从URL获取作者信息。</li>
+ <li>您还需要将作者的名称,传递给上下文中的页面。要在基于类的视图中执行此操作,您需要实现<code>get_context_data()</code>(在下面讨论)。</li>
+ </ul>
+ </li>
+ <li>可以使用基于函数的视图(以及关联的模型和表单),或使用通用<code>CreateView</code>,以创建添加注释表单。如果您使用<code>CreateView</code>(推荐),那么:
+ <ul>
+ <li>您还需要将博客文章的名称,传递到上下文中的评论页面(实现<code>get_context_data()</code> ,如下所述)。</li>
+ <li>表单应仅显示用户输入的注释“description”(日期和相关的博客文章,不应该是可编辑的)。由于它们本身不在表单中,因此您的代码,需要在<code> form_valid()</code> 函数中,设置注释的作者,以便将其保存到模型中(<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user">如此处所述</a> -  Django文档)。在同一个功能中,我们设置了相关的博客。可能的实现如下所示(<code>pk</code>是从URL / URL配置传入的博客ID)。
+ <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>在表单验证后,您需要提供成功的 URL,以进行重新定向;这应该是原来的博客。为此,您需要覆盖 <code>get_success_url()</code>,并为原来的博客 “反转” URL 。您可以使用<code>self.kwargs</code>属性,获取所需的博客ID,如上面的 <code>form_valid()</code> 方法所示。</li>
+ </ul>
+ </li>
+</ol>
+
+<p>我们简要地讨论了在<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views#Overriding_methods_in_class-based_views">Django教程 6:通用列表和详细信息视图</a>主题中,在基于类的视图中,将上下文传递给模板。要执行此操作,您需要覆盖<code>get_context_data()</code>(首先,获取现有上下文,使用要传递给模板的任何其他变量,更新它,然后返回更新的上下文)。例如,下面的代码片段,显示了如何根据<code>BlogAuthor</code> id,将 blogger 对象添加到上下文中。</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="评估">评估</h2>
+
+<p>这个任务的评估,可以在<a href="https://github.com/mdn/django-diy-blog/blob/master/MarkingGuide.md">Github</a>上找到。此评估主要基于您的应用程序,满足上面列出要求的程度,尽管评估的某些部分,会检查您的代码是否使用了适当的模型,并且您至少编写了一些测试代码。完成后,您可以查看我们<a href="https://github.com/mdn/django-diy-blog">完成的示例</a>,该示例项目的表现是 “满分”。</p>
+
+<p>完成本单元后,表示您还完成了所有 MDN 用于学习《基本 Django 服务器端网站编程》的内容!我们希望您喜欢这个模块,并感觉您已经掌握了基础知识!</p>
+
+<p>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p>
+
+<p> </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></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: 会话框架</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 到生产环境</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>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/django/forms/index.html b/files/zh-cn/learn/server-side/django/forms/index.html
new file mode 100644
index 0000000000..ab99a520a3
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/forms/index.html
@@ -0,0 +1,671 @@
+---
+title: 'Django 教程 9: 使用表单'
+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">在<font><font>本教程中,我们将向您展示如何在Django中使用HTML表单,特别是编写表单以创建,更新和删除模型实例的最简单方法。</font><font>作为本演示的一部分,我们将扩展</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,以便图书馆员可以使用我们自己的表单(而不是使用管理员应用程序)更新图书,创建,更新和删除作者</font></font>。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前置条件:</th>
+ <td>完成所有先前的教程主题,包含 <a href="/zh-CN/docs/Learn/Server-side/Django/authentication_and_sessions">Django 教程 8: 使用者授权与许可</a>。</td>
+ </tr>
+ <tr>
+ <th scope="row">目標:</th>
+ <td>了解如何撰写表单,向使用者取得资料,并更新资料库。了解通用类别表单编辑视图,如何大量地简化用于单一模型的新表单制作。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p>一张 <a href="/zh-CN/docs/Web/Guide/HTML/Forms">HTML 表单</a> ,是由一个或多个栏位/widget在一个网页上组成的,以用于向使用者收集资料,并提交至伺服器。表单是一个弹性的机制,用于收集使用者输入,有合适的 widgets 可输入许多不同型态的资料,包含文字框、复选框、单选按钮、日期选取组件等等。若是允许我们用 <code>POST</code> 方式传送资料,并附加 CSRF 跨站要求伪造保护,表单也是与伺服器分享资料的一种相对安全的方式。</p>
+
+<p>在这个教程目前为止,我们还没有创造任何表单,但我们已经在 Django 管理站点遇到这些表单了— 例如以下的撷图展示了一张表单,用于编辑我们的一个 <a href="/zh-CN/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>在本教程中,我们将展示一些方法,用以创造并使用表单,特别是,当你创造用以操作资料模型的表单,通用编辑表单视图如何显著降低你的工作量。在此过程中,我们将通过添加表单,来扩展我们的 LocalLibrary 应用程序,以允许图书馆员更新图书馆书本,我们将创建页面来创建,编辑和删除书本和作者(复制上面显示的表格的基本版本,以便编辑书本)。</p>
+
+<h2 id="HTML_表单">HTML 表单</h2>
+
+<p>首先简要概述<a href="/zh-CN/docs/Learn/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>&lt;form&gt;...&lt;/form&gt;</code> 标记内的元素集合,包含至少一个<code>type="submit"</code>的<code>input</code> 输入元素。</p>
+
+<pre class="brush: html">&lt;form action="/team_name_url/" method="post"&gt;
+    &lt;label for="team_name"&gt;Enter name: &lt;/label&gt;
+    &lt;input id="team_name" type="text" name="name_field" value="Default name for team."&gt;
+    &lt;input type="submit" value="OK"&gt;
+&lt;/form&gt;</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方法: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 如何处理表单请求的流程图,从对包含表单的页面的请求开始(以绿色显示)。</p>
+
+<p><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>
+
+<p> </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="表单">表单</h3>
+
+<p><code>Form</code> 类是 Django 表单处理系统的核心。它指定表单中的字段、其布局、显示窗口小部件、标签、初始值、有效值,以及(一旦验证)与无效字段关联的错误消息。该类还提供了使用预定义格式(表,列表等)在模板中呈现自身的方法,或者用于获取任何元素的值(启用细粒度手动呈现)的方法。</p>
+
+<h4 id="声明表单">声明表单</h4>
+
+<p><code>Form</code> 的声明语法,与声明<code>Model</code>非常相似,并且共享相同的字段类型(以及一些类似的参数)。这是有道理的,因为在这两种情况下,我们都需要确保每个字段处理正确类型的数据,受限于有效数据,并具有显示/文档的描述。</p>
+
+<p>要创建表单,我们导入表单库,从<code>Form</code> 类派生,并声明表单的字段。我们的图书馆图书续借表单的一个非常基本的表单类如下所示:</p>
+
+<pre class="brush: python">from django import forms
+
+class RenewBookForm(forms.Form):
+    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
+</pre>
+
+<h4 id="表单字段">表单字段</h4>
+
+<p>在这种情况下,我们有一个 <code><a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields//#datefield">DateField</a></code> 用于输入续借日期,该日期将使用空白值在 HTML 中呈现,默认标签为“续借日期:”,以及一些有用的用法文本:“输入从现在到 4 周之间的日期(默认为 3)周)。”  由于没有指定其他可选参数,该字段将使用 <a href="https://docs.djangoproject.com/zh-hans/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),并且将使用默认<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields/#widget">小部件</a>呈现:<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/widgets/#django.forms.DateInput">DateInput</a>。</p>
+
+<p>还有许多其他类型的表单字段,您可以从它们与等效模型字段类的相似性中大致认识到:</p>
+
+<p><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>下面列出了大多数字段共有的参数(这些参数具有合理的默认值):</p>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#required">required</a>: 如果为<code>True</code>,则该字段不能留空或给出<code>None</code>值。默认情况下需要字段,因此您可以设置<code>required=False</code>以允许表单中的空白值。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a>: 在 HTML 中呈现字段时使用的标签。如果未指定<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields/#label">label</a>,则 Django 将通过大写第一个字母、并用空格替换下划线(例如续订日期)的方式,从字段名称创建一个。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label-suffix">label_suffix</a>: 默认情况下,标签后面会显示冒号(例如续借日期:)。此参数允许您指定包含其他字符的不同后缀。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#initial">initial</a>: 显示表单时,字段的初始值。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: 要使用的显示小部件。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#help-text">help_text</a> (如上例所示):可以在表单中显示的附加文本,用于说明如何使用该字段。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#error-messages">error_messages</a>: 字段的错误消息列表。如果需要,您可以使用自己的消息,覆盖这些消息。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#validators">validators</a>: 验证时将在字段上调用的函数列表。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#localize">localize</a>: 启用表单数据输入的本地化(有关详细信息,请参阅链接)。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled">disabled</a>: 如果为<code>True</code>,该字段会被显示,但无法编辑其值。默认值为<code>False</code>。</li>
+</ul>
+
+<h4 id="验证">验证</h4>
+
+<p>Django 提供了许多可以验证数据的地方。验证单个字段的最简单方法,是覆盖要检查的字段的方法<code>clean_<strong>&lt;fieldname&gt;</strong>()</code> 。因此,例如,我们可以通过实现<code>clean_<strong>renewal_date</strong>()  </code>,验证输入的<code>renewal_date</code> 值是从现在到 4 周之间,如下所示。</p>
+
+<pre class="brush: python">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 &lt; datetime.date.today():
+            raise ValidationError(_('Invalid date - renewal in past'))
+
+        #Check date is in range librarian allowed to change (+4 weeks).
+        if data &gt; 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>有两件重要的事情需要注意。首先,我们使用<code>self.cleaned_data['renewal_date']</code> 获取数据,并且无论是否在函数末尾更改数据,我们都会返回此数据。此步骤使用默认验证器,将数据“清理”、并清除可能不安全的输入,并转换为数据的正确标准类型(在本例中为Python <code>datetime.datetime</code>对象)。</p>
+
+<p>第二点是,如果某个值超出了我们的范围,我们会引发<code>ValidationError</code>,指定在输入无效值时,我们要在表单中显示的错误文本。上面的例子,也将这个文本包含在 <a href="https://docs.djangoproject.com/zh-hans/2.0/topics/i18n/translation/">Django 的翻译函数</a><code>ugettext_lazy()</code>中(导入为 <code>_()</code>),如果你想在稍后翻译你的网站,这是一个很好的做法。</p>
+
+<div class="note">
+<p><strong>注意:</strong>  在<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/validation/">表单和字段验证</a>(Django docs)中验证表单还有其他很多方法和示例。例如,如果您有多个相互依赖的字段,则可以覆盖<a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.clean">Form.clean()</a> 函数并再次引发<code>ValidationError</code>。</p>
+</div>
+
+<p>这就是我们在这个例子中,对表单所需要了解的全部内容!</p>
+
+<h4 id="复制表单">复制表单</h4>
+
+<p>创建并打开文件 <strong>locallibrary/catalog/forms.py</strong>,并将前一个块中的整个代码清单,复制到其中。</p>
+
+<h3 id="URL_配置">URL 配置</h3>
+
+<p>在创建视图之前,让我们为续借页面添加 URL 配置。将以下配置,复制到<strong>locallibrary/catalog/urls.py </strong>的底部。</p>
+
+<pre class="brush: python">urlpatterns += [
+ path('book/&lt;uuid:pk&gt;/renew/', views.renew_book_librarian, name='renew-book-librarian'),
+]</pre>
+
+<p>URL 配置会将格式为 <strong>/catalog/book/<em>&lt;bookinstance id&gt;</em>/renew/</strong>的URL,重定向到 <strong>views.py </strong>中,名为<code>renew_book_librarian()</code> 的函数,并将<code>BookInstance</code> id作为名为 <code>pk</code>的参数发送。只有 <code>pk</code>是正确格式化的 <code>uuid</code>,该模式才会匹配。</p>
+
+<div class="note">
+<p><strong>注意</strong>:  我们可以将捕获的 URL 数据,命名为“<code>pk</code>”,因为我们可以完全控制视图函数(我们不使用需要具有特定名称的参数的通用详细视图类)。然而,<code>pk</code>,“主键” primary key 的缩写,是一个合理的惯例!</p>
+</div>
+
+<h3 id="视图">视图</h3>
+
+<p>正如上面的 Django 表单处理过程中,所讨论的那样,视图必须在首次调用时呈现默认表单,然后在数据无效时,重新呈现它,并显示错误消息,或者数据有效时,处理数据,并重定向到新页面。为了执行这些不同的操作,视图必须能够知道,它是第一次被调用以呈现默认表单,还是后续处理以验证数据。</p>
+
+<p>对于使用<code>POST</code> 请求向服务器提交信息的表单,最常见的模式,是视图针对<code>POST</code> 请求类型进行测试(<code>if request.method == 'POST':</code>)以识别表单验证请求和<code>GET</code> (使用一个<code>else</code> 条件)来识别初始表单创建请求。如果要使用<code>GET</code> 请求提交数据,则识别这是第一个、还是后续视图调用的典型方法,是读取表单数据(例如,读取表单中的隐藏值)。</p>
+
+<p>书本续借过程将写入我们的数据库,因此按照惯例,我们使用 <code>POST</code> 请求方法。下面的代码片段,显示了这种函数视图的(非常标准)模式。</p>
+
+<pre class="brush: python">from django.shortcuts import get_object_or_404
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+import datetime
+
+from .forms import RenewBookForm
+
+def renew_book_librarian(request, pk):
+    book_inst=get_object_or_404(BookInstance, pk = pk)
+
+    # If this is a POST request then process the Form data
+<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>首先,我们导入我们的表单(<code>RenewBookForm</code>)和视图函数中使用的许多其他有用的对象/方法:</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>:  根据模型的主键值,从模型返回指定的对象,如果记录不存在,则引发<code>Http404</code> 异常(未找到)。</li>
+ <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.HttpResponseRedirect">HttpResponseRedirect</a></code>:  这将创建指向指定URL的重定向(HTTP状态代码 302)。</li>
+ <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#django.urls.reverse">reverse()</a></code>:  这将从 URL 配置名称和一组参数生成 URL。它是我们在模板中使用的 <code>url</code> 标记的 Python 等价物。</li>
+ <li><code><a href="https://docs.python.org/3/library/datetime.html">datetime</a></code>:  用于操作日期和时间的 Python 库。</li>
+</ul>
+
+<p>在视图中,我们首先使用 <code>get_object_or_404()</code>中的 <code>pk</code> 参数,来获取当前的 <code>BookInstance</code> (如果这不存在,视图将立即退出,页面将显示“未找到”错误)。如果这不是 <code>POST</code> 请求(由 <code>else</code> 子句处理),那么我们创建默认表单,传递 <code>renewal_date</code> 字段的<code>initial</code> 初始值(如下面的<strong>粗体</strong>所示,这是从当前日期起的 3 周)。</p>
+
+<pre class="brush: python"> 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>创建表单后,我们调用 <code>render()</code> 来创建HTML页面,指定模板和包含表单的上下文。在这种情况下,上下文还包含我们的 <code>BookInstance</code>,我们将在模板中使用它,来提供有关我们正在续借的书本信息。</p>
+
+<p>但是,如果这是一个<code>POST</code> 请求,那么我们创建表单对象,并使用请求中的数据填充它。此过程称为“绑定”,并且允许我们验证表单。然后我们检查表单是否有效,它运行所有字段上的所有验证代码 - 包括用于检查我们的日期字段,实际上是有效日期的通用代码,以及用于检查日期的特定表单的<code>clean_renewal_date()</code>函数在合适的范围内。</p>
+
+<pre class="brush: python">    book_inst=get_object_or_404(BookInstance, pk = pk)
+
+    # If this is a POST request then process the Form data
+    if request.method == 'POST':
+
+        # Create a form instance and populate it with data from the request (binding):
+<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>如果表单无效,我们再次调用<code>render()</code> ,但这次在上下文中传递的表单值将包含错误消息。</p>
+
+<p>如果表单有效,那么我们可以开始使用数据,通过 <code>form.cleaned_data</code>属性访问它(例如 <code>data = form.cleaned_data['renewal_date']</code>)。这里我们只将数据保存到关联的<code>BookInstance</code> 对象的<code>due_back</code> 值中。</p>
+
+<div class="warning">
+<p><strong>重要</strong>:  虽然您也可以通过请求直接访问表单数据(例如<code>request.POST['renewal_date']</code> 或 <code>request.GET['renewal_date']</code>(如果使用 GET 请求),但不建议这样做。清理后的数据是无害的、验证过的、并转换为 Python 友好类型。</p>
+</div>
+
+<p>视图的表单处理部分的最后一步,是重定向到另一个页面,通常是“成功”页面。在这种情况下,我们使用 <code>HttpResponseRedirect</code> 和 <code>reverse()</code> ,重定向到名为'<code>all-borrowed</code>'的视图(这是在 <a href="/zh-CN/docs/learn/Server-side/Django/Authentication#Challenge_yourself">Django 教程第 8 部分中创建的 “挑战”:用户身份验证和权限</a>)。如果您没有创建该页面,请考虑重定向到URL'/'处的主页。</p>
+
+<p>这就是表单处理本身所需的一切,但我们仍然需要将视图,限制为图书馆员可以访问。我们应该在 <code>BookInstance</code> (“<code>can_renew</code>”)中创建一个新的权限,但为了简单起见,我们只需使用<code>@permission_required</code>函数装饰器,和我们现有的 <code>can_mark_returned</code> 权限。</p>
+
+<p>因此,最终视图如下所示。请将其复制到 <strong>locallibrary/catalog/views.py </strong>的底部。</p>
+
+<pre><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="模板">模板</h3>
+
+<p>创建视图中引用的模板(<strong>/catalog/templates/catalog/book_renew_librarian.html</strong>),并将下面的代码,复制到其中:</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+{% block content %}
+
+ &lt;h1&gt;Renew: \{{bookinst.book.title}}&lt;/h1&gt;
+ &lt;p&gt;Borrower: \{{bookinst.borrower}}&lt;/p&gt;
+ &lt;p{% if bookinst.is_overdue %} class="text-danger"{% endif %}&gt;Due date: \{{bookinst.due_back}}&lt;/p&gt;
+
+<strong> &lt;form action="" method="post"&gt;
+ {% csrf_token %}
+ &lt;table&gt;
+ \{{ form }}
+  &lt;/table&gt;
+ &lt;input type="submit" value="Submit" /&gt;
+ &lt;/form&gt;</strong>
+
+{% endblock %}</pre>
+
+<p>这里大部分内容,和以前的教程都是完全类似的。我们扩展基本模板,然后重新定义内容块。我们能够引用 <code>\{{bookinst}}</code>(及其变量),因为它被传递到 <code>render()</code>函数中的上下文对象中,我们使用这些来列出书名,借阅者和原始截止日期。</p>
+
+<p>表单代码相对简单。首先,我们声明表单标签,指定表单的提交位置(<code>action</code>)和提交数据的方法(在本例中为 “HTTP POST”) - 如果您回想一下页面顶部的 HTML 表单概述,如图所示的空<code>action</code> ,意味着表单数据将被发布回页面的当前 URL(这是我们想要的!)。在标签内部,我们定义了<code>submit</code> 提交输入,用户可以按这个输入来提交数据。在表单标签内添加的<code>{% csrf_token %}</code> ,是 Django 跨站点伪造保护的一部分。</p>
+
+<div class="note">
+<p><strong>注意:</strong>  将<code>{% csrf_token %}</code> 添加到您创建的每个使用 <code>POST</code> 提交数据的 Django 模板中。这将减少恶意用户劫持表单的可能性。</p>
+</div>
+
+<p>剩下的就是 <code>\{{form}}</code>模板变量,我们将其传递给上下文字典中的模板。也许不出所料,当如图所示使用时,它提供了所有表单字段的默认呈现,包括它们的标签、小部件、和帮助文本 - 呈现如下所示:</p>
+
+<pre class="brush: html">&lt;tr&gt;
+  &lt;th&gt;&lt;label for="id_renewal_date"&gt;Renewal date:&lt;/label&gt;&lt;/th&gt;
+  &lt;td&gt;
+  &lt;input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required /&gt;
+  &lt;br /&gt;
+  &lt;span class="helptext"&gt;Enter date between now and 4 weeks (default 3 weeks).&lt;/span&gt;
+  &lt;/td&gt;
+&lt;/tr&gt;
+</pre>
+
+<div class="note">
+<p><strong>注意:</strong>   它可能并不明显,因为我们只有一个字段,但默认情况下,每个字段都在其自己的表格行中定义(这就是变量在上面的<code>table </code>表格标记内部的原因)。如果您引用模板变量<code>\{{ form.as_table }}</code>,会提供相同的渲染。</p>
+</div>
+
+<p>如果您输入无效日期,您还会获得页面中呈现的错误列表(下面以<strong>粗体</strong>显示)。</p>
+
+<pre class="brush: html">&lt;tr&gt;
+  &lt;th&gt;&lt;label for="id_renewal_date"&gt;Renewal date:&lt;/label&gt;&lt;/th&gt;
+  &lt;td&gt;
+<strong>  &lt;ul class="errorlist"&gt;
+  &lt;li&gt;Invalid date - renewal in past&lt;/li&gt;
+  &lt;/ul&gt;</strong>
+  &lt;input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required /&gt;
+  &lt;br /&gt;
+  &lt;span class="helptext"&gt;Enter date between now and 4 weeks (default 3 weeks).&lt;/span&gt;
+ &lt;/td&gt;
+&lt;/tr&gt;</pre>
+
+<h4 id="使用表单模板变量的其他方法">使用表单模板变量的其他方法</h4>
+
+<p>如上所示使用<code>\{{form}}</code>,每个字段都呈现为表格行。您还可以将每个字段呈现为列表项(使用<code>\{{form.as_ul}}</code> )或作为段落(使用<code>\{{form.as_p}}</code>)。</p>
+
+<p>更酷的是,您可以通过使用点表示法,索引其属性,来完全控制表单每个部分的呈现。例如,我们可以为<code>renewal_date</code> 字段访问许多单独的项目:</p>
+
+<ul>
+ <li><code>\{{form.renewal_date}}:</code> 整个领域。</li>
+ <li><code>\{{form.renewal_date.errors}}</code>: 错误列表。</li>
+ <li><code>\{{form.renewal_date.id_for_label}}</code>: 标签的 id 。</li>
+ <li><code>\{{form.renewal_date.help_text}}</code>: 字段帮助文本。</li>
+ <li> 其他等等!</li>
+</ul>
+
+<p>有关如何在模板中,手动呈现表单,并动态循环模板字段的更多示例,请参阅<a href="https://docs.djangoproject.com/zh-hans/2.0/topics/forms/#rendering-fields-manually">使用表单&gt;手动呈现字段</a>(Django文档)。</p>
+
+<h3 id="测试页面">测试页面</h3>
+
+<p>如果您接受了<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Authentication#Challenge_yourself">Django 教程第 8 部分中的 “挑战”:用户身份验证和权限</a>,您将获得图书馆中借出的所有书本的列表,这只有图书馆工作人员才能看到。我们可以使用下面的模板代码,为每个项目旁边的续借页面,添加链接。</p>
+
+<pre class="brush: html">{% if perms.catalog.can_mark_returned %}- &lt;a href="{% url 'renew-book-librarian' bookinst.id %}"&gt;Renew&lt;/a&gt; {% endif %}</pre>
+
+<div class="note">
+<p><strong>注意</strong>:  请记住,您的测试登录需要具有“<code>catalog.can_mark_returned</code>”权限,才能访问续借书本页面(可能使用您的超级用户帐户)。</p>
+</div>
+
+<p>您也可以手动构建这样的测试URL  -  <a href="http://127.0.0.1:8000/catalog/book/&lt;bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em>&lt;bookinstance_id&gt;</em>/renew/</a> (可以通过导航到图书馆中的书本详细信息页面,获取有效的 bookinstance id,并复制<code>id</code> 字段)。</p>
+
+<h3 id="它看起来是什么样子?">它看起来是什么样子?</h3>
+
+<p>如果您成功,默认表单将如下所示:</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>输入无效值的表单将如下所示:</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>所有包含续借链接的图书清单如下所示:</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="模型表单">模型表单</h2>
+
+<p>使用上述方法创建<code>Form</code> 类非常灵活,允许您创建任何类型的表单页面,并将其与任何单一模型、或多个模型相关联。</p>
+
+<p>但是,如果您只需要一个表单,来映射单个模型的字段,那么您的模型,将已经定义了表单中所需的大部分信息:字段、标签、帮助文本等。而不是在表单中重新创建模型定义,使用 <a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">ModelForm </a>帮助程序类从模型创建表单更容易。然后,可以在视图中使用此<code>ModelForm</code> ,其方式与普通<code>Form</code>完全相同。</p>
+
+<p>包含与原始<code>RenewBookForm</code> 相同的字段的基本 <code>ModelForm</code> 如下所示。创建表单所需要做的,就是添加带有相关模型(<code>BookInstance</code>)的<code>class Meta</code>、和要包含在表单中的模型字段列表(您可以使用 <code>fields = '__all__'</code>,以包含所有字段,或者您可以使用 <code>exclude</code> (而不是字段),指定不包含在模型中的字段)。</p>
+
+<pre class="brush: python">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>注意</strong>:  这可能看起来不像使用<code>Form</code> 那么简单(在这种情况下不是这样,因为我们只有一个字段)。但是,如果你有很多字段,它可以显着减少代码量!</p>
+</div>
+
+<p>其余信息来自模型字段的定义(例如标签、小部件、帮助文本、错误消息)。如果这些不太正确,那么我们可以在<code> Meta</code>类中覆盖它们,指定包含要更改的字段、及其新值的字典。例如,在这种形式中,我们可能需要 “更新日期” <em>Renewal date </em>字段的标签(而不是基于字段名称的默认值:截止日期 <em>Due date</em>),并且我们还希望我们的帮助文本,特定于此用例。下面的<code>Meta</code> 显示了如何覆盖这些字段,如果默认值不够,您可以类似地方式设置<code>widgets</code> 窗口小部件和<code>error_messages</code> 。</p>
+
+<pre class="brush: python">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>要添加验证,您可以使用与普通表单相同的方法 - 定义名为 <code>clean_<em>field_name</em>()</code>的函数,并为无效值引发<code>ValidationError</code> 异常。与我们原始形式的唯一区别,是模型字段名为<code>due_back</code> 而不是“<code>renewal_date</code>”。</p>
+
+<pre class="brush: python">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 &lt; datetime.date.today():
+           raise ValidationError(_('Invalid date - renewal in past'))
+
+       #Check date is in range librarian allowed to change (+4 weeks)
+       if data &gt; 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>下面的 <code>RenewBookModelForm</code> 类现在在功能上等同于我们原来的 <code>RenewBookForm</code>。您可以在当前使用<code>RenewBookForm </code>的任何地方导入和使用它。</p>
+
+<h2 id="通用编辑视图">通用编辑视图</h2>
+
+<p>我们在上面的函数视图示例中,使用的表单处理算法,表示表单编辑视图中非常常见的模式。 Django 通过创建基于模型创建、编辑和删除视图的<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/class-based-views/generic-editing/">通用编辑视图</a>,为您抽象出大部分“样板”。这些不仅处理“视图”行为,而且它们会自动从模型中为您创建表单类(<code>ModelForm</code>)。</p>
+
+<div class="note">
+<p><strong>注意:  </strong>除了这里描述的编辑视图之外,还有一个 <a href="https://docs.djangoproject.com/zh-hans/2.0/ref/class-based-views/generic-editing/#formview">FormView </a>类,它位于我们的函数视图,和其他通用视图之间的 “灵活性” 与 “编码工作” 之间。使用 <code>FormView</code> ,您仍然需要创建表单,但不必实现所有标准表单处理模式。相反,您只需提供一个函数的实现,一旦知道提交有效,就会调用该函数。</p>
+</div>
+
+<p>在本节中,我们将使用通用编辑视图,来创建页面,以添加从我们的库中创建、编辑和删除<code>Author</code> 作者记录的功能 - 有效地提供管理站点一部分的基本重新实现(这可能很有用,如果您需要比管理站点能提供的、更加灵活的管理功能)。</p>
+
+<h3 id="视图_2">视图</h3>
+
+<p>打开视图文件(<strong>locallibrary/catalog/views.py</strong>),并将以下代码块,附加到其底部:</p>
+
+<pre class="brush: python">from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.urls import reverse_lazy
+from .models import Author
+
+class AuthorCreate(CreateView):
+ model = Author
+ fields = '__all__'
+ initial={'date_of_death':'05/01/2018',}
+
+class AuthorUpdate(UpdateView):
+ model = Author
+ fields = ['first_name','last_name','date_of_birth','date_of_death']
+
+class AuthorDelete(DeleteView):
+ model = Author
+ success_url = reverse_lazy('authors')</pre>
+
+<p>如您所见,要创建视图,您需要从<code>CreateView</code>, <code>UpdateView</code>, 和 <code>DeleteView</code>(分别)派生,然后定义关联的模型。</p>
+
+<p>对于 “创建” 和 “更新” 的情况,您还需要指定要在表单中显示的字段(使用与<code>ModelForm</code>相同的语法)。在这种情况下,我们将说明两者的语法,如何显示 “所有” 字段,以及如何单独列出它们。您还可以使用 field_name / value对的字典,为每个字段指定初始值(此处我们为了演示目的,而任意设置死亡日期 - 您可能希望删除它!)。默认情况下,这些视图会在成功时,重定向到显示新创建/编辑的模型项的页面,在我们的示例中,这将是我们在上一个教程中,创建的作者详细信息视图。您可以通过显式声明参数<code>success_url</code>  ,指定备用重定向位置(与<code>AuthorDelete</code> 类一样)。</p>
+
+<p><code>AuthorDelete</code> 类不需要显示任何字段,因此不需要指定这些字段。但是你需要指定<code>success_url</code>,因为 Django 没有明显的默认值。在这种情况下,我们使用<code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#reverse-lazy">reverse_lazy()</a></code>函数,在删除作者后,重定向到我们的作者列表 -  <code>reverse_lazy()</code>是一个延迟执行的<code>reverse()</code>版本,在这里使用,是因为我们提供了一个基于类的 URL 查看属性。</p>
+
+<h3 id="模板_2">模板</h3>
+
+<p>“创建” 和 “更新” 视图默认使用相同的模板,它将以您的模型命名:<em>model_name</em><strong>_form.html</strong>(您可以使用视图中的<code>template_name_suffix</code> 字段,将后缀更改为<strong>_form</strong> 以外的其他内容,例如,<code>template_name_suffix = '_other_suffix'</code>)</p>
+
+<p>创建模板文件 <strong>locallibrary/catalog/templates/catalog/author_form.html</strong>,并复制到下面的文本中。</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+
+&lt;form action="" method="post"&gt;
+ {% csrf_token %}
+ &lt;table&gt;
+ \{{ form.as_table }}
+ &lt;/table&gt;
+ &lt;input type="submit" value="Submit" /&gt;
+
+&lt;/form&gt;
+{% endblock %}</pre>
+
+<p>这与我们之前的表单类似,并使用表单呈现字段。另请注意我们如何声明<code>{% csrf_token %}</code>,以确保我们的表单能够抵抗 CSRF 攻击。</p>
+
+<p>“删除”视图需要查找以 <em>model_name</em><strong>_confirm_delete.html</strong> 格式命名的模板(同样,您可以在视图中,使用<code>template_name_suffix</code> 更改后缀)。创建模板文件 <strong>locallibrary/catalog/templates/catalog/author_confirm_delete</strong><strong>.html</strong> ,并复制到下面的文本中。</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+
+&lt;h1&gt;Delete Author&lt;/h1&gt;
+
+&lt;p&gt;Are you sure you want to delete the author: \{{ author }}?&lt;/p&gt;
+
+&lt;form action="" method="POST"&gt;
+ {% csrf_token %}
+ &lt;input type="submit" action="" value="Yes, delete." /&gt;
+&lt;/form&gt;
+
+{% endblock %}
+</pre>
+
+<h3 id="URL配置">URL配置</h3>
+
+<p>打开 URL 配置文件(<strong>locallibrary/catalog/urls.py</strong>),并将以下配置,添加到文件的底部:</p>
+
+<pre class="brush: python">urlpatterns += [
+    path('author/create/', views.AuthorCreate.as_view(), name='author_create'),
+    path('author/&lt;int:pk&gt;/update/', views.AuthorUpdate.as_view(), name='author_update'),
+    path('author/&lt;int:pk&gt;/delete/', views.AuthorDelete.as_view(), name='author_delete'),
+]</pre>
+
+<p>这里没有什么特别的新东西!您可以看到视图是类,因此必须通过<code>.as_view()</code>调用,并且您应该能够识别每种情况下的 URL 模式。我们必须使用 <code>pk</code> 作为捕获的主键值的名称,因为这是视图类所期望的参数名称。</p>
+
+<p>作者的创建,更新和删除页面,现在已准备好进行测试(在这种情况下,我们不会将它们连接到站点侧栏,尽管如果您愿意,也可以这样做)。</p>
+
+<div class="note">
+<p><strong>注意</strong>:   敏锐的用户会注意到,我们没有采取任何措施,来防止未经授权的用户访问这些页面!我们将其作为练习留给您(提示:您可以使用<code>PermissionRequiredMixin</code> ,并创建新权限,或重用我们的<code>can_mark_returned</code>权限)。</p>
+</div>
+
+<h3 id="测试页面_2">测试页面</h3>
+
+<p>首先,使用具有访问作者编辑页面权限的帐户(由您决定),登录该站点。</p>
+
+<p>然后导航到作者创建页面: <a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a>,它应该如下面的截图。</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>输入字段的值,然后按“提交” <strong>Submit</strong>  ,保存作者记录。现在,您应该进入新作者的详细视图,其 URL 为 http://127.0.0.1:8000/catalog/author/10。</p>
+
+<p>您可以通过将 /update/ ,附加到详细视图 URL 的末尾,来测试编辑记录(例如http://127.0.0.1:8000/catalog/author/10/update/) - 我们不显示截图,因为它看起来就像“创建”页面!</p>
+
+<p>最后,我们可以删除页面,方法是将删除,附加到作者详细信息视图URL的末尾(例如http://127.0.0.1:8000/catalog/author/10/delete/)。 Django应该显示如下所示的删除页面。按 "是,删除" <strong>(Yes, delete)</strong>。删除记录,并将其带到所有作者的列表中。</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>
+
+<p> </p>
+
+<h2 id="挑战自己">挑战自己</h2>
+
+<p>创建一些表单,来创建、编辑和删除书本记录<code>Book</code>。您可以使用与作者<code>Authors</code>完全相同的结构。如果您的 <strong>book_form.html</strong> 模板只是<strong> author_form.html</strong> 模板的复制重命名版本,则新的“创建图书”页面,将如下所示:</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="总结">总结</h2>
+
+<p>创建和处理表单可能是一个复杂的过程! Django通过提供声明、呈现和验证表单的编程机制,使其变得更加容易。此外,Django提供了通用的表单编辑视图,几乎可以完成所有工作,以定义可以创建,编辑和删除与单个模型实例关联的记录的页面。</p>
+
+<p>表单可以完成更多工作(请参阅下面的“请参阅”列表),但您现在应该了解,如何将基本表单和表单处理代码,添加到您自己的网站。</p>
+
+<h2 id="也可以参考">也可以参考</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 &gt; 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>
+
+<p> </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-cn/learn/server-side/django/generic_views/index.html b/files/zh-cn/learn/server-side/django/generic_views/index.html
new file mode 100644
index 0000000000..c8eeefc366
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/generic_views/index.html
@@ -0,0 +1,630 @@
+---
+title: 'Django 教程 6: 通用列表和详细信息视图'
+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="/zh-CN/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>完成所有先前的教程主题,包含<a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5: 创建主页。</a></td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>了解如何使用、在何处使用通用类别视图,以及如何从URLs取出模式,如何传送资料到视图。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p>本教程中,通过为书本和作者添加列表和详细信息页面,我们将完成第一个版本的<a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 网站(或者更准确地说,我们将向您展示如何实现书页,并让您自己创建作者页面!)</p>
+
+<p>该过程类似于创建索引页面,我们在上一个教程中展示了该页面。我们仍然需要创建URL地图,视图和模板。主要区别在于,对于详细信息页面,我们还有一个额外的挑战,即从URL中的模式中提取信息,并将其传递给视图。对于这些页面,我们将演示一种完全不同的视图类型:基于类别的通用列表和详细视图。这些可以显着减少所需的视图代码量,使其更易于编写和维护。</p>
+
+<p>本教程的最后一部分,将演示在使用基于类别的通用列表视图时,如何对数据进行分页。</p>
+
+<h2 id="书本清单页面">书本清单页面</h2>
+
+<p>书本清单页面,将显示页面中所有可用图书记录的列表,使用url: <code>catalog/books/</code>进行访问。该页面将显示每条记录的标题和作者,标题是指向相关图书详细信息页面的超链接。该页面将具有与站点中,所有其他页面相同的结构和导航,因此,我们可以扩展在上一个教程中创建的基本模板(<strong>base_generic.html</strong>)。</p>
+
+<h3 id="URL_映射">URL 映射</h3>
+
+<p>打开<strong>/catalog/urls.py</strong> ,并复制到下面粗体显示的行中。就像索引页面的方式,这个<code>path()</code>函数,定义了一个与 URL 匹配的模式('<strong>books/</strong>'),如果URL匹配,将调用视图函数(<code>views.BookListView.as_view()</code>)和一个对应这个特定映射的名称。</p>
+
+<pre class="brush: python">urlpatterns = [
+ path('', views.index, name='index'),
+<strong>  </strong>path<strong>('books/', views.BookListView.as_view(), name='books'),</strong>
+]</pre>
+
+<p>正如前一个教程中所讨论的,URL 必须已经先匹配了<code>/catalog</code>,因此实际上将为 URL 调用的视图是:<code>/catalog/books/</code>。</p>
+
+<p>视图函数具有与以前不同的格式 - 这是因为该视图,实际上将以类别来实现。我们将继承现有的泛型视图函数,该函数已经完成了我们希望此视图函数执行的大部分工作,而不是从头开始编写自己的函数。对于基于Django类的视图,我们通过调用类方法<code>as_view()</code>,来访问适当的视图函数。这样做可以创建类的实例,并确保为传入的 HTTP 请求调用正确的处理程序方法。</p>
+
+<h3 id="视图_(基于类别)">视图 (基于类别)</h3>
+
+<p>我们可以很容易地,将书本列表视图编写为常规函数(就像我们之前的索引视图一样),它将查询数据库中的所有书本,然后调用<code>render()</code>,将列表传递给指定的模板。然而,我们用另一种方法取代,我们将使用基于类的通用列表视图(<code>ListView</code>) - 一个继承自现有视图的类。因为通用视图,已经实现了我们需要的大部分功能,并且遵循 Django 最佳实践,我们将能够创建更强大的列表视图,代码更少,重复次数更少,最终维护更少。</p>
+
+<p>打开<strong> catalog/views.py</strong>,并将以下代码复制到文件的底部:</p>
+
+<pre class="brush: python">from django.views import generic
+
+class BookListView(generic.ListView):
+ model = Book</pre>
+
+<p>就是这样!通用视图将查询数据库,以获取指定模型(<code>Book</code>)的所有记录,然后呈现位于<strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> 的模板(我们将在下面创建)。在模板中,您可以使用名为<code>object_list</code> 或 <code>book_list</code>的模板变量(即通常为“<code><em>the_model_name</em>_list</code>”),以访问书本列表。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 模板位置的这个尴尬路径不是印刷错误 - 通用视图在应用程序的<code>/<em>application_name</em>/templates/</code>目录中<code>(/catalog/templates/</code>),查找模板<code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code>(在本例中为<code>catalog/book_list.html</code>)。</p>
+</div>
+
+<p>您可以添加属性,以更改上面的默认行为。例如,如果需要使用同一模型的多个视图,则可以指定另一个模板文件,或者如果<code>book_list</code>对于特定模板用例不直观,则可能需要使用不同的模板变量名称。可能最有用的变更,是更改/过滤返回的结果子集 - 因此,您可能会列出其他用户阅读的前5本书,而不是列出所有书本。</p>
+
+<pre class="brush: python">class BookListView(generic.ListView):
+ model = Book
+ context_object_name = 'my_book_list' # your own name for the list as a template variable
+ queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
+ template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location</pre>
+
+<h4 id="覆盖基于类别的视图中的方法">覆盖基于类别的视图中的方法</h4>
+
+<p>虽然我们不需要在这里执行此操作,但您也可以覆盖某些类别方法。</p>
+
+<p>例如,我们可以覆盖<code>get_queryset()</code>方法,来更改返回的记录列表。这比仅仅设置<code>queryset</code>属性更灵活,就像我们在前面的代码片段中所做的那样(尽管在这种情况下没有真正的好处):</p>
+
+<pre class="brush: python">class BookListView(generic.ListView):
+ model = Book
+
+    def get_queryset(self):
+        return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
+</pre>
+
+<p>我们还可以覆盖<code>get_context_data()</code> ,以将其他上下文变量传递给模板(例如,默认情况下传递书本列表)。下面的片段,显示了如何将一个名为“<code>some_data</code>”的变量添加到上下文中(然后它将作为一个模板变量,而被提供)。</p>
+
+<pre class="brush: python">class BookListView(generic.ListView):
+ model = Book
+
+    def get_context_data(self, **kwargs):
+        # Call the base implementation first to get the context
+        context = super(BookListView, self).get_context_data(**kwargs)
+        # Create any data and add it to the context
+        context['some_data'] = 'This is just some data'
+        return context</pre>
+
+<p>这样做时,遵循上面使用的模式非常重要:</p>
+
+<ul>
+ <li>首先从我们的超类别中,获取现有的上下文。</li>
+ <li>然后添加新的上下文信息。</li>
+ <li>然后返回新的(更新的)上下文。</li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>: 查看<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">内置的基于类的通用视图</a>(Django文档),了解更多可以执行的操作示例。</p>
+</div>
+
+<h3 id="创建列表视图模板">创建列表视图模板</h3>
+
+<p>创建 HTML 文件 <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong>,并复制到下面的文本中。如上所述,这是基于类的通用列表视图,所期望的默认模板文件(对于名为<code>catalog</code>的应用程序中,名为<code>Book</code>的模型)。</p>
+
+<p>通用视图的模板就像任何其他模板一样(当然,传递给模板的上下文/信息可能不同)。与我们的索引模板一样,我们在第一行扩展基本模板,然后替换名为<code>content</code>的区块。</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+    &lt;h1&gt;Book List&lt;/h1&gt;
+
+    <strong>{% if book_list %}</strong>
+    &lt;ul&gt;
+
+      {% for book in book_list %}
+      &lt;li&gt;
+        &lt;a href="\{{ book.get_absolute_url }}"&gt;\{{ book.title }}&lt;/a&gt; (\{{book.author}})
+      &lt;/li&gt;
+      {% endfor %}
+
+    &lt;/ul&gt;
+    <strong>{% else %}</strong>
+      &lt;p&gt;There are no books in the library.&lt;/p&gt;
+    <strong>{% endif %} </strong>
+{% endblock %}</pre>
+
+<p>视图默认将上下文(书本列表)作为 <code>object_list </code>和 <code>book_list</code> 的别名传递;任何一个都会奏效。</p>
+
+<h4 id="条件执行">条件执行</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>子句显示文本,说明没有要列出的书本。如果 <code>book_list</code>不为空,那么我们遍历书本列表。</p>
+
+<pre class="brush: html"><strong>{% if book_list %}</strong>
+ &lt;!-- code here to list the books --&gt;
+<strong>{% else %}</strong>
+ &lt;p&gt;There are no books in the library.&lt;/p&gt;
+<strong>{% endif %}</strong>
+</pre>
+
+<p>上述条件仅检查一种情况,但您可以使用 <code>elif </code>模板标记(例如<code>{% elif var2 %}</code> )测试其他条件。有关条件运算符的更多信息,请参阅:<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>,以及<a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">内置模板标记和过滤器</a>(Django Docs)中的 <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifchanged">ifchanged</a> 。</p>
+
+<h4 id="For_循环回圈">For 循环/回圈</h4>
+
+<p>模板使用<a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for">for</a> 和 <code>endfor</code>模板标签,以循环遍历书本列表,如下所示。每次迭代都会使用当前列表项的信息,填充书本模板变量<code>book</code>。</p>
+
+<pre class="brush: html">{% for <strong>book</strong> in book_list %}
+ &lt;li&gt; &lt;!-- code here get information from each <strong>book</strong> item --&gt; &lt;/li&gt;
+{% endfor %}
+</pre>
+
+<p>虽然这里没有使用,但在循环中,Django 还会创建其他可用于跟踪迭代的变量。例如,您可以测试<code>forloop.last</code> 变量,以运行最后一次循环当中的条件处理代码。</p>
+
+<h4 id="访问变量">访问变量</h4>
+
+<p>循环内的代码,为每本书创建一个列表项,显示作者和标题(作为尚未创建的详细视图的链接)。</p>
+
+<pre class="brush: html">&lt;a href="\{{ book.get_absolute_url }}"&gt;\{{ book.title }}&lt;/a&gt; (\{{book.author}})
+</pre>
+
+<p>我们使用“点符号”(例如 <code>book.title</code> 和 <code>book.author</code>)访问相关书本记录的字段,其中书本项目<code>book</code>后面的文本是字段名称(如同在模型中定义的)。</p>
+
+<p>我们还可以在模板中,调用模型中的函数 - 在这里,我们调用<code>Book.get_absolute_url()</code>,来获取可用于显示关联详细记录的URL。这项工作提供的函数没有任何参数(没有办法传递参数!)</p>
+
+<div class="note">
+<p><strong>注意</strong>: 在模板中调用函数时,我们必须要小心“副作用”。在这里我们只需要显示一个URL,但是一个函数几乎可以做任何事情 - 我们不想仅仅通过渲染模板,而删除了我们的数据库(例如)!</p>
+</div>
+
+<h4 id="更新基本模板">更新基本模板</h4>
+
+<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>)并将 <strong>{% url 'books' %} </strong>插入所有书本 <strong>All books </strong>的 URL 链接,如下所示。这将启用所有页面中的链接(由于我们已经创建了 “books” 的 url 映射器,我们可以成功地将其设置到位)。</p>
+
+<pre class="brush: python">&lt;li&gt;&lt;a href="{% url 'index' %}"&gt;Home&lt;/a&gt;&lt;/li&gt;
+<strong>&lt;li&gt;&lt;a href="{% url 'books' %}"&gt;All books&lt;/a&gt;&lt;/li&gt;</strong>
+&lt;li&gt;&lt;a href=""&gt;All authors&lt;/a&gt;&lt;/li&gt;</pre>
+
+<h3 id="它看起来是什么样子?">它看起来是什么样子?</h3>
+
+<p>您将无法构建书本清单,因为我们仍然缺少依赖项 - 书本详细信息页面的URL地图,这是创建单个书本的超链接所必需的。我们将在下一节之后,说明列表和详细视图的部分。</p>
+
+<h2 id="书本详细信息页面">书本详细信息页面</h2>
+
+<p>书本详细信息页面,将显示有关特定书本的信息,使用 URL <code>catalog/book/<em>&lt;id&gt;</em></code>(其中 <code><em>&lt;id&gt; </em></code>是书本的主键)进行访问。除了<code>Book</code>模型中的字段(作者,摘要,ISBN,语言和种类)之外,我们还将列出可用副本(<code>BookInstances</code>)的详细信息,包括状态,预期返回日期,印记和 id。这将使我们的读者,不仅可以了解该书,还可以确认是否/何时可用。</p>
+
+<h3 id="URL_映射_2">URL 映射</h3>
+
+<p>打开 <strong>/catalog/urls.py</strong> ,并添加下面粗体显示的 <strong>“book-detail”</strong> URL 映射器。这个<code> path()</code> 函数定义了一个模式,关联到基于通用类的详细信息视图和名称。</p>
+
+<pre class="brush: python">urlpatterns = [
+ path('', views.index, name='index'),
+    path('books/', views.BookListView.as_view(), name='books'),
+<strong>  path('book/&lt;int:pk&gt;', views.BookDetailView.as_view(), name='book-detail'),</strong>
+]</pre>
+
+<p>对于书本详细信息路径,URL 模式使用特殊语法,来捕获我们想要查看的书本的特定 id。语法非常简单:尖括号定义要捕获的URL部分,包含视图可用于访问捕获数据的变量的名称。例如,&lt;<strong>something</strong>&gt; 将捕获标记的模式,并将值作为变量 “something” ,传递给视图。您可以选择在变量名称前,加上一个定义数据类型的<a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters">转换器规范</a>(int,str,slug,uuid,path)。</p>
+
+<p>在这里,我们使用 <code>'&lt;int:pk&gt;' </code>来捕获 book id,它必须是一个整数,并将其作为名为 <code>pk </code>的参数(主键的缩写)传递给视图。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 如前所述,我们匹配的URL实际上是 <code>catalog/book/&lt;digits&gt;</code>(因为我们在应用程序 <strong>catalog </strong>中,假定使用<code>/catalog/</code>)。</p>
+</div>
+
+<div class="warning">
+<p><strong>要点</strong>: 基于类的通用详细信息视图,需要传递一个名为 <strong>pk </strong>的参数。如果您正在编写自己的函数视图,则可以使用您喜欢的任何参数名称,或者,确实也可以,在未命名的参数中传递信息。</p>
+</div>
+
+<h4 id="高级路径匹配正则表达式入门">高级路径匹配/正则表达式入门</h4>
+
+<div class="note">
+<p><strong>注意</strong>: 完成教程并不需要此部分说明!我们提供它,是因为了解此可选的部分,未来可能对您使用 Django 有帮助。</p>
+</div>
+
+<p><code>path()</code>提供的模式匹配非常简单,对于您只想捕获任何字符串或整数的(非常常见的)情况非常有用。如果需要更精细的过滤(例如,仅过滤具有一定数量字符的字符串),则可以使用 <a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path">re_path()</a> 方法。</p>
+
+<p>此方法与</p>
+
+<p><code>path()</code>的使用一样,除了它允许您使用<a href="https://docs.python.org/3/library/re.html">正则表达式</a>,以指定模式。例如,上面的路径可以编写为如下所示:</p>
+
+<pre class="brush: python"><strong>re_path(r'^book/(?P&lt;pk&gt;\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong>
+</pre>
+
+<p>正则表达式是一种非常强大的模式映射工具。坦率地说,对于初学者来说,他们是非常不直观和可怕的。下面是一个非常短的入门!</p>
+
+<p>首先要知道的是,正则表达式通常应该使用原始字符串文字语法声明(即它们如图所示:<strong>r'&lt;你的正则表达式文本放在这里&gt;'</strong>)。</p>
+
+<p>声明模式匹配需要知道的语法,主要部分是:</p>
+
+<table class="standard-table">
+ <thead>
+ <tr>
+ <th scope="col">符号</th>
+ <th scope="col">含义</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>^</td>
+ <td>匹配文本的开头</td>
+ </tr>
+ <tr>
+ <td>$</td>
+ <td>匹配文本的结尾</td>
+ </tr>
+ <tr>
+ <td>\d</td>
+ <td>匹配一个位数的数字(0,1,2,... 9)</td>
+ </tr>
+ <tr>
+ <td>\w</td>
+ <td>
+ <p>匹配单词字符,例如字母,数字或下划线字符(_)中的任何大写或小写字符</p>
+ </td>
+ </tr>
+ <tr>
+ <td>+</td>
+ <td>匹配前面一个或多个字符。例如,要匹配一个或多个位数的数字,您将使用<code>\d+</code>。要匹配一个或多个“ a” 字符,您可以使用 <code>a+</code></td>
+ </tr>
+ <tr>
+ <td>*</td>
+ <td>匹配前面字符的零个或多个。例如,要匹配没有内容或单词,您可以使用<code>\w*</code></td>
+ </tr>
+ <tr>
+ <td>( )</td>
+ <td>捕获括号内部模式的一部分。任何捕获的值,都将作为未命名参数,传递给视图(如果捕获了多个模式,则将按照声明捕获的顺序,提供相关参数)。</td>
+ </tr>
+ <tr>
+ <td>(?P&lt;<em>name</em>&gt;...)</td>
+ <td>捕获模式(由...表示)作为命名变量(在本例中为“name”)。捕获的值,将传递给具有指定名称的视图。因此,您的视图,必须声明具有相同名称的参数!</td>
+ </tr>
+ <tr>
+ <td>[  ]</td>
+ <td>匹配集合中的一个字符。例如,[abc] 将匹配 'a' 或 'b' 或 'c'。 [-\w] 将匹配 ' - ' 字符,或任何单词字符。</td>
+ </tr>
+ </tbody>
+</table>
+
+<p>大多数其他字符可以按字面意思理解!</p>
+
+<p>让我们考虑一些模式的真实例子:</p>
+
+<table class="standard-table">
+ <thead>
+ <tr>
+ <th scope="col">模式</th>
+ <th scope="col">描述</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><strong>r'^book/(?P&lt;pk&gt;\d+)$'</strong></td>
+ <td>
+ <p>这是我们的 url 映射器中使用的 RE。它匹配一个字符串,该字符串在行(<strong>^book/</strong>)的开头具有<code>book/</code>,然后有一个或多个数字(<code>\d+</code>),然后结束(在行标记结束之前,没有非数字字符)。</p>
+
+ <p>它还捕获所有数字(<strong>?P&lt;pk&gt;\d+</strong>),并将它们传递给名为 'pk' 的参数中的视图。<strong>捕获的值始终作为字符串传递</strong>!</p>
+
+ <p>例如,这将匹配 <code>book/1234</code>,并向视图发送变量 <code>pk='1234'</code>。</p>
+ </td>
+ </tr>
+ <tr>
+ <td><strong>r'^book/(\d+)$'</strong></td>
+ <td>这与前面的例子匹配相同的URL。捕获的信息,将作为未命名的参数,发送到视图。</td>
+ </tr>
+ <tr>
+ <td><strong>r'^book/(?P&lt;stub&gt;[-\w]+)$'</strong></td>
+ <td>
+ <p>这匹配一个字符串,该字符串在行(<strong>^book/</strong>)的开头具有<code>book/</code>,然后有一个或多个字符,可以是 ' - ' 或单词字符((<strong>[-\w]+</strong>),然后结束。它还捕获这组字符,并将它们传递给名为 “stub” 的参数中的视图。</p>
+
+ <p>这是 “stub” 的一种相当典型的模式。存根stub 是用于数据的、 URL 友好的、基于单词的主键。如果您希望本书网址提供更多信息,则可以使用 stub。例如 <code>/catalog/book/the-secret-garden</code> ,而不是<code>/catalog/book/33</code>。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<p>您可以在一个匹配中捕获多个模式,从而在 URL 中,编码许多不同的信息。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 作为一项挑战,请考虑如何对网址进行编码,以列出特定年份,月份,日期的所有图书,以及可用于匹配它的规则表达式 RE。</p>
+</div>
+
+<h4 id="在_URL_地图中传递其他选项">在 URL 地图中传递其他选项</h4>
+
+<p>我们在这里没有使用、但您可能觉得有价值的一个功能是,您可以向视图声明并传递<a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#views-extra-options">其他选项</a>。这些选项被声明为一个字典,您将其作为第三个未命名参数,传递给  <code>path()</code>函数。</p>
+
+<p>如果要对多个资源,使用相同的视图,并在每种情况下,传递数据以配置其行为,则此方法非常有用(下面我们在每种情况下提供不同的模板)。</p>
+
+<pre class="brush: python">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>注意:</strong> 额外选项和命名捕获的模式,二者都作为命名参数传递给视图。如果对捕获的模式和额外选项使用<strong>相同的名称</strong>,则仅将捕获的模式值发送到视图(将删除附加选项中指定的值)。</p>
+</div>
+
+<h3 id="视图_(基于类别)_2">视图 (基于类别)</h3>
+
+<p>打开 <strong>catalog / views.py</strong>,并将以下代码复制到文件的底部:</p>
+
+<pre class="brush: python">class BookDetailView(generic.DetailView):
+    model = Book</pre>
+
+<p>就是这样!您现在需要做的就是创建一个名为 <strong>/locallibrary/catalog/templates/catalog/book_detail.html </strong>的模板,该视图将向此模板,传递 URL 映射器提取的特定 <code>Book</code> 记录的数据库信息。在模板中,您可以使用名为  <code>object</code> 或 <code>book</code>的模板变量(即通常为 “<code><em>the_model_name</em></code>”),以访问书本列表。</p>
+
+<p>如果需要,可以更改使用的模板,以及用于在模板中,引用该书本的上下文对象的名称。您还可以覆盖方法,例如,向上下文添加其他信息。</p>
+
+<h4 id="如果记录不存在会怎样?">如果记录不存在会怎样?</h4>
+
+<p>如果请求的记录不存在,那么基于类的通用详细信息视图,将自动为您引发 <code>Http404 </code>异常 - 在生产环境中,这将自动显示适当的 “未找到资源” 页面,您可以根据需要自定义该页面。</p>
+
+<p>为了让您了解其工作原理,下面的代码片段,演示了如何在<strong>不使用</strong>基于类的详细信息视图的情况下,将基于类的视图实现为函数。</p>
+
+<pre class="brush: python">def book_detail_view(request,pk):
+ try:
+ book_id=Book.objects.get(pk=pk)
+ except Book.DoesNotExist:
+ raise Http404("Book does not exist")
+
+  #book_id=get_object_or_404(Book, pk=pk)
+
+ return render(
+ request,
+ 'catalog/book_detail.html',
+ context={'book':book_id,}
+ )
+</pre>
+
+<p>视图首先尝试从模型中,获取特定的书本记录。如果失败,则视图应引发 <code>Http404</code>异常,以指示该书本 “未找到”。然后,最后一步是使用模板名称,和上下文参数<code>context</code>中的书本数据(作为字典)调用<code>render()</code>。</p>
+
+<div class="note">
+<p><strong>注意</strong>: <code>get_object_or_404()</code>(如上所示)是一个方便的快捷方式,用于在未找到记录时,引发 <code>Http404 </code>异常。</p>
+</div>
+
+<h3 id="创建详细信息视图模板">创建详细信息视图模板</h3>
+
+<p>创建 HTML 文件 <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>,并为其提供以下内容。如上所述,这是基于类的通用详细信息视图,所期望的默认模板文件名(对于名为 <code>catalog </code>的应用程序中名为 <code>Book </code>的模型)。</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+ &lt;h1&gt;Title: \{{ book.title }}&lt;/h1&gt;
+
+ &lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=""&gt;\{{ book.author }}&lt;/a&gt;&lt;/p&gt; &lt;!-- author detail link not yet defined --&gt;
+ &lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; \{{ book.summary }}&lt;/p&gt;
+ &lt;p&gt;&lt;strong&gt;ISBN:&lt;/strong&gt; \{{ book.isbn }}&lt;/p&gt;
+ &lt;p&gt;&lt;strong&gt;Language:&lt;/strong&gt; \{{ book.language }}&lt;/p&gt;
+ &lt;p&gt;&lt;strong&gt;Genre:&lt;/strong&gt; {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}&lt;/p&gt;
+
+ &lt;div style="margin-left:20px;margin-top:20px"&gt;
+ &lt;h4&gt;Copies&lt;/h4&gt;
+
+ {% for copy in book.bookinstance_set.all %}
+ &lt;hr&gt;
+ &lt;p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"&gt;\{{ copy.get_status_display }}&lt;/p&gt;
+ {% if copy.status != 'a' %}&lt;p&gt;&lt;strong&gt;Due to be returned:&lt;/strong&gt; \{{copy.due_back}}&lt;/p&gt;{% endif %}
+ &lt;p&gt;&lt;strong&gt;Imprint:&lt;/strong&gt; \{{copy.imprint}}&lt;/p&gt;
+ &lt;p class="text-muted"&gt;&lt;strong&gt;Id:&lt;/strong&gt; \{{copy.id}}&lt;/p&gt;
+ {% endfor %}
+ &lt;/div&gt;
+{% endblock %}</pre>
+
+<ul>
+</ul>
+
+<div class="note">
+<p><strong>注意: </strong>上面模板中的作者链接,有一个空 URL,因为我们尚未创建作者详细信息页面。一旦创建了,您应该像这样更新URL:</p>
+
+<pre>&lt;a href="<strong>{% url 'author-detail' book.author.pk %}</strong>"&gt;\{{ book.author }}&lt;/a&gt;
+</pre>
+</div>
+
+<p>虽然有点大,但此模板中的几乎所有内容,都已在前面描述过:</p>
+
+<ul>
+ <li>我们扩展基本模板,并覆盖 “内容”区块 content。</li>
+ <li>我们使用条件处理,来确定是否显示特定内容。</li>
+ <li>我们使用 <code>for </code>循环遍历对象列表。</li>
+ <li>我们使用 "点表示法" 访问上下文字段(因为我们使用了详细的通用视图,上下文被命名为<code>book</code>;我们也可以使用 “<code>object</code>”)。</li>
+</ul>
+
+<p>我们以前没见过的一件有趣的事情是函数<code>book.bookinstance_set.all()</code>。此方法由 Django “自动” 构造,以便返回与特定<code> Book</code> 相关联的 <code>BookInstance</code>记录集合。</p>
+
+<pre class="brush: python">{% for copy in book.bookinstance_set.all %}
+&lt;!-- code to iterate across each copy/instance of a book --&gt;
+{% endfor %}</pre>
+
+<p>需要此方法,是因为您仅在关系的 “一” 侧声明 <code>ForeignKey</code>(一对多)字段。由于您没有做任何事情,来声明其他(“多”)模型中的关系,因此它没有任何字段,来获取相关记录集。为了解决这个问题,Django构造了一个适当命名的 “反向查找” 函数,您可以使用它。函数的名称,是通过对声明<code> ForeignKey</code> 的模型名称,转化为小写来构造的,然后是<code>_set</code>(即,在 <code>Book </code>中创建的函数是 <code>bookinstance_set()</code>)。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 这里我们使用<code>all()</code>来获取所有记录(默认值)。虽然您可以使用<code>filter()</code>方法获取代码中的记录子集,但您无法直接在模板中执行此操作,因为您无法指定函数的参数。</p>
+
+<p>还要注意,如果您没有定义顺序(在基于类的视图或模型上),您还会看到开发服务器中的错误,如下所示:</p>
+
+<pre>[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: &lt;QuerySet [&lt;Author: Ortiz, David&gt;, &lt;Author: H. McRaven, William&gt;, &lt;Author: Leigh, Melinda&gt;]&gt;
+ allow_empty_first_page=allow_empty_first_page, **kwargs)
+</pre>
+
+<p>发生这种情况,是因为 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">paginator object </a>对象希望在下划线数据库上看到一些 ORDER BY。没有它,它无法确定,返回的注册表实际上是否为正确顺序!</p>
+
+<p>本教程还没有说明到 <strong>Pagination</strong>(还没,但很快),但由于你不能使用<code>sort_by()</code> 并传递一个参数(与上面描述的<code>filter()</code> 相同),你将不得不在下面三个选择当中,进行挑选:   </p>
+
+<ol>
+ <li>在模型的<code>class Meta</code>声明中,添加排序<code>ordering</code>。</li>
+ <li>Add a <code>queryset</code> attribute in your custom class-based view, specifying a <code>order_by()</code>.在自定义基于类的视图中添加queryset属性,指定order_by()。</li>
+ <li>Adding a <code>get_queryset</code> method to your custom class-based view and also specify the <code>order_by()</code>.将get_queryset方法添加到基于类的自定义视图中,并指定order_by()。</li>
+</ol>
+
+<p>如果您决定使用<code>class Meta </code>作为作者模型<code>Author</code>(可能不像定制基于类的视图那样灵活,但很容易),您最终会得到这样的结果:</p>
+
+<pre>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)
+
+<strong> class Meta:
+ ordering = ['last_name']</strong></pre>
+
+<p>当然,该字段不需要是<code>last_name</code>:它可以是任何其他字段。</p>
+
+<p>最后,但并非最不重要的是,您应该按照实际上在数据库上具有索引(唯一或非唯一)的属性/栏位进行排序,以避免性能问题。当然,如果这么少量的书本(和用户!),这里就没有必要(我们可能会让自己提前做太多事情),但是对于未来的项目来说,这是需要考虑的事情。</p>
+</div>
+
+<h2 id="它看起来是什么样子?_2">它看起来是什么样子?</h2>
+
+<p>此时,我们应该创建了显示书本列表,和书本详细信息页面所需的所有内容。运行服务器(<code>python3 manage.py runserver</code>),并打开浏览器到 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>。</p>
+
+<div class="warning">
+<p><strong>警告:</strong> <span id="result_box" lang="zh-CN"><span>请还不要点击任何作者、或作者详细信息链接 - 您将在挑战练习中,创建这些链接!</span></span></p>
+</div>
+
+<p>单击所有书籍链接<strong> All books</strong> ,以显示书籍列表。</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>然后点击指向您的某本图书的链接。如果一切设置正确,您应该看到类似下面的屏幕截图。</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="分页">分页</h2>
+
+<p>如果您刚刚获得了一些记录,我们的图书清单页面看起来会很好。但是,当您进入数十或数百条记录的页面时,页面将逐渐花费更长时间加载(并且有太多内容无法合理浏览)。此问题的解决方案,是为列表视图添加分页,减少每页上显示的项目数。</p>
+
+<p>Django 在分页方面,拥有出色的内置支持。更好的是,它内置于基于类的通用列表视图中,因此您无需执行太多操作即可启用它!</p>
+
+<h3 id="视图">视图</h3>
+
+<p>打开 <strong>catalog/views.py</strong>,然后添加下面粗体显示的<code>paginate_by </code>行。</p>
+
+<pre class="brush: python">class BookListView(generic.ListView):
+ model = Book
+ <strong>paginate_by = 10</strong></pre>
+
+<p>通过添加这行,只要您有超过10条记录,视图就会开始对它发送到模板的数据,进行分页。使用 GET 参数访问不同的页面 - 要访问第2页,您将使用URL:<code>/catalog/books/<strong>?page=2</strong></code>。</p>
+
+<h3 id="模板">模板</h3>
+
+<p>现在数据已经分页,我们需要添加对模板的支持,以滚动结果集合。因为我们可能希望在所有列表视图中,都执行此操作,所以我们将以可添加到基本模板的方式,执行此操作。</p>
+
+<p>打开 <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>,并复制贴士以下内容区块下面的分页区块(以粗体突出显示)。代码首先检查当前页面上,是否启用了分页。如果是,则它会根据需要,添加下一个和上一个链接(以及当前页码)。</p>
+
+<pre class="brush: python">{% block content %}{% endblock %}
+
+<strong>{% block pagination %}
+ {% if is_paginated %}
+ &lt;div class="pagination"&gt;
+ &lt;span class="page-links"&gt;
+ {% if page_obj.has_previous %}
+ &lt;a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}"&gt;previous&lt;/a&gt;
+ {% endif %}
+ &lt;span class="page-current"&gt;
+ Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.
+ &lt;/span&gt;
+ {% if page_obj.has_next %}
+ &lt;a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}"&gt;next&lt;/a&gt;
+ {% endif %}
+ &lt;/span&gt;
+ &lt;/div&gt;
+ {% endif %}
+{% endblock %} </strong></pre>
+
+<p><code>page_obj </code>是一个 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">Paginator</a> 对象,如果在当前页面上使用分页,它将存在。 它允许您获取有关当前页面,之前页面,有多少页面等的所有信息。</p>
+
+<p>我们使用 <code>\{{ request.path }}</code>,来获取用于创建分页链接的当前页面URL。 这很有用,因为它独立于我们正在分页的对象。</p>
+
+<p>就是这样!</p>
+
+<h3 id="它看起来是什么样子的?">它看起来是什么样子的?</h3>
+
+<p>下面的屏幕截图,显示了分页的样子 - 如果您没有在数据库中输入超过10个标题,那么您可以通过降低 <strong>catalog/views.py </strong>文件中 <code>paginate_by </code>行指定的数量,来更轻松地测试它。 为了得到以下结果,我们将其更改为 <code>paginate_by = 2</code>。</p>
+
+<p>分页链接显示在底部,根据您所在的页面,显示下一个/上一个链接。</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="挑战自己">挑战自己</h2>
+
+<p>本文中的挑战,是创建完成项目所需的作者详细信息视图,和列表视图。这些应在以下URL中提供:</p>
+
+<ul>
+ <li><code>catalog/authors/</code> — 所有作者的名单。</li>
+ <li><code>catalog/author/<em>&lt;id&gt;</em></code><em> </em>— 特定作者的详细视图,并具有名为<em><code>&lt;id&gt;</code></em>的主键字段</li>
+</ul>
+
+<p>URL 映射器和视图所需的代码,应与我们上面创建的<code>Book</code>列表和详细视图几乎完全相同。模板将有所不同,但会分享类似的行为。</p>
+
+<p> </p>
+
+<div class="note">
+<p><strong>注意</strong>:</p>
+
+<ul>
+ <li>为作者列表页面,创建URL映射器之后,还需要更新基本模板中的所有作者 <strong>All authors </strong>链接。按照我们更新“所有图书”<strong>All books</strong> 链接时,所做的相同过程。</li>
+ <li>为作者详细信息页面,创建URL映射器之后,还应更新书本详细信息视图模板(<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>),以便作者链接,指向新的作者详细信息页面(而不是一个空的URL)。该行将更改为添加下面以粗体显示的模板标记。
+ <pre class="brush: html">&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="<strong>{% url 'author-detail' book.author.pk %}</strong>"&gt;\{{ book.author }}&lt;/a&gt;&lt;/p&gt;
+</pre>
+ </li>
+</ul>
+</div>
+
+<p>完成后,您的页面应该类似于下面的屏幕截图。</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="总结">总结</h2>
+
+<p>恭喜,我们的图书馆的基本功能现在完成了!</p>
+
+<p>本文中,我们学到如何使用基于类别的通用列表视图与详细视图,并使用它们创建页面,以查看我们的书本和作者。在此过程中,我们了解了与正则表达式匹配的模式,以及如何将数据从URL传递到视图。我们还学习了一些使用模板的技巧。最后,我们已经展示了如何对列表视图进行分页,这样即使我们有很多记录,我们也可以管理列表。</p>
+
+<p>在我们的下一篇文章,我们将扩充此图书馆,以支持使用者帐户,并从而演示使用者授权、许可、授权、会话, 以及表单。</p>
+
+<h2 id="参见">参见</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>
+
+<p> </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></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: 会话框架</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 到生产环境</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>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/django/index.html b/files/zh-cn/learn/server-side/django/index.html
new file mode 100644
index 0000000000..cb92e9cc05
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/index.html
@@ -0,0 +1,60 @@
+---
+title: Django Web 框架(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-CN/docs/Learn/Server-side/First_steps">服务端网站编程的第一步</a>模块。</p>
+
+<p>最好能有基本的编程概念并了解 <a href="/zh-CN/docs/Glossary/Python">Python</a> 语言,但其并不是理解本教程的核心概念的必然条件。</p>
+
+<div class="note">
+<p><span style="font-size: 14px;"><strong>注意:</strong></span>对于初学者来说,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="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Introduction">Django简介</a></dt>
+ <dd>在第一篇关于Django的文章里,我们会回答"什么是Django?",并概述这个网络框架的特殊之处.我们会列出主要的功能,包括一些高级的功能特性,这些高级特性我们在这部分教程里没有时间详细说明.在你设置好Django应用并开始把玩它之前,我们会展示Django应用的一些主要模块,让你明白Django应用能做什么.</dd>
+ <dt><a href="/zh-CN/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-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程1:本地图书馆网站</a></dt>
+ <dd>我们实用教程系列的第一篇文章会解释你将学习到什么,并提供"本地图书馆"网站这个例子的概述.我们会在接下来的文章里完成并不断的进化这个网站.</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django教程2:创建网站的框架</a></dt>
+ <dd>这篇文章会教你怎样创建一个网站的"框架".以这个网站为基础,你可以填充网站特定的settings,urls, models,views和templates</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Models">Django教程3:使用模型</a></dt>
+ <dd>这篇文章会为 <em>本地图书馆 </em>网站定义数据模板—数据模板是我们为应用存储的数据结构.并且允许Django在数据库中存储数据(以后可以修改).文章解释了什么是数据模板,怎样声明它和一些主要的数据种类.文章还简要的介绍了一些你可以获得数据模板的方法.</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django 管理站点</a></dt>
+ <dd>现在我们已经为本地图书馆网站创建了模型,我们将使用 Django 管理站点 添加一些 ‘真实的’ 的图书数据。首先,我们将向你介绍如何使用管理站点注册模型,然后我们介绍如何登录和创建一些数据。最后我们展示一些进一步改进管理站点的演示方法。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: 创建我们的主页</a></dt>
+ <dd>我们现在可以添加代码来展示我们的第一次完整页面—本地图书馆主页,来显示我们对每个模型类型有多少条记录,并提供我们其他页面的侧边栏导航链接。一路上,我们将获得编写基本URL地图和视图,从数据库获取记录以及使用模版的实践经验。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: 通用列表和详细视图</a></dt>
+ <dd>本教程扩展了我们的本地图书馆网站,添加书籍和作者和详细页面。在这里,我们将了解基于类的通用视图,并展示如何减少常用代码用例的代码量。我们还将更详细地深入理解URL处理,显示如何执行基本模式匹配。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: 会话框架</a></dt>
+ <dd>本教程扩展本地图书馆网站,向主页添加了一个基于会话的访问计数器。这是个比较简单的例子,但它显示如何使用会话框架为你自己的站点中的匿名用户提供一致的行为。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: 用户身份验证和权限</a></dt>
+ <dd>本教程,我们将向你展示如何允许用户使用自己的账户登录到你的网站,以及如何根据他们是否登录及其权限来控制他们可以做什么和看到什么。作为此次演示的一部分,我们将扩展本地图书馆网站,添加登录和注销页面以及用户和工作人员特定页面,以查看已借用的书籍。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: 使用表单</a></dt>
+ <dd>本教程,我们将向你展示如何使用Django 中的<a href="https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Forms">HTML表单</a>,特别是编写创建表单,更新和删除模型实例的最简单方法。作为此次演示的一部分,我们将扩展本地图书馆网站,以便图书馆员可以使用我们自己的表单(而不是使用管理应用程序) 来更新书籍,创建,更新和删除作者。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10:测试Django Web 应用程序</a></dt>
+ <dd>随着网站的的发展,手工测试越来越难测试—不仅要测试更多,而且随着组件之间的相互作用变得越来越复杂,一个领域的一个小的变化可能需要许多额外的测试来验证其对其他领域的影响。减轻这些问题的一种方法是编写自动化测试,每次更改时都可以轻松可靠地运行。本教程将介绍如何使用 Django 的测试框架对你的网站进行 <em><strong>单元测试</strong></em>自动化。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: 将Django部署到生产</a></dt>
+ <dd>现在,你已创建(并测试)一个酷的 本地图书馆网站,你将要把它安装在公共Web服务器上,以便图书馆员工和成员可以通过Internet访问。本文概述了如何找到主机来部署你的网站,以及你需要做什么才能使你的网站准备好进行生产。</dd>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/web_application_security">Django web 应用程序安全</a></dt>
+ <dd>保护用户数据是任何网站设计的重要组成部分,我们以前解释了Web安全文章中一些更常见的安全威胁—本文提供了Django内置如何保护处理这种危险的实际演示。</dd>
+</dl>
+
+<h2 id="评估">评估</h2>
+
+<p>以下评估将测试你对如何使用Django创建网站的理解,如上述指南中所述。</p>
+
+<dl>
+ <dt><a href="/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></dt>
+ <dd>在这个评估中,你将使用你从本单元中学到的一些知识来创建自己的博客。</dd>
+</dl>
diff --git a/files/zh-cn/learn/server-side/django/introduction/index.html b/files/zh-cn/learn/server-side/django/introduction/index.html
new file mode 100644
index 0000000000..4bb940e2f3
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/introduction/index.html
@@ -0,0 +1,268 @@
+---
+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="/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负责处理网站开发中麻烦的部分,因此你可以专注于编写应用程序,而无需重新开发。<br>
+ 它是免费和开源的,有活跃繁荣的社区,丰富的文档,以及很多免费和付费的解决方案。</p>
+
+<p>Django 可以使你的应用具有以下优点:</p>
+
+<dl>
+ <dt>完备性</dt>
+ <dd>Django遵循“功能完备”的理念,提供开发人员可能想要“开箱即用”的几乎所有功能。因为你需要的一切都是一个”产品“的一部分,它们都可以无缝结合在一起,遵循一致性设计原则,并且具有广泛和<a href="https://docs.djangoproject.com/en/1.10/">最新的文档</a>.</dd>
+ <dt>通用性</dt>
+ <dd>Django 可以(并已经)用于构建几乎任何类型的网站—从内容管理系统和维基,到社交网络和新闻网站。它可以与任何客户端框架一起工作,并且可以提供几乎任何格式(包括 HTML,Rss源,JSON,XML等)的内容。你正在阅读的网站就是基于Django。<br>
+ <br>
+ 在内部,尽管它为几乎所有可能需要的功能(例如几个流行的数据库,模版引擎等)提供了选择,但是如果需要,它也可以扩展到使用其他组件。</dd>
+ <dt>安全性</dt>
+ <dd>Django 帮助开发人员通过提供一个被设计为“做正确的事情”来自动保护网站的框架来避免许多常见的安全错误。例如,Django提供了一种安全的方式来管理用户账户和密码,避免了常见的错误,比如将session放在cookie中这种易受攻击的做法(取而代之的是cookies只包含一个密钥,实际数据存储在数据库中)或直接存储密码而不是密码哈希。<br>
+ <br>
+ 密码哈希是通过<em><a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">密码散列函数</a>发送密码而创建的固定长度值。 Django 能通过运行哈希函数来检查输入的密码-就是-将输出的哈希值与存储的哈希值进行比较是否正确。然而由于功能的“单向”性质,即时存储的哈希值受到威胁,攻击者也难以解决原始密码。(但其实有彩虹表-译者观点)</em><br>
+ <br>
+ 默认情况下,Django 可以防范许多漏洞,包括SQL注入,跨站点脚本,跨站点请求伪造和点击劫持 (请参阅 <a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">网站安全</a> 相关信息,如有兴趣).</dd>
+ <dt>可扩展</dt>
+ <dd>Django 使用基于组件的 “<a href="https://en.wikipedia.org/wiki/Shared_nothing_architecture">无共享</a>” 架构 (架构的每一部分独立于其他架构,因此可以根据需要进行替换或更改). 在不用部分之间有明确的分隔意味着它可以通过在任何级别添加硬件来扩展服务:缓存服务器,数据库服务器或应用程序服务器。一些最繁忙的网站已经成功地缩放了Django,以满足他们的需求(例如Instagram和Disqus,仅举两个例子,可自行添加)。</dd>
+ <dt>可维护性</dt>
+ <dd>Django 代码编写是遵照设计原则和模式,鼓励创建可维护和可重复使用的代码。特别是它使用了不要重复自己(DRY)原则,所以没有不必要的重复,减少了代码的数量。Django还将相关功能分组到可重用的“应用程序”中,并且在较低级别将相关代码分组或模块( <a href="/en-US/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">模型视图控制器 (MVC)</a> 模式).</dd>
+ <dt>灵活性</dt>
+ <dd>Django 是用Python编写的,它在许多平台上运行。这意味着你不受任务特定的服务器平台的限制,并且可以在许多种类的Linux,Windows和Mac OsX 上运行应用程序。此外,Django得到许多网络托管提供商的好评,他们经常提供特定的基础设施和托管Django网站的文档。</dd>
+</dl>
+
+<h2 id="它的出生">它的出生?</h2>
+
+<p>Django 最初由2003年到2005年间由负责创建和维护报纸网站的网络团队开发。在创建了许多网站后,团队开始考虑并重用许多常见的代码和设计模式。这个共同的代码演变一个通用的网络开发框架,2005年7月被开源“Django”项目。</p>
+
+<p>Django 不断发展壮大—从2008年9月的第一个里程碑版本(1.0)到最近发布的(1.11)-(2017)版本。每个版本都添加了新功能和错误修复,从支持新类型的数据库,模版引擎和缓存,到添加“通用”视图函数和类(这减少了开发人员必须编写的代码量)一些编程任务。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 查看Django网站上<span style="line-height: 1.5;"> <a href="https://docs.djangoproject.com/en/1.10/releases/">发行说明</a>,看看最近版本发生了什么变化,以及Django能做多少工作。</span></p>
+</div>
+
+<p>Django 现在是一个蓬勃发展的合作开源项目<span style="line-height: 1.5;">,拥有数千个用户和贡献着。虽然它仍然具有反映其起源的一些功能,但Django已经发展成为能够开发任何类型的网站的多功能框架。 </span></p>
+
+<h2 id="Django有多受欢迎">Django有多受欢迎?</h2>
+
+<p>服务器端框架的受欢迎程度没有任何可靠和明确的测量(尽管<a href="http://hotframeworks.com/">Hot Frameworks</a>网站 尝试使用诸如计算每个平台的GitHub项目数量和StackOverflow问题的机制来评估流行度)。一个更好的问题是Django是否“足够流行”,以避免不受欢迎的平台的问题。它是否继续发展?如果您需要帮助,可以帮您吗?如果您学习Django,有机会获得付费工作吗?</p>
+
+<p>基于使用Django的流行网站数量,为代码库贡献的人数以及提供免费和付费支持的人数,那么是的,Django是一个流行的框架!</p>
+
+<p>使用Django的流行网站包括:Disqus,Instagram,骑士基金会,麦克阿瑟基金会,Mozilla,国家地理,开放知识基金会,Pinterest和开放栈(来源:<a href="https://www.djangoproject.com/">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 和可能的 POST 数据或 GET 数据中的信息确定需要的内容。根据需要,可以从数据库读取或写入信息,或执行满足请求所需的其他任务。然后,该应用程序将返回对Web浏览器的响应,通常通过将检索到的数据插入 HTML模板中的占位符来动态创建用于浏览器显示的 HTML 页面。</p>
+
+<p>Django 网络应用程序通常将处理每个步骤的代码分组到单独的文件中:</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></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="/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(urlpatterns)定义了特定URL 模式 和相应视图函数之间的映射列表。如果接收到具有与指定模式匹配的URL(例如r'^$',下面)的HTTP请求,则将调用 相关联的视图功能(例如  views.index)并传递请求。</p>
+
+<pre>urlpatterns = [
+ <strong>url(r'^$', views.index),</strong>
+ url(r'^([0-9]+)/$', views.best),
+]
+</pre>
+
+<div class="note">
+<p><strong>注意</strong>: 一点点Python:</p>
+
+<ul>
+ <li>该 <code>urlpatterns</code> 对象的列表url() 功能。在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>.</li>
+ <li>该模式的奇怪的语法称为正则表达式。我们将在后面的文章中讨论这些内容!</li>
+ <li>第二个参数 url() 是当模式匹配时,将被调用的另一个函数。符号views.index 表示该函数被调用,index()并且可以在被调用的模块中找到views (即在一个名为views.py的文件中)。</li>
+</ul>
+</div>
+
+<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 href="https://docs.python.org/3/tutorial/modules.html">Python 模块</a> 是函数的“库”,存储在单独的文件中,我们可能想在我们的代码中使用它们。在这里我们只从django.http模块导入了HttpResponse对象,使我们可以在视图中使用它:<br>
+ from django.http import HttpResponse。<br>
+ 还有其他方法可以从模块导入一些或所有对象。</li>
+ <li>
+ <p>如上所示,使用<code>def</code>关键字声明函数<strong>,</strong>在函数名称后面的括号中列出命名参数;整行以冒号结尾。注意下一行是否都进行了<strong>缩进</strong>。缩进很重要,因为它指定代码行在该特定块内 (强制缩进是Python的一个关键特征,也是Python代码很容易阅读的一个原因)。</p>
+ </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>下面的代码片段为<strong>Team</strong>对象展示了一个非常简单的Django模型。本<strong>Team</strong>类是从Django的类派生<strong>models.Model</strong>。它将团队名称和团队级别定义为字符字段,并为每个记录指定了要存储的最大字符数。<strong>team_level</strong> 可以是几个值中的一个,因此,我们将其定义为一个选择字段,并在被展示的数据和被储存的数据之间建立映射,并设置一个默认值。</p>
+
+<p> </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><br>
+ 例如,我们有个 <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>团队。粗体显示如何使用模型查询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 <strong>render</strong>(request, '/best/index.html', context)
+</pre>
+
+<dl>
+</dl>
+
+<p>此功能使用 <strong>render</strong>() 功能创建 <strong>HttpResponse</strong> 发送回浏览器的功能。这个函数是一个快捷方式;它通过组合指定的HTML模版和一些数据来插入模版(在名为 “<strong>context</strong>” 的变量中提供)来创建一个<strong>HTML</strong>文件。在下一节中,我们将介绍如何在其中插入数据以创建<strong>HTML</strong>。</p>
+
+<h3 id="呈现数据_(HTML_模版)">呈现数据 (HTML 模版)</h3>
+
+<p>模版系统允许你指定输出文档的结构,使用<br>
+ 占位符<br>
+ {% if youngest_teams%}<br>
+ 来生成页面时填写的数据。模版通常用于创建HTMl,但也可以创建其他类型的文档。Django支持其原生模版系统和另一种流行的Python库(称为jinja2)开箱即用(如果需要,也可以支持其他系统)。</p>
+
+<p>代码片段显示render()了上一节中函数调用的HTML模版的外观。这个模版已经被写入这样的想法,即它将被访问一个列表变量,<br>
+ youngest_teams当它被渲染时</p>
+
+<pre class="brush: python">## filename: best/templates/best/index.html
+
+&lt;!DOCTYPE html&gt;
+&lt;html lang="en"&gt;
+&lt;body&gt;
+
+ {% if youngest_teams %}
+    &lt;ul&gt;
+    {% for team in youngest_teams %}
+        &lt;li&gt;\{\{ team.team_name \}\}&lt;/li&gt;
+    {% endfor %}
+    &lt;/ul&gt;
+{% else %}
+    &lt;p&gt;No teams are available.&lt;/p&gt;
+{% endif %}
+
+&lt;/body&gt;
+&lt;/html&gt;</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>
diff --git a/files/zh-cn/learn/server-side/django/models/index.html b/files/zh-cn/learn/server-side/django/models/index.html
new file mode 100644
index 0000000000..23975c8d7a
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/models/index.html
@@ -0,0 +1,449 @@
+---
+title: 'Django Tutorial Part 3: 使用模型'
+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>
+
+<div>这篇文章展示了如何为我们的LocalLibray(本地图书馆)网站定义models。它解释了一个模型是什么,它是怎么被声明的,和其中的一些主要域类型。</div>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">Prerequisites:</th>
+ <td><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a>.</td>
+ </tr>
+ <tr>
+ <th scope="row">Objective:</th>
+ <td>能够设计并创建你自己的数据模型,并为其合适地选择域。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概要">概要</h2>
+
+<p>Django网络应用通过作为模型被参照的Python对象访问并管理数据。模型定义了储存数据的结构,包括域类型和可能的最大值,默认值,可选择的列表,帮助理解文档的文本,表格内的标签文本,等等。模型的定义是独立于数据库的——你可以为你自己的项目设置选择一种。一旦你已经选择了你想用的数据库,你不需要直接谈论它——你只是写出你的模型结构和其他代码,然后Django会为你处理所有繁琐的和数据库打交道的工作。</p>
+
+<p>这个教程展示了如何定义并访问 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary website</a> 的模型。</p>
+
+<h2 id="设计LocalLibaray模型">设计LocalLibaray模型</h2>
+
+<p>在你继续开始为模型写代码前,用几分钟考虑一下我们需要储存什么数据和不同对象之间的关系是很有价值的。</p>
+
+<p>我们知道我们需要存储书籍(书名,简介,作者,写作语言,类别,ISBN编号)和我们可能有的可获取的副本数量(全局独立ID,可获取状态,等等)。我们可能需要存储更多的关于作者的信息而不仅仅是她们的名字。我们希望能够将信息根据书名,作者,写作语言和类别分类。</p>
+
+<p>当设计你的模型时,给予每个“对象”(一组关联信息)独立的模型似乎挺说得通的。当前情况下,最为明显的对象就是书籍和作者。</p>
+
+<p>你可能在想相比硬编码所有的选项到网站上,用模型来呈现选择列表(例如包括了许多选项的下拉菜单)——我们推荐这样,尤其是当选项未知或者可能改变时。显然,目前模型的候选者包括了书的流派(例如科幻小说,法国诗歌,等等)和语言(英语,法语,日语)。</p>
+
+<p>一旦我们决定了模型和域,我们需要考虑他们的关系。Django允许你定义一对一 (<code>OneToOneField</code>),一对多(<code>ForeignKey</code>)和多对多(<code>ManyToManyField</code>)的关系。(译者注:此处我们以关系型数据库为基准,如果采用NoSQL,如MangoDB则无法如此考虑)</p>
+
+<p>思考着以上内容,以下的UML关系表显示了我们在该例子里定义的模型。如上所述哦,我们已经为书籍(大概的细节),书籍实例(物理副本是否可获取状态)和作者创建了模型。我们也决定了为流派而创建的模型,所以变量值可以通过管理界面获取。此外,我们决定了不创建 <code>BookInstance:status</code>的模型——我们已经硬编码了这个值(<code>LOAN_STATUS</code>)因为我们不期望这些被改变。通过每个方框你可以看到模型名字,值域名和类型,还有方法和返回的类型。</p>
+
+<p>这个图表也显示了模型之间的关系, including their <em>multiplicities</em>. 这些在图表里每个模型边上的数字(最大和最小)显示了他们的关系。 例如,链接Book和Genre两个盒子的线表示它们是关联的。 靠近Book模型的数字显示一本书必须有一个或多个Genre(要多少有多少),然而另一端靠近Genre的数字显示了它可以有零或无数本相关的书籍。</p>
+
+<p><img alt="LocalLibrary Model UML - v3" src="https://mdn.mozillademos.org/files/14021/local_library_model_uml_v0_1.png" style="height: 660px; width: 937px;"></p>
+
+<div class="note">
+<p><strong>Note</strong>: 下一部分提供了基本的关于模型如何被定义和使用的解释。边阅读,边考虑以下我们是如何根据以上的图标构建数据库内的模型的。</p>
+</div>
+
+<h2 id="Model_primer">Model primer</h2>
+
+<p>This section provides a brief overview of how a model is defined and some of the more important fields and field arguments.</p>
+
+<h3 id="Model_definition">Model definition</h3>
+
+<p>Models are usually defined in an application's <strong>models.py</strong> file. They are implemented as subclasses of <code>django.db.models.Model</code>, and can include fields, methods and metadata. The code fragment below shows a "typical" model, named <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>In the below sections we'll explore each of the features inside the model in detail:</p>
+
+<h4 id="域">域</h4>
+
+<p>一个模型可以有任意数量的域,或任意的类型——每个用一行呈现我们想存储进数据库的数据。让我们看一下以下的例子吧o(≧v≦)o:</p>
+
+<pre class="brush: js notranslate">my_field_name = models.CharField(max_length=20, help_text="Enter field documentation")</pre>
+
+<p>上面的例子有一个单域,叫做my_field_name,类型是models.CharField——这意味着此域会包含着由字母组成的字符串们。域类型被特殊的class赋值,这确认了记录的类型是用来存入数据库的,以及当用户从HTML表格里提交值后,我们用来验证提交的值是否有效的条件。</p>
+
+<p><font><font><font><font>字段类型还可以获取参数,进一步指定字段如何存放或如何被使用。</font></font></font><font><font><font>在这里的情况下,我们给了字段两个参数:</font></font></font></font></p>
+
+<ul>
+ <li><code>max_length=20</code><font><font> <font><font>— 表示此字段中值的最大长度为20个字符的状态。</font></font></font></font></li>
+ <li><code>help_text="Enter field documentation"</code><font><font> <font><font>— 提供一个帮助用户的文本标签,让用户知道当前透过HTML表单输入时要提供什么值。</font></font></font></font></li>
+</ul>
+
+<p><font><font><font><font>字段名称用于在视图和模版中引用它。</font></font></font><font><font><font>字段还有一个标签,它被指定一个参数(</font></font></font></font><code>verbose_name</code><font><font><font><font>),或者通过大写字段的变量名的第一个字母,并用空格替换下划线(例如</font></font></font></font><code>my_field_name</code><font><font><font><font>的默认标签为My field name )。</font></font></font></font></p>
+
+<p><font><font><font><font>如果模型在表单中呈现(例如:在管理站点中),则声明该字段的顺序,将影响其默认顺序,但可能会被覆盖。</font></font></font></font></p>
+
+<h5 id="Common_field_arguments">Common field arguments</h5>
+
+<p>当声明很多/大多数不同的字段类型时,可以使用以下常用参数:</p>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#help-text" rel="noopener"><font><font><font><font>help_text</font></font></font></font></a><font><font> <font><font> :提供HTML表单文本标签(eg i在管理站点中),如上所述。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#verbose-name" rel="noopener"><font><font><font><font>verbose_name</font></font></font></font></a><font><font> <font><font> :字段标签中的可读性名称,如果没有被指定,Django将从字段名称推断默认的详细名称。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#default" rel="noopener"><font><font><font><font>default</font></font></font></font></a><font><font> <font><font> :该字段的默认值。</font></font></font><font><font><font>这可以是值或可呼叫物件(callable object),在这种情况下,每次创建新纪录时都将呼叫该物件。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#null" rel="noopener"><font><font><font><font>null</font></font></font></font></a><font><font><font><font>:如为</font></font><code>True</code><font><font>,即允许Django于资料库该栏位写入</font></font><code>NULL</code><font><font>(但栏位型态如为</font></font><code>CharField</code><font><font>则会写入空字串)。</font><font>预设值是</font></font><code>False</code><font><font>。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#blank" rel="noopener"><font><font><font><font>blank</font></font></font></font></a><font><font> <font><font> :如果</font></font></font></font><code><strong><font><font>True</font></font></strong></code><font><font><font><font>,表单中的字段被允许为空白。</font></font></font><font><font><font>默认是</font></font><code>False</code><font><font>,这意味着Django的表单验证将强制你输入一个值。</font></font></font><font><font><font>这通常搭配  </font></font><code>NULL=True</code><font><font> 使用,因为如果要允许空值,你还希望数据库能够适当地表示它们。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#choices" rel="noopener"><font><font><font><font>choices</font></font></font></font></a><font><font> <font><font> :这是给此字段的一组选项。</font></font></font><font><font><font>如果提供这一项,预设对应的表单部件是「该组选项的列表」,而不是原先的标准文本字段。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#primary-key" rel="noopener"><font><font><font><font>primary_key</font></font></font></font></a><font><font> <font><font> :如果是True,将当前字段设置为模型的主键(主键是被指定用来唯一辨识所有不同表记录的特殊数据库栏位(column))。</font></font></font><font><font><font>如果没有指定字段作为主键,则Django将自动为此添加一个字段。</font></font></font></font></li>
+</ul>
+
+<p><font><font><font><font>还有许多其他选项—你可以在</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-options" rel="noopener"><font><font><font><font>这里看到完整的字段选项</font></font></font></font></a><font><font><font><font>。</font></font></font></font></p>
+
+<h5 id="Common_field_types">Common field types</h5>
+
+<p><font><font><font><font>以下列表描述了一些更常用的字段类型。</font></font></font></font></p>
+
+<ul>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.CharField" rel="noopener"><font><font>CharField</font></font></a> </font></font><font><font><font><font>是用来定义短到中等长度的字段字符串。</font></font></font><font><font><font>你必须指定</font></font><code>max_length</code><font><font>要存储的数据。</font></font></font></font></li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.TextField" rel="noopener"><font><font><font><font>TextField </font></font></font></font></a><font><font><font><font>用于大型任意长度的字符串。</font></font></font><font><font><font>你可以</font></font><code>max_length</code><font><font>为该字段指定一个字段,但仅当该字段以表单显示时才会使用(不会在数据库级别强制执行)。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.IntegerField" rel="noopener" title="django.db.models.IntegerField"><font><font>IntegerField</font></font></a> </font></font><font><font><font><font>是一个用于存储整数(整数)值的字段,用于在表单中验证输入的值为整数。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datefield" rel="noopener"><font><font>DateField</font></font></a> </font></font><font><font><font><font>和</font></font></font></font><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datetimefield" rel="noopener"><font><font>DateTimeField</font></font></a> </font></font><font><font><font><font>用于存储/表示日期和日期/时间信息(分别是</font></font><code>Python.datetime.date</code><font><font> 和  </font></font><code>datetime.datetime</code><font><font> 对象)。</font><font>这些字段可以另外表明(互斥)参数  </font></font><code>auto_now=Ture</code><font><font>(在每次保存模型时将该字段设置为当前日期),</font></font><code>auto_now_add</code><font><font>(仅设置模型首次创建时的日期)和  </font></font><code>default</code><font><font>(设置默认日期,可以被用户覆盖)。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#emailfield" rel="noopener"><font><font>EmailField</font></font></a> </font></font><font><font><font><font>用于存储和验证电子邮件地址。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#filefield" rel="noopener"><font><font>FileField</font></font></a> </font></font><font><font><font><font>和</font></font></font></font><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#imagefield" rel="noopener"><font><font>ImageField</font></font></a> </font></font><font><font><font><font>分别用于上传文件和图像(</font></font><code>ImageField</code><font><font> 只需添加上传的文件是图像的附加验证)。</font></font></font><font><font><font>这些参数用于定义上传文件的存储方式和位置。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#autofield" rel="noopener"><font><font>AutoField</font></font></a> </font></font><font><font><font><font>是一种  </font></font></font></font><strong><font><font><font><font>IntegerField </font></font></font></font></strong><font><font><font><font>自动递增的特殊类型。</font></font></font><font><font><font>如果你没有明确指定一个主键,则此类型的主键将自动添加到模型中。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey" rel="noopener"><font><font>ForeignKey</font></font></a> </font></font><font><font><font><font>用于指定与另一个数据库模型的一对多关系(例如,汽车有一个制造商,但制造商可以制作许多汽车)。</font></font></font><font><font><font>关系的“一”侧是包含密钥的模型。</font></font></font></font></li>
+ <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener"><font><font>ManyToManyField</font></font></a> </font></font><font><font><font><font>用于指定</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener"><font><font><font><font>多对多</font></font></font></font></a><font><font><font><font>关系(例如,一本书可以有几种类型,每种类型可以包含几本书)。</font></font></font><font><font><font>在我们的图书馆应用程序中,我们将非常类似地使用它们ForeignKeys,但是可以用更复杂的方式来描述组之间的关系。</font></font></font><font><font><font>这些具有参数  </font></font><code>on_delete</code><font><font> 来定义关联记录被删除时会发生什么(例如,值  </font></font><code>models.SET_NULL</code><font><font> 将简单地设置为值NULL )。</font></font></font></font></li>
+</ul>
+
+<p><font><font><font><font>还有许多其他类型的字段,包括不同类型数字的字段(大整数,小整数,浮点数),布林值,URLs,唯一ids和其他“时间相关”的信息(持续时间,时间等)。</font></font></font><font><font><font>你可以查阅</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-types" rel="noopener"><font><font><font><font>完整列表</font></font></font></font></a><font><font> <font><font> .</font></font></font></font></p>
+
+<h4 id="元数据Metadata"><font><font><font><font>元数据(Metadata)</font></font></font></font></h4>
+
+<p><font><font>你可以通过宣告 class Meta 来宣告模型级别的元数据,如图所示:</font></font></p>
+
+<pre class="notranslate"><code>class Meta:
+ ordering = ['-my_field_name']
+</code></pre>
+
+<p><font><font><font><font>此元数据最有用的功能之一是控制在查询模型类型时返回之记录的默认排序。</font></font></font><font><font><font>你可以透过在</font></font></font></font><code>ordering</code><font><font><font><font>属性的字段名称列表中指定匹配顺序来执行此操作,如上所示。</font></font></font><font><font><font>排序将依赖字段的类型(字符串字段按字母顺序排序,而日期字段按时间顺序排序)。</font></font></font><font><font><font>如上所示,你可以使用减号(-)对字段名称进行前缀,以反转排序顺序。</font></font></font></font></p>
+
+<p><font><font><font><font>例如,如果我们选择依照此预设来排列书单:</font></font></font></font></p>
+
+<pre class="notranslate"><code>ordering = ['title', '-pubdate']</code></pre>
+
+<p><font><font><font><font>书单通过标题依据--字母排序--排列,从A到Z,然后再依每个标题的出版日期,从最新到最旧排列。</font></font></font></font></p>
+
+<p><font><font><font><font>另一个常见的属性是  </font></font></font></font><code>verbose_name</code><font><font><font><font>,一个  </font></font></font></font><code>verbose_name</code><font><font><font><font>说明单数和复数形式的类别。</font></font></font></font></p>
+
+<pre class="notranslate"><code>verbose_name = 'BetterName'</code></pre>
+
+<p><font><font><font><font>其他有用的属性允许你为模型创建和应用新的“访问权限”(预设权限会被自动套用),允许基于其他的字段排序,或声明该类是”抽象的“(你无法创建的记录基类,并将由其他型号派生)。</font></font></font></font></p>
+
+<p><font><font><font><font>许多其他元数据选项控制模型中必须使用哪些数据库以及数据的存储方式。</font></font></font><font><font><font>(如果你需要模型映射一个现有数据库,这会有用)。</font></font></font></font></p>
+
+<p><font><font><font><font>完整有用的元数据选项在这里</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/options/" rel="noopener"><font><font><font><font>Model metadata options</font></font></font></font></a><font><font> <font><font> (Django docs).</font></font></font></font></p>
+
+<h4 id="方法Methods"><font><font><font><font>方法(Methods)</font></font></font></font></h4>
+
+<p><font><font><font><font>一个模型也可以有方法。</font></font></font></font></p>
+
+<p><strong><font><font><font><font>最起码,在每个模型中,你应该定义标准的Python类方法</font></font></font></font><code>__str__()</code> </strong><font><font><font><font>,</font></font><strong><font><font>来为每个物件返回一个人类可读的字符串</font></font></strong><font><font>。</font></font></font><font><font><font>此字符用于表示管理站点的各个记录(以及你需要引用模型实例的任何其他位置)。</font></font></font><font><font><font>通常这将返回模型中的标题或名称字段。</font></font></font></font></p>
+
+<pre class="notranslate"><code>def __str__(self):
+  return self.field_name</code></pre>
+
+<p><font><font><font>Django方法中另一个常用方法是</font></font></font> <code>get_absolute_url()</code> <font><font><font>,这函数返回一个在网站上显示个人模型记录的URL(如果你定义了该方法,那么Django将自动在“管理站点”中添加“在站点中查看“按钮在模型的记录编辑栏)。</font></font></font><code>get_absolute_url()</code><font><font><font>的典型示例如下:</font></font></font></p>
+
+<pre class="notranslate"><code>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)])
+</code></pre>
+
+<p><strong><font><font><font><font>注意</font></font></font></font></strong><font><font> <font><font> :假设你将使用URL </font></font></font></font><code>/myapplication/mymodelname/2</code> <font><font><font><font>来显示模型的单个记录(其中“2”是id特定记录),则需要创建一个URL映射器来将响应和id传递给“模型详细视图” (这将做出显示记录所需的工作)。</font></font></font><font><font><font>以上示例中,</font></font></font></font><code>reverse()</code><font><font><font><font>函数可以“反转”你的url映射器(在上诉命名为“model-detail-view”的案例中,以创建正确格式的URL。</font></font></font></font></p>
+
+<p><font><font><font><font>当然要做这个工作,你还是要写URL映射,视图和模版!</font></font></font></font></p>
+
+<p><font><font>你可以定义一些你喜欢的其他方法,并从你的代码或模版调用它们(只要它们不带任何参数)。</font></font></p>
+
+<h3 id="Model_management">Model management</h3>
+
+<p><font><font><font>一旦你定义了模型类,你可以使用它们来创建,更新或删除记录,并运行查询获取所有记录或特定的记录子集。</font></font></font><font><font><font>当我们定义我们的视图,我们将展示给你在这个教程如何去做。</font></font></font></p>
+
+<h4 id="创建和修改记录"><font><font><font><font>创建和修改记录</font></font></font></font></h4>
+
+<p><font><font>要创建一个记录,你可以定义一个模型实例,然后呼叫  </font></font><code>save()</code><font><font>。</font></font></p>
+
+<pre class="brush: python notranslate"># Create a new record using the model's constructor.
+a_record = MyModelName(my_field_name="Instance #1")
+
+# Save the object into the database.
+a_record.save()
+</pre>
+
+<div class="note">
+<p><strong><font><font>注:</font></font></strong><font><font>如果没有任何的栏位被宣告为</font></font><code>主鍵</code><font><font>,这笔新的纪录会被自动的赋予一个主键并将主键栏命名为</font></font><code>id</code><font><font>。</font><font>上例的那笔资料被储存后,试着查询这笔纪录会看到它被自动赋予1的编号。</font></font></p>
+</div>
+
+<p><font><font>你可以透过「点(dot)的语法」取得或变更这笔新资料的栏位(字段)。</font><font>你需要呼叫</font></font><code>save()</code><font><font>将变更过的资料存进资料库:</font></font></p>
+
+<pre class="brush: python notranslate"># Access model field values using Python attributes.
+print(a_record.id) #should return 1 for the first record.
+print(a_record.my_field_name) # should print 'Instance #1'
+
+# Change record by modifying the fields, then calling save().
+a_record.my_field_name="New Instance Name"
+a_record.save()</pre>
+
+<h4 id="搜寻纪录"><font><font>搜寻纪录</font></font></h4>
+
+<p><font><font>你可以使用模型的  </font></font><code>objects</code><font><font> 属性(由base class提供)搜寻符合某个条件的纪录</font></font></p>
+
+<div class="note">
+<p><strong>Note</strong>: <font><font>要用"抽象的"模型还有栏位说明怎么搜寻纪录可能会有点令人困惑。</font><font>我们会以一个Book模型,其包含</font></font><code>title</code><font><font>与</font></font><code>genre</code><font><font>字段,而genre也是一个仅有</font></font><code>name</code><font><font>一个字段的模型。</font></font></p>
+</div>
+
+<p><font><font>我们可以取得一个模型的所有纪录,为一个  </font></font><code>QuerySet</code><font><font> 使用</font></font><code>objects.all()。</code> <code>QuerySet</code><font><font> 是一个可迭代的物件,表示他含有多个物件,而我们可以藉由迭代/回圈取得每个物件。</font></font></p>
+
+<pre class="brush: python notranslate">all_books = Book.objects.all()
+</pre>
+
+<p><font><font>Django的  </font></font><code>filter()</code><font><font>方法让我们可以透过符合特定文字或数值的字段筛选回传的</font></font><code>QuerySet</code><font><font>。</font><font>例如筛选书名里有"wild"的书并且计算总数,如下面所示。</font></font></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><font><font>要比对的字段与比对方法都要被定义在筛选的参数名称里,并且使用这个格式:</font></font><code>比對字段__比對方法</code><font><font> (请注意上方范例中的  </font></font><code>title</code><font><font> 与  </font></font><code>contains</code><font><font> 中间隔了两个底线唷)。</font><font>在上面我们使用大小写区分的方式比对</font></font><code>title</code><font><font>。</font><font>还有很多比对方式可以使用:  </font></font><code>icontains</code><font><font>(不区分大小写), </font></font><code>iexact</code><font><font>(大小写区分且完全符合), </font></font><code>exact</code><font><font>(不区分大小写但完全符合)还有  </font></font><code>in</code><font><font>, </font></font><code>gt</code><font><font>(大于), </font></font><code>startswith</code><font><font>,之类的。</font></font><a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#field-lookups" rel="noopener"><font><font>全部的用法在这里。</font></font></a></p>
+
+<p><font><font>有时候你会须要透过某个一对多的字段来筛选(例如一个  </font></font><code>外鍵</code><font><font>)。</font><font>这样的状况下,你可以使用两个底线来指定相关模型的字段。</font><font>例如透过某个特定的genre名称筛选书籍,如下所示:</font></font></p>
+
+<pre class="brush: python notranslate">books_containing_genre = Book.objects.filter(genre<strong>__</strong>name<strong>__</strong>icontains='fiction') # Will match on: Fiction, Science fiction, non-fiction etc.
+</pre>
+
+<div class="note">
+<p><strong>Note</strong>: 你可以用下划线来表示不同关系 (<code>ForeignKey</code>/<code>ManyToManyField</code>) .例如,一本书有不同的类型,用“cover“关系可能会帮助起一个参数名字 <code>type__cover__name__exact='hard'.</code></p>
+</div>
+
+<p><font><font>还有很多是你可以用索引(queries)来做的,包含从相关的模型做向后查询(backwards searches)、连锁过滤器(chaining filters)、回传「值的小集合」等。</font><font>更多资讯可以到  </font></font><a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/" rel="noopener"><font><font>Making queries</font></font></a><font><font> (Django Docs)查询。</font></font></p>
+
+<h2 id="Defining_the_LocalLibrary_Models">Defining the LocalLibrary Models</h2>
+
+<p>In this section we will start defining the models for the library. Open <em>models.py (in /locallibrary/catalog/)</em>. The boilerplate at the top of the page imports the <em>models</em> module, which contains the model base class <code>models.Model</code> that our models will inherit from.</p>
+
+<pre class="brush: python notranslate">from django.db import models
+
+# Create your models here.</pre>
+
+<h3 id="Genre_model">Genre model</h3>
+
+<p>Copy the Genre model code shown below and paste it into the bottom of your <code>models.py</code> file. This model is used to store information about the book category — for example whether it is fiction or non-fiction, romance or military history, etc. As mentioned above, we've created the Genre as a model rather than as free text or a selection list so that the possible values can be managed through the database rather than being hard coded.</p>
+
+<pre class="brush: python notranslate">class Genre(models.Model):
+    """
+    Model representing a book genre (e.g. Science Fiction, Non Fiction).
+    """
+    name = models.CharField(max_length=200, help_text="Enter a book genre (e.g. Science Fiction, French Poetry etc.)")
+
+    def __str__(self):
+        """
+        String for representing the Model object (in Admin site etc.)
+        """
+        return self.name</pre>
+
+<p>The model has a single <code>CharField</code> field (<code>name</code>), which is used to describe the genre (this is limited to 200 characters and has some <code>help_text</code>. At the end of the model we declare a <code>__str__()</code> method, which simply returns the name of the genre defined by a particular record. No verbose name has been defined, so the field will be called <code>Name</code> in forms.</p>
+
+<h3 id="Book_model">Book model</h3>
+
+<p>Copy the Book model below and again paste it into the bottom of your file. The book model represents all information about an available book in a general sense, but not a particular physical "instance" or "copy" available for loan. The model uses a <code>CharField</code> to represent the book's <code>title</code> and <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"). The model uses <code>TextField</code> for the <code>summary</code>, because this text may need to be quite long.</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 &lt;a href="https://www.isbn-international.org/content/what-isbn"&gt;ISBN number&lt;/a&gt;')
+    genre = models.ManyToManyField(Genre, help_text="Select a genre for this book")
+    # 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.
+
+    def __str__(self):
+        """
+        String for representing the Model object.
+        """
+        return self.title
+
+
+    def get_absolute_url(self):
+        """
+        Returns the url to access a particular book instance.
+        """
+        return reverse('book-detail', args=[str(self.id)])
+</pre>
+
+<p>The genre is a <code>ManyToManyField</code>, so that a book can have multiple genres and a genre can have many books. The author is declared as <code>ForeignKey</code>, so each book will only have one author, but an author may have many books (in practice a book might have multiple authors, but not in this implementation!)</p>
+
+<p>In both field types the related model class is declared as the first unnamed parameter using either the model class or a string containing the name of the related model. You must use the name of the model as a string if the associated class has not yet been defined in this file before it is referenced! The other parameters of interest in the <code>author</code> field are <code>null=True</code>, which allows the database to store a <code>Null</code> value if no author is selected, and <code>on_delete=models.SET_NULL</code>, which will set the value of the author to <code>Null</code> if the associated author record is deleted.</p>
+
+<p>The model also defines <code>__str__()</code> , using the book's <code>title</code> field to represent a <code>Book</code> record. The final method, <code>get_absolute_url()</code> returns a URL that can be used to access a detail record for this model (for this to work we will have to define a URL mapping that has the name <code>book-detail</code>, and define an associated view and template).</p>
+
+<h3 id="BookInstance_model">BookInstance model</h3>
+
+<p>Next, copy the <code>BookInstance</code> model (shown below) under the other models. The <code>BookInstance</code> represents a specific copy of a book that someone might borrow, and includes information about whether the copy is available or on what date it is expected back, "imprint" or version details, and a unique id for the book in the library.</p>
+
+<p>Some of the fields and methods will now be familiar. The model uses</p>
+
+<ul>
+ <li><code>ForeignKey</code> to identify the associated Book (each book can have many copies, but a copy can only have one <code>Book</code>).</li>
+ <li><code>CharField</code> to represent the imprint (specific release) of the book.</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 '%s (%s)' % (self.id,self.book.title)</pre>
+
+<p>We additionally declare a few new types of field:</p>
+
+<ul>
+ <li><code>UUIDField</code> is used for the <code>id</code> field to set it as the <code>primary_key</code> for this model. This type of field allocates a globally unique value for each instance (one for every book you can find in the library).</li>
+ <li><code>DateField</code> is used for the <code>due_back</code> date (at which the book is expected to come available after being borrowed or in maintenance). This value can be <code>blank</code> or <code>null</code> (needed for when the book is available). The model metadata (<code>Class Meta</code>) uses this field to order records when they are returned in a query.</li>
+ <li><code>status</code> is a <code>CharField</code> that defines a choice/selection list. As you can see, we define a tuple containing tuples of key-value pairs and pass it to the choices argument. The value in a key/value pair is a display value that a user can select, while the keys are the values that are actually saved if the option is selected. We've also set a default value of 'm' (maintenance) as books will initially be created unavailable before they are stocked on the shelves.</li>
+</ul>
+
+<p>The model <code>__str__()</code> represents the <code>BookInstance</code> object using a combination of its unique id and the associated <code>Book</code>'s title.</p>
+
+<div class="note">
+<p><strong>Note</strong>: A little Python:</p>
+
+<ul>
+ <li>The value returned by <code>__str__()</code> is a <em>formatted string</em>. Within the string we use <code>%s</code> to declare "placeholders'. After the string we specify <code>%</code> and then a tuple containing the values to be inserted in the placeholders. If you just have one placeholder then you can omit the tuple — e.g. <code>'My value: %s' % variable.</code><br>
+ <br>
+ Note also that although this approach is perfectly valid, please be aware that it is no longer prefered. Since Python 3 you should instead use the format method, eg. '{0} ({1})'.format(self.id,self.book.title). You can read more about it <a href="https://www.python.org/dev/peps/pep-3101/">here</a>.</li>
+</ul>
+</div>
+
+<h3 id="Author_model">Author model</h3>
+
+<p>Copy the <code>Author</code> model (shown below) underneath the existing code in <strong>models.py</strong>.</p>
+
+<p>All of the fields/methods should now be familiar. The model defines an author as having a first name, last name, date of birth, and (optional) date of death. It specifies that by default the <code>__str__()</code> returns the name in <em>last name</em>, <em>firstname </em>order. The <code>get_absolute_url()</code> method reverses the <code>author-detail</code> URL mapping to get the URL for displaying an individual author.</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)
+
+    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 '%s, %s' % (self.last_name, self.first_name)
+</pre>
+
+<h2 id="Re-run_the_database_migrations">Re-run the database migrations</h2>
+
+<p>All your models have now been created. Now re-run your database migrations to add them to your database.</p>
+
+<pre class="notranslate"><code>python3 manage.py makemigrations
+python3 manage.py migrate</code></pre>
+
+<h2 id="Language_model_—_challenge">Language model — challenge</h2>
+
+<p>Imagine a local benefactor donates a number of new books written in another language (say, Farsi). The challenge is to work out how these would be best represented in our library website, and then to add them to the models.</p>
+
+<p>Some things to consider:</p>
+
+<ul>
+ <li>Should "language" be associated with a <code>Book</code>, <code>BookInstance</code>, or some other object?</li>
+ <li>Should the different languages be represented using model, a free text field, or a hard-coded selection list?</li>
+</ul>
+
+<p>After you've decided, add the field. You can see what we decided on Github <a href="https://github.com/mdn/django-locallibrary-tutorial/blob/master/catalog/models.py">here</a>.</p>
+
+<ul>
+</ul>
+
+<ul>
+</ul>
+
+<h2 id="Summary">Summary</h2>
+
+<p>In this article we've learned how models are defined, and then used this information to design and implement appropriate models for the <em>LocalLibrary</em> website.</p>
+
+<p>At this point we'll divert briefly from creating the site, and check out the <em>Django Administration site</em>. This site will allow us to add some data to the library, which we can then display using our (yet to be created) views and templates.</p>
+
+<h2 id="See_also">See also</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial02/">Writing your first Django app, part 2</a> (Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/db/queries/">Making queries</a> (Django Docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/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>
diff --git a/files/zh-cn/learn/server-side/django/sessions/index.html b/files/zh-cn/learn/server-side/django/sessions/index.html
new file mode 100644
index 0000000000..d6b95b0157
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/sessions/index.html
@@ -0,0 +1,190 @@
+---
+title: 'Django 教程 7: 会话框架'
+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" style='font-style: normal; margin: 0px 0px 20px; padding: 20px 0px; border-width: 3px 0px; border-style: solid; border-color: rgb(131, 208, 242); max-width: 42rem; font-size: 1.25rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; 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/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='font-style: normal; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,为主页添加了一个基于会话的访问计数器。</font><font>这是一个相对简单的例子,但它确实显示了,如何使用会话框架为匿名用户提供持久的行为。</font></font></p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>完成之前的所有教程主题,包括<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django教程6:通用列表和详细信息视图</a></td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>了解会话的使用方式。</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><br>
+ 会话框架允许您实现此类行为,允许您基于每个站点访问者,以储存和检索任意数据。</p>
+
+<h2 id="会话是什么?">会话是什么?</h2>
+
+<p>Web浏览器和服务器之间的所有通信,都是通过HTTP协议进行的,该协议是无状态的。协议无状态的事实,意味着客户端和服务器之间的消息,完全相互独立 - 没有基于先前消息的“序列”或行为的概念。因此,如果您想拥有一个追踪与客户的持续关系的网站,您需要自己实现。</p>
+
+<p>会话是Django(以及大多数Internet)用于跟踪站点和特定浏览器之间“状态”的机制。会话允许您为每个浏览器存储任意数据,并在浏览器连接时,将该数据提供给站点。然后,通过“密钥”引用与会话相关联的各个数据项,“密钥”用于存储和检索数据。</p>
+
+<p>Django使用包含特殊会话ID的cookie,来识别每个浏览器,及其与该站点的关联会话。默认情况下,实际会话数据存储在站点数据库中(这比将数据存储在cookie中更安全,因为它们更容易受到恶意用户的攻击)。您可以将Django配置为,将会话数据存储在其他位置(缓存,文件,“安全”cookie),但默认位置是一个良好且相对安全的选项。</p>
+
+<h2 id="启用会话">启用会话</h2>
+
+<p>我们<a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">创建骨架网站</a>时,会自动启用会话(在教程2中)。</p>
+
+<p>配置在项目文件(<strong>locallibrary / locallibrary / settings.py</strong>)的<code>INSTALLED_APPS</code> 和 <code>MIDDLEWARE</code> 部分中设置,如下所示:</p>
+
+<pre class="brush: python">INSTALLED_APPS = [
+ ...
+<strong> 'django.contrib.sessions',</strong>
+ ....
+
+MIDDLEWARE = [
+ ...
+<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong>
+ ....</pre>
+
+<h2 id="使用会话">使用会话</h2>
+
+<p>您可以从<code>request</code>请求参数访问视图中的<code>session</code>会话属性(作为视图的第一个参数传入的<code>HttpRequest</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"># 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/">如何使用会话</a>(Django文档)中找到完整的API。</p>
+
+<h2 id="保存会话数据">保存会话数据</h2>
+
+<p>默认情况下,Django仅保存到会话数据库,并在会话被修改(分配)或删除时,将会话cookie发送到客户端。如果您使用会话密钥更新某些数据,如上一节所示,那么您无需担心这一点!例如:</p>
+
+<pre class="brush: python"># 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"># 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>: 您可以通过将<code>SESSION_SAVE_EVERY_REQUEST = True</code>添加到项目设置(<strong>locallibrary/locallibrary/settings.py</strong>),以更改站点行为,站点将在每个请求上更新数据库/发送cookie。</p>
+</div>
+
+<h2 id="简单的例子_-_获取访问次数">简单的例子 - 获取访问次数</h2>
+
+<p>作为一个简单的现实世界的例子,我们将更新我们的图书馆,告诉当前用户,他们访问 LocalLibrary 主页的次数。</p>
+
+<p>打开<strong>/locallibrary/catalog/views.py</strong>,并在下面以粗体显示更改。</p>
+
+<pre class="brush: python">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>
+
+ # Render the HTML template index.html with the data in the context variable.
+ return render(
+ request,
+ 'index.html',
+<strong> context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors,
+ 'num_visits':num_visits}, # num_visits appended</strong>
+ )</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/">如何使用会话</a>作为示例),或设计我们的UI,以便无论cookie是否受支持都无关紧要。</p>
+</div>
+
+<p>将以下区块底部那一行,添加到主HTML模板(<strong>/locallibrary/catalog/templates/index.html</strong>)的 “动态内容” 部分底部,以显示上下文变量:</p>
+
+<pre class="brush: html">&lt;h2&gt;Dynamic content&lt;/h2&gt;
+
+&lt;p&gt;The library has the following record counts:&lt;/p&gt;
+&lt;ul&gt;
+&lt;li&gt;&lt;strong&gt;Books:&lt;/strong&gt; \{{ num_books }}&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;Copies:&lt;/strong&gt; \{{ num_instances }}&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;Copies available:&lt;/strong&gt; \{{ num_instances_available }}&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;Authors:&lt;/strong&gt; \{{ num_authors }}&lt;/li&gt;
+&lt;/ul&gt;
+
+<strong>&lt;p&gt;You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.&lt;/p&gt;</strong>
+</pre>
+
+<p>保存更改,并重新启动测试服务器。每次刷新页面时,数字都应该更新。</p>
+
+<ul>
+</ul>
+
+<h2 id="总结">总结</h2>
+
+<p>你现在知道,使用sessions 改善与匿名使用者的互动,有多么容易了。</p>
+
+<p>在我们的下一篇文章,我们将解释授权与许可框架,并演示如何支持使用者帐户。</p>
+
+<h2 id="参见">参见</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">如何使用会话</a> (Django 文档)</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}</p>
+
+<p> </p>
+
+<h2 id="本教程">本教程</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">架设 Django 开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教程: The Local Library website</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: Creating a skeleton website</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Models">Django 教程 3: Using models</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django admin site</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5: Creating our home page</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: Generic list and detail views</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django 教程 7: Sessions framework</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Authentication">Django 教程 8: User authentication and permissions</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Forms">Django 教程 9: Working with forms</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Testing">Django 教程 10: Testing a Django web application</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/Deployment">Django 教程 11: Deploying Django to production</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/web_application_security">Django 网络应用安全</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博</a></li>
+</ul>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/django/skeleton_website/index.html b/files/zh-cn/learn/server-side/django/skeleton_website/index.html
new file mode 100644
index 0000000000..7b02df197e
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/skeleton_website/index.html
@@ -0,0 +1,372 @@
+---
+title: 'Django Tutorial Part 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"><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程</a>的第二篇文章会展示怎样创建一个网站的"框架",在这个框架的基础上,你可以继续填充整站使用的settings, urls,模型(models),视图(views)和模板(templates)。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前提:</th>
+ <td><a href="/en-US/docs/Learn/Server-side/Django/development_environment">创建Django的开发环境</a>。复习 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程</a>。</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>使用django-admin工具创建工程的文件夹,基本的文件模板和工程管理脚本(<strong>manage.py</strong>)。</li>
+ <li><span style="line-height: 1.5;">用</span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;"> 创建一个或多个应用。</span>
+ <div class="note">
+ <p><span style="font-size: 14px;"><strong>注意:</strong>一个网站可能由多个部分组成,比如,主要页面,博客,wiki,下载区域等。Django鼓励将这些部分作为分开的应用开发。如果这样的话,在需要可以在不同的工程中复用这些应用。</span></p>
+ </div>
+ </li>
+ <li> 在工程里注册新的应用。</li>
+ <li>为每个应用分配url。</li>
+</ol>
+
+<p>为 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">locallibrary</a> 这个项目创建的网站文件夹和它的工程文件夹都命名为<em>locallibrary</em>。我们只创建一个名为<em>catalog</em>的应用。最高层的项目文件结构如下所示:</p>
+
+<pre class="brush: bash"><em>locallibrary/ # 网站文件夹</em>
+  <strong>manage.py </strong># 用来运行Django工具的脚本(由django-admin创建)
+  <em>locallibrary/ # 网站/项目文件夹(由django-admin创建)</em>
+ <em>catalog/ # 应用文件夹 </em>(由manage.py创建)</pre>
+
+<p>接下来的部分会详细讨论创建网站框架的过程,并会展示怎么测试这些变化。最后,我们会讨论在这个阶段里你可以设置的整站级的配置。</p>
+
+<h2 id="创建项目">创建项目</h2>
+
+<p>首先打开命令行工具,进入你想要创建Django应用的地方(最好是你容易找到的地方),为新网站创建一个文件夹(这里是:<em>locallibrary</em>)。用cd命令进入文件夹:</p>
+
+<pre class="brush: bash">mkdir locallibrary
+cd locallibrary</pre>
+
+<p>用<code>django-admin startproject</code>命令创建新项目,并进入该文件夹。</p>
+
+<pre class="brush: bash">django-admin startproject locallibrary
+cd locallibrary</pre>
+
+<p><code>django-admin</code>工具会创建如下所示的文件夹结构</p>
+
+<pre class="brush: bash"><em>locallibrary/</em>
+  <strong>manage.py</strong>
+  <em>locallibrary/</em>
+    settings.py
+    urls.py
+    wsgi.py</pre>
+
+<p>locallibrary项目的子文件夹是整个网站的进入点:</p>
+
+<ul>
+ <li><strong>settings.py</strong> 包含所有的网站设置。这是可以注册所有创建的应用的地方,也是静态文件,数据库配置的地方,等等。</li>
+ <li><strong>urls.py </strong>定义了网站url到view的映射<strong>。</strong>虽然这里可以包含所有的url,但是更常见的做法是把应用相关的url包含在相关应用中,你可以在接下来的教程里看到。</li>
+ <li><strong style="line-height: 1.5;">wsgi.py</strong><span style="line-height: 1.5;"> 帮助Django应用和网络服务器间的通讯。你可以把这个当作模板。</span></li>
+</ul>
+
+<p><strong>manage.py</strong>脚本可以创建应用,和数据库通讯,启动开发用网络服务器。</p>
+
+<h2 id="创建catalog应用">创建catalog应用</h2>
+
+<p>接下来,在locallibrary项目里,使用下面的命令创建catalog应用(和您项目的<strong>manage.py</strong>在同一个文件夹下)</p>
+
+<pre class="brush: bash">python3 manage.py startapp catalog</pre>
+
+<div class="note">
+<p><span style="font-size: 14px;"><strong>注意:</strong>Linux/Mac OS X应用可以使用上面的命令。在windows平台下应该改为:</span> <code>py -3 manage.py startapp catalog</code></p>
+
+<p>如果你是windows系统,在这个部分用<code>py -3</code> 替代<code>python3</code>。</p>
+</div>
+
+<p>这个工具创建了一个新的文件夹,并为该应用创建了不同的文件(下面黑体所示)。绝大多数文件的命令和它们的目的有关(比如视图函数就是<strong>views.py,</strong>模型就是<strong>models.py,</strong>测试是<strong>tests.py,</strong>网站管理设置是<strong>admin.py,</strong>注册应用是<strong>apps.py)</strong>,并且还包含了为项目所用的最小模板。</p>
+
+<p>执行命令后的文件夹结构如下所示:</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>除上面所说的文件外,我们还有:</p>
+
+<ul>
+ <li>一个<em>migration</em>文件夹,用来存储“migrations”——当你修改你的数据模型时,这个文件会自动升级你的数据库。</li>
+ <li><strong>__init__.py</strong> — 一个空文件,Django/Python会将这个文件作为<a href="https://docs.python.org/3/tutorial/modules.html#packages">Python 包</a>并允许你在项目的其他部分使用它。</li>
+</ul>
+
+<div class="note">
+<p><span style="font-size: 14px;"><strong>注意</strong></span>: 你注意到上面的文件里有些缺失嘛? 尽管由views和models的文件,可是url映射,网站模板,静态文件在哪里呢?我们会在接下来的部分展示如何创建它们(并不是每个网站都需要,不过这个例子需要)</p>
+</div>
+
+<h2 id="注册catalog应用">注册catalog应用</h2>
+
+<p>既然应用已经创建好了,我们还必须在项目里注册它,以便工具在运行时它会包括在里面(比如在数据库里添加模型时)。在项目的settings里,把应用添加进<code>INSTALLED_APPS</code> ,就完成了注册。</p>
+
+<p>打开项目设置文件 <strong>locallibrary/locallibrary/settings.py</strong> 找到  <code>INSTALLED_APPS</code> 列表里的定义。 如下所示,在列表的最后添加新的一行。</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>新的这行详细说明了应用配置文件在 (<code>CatalogConfig</code>) <strong>/locallibrary/catalog/apps.py</strong> 里,当你创建应用时就完成了这个过程。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 注意到<code>INSTALLED_APPS已经有许多其他的应用了</code> (还有 <code>MIDDLEWARE</code>, 在settings的下面)。这些应用为  <a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django administration site</a> 提供了支持和许多功能(包括会话,认证系统等)。</p>
+</div>
+
+<h2 id="配置数据库">配置数据库</h2>
+
+<p>现在可以为项目配置数据库了——为了避免性能上的差异,最好在生产和开发中使用同一种数据库。你可以在<a href="https://docs.djangoproject.com/en/1.10/ref/settings/#databases">数据库</a> 里找到不同的设置方法(Django文档)。 </p>
+
+<p>在这个项目里,我们使用SQLite。因为在展示用的数据库中,我们不会有很多并发存取的行为。同时,也因为SQLite不需要额外的配置工作。你可以在<strong>settings.py</strong>里看到这个数据库怎样配置的。(更多信息如下所示)</p>
+
+<pre class="brush: python">DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+</pre>
+
+<p>因为我们使用SQLite,不需要其他的设置了。我们继续吧!</p>
+
+<h2 id="其他项目设置">其他项目设置</h2>
+
+<p>settings.py里还包括其他的一些设置,现在只需要改变<a href="https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-TIME_ZONE">时区</a> — 改为和 标准<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">tz时区数据表</a> 里的字符串相同就可以了(数据表里的TZ 列有你想要的时区)。 把<code>TIME_ZONE</code>的值改为你的时区,比如</p>
+
+<pre class="brush: python">TIME_ZONE = 'Asia/Shanghai'</pre>
+
+<p>有两个设置你现在不会用到,不过你应该留意:</p>
+
+<ul>
+ <li><code>SECRET_KEY</code>. 这个密匙值是Django网站安全策略的一部分。如果在开发环境中没有包好这个密匙,把代码投入生产环境时最好用不同的密匙代替。(可能从环境变量或文件中读取)。</li>
+ <li><code>DEBUG</code>. 这个会在debug日志里输出错误信息,而不是输入HTTP的返回码。在生产环境中,它应设置为false,因为输出的错误信息会帮助想要攻击网站的人。</li>
+</ul>
+
+<h2 id="链接URL映射器">链接URL映射器</h2>
+
+<p>在项目文件夹里,创建网站时同时生成了URL映射器(<strong>urls.py</strong>)。尽管你可以用它来管理所有的URL映射,但是更常用的做法是把URL映射留到它们相关的应用中。</p>
+
+<p>打开<strong>locallibrary/locallibrary/urls.py</strong> 并注意指导文字解释了一些使用URL映射器的方法。</p>
+
+<pre><code>"""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),
+]</code></pre>
+
+<p> </p>
+
+<p><span style="letter-spacing: -0.00333rem;">URL 映射通过</span><code style="font-style: normal; letter-spacing: -0.00333rem;">urlpatterns</code><span style="letter-spacing: -0.00333rem;"> 变量管理,它是</span><code style="font-style: normal; letter-spacing: -0.00333rem;">path()</code><span style="letter-spacing: -0.00333rem;"> 函数的一个Python列表结构。 每个</span><code style="font-style: normal; letter-spacing: -0.00333rem;">path()</code><span style="letter-spacing: -0.00333rem;">函数要么将URL式样(URL pattern)关联到特定视图(</span><em>specific view)</em><span style="letter-spacing: -0.00333rem;">,将在模式匹配时显示;要么关联到某个URL式样列表的测试代码。 (第二种情况下,URL式样是目标模型里的“base URL”). </span><code style="font-style: normal; letter-spacing: -0.00333rem;">urlpatterns</code><span style="letter-spacing: -0.00333rem;"> 列表最开始定义了一个函数,这个函数将所有带有模型 </span><em>admin/</em><span style="letter-spacing: -0.00333rem;"> 的URL映射到模块</span><code style="font-style: normal; letter-spacing: -0.00333rem;">admin.site.urls</code><span style="letter-spacing: -0.00333rem;">。这个函数包含了Administration 应用自己的URL映射定义。</span></p>
+
+<div class="note">
+<p>注意:path() 中的路由是一个字符串,用于定义要匹配的URL模式。该字符串可能包括一个命名变量(尖括号中)</p>
+
+<p>例:<code>'catalog/&lt;id&gt;/'</code>。此模式将匹配如 <strong>/catalog/<em>any_chars</em>/</strong> 的URL ,并将 any_chars 作为具有参数名称 <code>id</code> 的字符串传递给视图。我们将在后面的主题中进一步讨论路径方法和路由模式</p>
+</div>
+
+<p>将下面的行添加到文件的底部,以便将新的项添加到 <code>urlpatterns</code> 列表中。这个新项目包括一个 <code>path()</code> ,它将带有 <code>catalog/</code> 的请求转发到模块 <code>catalog.urls</code> (使用相对路径 URL <strong>/catalog/urls.py</strong>)。</p>
+
+<pre><code># 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')),
+]</code></pre>
+
+<p>现在让我们把网站的根URL(例:<code>127.0.0.1:8000</code>)重定向到该URL:<code>127.0.0.1:8000/catalog/</code>; 这是我们将在这个项目中使用的唯一应用程序,所以我们最好这样做。为了完成这个目标,我们将使用一个特殊的视图函数(<code>RedirectView</code>), 当在 <code>path()</code> 函数中指定的URL模式匹配时(在这个例子中是根URL),它将新的相对URL作为其第一个参数重定向到(<code>/catalog/</code>)。</p>
+
+<p>将以下行再次添加到文件的底部:</p>
+
+<pre><code>#Add URL maps to redirect the base URL to our application
+from django.views.generic import RedirectView
+urlpatterns += [
+ path('', RedirectView.as_view(url='/catalog/')),
+]</code></pre>
+
+<p>将路径函数的第一个参数留空以表示'/'。如果你将第一个参数写为'/',Django会在你启动服务器时给出以下警告:</p>
+
+<p> </p>
+
+<p> </p>
+
+<p> </p>
+
+<pre><code>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 '/'.</code></pre>
+
+<p> </p>
+
+<p>Django 默认不提供CSS, JavaScript, 和图片等静态文件 。但是当你在开发环境中开发时,这些静态文件也很有用。作为对这个URL映射器的最后一项添加,你可以通过添加以下行在开发期间启用静态文件的服务。</p>
+
+<p>把下面的代码加到文件最后:</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>
+
+<p> </p>
+
+<div class="note">
+<p><strong>注意</strong>: 有很多方法扩展<code>urlpatterns</code> 列表(在上面的代码里我们通过 <code>+=</code> 运算符来区分新旧代码)。我们同样可以用原先列表的定义:</p>
+
+<pre>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> </p>
+
+<p><span style="font-size: 1.2rem; letter-spacing: -0.00333rem;">除此以外,我们也可以包含import代码行 (</span><code style="font-style: normal; letter-spacing: -0.00333rem;">from django.conf.urls import include</code><span style="font-size: 1.2rem; letter-spacing: -0.00333rem;">) ,这样更容易看出我们添加的代码,通常我们把import代码行放在Python文件的开头。</span></p>
+</div>
+
+<p>最后,在catalog 文件夹下创建一个名为 <strong>urls.py </strong>的文件,并添加以下文本以定义导入(空)的 <code>urlpatterns</code>。这是我们在编写应用时添加式样的地方。</p>
+
+<pre><code>from django.urls import path
+from catalog import views
+
+urlpatterns = [
+
+]</code></pre>
+
+<h2 id="测试网站框架">测试网站框架</h2>
+
+<p>现在我们有了一个完整的框架项目。这个网站现在还什么都不能做,但是我们仍然要运行以下,以确保我们的更改是有效的。</p>
+
+<p>在运行前,我们应该向运行<em>数据库迁移</em>。这会更新我们的数据库并且包含所有安装的应用(同时去除一些警告)。</p>
+
+<h3 id="运行数据库迁移">运行数据库迁移</h3>
+
+<p>Django 使用对象关系映射器(ORM)将Django代码中的模型定义映射到底层数据库使用的数据结构。当我们更改模型定义时,Django会跟踪更改并创建数据库迁移脚本 (in <strong>/locallibrary/catalog/migrations/</strong>) 来自动迁移数据库中的底层数据结构来</p>
+
+<p>当我们创建网站时,Django会自动添加一些模型供网站的管理部分使用(稍后我们会解释)。运行以下命令来定义数据库中这些模型的表(确保你位于包含<strong> manage.py 的目录中</strong>):</p>
+
+<pre class="brush: bash">python3 manage.py makemigrations
+python3 manage.py migrate
+</pre>
+
+<div class="warning">
+<p><strong>重要信息</strong>: 每次模型改变,都需要运行以上命令,来影响需要存储的数据结构(包括添加和删除整个模型和单个字段)。</p>
+</div>
+
+<p>该 <strong><code>makemigrations</code></strong> 命令创建(但不适用)项目中安装的所有应用程序的迁移(你可以指定应用程序名称,也可以为单个项目运行迁移)。这让你有机会在应用这些迁移之前检查这些迁移代码—当你是Django专家时,你可以选择稍微调整它们。</p>
+
+<p>这 <strong><code>migrate</code></strong> 命令 明确应用迁移你的数据库(Django跟踪哪些已添加到当前数据库)。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 看 <a href="https://docs.djangoproject.com/en/1.10/topics/migrations/">Migrations</a> (Django docs) ,了解较少使用的迁移命令的其他信息。</p>
+</div>
+
+<h3 id="运行网站">运行网站</h3>
+
+<p>在开发期间,你首先要使用开发网络服务器和浏览你本机的浏览器,来测试你的网站。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 这个开发网络服务器并不够强大以及不足以用于生产使用,但是它能非常容易得使你在开发期间,获得你的Django网站和运行它,以此来进行快速测试。<br>
+ 默认情况下,服务器会开通(http://127.0.0.1:8000/),但你也可以选择其他端口。有关更多信息,查阅( <a href="https://docs.djangoproject.com/en/1.10/ref/django-admin/#runserver">django-admin and manage.py: runserver</a> )(Django docs).</p>
+</div>
+
+<p>通过调用 <code>runserver</code> 命令运行Web服务器(与<strong>manage.py</strong>位于同一目录下):</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>一旦服务器运行,你可以用你的浏览器导航到 <a href="http://127.0.0.1:8000/"><code>http://127.0.0.1:8000/</code> </a>查看。你应该会看到一个错误页面,如下所示。</p>
+
+<p><img alt="Django debug page for a 404 not found error" src="https://mdn.mozillademos.org/files/14009/django_404_debug_page.png" style="display: block; height: 545px; margin: 0px auto; width: 871px;"></p>
+
+<p>别担心,这个错误页面是预期结果。因为我们没有在 <code>catalogs.urls</code> 模块中定义任何页面/网址。<strong>(留意</strong>:当我们导航网站根目录URL时,我们被重定向到了<strong>/catalog 。)</strong></p>
+
+<div class="note">
+<p><strong>注意</strong>: 上面的页面展示了一个重要的Django功能—自动调试日志记录。每当找不到页面,或者代码引发任何错误,就会显示错误页面,其中会提供有用的信息。在这种情况下,你可以看到我们提供的 URL 与我们任何 URL 模式都不匹配(像列出的那样)。生产环境中,日志功能将被关闭(当我们将网站存放在网络上时),这种情况下,将提供的信息量更少,但用户友好的页面。</p>
+</div>
+
+<p>这个时候,我们知道Django正在工作。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 每当进行重大更改时,都应重新运行迁移并重新测试站点。这并不需要很长时间。</p>
+</div>
+
+<h2 id="挑战自我">挑战自我</h2>
+
+<p>该 <strong>catalog/ </strong>目录包含视图,模型和应用程序其他部分的文件。你可以打开这些文件并查看样板。</p>
+
+<p>如上所述,管理站点的 URL 映射已经添加到项目的 <strong>urls.py</strong> 中。导航到浏览器中的管理区域,看看会发生什么(您可以从上面的映射中推断出正确的URL)。</p>
+
+<ul>
+</ul>
+
+<h2 id="概要">概要</h2>
+
+<p>你现在已经创建了一个完整的基本网站项目骨架,你可以继续填加网址,模型,视图和模版。</p>
+
+<p>现在, <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Local Library website</a> 的骨架已经完成并运行,是时候开始编写代码,让这个网站做它应该做的事情了。</p>
+
+<h2 id="更多">更多</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial01/">编写你的第一个Django应用 - part 1</a>  (Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/ref/applications/#configuring-applications">Applications</a> (Django Docs). 包括配置应用的信息。</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</p>
diff --git a/files/zh-cn/learn/server-side/django/testing/index.html b/files/zh-cn/learn/server-side/django/testing/index.html
new file mode 100644
index 0000000000..26ca9b62bf
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/testing/index.html
@@ -0,0 +1,917 @@
+---
+title: 'Django 教程 10: 测试 Django 网页应用'
+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">随<font><font>着网站的增长,他们越来越难以手动测试。</font><font>不仅要进行更多的测试,而且随着组件之间的交互变得越来越复杂,一个区域的小改变可能会影响到其他区域,所以需要做更多的改变来确保一切正常运行,并且在进行更多更改时不会引入错误。</font><font>减轻这些问题的一种方法是编写自动化测试,每当您进行更改时,都可以轻松可靠地运行测试。</font><font>本教程演示如何</font><font>使用Django的测试框架</font><font>自动化</font><font>您的网站的</font></font><em><font><font>单元测试</font></font></em>。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>完成之前的所有教程主题,包括 Django教程 9:使用表单。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>了解如何为基于 Django 的网站编写单元测试。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p>LocalLibrary 目前有页面显示所有书本和作者的列表,书本和作者项目的详细视图,续借<code>BookInstances</code>的页面,以及创建,更新和删除作者项目的页面(如果您完成了<strong>Django 教程 9:使用表单</strong>中的自我挑战,也可以创建,更新和删除书本记录)。即使使用这个相对较小的站点,手动导航到每个页面,并且表面地检查一切是否按预期工作,可能需要几分钟。当我们进行更改,并扩展网站时,手动检查所有内容 “正常” 工作所需的时间只会增长。如果我们继续这样做,最终我们将花费大部分时间进行测试,并且很少有时间来改进我们的代码。</p>
+
+<p>自动化测试可以真正帮助解决这个问题!显而易见的好处,是它们可以比手动测试运行得更快,可以测试更底层级别的细节,并且每次都测试完全相同的功能(人类测试员远远没有这么可靠!)因为它们很快速,自动化的测试可以更频繁地执行,如果测试失败,他们会指出代码未按预期执行的位置。</p>
+
+<p>此外,自动化测试可以充当代码的第一个真实“用户”,迫使您严格定义和记录网站的行为方式。它们通常是您的代码示例,和文档的基础。由于这些原因,一些软件开发过程,从测试定义和实现开始,之后编写代码以匹配所需的行为(例如,测试驱动<a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven</a> 和行为驱动 <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">behaviour-driven</a>的开发)。</p>
+
+<p>本教程通过向 LocalLibrary 网站添加大量测试,来演示如何为 Django 编写自动化测试。</p>
+
+<h3 id="测试的类型">测试的类型</h3>
+
+<p>测试和测试方法有许多类型,级别和分类。最重要的自动化测试是:</p>
+
+<dl>
+ <dt>单元测试Unit tests</dt>
+ <dd>验证各个组件的功能行为,通常是类别和功能级别。</dd>
+ <dt>回归测试</dt>
+ <dd>测试重现历史错误。最初运行每个测试,以验证错误是否已修复,然后重新运行,以确保在以后更改代码之后,未重新引入该错误。</dd>
+ <dt>集成测试</dt>
+ <dd>验证组件分组在一起使用时的工作方式。集成测试了解组件之间所需的交互,但不一定了解每个组件的内部操作。它们可能涵盖整个网站的简单组件分组。</dd>
+</dl>
+
+<div class="note">
+<p><strong>注意: </strong>其他常见类型的测试,包括黑盒,白盒,手动,自动,金丝雀,烟雾,一致性,验收,功能,系统,性能,负载和压力测试。查找它们以获取更多信息。</p>
+</div>
+
+<h3 id="Django为测试提供了什么?">Django为测试提供了什么?</h3>
+
+<p>测试网站是一项复杂的任务,因为它由多层逻辑组成 - 从 HTTP 级请求处理,查询模型,到表单验证和处理,以及模板呈现。</p>
+
+<p>Django 提供了一个测试框架,其中包含基于 Python 标准<code><a href="https://docs.python.org/3/library/unittest.html#module-unittest" title="(in Python v3.5)">unittest</a></code>库的小型层次结构。尽管名称如此,但该测试框架适用于单元测试和集成测试。 Django 框架添加了 API 方法和工具,以帮助测试 Web 和 Django 特定的行为。这允许您模拟请求,插入测试数据以及检查应用程序的输出。 Django 还提供了一个API(<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>)和<a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/#other-testing-frameworks">使用不同测试框架</a>的工具,例如,您可以与流行的 <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> 框架集成,以模拟用户与实时浏览器交互。</p>
+
+<p>要编写测试,您可以从任何 Django(或unittest)测试基类(<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>)派生,然后编写单独的方法,来检查特定功能,是否按预期工作(测试使用 “assert” 方法来测试表达式导致 <code>True</code>或 <code>False</code>值,或者两个值相等,等等。)当您开始测试运行时,框架将在派生类中执行所选的测试方法。测试方法独立运行,具有在类中定义的常见设置和/或拆卸行为,如下所示。</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>大多数测试的最佳基类是 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a>。此测试类在运行测试之前,创建一个干净的数据库,并在自己的事务中,运行每个测试函数。该类还拥有一个<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">测试客户端</a>,您可以使用该客户端,模拟在视图级别与代码交互的用户。在下面的部分中,我们将集中讨论使用此<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a> 基类创建的单元测试。</p>
+
+<div class="note">
+<p><strong>注意:</strong> <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a> 类非常方便,但可能会导致某些测试,比它们需要的速度慢(并非每个测试,都需要设置自己的数据库,或模拟视图交互)。一旦熟悉了这个类可以做什么,您可能希望用可以用更简单的测试类,替换一些测试。</p>
+</div>
+
+<h3 id="你应该测试什么?">你应该测试什么?</h3>
+
+<p>您应该测试自己代码的所有方面,但不要测试 Python 或 Django 的一部分提供的任何库或功能。</p>
+
+<p>例如,考虑下面定义的 <code>Author</code>模型。您不需要显式测试 <code>first_name</code> 和 <code>last_name</code> 是否已在数据库中正确储存为<code>CharField</code>,因为这是 Django 定义的内容(当然,在实践中,您将不可避免地在开发期间测试此功能)。你也不需要测试<code>date_of_birth</code>是否已被验证为日期字段,因为这也是 Django 中实现的东西。</p>
+
+<p>但是,您应该检查用于标签的文本(名字,姓氏,出生日期,死亡),以及为文本分配的字段大小(100个字符),因为这些是您的设计的一部分,可能会在将来被打破/改变。</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>同样,您应该检查自定义方法 <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> 和 <code style="font-style: normal; font-weight: normal;">__str__()</code> 是否符合要求,因为它们是您的代码/业务逻辑。在<code style="font-style: normal; font-weight: normal;">get_absolute_url()</code>的情况下,您可以相信 Django <code>reverse()</code>方法已经正确实现,因此您正在测试的是实际上已经定义了关联的视图。</p>
+
+<p> </p>
+
+<div class="note">
+<p><strong>注意:</strong> 精明的读者可能会注意到,我们也希望将出生和死亡的日期限制在合理的值,并检查出生后是否死亡。在 Django中,此约束将添加到表单类中(尽管您可以为字段定义验证器,这些字段似乎仅在表单级别使用,而不是在模型级别使用)。</p>
+</div>
+
+<p>考虑到这些,让我们开始研究如何定义和运行测试。</p>
+
+<h2 id="测试结构概述">测试结构概述</h2>
+
+<p>在我们详细讨论“测试内容”之前,让我们先简要介绍一下测试的定位和方式。</p>
+
+<p>Django 使用 unittest 模块的<a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery">内置测试查找</a>,它将在任何使用模式<strong>test*.py </strong>命名的文件中,查找当前工作目录下的测试。如果您正确命名文件,则可以使用您喜欢的任何结构。我们建议您为测试代码创建一个模块,并为模型,视图,表单和您需要测试的任何其他类型的代码,分别创建文件。例如:</p>
+
+<pre>catalog/
+  /tests/
+  __init__.py
+  test_models.py
+  test_forms.py
+  test_views.py
+</pre>
+
+<p>在 LocalLibrary 项目中,创建如上所示的文件结构。<strong>__init__.py </strong>应该是一个空文件(这告诉 Python 该目录是一个套件包)。您可以通过复制和重命名框架测试文件<strong>/catalog/tests.py</strong>,来创建三个测试文件。</p>
+
+<p> </p>
+
+<div class="note">
+<p><strong>注意:</strong> 我们构建 Django 骨架网站时,会自动创建骨架测试文件<strong>/catalog/tests.py</strong> 。将所有测试放入其中是完全“合法的”,但如果测试正确,您将很快得到一个非常庞大且难以管理的测试文件。</p>
+
+<p>删除骨架文件,因为我们不需要它。</p>
+</div>
+
+<p>打开 <strong>/catalog/tests/test_models.py</strong>。 该文件应导入<code>django.test.TestCase</code>,如下所示:</p>
+
+<pre class="brush: python">from django.test import TestCase
+
+# Create your tests here.
+</pre>
+
+<p>通常,您将为要测试的每个模型/视图/表单添加测试类别,并使用个别方法来测试特定功能。在其他情况下,您可能希望有一个分开的类别,来测试特定用例,使用个别的测试函数,来测试该用例的各个方面(例如,测试模型字段已正确验证的类,以及测试每个可能的失败案例的函数)。相同地,这样的结构非常适合您,但最好您能保持一致。</p>
+
+<p>将下面的测试类别,添加到文件的底部。该类别演示了,如何通过派生<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>新的类别定义了两个可用于测试之前的配置的方法(例如,创建测试所需的任何模型或其他对象):</p>
+
+<ul>
+ <li><code>setUpTestData()</code> 用于类级别设置,在测试运行开始的时侯,会调用一次。您可以使用它来创建在任何测试方法中,都不会修改或更改的对象。</li>
+ <li><code>setUp()</code> 在每个测试函数之前被调用,以设置可能被测试修改的任何对象(每个测试函数,都将获得这些对象的 “新” 版本)。</li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>:测试类别还有一个我们还没有使用的<code>tearDown()</code>方法。此方法对数据库测试不是特别有用,因为<code>TestCase</code>基类会为您处理数据库拆卸。</p>
+</div>
+
+<p>下面我们有一些测试方法,它们使用 <code>Assert </code>函数来测试条件是真,假或相等(<code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code>)。如果条件评估不如预期,则测试将失败,并将错误报告给控制台。</p>
+
+<p><code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual </code>是 <strong>unittest </strong>提供的标准断言。框架中还有其他标准断言,还有 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#assertions">Django 特定的断言</a>,来测试视图是否重定向(<code>assertRedirects</code>),或测试是否已使用特定模板(<code>assertTemplateUsed</code>)等。</p>
+
+<div class="note">
+<p><strong>注意</strong>:您通常<strong>不应</strong>在测试中包含<strong>print()</strong> 函数,如上所示。我们这样做,只是为了让您可以看到在控制台中,调用设置功能的顺序(在下一节中)。</p>
+</div>
+
+<h2 id="如何运行测试">如何运行测试</h2>
+
+<p>要运行所有测试,最简单的方法,是使用以下命令:</p>
+
+<pre class="brush: bash">python3 manage.py test</pre>
+
+<p>这将查找当前目录下,使用模式 <strong>test*.py</strong> 命名的所有文件,并运行使用适当基类定义的所有测试(这里我们有许多测试文件,但只有 <strong>/catalog/tests/test_models.py</strong> 目前包含任何测试。)。默认情况下,测试将仅单独报告测试失败,然后是测试摘要。</p>
+
+<p> </p>
+
+<div class="note">
+<p>如果您收到类似于以下内容的错误:<code>ValueError: Missing staticfiles manifest entry ...</code> 这可能是因为默认情况下,测试不会运行 collectstatic,而您的应用程序正在使用需要它的储存类别(有关更多信息,请参阅 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict">manifest_strict</a>)。有许多方法可以解决这个问题 - 最简单的方法,是在运行测试之前,简单地运行collectstatic:</p>
+
+<pre class="brush: bash">python3 manage.py collectstatic
+</pre>
+</div>
+
+<p>在 LocalLibrary 的根目录中,运行测试。您应该看到如下所示的输出。</p>
+
+<pre class="brush: bash">&gt;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>在这里,我们看到有一个测试失败,我们可以确切地看到哪个函数失败了、为什么失败(这个失败是预期的,因为 <code>False</code>不是 <code>True</code>!)。</p>
+
+<div class="note">
+<p><strong>提示: </strong>从上面的测试输出中,学到的最重要事情是,如果为对象和方法使用描述性/信息性名称,它会更有价值。</p>
+</div>
+
+<p>上面以<strong>粗体</strong>显示的文本,通常不会出现在测试输出中(这是由我们的测试中的<code>print()</code>函数生成的)。这显示了如何为类调用<code>setUpTestData()</code>方法,并在每个方法之前调用<code>setUp()</code>。</p>
+
+<p>接下来的部分,将介绍如何运行特定测试,以及如何控制测试显示的信息量。</p>
+
+<h3 id="显示更多测试信息">显示更多测试信息</h3>
+
+<p>如果您想获得有关测试运行的更多信息,可以更改详细程度。例如,要列出测试成功和失败(以及有关如何设置测试数据库的大量信息),您可以将详细程度设置为 “2”,如下所示:</p>
+
+<pre class="brush: bash">python3 manage.py test --verbosity 2</pre>
+
+<p>允许的详细级别为 0, 1 ,2 和 3,默认值为 “1”。</p>
+
+<h3 id="运行特定测试">运行特定测试</h3>
+
+<p>如果要运行测试的子集,可以通过指定包,模块,<code>TestCase</code>子类或方法的完整路径​​(包含点)来执行此操作:</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_测试">LocalLibrary 测试</h2>
+
+<p>现在我们知道,如何运行我们的测试,以及我们需要测试哪些东西,让我们看一些实际的例子。</p>
+
+<div class="note">
+<p><strong>注意: </strong>我们不会编写所有可能的测试,但这应该可以让您了解测试的工作原理,以及您可以做些什么。</p>
+</div>
+
+<h3 id="模型">模型</h3>
+
+<p>如上所述,我们应该测试我们设计的任何内容,或由我们编写的代码定义的内容,而不是已经由 Django 或 Python 开发团队测试过的库/代码。</p>
+
+<p>例如,请考虑下面的作者模型<code> Author</code>。在这里,我们应该测试所有字段的标签,因为即使我们没有明确指定它们中的大部分,我们也有一个设计,说明这些值应该是什么。如果我们不测试值,那么我们不知道字段标签,是否具有其预期值。同样,虽然我们相信 Django 会创建一个指定长度的字段,但值得为这个长度指定一个测试,以确保它按计划实现。</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>打开我们的 <strong>/catalog/tests/test_models.py</strong>,并用 <code>Author</code>模型的以下测试代码,替换任何现有代码。</p>
+
+<p>在这里,您将看到我们首先导入 <code>TestCase</code>,并使用描述性名称,从中派生我们的测试类(<code>AuthorModelTest</code>),以便我们可以轻松识别测试输出中的任何失败测试。然后我们调用<code>setUpTestData()</code>,来创建一个我们将使用,但不在任何测试中修改的作者对象。</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>字段测试检查字段标签(<code>verbose_name</code>)的值,以及字符字段的大小,是否符合预期。这些方法都有描述性名称,并遵循相同的模式:</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>有趣的事情是:</p>
+
+<ul>
+ <li>我们无法使用 <code>author.first_name.verbose_name</code>直接获取 <code>verbose_name</code>,因为<code>author.first_name </code>是一个字符串(不是我们可以用来访问其属性的<code>first_name</code> 对象的句柄)。取而代之的是,我们需要使用作者的 <code>_meta</code>属性,来获取字段的实例,并使用它来查询其他信息。<br>
+    </li>
+ <li>我们选择使用 <code>assertEquals(field_label,'first name')</code> ,而不是<code>assertTrue(field_label == 'first name')</code>。这样做的原因是,如果测试失败,前者的输出,会告诉您标签实际上是什么,这使得调试问题变得更容易一些。</li>
+</ul>
+
+<div class="note">
+<p><strong>注意:</strong> 已省略对<code>last_name</code> 和 <code>date_of_birth</code>标签的测试,以及 <code>last_name</code>字段长度的测试。现在按照上面显示的命名约定和方法,添加您自己的版本。</p>
+</div>
+
+<p>我们还需要测试我们的自定义方法。这些基本上只是检查对象名称,是否按照我们的预期,使用“姓氏”,“名字”格式构建,并且我们为<code>Author</code>获取的 URL,是我们所期望的。</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>立即运行测试。如果您按照模型教程中的描述,创建了作者模型,则很可能会出现<code>date_of_death</code>标签的错误,如下所示。测试失败,是因为它写的是期望标签定义遵循 Django 的约定,即没有大写标签的第一个字母(Django 会为你做这个)。</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>这是一个非常小的错误,但它确实强调了,编写测试如何能够更彻底地检查,您可能做出的任何假设。</p>
+
+<div class="note">
+<p><strong>注意: </strong>将 date_of_death字段(/catalog/models.py)的标签更改为“death”并重新运行测试。</p>
+</div>
+
+<p>用于测试其他模型的模式,也类似于此,因此我们不会继续进一步讨论这些模式。请随意为其他模型,创建您自己的测试。</p>
+
+<h3 id="表单">表单</h3>
+
+<p>测试表单的理念,与测试模型的理念相同;您需要测试您编码、或设计指定的任何内容,但不测试底层框架,和其他第三方库的行为。</p>
+
+<p>通常,这意味着您应该测试表单,是否包含您想要的字段,并使用适当的标签和帮助文本,显示这些字段。您无需验证 Django 是否正确验证了字段类型(除非您创建了自己的自定义字段和验证) - 即您不需要测试电子邮件字段,是否只接受电子邮件。但是,您需要测试,您希望在字段上执行的任何其他验证,以及您的代码将为错误生成的任何消息。</p>
+
+<p>考虑我们更新书本的表格。这只有一个继续借阅的日期字段,它将包含我们需要验证的标签,和帮助文本。</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 &lt; datetime.date.today():
+ raise ValidationError(_('Invalid date - renewal in past'))
+ #Check date is in range librarian allowed to change (+4 weeks)
+ if data &gt; 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>打开我们的<strong> /catalog/tests/test_forms.py</strong> 文件,并用<code>RenewBookForm</code>表单的以下测试代码,替换任何现有代码。我们首先导入我们的表单,和一些 Python 和 Django 库,以帮助测试与时间相关的功能。然后,我们以与模型相同的方式,声明我们的表单测试类,使用我们的 <code>TestCase</code> 派生测试类的描述性名称。</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>前两个函数,测试字段的<code>label</code> 和 <code>help_text</code>,是否符合预期。我们必须使用字段字典访问该字段(例如<code>form.fields['renewal_date']</code>)。请注意,我们还必须测试标签值,是否为<code>None</code>,因为即使 Django 将呈现正确的标签,如果未明确设置该值,它也会返回<code>None</code>。</p>
+
+<p>其余函数,测试表单对于续借日期,在可接受范围内是否有效,对于范围外的值,是否无效。请注意我们如何使用<code>datetime.timedelta()</code>,在当前日期(<code>datetime.date.today()</code>)周围构建测试日期值(在这种情况下指定天数或周数)。然后我们只需创建表单,传入我们的数据,并测试它是否有效。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 这里我们实际上并没有使用数据库,或测试客户端。考虑修改这些测试,以使用<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.SimpleTestCase">SimpleTestCase</a>。</p>
+
+<p>如果表单无效,我们还需要验证是否引发了正确的错误,但这通常是作为视图处理的一部分完成的,因此我们将在下一节中处理。</p>
+</div>
+
+<p>这就是表单的全部;我们确实有其他一些的东西,但它们是由基于类的通用编辑视图自动创建的,应该在那里进行测试!运行测试,并确认我们的代码仍然通过!</p>
+
+<h3 id="视图">视图</h3>
+
+<p>为了验证我们的视图行为,我们使用 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">Django 的测试客户端</a>。这个类,就像一个虚拟的Web浏览器,我们可以使用它,来模拟URL上的<code>GET</code>和<code>POST</code>请求,并观察响应。我们几乎可以看到,关于响应的所有内容,从低层级的 HTTP(结果标头和状态代码),到我们用来呈现HTML的模板,以及我们传递给它的上下文数据。我们还可以看到重定向链(如果有的话),并在每一步检查URL,和状态代码。这允许我们验证每个视图,是否正在执行预期的操作。</p>
+
+<p>让我们从最简单的视图开始,它提供了所有作者的列表。它显示在 URL <strong>/catalog/authors/</strong> 当中(URL 配置中,名为 “authors” 的 URL)。</p>
+
+<pre class="brush: python">class AuthorListView(generic.ListView):
+ model = Author
+ paginate_by = 10
+</pre>
+
+<p>由于这是一个通用列表视图,几乎所有内容,都由 Django 为我们完成。可以说,如果您信任 Django,那么您唯一需要测试的,是视图可以通过正确的 URL 访问,并且可以使用其名称进行访问。但是,如果您使用的是测试驱动的开发过程,则首先编写测试,确认视图显示所有作者,并将其分成10个。</p>
+
+<p>打开 <strong>/catalog/tests/test_views.py</strong> 文件,并用<code>AuthorListView</code>的以下测试代码,替换任何现有文本。和以前一样,我们导入模型,和一些有用的类。在<code>setUpTestData()</code>方法中,我们设置了许多<code>Author</code>对象,以便我们可以测试我们的分页。</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>所有测试,都使用客户端(属于我们的<code>TestCase</code>的派生类)来模拟<code>GET</code>请求,并获得响应(<code>resp</code>)。第一个版本检查特定 URL(注意,只是没有域名的特定路径),而第二个版本从 URL配置中的名称生成 URL。</p>
+
+<pre class="brush: python">resp = self.client.get('/catalog/authors/')
+resp = self.client.get(reverse('authors'))
+</pre>
+
+<p>获得响应后,我们会查询其状态代码,使用的模板,响应是否已分页,返回的项目数以及项目总数。</p>
+
+<p><br>
+ 我们在上面演示的最有趣的变量是<code>resp.context</code>,它是视图传递给模板的上下文变量。这对测试非常有用,因为它允许我们确认模板正在获取所需的所有数据。换句话说,我们可以检查是否正在使用预期的模板,以及模板获得的数据,这对于验证任何渲染问题,是否真的仅仅归因于模板有很大帮助。</p>
+
+<h4 id="仅限登录用户的视图">仅限登录用户的视图</h4>
+
+<p>在某些情况下,您需要测试仅限登录用户的视图。例如,我们的<code>LoanedBooksByUserListView</code>与我们之前的视图非常相似,但仅供登录用户使用,并且仅显示当前用户借用的<code>BookInstance</code>记录,具有出借中“on loan”状态,并且排序方式为“旧的优先”。</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>将以下测试代码,添加到 <strong>/catalog/tests/test_views.py</strong>。这里我们首先使用<code>SetUp()</code>创建一些用户登录帐户,和<code>BookInstance</code>对象(以及它们的相关书本,和其他记录),我们稍后将在测试中使用它们。每个测试用户都借用了一半的书本,但我们最初,将所有书本的状态设置为“维护”。我们使用了<code>SetUp()</code>而不是<code>setUpTestData()</code>,因为我们稍后会修改其中的一些对象。</p>
+
+<p> </p>
+
+<div class="note">
+<p><strong>注意:</strong> 下面的<code>setUp()</code>代码,会创建一个具有指定语言<code>Language</code>的书本,但您的代码可能不包含语言模型<code>Language</code>,因为它是作为挑战创建的。如果是这种情况,只需注释掉创建或导入语言对象的代码部分。您还应该在随后的<code>RenewBookInstancesViewTest</code>部分中,执行此操作。</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>要验证如果用户未登录,视图将重定向到登录页面,我们使用<code>assertRedirects</code>,如<code>test_redirect_if_not_logged_in()</code>中所示。要验证是否已为登录用户显示该页面,我们首先登录我们的测试用户,然后再次访问该页面,并检查我们获得的<code>status_code</code>为200(成功)。</p>
+
+<p>测试的其余部分,验证我们的观点,仅返回借给当前借用人的书本。复制上面测试类末尾的(自解释)代码。</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 &lt;= copy.due_back)</pre>
+
+<p>你也可以添加分页测试,如果你愿意的话!</p>
+
+<h4 id="使用表单测试视图">使用表单测试视图</h4>
+
+<p>使用表单测试视图,比上面的情况稍微复杂一些,因为您需要测试更多代码路径:初始显示,数据验证失败后显示,以及验证成功后显示。好消息是,我们使用客户端进行测试的方式,与我们对仅显示视图的方式,几乎完全相同。</p>
+
+<p>为了演示,让我们为用于续借书本的视图,编写一些测试(<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>我们需要测试该视图,仅供具有<code>can_mark_returned</code>权限的用户使用,并且如果用户尝试续借不存在的<code>BookInstance</code>,则会将用户重定向到HTTP 404错误页面。我们应该检查表单的初始值,是否以未来三周的日期为参考值,如果验证成功,我们将被重定向到 “所有借阅的书本” 视图。作为验证 - 失败测试的一部分,我们还将检查我们的表单,是否发送了相应的错误消息。</p>
+
+<p>将测试类的第一部分(如下所示),添加到 <strong>/catalog/tests/test_views.py</strong> 的底部。这将创建两个用户和两个书本实例,但只为一个用户提供访问该视图所需的权限。在测试期间,授予权限的代码以<strong>粗体</strong>显示:</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>将以下测试添加到测试类的底部。这些检查只有具有正确权限的用户(testuser2)才能访问该视图。我们检查所有情况:当用户没有登录时、当用户登录但没有正确的权限,当用户有权限但不是借用人(应该成功),以及当他们尝试访问不存在的<code>BookInstance</code>,会发生什么。我们还检查是否使用了正确的模板。</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>添加下一个测试方法,如下所示。这将检查表单的初始日期,是将来三周。请注意我们如何能够访问表单字段的初始值内的值(以<strong>粗体</strong>显示)。</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>下一个测试(将其添加到类中)会检查如果续借成功,视图会重定向到所有借书的列表。这里的不同之处在于,我们首次展示了,如何使用客户端发布(<code>POST</code>)数据。 post数据是post函数的第二个参数,并被指定为键/值的字典。</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><strong>重要</strong>:全部借用的视图作为额外挑战,您的代码可能会改为重定向到主页'/'。如果是这样,请将测试代码的最后两行,修改为与下面的代码类似。请求中的<code>follow=True</code>,确保请求返回最终目标URL(因此检查<code>/catalog/</code>而不是<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>将最后两个函数,复制到类中,如下所示。这些再次测试<code>POST</code>请求,但在这种情况下具有无效的续借日期。我们使用<code>assertFormError() </code>,来验证错误消息是否符合预期。</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>可以使用相似的技术,来测试其他视图。</p>
+
+<h3 id="模板">模板</h3>
+
+<p>Django 提供测试 API 来检查您的视图,是否正在调用正确的模板,并允许您验证,是否正在发送正确的信息。但是,没有特定的 API,支持在 Django中测试 HTML输出,是否按预期呈现。</p>
+
+<h2 id="其他推荐的测试工具">其他推荐的测试工具</h2>
+
+<p>Django 的测试框架,可以帮助您编写有效的单元和集成测试 - 我们只涉及底层单元测试框架<strong>unittest</strong>可以做什么,而不去谈 Django 的其他部分(例如,查看如何使用<a href="https://docs.python.org/3.5/library/unittest.mock-examples.html">unittest.mock</a> 修补第三方库,以便您可以更彻底地测试自己的代码)。</p>
+
+<p>虽然您可以使用许多其他测试工具,但我们只重点介绍两个:</p>
+
+<ul>
+ <li><a href="http://coverage.readthedocs.io/en/latest/">Coverage</a>: 此Python工具报告您的测试,实际执行了多少代码。当开始使用时,你正试图找出你应该测试的确切内容,它会特别有用。</li>
+ <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> 是一个在真实浏览器中,自动化测试的框架。它允许您模拟与站点交互的真实用户,并为系统测试您的站点,提供了一个很好的框架(从集成测试开始的下一步)。</li>
+</ul>
+
+<h2 id="挑战自己">挑战自己</h2>
+
+<p>有许多模型与视图,我们可以用来测试。比如一个简单的任务,试着为<code>AuthorCreate</code>视图,创造一个测试案例。</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>请记住,您需要检查您指定的任何内容、或设计的一部分。这将包括谁有权访问,初始日期,使用的模板,以及视图在成功时,重定向的位置。</p>
+
+<h2 id="总结">总结</h2>
+
+<p>撰写测试代码既不有趣也不吸引人,因此在创造一个网站时,经常被留到最后才处理(或者完全不处理)。然而,它是一个基础的部分,以保证你的程式码,在更改之后是安全、可发布的,并且维护起来不会花费太多成本。</p>
+
+<p>本教程中,我們演示了如何为模型、表单和视图,编写并运行测试。最重要的是,我们已经提供给您,应该测试的内容的简短摘要,这通常是您开始时,最难解决的问题。还有很多东西要知道,但即使你已经学到了什么,你也应该能够为你的网站创建有效的单元测试。</p>
+
+<p>下一个、也是最后一个教程,展示了如何部署精彩的(并经过全面测试的!)Django网站。</p>
+
+<h2 id="也可以参考">也可以参考</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 &gt; 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="本系列教程">本系列教程</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-cn/learn/server-side/django/tutorial_local_library_website/index.html b/files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html
new file mode 100644
index 0000000000..5b2a54fc9e
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html
@@ -0,0 +1,70 @@
+---
+title: 'Django Tutorial: The Local Library website'
+slug: learn/Server-side/Django/Tutorial_local_library_website
+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>阅读 <a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django介绍</a>。在接下来的文章里你需要 <a href="/en-US/docs/Learn/Server-side/Django/development_environment">创建Django开发环境</a>. </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><em>本地图书馆</em>是我们在本系列教程里创建和不断改善的网站。跟你期望的一样,这个网站的目标是为一个小型的图书馆提供一个在线的目录。在这个小型图书管里,用户能浏览书籍和管理他们的账户。</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>里找到网站的完整代码。I</p>
+
+<h2 id="总结">总结</h2>
+
+<p>现在你对本地图书馆网站有了一些了解并知道你会学到什么。是时候创建我们例子会用到的<a href="/en-US/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>
diff --git a/files/zh-cn/learn/server-side/django/web_application_security/index.html b/files/zh-cn/learn/server-side/django/web_application_security/index.html
new file mode 100644
index 0000000000..fa0664bb33
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/web_application_security/index.html
@@ -0,0 +1,180 @@
+---
+title: Django Web应用安全
+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">保<font><font>护用户数据是任何网站设计的重要部分。</font><font>我们之前在文章<a href="https://developer.mozilla.org/en-US/docs/Web/Security">web安全</a>中</font></font><font><font>解释了一些更常见的安全威胁--</font><font>本文提供了Django的内置保护如何处理这些威胁的实际演示</font></font>。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前提:</th>
+ <td>阅读服务器端网页编程中的 "<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/First_steps/Website_security">Website security</a>" 主题。并请至少完成Django Web框架教程 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: 使用表单</a> 及以前的教程。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>理解保障Django网站安全应该(和不应该)做的事情。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概述">概述</h2>
+
+<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security">web安全</a><a href="https://developer.mozilla.org/en-US/docs/Web/Security"> </a>主題提供一个概述,说明了网站安全对于服务器端设计的意义,以及以及一些需要应对的常见威胁。本文中包含一个关键的概念:如果网站信任任何来自浏览器的数据,几乎所有的攻击方法都会成功。</p>
+
+<div class="warning">
+<p><strong>重要提示:</strong> 切记,对于网站安全来说最重要一点就是<strong>“永远不要相信浏览器端提交的数据”</strong>。 这些数据包括使用<code>GET</code>方式请求时URL中的参数,<code>POST</code> 方式请求的数据,HTTP headers 和 cookies,以及用户上传的文件等等. 请确保一定要检查和清洗这些提交的数据。对于网站安全来说,总是要做好最坏的打算。</p>
+</div>
+
+<p>对Django用户来说,好消息是Django框架已经处理了大量的常见威胁。请阅读Django官方文档中的"<a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a>"部分来了解Django的安全细节,以及如何确保基于Django的网站的安全。</p>
+
+<h2 id="常见威胁及保护">常见威胁及保护</h2>
+
+<p>在本文中,我们将使用前面章节中的“<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Tutorial_local_library_website">本地图书馆</a>”项目作为示范来演示一些Django的安全特性。</p>
+
+<h3 id="跨站脚本_(XSS)">跨站脚本 (XSS)</h3>
+
+<p>XSS(英语:Cross site scripting,通常简称:XSS)是指一类恶意攻击者将代码通过网站注入到其他用户浏览器中的攻击方式。一般攻击者会把恶意代码作为普通数据放入到网站数据库中,这样其他用户在获取和展示数据的过程中就会受到攻击。此外,攻击者还可以通过引诱用户点击某些链接来执行恶意的JavaScript代码。</p>
+
+<p>Django的模板系统可以帮您抵挡大部分的XSS攻击,实现的方式在于转义对于HTML来说比较<strong>“危险”</strong>的特殊字符(可参考官方文档:<a href="https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping">escaping specific characters</a>)。现在,我们用<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a> 这一章中的“创建作者”表单来做个演示,尝试向我们的本地图书馆网站注入一些JavaScript脚本。</p>
+
+<ol>
+ <li>使用开发服务器启动网站(参考命令:<code>python3 manage.py runserver</code>)。</li>
+ <li>在浏览器中打开网站,并用超级用户身份登录。</li>
+ <li>进入创建作者页面 (地址可能会是:<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>输入姓名、生日等信息,随后在Last Name这个字段里面填入以下的内容:<br>
+ <code>&lt;script&gt;alert('Test alert');&lt;/script&gt;</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>注意:</strong> 这一段代码并没有任何杀伤力,在执行的时候只会在浏览器中弹出一个警告提示框。如果这个警告提示框出现,则表明本网站存在可被XSS攻击的漏洞。</p>
+ </div>
+ </li>
+ <li>点击 <strong>Submit</strong> 按钮保存信息。</li>
+ <li>保存后的作者信息将会显示为下图的样式。因为XSS防护措施的存在,注入代码中的<code>alert()</code>部分并没有执行,而只是用文本的方式直接显示了出来。<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>如果你有兴趣阅读下页面的HTML源码,则会发现危险的字符已被转义成了无害的字符(例如: <code>&gt;</code> 被转义为了 <code>&amp;gt;</code> )</p>
+
+<pre class="brush: html">&lt;h1&gt;Author: Boon&amp;lt;script&amp;gt;alert(&amp;#39;Test alert&amp;#39;);&amp;lt;/script&amp;gt;, David (Boonie) &lt;/h1&gt;
+</pre>
+
+<p>Django的模板系统可以帮助抵御大部分的XSS攻击。当然,XSS保护功能也可以被关闭,而且XSS保护一般对非用户输入的内容不会自动进行防护(例如表单中字段的<code>help_text</code>通常不会是用户提交的,所以这部分数据Django也不会进行转义)</p>
+
+<p>XSS攻击也可能来自于其他不可信的数据来源,例如cookies,Web服务或上传的文件(实际上只要是未经清洗的数据直接展示出来都会有被攻击的可能)。如果你要显示这些不可信来源的数据,切记一定要自己做好数据清洗的工作。</p>
+
+<h3 id="防护跨站请求伪造_(CSRF)">防护跨站请求伪造 (CSRF) </h3>
+
+<p>CSRF(英语:Cross-site request forgery,通常简称:CSRF或XSRF)攻击可以让恶意攻击者在用户不知情的情况下,使用用户的身份来进行系统操作。举个例子,现在有一名黑客想要在我们的本地图书馆中添加一些作者信息。</p>
+
+<div class="note">
+<p><strong>注意:这个示例里面的黑客没有考虑对钱下手。而现实生活中的黑客则极有可能会产生更加危险的操作(例如,把钱转入他们自己的账户中等等)。</strong></p>
+</div>
+
+<p>为了实现这个目的,黑客可以创建一个类似于下面示例的HTML文件,这个文件包含了一个创建作者的表单(类似我们在之前章节中用过的),并且一旦加载完毕就会立即进行提交。随后黑客可以将这个文件发送至所有的图书管理员,并且引诱他们打开这个文件(文件中真的没有啥有害的信息)。如果任何一个已登录的图书管理员不慎打开了这个文件,那么文件中的表单就会利用图书管理员的身份来提交,随后就会创建出一个新的作者来。</p>
+
+<pre class="brush: html">&lt;html&gt;
+&lt;body onload='document.EvilForm.submit()'&gt;
+
+&lt;form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name='EvilForm'&gt;
+ &lt;table&gt;
+ &lt;tr&gt;&lt;th&gt;&lt;label for="id_first_name"&gt;First name:&lt;/label&gt;&lt;/th&gt;&lt;td&gt;&lt;input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /&gt;&lt;/td&gt;&lt;/tr&gt;
+ &lt;tr&gt;&lt;th&gt;&lt;label for="id_last_name"&gt;Last name:&lt;/label&gt;&lt;/th&gt;&lt;td&gt;&lt;input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /&gt;&lt;/td&gt;&lt;/tr&gt;
+ &lt;tr&gt;&lt;th&gt;&lt;label for="id_date_of_birth"&gt;Date of birth:&lt;/label&gt;&lt;/th&gt;&lt;td&gt;&lt;input id="id_date_of_birth" name="date_of_birth" type="text" /&gt;&lt;/td&gt;&lt;/tr&gt;
+ &lt;tr&gt;&lt;th&gt;&lt;label for="id_date_of_death"&gt;Died:&lt;/label&gt;&lt;/th&gt;&lt;td&gt;&lt;input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /&gt;&lt;/td&gt;&lt;/tr&gt;
+ &lt;/table&gt;
+ &lt;input type="submit" value="Submit" /&gt;
+&lt;/form&gt;
+
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+<p>运行Django开发服务器,然后使用超级管理员账号进行登录。将上面的代码贴到一个文件中,并在浏览器中打开这个文件,随后你就会看到一个CSRF错误,这是因为Django的安全机制防护了此类的攻击。</p>
+
+<p>在表单定义的时候加入 <code>{% csrf_token %} </code>这个模板标签, CSRF保护功能即可启用。在模板渲染的时候,这个token在 HTML代码中将会按照下面的格式显示,包含了一个与当前用户和当前浏览器关联的值。</p>
+
+<pre class="brush: html">&lt;input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' /&gt;
+</pre>
+
+<p>Django生成这个用户/浏览器关联key的目的在于可以据此来拒绝那些不包含这个key的表单请求,也可以拒绝那些包含了错误了用户/浏览器关联key的表单请求。</p>
+
+<p>有了这种保护机制后,攻击者要发起攻击就需要找到目标用户的CSRF key。通过广撒网给所有的图书管理员发送恶意代码文件的方式也很难奏效,因为CSRF key是和浏览器相关联的。</p>
+
+<p>Django的CSRF防御默认是开启的。一定要在表单的位置使用 <code>{% csrf_token %}</code>这个标签,同时,切记使用<code>POST</code>方式来发起新增和更新数据的请求。</p>
+
+<h3 id="其他防护措施">其他防护措施</h3>
+
+<p>Django还提供了很多其他形式的防护措施 (大部分不是很容易进行演示):</p>
+
+<dl>
+ <dt>SQL注入防护</dt>
+ <dd>SQL注入漏洞可以让攻击者直接对网站数据库执行构造好的SQL语句,在无需用户权限的情况下即可实现对数据的访问、修改甚至是删除。绝大多数的情况下,使用Django的查询集/模型直接进行数据库访问时,实际使用的SQL语句已经被底层的数据库驱动妥善地进行了转义。如果必须要直接执行自定义的SQL语句,那么也请一定要注意防范SQL注入的问题。</dd>
+ <dt>点击劫持防护</dt>
+ <dd>点击劫持是指攻击者通过诱导用户,用户本意要访问A网站,最终却访问到了B网站。举例说明,攻击者可以给用户显示一个合法的银行网站,同时把用户名密码登录框改为不可见的<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe">&lt;iframe&gt; </a>标签,以此来窃取用户的登录信息。Django通过 <code><a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#django.middleware.clickjacking.XFrameOptionsMiddleware" title="django.middleware.clickjacking.XFrameOptionsMiddleware">X-Frame-Options</a></code>中间件来防御点击劫持攻击,在支持的浏览器中,这种方式可以避免网站在iframe中显示。</dd>
+ <dt>强制SSL/HTTPS</dt>
+ <dd>web服务器可通过启用SSL/HTTPS来加密网站和浏览器之间的所有通信流量,包括了身份认证及其他通过纯文本方式来发送的数据流量(强烈建议启用HTTPS)。如果HTTPS已启用,Django还提供了一起实用的保护措施:</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> 设置可以用于检查内容是否安全,可用于代理和Django之间使用非HTTPS方式通讯的情况下。</li>
+ <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT"><code>SECURE_SSL_REDIRECT</code></a> 可以将所有HTTP的请求重定向到HTTPS。</li>
+ <li>使用 <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#http-strict-transport-security">HTTP Strict Transport Security</a> (HSTS) 头来通知浏览器未来与此网站的连接仅使用HTTPS。与HTTP连接重定向至HTTPS的配置相结合后,HSTS可以确保之后的连接强制使用HTTPS。HSTS还有 <code><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS">SECURE_HSTS_SECOND</a></code>和 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> 等选项可以进行配置。</li>
+ <li>将 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE"><code>SESSION_COOKIE_SECURE</code></a> 和 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE"><code>CSRF_COOKIE_SECURE</code></a> 设置为 <code>True</code>。这些配置将确保session和csrf的cookie仅使用HTTPS连接来发送。</li>
+</ul>
+
+<dl>
+ <dt>Host头校验</dt>
+ <dd>使用 <code><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-ALLOWED_HOSTS">ALLOWED_HOSTS</a></code> 配置仅接受由信任的host发起的请求。</dd>
+</dl>
+
+<p>还有很多其他的安全措施及使用这些安全措施的注意事项我们没有提到。我们这里仅仅提供了Django安全措施的一个概览,更多的信息请参阅Django官方安全文档。</p>
+
+<ul>
+</ul>
+
+<h2 id="总结">总结</h2>
+
+<p>Django具备有效的防护措施,以对抗一些常見的威胁,包括 XSS 和 CSRF 攻击。本文中,我们已经使用本地图书馆网站来了演示Django如何处理一些特定的攻击。我们也提供了关于其它保护措施的简单概述。</p>
+
+<p>但这仅仅是对网站安全的一个入门。我们强烈建议您阅读 <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Django中的安全</a> 以获得更加深入的理解。</p>
+
+<p>本Django教程的下一步,也是最后一步,是完成 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">评估任务</a>。</p>
+
+<h2 id="参阅">参阅</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> (Django官方文档)</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-cn/learn/server-side/django/主页构建/index.html b/files/zh-cn/learn/server-side/django/主页构建/index.html
new file mode 100644
index 0000000000..0527ba8731
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/主页构建/index.html
@@ -0,0 +1,358 @@
+---
+title: 'Django Tutorial Part 5: 主页构建'
+slug: learn/Server-side/Django/主页构建
+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="/en-US/docs/Learn/Server-side/Django/Introduction">Django Introduction</a>. 完成上章节 (including <a href="/zh-CN/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中)以及如何从模型中获取数据并创建模版。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概要">概要</h2>
+
+<p>现在我们已经定义了我们的模型,并创建了一些初始库记录来处理,现在是编写代码以向用户呈现该信息的时候了。我们需要做的第一件事是确定我们希望能够在我们的页面中显示哪些信息,然后为返回这些资源定义适当的URL。那么我们将需要创建一个url映射器,视图和模板来显示这些页面。</p>
+
+<p>以下图表提供了处理HTTP请求/响应时需要实现的数据和事情的主要流程。我们已经创建了这个模型,我们需要创建的主要内容是:</p>
+
+<ul>
+ <li>URL映射-根据-支持的URL(以及任何编码在URL里的信息)跳转到相应的<strong>View</strong>功能函数。</li>
+ <li><strong>View</strong> 函数从模型中获取请求的数据,创建一个显示数据的HTML页面,并将其返回给用户在浏览器查看。</li>
+ <li><strong>Templates</strong> 在View视图中进行数据渲染的时候使用。</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>&lt;id&gt;</em></code> — 主键字段 ID的具体书(默认) —详细视图。如下例子 <code>/catalog/book/3</code>,第三本书。</li>
+ <li><code>catalog/author/<em>&lt;id&gt;</em></code><em> </em>— 主键字段 ID的具体作者(默认) —详细视图。如下例子 <code>/catalog/author/11</code>,第11个作者。</li>
+</ul>
+
+<p>前三个URL用于列出索引,书籍和作者。这些不会对任何附加信息进行编码,而返回的结果将取决于数据库中的内容,运行获取信息的查询将始终保持一致。</p>
+
+<p>相比之下,最后两个URL用于显示有关特定书籍或作者的详细信息 - 这些URL将编码要显示在URL中的项目的标识(如上所示&lt;id&gt;)。URL映射器可以提取编码信息并将其传递给视图,然后将动态地确定从数据库获取哪些信息。通过对我们的URL中的信息进行编码,我们只需要一个URL映射,视图和模板来处理每本书(或作者)。</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允许您以任何您喜欢的方式构建您的URL - 您可以如上所示编码URL正文中的信息,或使用URL<span> </span></font></font><code style='margin: 0px; padding: 0px; border: 0px; font-style: normal; font-weight: normal; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: 18px; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212); text-decoration-style: initial;'>GET</code><font><font>参数(例如 <span> </span></font></font><code style='margin: 0px; padding: 0px; border: 0px; font-style: normal; font-weight: normal; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: 18px; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212); text-decoration-style: initial;'>/book/?id=6</code><font><font>)。</font><font>无论您使用哪种方法,URL都应保持清洁,逻辑和可读性</font></font> (<a href="https://www.w3.org/Provider/Style/URI">check out the W3C advice here</a>).<br>
+ <br>
+ <font><font>Django文档倾向于在URL的主体中推荐编码信息,这是他们觉得鼓励更好的URL设计的实践。</font></font></p>
+</div>
+
+<p>如概述,本文其余部分介绍如何构建索引页</p>
+
+<h2 id="创建索引页">创建索引页</h2>
+
+<p>我们创建的第一个页面将会是索引页(catalog/)。这会显示一些静态HTML,以及数据库中不同记录的一些计算的“计数“。为了使其工作,我们必须创建一个URL映射,视图和模版。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 本节应该特别注意。一些”材料“在所有页面都通用。</p>
+</div>
+
+<h3 id="URL_映射">URL 映射</h3>
+
+<p>在我们创建的<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">基础网站</a>上,更新 <strong>/locallibrary/urls.py</strong> 文件。以确保每当收到以<code><strong>catalog/</strong></code>开头的URL时,URLConf模块中的<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><strong>catalog.urls</strong></span></font> 会处理剩余的字符串。</p>
+
+<p>打开 catalog/<strong>urls.py</strong> ,复制下面代码</p>
+
+<pre class="brush: python">urlpatterns = [
+<strong> path('', views.index, name='index'),</strong>
+]</pre>
+
+<p>如果检测到URL模式'',(views.index——在view.py中函数命名index() )将被调用。URL模式是<a href="https://docs.python.org/3/library/re.html">Python 正则表达式</a> (RE)。我们将在本教程中进一步介绍RE。</p>
+
+<div class="note">
+<p><strong>注意: </strong>在  <strong>/locallibrary/locallibrary/urls.py</strong> </p>
+
+<pre><code>urlpatterns += [
+    path('catalog/', include('catalog.urls')),
+]</code></pre>
+
+<p>每当Django 使用 include() (<a href="https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.include" title="django.conf.urls.include"><code>django.conf.urls.include()),</code></a><code>它排除与该点 匹配URL的任何部分,并将剩余的字符串发送到随附的 URLconf 进行一步处理。</code></p>
+
+<p>匹配的URL 实际上是 <code>catalog/</code>+&lt;空字符串&gt; (<code>/catalog/</code> 假定是因为 <code>include()</code>是使用的方法)。如果我们收到一个URL的HTTP请求,我们的第一个视图函数将被调用<code>/catalog/。</code></p>
+</div>
+
+<p>此函数还说明了一个<code>name</code>参数,此唯一标识指定 URL 映射。你可以使用 "reverse" 映射—去动态创建指定映射设计处理的资源的一个URL。例如,我们现在可以通过在我们的模版中创建以下链接到我们的主页:</p>
+
+<pre class="brush: html">&lt;a href="<strong>{% url 'index' %}</strong>"&gt;Home&lt;/a&gt;.</pre>
+
+<div class="note">
+<p><strong>注意</strong>: 我们当然可以硬编码上面的链接(如:<code>&lt;a href="<strong>/catalog/</strong>"&gt;Home&lt;/a&gt;</code>),但是如果我们改变了主页的模式,模版将不再正确链接,使用反向网址映射会更灵活和强大。</p>
+</div>
+
+<h3 id="View_基于功能">View (基于功能)</h3>
+
+<p>视图是处理HTTP请求的功能,根据需要从数据库获取数据,通过使用HTML模板呈现此数据生成HTML页面,然后以HTTP响应返回HTML以显示给用户。索引视图遵循此模型 - 它提取有关数据库中有多少<code>Book</code>,<code>BookInstance </code>可用 <code>BookInstance</code> 和<code> Author</code> 记录的信息,并将其传递给模板以进行显示。</p>
+
+<p>打开catalog / views.py,并注意该文件已经导入了 使用模板和数据生成HTML文件的 <a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">render()</a> 快捷方式函数。</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()
+ num_authors=Author.objects.count() # The 'all()' is implied by default.
+
+ # Render the HTML template index.html with the data in the context variable
+ return render(
+ request,
+ 'index.html',
+ context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
+ )</pre>
+
+<p>视图函数的第一部分使用<code>objects.all()</code>模型类的属性来获取记录计数。它还会获取一个<code>BookInstance</code>状态字段值为“a”(可用)的对象列表。您可以在前面的教程 (<a href="/en-US/docs/Learn/Server-side/Django/Models#Searching_for_records">Django Tutorial Part 3: Using models &gt; Searching for records</a>)中找到更多关于如何访问模型的信息。</p>
+
+<p>在函数结束时,我们将该函数称为<code>render()</code>创建和返回HTML页面作为响应(此快捷方式函数包含许多其他函数,简化了这种非常常见的用例)。它将原始<code>request</code>对象(an HttpRequest)作为参数,具有数据占位符的HTML模板以及<code>context</code>变量(包含要插入到这些占位符中的数据的Python字典)。</p>
+
+<p>我们将在下一节中详细介绍模板和上下文变量; 让我们创建我们的模板,以便我们可以向用户显示一些内容</p>
+
+<h3 id="模版">模版</h3>
+
+<p>模版是定义一个文件(例如HTML页面)的结构与布局的文本文件,其中占位符用于表示实际内容。Django将自动在应用程序“templates”目录查找模版。所以例如,在我们刚刚加的索引页,<code>render()</code> 函数会期望能够找到<strong>/locallibrary/catalog/templates/<em>index.html</em></strong>这个文件,如何找不到该文件,则会引发错误。如果保存以前的更改并返回到浏览器,你可以看到访问 <code><a href="127.0.0.1:8000">127.0.0.1:8000</a> 现在将提供你一个相当直观的错误信息</code>"<strong>TemplateDoesNotExist at /catalog/</strong>“以及其他详细信息。</p>
+
+<div class="note">
+<p><strong>注意</strong>: Django 将根据你的项目的设置文件, 来查看模版的许多位置 (在已安装的应用程序中进行搜索是默认设置). 你可以查阅更多关于Django如何找到模版以及它支持的模版格式在<a href="https://docs.djangoproject.com/en/1.10/topics/templates/">(Templates</a> )。</p>
+</div>
+
+<h4 id="扩展模版">扩展模版</h4>
+
+<p>索引模版将需要标准的HTML标记头部和正文,以及用于导航的部分(去我们尚为创建的网站其他的页面)以及显示一些介绍文本和我们书籍数据。我们网站上的每一页,大部分文字(HTML和导航结构)都是一样的。Django模版语言不是强制开发人员在每个页面中复制这个“样板”,而是让你声明一个基本模版,然后再扩展它,仅替换每个特定页面不同的位置。</p>
+
+<p>例如,基本模版 <code>base_generic.html</code> 可能看起来像下面的文本。正如你所见的,它包含一些“常见“HTML”和标题,侧边栏和使用命名 <code>block</code> 和 <code>endblock</code> 模版标记(粗体显示)标记的内容部分。块可以是空的,或者包含将被派生页“默认使用”的内容。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 模版标签就像你可以在模版中使用的函数循环列表,基于变量的值执行条件操作等。除了模版标签,模版语法允许你引用模版变量(通过从视图进入模版),并使用模版过滤器,其中重新格式化变量(例如,将字符串设置为小写)。</p>
+</div>
+
+<pre class="brush: html">&lt;!DOCTYPE html&gt;
+&lt;html lang="en"&gt;
+&lt;head&gt;
+ <strong>{% block title %}</strong>&lt;title&gt;Local Library&lt;/title&gt;<strong>{% endblock %}</strong>
+&lt;/head&gt;
+
+&lt;body&gt;
+ <strong>{% block sidebar %}</strong>&lt;!-- insert default navigation text for every page --&gt;<strong>{% endblock %}</strong>
+ <strong>{% block content %}</strong>&lt;!-- default content text (typically empty) --&gt;<strong>{% endblock %}</strong>
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+<p>当我们要为特定视图定义一个模版时,我们首先指定基本模版(使用 <code>extends</code> 模版标签—查看下一个代码片段)。如果我们想要在模版中替换的章节,会使用相同的 <code>block/endblock </code>部分在基本模版表明。</p>
+
+<p>例如,下面我们使用 <code>extends</code> 模版标签,并覆盖 <code>content</code> 块。生成的最终HTML页面将具有基本模版中定义的所以HTML和结构(包括你在<code>title</code>块中定义的默认内容),但你新的 <code>content</code> 块插入到了默认的那块。</p>
+
+<p><code>base_generic.html</code> 详细会在下文中,请耐心往下看。</p>
+
+<pre class="brush: html">{% extends "base_generic.html" %}
+
+{% block content %}
+&lt;h1&gt;Local Library Home&lt;/h1&gt;
+&lt;p&gt;Welcome to &lt;em&gt;LocalLibrary&lt;/em&gt;, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.&lt;/p&gt;
+{% endblock %}</pre>
+
+<h4 id="本地图书馆-基本模版">本地图书馆-基本模版</h4>
+
+<p>下面就是我们计划的基本模版用于本地图书馆网站。正如所看到的,内容包括一些HTML和定义块 <code>title</code> ,<code>sidebar</code> 和 <code>content</code>。我们有默认的 <code>title</code>(当然我们可以改)和默认的所以书籍和作者的链接列表 <code>sidebar</code> (我们可能并不会怎么改,但需要时,我们通过把想法放入块<code>block</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">&lt;!DOCTYPE html&gt;
+&lt;html lang="en"&gt;
+&lt;head&gt;
+
+ {% block title %}&lt;title&gt;Local Library&lt;/title&gt;{% endblock %}
+ &lt;meta charset="utf-8"&gt;
+ &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
+ &lt;link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"&gt;
+ &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"&gt;&lt;/script&gt;
+ &lt;script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"&gt;&lt;/script&gt;
+
+ &lt;!-- Add additional CSS in static file --&gt;
+ {% load static %}
+ &lt;link rel="stylesheet" href="{% static 'css/styles.css' %}"&gt;
+&lt;/head&gt;
+
+&lt;body&gt;
+
+ &lt;div class="container-fluid"&gt;
+
+ &lt;div class="row"&gt;
+ &lt;div class="col-sm-2"&gt;
+ {% block sidebar %}
+ &lt;ul class="sidebar-nav"&gt;
+ &lt;li&gt;&lt;a href="{% url 'index' %}"&gt;Home&lt;/a&gt;&lt;/li&gt;
+ &lt;li&gt;&lt;a href=""&gt;All books&lt;/a&gt;&lt;/li&gt;
+ &lt;li&gt;&lt;a href=""&gt;All authors&lt;/a&gt;&lt;/li&gt;
+ &lt;/ul&gt;
+ {% endblock %}
+ &lt;/div&gt;
+ &lt;div class="col-sm-10 "&gt;
+ {% block content %}{% endblock %}
+ &lt;/div&gt;
+ &lt;/div&gt;
+
+ &lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>该模版使用(并包含)JavaScript 和  <a href="http://getbootstrap.com/">Bootstrap  </a>(css框架)来改进HTML页面的布局和显示,这个框架或者另一个客户端网络框架,这是快速创建一个可用页面来适应在不同浏览器尺寸和允许我们处理页面呈现且不用一点细节—我们只需要专注在服务器端。</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="索引模版">索引模版</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 %}
+&lt;h1&gt;Local Library Home&lt;/h1&gt;
+
+ &lt;p&gt;Welcome to &lt;em&gt;LocalLibrary&lt;/em&gt;, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.&lt;/p&gt;
+
+&lt;h2&gt;Dynamic content&lt;/h2&gt;
+
+ &lt;p&gt;The library has the following record counts:&lt;/p&gt;
+ &lt;ul&gt;
+ &lt;li&gt;&lt;strong&gt;Books:&lt;/strong&gt; <strong>\{{ num_books }}</strong>&lt;/li&gt;
+ &lt;li&gt;&lt;strong&gt;Copies:&lt;/strong&gt; <strong>\{{ num_instances }}</strong>&lt;/li&gt;
+ &lt;li&gt;&lt;strong&gt;Copies available:&lt;/strong&gt; <strong>\{{ num_instances_available }}</strong>&lt;/li&gt;
+ &lt;li&gt;&lt;strong&gt;Authors:&lt;/strong&gt; <strong>\{{ num_authors }}</strong>&lt;/li&gt;
+ &lt;/ul&gt;
+
+{% endblock %}</pre>
+
+<div class="note">
+<p>注意:由于本网站就是通过<strong>django </strong>来运维,<code><strong>\{{ </strong></code>的模版标签 在上面代码中会运行,只能通过增加 <code>\ </code>来转义,而不能直接写出“双大括号”。</p>
+</div>
+
+<p>在动态内容部分,我们的占位符(模版变量),是给我们想要视图的信息声明。变量使用“双大括号“ 或者“句柄“语法进行标记。</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> (下面);当渲染模版时,这些将替换为相关联的值。</p>
+
+<pre class="brush: python">return render(
+ request,
+ 'index.html',
+ 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},
+)</pre>
+
+<h4 id="在模版中引用静态文件">在模版中引用静态文件</h4>
+
+<p>你的项目可能会使用静态资源,包括<strong>javascript</strong>,<strong>css</strong> 和图像。由于这些文件的位置可能不知道(或者可能会发生变化),则Django允许你指定你的模版相对于这些文件的位置 <code><strong>STATIC_URL</strong></code> 全局设置(默认基本网站设置的值 <code><strong>STATIC_URL</strong></code>,以“<code><strong>/static/</strong></code>”,但你可能选择在CDN和其他地方托管内容)。</p>
+
+<p>在模版中,你首先调用 <code>load</code> 指定“ <code>static</code>”去添加此模版库(如下)。静态加载后,你可以使用 <code>static</code> 模版标签,指定感兴趣的文件相对<code>URL</code></p>
+
+<pre class="brush: html"> &lt;!-- Add additional CSS in static file --&gt;
+{% load static %}
+&lt;link rel="stylesheet" href="{% static 'css/styles.css' %}"&gt;</pre>
+
+<p>你可以用同样的方式将图像添加到页面中:</p>
+
+<pre class="brush: html">{% load static %}
+&lt;img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="My image" style="width:555px;height:540px;"/&gt;
+</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映射器r (<strong>/locallibrary/locallibrary/urls.py</strong>) 中开发Web服务器提供服务,你仍然需要安排它们在生产中投放。我们接下来看一看</p>
+</div>
+
+<p>更多内容—<a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">Managing static files</a> (Django docs).</p>
+
+<h4 id="链接URLs">链接URLs</h4>
+
+<p>基本的模版引入<code> url</code> 模版标签</p>
+
+<pre class="brush: python">&lt;li&gt;&lt;a href="{% url 'index' %}"&gt;Home&lt;/a&gt;&lt;/li&gt;
+</pre>
+
+<p>此标记<code>url()</code>使用您的<strong>urls.py</strong>中调用的函数的名称 和相关视图将从该函数接收的任何参数的值,并返回可用于链接到该资源的URL。</p>
+
+<h2 id="它看起来什么样?">它看起来什么样?</h2>
+
+<p>运行 (<code>python3 manage.py runserver</code>) 和在浏览器中打开 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>. I如果一切都正确设置,当当当当。</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 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>您将无法使用“<span> </span></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>所有图书</font></font></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>所有作者”</font></font></strong><font><font>链接(目前我们刚刚在</font></font><code style='background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); margin: 0px; padding: 2px 5px; border: 0px; font-style: normal; font-weight: normal; border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>base_generic.html</code><font><font>模板中</font><font>插入了这些链接的占位符</font><font>)</font></font></p>
+</div>
+
+<h2 id="挑战自己">挑战自己</h2>
+
+<p>以下是一些测试您熟悉模型查询,视图和模板的任务。</p>
+
+<p>   1. 在索引模板中声明一个新的标题块,并更改页面标题以匹配此特定页面。<br>
+    2. 修改视图以生成包含特定单词(不区分大小写)的类型计数和书数,然后将这些字段添加到模板。</p>
+
+<ul>
+</ul>
+
+<h2 id="概要_2">概要</h2>
+
+<p>我们现在已经为我们的网站创建了主页 - 一个HTML页面,显示数据库中的一些记录数,并且链接到我们其他尚待创建的页面。一路上,我们已经学到了很多有关url映射器,视图,使用我们的模型查询数据库的基本信息,如何从您的视图传递信息到模板,以及如何创建和扩展模板。</p>
+
+<p>在我们的下一篇文章中,我们将基于我们的知识来创建其他四个页面。</p>
+
+<h2 id="也可以看看">也可以看看</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial03/">Writing your first Django app, part 3: Views and Templates</a>  (Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/">URL 调度程序</a> (Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/views/">视图函数</a> (DJango docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/templates/">模版</a> (Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">管理静态文件</a>(Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">Django 快捷功能</a>(Django docs)</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</p>
diff --git a/files/zh-cn/learn/server-side/django/开发环境/index.html b/files/zh-cn/learn/server-side/django/开发环境/index.html
new file mode 100644
index 0000000000..fb6041621f
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/开发环境/index.html
@@ -0,0 +1,406 @@
+---
+title: 设置Django开发环境
+slug: learn/Server-side/Django/开发环境
+tags:
+ - Python
+ - django
+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">现在,你知道什么是Django。<br>
+ 那么我们将向你展示如何在Windows,Linux(Ubuntu)和 Mac OSX上设置和测试Django开发环境—无论你常用哪种操作系统,本文能给你开发Django应用所需的一切。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>知道如何在你开发所用的计算机操作系统中,打开终端/命令行和安装软件包。</td>
+ </tr>
+ <tr>
+ <th scope="row">目的:</th>
+ <td>在你的计算机中运行Django(1.10)开发环境。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="Django_开发环境概述">Django 开发环境概述</h2>
+
+<p>Django 使你可以轻松配置自己的电脑,以便开始开发网络应用。本节解释您可以从开发环境中获得什么,并提供一些设置和配置选项的概述。本文的其余部分介绍了在<strong>Ubuntu</strong>,<strong>Mac</strong> OSX和<strong>Windows</strong>上安装Django开发环境的 <strong>推荐方法</strong>,以及如何测试。</p>
+
+<h3 id="什么是Django开发环境">什么是Django开发环境?</h3>
+
+<p>开发环境是本地计算机上的Django安装,在将Django应用程序部署到生产环境之前,您可以使用它来开发和测试Django应用程序。</p>
+
+<p>Django 本身提供的主要工具是一组用于创建和使用Django项目的Python脚本,以及可在你电脑的web 浏览器中测试本地Django web应用(在你的计算机,而不是在外部的web 服务器)。</p>
+
+<p>还有其他外部工具, 它们构成了开发环境的一部分, 我们将不再赘述。这些包括 <a href="/en-US/docs/Learn/Common_questions/Available_text_editors">文本编辑器</a> 或编辑代码的IDE,以及像 <a href="https://git-scm.com/">Git</a> 这样的源代码控制管理工具,用于安全地管理不同版本的代码。我们假设你已经安装了一个文本编辑器。</p>
+
+<h3 id="什么是Django设置选项">什么是Django设置选项?</h3>
+
+<p>Django 在安装和配置方面非常灵活。Django可以:</p>
+
+<ul>
+ <li>安装在不同的操作系统上。</li>
+ <li>通过源代码、Python包索引(PyPi)进行安装,而大多数情况下,是通过主机的包管理应用程序安装的。</li>
+ <li>配置为使用几个数据库之一,可能还需要单独安装和配置。</li>
+ <li>在主系统的Python环境或在单独的Python虚拟环境中运行。</li>
+</ul>
+
+<p>每个选项都需要略微不同的配置和设置。以下小节解释了你的一些选择。在本文的其余部分中,我们将介绍Django在几个操作系统上的设置,并且在本教程的剩余模块中将假设你已进行该设置。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 其他可能的安装选项在官方Django文档中介绍。<a href="#furtherreading">相应文件 点击这里</a>.</p>
+</div>
+
+<h4 id="支持哪些操作系统">支持哪些操作系统?</h4>
+
+<p>Django web应用程序能运行在几乎任何可以运行Python3的计算机上:Windows,Mac OSX,Linux/Unix,Solaris,仅举几例。几乎任何计算机都具备在开发期间运行Django所需的性能。</p>
+
+<p>在本文中。我们将提供Windows,macOS 和Linux/Unix的说明。</p>
+
+<h4 id="你应该使用什么版本的Python">你应该使用什么版本的Python?</h4>
+
+<p>我们建议你使用最近发行的版本,在本文档写作的时候是Python 3.8.2。</p>
+
+<p>事实上,Python 3.5 以及更新的版本都可以用来开发,不过对Python 3.5的支持可能会在未来的版本更新中被移除。</p>
+
+<p>我们建议你使用最新版本的Python 3,除非该站点依赖于仅适用于Python 2 的第三方库。本文将介绍如何为Python 3安装环境(Python 2 的等效设置将非常相似)。</p>
+
+<ul>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>: Python 2.7 无法用于当前的 Django 发行版本(Django 1.11.x 系列是最后支持 Python 2.7 的版本)。</p>
+</div>
+
+<h4 id="我们在哪里下载Django">我们在哪里下载Django?</h4>
+
+<p>有三个地方可以下载Django:</p>
+
+<ul>
+ <li>Python包资源库 (PyPi)。并用<strong> pip </strong>工具进行安装,这是获取Django 最新稳定版本的最佳方式。</li>
+ <li>计算机软件包管理器。与操作系统捆绑在一起的Django发行版是一种常见的安装途径。请注意,打包的版本可能很老,且只能安装到系统Python 环境中(而这可能不是你想要的)。</li>
+ <li>源代码。你可以从源代码获得并安装最新版本的Django。这并不推荐给初学者,但是当你准备好开始贡献给Django项目本身的时候,它是必需的。</li>
+</ul>
+
+<p>本文介绍如何从PyPi安装Django的最新稳定版本。</p>
+
+<h4 id="哪个数据库">哪个数据库?</h4>
+
+<p>Django支持四个主要数据库(PostgreSQL,MySQL,Oracle和SQLite),还有一些社区库可以为其他流行的SQL和NOSQL数据库提供不同级别的支持。我们建议你为生产和开发选择相同的数据库(尽管Django使用其对象关系映射器(ORM)抽象了许多数据库之间的差异,但是仍然存在本可以避免的<a href="https://docs.djangoproject.com/en/1.10/ref/databases/">潜在问题</a> ).</p>
+
+<p>对于本文(和本模块的大部分),我们将使用将数据存储在文件中的SQLite数据库。SQLite旨在用作轻量级数据库,不能支持高并发。然而,这确实是只读的应用程序的绝佳选择。</p>
+
+<div class="note">
+<p><strong>注意:</strong>当你使用标准工具(django-admin)启动你的网站项目时,Django将默认使用SQLite。用来入门时,这是一个很好的选择,因为它不需要额外的配置和设置。</p>
+</div>
+
+<h4 id="安装本机系统还是Python虚拟环境中">安装本机系统还是Python虚拟环境中?</h4>
+
+<p>当你安装Python3时,将获得一个由所有Python3代码共享的全局环境。虽然你可以在该环境中安装任何你喜欢的Python包,但是每次只能安装每个包的一个特定版本。</p>
+
+<div class="blockIndicator 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>为了使用Django,你需要在你的操作系统中安装Python。如果你使用Python3,那么你同样需要<a href="https://pypi.python.org/pypi">Python 包管理工具</a>  — <em>pip3</em> — 用来管理 (安装,更新和删除)被Django和其他Python应用程序使用的Python软件包/库。</p>
+
+<p>本节简要介绍了如何检查有哪些版本的Python,并根据需要安装适用于 <strong>Ubuntu Linux 16.04,macOS, and Windows 10</strong>的新版本。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 根据你的平台, 您还可以从操作系统自己的软件包管理器或其他机制安装Python / pip。对于大多数平台,您可以从<a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a>下载所需的安装文件,并使用该平台特定的方法进行安装。</p>
+</div>
+
+<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3>
+
+<p>Ubuntu Linux 18.04 LTS默认包含Python 3.6.6。你可以通过在Bash终端中运行以下命令来确认这一点:</p>
+
+<pre class="notranslate"><span style="line-height: 1.5;">python3 -V
+ Python 3.6.6</span></pre>
+
+<p>然而,在默认情况下,为Python 3(包括Django)安装软件包的Python包管理工具<strong>不可用。你</strong>可以在<strong>bash</strong>终端中使用以下命令安装<strong>pip3</strong><strong>:</strong></p>
+
+<pre class="notranslate">sudo apt-get install python3-pip
+</pre>
+
+<h3 id="macOS">macOS</h3>
+
+<p>macOS 的"El Capitan" 及其他最新版本不包含Python 3。你可以通过在bash终端中运行一下命令来确认:</p>
+
+<pre class="notranslate"><span style="line-height: 1.5;">python3 -V
+ </span>-bash: python3: command not found</pre>
+
+<p>你可以轻松地从<a href="https://www.python.org/"> python.org</a>安装Python 3(以及pip3工具):</p>
+
+<ol>
+ <li>下载所需的安装程序:
+ <ol>
+ <li>点击 <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li>
+ <li>选择 <strong>Download Python 3.8.2</strong>  (具体的版本号可能不同)。</li>
+ </ol>
+ </li>
+ <li>使用Finder找到安装包,然后双击运行,并按照提示进行安装。</li>
+</ol>
+
+<p>之后可以通过检查Python3版本确认是否安装成功,如下所示:</p>
+
+<pre class="notranslate"><span style="line-height: 1.5;">python3 -V
+Python 3.8.2</span></pre>
+
+<p>你也可以通过列出可用的包来检查pip3是否安装了:</p>
+
+<pre class="notranslate">pip3 list</pre>
+
+<h3 id="Windows_10">Windows 10</h3>
+
+<p>windows默认不包含Python, 但你可以从<a href="https://www.python.org/"> python.org</a>轻松地安装它(以及pip3工具):</p>
+
+<ol>
+ <li>下载所需版本:
+ <ol>
+ <li>点击 <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li>
+ <li>选择 <strong>Download Python 3.8.2</strong>  (具体的版本号可能不同)。</li>
+ </ol>
+ </li>
+ <li>双击现在的文件并按照提示安装Python。</li>
+ <li>确保勾选了"Add Python to PATH"选项。</li>
+</ol>
+
+<p>你可以在命令提示符中输入以下内容来验证是否安装了Python:</p>
+
+<pre class="notranslate"><span style="line-height: 1.5;">python -V
+ Python 3.8.2</span>
+</pre>
+
+<p>Windows安装程序默认包含pip3 (Python包管理器)。同样在命令提示符中输入以下内容来列出已安装的包:</p>
+
+<pre class="notranslate"><span style="line-height: 1.5;">pip3 list</span>
+</pre>
+
+<div class="blockIndicator note">
+<p><strong>注意:</strong>安装包应该已把运行上述命令所需的一切设置完成。但如果你得到的消息是找不到Python,那么你可能忘记将Python添加到系统路径中了。你可以通过再次运行安装包,选择"Modify",并在下一页面中勾选 "Add Python to environment variables"来修复这个问题。</p>
+</div>
+
+<h2 id="在Python虚拟环境中使用Django">在Python虚拟环境中使用Django</h2>
+
+<p>我们使用<a href="https://virtualenvwrapper.readthedocs.io/en/latest/index.html" rel="noopener">virtualenvwrapper</a>(Linux及macOS)和 <a href="https://pypi.python.org/pypi/virtualenvwrapper-win" rel="noopener">virtualenvwrapper-win</a>(WIndows)来创建Python虚拟环境,而它们又使用了<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安装该工具:<span>​​​​​​</span></p>
+
+<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre>
+
+<p>然后将以下代码行添加到shell启动文件的末尾(这是主目录中的一个隐藏文件,名字是.bashrc)。这些文件设置了虚拟环境应该存在的位置、开发项目目录的位置以及与这个包一起安装的脚本的位置。</p>
+
+<pre class="notranslate"><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="blockIndicator 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>脚本的一般安装位置。 如果您在测试时发现<em>virtualenv</em>无法正常工作,则要检查的一件事是Python和该脚本是否在预期的位置(然后适当更改启动文件)。</p>
+
+<p>你可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>命令为你的系统找到正确的安装位置。</p>
+</div>
+
+<p>然后通过在终端中运行以下命令重载启动文件:</p>
+
+<pre class="notranslate"><code>source ~/.bashrc</code></pre>
+
+<p>此时,你应该能看到一些脚本正在运行,如下所示:</p>
+
+<pre class="notranslate"><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_虚拟环境设置">macOS 虚拟环境设置</h4>
+
+<p>在macOS上设置<em>virtualenvwrapper</em> 几乎和在Ubuntu上是一样的(你同样可以按照以下指导操作,或在<a href="https://virtualenvwrapper.readthedocs.io/en/latest/install.html">这里</a>找到正式的安装指南)。</p>
+
+<p>使用<em>pip</em>安装<em>virtualenvwrapper</em>(并绑定<em>virtualenv</em>),如下所示。</p>
+
+<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre>
+
+<p>然后将以下代码行添加到shell启动文件的末尾:</p>
+
+<pre class="notranslate"><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="blockIndicator 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>脚本的一般安装位置。 如果您在测试时发现<em>virtualenv</em>无法正常工作,则要检查的一件事是Python和该脚本是否在预期的位置(然后适当更改启动文件)。</p>
+
+<p>例如,在macOS上的一个安装测试中,启动文件中必须有以下几行代码:</p>
+
+<pre class="notranslate"><code>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</code></pre>
+
+<p>你可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>命令为你的系统找到正确的安装位置。</p>
+</div>
+
+<p>此处使用和Ubuntu相同的代码行,但是启动文件是主目录中叫做<strong>.bash_profile</strong>的隐藏文件。</p>
+
+<div class="blockIndicator note">
+<p><strong>注意:</strong>如果找不到<strong>.bash_profile</strong>进行编辑,也可以使用nano在终端中打开它,命令看起来类似于:</p>
+
+<pre class="notranslate"><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>
+</div>
+
+<p>然后通过在终端中运行以下命令重载启动文件:</p>
+
+<pre class="notranslate"><code>source ~/.bashrc</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" rel="noopener">virtualenvwrapper-win</a> 甚至比设置<em>virtualenvwrapper</em> 更简单,因为你无需配置工具用来存储虚拟环境信息的位置(有一个默认值)。你需要做的只是在命令提示符中运行以下命令:</p>
+
+<pre class="notranslate"><code>pip3 install virtualenvwrapper-win</code></pre>
+
+<p>然后你就可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。</p>
+
+<h3 id="创建虚拟环境">创建虚拟环境</h3>
+
+<p>一旦你成功安装了<em>virtualenvwrapper</em> 或 <em>virtualenvwrapper-win,</em>那么在所有平台中使用虚拟环境的方法是非常相似的。</p>
+
+<p>现在你可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。在运行此命令时,你将看到正在设置的环境(你所看到的只略微与平台相关)。命令完成后,新的虚拟环境将被激活——你能看到提示符的开头就是括号中的环境名称(以下我们展示的是Ubuntu的,但是在Windows/macOS上,末行时相似的|)</p>
+
+<pre class="notranslate"><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="blockIndicator 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>
+
+<div class="blockIndicator note"></div>
+
+<h2 id="安装Django">安装Django</h2>
+
+<p>一旦你创建了一个虚拟环境,并且使用<code>workon</code> 进入了它,就可以使用pip3来安装Django。</p>
+
+<pre class="notranslate">pip3 install django</pre>
+
+<p>您可以通过运行以下命令来测试Django是否安装(这只是用来测试Python是否可以找到Django模块):</p>
+
+<pre class="notranslate"># Linux/macOS
+python3 -m django --version
+ 1.10.10
+
+# Windows
+py -3 -m django --version
+ 1.10.10
+</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>如果上面的Windows命令没有显示django模块,请尝试:</p>
+
+<pre class="notranslate"><code>py -m django --version</code></pre>
+
+<p>在Windows中,Python 3脚本是通过在命令前面加上<code>py -3</code>来启动的,尽管该脚本可能会因您的特定安装而有所不同。 如果遇到命令问题,请尝试省略<code>-3</code>修饰符。 在Linux /macOS中,命令是python3。</p>
+</div>
+
+<div class="warning">
+<p>重要提示:本模块的其余部分使用Linux命令来调用Python 3(<code>python3</code>)。如果您在Windows上工作,只需将此前缀替换为: <code>py -3</code></p>
+</div>
+
+<h2 id="测试你的安装">测试你的安装</h2>
+
+<p>上面的测试工作并不是很有趣。一个更有趣的测试是创建一个框架项目并查看它的工作情况。要做到这一点,先在你的命令提示符/终端导航到你想存储你<strong>Django</strong>应用程序的位置。为您的测试站点创建一个文件夹并进入其中。</p>
+
+<pre class="notranslate">mkdir django_test
+cd django_test
+</pre>
+
+<p>然后,您可以像所展示的一样使用django-admin工具创建一个名为“<em> mytestsite </em>” 的新框架站点。创建网站后,您可以CD到此文件夹,并将在其中找到管理项目的主要脚本,名为<strong>manage.py</strong>。</p>
+
+<pre class="notranslate">django-admin startproject mytestsite
+cd mytestsite</pre>
+
+<p>我们可以在这个文件夹中使用<strong>manager.py</strong>和<code>runserver</code>命令运行<em>开发web服务器</em>,如下所示。</p>
+
+<pre class="notranslate"><code>$ python3 manage.py runserver
+Performing system checks...
+
+System check identified no issues (0 silenced).
+
+You have 15 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 16, 2018 - 07:06:30
+Django version 2.2.12, using settings 'mytestsite.settings'
+Starting development server at http://127.0.0.1:8000/
+Quit the server with CONTROL-C.</code></pre>
+
+<div class="note">
+<p>注意:上面的命令显示了Linux /macOS命令。您可以忽略关于“15 unapplied migration(s)”的警告!</p>
+</div>
+
+<p>一旦服务器运行,您可以通过本地Web浏览器打开<code>http://127.0.0.1:8000/</code>来查看该站点。你应该看到一个如下所示的网站:</p>
+
+<p><img alt="The home page of the skeleton Django app." src="https://mdn.mozillademos.org/files/16288/Django_Skeleton_Website_Homepage_2_1.png" style="height: 714px; width: 806px;"></p>
+
+<ul>
+</ul>
+
+<h2 id="概要">概要</h2>
+
+<p>现在,你的计算机中已经启动并运行了一个Django开发环境。</p>
+
+<p>在测试部分,您还简要地了解了如何使用d<code>jango -admin startproject</code>创建一个新的Django网站,并使用开发web服务器(<code>python3 manager .py runserver</code>)在浏览器中运行它。在下一篇文章中,我们将对此过程进行扩展,构建一个简单但完整的web应用程序。</p>
+
+<h2 id="看看瞧瞧">看看瞧瞧</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/intro/install/">快速安装指南</a>(Django docs)</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/topics/install/">如何安装Django — 完整指南</a> (Django docs) - 包含有关如何删除Django的信息</li>
+ <li><a href="https://docs.djangoproject.com/en/1.10/howto/windows/">如何安装Django在 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>
diff --git a/files/zh-cn/learn/server-side/django/管理站点/index.html b/files/zh-cn/learn/server-side/django/管理站点/index.html
new file mode 100644
index 0000000000..d3252d84c5
--- /dev/null
+++ b/files/zh-cn/learn/server-side/django/管理站点/index.html
@@ -0,0 +1,339 @@
+---
+title: 'Django Tutorial Part 4: Django 管理员站点'
+slug: learn/Server-side/Django/管理站点
+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: 使用模型</a>。</td>
+ </tr>
+ <tr>
+ <th scope="row">目的:</th>
+ <td>
+ <p>了解关于管理站点的优点与缺点,并且可以使用它为我们模型创建一些记录。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="综述">综述</h2>
+
+<p>Django管理应用程序可以使用您的模型自动构建可用于创建,查看,更新和删除记录的站点区域。这可以在开发过程中节省大量的时间,从而很容易测试您的模型,并了解您是否拥有正确的数据。根据网站的类型,管理应用程序也可用于管理生产中的数据。Django项目建议仅用于内部数据管理(即仅供管理员或组织内部人员使用),因为以模型为中心的方法不一定是所有用户最好的界面,并且暴露了大量不必要的细节关于模型。</p>
+
+<p><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">创建基础项目时,</a>自动完成所有将您的网站中的管理应用程序包含在内的配置文件 (有关所需实际依赖关系的信息  (如有需要请看 <a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">Django docs here</a>). 其结果是,你必须做你的模型添加到管理应用程序是  注册  他们。在本文末尾,我们将简要介绍如何进一步配置管理区域以更好地显示我们的模型数据。</p>
+
+<p>注册模型后,我们将展示如何创建一个新的“超级用户”,登录到该网站,并创建一些书籍,作者,书籍实例和流派。这些将有助于测试我们将在下一个教程中开始创建的视图和模板。</p>
+
+<h2 id="注册模型">注册模型</h2>
+
+<p>首先,在目录应用程序(<strong>/locallibrary/catalog/admin.py</strong>)中打开 <strong>admin.py </strong>。此时此刻它看起来像这样—注意它已经导入了django.contrib.admin:</p>
+
+<pre class="brush: python">from django.contrib import admin
+
+# Register your models here.
+</pre>
+
+<p>通过将以下文本复制到文件的底部来注册模型。该代码简单地导入模型,调用 <strong>admin.site.register </strong>来注册它们。</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="/en-US/docs/Learn/Server-side/Django/Models">see the models tutorial article</a>), 导入并注册。</div>
+
+<p>这是在网站上注册模型或多模型的简单方法,管理站点是高度可定制的,我们将进一步讨论注册模型的其他方式。</p>
+
+<h2 id="创建一个超级用户">创建一个超级用户</h2>
+
+<p>为了登录管理员站点,我们需要启动工作人员状态的用户账户。为了查看和创建记录,我们还需要该用户具有所有对象的记录。你可以创建一个“超级用户”账号,该账号具有完全访问该站点和所有必需的权限可以使用<code>manage.py </code></p>
+
+<p>调用接下来的命令,在同样的目录下,<code>manage.py 创建超级用户。你将被提示输入用户名,电子邮件地址,和强密码。</code></p>
+
+<pre class="brush: bash">python3 manage.py createsuperuser
+</pre>
+
+<p>一旦命令完成,一个新超级用户将被添加到数据库。现在重新启动开发服务器,以便我们可以测试登录名:</p>
+
+<pre class="brush: bash">python3 manage.py runserver
+</pre>
+
+<h2 id="登入并使用该网站">登入并使用该网站</h2>
+
+<p>登录网站,打开 <code>/admin</code> (e.g. <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin</a>)<br>
+ 和进入你的新超级用户名和密码凭据(你将被重定向到 登录页面,然后在你进入你的详细信息后回到 <code>/admin</code> URL</p>
+
+<p>这部分网站展示我们所有的模型,按安装的应用程序分组。你可以点击模型名称来进入到 它所有相关详细记录的页面,你可以进一步点击这些记录进行编辑。你也可以直接点击每个模型旁边的添加链接,开始创建该类型的记录。</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>点击图书右侧的添加链接来新建一本书(这将显示一个类似下面的对话框)。注意每个字段标题,使用的小部件的类型以及<strong>help_text</strong>(如果有的话)你要在模型中匹配指定的值。</p>
+
+<p>输入字段的值,你可以创建一个新的作者或类型通过 按 <code>+</code> 按钮(或者如果你已经创建选项,选择已有的值)。完成后,你可以按 <strong>保存</strong>,<strong>保存并添加另一个</strong>,或<strong>保存并继续编辑</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>注意</strong>: 在这里,我们希望你花费一点时间添加一些书,作者,类型(如: 幻想)到你的应用。确保每个作者和类型都包含几本不同的书籍(这会是你的列表和详细视图在文章系列中后期使用时更有趣)。</p>
+</div>
+
+<p>我们完成添加书籍,在顶部标签中,点击 <strong>Home</strong> 链接将回到主管理页面。然后点击 <strong>Books</strong> 链接显示当前书籍的列表(或其他链接之一,以查看其他型号列表)。现在你已经添加了几本书,列表可能与下面的截图类似。显示每本书的标题;这是书模型 __str__() 方法返回的值,在上一文章中提到。</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>从该列表中,您可以通过选中不需要的图书旁边的复选框来删除图书,从“ 操作”下拉列表中选择“ 删除”操作  ,然后按Go按钮。您也可以通过按下ADD BOOK按钮添加新书。</p>
+
+<p>您可以通过在链接中选择其名称来编辑书籍。一本书的编辑页面如下所示,与“添加”页面几乎相同。主要的区别是页面标题(更改书)和添加  删除,历史和<code>VIEW ON SITE</code>按钮(最后一个按钮出现,因为我们定义了<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>现在回到主页(使用主页链接的导航痕迹),然后查看作者  和类型  列表 - 您应该已经有很多创建从添加新书,但可以自由添加一些更多。</p>
+
+<p>你不会有任何书籍实例,因为这些不是从图书创建的(虽然你可以从 <strong>BookInstance</strong> - 创建一个书  - 这是ForeignKey字段的性质)。返回主页,然后按关联的添加按钮显示下面的添加书实例屏幕。请注意,全球唯一的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>为你的书创建一些记录。将状态设置为可用于至少一些记录,并为其他记录贷款。如果状态 不可 用,则还设置未来到期日期。</p>
+
+<p>而已!您现在已经学会了如何 设置和使用管理站点。您还创建书的记录,BookInstance,Genre,和Author 我们就可以一次我们创造我们自己的观点和模板使用。</p>
+
+<h2 id="高级配置">高级配置</h2>
+
+<p>Django 使用注册模型的信息为创建基本管理站点做了非常好的工作:</p>
+
+<ul>
+ <li>每个模型都有一个单独的记录列表,由使用模型 __str__()<br>
+ 方法创建的字符串标识,并链接到详细视图/表单进行编辑。默认,视图最上面有一个操作菜单,可用于对记录执行批量删除操作。</li>
+ <li>进行编辑和添加记录的模型详细记录表单包含 模型的所有字段,以其声明顺序垂直布置。</li>
+</ul>
+
+<p>你可以进一步自定义界面,使它更容易使用,你可以改进的一些想法:</p>
+
+<ul>
+ <li>视图列表:
+ <ul>
+ <li>添加每个记录显示的其他字段/信息</li>
+ <li>添加过滤器以根据日期或某些其他选择值(例如图书货款状态)选择列出哪些记录。</li>
+ <li>在列表视图中的操作菜单中添加其他选项,并选择此菜单在表单上显示的位置。</li>
+ </ul>
+ </li>
+ <li>详细视图
+ <ul>
+ <li>选择要显示(或排除)的字段,以及其顺序,分组,是否可编辑,使用的小部件,方向等。</li>
+ <li>将相关字段添加到记录以允许内联编辑(例如:添加在创建作者记录时添加和编辑图书记录的功能)。</li>
+ </ul>
+ </li>
+</ul>
+
+<p>在本节中,我们将看一些改进本地图书馆界面的更改,其中包括添加更多信息Book和Author 模型列表,以及改进编辑视图的布局。我们不会改变 Language 和 Genre 模拟演示,因为它们只有一个字段,所以这样没有真正的好处。</p>
+
+<p>你可以 在<a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">The Django Admin site </a>中找到所以管理员网站自定义选项的完整参考。</p>
+
+<h3 id="注册_一个_ModelAdmin_类">注册 一个 ModelAdmin 类</h3>
+
+<p>在管理界面去改变一个模型的展示方式,当你定义了 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects">ModelAdmin</a> 类(描述布局)和将其注册到模型中。</p>
+
+<p>让我们开始作者模型。打开 <strong>admin.py</strong> 在目录应用程序(<strong>/locallibrary/catalog/admin.py</strong>)。注释你的原始注册(前缀为#)在<strong> Author</strong> 模型</p>
+
+<pre class="brush: js"># admin.site.register(Author)</pre>
+
+<p>现在添加一个 AuthorAdmin 和注册,如下</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>我们再为<strong>Book</strong> 添加 <strong>ModelAdmin</strong> 类 和 <strong>BookInstance</strong> 类。我们需要注释我们原始注册:</p>
+
+<pre class="brush: js">#admin.site.register(Book)
+#admin.site.register(BookInstance)</pre>
+
+<p>现在创建和注册新的模型;为了演示的目的,我们将使用<code>@register 装饰器来注册模型(这和 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>可以看到我们现在 的 类都是空的 (“pass”),所以管理操作并不会改变,我们现在对这些类进行扩展,以定义我们针对模型的管理行为。</p>
+
+<h3 id="配置列表视图">配置列表视图</h3>
+
+<p>该 本地图书馆 目前列出的所以作者都使用从模型生成的对象名称的<code>__str__()</code> 方法。如果只是几个作者,这无关紧要。但一旦你有许多作者,这可能会重复。要区分它们,或仅仅因为你想要显示有关每个作者的更多有趣的信息,你可以使用<a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display">list_display</a> 向视图添加其他字段。</p>
+
+<p>用下面的代码替代 你 <strong>AuthorAdmin</strong> 的类。在元组中声明要显示列表中的字段名称以所需的顺序排列,如图(这些和原始模型中指定的名称相同)。</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>对于我们的Book模型,我们将另外显示<strong>author</strong>和<strong>genre</strong>。这author是一个<strong>ForeignKey</strong>字段(一对多)的关系,所以将由<code>__str()__</code>相关记录的值表示。用<strong>BookAdmin</strong>下面的版本替换课程。</p>
+
+<pre class="brush: python">class BookAdmin(admin.ModelAdmin):
+ list_display = ('title', 'author', 'display_genre')
+</pre>
+
+<p>不幸的是,我们不能直接指定 <strong>list_display </strong>中的 <strong>genre</strong> 字段, 因为它是一个<strong>ManyToManyField</strong> (Django可以防止这种情况,因为在这样做时会有大量的数据库访问“成本”)。相反,我们将定义一个 <code>display_genre </code>函数来获取信息作为一个字符串(这是我们上面调用的函数;下面我们将定义它)。</p>
+
+<div class="note">
+<p>注意:在<strong>genre</strong>这里获取可能不是一个好主意,因为数据库操作的“成本”。我们向您展示了如何在模型中调用函数的其他原因非常有用 - 例如在列表中的每个项目旁边添加一个“ 删除”链接。</p>
+</div>
+
+<p>将以下代码添加到Book模型(<strong>models.py</strong>)中。这将从 genre字段的前三个值(如果存在)创建一个字符串,并创建一个<code>short_description</code>可以在此方法的管理站点中使用的字符串。</p>
+
+<pre class="brush: python">    def display_genre(self):
+ """
+ Creates 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>保存模型并更新管理员后,重新启动站点并转到图书列表页面; 你应该看到像下面这样的书籍清单:</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>该Genre模型(和Language模式,如果你定义一个)都有一个单一的领域,所以没有一点为他们创造更多的显示领域的附加模型。</p>
+
+<div class="note">
+<p>注意:值得更新BookInstance模型列表,至少显示状态和预期的返回日期。我们已经补充说,作为本文末尾的挑战!</p>
+</div>
+
+<h3 id="添加列表过滤器">添加列表过滤器</h3>
+
+<p>一旦列表中有很多项目,就可以过滤哪些项目被显示出来。这是通过在<code>list_filter</code>属性中列出字段来完成的。用<code>BookInstanceAdmin</code>下面的代码片段替换你当前的  类。</p>
+
+<pre class="brush: python">class BookInstanceAdmin(admin.ModelAdmin):
+<strong> list_filter = ('status', 'due_back')</strong>
+</pre>
+
+<p>列表视图现在将在右侧包含一个过滤器框。请注意如何选择日期和状态来过滤值:</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>注意:LocalLibrary模型比较简单,因此我们不需要更改布局; 不管怎样,我们会做一些改变,只是为了向你展示如何。</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>注意:您还可以使用<code>exclude</code>属性来声明要从表单中排除的属性列表(将显示模型中的所有其他属性)。</p>
+</div>
+
+<h4 id="剖切细节视图"><font><font>剖切细节视图</font></font></h4>
+
+<p>你可以使用 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">fieldsets</a> 属性添加“部分”以在详细信息表单中对相关的模型信息进行分组。</p>
+
+<p>在  <code>BookInstance</code>模型中,我们有相关的书是什么(即信息  <code>name,imprint和id</code>),并且当将可用(<code>status,due_back</code>)。我们可以通过将粗体文本添加到我们的<code>BookInstanceAdmin</code>类中来将其添加到不同的部分  。</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>每个部分都有自己的标题(或者None如果你不想要一个标题)和字典中的一个相关的元组 - 描述的格式很复杂,但是如果你看上面的代码片段,那么它们很容易理解。</p>
+
+<p>重新启动并导航到书籍实例视图; 表格应如下所示:</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="关联记录的内联编辑"><font><font>关联记录的内联编辑</font></font></h3>
+
+<p>有时,可以同时添加关联记录是有意义的。例如,将书籍信息和有关您在同一详细信息页面上的特定副本的信息同时显示可能是有意义的。</p>
+
+<p>你可以通过声明 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">inlines</a>, 类型 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> (水平布局 ) or <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.StackedInline">StackedInline</a> (垂直布局 ,就像默认布局)这样做. 您可以通过在您的以下的粗体中添加以下行,将内容中的<code>BookInstance</code>信息添加到我们的Book详细信息中<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>尝试重新启动您的应用程序,然后查看图书的视图 - 在底部您应该看到与本书相关的图书实例:</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>在这种情况下,我们所做的就是声明我们的<code>tablular</code>内联类,它只是从内联模型添加所有字段。您可以为布局指定各种附加信息,包括要显示的字段,其顺序,是否只读等。(有关详细信息,请参阅  <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> ). </p>
+
+<div class="note">
+<p>注意:这个功能有一些痛苦的限制!在上面的屏幕截图中,我们有三个现有的书籍实例,其次是新的书籍实例的三个占位符(看起来非常相似!)。默认情况下没有备用书实例会更好,只需使用“ 添加另一个书”实例链接添加它们,或者可以<code>BookInstance</code>从这里列出作为不可读的链接。第一个选项可以通过<code>extra</code>在<code>BookInstanceInline</code>模型中将属性设置为0 来完成,自己尝试一下。</p>
+</div>
+
+<h2 id="挑战自己"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px;"><font><font>挑战自己</font></font></span></h2>
+
+<p>我们在本节学到了很多东西,所以现在是时候尝试一些事情了。</p>
+
+<p>1. 对于  <code>BookInstance</code>列表视图,添加代码以显示书籍,状态,到期日期和ID(而不是默认<code>__str__()</code>文本)。<br>
+ 2. 添加的在线上市Book项目的Author使用,因为我们做了同样的做法详细视图<code>Book/ BookInstance。</code></p>
+
+<h2 id="概要">概要</h2>
+
+<p>而已!您现在已经了解了如何以最简单和改进的形式设置管理站点,如何创建超级用户以及如何导航管理站点以及查看,删除和更新记录。一路上,您创建了一堆书籍,BookInstances,流派和作者,一旦我们创建了自己的视图和模板,我们就可以列出和展示。</p>
+
+<ul>
+</ul>
+
+<h2 id="进阶阅读">进阶阅读</h2>
+
+<ul>
+ <li><a href="https://docs.djangoproject.com/en/1.10/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/1.10/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>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html
new file mode 100644
index 0000000000..b4802ca3c5
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html
@@ -0,0 +1,526 @@
+---
+title: 'Express 教程 7: 部署到生产环境'
+slug: learn/Server-side/Express_Nodejs/deployment
+tags:
+ - Express
+ - Learn
+ - Node
+ - 初学者
+ - 部署
+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">现在你已经创建(并测试)了一个不错的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆</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/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>到目前为止,您一直在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">开发环境</a>中工作,使用 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="/zh-CN/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">数字海洋node社区文档</a>中,有许多各种配置的手把手指南。</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/">文档</a>)。</p>
+
+<p>通过在项目的根目录下,运行以下命令,将其安装到项目中。</p>
+
+<pre class="brush: bash">npm install helmet
+</pre>
+
+<p>打开<strong>./app.js</strong>,并导入如图所示的<em> helmet</em> 库。然后使用<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">免费套餐</a>(尽管有一些限制)。</li>
+ <li>作为PaaS,Heroku为我们提供了大量的 Web 基础架构。这使得入门更加容易,因为您不必担心服务器,负载平衡器,反向代理,崩溃时重新启动网站,或者 Heroku 为我们提供的任何其他 Web 基础结构。</li>
+ <li>虽然它确实有一些限制,但这些不会影响这个特定的应用程序。例如:
+ <ul>
+ <li>Heroku只提供短期存储,因此用户上传的文件无法安全地存储在Heroku本身。</li>
+ <li>如果半小时内没有请求,免费套餐将使不活动的网络应用程序进入睡眠。然后,该网站可能需要几秒钟才能被唤醒。</li>
+ <li>免费套餐将您网站运行的时间,限制为每月一定的小时数(不包括网站“睡着”的时间)。这对于低使用/演示站点来说很好,但如果需要100%的正常运行时间,则不适用。</li>
+ <li><a href="https://devcenter.heroku.com/articles/limits">Heroku 官方文档</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">配置变量</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>这是您开始教程所需的全部概述(有关更全面的指南,请参阅<a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">带有Node.js的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> +</strong> 号链接,然后选择新建存储库<strong>New repository</strong>。</li>
+ <li>填写此表单上的所有字段。虽然这些不是强制性的,但强烈建议使用它们。
+ <ul>
+ <li>输入新的存储库名称(例如,express-locallibrary-tutorial)和描述(例如 “以Express(node)编写的本地图书馆网站”)。</li>
+ <li>在 Add .gitignore 选择列表中选择 <strong>Node</strong>。</li>
+ <li>在添加许可证 <em>Add license</em> 选择列表中,选择您偏好的许可证。</li>
+ <li>点选 <strong>使用自述文件初始化此存储库 </strong>“<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>&lt;your_git_user_id&gt;</em>/express-locallibrary-tutorial.git</strong>)。</li>
+</ol>
+
+<p>现在创建了存储库(“repo”),我们将要在本地计算机上克隆它:</p>
+
+<ol>
+ <li>为您的本地计算机安装git(您可以在<a href="https://git-scm.com/downloads">此处</a>找到不同平台的版本)。</li>
+ <li>打开命令提示符/终端,并使用您在上面复制的 URL ,克隆clone存储库:
+ <pre class="brush: bash">git clone https://github.com/<strong><em>&lt;your_git_user_id&gt;</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>&gt; git status
+On branch master
+Your branch is up-to-date with 'origin/master'.
+Changes to be committed:
+  (use "git reset HEAD &lt;file&gt;..." 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">&gt;node --version
+v8.9.1</pre>
+
+<p>打开<strong>package.json</strong>,并将此信息添加为<strong>engines &gt; 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-CN/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">&gt;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-CN/docs/Learn/Server-side/Express_Nodejs/mongoose#Setting_up_the_MongoDB_database">我们原来的方式</a>,设置新数据库和数据库用户,并获取其URI。您可以如下图所示设置URI(显然,要使用您自己的URI!)</p>
+
+<pre class="brush: bash">&gt;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">&gt;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">管理插件</a>(Heroku docs)。</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 介绍</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li>
+</ul>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html
new file mode 100644
index 0000000000..9a5e9ac7de
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html
@@ -0,0 +1,390 @@
+---
+title: 设置 Node 开发环境
+slug: learn/Server-side/Express_Nodejs/development_environment
+tags:
+ - Express
+ - Node
+ - node.js
+ - npm
+ - 初学者
+ - 学习
+ - 开发环境
+ - 服务器端
+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>会打开终端 / 命令行。会为开发用操作系统安装软件包。</td>
+ </tr>
+ <tr>
+ <th scope="row">学习目标:</th>
+ <td>在电脑上搭建 Express 开发环境。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="Express_开发环境概述">Express 开发环境概述</h2>
+
+<p>使用 Node 和 Express 搭建 web 应用程序开发环境非常简便。这一章节将简述所需的工具,在主流操作系统(Ubuntu、macOS 和 Windows)上安装 Node 的步骤,以及测试安装是否成功的方法。</p>
+
+<h3 id="什么是_Express_开发环境?">什么是 Express 开发环境?</h3>
+
+<p>完整的 Express 本地开发环境包括 Nodejs、NPM 包管理器和 <strong>Express 应用生成器</strong>(可选)。</p>
+
+<p>Node 和 NPM 包管理器可以用二进制包、安装程序或系统包管理器一并安装(下文将介绍)。然后在开发每个 Express web 应用时,由 NPM 针对当前应用将 Express(以及模板引擎、数据库驱动程序、身份验证中间件、静态文件托管中间件等其它库)作为依赖项进行安装。</p>
+
+<p>NPM 也可以安装(全局的)<strong>Express 应用生成器</strong>,可用于创建遵循 <a href="/zh-CN/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC模式</a> 的 Express 应用框架。它不是必备的,因为无需这个工具就可以创建 Express 应用(或相同架构布局或依赖的 Express 应用)。但我们还是会使用它,因为它更容易上手,还有助于应用结构的模块化管理。</p>
+
+<div class="note">
+<p><strong>注:</strong> 与某些其他Web框架不同,开发环境不包含单独的开发Web服务器。在Node / Express中,Web应用程序将创建并运行自己的Web服务器!</p>
+</div>
+
+<p>典型的开发环境中还需要一些外围工具,包括用于编写代码的 <a href="zh-CN/docs/Learn/Common_questions/实用文本编辑器">文本编辑器</a> 或 IDE ,用于代码控制管理的工具(比如代码版本控制工具 <a href="https://git-scm.com/">Git</a>)。这里假定你已经安装了这些工具(尤其是文本编辑器)。</p>
+
+<h3 id="支持哪些操作系统?">支持哪些操作系统?</h3>
+
+<p>Node 可以在 Windows、macOS、Linux 的诸多发行版本或 Docker 等环境运行(完整列表见 Node <a href="https://nodejs.org/zh-cn/download/">下载页面</a>)。几乎所有的个人电脑都具备 Node 开发所需性能。Express 运行在 Node 环境中,因此可运行 Node 的平台均可运行 Express。</p>
+
+<p>本文将介绍 Windows、macOS 和 Ubuntu Linux 上的安装步骤。</p>
+
+<h3 id="应该选择_NodeExpress_的哪个版本?">应该选择 Node/Express 的哪个版本?</h3>
+
+<p>Node 有许多 <a href="https://nodejs.org/zh-cn/blog/release/">发行版本</a>,新版包含 bug 修复、对最新版本 ECMAScript 标准的支持,以及 API 的改进。</p>
+
+<p>通常应该选择最新的 LTS(Long-term supported,长期支持版)发行版,因为它比当前发布版(current)更稳定。当前发布版包含最新的特性(维护中),如果需要 LTS 版本中没有提供的特征,那么可以选择它。</p>
+
+<p>Express 应选用最新版本。</p>
+
+<h3 id="数据库和其它依赖该如何选择?">数据库和其它依赖该如何选择?</h3>
+
+<p>其它依赖(例如数据库驱动程序、模板引擎、身份认证引擎等)是应用的一部分,使用 NPM 将它们引入到应用环境中。稍后进行讨论。</p>
+
+<h2 id="安装_Node">安装 Node</h2>
+
+<p>先在操作系统上安装 Node.js 和 NPM 后才可使用 Express。接下来将介绍如何最简便地在 Ubuntu 18.04、macOS Mojave 以及 Windows 10 上安装 Node.js 最新的 LTS 版本。.</p>
+
+<div class="note">
+<p><strong>提示:</strong>以下内容将介绍在上述三种 OS 上安装 Node 和 NPM 的最简便方法。对于其它操作系统,以及更多的安装方法,可以参考 <a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org).</p>
+</div>
+
+<h3 id="Windows_和_macOS">Windows 和 macOS</h3>
+
+<p>在 Windows 和 macOS 上安装 Node 和 NPM 非常简单明了,使用现成的安装包就行了:</p>
+
+<ol>
+ <li>下载安装包:
+ <ol>
+ <li>访问 <a href="https://nodejs.org/zh-cn/">https://nodejs.org/zh-cn/</a></li>
+ <li>左侧按钮上写着“推荐多数用户使用(LTS)”,点击下载。</li>
+ </ol>
+ </li>
+ <li>双击下载的安装包,按照提示即可安装。</li>
+</ol>
+
+<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3>
+
+<p>安装 Node 最新的 LTS 版本的最简便方法就是使用 <a href="https://nodejs.org/zh-cn/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages">包管理器</a>,可以直接从 Ubuntu 二进制发行仓库中下载。通过在终端运行以下两行简单的命令就可以做到:</p>
+
+<pre class="brush: bash"><code>curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
+sudo apt-get install -y nodejs</code>
+</pre>
+
+<div class="warning">
+<p><strong>警告:</strong>直接从 Ubuntu 默认仓库中下载的 Node 是 8.x 版本的。</p>
+</div>
+
+<ol>
+</ol>
+
+<h3 id="测试_Node.js_和_NPM_是否安装成功">测试 Node.js 和 NPM 是否安装成功</h3>
+
+<p>检查 Node 是否成功安装的最简单方法就是在终端(或命令行)中运行 "<code>version</code>" 命令,看是否返回版本号字符串:</p>
+
+<pre class="brush: bash">$ node -v
+v10.15.0</pre>
+
+<p>NPM 应该与 Node.js 一同成功安装,可以使用同样的方法来测试一下:</p>
+
+<pre class="brush: bash">$ npm -v
+6.7.0</pre>
+
+<p>下面的测试也许会带来小小激动:创建一个非常基础的“纯 Node”服务器,在浏览器中访问正确的 URL 地址时将直接打印 "Hello world":</p>
+
+<ol>
+ <li>以下代码使用了纯 Node 的特性(与 Express 无关)和一些 ES6 的语法,把它复制到 <strong>hellonode.js</strong> 文件中:
+
+ <pre class="brush: js">// 加载 HTTP 模块
+const http = require("http");
+const hostname = '127.0.0.1';
+const port = 3000;
+
+// 创建 HTTP 服务器
+const server = http.createServer((req, res) =&gt; {
+
+ // 用 HTTP 状态码和内容类型(Content-Type)设置 HTTP 响应头
+ res.statusCode = 200;
+ res.setHeader('Content-Type', 'text/plain');
+
+ // 发送响应体
+ res.end('Hello World\n');
+});
+
+// 监听 3000 端口的请求,注册一个回调函数记录监听开始
+server.listen(port, hostname, () =&gt; {
+ console.log(`服务器运行于 http://${hostname}:${port}/`);
+});
+</pre>
+
+ <p>代码导入了 <code>"http"</code> 模块,并用它(<code>createServer()</code>)创建了一个服务器来监听 3000 端口的 HTTP 请求。随后在控制台打印一条信息,提示测试服务器的正确 URL。<code>createServer()</code> 函数接受一个回调函数作为参数,并在接收 HTTP 请求后进行回调。直接返回了 HTTP 状态码 200 ("<code>OK</code>"),以及纯文本信息 "Hello World"。</p>
+
+ <div class="note">
+ <p><strong>注:</strong>现在看不懂这些代码请不要担心,开始使用 Express 后候会进行更加详细的解释。</p>
+ </div>
+ </li>
+ <li>在命令行工具中进入 hellonode.js 文件所在的目录,输入“node + 文件名”并运行,服务器就启动了:
+ <pre class="brush: bash">$ node hellonode.js
+服务器运行于 http://127.0.0.1:3000/
+</pre>
+ </li>
+ <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:3000/</a>),如果一切正常,浏览器会直接显示出 "Hello world" 字符串。</li>
+</ol>
+
+<h2 id="使用_NPM">使用 NPM</h2>
+
+<p>构建 Node 应用过程中,<a href="https://docs.npmjs.com/">NPM</a> 是除了 Node 本身之外最重要的工具。可用于获取应用开发、测试以及生产所需的所有包(JavaScript 库)。也可运行开发过程中使用的测试单元和工具。</p>
+
+<div class="note">
+<p><strong>注:</strong>以 Node 的角度来看,Express 只是一个用 NPM 安装、供人使用的包而已。</p>
+</div>
+
+<p>可以用 NPM 手动逐个安装所需包。但通常可用 <a href="https://docs.npmjs.com/files/package.json">package.json</a> 文件来管理依赖。把每个<font><font>依赖以一个</font></font> JavaScript “包”的形式(其中<font><font>包括名称、版本、描述,初始执行文件、生产依赖,开发依赖、支持的 </font></font><em><font><font>Node </font></font></em><font><font>版本,等等</font></font>)罗<font><font>列在这个文件中。package.json 文件包含 NPM 获取和运行应用程序所需的所有内容(在编写可重用的库时,可以用它把包上传到 NPM 仓库中供其他用户使用)。</font></font></p>
+
+<h3 id="添加依赖项">添加依赖项</h3>
+
+<p>下面介绍用 NPM 下载包、将包保存进工程依赖树,以及在 Node 应用中调用包的方法和步骤。</p>
+
+<div class="note">
+<p><strong>注:</strong>现在来讲解获取和安装 Express 包的步骤。稍后解释为什么可以直接对 Express 包(乃至其它包)使用 <strong>Express 应用生成器</strong>。这段对理解 NPM 的工作原理和应用生成器的工作机制有一定的帮助。</p>
+</div>
+
+<ol>
+ <li>首先为新应用创建一个目录,并进入它:
+ <pre class="brush: bash">$ mkdir myapp
+$ cd myapp</pre>
+ </li>
+ <li>然后,使用 NPM 的 init 命令为应用创建一个 <strong>package.json</strong> 文件。这个命令将请求一系列的信息,包括应用的名称和版本,程序初始进入点的文件名(默认为 <strong>index.js</strong>)。现在先接受默认信息即可:
+ <pre class="brush: bash">$ npm init</pre>
+
+ <p><strong>package.json</strong> 文件中保存了所接受的默认信息,最后一条是许可证信息:</p>
+
+ <pre class="brush: json">{
+ "name": "myapp",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
+</pre>
+ </li>
+ <li>接下来在 <strong>myapp</strong> 目录中安装 Express,用下面的命令将 Express 保存在 <strong>package.json</strong> 文件中的依赖表里:
+ <pre class="brush: bash">$ npm install express</pre>
+
+ <p>此时 <strong>package.json</strong> 文件的底部会出现依赖列表("dependencies"),其中包含 Express:</p>
+
+ <pre class="brush: json">{
+ "name": "myapp",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+<strong> "dependencies": {
+ "express": "^4.16.4"
+ }</strong>
+}
+</pre>
+ </li>
+ <li>可以调用 <code>require()</code> 函数来使用库:
+ <pre><code><strong>const express = require('express');
+</strong>const app = express();
+
+app.get('/', (req, res) =&gt; {
+ res.send('Hello World!')
+});
+
+app.listen(8000, () =&gt; {
+ console.log('示例程序正在监听 8000 端口!')
+});</code>
+</pre>
+
+ <p>以上代码展示了一个最简单的 "HelloWorld" Express 应用。它导入了 "express" 模块并用它创建了一个服务器(app)来监听 8000 端口,并且在控制台打印了一条信息以提示测试服务器的正确 URL。<code>app.get()</code> 函数只响应对特定路径(<code>'/'</code>)的 HTTP <code>GET</code> 请求,此处的响应就是发送 "Hello World!"。<br>
+ <br>
+ 在 myapp 应用的根目录下新建一个 <strong>index.js</strong> 文件,将上述代码粘贴进来并保存。</p>
+ </li>
+ <li>在命令行输入 node + 文件名 即可启动服务器:
+ <pre class="brush: bash">$ node index.js
+<code>示例程序正在监听 8000 端口!</code>
+</pre>
+ </li>
+ <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>),如果一切正常,浏览器会直接显示出 "Hello world!" 字符串。</li>
+</ol>
+
+<h3 id="开发依赖(Development_dependencies)">开发依赖(Development dependencies)</h3>
+
+<p>如果一个依赖只在开发过程中用到,应该将其保存为“开发依赖”(这样,包的用户便无需在生产环境中安装它们)。比如,如果要使用 <a href="http://eslint.org/">eslint</a>(一款流行的 JavaScript lint 工具)可以这样调用 NPM:</p>
+
+<pre class="brush: bash"><code>$ npm install eslint --save-dev</code></pre>
+
+<p>当前应用的 <strong>package.json </strong>文件中将自动添加以下项目:</p>
+
+<pre class="brush: js"> "devDependencies": {
+ "eslint": "^5.12.0"
+ }
+</pre>
+
+<div class="note">
+<p><strong>注:</strong>"<a href="https://zh.wikipedia.org/wiki/Lint">lint</a>" 是用于对软件进行静态分析的工具,可以发现并报告软件是否遵循某些最佳编程惯例。</p>
+</div>
+
+<h3 id="运行任务">运行任务</h3>
+
+<p>在 <strong>package.json</strong> 中,除了定义和获取依赖,还可以定义脚本,然后通过 NPM 的 <a href="https://docs.npmjs.com/cli/run-script">run-script</a> 命令来运行。这个用法普遍用于自动运行测试单元或部分应用,也可用于构建工具链(比如运行工具来压缩 JavaScript 文件或图片,lint 或分析代码,等等)。</p>
+
+<div class="note">
+<p><strong>注:</strong><a href="http://gulpjs.com/">Gulp</a> 和 <a href="http://gruntjs.com/">Grunt</a> 等任务运行器可用于运行测试单元或其它外部工具。</p>
+</div>
+
+<p>比如,可以在 <strong>package.json</strong> 文件中添加以下内容来定义一个脚本,从而对上文的代码运行 eslint(假设应用代码在 /src/js 文件夹下):</p>
+
+<pre class="brush: js">"scripts": {
+ ...
+ "lint": "eslint src/js"
+ ...
+}
+</pre>
+
+<p>深入解释一下,eslint src/js 命令可以在终端/命令行对应用目录下的 src/js 目录中的 JavaScript 文件运行 eslint。把上面一段脚本添加进应用的 package.json 中还可以为此命令提供一个快捷方式—— lint。</p>
+
+<p>然后就可以用 NPM 这样运行 eslint 了:</p>
+
+<pre class="brush: bash"><code>$ npm run-script lint</code></pre>
+
+<p>或使用别名:</p>
+
+<pre class="brush: bash"><code>$ npm run lint</code></pre>
+
+<p>这个示例看上去并没有让原始命令简洁多少,但在 NPM 脚本中可以加入更长的命令,甚至是多命令链。比如可以让单一的 NPM 脚本来一次运行所有的测试单元。</p>
+
+<h2 id="安装_Express_应用生成器">安装 Express 应用生成器</h2>
+
+<p><a href="https://expressjs.com/en/starter/generator.html">Express 应用生成器</a> 工具可以生成一个 Express 应用的“框架”。可以用 NPM 这样安装它(-g 参数可以把该工具全局安装,那样就可以在任意应用中使用了):</p>
+
+<pre class="brush: bash"><code>$ npm install express-generator -g</code></pre>
+
+<p>进入应用目录,运行以下命令,即可创建一个名为 "helloworld" 的 Express 应用:</p>
+
+<pre class="brush: bash">$ express helloworld</pre>
+
+<div class="note">
+<p><strong>注:</strong>也可以指定模板库来使用其它丰富的设置。可通过 help 命令来查看所有选项:</p>
+
+<pre class="brush: bash">$ express --help
+</pre>
+</div>
+
+<p>NPM 将在当前位置的子目录中创建新的 Express 应用,可以在控制台看到构建的过程。在完成时,NPM 会提示你需要安装哪些 Node 依赖,以及如何开启应用。</p>
+
+<div class="note">
+<p>新应用的根目录有一个 <strong>package.json</strong> 文件。可以打开它看看都安装了哪些依赖,其中可以看到 Express 和 Jade 模板库:</p>
+
+<pre class="brush: js">{
+ "name": "helloworld",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.9",
+ "express": "~4.16.0",
+ "http-errors": "~1.6.2",
+ "jade": "~1.11.0",
+ "morgan": "~1.9.0"
+ }
+}</pre>
+</div>
+
+<p>用下列命令可为 helloworld 应用安装所有依赖:</p>
+
+<pre class="brush: bash">$ cd helloworld
+$ npm install
+</pre>
+
+<p>然后运行这个应用(Windows 环境):</p>
+
+<pre class="brush: bash">&gt; SET DEBUG=helloworld:* &amp; npm start
+</pre>
+
+<p>(Linux/macOS 环境):</p>
+
+<pre class="brush: bash">$ DEBUG=helloworld:* npm start</pre>
+
+<p>DEBUG 命令可以展示应用运行时返回的有用的日志信息,如下所示:</p>
+
+<p><img alt="设置 DEBUG 命令显示的日志信息" src="https://mdn.mozillademos.org/files/16404/debug.png"></p>
+
+<p>打开浏览器并访问 <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> 将看到 Express 的默认欢迎页面。</p>
+
+<p><img alt="生成应用的默认主页" src="https://mdn.mozillademos.org/files/16405/express.png"></p>
+
+<p>稍后在创建应用框架一节中将讨论生成应用的具体细节。</p>
+
+<ul>
+</ul>
+
+<h2 id="小结">小结</h2>
+
+<p>现在 Node 开发环境已经配置好了,可以用于创建 Express 应用了。你还了解了用 NPM 导入 Express 的步骤,以及如何创建(使用 Express 应用生成器)和运行 web 应用。</p>
+
+<p>下一节将开始用上述的环境和工具通过实战逐步搭建一个完整的 web 应用。</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li><a href="https://nodejs.org/zh-cn/download/">Node.js 下载页面</a> (nodejs.org 官方中文页面)</li>
+ <li><a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org 官方中文页面)</li>
+ <li><a href="http://www.expressjs.com.cn/starter/installing.html">安装 Express</a> (expressjs.com.cn 中文镜像页面)</li>
+ <li><a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用程序生成器</a> (expressjs.com.cn 中文镜像页面)</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="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html
new file mode 100644
index 0000000000..c62c5fbbec
--- /dev/null
+++ b/files/zh-cn/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>Book</code>的列表。</p>
+
+<h2 id="控制器">控制器</h2>
+
+<p>打开 <strong>/controllers/authorController.js</strong>。</p>
+
+<p>在档案最上方,加入底下几行,引入 async 和 Book 模组(作者细节页面需要它们)。</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 个要求都成功完成,就运行回调。这个方式,就跟前面的种类细节页面所说明的完全相同。</p>
+
+<h2 id="视图">视图</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>。选择All Authors 连结,然后选择一个作者。如果每个东西都设定正确了,你的网站看起来应该会像底下的截图。</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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 的下一个部分 : <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge"> 书本实例细节页面和自我挑战 </a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html
new file mode 100644
index 0000000000..055ae91e1c
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html
@@ -0,0 +1,89 @@
+---
+title: 作者清单面页、分类清单页面、与自我挑战
+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="控制器"><span class="highlight-span">控制器</span></h2>
+
+<p>作者列表控制器函数,需要获取所有作者实例的列表,然后将这些实例传递给模板进行渲染。</p>
+
+<p>打开<strong>/controllers/authorController.js</strong>。在文件顶部附近,找到导出的<code>author_list()</code> 控制器方法,并将其替换为以下代码(更改后的代码以粗体显示)。</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>该方法使用模型的 <code>find()</code>, <code>sort()</code> 和 <code>exec()</code> 函数,以返回所有<code>Author</code>对象,并按<code>family_name</code>的字母顺排列。传递给<code>exec()</code>方法的回调被调用,并将传入任何错误(或<code>null</code>)作为第一个参数,或者成功时,传入所有作者列表。如果出现错误,则调用带有错误值的下一个中间件函数,如果没有错误,则呈现<strong> author_list</strong>(.pug)模板,传递页面标题<code>title,</code>和作者列表(<code>author_list</code>)。</p>
+
+<h2 class="highlight-spanned" id="视图">视图</h2>
+
+<p>打开 <strong>/views/author_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 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>如同我们其它的模板,上面视图也依照着同样的模式。</p>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行本应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a> 。然后选择所有作者 All authors 连结。如果每个东西都设定正确了,页面看起来应该像底下的截图。</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>注意:</strong> 作者生命日期的外观是丑陋的!您可以使用我们用于<code>BookInstance</code> 列表的<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">相同方法</a>(将生命周期的虚拟属性,添加到 <code>Author</code> 模型),来改进此方法。</p>
+
+<p>但是,这次缺少日期,除非严格模式生效,否则将忽略对不存在的属性的引用。<code>moment()</code>返回当前时间,并且您不希望将缺少的日期格式化为就像今天一样。</p>
+
+<p>解决此问题的一种方法,是定义返回格式化日期的函数内容,以便返回空字符串,除非日期实际存在。例如:</p>
+
+<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p>
+</div>
+
+<h2 id="种类列表页面—自我挑战!Edit">种类列表页面—自我挑战!<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>在这个部分,你应该实作你自己的种类列表页面。该页面应显示数据库中所有种类的列表,每个种类都链接到其关联的详细信息页面。预期结果的屏幕截图如下所示。</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>种类列表控制器功能,需要获取所有种类实例的列表,然后将这些实例传递给模板进行渲染。</p>
+
+<ol>
+ <li>您需要在 <strong>/controllers/genreController.js</strong> 中编辑<code>genre_list()</code>。</li>
+ <li>实现方式几乎与<code>author_list()</code>控制器完全相同。
+ <ul>
+ <li>按名称以上升顺序,对结果进行排序。</li>
+ </ul>
+ </li>
+ <li>要呈现的模板,应命名为 <strong>genre_list.pug</strong>。</li>
+ <li>要呈现的模板应该传递变量<code>title</code>('Genre List')和种类列表<code>genre_list</code>(从<code>Genre.find()</code>回调返回)。</li>
+ <li>该视图应与上面的屏幕截图/要求相匹配(这应该与作者列表视图具有非常相似的结构/格式,除了种类没有日期)。</li>
+</ol>
+
+<h2 id="下一步">下一步</h2>
+
+<p>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></p>
+
+<p>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">种类细节页面</a></p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html
new file mode 100644
index 0000000000..775bcd387f
--- /dev/null
+++ b/files/zh-cn/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>书本细节页面需要呈现一本指定书本(<code>Book</code>)的信息, 使用它的 <code>_id</code> 字段值(自动产生)做为识别,接着是图书馆中书本实例(<code>BookInstance</code>)的信息。无论我们在哪里呈现一个作者、种类、或书本实例,都应该连结到它的细节页面。</p>
+
+<h2 id="控制器">控制器</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 导入 async 和 BookInstance,当我们实作主页面控制器的时候,我们就已经引入这些模组。</p>
+</div>
+
+<p>此处的控制器方法使用 <code>async.parallel()</code>,用平行的方式找到 <code>Book</code> 以及它的相应复本 (<code>BookInstances</code>) 。这样的处理方式,就跟上面的 种类细节页面 所说明的完全相同。</p>
+
+<h2 id="视图">视图</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:]&amp;nbsp;
+    each val, index in book.genre
+      a(href=val.url) #{val.name}
+  if index &lt; 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:]&amp;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 &lt; book.genre.length - 1
+  |, </pre>
+</div>
+
+<h2 id="它看起來像是">它看起來像是?</h2>
+
+<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All books 连结,然后选择其中一本书。如果每个东西都设定正确了,你的页面看起来应该像是底下的截图。</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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者细节页面</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html
new file mode 100644
index 0000000000..bb76f61d50
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html
@@ -0,0 +1,70 @@
+---
+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="控制器"><span class="highlight-span">控制器</span></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="视图">视图</h2>
+
+<p>创建 <strong>/views/book_list.pug </strong>并复制底下的文字。</p>
+
+<p> </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>
+
+<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="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行本应用 (参见 <a href="https://developer.mozilla.org/en-US/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>。然后选择 所有书本 连结。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例列表页面</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html
new file mode 100644
index 0000000000..cba25ab30d
--- /dev/null
+++ b/files/zh-cn/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> 名称 (也是一个连结,连到 书本细节页面),接着是纪录中的其它的信息。</p>
+
+<h3 id="控制器">控制器</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="视图">视图</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>。选择 All book-instances 连结,然后选择其中一本。如果每个东西都设定正确了,你的网站看起来应该像是底下的截图。</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>生命周期日期显示的外观信息(死亡/出生日期)和BookInstance详细信息页面,使用格式:December 6th, 2016。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 您可以使用与我们用于 Book Instance List 的相同方法(将生命周期的虚拟属性,添加到<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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html
new file mode 100644
index 0000000000..1e8403f0a5
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html
@@ -0,0 +1,69 @@
+---
+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="控制器"><span class="highlight-span">控制器</span></h2>
+
+<p><code>BookInstance</code>列表控制器函数,需要获取所有书本实例的列表,填充关联的书本信息,然后将列表传递给模板以进行呈现。</p>
+
+<p>打开 <strong>/controllers/bookinstanceController.js</strong>。找到导出的 <code>bookinstance_list()</code> 控制器方法,并用以下代码替换它(更改后的代码以粗体显示)。</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="视图"><span class="highlight-span">视图</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>&lt;span class="text-success"&gt;</code> (也可以用 Pug 编写为 <code>span(class="text-success")</code>.</p>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化与使用 moment</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html
new file mode 100644
index 0000000000..8abbf8d290
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html
@@ -0,0 +1,58 @@
+---
+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>。在本节中,我们将展示如何更新上一节中的 書本實例 BookInstance 列表页面,以更友好的格式显示<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 模板中,使用 <em>moment </em>格式化字符串,或者可以在许多其它地方格式化字符串。使用虚拟属性,可以使我们获得格式化的日期,這与我们当前获取 <code>due_date</code> 的方式完全相同。</p>
+</div>
+
+<h2 class="highlight-spanned" id="安装_moment">安装<span class="highlight-span"> moment</span></h2>
+
+<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>在此页面最上方,引用 moment
+ <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="更新视图"><span class="highlight-span">更新视图</span></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>这就是本章节的全部了。如果你访问侧边栏的 All book-instances ,你应该会看到所有的归还日期都更吸引人了!</p>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者列表页面、种类列表页面、与自我挑战</a></li>
+</ul>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html
new file mode 100644
index 0000000000..f720812c50
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html
@@ -0,0 +1,139 @@
+---
+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>有些本地图书馆网页的控制器代码,会依赖多重非同步要求的结果,可能会需要以某种特定次序运行,或者以平行方式运行。为了管理流控制,并在我们所有需要用到的信息,都已经可以取用的时候,再绘制网页,我们将使用许多人采用的 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>
+
+<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>我们在 Express 中使用的大多数方法,都是异步的 - 您指定要执行的操作,传递回调。该方法立即返回,并在请求的操作完成时,调用回调。按照 Express 中的惯例,回调函数将错误值作为第一个参数传递(或成功时为 <code>null</code>),并将函数的结果(如果有的话)作为第二个参数传递。</p>
+
+<p>如果控制器只需要执行<strong>一个</strong>异步操作,来获取呈现页面所需的信息,那么实现很简单 - 我们只需在回调中呈现模板。下面的代码片段,显示了一个函数,该函数呈现模型 <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>一个更好的解决方案,是并行执行所有请求,然后在所有查询完成后执行单个回调。这是 Async 模块简化的流操作!</p>
+
+<h2 class="highlight-spanned" id="平行的非同步操作"><span class="highlight-span">平行的非同步操作</span></h2>
+
+<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code>用于并行运行多个异步操作。</p>
+
+<p><code>async.parallel()</code> 的第一个参数,是要运行的异步函数的集合(数组,对象或其他可迭代的)。每个函数都传递一个回调函数<code>callback(err, result)</code> ,它必须在完成时调用错误<code>err</code>(可以为<code>null</code>)和可选的结果值。</p>
+
+<p><code>async.parallel()</code>的可选第二个参数是一个回调,它将在第一个参数中的所有函数完成时运行。回调的调用,是使用错误参数和包含各个异步操作结果的结果集合。结果集合与第一个参数的类型相同(即,如果传递异步函数数组,则将使用结果数组,调用最终回调)。如果任何并行函数报告错误,则提前调用回调(具有错误值)。</p>
+
+<p>下面的示例,显示了当我们将对象作为第一个参数传递时它是如何工作的。如您所见,结果将返回到一个对象中,该对象具有与传入的原始函数相同的属性名称。</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>如果您将一组函数,作为第一个参数传递,则结果将是一个数组(数组顺序结果,将与声明函数的原始顺序匹配 - 而不是它们完成的顺序)。</p>
+
+<h2 class="highlight-spanned" id="序列的非同步操作"><span class="highlight-span">序列的非同步操作</span></h2>
+
+<p><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code>方法用于按顺序运行多个异步操作,后续函数不依赖于先前函数的输出。它本质上是声明的,并且行为与<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>注意:</strong> ECMAScript(JavaScript)语言规范指出,对象的枚举顺序是未定义的,因此可能不会按照在所有平台上指定它们的顺序,调用这些函数。如果顺序真的很重要,那么你应该传递一个数组而不是一个对象,如下所示。</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="依赖序列的非同步操作">依赖序列的非同步操作</h2>
+
+<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code>用于在每个操作依赖于前一个操作的结果时,依次运行多个异步操作。</p>
+
+<p>每个异步函数调用的回调,包含第一个参数的<code>null</code>,与后续参数里的结果。该序列中的每个函数,都将前一个回调的结果参数,作为第一个参数,然后是回调函数。</p>
+
+<p>完成所有操作后,将使用上一操作的结果,调用最终回调。当您参考下面的代码片段时,这种工作方式会更加明确(此示例来自 <em>async</em> 文档):</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="安装_async">安装<span class="highlight-span"> async</span></h2>
+
+<p>使用 NPM 包管理器安装 async 模块,以便我们可以在代码中使用它。您可以常规方式执行此操作,在 LocalLibrary 项目的根目录中,打开命令提示并输入以下命令:</p>
+
+<p> </p>
+
+<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模板入门</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html
new file mode 100644
index 0000000000..825474b9ab
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html
@@ -0,0 +1,120 @@
+---
+title: 种类细节页面
+slug: learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page
+translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page
+---
+<p>种类细节页面,需要利用<code>_id</code> 字段值 (自动生成) ,以呈现特定种类实例的信息。此页面应该呈现种类名称,各个种类的所有书本列表(每本书都连结到书本的细节页面)。</p>
+
+<h2 id="控制器">控制器</h2>
+
+<p>打开 <strong>/controllers/genreController.js</strong> ,并在档案最上方引用 async 和 Book 模组。</p>
+
+<pre class="brush: js">var Book = require('../models/book');
+var async = require('async');
+</pre>
+
+<p>找到导出的<code>genre_detail</code><code>()</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>该方法使用<code>async.parallel()</code>,并行查询类型名称及其相关联的书本,并在(如果)两个请求成功完成时,回调呈现页面。</p>
+
+<p>所需种类记录的 ID ,在 URL 的末尾编码,并根据路由定义(<strong>/genre/:id</strong>)自动提取。通过请求参数(<code style="font-style: normal; font-weight: normal;">req.params.id</code><code style="font-style: normal; font-weight: normal;">)</code>在控制器内访问 ID。它在<code style="font-style: normal; font-weight: normal;">Genre.findById()</code>中用于获取当前种类。它还用于获取符合当前种类的所有<code>Book</code>对象,就是在种类字段中具有种类ID的那些 <code>Book.find({ 'genre': req.params.id })</code>。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 如果数据库中不存在该类型(即它可能已被删除),则<code>findById()</code>将成功返回,但没有结果。在这种情况下,我们想要显示一个“未找到”页面,因此我们创建一个<code>Error</code>对象,并将其传递给链中的下一个中间件函数<code>next</code>。</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>然后,此消息将传播给我们的错误处理代码(这是在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">生成应用程序框架</a>时设置的 - 有关更多信息,请参阅<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">处理错误</a>)。</p>
+</div>
+
+<p>渲染的视图是 <strong>genre_detail</strong>,它传递了该类型的标题<code>title</code>,种类<code>genre</code>和书本列表的变量(<code>genre_books</code>)。</p>
+
+<h2 id="视图">视图</h2>
+
+<p>创建 <strong>/views/genre_detail.pug</strong> ,并填写底下文字:</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>这个视图跟我们其它的模板非常相似。主要的差别在于,我们不使用 <code>title</code> 传送第一个标题 (虽然它还是用在底层的 <strong>layout.pug</strong> 模板,设定页面的标题)。</p>
+
+<h2 id="它看起來像是">它看起來像是?</h2>
+
+<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All genres 连结,然后选择其中一个种类 (例如,"Fantasy")。如果每样东西都设定正确了,你的页面看起来应该像底下的截图。</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>您可能会收到与此类似的错误:</p>
+
+<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre"
+</pre>
+
+<p>这是来自 <strong>req.params.id</strong> 的 mongoose 错误。要解决这个问题,首先需要在<strong> genreController.js</strong> 页面上要求mongoose,如下所示:</p>
+
+<pre class="brush: js"> var mongoose = require('mongoose');
+</pre>
+然后使用 <strong>mongoose.Types.ObjectId()</strong>将 id 转换为可以使用的。例如:
+
+<pre class="brush: js">exports.genre_detail = function(req, res, next) {
+ var id = mongoose.Types.ObjectId(req.params.id);
+ ...
+</pre>
+</div>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本细节页面</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html
new file mode 100644
index 0000000000..27dbdb5788
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html
@@ -0,0 +1,134 @@
+---
+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 class="highlight-spanned" id="路由"><span class="highlight-span">路由</span></h2>
+
+<p>在 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">前面的教程</a>,我们创建 index 页面路由。此处要提醒的是,所有的路由函式,都定义在 <strong>/routes/catalog.js</strong>:</p>
+
+<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// GET catalog home page.</span>
+router<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="string token">'/'</span><span class="punctuation token">,</span> book_controller<span class="punctuation token">.</span>index<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="comment token">//This actually maps to /catalog/ because we import the route with a /catalog prefix</span></code></pre>
+
+<p>在 <strong>/controllers/bookController.js</strong> 中,定义回调函数参数(<code>book_controller.index</code>) :</p>
+
+<pre class="brush: js"><code>exports.index = function(req, res, next) {
+ res.send('NOT IMPLEMENTED: Site Home Page');
+}</code>
+</pre>
+
+<p>我们扩展这个控制器函数,以从我们的模型获取信息,然后使用模板(视图)渲染它。</p>
+
+<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2>
+
+<p>索引控制器函数需要获取以下有关信息,即数据库中有多少<code>Book</code>,<code>BookInstance</code>,可用的<code>BookInstance</code>,<code>Author</code>和<code>Genre</code>记录,将这些数据渲染到模板中,以创建HTML页面,然后将其返回到HTTP响应中。</p>
+
+<div class="note">
+<p><strong>Note:</strong> 我们使用<code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code> 方法来获取每个模型的实例数量。这在具有一组可选条件的模型上进行调用,以匹配第一个参数,而回调放在第二个参数(如<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">使用数据库</a><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">(Mongoose)</a>)中讨论的那样,并且还可以返回 <code>Query</code> ,然后稍后以回调执行它。当数据库返回计数时,将返回该回调,并将错误值(或空值<code>null</code>)作为第一个参数,并将记录计数(如果存在错误,则返回null)作为第二个参数。</p>
+
+<pre class="brush: js line-numbers language-js"><code class="language-js">SomeModel<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span> a_model_field<span class="punctuation token">:</span> <span class="string token">'match_value'</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> count<span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="comment token">// ... do something if there is an err</span>
+ <span class="comment token">// ... do something with the count if there was no error</span>
+ <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre>
+</div>
+
+<p>打开 <strong>/controllers/bookController.js</strong>. 在文件顶部附近,您应该看到导出的 <code>index()</code> 函数。</p>
+
+<pre class="brush: python line-numbers language-python"><code class="language-python">var Book = require('../models/book')
+
+exports.index = function(req, res, next) {
+ res.send('NOT IMPLEMENTED: Site Home Page');
+}</code></pre>
+
+<p>用以下代码片段替换上面的所有代码。这要做的第一件事,是导入(<code>require()</code>)所有模型(以粗体突出高亮显示)。我们需要这样做,是因为我们将使用它们来获取记录的计数。然后它会导入异步模块<em> async</em> 。</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>
+<span class="keyword token">var</span> Author <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/author'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+<span class="keyword token">var</span> Genre <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/genre'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+<span class="keyword token">var</span> BookInstance <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/bookinstance'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+
+<span class="keyword token">var</span> <span class="keyword token">async</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'async'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+
+exports<span class="punctuation token">.</span>index <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> <span class="punctuation token">{</span>
+
+ <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>
+ book_count<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>
+ Book<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">); // Pass an empty object as match condition to find all documents of this collection</span>
+ <span class="punctuation token">}</span><span class="punctuation token">,</span>
+ book_instance_count<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>
+ BookInstance<span class="punctuation token">.</span><span class="function token">count</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>
+ book_instance_available_count<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>
+ BookInstance<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span>status<span class="punctuation token">:</span><span class="string token">'Available'</span><span class="punctuation token">}</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>
+ author_count<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>
+ Author<span class="punctuation token">.</span><span class="function token">count</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>
+ genre_count<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>
+ Genre<span class="punctuation token">.</span><span class="function token">count</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="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>
+ res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'index'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Local Library Home'</span><span class="punctuation token">,</span> error<span class="punctuation token">:</span> err<span class="punctuation token">,</span> data<span class="punctuation token">:</span> results <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>async.parallel()</code> 方法传递一个对象,其中包含用于获取每个模型计数的函数。这些函数都是在同一时间开始的。当这些函数全部完成时,最终回调将与结果参数中的计数(或错误)一起被调用。</p>
+
+<p>成功时,回调函数调用 <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>,指定名为 '<strong>index</strong>' 的视图(模板),以及一个对象包含了要插入其中的数据 (这包括我们模型计数的结果对象)。数据以键值对的形式提供,可以使用键在模板中访问。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 上面的<code>async.parallel()</code>裡的回调函数有点不寻常,因为不管是否出现错误,我们都会渲染页面(通常您可能使用单独的执行路径来处理错误的显示)。</p>
+</div>
+
+<h2 class="highlight-spanned" id="视图">视图</h2>
+
+<p>打开 <strong>/views/index.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
+ p Welcome to #<span class="punctuation token">[</span>em LocalLibrary<span class="punctuation token">]</span><span class="punctuation token">,</span> a very basic Express website developed <span class="keyword token">as</span> a tutorial example on the Mozilla Developer Network<span class="punctuation token">.</span>
+
+ h1 Dynamic content
+
+ <span class="keyword token">if</span> error
+ p Error getting dynamic content<span class="punctuation token">.</span>
+ <span class="keyword token">else</span>
+ p The library has the following record counts<span class="punctuation token">:</span>
+
+ ul
+ li #<span class="punctuation token">[</span>strong Books<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_count<span class="punctuation token">}</span>
+ li #<span class="punctuation token">[</span>strong Copies<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_count<span class="punctuation token">}</span>
+ li #<span class="punctuation token">[</span>strong Copies available<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_available_count<span class="punctuation token">}</span>
+ li #<span class="punctuation token">[</span>strong Authors<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>author_count<span class="punctuation token">}</span>
+ li #<span class="punctuation token">[</span>strong Genres<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>genre_count<span class="punctuation token">}</span></code></pre>
+
+<p>这个视图很简单。我们扩展了 <strong>layout.pug</strong> 基本模板,覆盖了名为 '<strong>content</strong>' 的模块 <code>block</code>。第一个<code>h1</code>标题,将是传递给<code>render()</code>函数的<code>title</code> 变量的转义文本 — 请注意 '<code>h1=</code>' 的使用方式,将使得接下來的文本,被视为 JavaScript 表达式。然后我们放入一个介绍本地图书馆的段落。</p>
+
+<p>在动态内容标题下,我们检查从<code>render()</code>函数传入的错误变量,是否已定义。如果是这样,我们列出这个错误。如果不是,我们从<code>data</code>变量中,获取并列出每个模型的副本数量。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 我们没有转义计数值 (i.e. 我们使用 <code>!{}</code> 语法) ,因为计数值已经被计算过了。如果信息是由终端用户提供的,那么我们就会转义該变量,以用于显示。</p>
+</div>
+
+<h2 class="highlight-spanned" id="它看起来像是">它看起来像是?</h2>
+
+<p>此处,我们应该已经创建了呈现index页面,所需要的每样东西。运行本地图书馆应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</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>注意: </strong>您将无法使用侧边栏链接,因为这些网页的网址,视图和模板尚未定义。例如,如果您尝试,取决于您点击的链接,您将获取“尚未实作:图书清单”等错误。在“控制器”文件中的不同控制器中,會指定这些字符串文字(将被合适的数据替换)。</p>
+</div>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本列表页面</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html
new file mode 100644
index 0000000000..b59601e248
--- /dev/null
+++ b/files/zh-cn/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">我们现在准备好要新增网页,以显示<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆网站</a>的书本与其它资料。这些网页将包含一个主页 ,显示我们拥有的每个模型的记录数,以及所有模型的清单和详细信息页面。借此,我们将获得从数据库获取记录、以及使用模板的实战经验。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前置条件:</th>
+ <td>完成先前教程主题 (包含 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 Part 4: 路由与控制器</a>)。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>
+ <p>了解如何使用异步模组与Pug 样版语言,以及如何从我们的控制器函数中的URL取得信息。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p>在我们先前的教程中,定义了可以用来跟资料库互动的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose models</a> ,并创建了一些初始的图书馆记录。我们接着<a href="/en-US/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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">使用 async 进行异步流控制</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模版入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">本地图书馆基础样版</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本清单页面</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例清单页面</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化-使用 moment</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者清单页面、分类清单页面</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">、</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">与自我挑战</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">分类详情页面</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本详情页面</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者详情页面</a></li>
+ <li><a href="/zh-CN/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 </a>模组 (Async 模组官方文件)</li>
+ <li><a href="https://expressjs.com/en/guide/using-template-engines.html">在Express中使用模板引擎</a> (Express 官方文件)</li>
+ <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug 官方文件)</li>
+ <li><a href="http://momentjs.com/docs/">Moment</a> (Moment 官方文件)</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p>
+
+
+
+<h2 id="本教程文章列表">本教程文章列表</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 新建网站骨架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html
new file mode 100644
index 0000000000..41d851e7d7
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html
@@ -0,0 +1,69 @@
+---
+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>现在我们了解如何使用Pug拓展模板,让我们开始项目,创建一个基础模板。这个模板会有一个侧边栏,连结到本教程中将要创建的各个页面(例如,呈现并创建书本、种类、作者等等),以及一个主要内容区域,我们将在每个页面中进行覆写。</p>
+
+<p>开启 <strong>/views/layout.pug</strong> ,并以下列代码置换其内容。</p>
+
+<pre class="brush: html line-numbers language-html notranslate"><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="https://developer.mozilla.org/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 notranslate"><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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5 的下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html
new file mode 100644
index 0000000000..374957bb1b
--- /dev/null
+++ b/files/zh-cn/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>模板是一个文字档,定义了一个输出档的结构或者排版,使用定位符号表示,当模板被绘制时,资料将插入到何处(在Express,模板被称为视图)。</p>
+
+<h2 id="Express_模板选择">Express 模板选择</h2>
+
+<p>Express 可以与许多不同的<a href="https://expressjs.com/en/guide/using-template-engines.html">模板渲染引擎</a>一起使用。在本教程中,我们使用<a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>(以前称为Jade)作为模板。这是最流行的 Node 模板语言,并且官方将自身描述为 “用于编写HTML,语法干净且空格敏感,受 <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml</a>影响很大”。</p>
+
+<p>不同的模板语言使用不同的方法,来定义布局和标记数据的占位符 —  一些使用 HTML 来定义布局,而另一些则使用可以编译为 HTML 的不同标记格式。Pug 是第二种类型;它使用 HTML 的表示形式,其中任何行中的第一个单词,通常表示HTML元素,后续行中的缩进,用于表示嵌套在这些元素中的任何内容。结果是一个页面定义直接转换为 HTML,但可以说更简洁,更容易阅读。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 使用 Pug 的缺点,是它对缩进和空格敏感(如果在错误的位置添加额外的空格,可能会得到没什么帮助的错误代码)。但是,一旦您的模板到位,它们就很容易阅读和维护。</p>
+</div>
+
+<h2 class="highlight-spanned" id="模板组态"><span class="highlight-span">模板</span>组态</h2>
+
+<p>在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">创建骨架网站</a>时,LocalLibrary 配置为使用 <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>。您应该看到 Pug 模块作为依赖项,包含在网站的 <strong>package.json</strong>文件中,以及 <strong>app.js</strong>文件中的以下配置设置。设置告诉我们,使用 Pug 作为视图引擎,Express 应该在<strong> /views</strong>子目录中搜索模板。</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>如果查看 views 目录,您将看到项目默认视图的 .pug 文件。这包括需要用自己的内容替换的主页(<strong>index.pug</strong>)和基本模板(<strong>layout.pug</strong>)的视图。</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="模板语法">模板语法</h2>
+
+<p>下面的示例模板文件,展示了许多 Pug 最有用的功能。</p>
+
+<p>首先要注意的是,该文件映射典型 HTML 文件的结构,其中(几乎)每一行中的第一个单词是 HTML 元素,并且缩进用于指示嵌套元素。因此,例如,<code>body</code> 本文元素位于 <code>html</code> 元素内,而段落元素(<code>p</code>)位于 <code>body</code> 元素内等。非嵌套元素(例如,各个段落)位于不同的行上。</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">&lt;</span>em</span><span class="punctuation token">&gt;</span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>em</span><span class="punctuation token">&gt;</span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>'}.
+ | This line follows on.
+ p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>em</span><span class="punctuation token">&gt;</span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>:' + title
+
+ <span class="comment token">&lt;!-- You can add HTML comments directly --&gt;</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>元素属性被定义在其关联元素之后的括号中。在括号内,属性定义在以逗号或空格分隔的属性名称和属性值对的列表中,例如:</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>所有属性的值都被转义(例如 “<code>&gt;</code>” 等字符转换为 HTML 代码等效项,如“<code>&amp;gt;</code>”),以防止注入 JavaScript 或跨站点脚本攻击。</p>
+
+<p>如果标记后跟着等号,则以下文本将被视为 JavaScript 表达式。因此,打个比方,在下面的第一行中,<code>h1</code>标记的内容将是标题变量<code>title</code>(在文件中定义,或从 Express 传递到模板中)。在第二行中,段落内容是与标题变量<code>title</code>连接的文本字符串。在这两种情况下,默认行为是转义该行。</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">&lt;</span>em</span><span class="punctuation token">&gt;</span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>:' + title</code></pre>
+
+<p>如果标记后面没有等号,则将内容视为纯文本。在纯文本中,您可以使用<code>#{}</code> 和<code>!{}</code>语法,插入转义和非转义数据,如下所示。您还可以在纯文本中添加原始 HTML。</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">&lt;</span>em</span><span class="punctuation token">&gt;</span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>'}, an escaped string: #{'<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>em</span><span class="punctuation token">&gt;</span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>em</span><span class="punctuation token">&gt;</span></span>'}, and escaped variables: #{title}.</code></pre>
+
+<div class="note">
+<p><strong>提示:</strong> 您几乎总是希望转义来自用户的数据(通过<strong><code>#{}</code></strong>语法)。可信任的数据(例如,生成的记录计数等)可以不先转义就显示。</p>
+</div>
+
+<p>您可以在行的开头使用管道(“<strong>|</strong>”)字符来表示“<a href="https://pugjs.org/language/plain-text.html">纯文本</a>”。例如,下面显示的附加文本,将显示在与前一个锚点相同的行上,但不会链接。</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 允许您使用<code>if</code>, <code>else</code> , <code>else if</code> 和 <code>unless</code>执行条件操作 - 例如:</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>以使用<code>each-in</code> 或 <code>while</code>语法执行循环/迭代操作。在下面的代码片段中,我们循环遍历数组,以显示变量列表(注意,使用 'li =' 来评估 “val” ,以作为下面的变量。)迭代的值也可以传递给模板作为变量!</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>语法还支持注释(可以在输出中呈现 - 或者不是 - 可自行选择),支持mixins创建可重用的代码块,case语句和许多其他功能。有关更多详细信息,请参阅<a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>文档。</p>
+
+<h2 class="highlight-spanned" id="扩展模板">扩展模板</h2>
+
+<p>在一个站点中,通常所有页面都有一个共同的结构,包括页首,页脚,导航等的标准HTML标记。比起强迫开发人员在每个页面中复制这个 “样板”的做法,Pug 允许你声明一个基本模板,然后扩展它,只替换每个特定页面不同的地方。</p>
+
+<p>例如,在我们的<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">骨架项目</a>中,创建的基本模板 <strong>layout.pug</strong>,如下所示:</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>块标记<code>block</code>用于标记 “可在派生模板中替换的内容部分“(如果未重新定义块,则使用其在基类中的实现)。</p>
+
+<p>默认的 <strong>index.pug</strong>(为我们的骨架项目所创建),显示了我们如何覆盖基本模板。<code>extends</code>标记,标识要使用的基本模板,然后我们使用 <code>block <em>section_name</em></code> ,来指示我们将覆盖的部分的新内容。</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="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li>继续教程 5下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">图书馆基本模板</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html
new file mode 100644
index 0000000000..84a1b71db3
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html
@@ -0,0 +1,155 @@
+---
+title: 创建作者表单
+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>Ed</span></a>本章节演示,如何为创建作者对象<code>Author</code>定义一个页面。</p>
+
+<h2 class="highlight-spanned" id="导入验证和清理方法">导入验证和清理方法</h2>
+
+<p>为了在种类表单使用express验证器,我们必须用 require 导入我们想用的函式。</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="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2>
+
+<p>找到导出的 <code>author_create_get()</code>控制器方法,并替换为底下代码。这里单纯呈现 <strong>author_form.pug</strong> 视图,传送 <code>title</code> 变数。</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="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2>
+
+<p>找到导出的 <code>author_create_post()</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">&gt;</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>此代码的结构和行为,几乎与创建<code>Genre</code>对象完全相同。首先,我们验证并清理数据。如果数据无效,那么我们将重新显示表单,以及用户最初输入的数据,和错误消息列表。如果数据有效,那么我们保存新的作者记录,并将用户重定向到作者详细信息页面。</p>
+
+<div class="note">
+<p><strong>注意:</strong>与<code>Genre</code> post处理程序不同,我们不会在保存之前,检查<code>Author</code>对象是否已存在。可以说,我们应该这样做,尽管现在我们可以有多个具有相同名称的作者。</p>
+</div>
+
+<p>验证代码演示了几个新功能:</p>
+
+<ul>
+ <li>我们可以用菊花链式连接验证器,使用<code>withMessage()</code>指定在前一个验证方法失败时,显示的错误消息。这使得在没有大量代码重复的情况下,提供特定的错误消息变得非常容易。
+
+ <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>我们可以使用<code>optional()</code>函数,仅在输入字段时运行后续验证(这允许我们验证可选字段)。例如,下面我们检查可选的出生日期是否符合 ISO8601 标准(<code>checkFalsy</code> 旗标,表示我们接受空字符串或<code>null</code>作为空值)。
+ <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>参数从请求中作为字符串接收。我们可以使用<code>toDate()</code>(或<code>toBoolean()</code>等)将这些转换为正确的JavaScript类型。
+
+ <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="视图"><span class="highlight-span">视图</span></h2>
+
+<p>创建 <strong>/views/author_form.pug</strong> 并复制贴上以下文字。</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>此视图的结构和行为与<strong>genre_form.pug</strong>模板完全相同,因此我们不再对其进行描述。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 某些浏览器不支持input <code>type=“date”</code>,因此您不会获得日期选取部件或默认的<em><code>dd/mm/yyyy</code></em>占位符,而是获取一个空的纯文本字段。一种解决方法,是明确添加属性<code>placeholder='dd/mm/yyyy'</code>,以便在功能较少的浏览器上,仍然可以获得有关所需文本格式的信息。</p>
+</div>
+
+<h3 id="自我挑战_加入死亡日期">自我挑战: 加入死亡日期</h3>
+
+<p>上面的模板少了一个输入字段 <code>date_of_death</code> 。依照跟生日表单同样的模式,创建此字段!</p>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行本应用,打开浏览器访问网址<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后点击创建新作者 Create new author 连结。如果每个东西都设定正确了,你的网站看起应该像底下的截图。在你输入一个值之后,它应该会被储存,并且你将被带到作者详细信息页面。</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>注意:</strong> 如果您尝试使用日期的各种输入格式,您可能会发现格式<code>yyyy-mm-dd</code>行为不正常。这是因为 JavaScript 将日期字符串,视为包含 0 小时的时间,但另外将该格式的日期字符串(ISO 8601标准)视为包括 0 小时 UTC 时间,而不是本地时间。如果您的时区在 UTC 以西,则日期显示(即本地)将在您输入的日期之前一天。这是我们在这里没有解决的几个复杂问题之一(例如多字姓和有多个作者的书本)。</p>
+</div>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">创建书本表单</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html
new file mode 100644
index 0000000000..4b460b36ea
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html
@@ -0,0 +1,212 @@
+---
+title: 创建书本表单
+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>
+
+<h2 class="highlight-spanned" id="导入验证和清理方法">导入验证和清理方法</h2>
+
+<p>打开 <strong>/controllers/bookController.js</strong>,并在文件顶部添加以下行:</p>
+
+<pre class="brush: js line-numbers language-js">const { body,validationResult } = require('express-validator/check');
+const { sanitizeBody } = require('express-validator/filter');</pre>
+
+<h2 class="highlight-spanned" id="控制器—get_路由">控制器—get 路由</h2>
+
+<p>找到导出的<code>book_create_get()</code> 控制器方法,并将其替换为以下代码。</p>
+
+<pre class="brush: js line-numbers 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 });
+ });
+
+};</pre>
+
+<p>这使用异步模块 async(在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">教程5:显示数据库中的数据</a>),来获取所有作者和种类对象。然后将它们作为名为<code>authors</code>和<code>genres</code>的变量(以及页面标题<code>title</code>),传递给视图<code><strong>book_form.pug</strong></code>。</p>
+
+<h2 class="highlight-spanned" id="控制器—post_路由">控制器—post 路由</h2>
+
+<p>找到导出的<code>book_create_post()</code>控制器方法,并将其替换为以下代码。</p>
+
+<pre class="brush: js line-numbers language-js">// Handle book create on POST.
+exports.book_create_post = [
+ // Convert the genre to an array.
+ (req, res, next) =&gt; {
+ 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(),
+ sanitizeBody('genre.*').escape(),
+ // Process request after validation and sanitization.
+ (req, res, next) =&gt; {
+
+ // 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 &lt; results.genres.length; i++) {
+ if (book.genre.indexOf(results.genres[i]._id) &gt; -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);
+ });
+ }
+ }
+];</pre>
+
+<p>此代码的结构和行为,几乎与创建种类<code>Genre</code>或作者<code>Author</code>对象完全相同。首先,我们验证并清理数据。如果数据无效,那么我们将重新显示表单,以及用户最初输入的数据,和错误消息列表。如果数据有效,我们将保存新的<code>Book</code>记录,并将用户重定向到<code>Book</code>详细信息页面。</p>
+
+<p>与其他表单处理代码相关的第一个主要区别,是我们使用通配符,一次修剪和转义所有字段(而不是单独清理它们):</p>
+
+<pre class="brush: js line-numbers language-js">sanitizeBody('*').trim().escape(),</pre>
+
+<p>与其他表单处理代码相关的下一个主要区别,是我们如何清理种类<code>Genre</code>信息。表单返回一个<code>Genre</code>项的数组(而对于其他字段,它返回一个字符串)。为了验证信息,我们首先将请求转换为数组(下一步需要)。</p>
+
+<pre class="brush: js line-numbers language-js">// Convert the genre to an array.
+(req, res, next) =&gt; {
+ 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();
+},</pre>
+
+<p>然后,我们在清理器中使用通配符(*)来单独验证每个种类数组条目。下面的代码显示了 - 这转换为 “清理关键种类<code>genre</code>下的每个项目”。</p>
+
+<pre class="brush: js line-numbers language-js">sanitizeBody('genre.*').trim().escape(),</pre>
+
+<p>与其他表单处理代码的最终区别,在于我们需要将所有现有的种类和作者传递给表单。为了标记用户已经检查过的种类,我们遍历所有种类,并将<code>checked='true'</code>参数,添加到我们的 POST 数据中(如下面的代码片段中所示)。</p>
+
+<pre class="brush: js line-numbers language-js">// Mark our selected genres as checked.
+for (let i = 0; i &lt; results.genres.length; i++) {
+ if (book.genre.indexOf(results.genres[i]._id) &gt; -1) {
+ // Current genre is selected. Set "checked" flag.
+ results.genres[i].checked='true';
+ }
+}</pre>
+
+<h2 class="highlight-spanned" id="视图">视图</h2>
+
+<p>创建 <strong>/views/book_form.pug</strong>,并复制下面的文本。</p>
+
+<pre class="line-numbers 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</pre>
+
+<p>视图结构和行为与 <strong>genre_form.pug</strong> 模板几乎相同。</p>
+
+<p>主要区别在于,我们如何实现选择类型字段:作者<code>Author</code>和种类<code>Genre</code>。</p>
+
+<ul>
+ <li>种类集合显示为复选框,使用我们在控制器中设置的检查值<code>checked</code>,来确定是否应该选中该框。</li>
+ <li>作者集合显示为单选下拉列表。在这种情况下,我们通过比较当前作者选项的 id 与用户先前输入的值(作为<code>book</code>变量传入),来确定要显示的作者。这在上面突出显示!
+ <div class="note">
+ <p><strong>注意:</strong> 如果提交的表单中存在错误,那么,当要重新呈现表单时,新的书本作者仅使用字符串(作者列表中选中选项的值)进行标识。相比之下,现有的书本作者的<code>_id</code>属性不是字符串。因此,要比较新的和现有的,我们必须将每个现有书本作者的<code>_id</code>,强制转换为字符串,如上所示。</p>
+ </div>
+ </li>
+</ul>
+
+<h2 class="highlight-spanned" id="它看起來像是">它看起來像是?</h2>
+
+<p>运行应用程序,将浏览器打开到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000</a>,然后选择Create new book链接。如果一切设置正确,您的网站应该类似于以下屏幕截图。提交有效的图书后,应将其保存,然后您将进入图书详细信息页面。</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="下一步">下一步</h2>
+
+<p>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></p>
+
+<p>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">创建书本实例表单</a></p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html
new file mode 100644
index 0000000000..6809ec4e93
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html
@@ -0,0 +1,150 @@
+---
+title: 创建书本实例表单
+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="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2>
+
+<p>在档案最上方,用 require 导入书本模型 (因为每个<code>BookInstance</code> 都有关连的 <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>找到导出的 <code>bookinstance_create_get()</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>控制器取得所有书本的列表 (<code>book_list</code>) 并将它传送到视图 <code><strong>bookinstance_form.pug</strong></code> (里面附加上 <code>title</code>)。</p>
+
+<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2>
+
+<p>找到导出的  <code>bookinstance_create_post()</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">&gt;</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>此代码的结构和行为,与创建其他对象的结构和行为相同。首先,我们验证数据,并為数据做無害化處理。如果数据无效,我们会重新显示表單,以及用户最初输入的数据,還有错误消息列表。如果数据有效,我们保存新的<code>BookInstance</code>记录,并将用户重定向到详细信息页面。</p>
+
+<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2>
+
+<p>创建 <strong>/views/bookinstance_form.pug</strong> ,并复制贴上以下代码。</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>这个视图的结构和行为,几乎等同于 <strong>book_form.pug</strong> 模板,因此我们就不再重覆说明一次了。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 以上的模板将状态值 (Maintenance, Available, 等等) 写死在代码里,而且不能 "记忆" 使用者的输入值。如果你愿意的话,考虑重新实作此列表,当表单被重新呈现时,从控制器传入选项数据,并设定选中的值。</p>
+</div>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行本应用,打开浏览器访问网址 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后点击创建新书本实例 Create new book instance (copy) 连结。如果每个东西都设定正确了,你的网站看起应该像底下的截图。在你提交一个有效的 <code>BookInstance</code> 之后,它应该会被储存,并且你将被带到详细信息页面。</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="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">删除作者表单</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html
new file mode 100644
index 0000000000..ffee2341cf
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html
@@ -0,0 +1,298 @@
+---
+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>name</code>,没有依赖项)。像任何其他页面一样,我们需要设置路由,控制器和视图。</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="控制器—get路由"><span class="highlight-span">控制器—get路由</span></h2>
+
+<p>找到导出的<code>genre_create_get()</code> 控制器方法,并将其替换为以下代码。这只是渲染<strong>genre_form.pug</strong>视图,传递一个title变量。</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="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2>
+
+<p>找到导出的<code>genre_create_post()</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">&gt;</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>首先要注意的是,控制器不是单个中间件函数(带参数(<code>req, res, next</code>)),而是指定一组中间件函数。数组传递给路由器函数,并按顺序调用每个方法。</p>
+
+<ul>
+</ul>
+
+<div class="note">
+<p><strong>注意:</strong> 这种方法是必需的,因为消毒/验证器是中间件功能。</p>
+</div>
+
+<p>数组中的第一个方法定义了一个验证器(<code>body</code>),来检查 name 字段是否为空(在执行验证之前调用<code>trim()</code>,以删除任何尾随/前导空格)。</p>
+
+<p>数组中的第二个方法(<code>sanitizeBody()</code>),创建一个清理程序来调用<code>trim()</code>修剪名称字段和调用<code>escape()</code>转义任何危险的 HTML 字符。</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>注意:</strong> 验证期间运行的清洁器不会修改请求。这就是为什么我们必须在上面的两个步骤中调用<code>trim()</code>!</p>
+</div>
+
+<p>在指定验证器和清理器之后,我们创建了一个中间件函数,来提取任何验证错误。我们使用<code>isEmpty()</code> 来检查验证结果中,是否有任何错误。如果有,那么我们再次渲染表单,传入我们的已清理种类对象和错误消息的数组(<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">&gt;</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">&lt;</span>save the result<span class="operator token">&gt;</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>Genre</code>(因为我们不想创建重复项)。</p>
+
+<p>如果是,我们会重定向到现有种类的详细信息页面。如果没有,我们保存新种类,并重定向到其详细信息页面。</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>在我们所有的 <code>POST</code>控制器中,都使用了相同的模式:我们运行验证器,然后运行消毒器,然后检查错误,并使用错误信息重新呈现表单,或保存数据。</p>
+
+<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2>
+
+<p>当我们创建一个新的种类<code>Genre</code>时,在<code>GET</code>和<code>POST</code>控制器/路由中,都会呈现相同的视图(稍后在我们更新种类<code>Genre</code>时也会使用它)。</p>
+
+<p>在<code>GET</code>情况下,表单为空,我们只传递一个title变量。在<code>POST</code>情况下,用户先前输入了无效数据 - 在种类变量<code>genre</code>中,我们传回了输入数据的已清理版本,并且在<code>errors</code>变量中,我们传回了一组错误消息。</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>创建 <strong>/views/genre_form.pug</strong>,并复制下面的文本。</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>从我们之前的教程中,可以很好地理解这个模板的大部分内容。首先,我们扩展 <strong>layout.pug</strong>基本模板,并覆盖名为 “<strong>content</strong>” 的块<code>block</code>。然后我们有一个标题,我们从控制器传入的标题<code>title</code>(通过<code>render()</code> 方法)。</p>
+
+<p>接下来,我们有 HTML表单的 Pug 代码,它使用<code>POST</code>方法将数据发送到服务器,并且因为操作<code>action</code>是空字符串,所以将数据发送到与页面相同的URL。</p>
+
+<p>表单定义了一个名为 “name” 的 “text” 类型的必填字段。字段的默认值,取决于是否定义了种类变量<code>genre</code>。如果从<code>GET</code>路由调用,它将为空,因为这是一个新表单。如果从<code>POST</code>路由调用,它将包含用户最初输入的(无效)值。</p>
+
+<p>页面的最后一部分是错误代码。如果已定义错误变量,则只会打印错误列表(换句话说,当模板在<code>GET</code>路由上呈现时,此部分不会出现)。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 这只是呈现错误的一种方法。您还可以从错误变量中,获取受影响字段的名称,并使用这些,来控制错误消息的呈现位置,以及是否应用自定义 CSS 等。</p>
+</div>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行应用程序,打开浏览器到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后选择 Create new genre 链接。如果一切设置正确,您的网站应该类似于以下屏幕截图。输入值后,应保存该值,您将进入种类详细信息页面。</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>我们针对服务器端,验证的唯一错误是种类字段不能为空。下面的屏幕截图,显示了如果您没有提供种类(以红色突出显示),错误列表会是什么样子。</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>注意:</strong> 我们的验证使用<code>trim()</code>,来确保不接受空格作为种类名称。我们还可以在表单中​​的字段定义中,添加值<code>required='true'</code>,来验证客户端字段不为空:</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="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li>继续教程 6 下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/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&amp;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-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html
new file mode 100644
index 0000000000..3b38e85e8a
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html
@@ -0,0 +1,165 @@
+---
+title: 删除作者表单
+slug: learn/Server-side/Express_Nodejs/forms/Delete_author_form
+translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form
+---
+<p>此子文档显示,如何定义页面以删除<code>Author</code>对象。</p>
+
+<p>正如<a href="/zh-CN/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="控制器—get_路由">控制器—get 路由</h2>
+
+<p>打开<strong>/controllers/authorController.js</strong>。找到导出的<code>author_delete_get()</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>控制器从URL参数(<code>req.params.id</code>)中,获取要删除的<code>Author</code>实例的 id。它使用<code>async.parallel()</code> 方法,并行获取作者记录和所有相关书本。当两个操作都完成后,它将呈现<code><strong>author_delete</strong></code><strong>.pug</strong>视图,为<code>title</code>、<code>author</code>、和 <code>author_books</code>传递变量。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 如果<code>findById()</code>返回“没有结果”,则作者不在数据库中。在这种情况下,没有什么可以删除,所以我们立即呈现所有作者的列表。</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="控制器—post_路由">控制器—post 路由</h2>
+
+<p>找到导出的<code>author_delete_post()</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 &gt; 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>首先,我们验证是否已提供id(这是通过表单主体参数发送的,而不是使用URL中的版本)。然后我们以与<code>GET</code>路由相同的方式,获得作者及其相关书本。如果没有书本,那么我们删除作者对象,并重定向到所有作者的列表。如果还有书本,那么我们只需重新呈现表格,传递作者和要删除的书本列表。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 我们可以检查对<code>findById()</code>的调用,是否返回任何结果,如果没有,则立即呈现所有作者的列表。为简洁起见,我们将代码保留在上面(如果找不到id,它仍会返回作者列表,但这将在<code>findByIdAndRemove()</code>之后发生)。</p>
+</div>
+
+<h2 class="highlight-spanned" id="视图">视图</h2>
+
+<p>创建 <strong>/views/author_delete.pug</strong> 并复制贴上底下文字。</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>视图扩展了布局模板,覆盖了名为<code>content</code>的区块。在顶部显示作者详细信息。然后它包含一个,基于<code><strong>author_books</strong></code>(<code>if</code>和<code>else</code>子句)数量的条件语句。</p>
+
+<ul>
+ <li>如果存在与作者相关联的书本,则该页面列出书本,并说明在删除该作者<code>Author</code>之前,必须删除这些书籍。</li>
+ <li>如果没有书本,则页面会显示确认提示。如果单击“删除”<strong>Delete</strong>按钮,则会在<code>POST</code>请求中,将作者ID发送到服务器,并且将删除该作者的记录。</li>
+</ul>
+
+<h2 class="highlight-spanned" id="加入一个删除控制器">加入一个删除控制器</h2>
+
+<p>接下来,我们将向 Author 详细视图添加 <strong>Delete </strong>控件(详细信息页面是删除记录的好地方)。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 在完整实现中,控件将仅对授权用户可见。但是在这个时间点上,我们还没有一个授权系统!</p>
+</div>
+
+<p>打开<strong> author_detail.pug</strong> 视图,并在底部添加以下行。</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>控件现在应显示为链接,如下面的作者详细信息页面所示。</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="它看起來像是">它看起來像是?</h2>
+
+<p>运行应用程序,并将浏览器打开,到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后选择所有作者链接<em> All authors</em>,然后选择一个特定作者。最后选择删除作者链接 <em>Delete</em> author。</p>
+
+<p>如果作者没有书本,您将看到这样的页面。按删除后,服务器将删除作者并重定向到作者列表。</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>如果作者确实有书本,那么您将看到如下视图。然后,您可以从其详细信息页面中,删除这些书本(一旦该代码被实现!)。</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>注意:</strong> 其他删除对象的页面,可以用相同的方式实现。我们把它留下,作为挑战。</p>
+</div>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li>继续教程 6 子文档的下一步: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新书本表单</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/index.html
new file mode 100644
index 0000000000..f3cee84344
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/index.html
@@ -0,0 +1,286 @@
+---
+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>
+ <p>完成前面所有的教程,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程第5章: 展示图书馆数据</a></p>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>
+ <p>了解如何编写表单获取用户信息,并且将这些数据更新到数据库中。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML表单</a>在网页中是一个或多个字段/小工具的集合,它被用来收集用户的信息,并将信息上传到服务器。表单作为一种用来收集用户的机制,非常的灵活,因为有各种合适的输入框,来接受各种类型的数据——文本框,复选框,单选按钮,时间选择器等。表单和服务器交互数据也相对安全,因为它使用POST请求发送数据,保护不受跨站点请求伪造攻击(cross-site request forgery)的威胁。</p>
+
+<p>但是表单同样也很复杂!开发者需要为表单编写 HTML,在服务器上验证,并且正确去除有害的数据(浏览器上也可能需要),对于任何不合法的字段,需要传给用户相应的错误信息,当数据成功提交后,处理数据,并设法通知用户提交成功。</p>
+
+<p>此教程将展示上述的操作,如何在 <em>Express </em>中实现。在此过程中,我们将扩展 LocalLibrary 网站,以允许用户创建,编辑和删除图书馆中的项目。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 我们还没有考虑如何将特定路由,限制为经过身份验证或授权的用户,因此在这个时间点,任何用户都可以对数据库进行更改。</p>
+</div>
+
+<h3 id="HTML表单">HTML表单</h3>
+
+<p>首先简要概述<a href="/zh-CN/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>&lt;form&gt;...&lt;/form&gt;</code>标记内的元素集合,包含至少一个<code>type="submit"</code>的<code>input</code>输入元素。</p>
+
+<p>请注意,我非常建议您这里使用input的submit而不是button!这会使你感到愉悦。</p>
+
+<pre class="brush: html notranslate">&lt;form action="/team_name_url/" method="post"&gt;
+    &lt;label for="team_name"&gt;Enter name: &lt;/label&gt;
+    &lt;input id="team_name" type="text" name="name_field" value="Default name for team."&gt;
+    &lt;input type="submit" value="OK"&gt;
+&lt;/form&gt;</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>另外,有必要说一下,HTML中form表单默认就是以post提交的。它比get方式存储量更大、传输更安全。</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>方法,因为这更加可以抵抗跨站点伪造请求攻击。</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>路由到同一路径,以处理表单数据的验证和处理。这是将在本教程中使用的方法!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 notranslate">npm install express-validator --save
+</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>
+</div>
+
+<p>要在我们的控制器中使用验证器,我们必须从'<strong>express-validator/check</strong>'和'<strong>express-validator/filter</strong>'模块中,导入我们想要使用的函数,如下所示:</p>
+
+<pre class="brush: js notranslate">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>(如上面 “导入”的 )。</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 notranslate">body('name', 'Empty name').isLength({ min: 1 }),
+body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
+</pre>
+ 您还可以用菊花链式连接不同的验证器,并添加前面验证器为真时显示的消息。</li>
+ <li>
+ <pre class="brush: js notranslate">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 notranslate">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 notranslate">(req, res, next) =&gt; {
+    // 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">阅验证结果API</a>。</li>
+</ul>
+
+<p>验证和清理链,是应该传递给Express路由处理程序的中间件(我们通过控制器,间接地执行此操作)。中间件运行时,每个验证器/清理程序都按指定的顺序运行。<br>
+ 当我们实现下面的LocalLibrary表单时,我们将介绍一些真实的例子。</p>
+
+<h3 id="表单设计">表单设计</h3>
+
+<p>图书馆中的许多模型都是相关/依赖的 - 例如,一本书需要一个作者,也可能有一个或多个种类。这提出了一个问题,即我们应该如何处理用户希望的情况:</p>
+
+<ul>
+ <li>在其相关对象尚不存在时,创建对象(例如,尚未定义作者对象的书)。</li>
+ <li>删除另一个对象仍在使用的对象(例如,删除仍有书本使用的种类)。</li>
+</ul>
+
+<p>在这个项目,我们将简单声明表单只能:</p>
+
+<ul>
+ <li>使用已存在的对象创建对象(因此用户在尝试创建任何<code>Book</code>对象之前,必须创建任何所需的<code>Author</code>和<code>Genre</code>实例)。</li>
+ <li>如果对象未被其他对象引用,则删除该对象(例如,在删除所有关联的<code>BookInstance</code>对象之前,您将无法删除该书)。</li>
+</ul>
+
+<p>让我们看看更高级的内容吧:</p>
+
+<p>我们通常会在“后台”接收form表单提交的数据。显而易见,这里应该是express!</p>
+
+<p>首先我们可以知道(也许你会知道)应该先引入express:</p>
+
+<p><code>const app=express();</code> </p>
+
+<p>这很好。</p>
+
+<p>那么既然是post提交,给大家推荐一款中间件:body-parser。它能让你轻松地处理body数据。</p>
+
+<p>哦,如果你涉及文件上传,那么你可能需要“<a href="https://blog.csdn.net/qq_43624878/article/details/103607944">multer</a>”中间件,你大概听说过“formidable”,但multer比它更强大!</p>
+
+<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>(在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">之前的教程</a>中)为我们所有模型的创建页面,创建了路径。例如,种类路由如下所示:</p>
+
+<pre class="brush: js notranslate">// 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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">创建种类表单</a> — 定义我们的页面以创建<code>Genre</code>种类对象。</li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">创建作者表单</a> — 定义用于创建作者对象的页面。</li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">创建书本表单</a> — 定义页面/表单以创建书本对象。</li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">创建书本实例表单</a> — 定义页面/表单以创建书本实例对象。</li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">删除作者表单</a> — 定义要删除作者对象的页面。</li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新书本表单</a> — 定义页面以更新书本对象。</li>
+</ol>
+
+<h2 id="挑战自我">挑战自我</h2>
+
+<p>实现<code>Book</code>, <code>BookInstance</code>, 和 <code>Genre</code>模型的删除页面,以与我们的作者删除页面相同的方式,将它们与关联的详细信息页面,链接起来。页面应遵循相同的设计方法:</p>
+
+<ul>
+ <li>如果有来自其他对象的、对于对象的引用,则应显示注释,列出这些对象,并说明在删除列出的对象之前,无法删除此记录。</li>
+ <li>如果没有对该对象的其他引用,则视图应提示删除它。如果用户按下“删除”按钮,则应删除该记录。</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>BookInstance</code>是最简单的,因为没有依赖对象。在这种情况下,您只需找到相关记录并将其删除即可。</li>
+</ul>
+
+<p>实现<code>BookInstance</code>, <code>Author</code>, 和 <code>Genre</code>模型的更新页面,以与我们的书本更新页面相同的方式,将它们与关联的详细信息页面,链接起来。</p>
+
+<p>一些提示:</p>
+
+<ul>
+ <li>我们刚刚实施的图书更新页面是最难的!相同的模式可用于其他对象的更新页面。</li>
+ <li>作者死亡日期和出生日期字段以及书本实例 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, how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.</p>
+
+<p>你现在应该了解如何新增基本表单,以及表单处理码到你的 node 网站!</p>
+
+<h2 id="请见">请见</h2>
+
+<ul>
+ <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm 文档).</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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html
new file mode 100644
index 0000000000..de1faa0909
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html
@@ -0,0 +1,189 @@
+---
+title: 更新书本表单
+slug: learn/Server-side/Express_Nodejs/forms/Update_Book_form
+translation_of: Learn/Server-side/Express_Nodejs/forms/Update_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#Update_Book_form" rel="nofollow, noindex"><span>Edit</span></a>本文最后一部分演示如何定义一个页面,以更新书本(<code>Book</code>)对象。当更新一本书的时候,表单处理更像是创建一本书,除了你必须将表单填进 <code>GET</code> 路由,并附加上来自数据库的值。</p>
+
+<h2 class="highlight-spanned" id="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2>
+
+<p>打开<strong> /controllers/bookController.js</strong>. 找到 exported <code>book_update_get()</code> 控制方法,并用底下的代码替换。</p>
+
+<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display book update form on GET.</span>
+exports<span class="punctuation token">.</span>book_update_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>
+
+ <span class="comment token">// Get book, authors and genres for form.</span>
+ <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>
+ book<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>
+ Book<span class="punctuation token">.</span><span class="function token">findById</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<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">populate</span><span class="punctuation token">(</span><span class="string token">'genre'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">exec</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>
+ authors<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>
+ Author<span class="punctuation token">.</span><span class="function token">find</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>
+ genres<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>
+ Genre<span class="punctuation token">.</span><span class="function token">find</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="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="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>results<span class="punctuation token">.</span>book<span class="operator token">==</span><span class="keyword token">null</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="comment token">// No results.</span>
+ <span class="keyword token">var</span> err <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Error</span><span class="punctuation token">(</span><span class="string token">'Book not found'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+ err<span class="punctuation token">.</span>status <span class="operator token">=</span> <span class="number token">404</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">// Success.</span>
+ <span class="comment token">// Mark our selected genres as checked.</span>
+ <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> all_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> all_g_iter <span class="operator token">&lt;</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> all_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> book_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> book_g_iter <span class="operator token">&lt;</span> results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span>length<span class="punctuation token">;</span> book_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">==</span>results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">[</span>book_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</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">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span> authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> results<span class="punctuation token">.</span>book <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>这个控制器从URL参数(<code>req.params.id</code>)中,取得要更新的书本 <code>Book</code> 的 id。它使用 <code>async.parallel()</code>方法,取得指定的书本 <code>Book</code> 纪录 (填入它的种类和作者字段) ,并列出所有作者 <code>Author</code> 和种类 <code>Genre</code>对象。当所有操作都完成,它用勾选的方式,标记当前选择的种类,并呈现 <strong>book_form.pug</strong> 视图,传送变数 <code>title</code>、<code>book</code>、所有 <code>authors</code>、所有 <code>genres</code>。</p>
+
+<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2>
+
+<p>找到 exported <code>book_update_post()</code> 控制器方法,并替换为底下的代码。</p>
+
+<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle book update on POST.</span>
+exports<span class="punctuation token">.</span>book_update_post <span class="operator token">=</span> <span class="punctuation token">[</span>
+
+ <span class="comment token">// Convert the genre to an array</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">&gt;</span> <span class="punctuation token">{</span>
+ <span class="keyword token">if</span><span class="punctuation token">(</span><span class="operator token">!</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre <span class="keyword token">instanceof</span> <span class="class-name token">Array</span><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="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span>
+ req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="punctuation token">[</span><span class="punctuation token">]</span><span class="punctuation token">;</span>
+ <span class="keyword token">else</span>
+ req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="keyword token">new</span> <span class="class-name token">Array</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">)</span><span class="punctuation token">;</span>
+ <span class="punctuation token">}</span>
+ <span class="function token">next</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">// Validate fields.</span>
+ <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">,</span> <span class="string token">'Title must not be empty.'</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">'author'</span><span class="punctuation token">,</span> <span class="string token">'Author must not be empty.'</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">'summary'</span><span class="punctuation token">,</span> <span class="string token">'Summary must not be empty.'</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">'isbn'</span><span class="punctuation token">,</span> <span class="string token">'ISBN must not be empty'</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 fields.</span>
+ <span class="function token">sanitizeBody</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">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">'author'</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">'summary'</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">'isbn'</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">'genre.*'</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">&gt;</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 Book object with escaped/trimmed data and old id.</span>
+ <span class="keyword token">var</span> book <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Book</span><span class="punctuation token">(</span>
+ <span class="punctuation token">{</span> title<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>title<span class="punctuation token">,</span>
+ author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>author<span class="punctuation token">,</span>
+ summary<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>summary<span class="punctuation token">,</span>
+ isbn<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>isbn<span class="punctuation token">,</span>
+ genre<span class="punctuation token">:</span> <span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> <span class="operator token">?</span> <span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">,</span>
+ _id<span class="punctuation token">:</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id <span class="comment token">//This is required, or a new ID will be assigned!</span>
+ <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/error messages.</span>
+
+ <span class="comment token">// Get all authors and genres for form.</span>
+ <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>
+ authors<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>
+ Author<span class="punctuation token">.</span><span class="function token">find</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>
+ genres<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>
+ Genre<span class="punctuation token">.</span><span class="function token">find</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="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="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">// Mark our selected genres as checked.</span>
+ <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">let</span> i <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> i <span class="operator token">&lt;</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> i<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="keyword token">if</span> <span class="punctuation token">(</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span><span class="function token">indexOf</span><span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">)</span> <span class="operator token">&gt;</span> <span class="operator token">-</span><span class="number token">1</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</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">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span>authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> book<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="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. Update the record.</span>
+ Book<span class="punctuation token">.</span><span class="function token">findByIdAndUpdate</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">,</span> book<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>thebook<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 book detail page.</span>
+ res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>thebook<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>这很像是创建一本书的时候,所使用的 post 路由。首先,我们验证来自表单的书本数据,并进行无害化处理,并使用它创建一个新的书本 <code>Book</code> 对象 (将它的 <code>_id</code> 值,设置给将要更新的对象的 id)。当我们验证资料,然后重新呈现表单的时候,如果存在错误,再附加显示使用者输入的资料、错误信息、以及种类和作者列表。当我们调用<code>Book.findByIdAndUpdate()</code> 去更新 <code>Book</code> ,如果没有错误,就重新导向到它的细节页面。</p>
+
+<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2>
+
+<p>打开 <strong>/views/book_form.pug</strong> ,并更新作者表单控制器的区段,以加入底下条件控制代码。</p>
+
+<pre class="line-numbers language-html"><code class="language-html"> 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
+ //- Handle GET form, where book.author is an object, and POST form, where it is a string.
+ option(
+ value=author._id
+ selected=(
+ author._id.toString()==book.author._id
+ || author._id.toString()==book.author
+ ) ? 'selected' : false
+ ) #{author.name}
+ else
+ option(value=author._id) #{author.name}</code></pre>
+
+<div class="note">
+<p><strong>注意</strong>: 此处代码的更动,是为了让书本表单 book_form ,能被创建和更新书本的对象共同使用 (如果不这么做,当创建表单时,在 <code>GET</code> 路由会发生一个错误)。</p>
+</div>
+
+<h2 class="highlight-spanned" id="加入一个更新按钮">加入一个更新按钮</h2>
+
+<p>打开 <strong>book_detail.pug </strong>视图,并确认在页面下方,有删除和更新书本的连结,如下所示。</p>
+
+<pre class="brush: html line-numbers language-html"><code class="language-html"> hr
+ p
+ a(href=book.url+'/delete') Delete Book
+ p
+ a(href=book.url+'/update') Update Book</code></pre>
+
+<p>你现在应该能够更新来自书本细节页面的书了。</p>
+
+<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2>
+
+<p>运行本应用,打开浏览器,访问网址 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,点击所有书本 All books 连结,然后点击一本书。最后点击更新书本 Update Book 连结。</p>
+
+<p>表单看起来应该就像是创建书本页面,只是标题变为 'Update book',并且事先填入纪录值。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14498/LocalLibary_Express_Book_Update_NoErrors.png" style="border-style: solid; border-width: 1px; display: block; height: 443px; margin: 0px auto; width: 1000px;"></p>
+
+<div class="note">
+<p><strong>注意:</strong> 其它更新对象的页面,也可以用同样的方式处理。我们把这些更新页面的实作留下,做为自我挑战。</p>
+</div>
+
+<p> </p>
+
+<h2 id="下一步">下一步</h2>
+
+<ul>
+ <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
+</ul>
+
+<p> </p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/index.html b/files/zh-cn/learn/server-side/express_nodejs/index.html
new file mode 100644
index 0000000000..294474589a
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/index.html
@@ -0,0 +1,72 @@
+---
+title: Express Web Framework (Node.js/JavaScript)
+slug: learn/Server-side/Express_Nodejs
+tags:
+ - Express
+ - Node
+translation_of: Learn/Server-side/Express_Nodejs
+---
+<div>{{LearnSidebar}}</div>
+
+<p class="summary">Express是一款受欢迎的开源web框架,构建语言是JavaScript,可以在node.js环境运行。本系列文章介绍了该框架的优点,如何搭建开发环境以及部署web开发环境并进行开发任务。</p>
+
+<h2 id="前提">前提</h2>
+
+<p>在开始这个模块之前你需要知道什么是服务端网页编程和 web 框架,建议你先阅读 <a href="/zh-CN/docs/Learn/Server-side">服务端网页编程</a> 模块。强烈推荐了解编程概念和  <a href="/zh-CN/docs/Web/JavaScript">JavaScript</a> ,但这对理解核心概念不是必需的。</p>
+
+<div class="note">
+<p>注意:这个网站有很多有用的资源用来学习JavaScript做客户端开发: <a href="/zh-CN/docs/Web/JavaScript">JavaScript</a>, <a href="/zh-CN/docs/Web/JavaScript/Guide">JavaScript Guide</a>, <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript Basics</a>, <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> (learning). 核心的JavaScript语言和概念用Nodejs服务端开发是相同的,也是相关的。Node.js 提供<a href="https://nodejs.org/dist/latest-v6.x/docs/api/"> 额外的API</a> 用于支持在无浏览器环境中有用的功能,例如,创建HTTP服务器并访问文件系统,但不支持JavaScript API以使用浏览器和DOM。</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-CN/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-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">搭建 Node(Express) 开发环境</a></dt>
+ <dd>介绍了 Express 的所用之处后,我们将向您展示如何在不同操作系统下建立并验证 Node/Express 开发环境。无论您使用任何操作系统,这篇文章都可以完全指导如何开始构建 Express 应用。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程——第一部分:本地图书馆</a></dt>
+ <dd>该实用教程系列中的第一篇文章,介绍了即将学习的内容,并概述了在后续文章中不断迭代的 “本地图书馆”例子 。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程——第二部分:建立网站的骨架</a></dt>
+ <dd>这篇文章将介绍如何建立一个网站项目的 “骨架”,然后您可以继续添加自己的路由、模板/视图和数据库。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程——第三部分:使用数据库(Mongoose)</a></dt>
+ <dd>这篇文章简单介绍了在 Node/Express 中如何使用数据库。本文中我们将会使用 Mongoose 为该项目(本地图书馆)提供数据访问,同时解释了如何定义对象模式、模型和基础和验证。本文也简单介绍了访问模型数据的一些主流方式。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程——第四部分:路由和控制器</a></dt>
+ <dd>我们在本教程中会设置路由来编写一些本地图书馆所需的伪造端点(endpoints)。在接下来的文章中,路由会有一个模块结构,可用来拓展real handler functions。最终,我们会对用Express创建模块化路由有很好的理解。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程——第五部分:在 HTML 上展示图书数据</a></dt>
+</dl>
+
+<p>我们现在已经准备好为展示本地图书馆图书和其他数据添加页面,包括展示每个model有多少记录的主页,以及list和detail页面。我们会积累从database获取记录以及使用模版的实战经验。</p>
+
+<dl>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express教程——第六部分: 如何使用表单</a></dt>
+ <dd>本教程我们会教你如何在Express使用HTML表单,Pug,以及从数据库中创建,更新,删除文件。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express教程——第七部分:如何部署到生产环境</a></dt>
+ <dd>现在你已经创建了一个很棒的本地图书馆网站,你可以把本地环境迁移到公共网络服务器上,别人也可以使用网站。本章提供了如何找服务器,部署网站到生产环境的概览。</dd>
+</dl>
+
+<h2 id="另见">另见</h2>
+
+<dl>
+ <dt><a href="/zh-CN/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 </a>云上安装LocalLibrary的实际演示 - 这是Heroku的全功能,开源替代品,Heroku是本教程第7部分中使用的PaaS云服务,如上所列。 如果您正在寻找Heroku(或其他PaaS云服务)的替代方案,或者只是想尝试不同的东西,PWS / Cloud Foundry绝对值得一试。</dd>
+ <dd></dd>
+</dl>
+
+<h2 id="其它教程">其它教程</h2>
+
+<div>
+<p>本教程到此结束,如果你想要更进一步,以下包含更多有趣的主题:</p>
+
+<ul>
+ <li>使用sessions</li>
+ <li>使用者授权</li>
+ <li>使用者许可</li>
+ <li>测试Express网页应用</li>
+ <li>Express网页应用的安全</li>
+</ul>
+
+<p>当然,如果做一个评估任务会更好!</p>
+</div>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html b/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html
new file mode 100644
index 0000000000..e3f321e83a
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html
@@ -0,0 +1,242 @@
+---
+title: 在 PWS/Cloud Foundry 上,安装 LocalLibrary
+slug: learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry
+translation_of: Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry
+---
+<div>{{LearnSidebar}}</div>
+
+<p class="summary">本文提供了如何在 <a href="http://run.pivotal.io">Pivotal Web Services PaaS cloud </a>云上安装 LocalLibrary的实际演示 - 这是 Heroku 的全功能,开源替代品,Heroku 是我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">教程第 7 部分</a>中使用的 PaaS 云服务。如果您正在寻找 Heroku(或其他 PaaS 云服务)的替代方案,或者只是想尝试不同的东西,PWS / Cloud Foundry 绝对值得一试。</p>
+
+<h2 id="为什么是_PWS">为什么是 PWS?</h2>
+
+<p>Pivotal Web Services 是<a href="https://www.cloudfoundry.org/">开源云代工平台</a>的公共实例。它是一个多语言平台,支持许多不同的语言,包括Node.js,Java,PHP,Python,Staticfiles 和 Ruby。它有一个免费试用版,对于运行 Node 应用程序非常有效!由于 Node 和 Express 是开源项目,因此与使用 Cloud Foundry 等开放式部署平台保持一致。您可以<a href="https://github.com/cloudfoundry">深入了解</a>应用程序的托管方式。</p>
+
+<p>使用PWS有多种原因!</p>
+
+<ul>
+ <li>PWS具有<a href="https://run.pivotla.io/pricing">灵活的定价</a>,可以很好地适应 node 等小型运行。你可以用每月不到 5 美元的价格,运行一对冗余的应用程序。这包括备用故障转移系统,如果主服务器在任何时候出现故障,它将接管运行您的应用程序。</li>
+ <li>作为PaaS,PWS为我们提供了大量的 Web 基础设施。这使得入门更加容易,因为您不必担心服务器、负载平衡器、反向代理、崩溃时重新启动网站,或者PWS 为我们提供的任何其他 Web 基础结构。</li>
+ <li>因为 PWS 使用的是 Cloud Foundry,一个开放的平台。因此,您可以轻松地将应用程序,部署到其他 Cloud Foundry 提供商,例如 <a href="https://www.ibm.com/cloud-computing/bluemix/">IBM BlueMix</a>, <a href="https://www.anynines.com/">AnyNines</a> 和 <a href="https://www.swisscom.ch/en/business/enterprise/offer/cloud-data-center-services/paas/application-cloud.html">Swisscomm Application Cloud</a>。以下说明,适用于任何标准的 Cloud Foundry 部署,只需稍加修改即可。</li>
+ <li>虽然它确实有一些限制,但这些不会影响这个特定的应用程序。例如:
+ <ul>
+ <li>PWS 和 Cloud Foundry 仅提供短期存储,因此用户上载的文件,无法安全地长期存储在 PWS 本身上。</li>
+ <li>免费试用一年有效,限制最高 87美元的应用程序使用量。对于典型的 Node 应用程序,这意味着您可以运行一整年的应用程序。</li>
+ </ul>
+ </li>
+ <li>大多数情况下它只是可以工作,如果你最终喜欢它,并希望升级,那么扩展你的应用程序非常容易。</li>
+ <li>PWS 和其他 Cloud Foundry 应用程序,可用于生产级别的应用程序。</li>
+</ul>
+
+<h2 id="PWS_是如何工作的?">PWS 是如何工作的?</h2>
+
+<p>PWS通过容器来运行网站和应用已经有些年头了。Cloud Foundry一开始采用的容器技术名为Warden,现在使用的是名为Garden的容器系统。这些技术与流行的Docker容器很相似,而且事实上Cloud Foundry云平台的很多设施都支持部署Docker容器。</p>
+
+<p>使用Cloud Foundry云平台的好处之一是您不需要创建容器规范,因为Cloud Foundry的构建包会基于最新的组件来生成它们。由于容器是临时部署的,而且可能随时被清理并部署到云中的其他位置,所以Cloud Foundry云平台上的应用需要遵循<a href="https://12factor.net/zh_cn/">十二要素准则</a>。如此可以确保您的应用和平台使用最新的软件。一个应用程序可以包含多个实例,在这些实例中,应用程序被放置到冗余容器中,从而实现应用程序的高可用性。Cloud Foundry会自动处理相同实例之间的所有负载平衡。这允许您扩展应用程序的性能和可用性。</p>
+
+<p>由于文件系统是临时的,所以任何临时存储或服务都应该使用备份服务放置到其他地方。这可以通过使用不同提供商提供的市场服务或通过<a href="https://docs.run.pivotal.io/devguide/services/user-provided.html">用户自己提供的服务</a>来实现。</p>
+
+<h2 id="What_do_we_cover_below">What do we cover below?</h2>
+
+<p>This post covers how to modify the LocalLibrary application from the tutorial for deployment on PWS and Cloud Foundry. In doing so, it covers the basics of deploying any node.js application to PWS with the following steps.</p>
+
+<ul>
+ <li>Configuring the package.json file to run with the engines available on PWS.</li>
+ <li>Adding and installing the<a href="https://github.com/cloudfoundry-community/node-cfenv"> 'cfenv' node module</a> to make working with services easier.</li>
+ <li>Using the cfenv module to connect to a MongoDB instance from mLab that was created and bound using the PWS marketplace.</li>
+ <li>Using the <a href="https://github.com/cloudfoundry/cli">cf CLI</a> tool to create a new mongoDB service instance and bind it to the local library application.</li>
+ <li>How to set environment variables for Node using the cf CLI.</li>
+ <li>How to look at logs, again using the cf CLI tool.</li>
+</ul>
+
+<p>So let's get started. You have two options, you can go through the tutorial from the <a href="&lt;https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website>">beginning</a> or you can just download the completed project and modify it from there for use on PWS. To do the latter, you can do the following from a terminal:</p>
+
+<pre class="brush: bash notranslate"><code>git clone https://github.com/mdn/express-locallibrary-tutorial</code></pre>
+
+<p>You'll then need to follow the preparation steps listed in the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment#Getting_your_website_ready_to_publish">Getting your website ready to publish</a> section of <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a>, before then following the steps listed below.</p>
+
+<div class="note">
+<p><strong>Note</strong>: This work flow is based on the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment#Example_Installing_LocalLibrary_on_Heroku">Mozilla Heroku work flow in the main Express/Node tutorial series</a> for consistency, to help readers compare and contrast. </p>
+</div>
+
+<h2 id="Modifying_the_LocalLibrary_for_PWS">Modifying the LocalLibrary for PWS</h2>
+
+<p id="How_does_PWS_work">Deployment of a Node application on Cloud Foundry takes the following steps. Using the downloaded 'cf' CLI tool on your environment, your source code and supporting metadata files are uploaded to Cloud Foundry which will assemble and package the components of your application. Note that your files need to be located on your system to deploy or as a zip archive somewhere accessible on the internet. We'll assume the former.</p>
+
+<p>This means, no assumptions about which source control system is used. As long as you have a complete source tree in your local file system you can deploy the app. There are some things you have to make available to ensure the correctly assembly of your Node application. First Cloud Foundry cf CLI will look for the presence of the 'package.json' file to determine the dependencies and download the necessary components. The rules of this assembly are defined in Cloud Foundry's <a href="http://docs.run.pivotal.io/buildpacks/node/">nodejs buildpack</a>. An optional cloud foundry manifest file can specify information about your application such as name, size and start command if non-standard. In addition to deploying the application, the cf CLI tool can also configure services, set environment variables and view logs. That's all the overview you need in order to get started (see <a href="https://docs.run.pivotal.io">Getting Started on Pivotal Web Services</a> for a more comprehensive guide). Let's start making the changes so you'll need to deploy the <em>LocalLibrary</em> application to  PWS.</p>
+
+<h3 id="Set_node_version">Set node version</h3>
+
+<p>The <strong>package.json</strong> contains everything needed to work out your application dependencies and what file should be launched to start your site. Cloud Foundry and PWS detects the presence of this file, and will use it to provision your app environment. The only useful information missing in our current <strong>package.json</strong> is the version of node. We can find the version of node we're using for development by entering the command:</p>
+
+<pre class="brush: bash notranslate">node --version
+# <em>will return version e.g. v6.10.3</em></pre>
+
+<p>Open <strong>package.json</strong> with a text editor, and add this information as an <strong>engines &gt; node</strong> section as shown (using the version number retrieved above).</p>
+
+<pre class="brush: json notranslate">{
+ "name": "express-locallibrary-tutorial",
+ "version": "0.0.0",
+<strong> "engines": {
+ "node": "6.10.3"
+ },</strong>
+ "private": true,
+  ...
+</pre>
+
+<h3 id="Database_configuration">Database configuration</h3>
+
+<p>So far in this tutorial we've used a single database that is hard coded into the <strong>app.js </strong>file. Normally we'd like to be able to have a different database for production and development, so next we'll modify the LocalLibrary website to get the database URI from the OS environment, and otherwise use our development database that we added manually earlier. Cloud Foundry has a very flexible services model that enables multiple services of the same type to exist in the environment. It stores all services related configurations in a single parseable JSON object called <code>VCAP_SERVICES</code>. A typical <code>VCAP_SERVICES</code> variable looks like this:</p>
+
+<pre class="brush: json notranslate">{
+ "VCAP_SERVICES": {
+ "mlab": [
+ {
+ "credentials": {
+ "uri": "mongodb://CloudFoundry_test_dev:somecr8zypassw0rd@dbhost.mlab.com:57971/CloudFoundry_dbname"
+ },
+ "label": "mlab",
+ "name": "node-express-tutorial-mongodb",
+ "plan": "sandbox",
+ "provider": null,
+ "syslog_drain_url": null,
+ "tags": [
+ "Cloud Databases",
+ "Developer Tools",
+ "Web-based",
+ "Data Store",
+ ],
+ "volume_mounts": []
+ }
+ ]
+ }
+}
+
+</pre>
+
+<p>Writing the code to extract and parse this environment variable is not hard, but it doesn't add a lot of value when others have written libraries to do this. In this case, there is a node.js package we can use called <a href="https://github.com/cloudfoundry-community/node-cfenv"><em>cfenv</em></a>.</p>
+
+<p>This will download the cfenv module and its dependencies, and modify the package.json file as required. Open <strong>app.js</strong> and find the block with all the 'requires' that load the modules into your application. In this example look for the line that looks something like this:</p>
+
+<pre class="brush: js notranslate">var expressValidator = require('express-validator');</pre>
+
+<p>If you cannot find that exact line, look for the blocks of 'requires' and look for the last one. Add the following text after it:</p>
+
+<pre class="brush: js notranslate">var cfenv = require('cfenv');</pre>
+
+<ol>
+ <li>
+ <p>To install the package, go to your terminal and make sure you are in the directory where the <code>package.json</code> file for LocalLibrary is. From the command line, type:</p>
+
+ <pre class="brush: bash notranslate">npm install cfenv</pre>
+ </li>
+ <li>
+ <p>Now that you have loaded the module, this next line will instantiate an object that will contain the app and services information required for deployment. Add the following after the line that contains <code>app.use(helmet());</code></p>
+
+ <pre class="brush: js notranslate">// Set up CF environment variables
+var appEnv = cfenv.getAppEnv();
+</pre>
+
+ <p>When this line executes, all the Cloud Foundry application environment information will become available to the application in the <code>appEnv</code> object.</p>
+ </li>
+ <li>
+ <p>Now it is time to update the application to use the database configured by the platform. Find the line that sets the mongoDB connection variable. It will look something like this:</p>
+
+ <pre class="brush: js notranslate">var mongoDB = process.env.MONGODB_URI || dev_db_url;</pre>
+ </li>
+ <li>
+ <p>You will now modify the line with the following code  <code>appEnv.getServiceURL('node-express-tutorial-mongodb')</code> to get the connection string from an environment variable that is being managed by the <code>cfenv</code>  module. If no service has been created and bound it will use your own database URL you created as part of the tutorial instead of the one from the environment. So replace the line above with the following:</p>
+
+ <pre class="brush: js notranslate">var mongoDB = appEnv.getServiceURL('node-express-tutorial-mongodb') || dev_db_url;
+</pre>
+ </li>
+ <li>
+ <p>Now run the site locally (see <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">Testing the routes</a> for the relevant commands) and check that the site still behaves as you expect. At this point your app is ready to use with Cloud Foundry and Pivotal Web Services.</p>
+ </li>
+</ol>
+
+<h2 id="Get_a_Pivotal_Web_Services_account">Get a Pivotal Web Services account</h2>
+
+<p>To start using Pivotal Web Services you will first need to create an account (skip ahead to <a href="#Create_and_upload_the_website">Create and upload the website</a> if you've already got an account and have already installed the PWS cf CLI client).</p>
+
+<ul>
+ <li>Go to <a href="https://run.pivotal.io">https://run.pivotal.io</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 email 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 and you will complete the registration.</li>
+ <li>You will set your password and go through the rest of the new user sign up and how to claim your free trial account. Note you need a mobile phone to confirm your account. You will receive an "org" account funded with $87 of application usage credit. Note your email account can be in multiple orgs on PWS. This is similar to your user account on services like GitHub.</li>
+ <li>Go to <a href="https://login.run.pivotal.io">https://console.run.pivotal.io</a> and login in. You'll then be logged in and taken to the PWS dashboard: <a href="https://console.run.pivotal.io">https://console.run.pivotal.io</a>.</li>
+</ul>
+
+<h2 id="Install_the_cf_CLI_client">Install the cf CLI client</h2>
+
+<p>The cf CLI client is a software tool for managing and deploying your application. Download and install the PWS cf CLI client by following the <a href="https://console.run.pivotal.io/tools">instructions on Pivotal Web Services</a> or downloading directly from <a href="https://github.com/cloudfoundry/cli">GIthub</a>. Be sure to download the correct version for your computer. After the client is installed you will be able run commands, for example to get help on the client:</p>
+
+<pre class="brush: bash notranslate">cf help
+</pre>
+
+<p>We'll now go through the steps to login to PWS using the CLI and deploy — or in Cloud Foundry parlance "push" your app.</p>
+
+<h2 id="Create_and_upload_the_website">Create and upload the website</h2>
+
+<p>To create the app we navigate to the directory where our modified files are. This is the same directory where the LocalLibrary package.json file is located. First, let's tell the cf CLI which Cloud Foundry instance you want to use. We need to do this, since the cf CLI tool can be used with any standard Cloud Foundry system, so this command indicates which specific Cloud Foundry you are using. Enter the following terminal command now:</p>
+
+<pre class="brush: bash notranslate">cf api api.run.pivotal.io</pre>
+
+<p>Next login using the following command (enter your email and password when prompted):</p>
+
+<pre class="brush: bash notranslate">cf login
+Email: enter your email
+Password: enter your password</pre>
+
+<p>We can now push our app to PWS. In the below example. replace 'some-unique-name' with something you can remember that is likely to be unique. If it isn't unique, the system will let you know. The reason this name has to be unique to the PWS system is it is the address we will use to to access your LocalLibrary application. I used <em>mozilla-express-tutorial-xyzzy</em>. You should use something else.</p>
+
+<pre class="brush: bash notranslate">cf push some-unique-name -m 256MB</pre>
+
+<p>Note the <code>-m</code> flag we added is not required. We just included it so that we only use 256MB of memory to run the app. Node apps typically can run in 128 MB, but we are being safe. If we didn't specify the memory, the CLI would use the default 1 GB of memory, but we want to make sure your trial lasts longer. You should now see a bunch of text on the screen. It will indicate that the CLI is uploading all your files, that it's using the node buildpack, and it will start the app. If we're lucky, the app is now "running" on the site at the URL <code>https://some-unique-name.cfapps.io</code>. Open your browser and run the new website by going to that URL.</p>
+
+<div class="note"><strong>Note</strong>: The site will be running using our hardcoded development database at this time. Create some books and other objects, and check out whether the site is behaving as you expect. In the next section we'll set it to use our new database.</div>
+
+<h2 id="Setting_configuration_variables">Setting configuration variables</h2>
+
+<p>You will recall from a preceding section that we need to <a href="#NODE_ENV">set NODE_ENV to 'production'</a> in order to improve our performance and generate less-verbose error messages.</p>
+
+<ol>
+ <li>
+ <p>Do this by entering the following command:</p>
+
+ <pre class="brush: bash notranslate">cf set-env some-unique-name NODE_ENV production
+</pre>
+ </li>
+ <li>
+ <p>We should also use a separate database for production. Cloud Foundry can take advantage of a marketplace to create a new service and automatically bind it to your app. Bind means place the service database credentials in the environment variable space of the container running your application for you to access. Enter the following commands:</p>
+
+ <pre class="brush: bash notranslate">cf create-service mlab sandbox node-express-tutorial-mongodb
+cf bind-service some-unique-name node-express-tutorial-mongodb
+</pre>
+ </li>
+ <li>
+ <p>You can inspect your configuration variables at any time using the <code>cf env some-unique-name</code> command — try this now:</p>
+
+ <pre class="brush: bash notranslate">cf env some-unique-name
+</pre>
+ </li>
+ <li>
+ <p>In order for your applications to use the new credentials you will have to restage your application, meaning that it will restart and apply the new environment variables. This can be done using the following — enter this command now:</p>
+
+ <pre class="brush: bash notranslate">cf restage some-unique-name
+</pre>
+ </li>
+ <li>
+ <p>If you check the home page now it should show zero values for your object counts, as the changes above mean that we're now using a new (empty) database.</p>
+ </li>
+</ol>
+
+<h2 id="Debugging">Debugging</h2>
+
+<p>The PWS cf client provides a few tools for debugging:</p>
+
+<pre class="brush: bash notranslate">&gt;cf logs some-unique-name --recent # Show current logs
+&gt;cf logs some-unique-name # Show current logs and keep updating with any new results</pre>
+
+<h2 id="Summary">Summary</h2>
+
+<p>If you followed the above steps, you should have now deployed the LocalLibrary app to PWS. Well done! If the deployment wasn't successful, double check all the steps.</p>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html b/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html
new file mode 100644
index 0000000000..3f0372024e
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html
@@ -0,0 +1,525 @@
+---
+title: Express/Node 入门
+slug: learn/Server-side/Express_Nodejs/Introduction
+tags:
+ - Express
+ - Node
+ - node.js
+ - 初学者
+ - 学习
+ - 服务器端
+ - 脚本
+translation_of: Learn/Server-side/Express_Nodejs/Introduction
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div>
+
+<p class="summary">本节将回答“什么是 Node”以及“什么是 Express”这两个问题,并通过主要特征和构成要件来简要介绍 Express 框架的与众不同之处。(只是目前尚不能用一个开发环境来测试它)</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识:</th>
+ <td>掌握计算机基础知识。了解 <a href="/zh-CN/docs/learn/Server-side">服务器端编程</a>,特别是 <a href="/zh-CN/docs/learn/Server-side/First_steps/Client-Server_overview">客户-服务器交互机制</a>。</td>
+ </tr>
+ <tr>
+ <th scope="row">学习目标:</th>
+ <td>熟悉 Express,以及它适配 Node 的方式、具体功能和构成要件。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="什么是_Node">什么是 Node?</h2>
+
+<p><a href="https://nodejs.org/zh-cn/">Node</a>(正式名称 Node.js)是一个开源的、跨平台的运行时环境,有了它,开发人员可以使用 <a href="/zh-CN/docs/Glossary/JavaScript">JavaScript</a> 创建各种服务器端工具和应用程序。此运行时主要用于浏览器上下文之外(即可以直接运行于计算机或服务器操作系统上)。据此,该环境省略了一些浏览器专用的 JavaScript API,同时添加了对更传统的 OS API(比如 HTTP 库和文件系统库)的支持。</p>
+
+<p>从 web 服务器开发的角度来看,Node 有很多好处:</p>
+
+<ul>
+ <li>卓越的性能表现!Node 为优化 web 应用的吞吐量和扩展度而生,对常见的 web 开发问题是一套绝佳方案(比如实时 web 应用)。</li>
+ <li>代码还是熟悉的老伙伴 JavaScript,这意味着在客户端和服务器端“上下文切换”的时间成本更低。</li>
+ <li>与传统的 web 服务器语言(例如 Python、PHP 等)相比,JavaScript 理念更新,语言设计的改进带来了诸多好处。许多其它新近流行的语言也可编译/转换成 JavaScript,所以TypeScript、CoffeeScript、ClojureScript、Scala、LiveScript 等等也可以使用。</li>
+ <li>Node 包管理工具(node package manager,NPM)提供了数十万个可重用的工具包。它还提供了一流的依赖解决方案,可实现自动化工具链构建。</li>
+ <li>Node.js 是可移植的,可运行于 Microsoft Windows、macOS、Linux、Solaris、FreeBSD、OpenBSD、WebOS 和 NonStop OS。此外,许多 web 主机供应商对其提供了良好支持(包括专用的基础框架和构建 Node 站点的文档)。</li>
+ <li>它有一个非常活跃的第三方生态系统和开发者社区,很多人愿意提供帮助。</li>
+</ul>
+
+<p>可以用 Node.js 的 HTTP 包来创建一个简单的 web 服务器。</p>
+
+<h3 id="Hello_Node.js">Hello Node.js</h3>
+
+<p>以下示例将创建一个 web 服务器,它将监听对 URL <code>http://127.0.0.1:8000/</code> 所有种类的 HTTP 请求,当接收到一个请求时,脚本将做出响应:返回一个字符串“Hello World”。如果已经安装了 Node,可以按照下面的步骤尝试一下:</p>
+
+<ol>
+ <li>打开终端(Windows 中打开命令行工具)</li>
+ <li>创建一个空文件夹用来存放项目,比如 <code>"test-node"</code>,然后在终端输入以下命令进入这个文件夹:</li>
+</ol>
+
+<pre class="brush: bash">cd test-node</pre>
+
+<ol start="3">
+ <li>用你最喜欢的文本编辑器创建一个名为 <code>"hello.js"</code> 的文件,把以下代码粘贴进来。</li>
+</ol>
+
+<pre class="brush: js">// 调用 HTTP 模块
+const http = require("http");
+
+// 创建 HTTP 服务器并监听 8000 端口的所有请求
+http.createServer((request, response) =&gt; {
+
+ // 用 HTTP 状态码和内容类型来设定 HTTP 响应头
+ response.writeHead(200, {'Content-Type': 'text/plain'});
+
+ // 发送响应体 "Hello World"
+ response.end('Hello World\n');
+}).listen(8000);
+
+// 在控制台打印访问服务器的 URL
+console.log('服务器运行于 http://127.0.0.1:8000/');</pre>
+
+<ol start="4">
+ <li>将其保存在刚才创建的文件夹。</li>
+ <li>返回终端并输入以下命令:</li>
+</ol>
+
+<pre class="brush: bash"><code>$ node "hello.js"</code></pre>
+
+<p>最后,在浏览器地址栏中输入 <code>"http://localhost:8000"</code> 并按回车,可以看到一个大面积空白的网页,左上角有 “Hello World" 字样。</p>
+
+<h2 id="Web_框架">Web 框架</h2>
+
+<p>Node 本身并不支持其它常见的 web 开发任务。如果需要进行一些具体的处理,比如运行其它 HTTP 动词(比如 <code>GET</code>、<code>POST</code>、<code>DELETE</code> 等)、分别处理不同 URL 路径的请求(“路由”)、托管静态文件,或用模板来动态创建响应,那么可能就要自己编写代码了,亦或使用 web 框架,以避免重新发明轮子。</p>
+
+<h2 id="什么是_Express">什么是 Express?</h2>
+
+<p><a href="http://www.expressjs.com.cn/">Express</a> 是最流行的 Node 框架,是许多其它流行 <a href="http://www.expressjs.com.cn/resources/frameworks.html">Node 框架</a> 的底层库。它提供了以下机制:</p>
+
+<ul>
+ <li>为不同 URL 路径中使用不同 HTTP 动词的请求(路由)编写处理程序。</li>
+ <li>集成了“视图”渲染引擎,以便通过将数据插入模板来生成响应。</li>
+ <li>设置常见 web 应用设置,比如用于连接的端口,以及渲染响应模板的位置。</li>
+ <li>在请求处理管道的任何位置添加额外的请求处理“中间件”。</li>
+</ul>
+
+<p>虽然 Express 本身是极简风格的,但是开发人员通过创建各类兼容的中间件包解决了几乎所有的 web 开发问题。这些库可以实现 cookie、会话、用户登录、URL 参数、<code>POST</code> 数据、安全头等功能。可在 <a href="http://www.expressjs.com.cn/resources/middleware.html">Express 中间件</a> 网页中找到由 Express 团队维护的中间件软件包列表(还有一张流行的第三方软件包列表)。</p>
+
+<div class="note">
+<p><strong>注:</strong> 这种灵活性是一把双刃剑。虽然有一些中间件包可以解决几乎所有问题或需求,但是挑选合适的包有时也会成为一个挑战。这里构建应用没有“不二法门”,Internet 上许多示例也不是最优的,或者只展示了开发 web 应用所需工作的冰山一角。</p>
+</div>
+
+<h2 id="Node_和_Express_从哪儿来">Node 和 Express 从哪儿来?</h2>
+
+<p>Node 发布于 2009 年,最初版本仅支持 Linux。NPM 包管理器发布于 2010年,并于 2012 年支持 Windows。目前(2019 年 1 月)的 LTS 版本是 Node 10.15.0,最新版本是 Node 11.8.0。这只是沧海一粟,更多历史信息请到 <a href="https://zh.wikipedia.org/wiki/Node.js#%E6%AD%B7%E5%8F%B2">维基百科</a> 探究。</p>
+
+<p>Express 发布于 2010 年 11 月,目前 API 的版本为 4.16.4。可以查看 <a href="http://www.expressjs.com.cn/changelog/4x.html#4.16.0">修改记录</a> 来查看当前版本的更新信息,或者访问 <a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a> 页面来查看更详细的历史发布记录。</p>
+
+<h2 id="Node_和_Express_有多流行">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="Express_是固执的吗?">Express 是固执的吗?</h2>
+
+<p>Web 框架通常自称“固执的(opinionated)”或“包容的(unopinionated)”。</p>
+
+<p>固执的框架认为应该有一套“标准答案”来解决各类具体任务。通常支持特定领域的快速开发(解决特定类型的问题)。因为标准答案通常易于理解且文档丰富。然而在解决主领域之外的问题时,就会显得不那么灵活,可用的组件和方法也更少。</p>
+
+<p>相比之下,那些包容的框架,对于用于实现目标的组件组合的最佳方式限制要少得多,甚至不怎么限定组件的选择。这使开发人员更容易使用最合适的工具来完成特定的任务,但是要付出亲自寻找组件的成本。</p>
+
+<p>Express 是高度包容的。几乎可以将任何兼容的中间件以任意顺序插入到请求处理链中,只要你喜欢。可以用单一文件或多个文件构造应用,怎样的目录结构都可以。有时候你自己都会觉得眼花缭乱!</p>
+
+<h2 id="Express_代码是什么样子的?">Express 代码是什么样子的?</h2>
+
+<p>传统的数据驱动型网站中,web 应用是用于等待来自浏览器(或其它客户端)的 HTTP 请求的。当 web 应用收到一个请求时,会根据 URL 的模式,以及 <code>POST</code> 数据和 <code>GET</code> 数据可能包含的信息,来解析请求所需的功能。根据请求的内容,web 应用可能会从数据库读或写一些信息,等等操作来满足请求。随后,web 应用会返回给浏览器一个响应,通常是动态生成一页 HTML,在页面中用所取得的信息填充占位符。</p>
+
+<p>使用 Express 可以调用特定 HTTP 动词(<code>GET</code>, <code>POST</code>, <code>SET</code>等)函数和 URL 模式(“路由”)函数,还可以指定模板(“视图”)引擎的种类、模板文件的位置以及渲染响应所使用的模板。可以使用 Express 中间件来添加对 cookie、会话、用户、获取 <code>POST</code>/<code>GET</code> 参数,等。可以使用Node 支持的任何类型数据库(Express 本身没有定义任何数据库行为)。</p>
+
+<p>下文将介绍 Express 和 Node 的一些常见知识点。</p>
+
+<h3 id="Helloworld_Express">Helloworld Express</h3>
+
+<p>首先来看 Express 的 <a href="http://www.expressjs.com.cn/starter/hello-world.html">Hello World</a> 的示例(下文将逐行讨论)。</p>
+
+<div class="note">
+<p><strong>提示:</strong>如果你已经安装了 Node 和 Express(或者你已经按照 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/development_environment">下一节</a> 中的说明安装好了),可以将此代码保存为 <strong>app.js</strong>,并通过在 bash 中这样运行它:</p>
+
+<p><strong><code>node ./app.js</code></strong></p>
+</div>
+
+<pre class="brush: js">const express = require('express');
+const app = express();
+
+<strong>app.get('/', (req, res) =&gt; {
+ res.send('Hello World!');
+});</strong>
+
+app.listen(3000, () =&gt; {
+ console.log('示例应用正在监听 3000 端口!');
+});
+</pre>
+
+<p>前两行通过 <code>require()</code> 导入 Express 模块,并创建了一个 <a href="http://www.expressjs.com.cn/4x/api.html#app">Express 应用</a>。传统上把这个对象命名为 <code>app</code>,它可以进行路由 HTTP 请求、配置中间件、渲染 HTML 视图、注册模板引擎以及修改 <a href="http://www.expressjs.com.cn/4x/api.html#app.settings.table">应用程序设置</a> 等操作,从而控制应用的行为(例如,环境模式,路由定义是否为区分大小写等)。</p>
+
+<p>代码的中间部分(从 <code>app.get()</code> 开始共三行)是<strong>路由定义</strong>。<code>app.get()</code> 方法指定了一个回调(callback)函数,该函数在每监听到一个关于站点根目录路径(<code>'/'</code>)的 HTTP <code>GET</code> 请求时调用。此回调函数以一个请求和一个响应对象作为参数,并直接调用响应的 <code><a href="http://www.expressjs.com.cn/4x/api.html#res.send">send()</a></code> 来返回字符串“Hello World!”</p>
+
+<p>最后一个代码块在 “3000” 端口上启动服务器,并在控制台打印日志。服务器运行时,可用浏览器访问 <code>localhost:3000</code>,看看响应返回了什么。</p>
+
+<h3 id="导入和创建模块">导入和创建模块</h3>
+
+<p>模块是 JavaScript 库或文件,可以用 Node 的 <code>require()</code> 函数将它们导入其它代码。Express 本身就是一个模块,Express 应用中使用的中间件和数据库也是。</p>
+
+<p>下面的代码以 Express 框架为例展示了如何通过名字来导入模块。首先,调用 <code>require()</code> 函数,用字符串(<code>'express'</code>)指定模块的名字,然后调用返回的对象来创建Express 应用 。然后就可以访问应用对象的属性和函数了。</p>
+
+<pre class="brush: js">const express = require('express');
+const app = express();
+</pre>
+
+<p>还可以创建自定义模块,并用相同的方法导入。</p>
+
+<div class="note">
+<p><strong>提示:</strong>你一定会有自建模块的<strong>需求</strong>,因为这可以让代码管理更有序。单文件应用是很难理解和维护的。使用模块还有助于管理名字空间,因为在使用模块时只会导入模块中显式导出的变量。</p>
+</div>
+
+<p>为了让对象暴露于模块之外,只需把它们设置为 <code>exports</code> 对象的附加属性即可。例如,下面的 <strong>square.js </strong>模块就是一个导出了 <code>area()</code> 和 <code>perimeter()</code> 方法的文件:</p>
+
+<pre class="brush: js">exports.area = width =&gt; { return width * width; };
+exports.perimeter = width =&gt; { return 4 * width; };
+</pre>
+
+<p>可以用 <code>require()</code> 导入这个模块,然后调用导出的方法,用法如下:</p>
+
+<pre class="brush: js">const square = require('./square');
+// 这里 require() 了文件名,省略了 .js 扩展名(可选)
+console.log('边长为 4 的正方形面积为 ' + square.area(4));</pre>
+
+<div class="note">
+<p><strong>注:</strong>为模块指定绝对路径(或模块的名字,见最初的示例)也是可行的。</p>
+</div>
+
+<p>一次赋值不仅能构建一个单一的属性,还能构建一个完整的对象,可以像下面这样把对象赋值给 <code>module.exports</code>(也可以让 <code>exports</code> 对象直接作为一个构造器或另一个函数):</p>
+
+<pre class="brush: js">module.exports = {
+ area: width =&gt; { return width * width; },
+ perimeter: width =&gt; { return 4 * width; }
+};</pre>
+
+<div class="blockIndicator note">
+<p><strong>注:</strong>在一个既定的模块内,可以把 <code>exports</code> 想象成 <code>module.exports</code> 的 <a href="http://nodejs.cn/api/modules.html#modules_exports_shortcut">快捷方式</a>。<code>exports</code> 本质上就是在模块初始化前为 <code>module.exports</code> 的值进行初始化的一个变量。这个值是对一个对象(这里是空对象)的引用。这意味着 <code>exports</code> 与 <code>module.exports</code> 引用了同一个对象,也意味着如果为 <code>exports</code> 赋其它值不会影响到 <code>module.exports</code>。</p>
+</div>
+
+<p>更多信息请参阅 <a href="http://nodejs.cn/api/modules.html">模块</a>(Node API 文档)。</p>
+
+<h3 id="使用异步_APIs">使用异步 APIs</h3>
+
+<p>JavaScript 代码在完成那些需要一段时间才能完成的操作时,经常会用异步 API 来取代同步 API 。同步 API 下,每个操作完成后才可以进行下一个操作。例如,下列日志函数是同步的,将按顺序将文本打印到控制台(第一、第二)。</p>
+
+<pre class="brush: js">console.log('第一');
+console.log('第二');
+</pre>
+
+<p>而异步 API 下,一个操作开始后(在其完成之前)会立即返回。一旦操作完成,API 将使用某种机制来执行附加操作。例如,下面的代码将打印“第二、第一”。这是因为虽然先调用了 <code>setTimeout()</code> 方法并立即返回,但它的操作到 3 秒后才完成。</p>
+
+<pre class="brush: js">setTimeout(() =&gt; {
+ console.log('第一');
+}, 3000);
+console.log('第二');</pre>
+
+<p>在 Node 中使用无阻塞异步 API 甚至比在浏览器中更为重要,这是因为 Node 是一个单线程事件驱动的执行环境。“单线程”意味着对服务器的所有请求运行在同一个线程上,而不是分布在不同的进程上。这个模式在速度和管理服务器资源方面效率很高,但也意味着如果以同步方式调用的函数占用了很长时间,不仅会阻塞当前请求,还会阻塞当前 web 应用其它所有请求。</p>
+
+<p>有多种方法可以让一个异步 API 通知当前应用它已执行完毕。最常用的是在调用异步 API 时注册一个回调函数,在 API 操作结束后将“回调”之。这也是上面的代码所使用的方法。</p>
+
+<div class="note">
+<p class="brush: html"><strong>提示:</strong>如果有一系列独立的异步操作必须按顺序执行,那么使用回调可能会非常“混乱”,因为这会导致多级嵌套回调。人们通常把这个问题叫做“回调地狱”。缓解这个问题有以下办法:良好的编码实践(参考 <a href="http://callbackhell.com/">http://callbackhell.com/</a>)、使用 <a href="https://www.npmjs.com/package/async">async</a> 等模块、迁移至 ES6 并使用 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> 等特性。</p>
+</div>
+
+<div class="note">
+<p><strong>注:</strong>Node 和 Express 有一个一般性约定,即:使用“错误优先”回调。这个约定要求回调函数的第一个参数是错误值,而后续的参数包含成功数据。以下博文很好的解释了这个方法的有效性:<a href="http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/%C2%A0">以 Node.js 之名:理解错误优先回调</a>(fredkschott.com 英文文章)</p>
+</div>
+
+<h3 id="创建路由处理器(Route_handler)">创建路由处理器(Route handler)</h3>
+
+<p>上文的 Hello World 示例中定义了一个(回调)路由处理函数来处理对站点根目录(<code>'/'</code>)的 HTTP <code>GET</code> 请求。</p>
+
+<pre class="brush: js">app.<strong>get</strong>('/', (req, res) =&gt; {
+ res.send('Hello World!');
+});
+</pre>
+
+<p>回调函数将请求和响应对象作为参数。该函数直接调用响应的 <code>send()</code> 以返回字符串“Hello World!”。有 <a href="http://www.expressjs.com.cn/guide/routing.html#response-methods">许多其它响应方法</a> 可以结束请求/响应周期,例如,通过调用 <code>res.json()</code> 来发送JSON 响应、调用 <code>res.sendFile()</code> 来发送文件。</p>
+
+<div class="note">
+<p><strong>JavaScript 提示</strong>:虽然回调函数的参数命名没有限制,但是当调用回调时,第一个参数将始终是请求,第二个参数将始终是响应。合理的命名它们,在回调体中使用的对象将更容易识别。</p>
+</div>
+
+<p><strong>Express 应用 </strong>对象还提供了为其它所有 HTTP 动词定义路由处理器的方法,大多数处理器的使用方式完全一致:</p>
+
+<p><code>checkout()</code>, <code>copy()</code>, <strong><code>delete()</code></strong>, <strong><code>get()</code></strong>, <code>head()</code>, <code>lock()</code>, <code>merge()</code>, <code>mkactivity()</code>, <code>mkcol()</code>, <code>move()</code>, <code>m-search()</code>, <code>notify()</code>, <code>options()</code>, <code>patch()</code>, <strong><code>post()</code></strong>, <code>purge()</code>, <strong><code>put()</code></strong>, <code>report()</code>, <code>search()</code>, <code>subscribe()</code>, <code>trace()</code>, <code>unlock()</code>, <code>unsubscribe()</code>.</p>
+
+<p>有一个特殊的路由方法 <code>app.all()</code>,它可以在响应任意 HTTP 方法时调用。用于在特定路径上为所有请求方法加载中间件函数。以下示例(来自 Express 文档)中的处理程序将在监听到针对 <code>/secret</code> 的任意 HTTP 动词(只要 <a href="http://nodejs.cn/api/http.html#http_http_methods">HTTP 模块</a> 支持)的请求后执行。</p>
+
+<pre class="brush: js">app.all('/secret', (req, res, next) =&gt; {
+ console.log('访问私有文件 ...');
+ next(); // 控制权传递给下一个处理器
+});
+</pre>
+
+<p>路由器可以匹配 URL 中特定的字符串模式,并从 URL 中提取一些值作为参数传递给路由处理程序(作为请求对象的属性)。</p>
+
+<p>可以为站点的特定部分提供一组路由处理器(使用公共路由前缀进行组合)。(比如对于一个有 维基(Wiki)内容的站点,可以把所有 Wiki 相关的路由放在同一个文件里,使用路由前缀 <em><code>'/wiki/'</code> </em>访问它们)。在 Express 中可以使用 <a href="http://www.expressjs.com.cn/guide/routing.html#express.Router">express.Router</a> 对象实现。例如,可以把所有维基相关的路由都放在一个 <strong>wiki.js </strong>模块中,然后导出 <code>Router</code> 对象,如下:</p>
+
+<pre class="brush: js">// wiki.js - 维基路由模块
+
+const express = require('express');
+const router = express.Router();
+
+// 首页路由
+router.get('/', (req, res) =&gt; {
+ res.send('维基首页');
+});
+
+// “关于”页面路由
+router.get('/about', (req, res) =&gt; {
+ res.send('关于此维基');
+});
+
+module.exports = router;
+</pre>
+
+<div class="note">
+<p><strong>注意:</strong>向 <code>Router</code> 对象添加路由就像向之前为 <code>app</code> 对象添加路由一样。</p>
+</div>
+
+<p>首先 <code>require()</code> 路由模块(<strong>wiki.js</strong>),然后在 Express 应用中调用 <code>use()</code> 把 <code>Router</code> 添加到中间件处理路径中,就可以在主应用中使用这个模块中的路由处理器了。路由路径有两条:<code style="font-style: normal; font-weight: normal;">/wiki</code> 和 <code style="font-style: normal; font-weight: normal;">/wiki/about/</code>。</p>
+
+<pre class="brush: js">const wiki = require('./wiki.js');
+// ...
+app.use('/wiki', wiki);
+</pre>
+
+<p>今后将介绍更多关于路由的信息,特别是关于 <code>Router</code> 的用法,请参见 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/routes">路由和控制器</a> 一节。</p>
+
+<h3 id="使用中间件(Middleware)">使用中间件(Middleware)</h3>
+
+<p>中间件在 Express 应用中得到了广泛使用,从提供错误处理静态文件、到压缩 HTTP 响应等等。路由函数可以通过向 HTTP 客户端返回一些响应来结束 HTTP “请求 - 响应”周期,而中间件函数<em>通常是</em>对请求或响应执行某些操作,然后调用“栈”里的下一个函数,可能是其它中间件或路由处理器。中间件的调用顺序由应用开发者决定。</p>
+
+<div class="note">
+<p><strong>注:</strong>中间件可以执行任何操作,运行任何代码,更改请求和响应对象,也可以<strong>结束“请求 - 响应”周期</strong>。如果它没有结束循环,则必须调用 <code>next()</code> 将控制传递给下一个中间件函数(否则请求将成为悬挂请求)。</p>
+</div>
+
+<p>大多数应用会使用<strong>第三方</strong>中间件来简化常见的 web 开发任务,比如 cookie、会话、用户身份验证、访问请求 <code>POST</code> 和 JSON 数据,日志记录等。参见 <a href="http://www.expressjs.com.cn/resources/middleware.html" rel="noopener">Express 团队维护的中间件包列表</a>(包含受欢迎的第三方包)。NPM 有提供其它 Express 包。</p>
+
+<p>要使用第三方中间件,首先需要使用 NPM 将其安装到当前应用中。比如,要安装 <a href="http://www.expressjs.com.cn/resources/middleware/morgan.html" rel="noopener">morgan</a> HTTP 请求记录器中间件,可以这样做:</p>
+
+<pre class="brush: bash"><code>$ npm install morgan
+</code></pre>
+
+<p>然后,您可以对 <em>Express 应用对象</em>调用 <code>use()</code> 将该中间件添加到栈:</p>
+
+<pre class="brush: js">const express = require('express');
+<strong>const logger = require('morgan');</strong>
+const app = express();
+<strong>app.use(logger('dev'));</strong>
+...</pre>
+
+<div class="note">
+<p><strong>注意:</strong>中间件和路由函数是按声明顺序调用的。一些中间件的引入顺序很重要(例如,如果会话中间件依赖于 cookie 中间件,则必须先添加 cookie 处理器)。绝大多数情况下要先调用中间件后设置路由,否则路由处理器将无法访问中间件的功能。</p>
+</div>
+
+<p>可以自己编写中间件函数,这是基本技能(仅仅为了创建错误处理代码也需要)。中间件函数和路由处理回调之间的<strong>唯一</strong>区别是:中间件函数有第三个参数 <code>next</code>,在中间件不会结束请求周期时应调用这个 <code>next</code>(它包含中间件函数调用后应调用的<strong>下一个</strong>函数)。</p>
+
+<p>可以使用 <code>app.use()</code> 或 <code>app.add()</code> 将一个中间件函数添加至处理链中,这取决于中间件是应用于所有响应的,还是应用于特定 HTTP 动词(<code>GET</code>,<code>POST</code>等)响应的。可以为两种情况指定相同的路由,但在调用 <code>app.use()</code> 时路由可以省略。</p>
+
+<p>下面的示例显示了如何使用这两种方法添加中间件功能,以及是否使用路由。</p>
+
+<pre class="brush: js">const express = require('express');
+const app = express();
+
+// 示例中间件函数
+const a_middleware_function = (req, res, <em>next</em>) =&gt; {
+ // ... 进行一些操作
+ next(); // 调用 next() ,Express 将调用处理链中下一个中间件函数。
+};
+
+// 用 use() 为所有的路由和动词添加该函数
+app.use(a_middleware_function);
+
+// 用 use() 为一个特定的路由添加该函数
+app.use('/someroute', a_middleware_function);
+
+// 为一个特定的 HTTP 动词和路由添加该函数
+app.get('/', a_middleware_function);
+
+app.listen(3000);</pre>
+
+<div class="note">
+<p><strong>JavaScript提示:</strong>上面代码中单独声明了中间件函数,并把它设置为回调。之前是把路由处理函数在使用时声明为回调。在 JavaScript 中,两种方法都可行。</p>
+</div>
+
+<p>请参阅 Express 文档中关于 <a href="http://www.expressjs.com.cn/guide/using-middleware.html" rel="noopener">使用</a> 和 <a href="http://www.expressjs.com.cn/guide/writing-middleware.html" rel="noopener">开发</a> Express 中间件的内容。</p>
+
+<h3 id="托管静态文件">托管静态文件</h3>
+
+<p>可以使用 <a href="http://www.expressjs.com.cn/4x/api.html#express.static">express.static</a> 中间件来托管静态文件,包括图片、CSS 以及 JavaScript 文件(其实 <code>static()</code> 是 Express 提供的<strong>原生</strong>中间件函数之一)。例如,可以通过下面一行来托管 'public' 文件夹(应位于 Node 调用的同一级)中的文件:</p>
+
+<pre class="brush: js">app.use(express.static('public'));
+</pre>
+
+<p>现在 'public' 文件夹下的所有文件均可通过在根 URL 后直接添加文件名来访问了,比如:</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>可以通过多次调用 <code>static()</code> 来托管多个文件夹。如果一个中间件函数找不到某个文件,将直接传递给下一个中间件(中间件的调用顺序取决于声明顺序)。</p>
+
+<pre class="brush: js">app.use(express.static('public'));
+app.use(express.static('media'));
+</pre>
+
+<p>还可以为静态 URL 创建一个虚拟的前缀,而不是直接把文件添加到根 URL 里。比如,这里 <a href="http://www.expressjs.com.cn/4x/api.html#app.use">指定了一个装载路径</a>,于是这些文件将通过 '/media' 前缀调用:</p>
+
+<pre class="brush: js">app.use('/media', express.static('public'));
+</pre>
+
+<p>现在可以通过 '/media' 路径前缀来访问 'public' 文件夹中的文件。</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>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/starter/static-files.html">托管静态文件</a>。</p>
+
+<h3 id="错误处理">错误处理</h3>
+
+<p>用来处理错误的特殊中间件函数有四个参数<code>(err, req, res, next)</code>,而不是之前的三个。例如:</p>
+
+<pre class="brush: js">app.use((err, req, res, next) =&gt; {
+ console.error(err.stack);
+ res.status(500).send('出错了!');
+});
+</pre>
+
+<p>错误处理中间件可以任何所需内容,但是必须在所有其它 <code>app.use()</code> 和路由调用后才能调用,因此它们是需求处理过程中最后的中间件。</p>
+
+<p>Express 内建了错误处理机制,可以协助处理 app 中没有被处理的错误。默认的错误处理中间件函数在中间件函数栈的末尾。如果一个错误传递给 <code>next()</code> 而没有用错误处理器来处理它,内建处理机制将启动,栈跟踪的错误将回写给客户端。</p>
+
+<div class="note">
+<p><strong>注:</strong> 生产环境中不保留栈跟踪轨迹。可将环境变量 <code>NODE_ENV</code> 设置为 <code>'production'</code> 来运行所需的生产环境。</p>
+</div>
+
+<div class="note">
+<p><strong>注:</strong>HTTP404 和其它“错误”状态码不作为错误处理。可使用中间件来自行处理这些状态。更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/starter/faq.html#如何处理-404-响应">FAQ</a>。</p>
+</div>
+
+<p>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/guide/error-handling.html">错误处理</a>。</p>
+
+<h3 id="使用数据库">使用数据库</h3>
+
+<p><em>Express</em> 应用可以使用 Node 支持的所有数据库(Express 本身并没有定义任何数据库管理的附加行为或需求)。其中包括:PostgreSQL、MySQL、Redis、SQLite、MongoDB,等等。</p>
+
+<p>使用数据库前先要用 NPM 来安装驱动程序。比如,要安装流行的 NoSQL 数据库 MongoDB 的驱动程序,可运行以下命令:</p>
+
+<pre class="brush: bash"><code>$ npm install mongodb
+</code></pre>
+
+<p>数据库可以安装在本地或云端。在 Express 代码中 <code>require()</code> 驱动程序,连接,然后就可以执行增加、读取、更新、删除四种操作(CRUD)。以下示例展示了如何查找 MongoDB 表中 '哺乳动物' 的记录:</p>
+
+<pre class="brush: js">// MongoDB 3.0 以上版本适用,老版本不适用。
+const MongoClient = require('mongodb').MongoClient;
+
+MongoClient.connect('mongodb://localhost:27017/animals', (err, client) =&gt; {
+ if(err) {
+ throw err;
+ }
+
+ let db = client.db('动物');
+ db.collection('哺乳动物').find().toArray((err, result) =&gt; {
+ if(err) throw err;
+ console.log(result);
+ client.close();
+ });
+});</pre>
+
+<p>还有一种通过“对象关系映射(Object Relational Mapper,简称 ORM)”间接访问数据库的方法。可以把数据定义为“对象”或“模型”,然后由 ORM 根据给定的数据库格式搞定所有映射关系。这种方法对于开发者有一个好处:可以用 JavaScript 对象的思维而无需直接使用数据库语法,同时传进的数据也有现成的检查工具。稍后详细讨论数据库问题。.</p>
+
+<p>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>。</p>
+
+<h3 id="渲染数据(视图,view)">渲染数据(视图,view)</h3>
+
+<p>模板引擎可为输出文档的结构指定一个模板,在数据处先放置占位符,并于页面生成时填充。模板通常用于生成 HTML,也可以生成其它类型的文档。Express 支持 <a href="https://github.com/expressjs/express/wiki#template-engines">多个版本的模板引擎</a>,可以参阅:<a href="https://strongloop.com/strongblog/compare-javascript-templates-jade-mustache-dust/">JavaScript 模板引擎对比评测:Jade、Mustache、Dust与其它</a>。</p>
+
+<p>在应用设置代码中声明了模板引擎的名称和位置后,Express 可以使用 <code>'views'</code> 和 <code>'view engines'</code> 设置来寻找模板,如下所示(必须事先安装包含模板库的包!):</p>
+
+<pre class="brush: js">const express = require('express');
+const app = express();
+
+// 设置包含模板的文件夹('views')
+app.set('views', path.join(__dirname, 'views'));
+
+// 设置视图引擎,比如'some_template_engine_name'
+app.set('view engine', 'some_template_engine_name');
+</pre>
+
+<p>模板的外观取决于所使用的引擎。假设一个模板文件名为 "index.&lt;template_extension&gt;",其中包括数据变量 <code>'title'</code> 和 <code>'message'</code> 的两个占位符,可以在路由处理器函数中调用 <code><a href="http://www.expressjs.com.cn/4x/api.html#res.render">Response.render()</a></code> 来创建并发送 HTML 响应:</p>
+
+<pre class="brush: js">app.get('/', (req, res) =&gt; {
+ res.render('index', { title: '关于狗狗', message: '狗狗很牛!' });
+});</pre>
+
+<p>更多信息请参见 Express 文档 <a href="http://www.expressjs.com.cn/guide/using-template-engines.html">使用模板引擎</a>。</p>
+
+<h3 id="文件结构">文件结构</h3>
+
+<p>Express 不对文件结构和组件的选用做任何约定。路由、视图、静态文件,以及其它应用具体逻辑均可按任意文件结构保存在任意数量的文件中。当然可以让整个 Express 应用保存在单一文件中,但是一般情况下,把应用按功能(比如账户管理、博客、论坛)和架构问题域(比如 <a href="/zh-CN/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC 架构</a> 中的模型、视图、控制器)进行拆分是有意义的。</p>
+
+<p>后文将使用 <strong>Express 应用生成器 </strong>来创建一个模块化的应用框架,从而可以更方便的扩展出新的 web 应用。</p>
+
+<ul>
+</ul>
+
+<h2 id="小结">小结</h2>
+
+<p>恭喜,你迈出了 Express/Node 旅程的第一步 !你现在已经了解了 Express 与 Node 的主要优势,并大致了解了 Express 应用的结构 (路由处理器、中间件、错误处理和模板代码)。你还了解到 Express 作为一个高度包容的框架,让你在组织应用结构和库时更自由,更开放!</p>
+
+<p>诚然,Express 是一个非常轻量的 web 应用框架,这是有意为之的,它巨大的裨益和无尽的潜能都来自第三方的库和功能。今后的章节会详细讨论。下一节会讲如何设置 Node 开发环境,之后就能开始 Express 的实战了。</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li><a href="https://medium.com/@ramsunvtech/manage-multiple-node-versions-e3245d5ede44">Venkat.R - 管理多版本 Node</a>(英文页面)</li>
+ <li><a href="http://nodejs.cn/api/modules.html">模块</a> (Node API 中文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/">Express 主页</a> (Express 国内镜像)</li>
+ <li><a href="http://www.expressjs.com.cn/starter/basic-routing.html">路由入门</a>(Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/routing.html">路由指南</a> (Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/using-template-engines.html">使用模板引擎</a> (Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/using-middleware.html">使用中间件</a>(Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/writing-middleware.html">开发中间件</a>(Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>(Express 英文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/starter/static-files.html">托管静态文件</a> (Express 中文文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/error-handling.html">错误处理</a> (Express 英文文档)</li>
+</ul>
+
+<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div>
+
+<h2 id="本章目录">本章目录</h2>
+
+<div>
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
+</div>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html b/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html
new file mode 100644
index 0000000000..6233ef5edb
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html
@@ -0,0 +1,831 @@
+---
+title: Express 教程 3:使用数据库 (Mongoose)
+slug: learn/Server-side/Express_Nodejs/mongoose
+tags:
+ - Express
+ - MongoDB
+ - Node
+ - ODM
+ - mongoose
+ - nodejs
+ - 初学者
+ - 学习
+ - 数据库
+ - 服务器端
+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> 为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 提供数据库访问的方式。还讲解了对象模式(Schema)和模型(Model)的声明方式、主要域的类型、基础验证机制。同时还简短演示了访问模型数据的一些方法。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识:</th>
+ <td><a href="https://developer.mozilla.org/zh-CN/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>图书馆的员工会使用 LocalLibrary 网站来保存藏书和借阅者的信息。会员会浏览和查找所需藏书,找到后预约或借阅。为了更高效地存取信息,网站将使用数据库。</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>选用数据库应考虑以下因素:进入生产状态用时/学习曲线、性能、复制/备份的易用度、成本、社区支持,等等。这些数据库各有千秋,但绝大多数都足以胜任 LocalLibrary 这样中小规模的网站了。</p>
+
+<p>更多信息请参阅:<a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>(Express 文档)。</p>
+
+<h3 id="与数据库交互的最佳方式是什么">与数据库交互的最佳方式是什么?</h3>
+
+<p>与数据库交互有两种方法:</p>
+
+<ul>
+ <li>使用数据库的原生查询语言(例如SQL)</li>
+ <li>使用对象数据模型(Object Data Model,简称 ODM)或对象关系模型(Object Relational Model,简称 ORM)。 ODM / ORM 能将网站中的数据表示为 JavaScript 对象,然后将它们映射到底层数据库。一些 ORM 只适用某些特定数据库,还有一些是普遍适用的。</li>
+</ul>
+
+<p>使用 SQL 或其它受到支持的查询语言才能达到最佳性能。ODM 通常慢一些,因为在对象和数据库格式之间存在一层用于映射的翻译代码,使它不一定会选用最高性能的数据库查询(尤其是普遍使用级别的 ODM,它必须在各类数据库功能方面做出更大的折衷)。</p>
+
+<p>使用 ORM 的好处是:程序员可以继续用 JavaScript 对象的思维而不用转向数据库语义的思维。 在(同一个或不同网站)使用不同数据库时尤为明显。使用 ORM 还可以更方便地对数据进行验证和检查。</p>
+
+<div class="note">
+<p><strong>提示:</strong>使用 ODM / ORM 通常可以降低开发和维护成本!除非你非常熟悉本地查询语言,或者项目对性能要求很高,否则强烈推荐使用 ODM。</p>
+</div>
+
+<h3 id="我应该使用哪种_ORMODM">我应该使用哪种 ORM/ODM ?</h3>
+
+<p>NPM 站点上有许多 ODM / ORM 解决方案(另请参阅 NPM 站点上的 <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> 和 <a href="https://www.npmjs.com/browse/keyword/orm">orm</a> 标签列表)。</p>
+
+<p>以下是迄今(2018 年 12 月)几种流行的解决方案:</p>
+
+<ul>
+ <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>:一款为异步工作环境设计的 <a href="https://www.mongodb.org/">MongoDB</a> 对象建模工具。</li>
+ <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>:从基于Express 的 <a href="http://sailsjs.com/">Sails</a> 框架中提取的 ORM。它提供了一套统一的 API 来访问众多不同的数据库,其中包括 Redis,mySQL,LDAP,MongoDB 和 Postgres。</li>
+ <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>:同时提供基于 promise 和传统回调两套接口,支持事务处理、渴求式/嵌套渴求式关系加载、多态关联,以及对一对一,一对多和多对多关系。支持 PostgreSQL、MySQL 和 SQLite3。</li>
+ <li><a href="https://www.npmjs.com/package/objection">Objection</a>:以尽可能简单的方式使用 SQL 和底层数据库引擎的全部功能(支持SQLite3、Postgres 和 MySQL)。</li>
+ <li><a href="https://www.npmjs.com/package/sequelize">Sequelize</a>:基于 promise 的 Node.js 版 ORM,它支持 PostgreSQL、MySQL、MariaDB、SQLite 和 MSSQL,并提供可靠的事务支持、关系、复本读取等功能。</li>
+ <li><a href="https://node-orm.readthedocs.io/en/latest/">Node ORM2</a>:一款 Node.js 对象关系管理系统。支持 MySQL、SQLite 以及 Progress,可以帮助你用面向对象的方法操作数据库。</li>
+ <li><a href="http://1602.github.io/jugglingdb/" rel="nofollow">JugglingDB</a>:一款 Node.js 版跨数据库的 ORM。它为多数流行数据库提供了统一接口,当前支持 MySQL、SQLite3、Postgres、MongoDB、Redis 和 js-memory-storage(自写引擎,仅供测试用)。</li>
+</ul>
+
+<p>一般来说,选择解决方案应该考虑功能和“社区活跃度”(下载量、贡献数、bug 报告、文档质量,等)。在撰写本文时,Mongoose 是最受欢迎的 ODM,选用 MongoDB 数据库时,它是一个合理的选择。</p>
+
+<h3 id="在_LocalLibrary_中使用_Mongoose_和_MongoDb">在 LocalLibrary 中使用 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>(面向文档数据模型的开源 <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,但是部分 <a href="http://mongoosejs.com/docs/guide.html">Mongoose文档</a> 对于熟悉 MongoDB 的朋友会更易于使用和理解。</p>
+</div>
+
+<p>下面将介绍如何为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary 网站</a> 定义和访问 Mongoose 模式和模型。</p>
+
+<h2 id="设计_LocalLibrary_模型">设计 LocalLibrary 模型</h2>
+
+<p>在开始投入模型编写之前,有必要先思考一下:本网站需要存储什么数据?不同对象之间的关系是怎样的?</p>
+
+<p>图书馆需要存储藏书信息(书名、摘要、作者、种类、ISBN),藏书副本信息(全站唯一 ID,借出状态,等)。还可能需要存储作者姓名之外的更多信息,以及多个作者的信息。还希望数据库内容能够根据书名、作者姓名、种类和编目进行排序。</p>
+
+<p>有必要为每个“对象”(一组相关信息)设计独立的模型。本示例的关键对象包括书籍、书籍副本和作者。</p>
+
+<p>也许还希望使用模型而不是站点代码来表示选项表(比如下拉列表),在选项无法预知或可能更改时更推荐模型方式。很明显,藏书类型(比如科幻小说、法语诗歌,等)就是这种情况。</p>
+
+<p>确定模型和字段后还要考虑它们之间的关系,以下 UML 图显示了本示例即将定义的模型(框图)。如上所述,我们为藏书(一般细节)、藏书副本(系统)和作者创建了模型。还有一个可以动态选择的书籍种类模型。对于 <code>BookInstance:status</code>,我们不会为它建立模型,而是将可能的值直接编入站点代码中,因为我们不希望这些值发生变化。下图每个框都包括模型名、字段名和类型,还有方法及其返回类型。</p>
+
+<p>下图还展示了模型之间的关系以及重复度(Multiplicity)。重复度就是图中两框间连线两端的数字,表示两个模型之间存在的关系的数量(最大值和最小值)。例如,<code>Book</code> 框和 <code>Genre</code> 框之间有连线说明二者之间存在关系,<code>Book</code> 模型端的数字(0..*)表示一个种类必包括零种或多种藏书(多少都可以),而 <code>Genre</code> 端的数字表示一种藏书可以有零个或多个种类。</p>
+
+<div class="note">
+<p><strong>注:</strong>正如下文 <a href="#">Mongoose 入门</a> 中所讲,通常应该把定义文档/模型关系的字段置于同一模型中(仍可通过在搜索相关 <code>_id</code> 来回寻模型间的关系)。以下的 Book 模式中定义了 Book/Genre 和 Book/Author 关系,BookInstance 模式中定义了 Book/BookInstance 关系。这样做是简便起见,但稍存歧义,让这些字段存在于其他模式中也是可以的。</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>注:</strong>下面是一段入门知识,讲解如何定义和使用模型。请在阅读时想想将如何构建上图中的模型。</p>
+</div>
+
+<h2 id="Mongoose_入门">Mongoose 入门</h2>
+
+<p>这一段将简要介绍如何将 Mongoose 连接到 MongoDB 数据库,如何定义模式和模型,以及如何进行基本查询。</p>
+
+<div class="note">
+<p><strong>注:</strong>本入门受到 npm 上的 <a href="https://www.npmjs.com/package/mongoose">Mongoose 快速入门</a> 和 <a href="http://mongoosejs.com/docs/guide.html">Mongoose 官方文档</a> 的“深度影响”。</p>
+</div>
+
+<h3 id="安装_Mongoose_和_MongoDB">安装 Mongoose 和 MongoDB</h3>
+
+<p>Mongoose 像任何其他依赖项一样,使用 NPM 将其安装在您的项目(<strong>package.json</strong>)中 。请在项目文件夹中运行下面的命令以完成安装:</p>
+
+<pre class="brush: bash notranslate"><code>$ npm install mongoose</code>
+</pre>
+
+<p>安装 Mongoose 会添加所有依赖项,包括 MongoDB 数据库驱动程序,但不会安装 MongoDB 本身。要安装 MongoDB 服务器,可以 <a href="https://www.mongodb.com/download-center">点击下载</a> 各操作系统的安装程序在本地安装。也可以使用云端 MongoDB 实例。</p>
+
+<div class="note">
+<p><strong>注:</strong>本教程选用 mLab 提供的 <a href="https://mlab.com/plans/pricing/">沙箱级 </a>云端“数据库即服务”(Database as a Service,DBaaS)。它适用于开发环境,且部署过程与操作系统无关(DBaaS 也适用于生产环境)。</p>
+</div>
+
+<h3 id="连接到_MongoDB">连接到 MongoDB</h3>
+
+<p>Mongoose 需要连接到 MongoDB 数据库。可以 <code>require()</code> 之,并通过 <code>mongoose.connect()</code> 连接到本地数据库,如下。</p>
+
+<pre class="brush: js notranslate">// 导入 mongoose 模块
+const mongoose = require('mongoose');
+
+// 设置默认 mongoose 连接
+const mongoDB = 'mongodb://127.0.0.1/my_database';
+mongoose.connect(mongoDB);
+// 让 mongoose 使用全局 Promise 库
+mongoose.Promise = global.Promise;
+// 取得默认连接
+const db = mongoose.connection;
+
+// 将连接与错误事件绑定(以获得连接错误的提示)
+db.on('error', console.error.bind(console, 'MongoDB 连接错误:'));</pre>
+
+<p>可以用 <code>mongoose.connection</code> 取得默认的 <code>Connection</code> 对象。一旦连接,<code>Connection</code> 实例将触发打开事件。</p>
+
+<div class="note">
+<p><strong>提示:</strong>可以使用 <code>mongoose.createConnection()</code> 创建其它连接。该函数与 <code>connect()</code> 的参数(数据库 URI,包括主机地址、数据库名、端口、选项等)一致,并返回一个 <code>Connection</code> 对象。</p>
+</div>
+
+<h3 id="定义和添加模型">定义和添加模型</h3>
+
+<p>模型使用 <code>Schema</code> 接口进行定义。 <code>Schema</code> 可以定义每个文档中存储的字段,及字段的验证要求和默认值。还可以通过定义静态和实例辅助方法来更轻松地处理各种类型的数据,还可以像使用普通字段一样使用数据库中并不存在的虚拟属性(稍后讨论)。</p>
+
+<p><code>mongoose.model()</code> 方法将模式“编译”为模型。模型就可以用来查找、创建、更新和删除特定类型的对象。</p>
+
+<div class="note">
+<p><strong>注:</strong>MongoDB 数据库中,每个模型都映射至一组文档。这些文档包含 <code>Schema</code> 模型定义的字段名/模式类型。</p>
+</div>
+
+<h4 id="定义模式">定义模式</h4>
+
+<p>下面的代码片段中定义了一个简单的模式。首先 <code>require()</code> mongoose,然后使用 <code>Schema</code> 构造器创建一个新的模式实例,使用构造器的对象参数定义各个字段。</p>
+
+<pre class="brush: js notranslate">// 获取 Mongoose
+const mongoose = require('mongoose');
+
+// 定义一个模式
+var Schema = mongoose.Schema;
+
+var SomeModelSchema = new Schema({
+ a_string: String,
+ a_date: Date
+});
+</pre>
+
+<p>上面示例只有两个字段(一个字符串和一个日期),接下来将展示其它字段类型、验证和其它方法。</p>
+
+<h4 id="创建模型">创建模型</h4>
+
+<p>使用 <code>mongoose.model()</code> 方法从模式创建模型:</p>
+
+<pre class="brush: js notranslate">// 定义模式
+const Schema = mongoose.Schema;
+
+const SomeModelSchema = new Schema({
+ a_string: String,
+ a_date: Date
+});
+
+<strong>// 使用模式“编译”模型
+const SomeModel = mongoose.model('SomeModel', SomeModelSchema);</strong></pre>
+
+<p>第一个参数是为模型所创建集合的别名(Mongoose 将为 SomeModel 模型创建数据库集合),第二个参数是创建模型时使用的模式。</p>
+
+<div class="note">
+<p><strong>注:</strong>定义模型类后,可以使用它们来创建、更新或删除记录,以及通过查询来获取所有记录或特定子集。我们将在以下“<a href="#">使用模型</a>”部分展示,包括创建视图的情况。</p>
+</div>
+
+<h4 id="模式类型(字段)">模式类型(字段)</h4>
+
+<p>模式可以包含任意数量的字段,每个字段代表 MongoDB 文档中的一段存储区域。下面是一个模式的示例,其中有许多常见字段类型和声明方式:</p>
+
+<pre class="brush: js notranslate">const 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>], // 其他类型也可使用数组
+ nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } }
+})</pre>
+
+<p>大多数 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a>( <a href="http://mongoosejs.com/docs/schematypes.html">SchemaType</a>,字段名之后的描述符)都是自解释的。除了:</p>
+
+<ul>
+ <li><code>ObjectId</code>:表示数据库中某一模型的特定实例。例如,一本书可能会使用它来表示其作者对象。它实际只包含指定对象的唯一 ID(<code>_id</code>) 。可以使用 <code>populate()</code> 方法在需要时提取相关信息。</li>
+ <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>:任意模式类型。</li>
+ <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>:对象数组。以在此类模型上执行 JavaScript 数组操作(<code>push</code>、<code>pop</code>、<code>unshift</code>等)。上例中有一个没有指定类型的对象数组和一个 <code>String</code> 对象数组,数组中的对象可以是任意类型的。</li>
+</ul>
+
+<p>代码还展示了声明字段的两种方法:</p>
+
+<ul>
+ <li>字段名和类型名作为键-值对(就像 <code>name</code>、<code>binary</code> 和 <code>living</code>)。</li>
+ <li>字段名后跟一个对象,在对象中定义 <code>type</code> 和字段的其它选项,可以是:
+ <ul>
+ <li>默认值。</li>
+ <li>内置验证器(例如最大/最小值)和自定义验证函数。</li>
+ <li>该字段是否必需。</li>
+ <li>是否将 <code>String</code> 字段自动转换为小写、大写,或截断两端空格(例如<code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li>
+ </ul>
+ </li>
+</ul>
+
+<p>关于选项的更多信息请参阅 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a>(Mongoose 英文文档)。</p>
+
+<h4 id="验证">验证</h4>
+
+<p>Mongoose 提供内置的和自定义的验证器,以及同步的和异步的验证器。你可以在所有情况下,指定可接受的范围或值,以及验证失败的错误消息。</p>
+
+<p>内置的验证器包括:</p>
+
+<ul>
+ <li>所有 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a> 都具有内置的 <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a> 验证器。用于指定当前字段是否为保存文档所必需的。</li>
+ <li><a href="https://mongoosejs.com/docs/api.html#mongoose_Mongoose-Number">Number</a> 有数值范围验证器 <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> 和 <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a>。</li>
+ <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">String</a> 有:
+ <ul>
+ <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a>:指定当前字段允许值的集合。</li>
+ <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a>:指定字符串必须匹配的正则表达式。</li>
+ <li>字符串的最大长度 <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a> 和最小长度 <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a></li>
+ </ul>
+ </li>
+</ul>
+
+<p>以下是类型验证器和错误消息的设定方法(从 Mongoose 文档稍作修改而来):</p>
+
+<pre class="brush: js notranslate">const breakfastSchema = new Schema({
+ eggs: {
+ type: Number,
+ min: [6, '鸡蛋太少'],
+ max: 12
+ },
+ drink: {
+ type: String,
+ enum: ['咖啡', '茶']
+ }
+});</pre>
+
+<p>字段验证的完整信息请参阅 <a href="http://mongoosejs.com/docs/validation.html">验证</a>(Mongoose 英文文档)。</p>
+
+<h4 id="虚拟属性">虚拟属性</h4>
+
+<p>虚拟属性是可以获取和设置、但不会保存到 MongoDB 的文档属性。getter 可用于格式化或组合字段,而 setter 可用于将单个值分解为多个值从而便于存储。文档中的示例,从名字和姓氏字段构造(并解构)一个全名虚拟属性,这比每次在模板中使用全名更简单,更清晰。</p>
+
+<div class="note">
+<p><strong>注:</strong>我们将使用库中的一个虚拟属性,用路径和记录的 <code>_id</code> 来为每个模型记录定义唯一的 URL。</p>
+</div>
+
+<p>更多信息请参阅 <a href="http://mongoosejs.com/docs/guide.html#virtuals">虚拟属性</a>(Mongoose 英文文档)。</p>
+
+<h4 id="方法和查询助手">方法和查询助手</h4>
+
+<p>模式支持 <a href="http://mongoosejs.com/docs/guide.html#methods">实例方法</a>、<a href="http://mongoosejs.com/docs/guide.html#statics">静态方法</a> 和 <a href="http://mongoosejs.com/docs/guide.html#query-helpers">查询助手</a>。实例方法和静态方法外表很相似,但有本质区别,实例方法针对特定记录,且可以访问当前对象。查询助手可用于扩展 Mongoose 的 <a href="http://mongoosejs.com/docs/queries.html">链式查询 API</a>(例如,在 <code>find()</code>、<code>findOne()</code> 和 <code>findById()</code> 方法外还可以添加一个 “<code>byName</code>” 查询)。</p>
+
+<h3 id="使用模型">使用模型</h3>
+
+<p>就可以使用创建好的模式来创建模型。模型即数据库中可以搜索的一类文档,模型的实例即可以存取的单个文档。</p>
+
+<p>以下是简介。更多信息请参阅:<a href="http://mongoosejs.com/docs/models.html">模型</a>(Mongoose 英文文档)。</p>
+
+<h4 id="创建和修改文档">创建和修改文档</h4>
+
+<p>可以通过定义模型的实例并调用 <code>save()</code> 来创建记录。以下示例假定 <code>SomeModel</code> 是用现有模式创建的模型(只有一个字段 "<code>name</code>" ):</p>
+
+<pre class="brush: js notranslate"><code>// 创建一个 SomeModel 模型的实例
+const awesome_instance = new </code>SomeModel<code>({ name: '牛人' });
+
+// 传递回调以保存这个新建的模型实例
+awesome_instance.save( function (err) {
+ if (err) {
+ return handleError(err);
+ } // 已保存
+});
+</code></pre>
+
+<p>记录的创建(以及更新、删除和查询)操作是异步的,可以提供一个回调函数在操作完成时调用。由于 API 遵循错误参数优先的惯例,因此回调的第一个参数必为错误值(或 <code>null</code>)。如果 API 需要返回一些结果,则将结果作为第二个参数。</p>
+
+<p>还可以使用 <code>create()</code>,在定义模型实例的同时将其保存。回调的第一个参数返回错误,第二个参数返回新建的模型实例。</p>
+
+<pre class="brush: js notranslate">SomeModel<code>.create(
+ { name: '也是牛人' },
+ function(err, awesome_instance) {
+ if (err) {
+ return handleError(err);
+ } // 已保存
+ }
+);</code></pre>
+
+<p>每个模型都有一个相关的连接(使用 <code>mongoose.model()</code> 时将做为默认连接)。可以通过创建新连接并对其调用 <code>.model()</code>,从而在另一个数据库上创建文档。</p>
+
+<p>可以使用“圆点”加字段名来访问、修改新记录中的字段。操作后必须调用 <code>save()</code> 或 <code>update()</code> 以将改动保存回数据库。</p>
+
+<pre class="brush: js notranslate">// 使用圆点来访问模型的字段值
+console.log(<code>awesome_instance.name</code>); // 控制台将显示 '<code>也是牛人</code>'
+
+// 修改字段内容并调用 save() 以修改记录
+<code>awesome_instance</code>.name = "酷毙了的牛人";
+<code>awesome_instance.save( function(err) {
+ if (err) {
+ return handleError(err);
+ } // 已保存
+});</code>
+</pre>
+
+<h4 id="搜索纪录">搜索纪录</h4>
+
+<p>可以使用查询方法搜索记录,查询条件可列在 JSON 文档中。以下代码展示了如何在数据库中找到所有网球运动员,并返回运动员姓名和年龄字段。这里只指定了一个匹配字段(运动项目,<code>sport</code>),也可以添加更多条件,指定正则表达式,或去除所有条件以返回所有运动员。</p>
+
+<pre class="brush: js notranslate"><code>const Athlete = mongoose.model('Athlete', yourSchema);
+
+// SELECT name, age FROM Athlete WHERE sport='Tennis'
+Athlete.find(
+ { 'sport': 'Tennis' },
+ 'name age',
+ function (err, athletes) {
+ if (err) {
+ return handleError(err);
+ } // 'athletes' 中保存一个符合条件的运动员的列表
+ }
+);</code></pre>
+
+<p>若像上述代码那样指定回调,则查询将立即执行。搜索完成后将调用回调。</p>
+
+<div class="note">
+<p><strong>注:</strong>Mongoose 中所有回调都使用 <code>callback(error, result)</code> 模式。如果查询时发生错误,则参数 <code>error</code> 将包含错误文档,<code>result</code> 为 <code>null</code>。如果查询成功,则 <code>error</code>为 <code>null</code>,查询结果将填充至 <code>result</code> 。</p>
+</div>
+
+<p>若未指定回调,则 API 将返回 <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a> 类型的变量。可以使用该查询对象来构建查询,随后使用 <code>exec()</code> 方法执行(使用回调)。</p>
+
+<pre class="brush: js notranslate"><code>// 寻找所有网球运动员
+const query = Athlete.find({ 'sport': 'Tennis' });
+
+// 查找 name, age 两个字段
+query.select('name age');
+
+// 只查找前 5 条记录
+query.limit(5);
+
+// 按年龄排序
+query.sort({ age: -1 });
+
+// 以后某个时间运行该查询
+query.exec(function (err, athletes) {
+ if (err) {
+ return handleError(err);
+ } // athletes 中保存网球运动员列表,按年龄排序,共 5 条记录
+})</code></pre>
+
+<p>上面的查询条件定义在 <code>find()</code> 方法中。也可以使用 <code>where()</code> 函数来执行此操作,可以使用点运算符(<code>.</code>)将所有查询链接在一起。以下代码与上述的查询基本相同,还添加了年龄范围的附加条件。</p>
+
+<pre class="brush: js notranslate"><code>Athlete.
+ find().
+ where('sport').equals('Tennis').
+ where('age').gt(17).lt(50). // 附加 WHERE 查询
+ limit(5).
+ sort({ age: -1 }).
+ select('name age').
+ exec(callback); // 回调函数的名字是 callback</code></pre>
+
+<p><code><a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a></code> 方法会取得所有匹配记录,但通常你只想取得一个。以下方法可以查询单个记录:</p>
+
+<ul>
+ <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>:用指定 <code>id</code> 查找文档(每个文档都有一个唯一 <code>id</code>)。</li>
+ <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>:查找与指定条件匹配的第一个文档。</li>
+ <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>、<code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>、<code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>、 <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>:通过 <code>id</code> 或条件查找单个文档,并进行更新或删除。以上是更新和删除记录的便利函数。</li>
+</ul>
+
+<div class="note">
+<p><strong>注:</strong>还有一个 <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code> 方法,可获取匹配条件的项目的个数。在只期望获得记录的个数而不想获取实际的记录时可以使用。</p>
+</div>
+
+<p>查询还能做更多。请参阅 <a href="http://mongoosejs.com/docs/queries.html">查询</a>(Mongoose 英文文档)。</p>
+
+<h4 id="文档间协同_——_population">文档间协同 —— population</h4>
+
+<p>可以使用 <code>ObjectId</code> 模式字段来创建两个文档/模型实例间一对一的引用,(一组 <code>ObjectIds</code> 可创建一对多的引用)。该字段存储相关模型的 id。如果需要相关文档的实际内容,可以在查询中使用 <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code> 方法,将 id 替换为实际数据。</p>
+
+<p>例如,以下模式定义了作者和简介。每个作者可以有多条简介,我们将其表示为一个 <code>ObjectId</code> 数组。每条简介只对应一个作者。“<code>ref</code>”(黑体字)告知模式分配哪个模型给该字段。</p>
+
+<pre class="brush: js notranslate"><code>const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+const authorSchema = Schema({
+ name : String,
+ stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }]
+});
+
+const storySchema = Schema({
+ author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' },
+ title : String
+});
+
+const Story = mongoose.model('Story', storySchema);
+const Author = mongoose.model('Author', authorSchema);</code></pre>
+
+<p>可以通过分配 <code>_id</code> 值来保存对相关文档的引用。下面我们创建一个作者、一条简介,并将新简介的 <code>author</code> 字段设置为新建作者的 id。</p>
+
+<pre class="brush: js notranslate"><code>const wxm = new Author({ name: '司马迁' });
+
+wxm.save(function (err) {
+ if (err) {
+ return handleError(err);
+ }
+
+ // 现在库中有了作者司马迁,我们来新建一条简介
+ const story = new Story({
+ title: "司马迁是历史学家",
+ author: wxm._id // author 设置为作者 司马迁 的 _id。ID 是自动创建的。
+ });
+
+ story.save(function (err) {
+ if (err) {
+ return handleError(err);
+ } // 司马迁有了一条简介
+ });
+});</code></pre>
+
+<p>现在简介文档通过作者文档的 ID 引用了作者。可使用 <code>populate()</code> 在简介中获取作者信息,如下所示。</p>
+
+<pre class="brush: js notranslate"><code>Story
+ .findOne({ title: '司马迁是历史学家' })
+ .populate('author') // 使用作者 id 填充实际作者信息
+ .exec(function (err, story) {
+ if (err) {
+ return handleError(err);
+ }
+ console.log('作者是 %s', story.author.name);
+ // 控制台将打印 "作者是 司马迁"
+ });</code></pre>
+
+<div class="note">
+<p><strong>注:</strong>目光敏锐的读者可能会注意到,新的简介添加了作者,但并没有添加到 <code>stories</code> 数组中。那么怎样才能得到指定作者的所有简介?考虑把作者添加到 <code>stories</code> 数组中,但会导致作者和简介相关信息的要在两处进行维护。</p>
+
+<p>更好的方法是获取作者的 <code>_id</code>,然后使用 <code>find()</code> 在所有简介的作者字段中搜索。</p>
+
+<pre class="brush: js notranslate"><code>Story
+ .find({ author : wxm._id })
+ .exec(function (err, stories) {
+ if (err) {
+ return handleError(err);
+ } // 返回所有 author 字段的值为 司马迁id 的简介
+ });</code>
+</pre>
+</div>
+
+<p>以上是本教程中“项目间协同”需要了解的所有内容。更多详细信息请参阅 <a href="http://mongoosejs.com/docs/populate.html">Population</a>(Mongoose 英文文档)。</p>
+
+<h3 id="一模式(模型)一文件">一模式(模型)一文件</h3>
+
+<p>虽然创建模式和模型没有文件结构的限制,但强烈建议将单一模式定义在单一模块(文件)中,并通过导出方法来创建模型。如下所示:</p>
+
+<pre class="brush: js notranslate"><code>// 文件:./models/somemodel.js
+
+// Require Mongoose
+const mongoose = require('mongoose');
+
+// 定义一个模式
+const Schema = mongoose.Schema;
+
+const SomeModelSchema = new Schema({
+ a_string : String,
+ a_date : Date
+});
+
+<strong>// 导出函数来创建 "SomeModel" 模型类
+module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre>
+
+<p>然后就可以在其它文件中,<code>require</code> 并使用该模型。下面是通过 <code>SomeModel</code> 模块来获取所有实例的方法。</p>
+
+<pre class="brush: js notranslate"><code>// 通过 require 模块来创建 SomeModel 模型
+const SomeModel = require('../models/somemodel')
+
+// 使用 SomeModel 对象(模型)来查找所有的 SomeModel 记录
+SomeModel.find(callback_function);</code></pre>
+
+<h2 id="架设_MongoDB_数据库">架设 MongoDB 数据库</h2>
+
+<p>我们已经初步了解了 Mongoose 以及设计模型的方法,现在该开始搭建 LocalLibrary 网站了。第一件事就是设置 MongoDB 数据库,来存储图书馆的数据。</p>
+
+<p>本教程将使用 <a href="https://mlab.com/welcome/">mLab</a> 免费版“<a href="https://mlab.com/plans/pricing/">沙盒</a>”云数据库。这一版不适用于生产环境,因为它没有冗余设计,但非常适合进行开发和原型设计。选用它是因为它免费且易于设置,并且 mLab 是一家流行的数据库服务供应商,也是生产环境数据库的理想选择(撰写本文时(2019年1月),国内流行的云数据库解决方案有 <a href="https://www.aliyun.com/product/mongodb?spm=5176.10695662.778269.1.2e5b8cb3Hw9HUr">阿里云</a>、<a href="https://cloud.tencent.com/product/mongodb">腾讯云</a>、<a href="https://cloud.baidu.com/product/mongodb.html">百度云</a> 等)。</p>
+
+<div class="note">
+<p><strong>注:</strong>也可以下载并安装 <a href="https://www.mongodb.com/download-center">对应系统的安装包</a>,设置本地版 MongoDB 数据库。多数指令和使用云数据库是一样的,除了连接时数据库的 URL。</p>
+</div>
+
+<div class="note">
+<p><strong>译注:</strong>目前 mLab 网站在国内速度很慢,若遇到无法正常注册或登录的情况可以考虑本地版 MongoDB。</p>
+</div>
+
+<p>首先 <a href="https://mlab.com/signup/">用 mLab 创建一个账户</a>(这是免费的,只需要输入基本联系信息,并同意服务条款)。</p>
+
+<p>登录后将进入 <a href="https://mlab.com/home">mLab 主屏幕</a>:</p>
+
+<ol>
+ <li>单击 <em>MongoDB Deployments</em>(MongoDB 部署)部分中的 <strong>Create New(新建)</strong>。<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li>
+ <li>将打开 Cloud Provider(云服务提供商)选择屏幕。<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;">
+ <ul>
+ <li>在 Plan Type(方案类型)部分中,选择 SANDBOX(Free)免费沙箱方案。</li>
+ <li>从 <em>Cloud Provider</em>(云服务提供商)部分选择任意提供商。不同地区适用不同提供商(显示在选定的计划类型下面)。</li>
+ <li>点击 <strong>Continue(继续)</strong>按钮。</li>
+ </ul>
+ </li>
+ <li>此时将打开 <em>Select Region</em>(选择区域)屏幕。
+ <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>选择离你最近的地区,然后 <strong>Continue</strong>。</p>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <p>将打开 Final Details(最后的细节)屏幕。<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>输入新数据库的名称 <code>local_library</code>,然后 <strong>Continue</strong>。</p>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <p>将打开 <em>Order Confirmation</em>(订单确认)屏幕。<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>点击 <strong>Submit Order(提交订单)</strong>以创建数据库。</p>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <p>将返回到主屏幕。点击刚创建的新数据库可以打开详细信息屏幕。当前数据库还没有任何数据。</p>
+
+ <p><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>
+ <br>
+ 表单显示了访问数据库的 URL(上图的红框)。此时可以创建一个用户,并在 URL 中指定用户名,就可以访问这个 URL 了。</p>
+ </li>
+ <li>点击 <strong>Users </strong>选项卡,点击 <strong>Add database user </strong>按钮。</li>
+ <li>输入用户名和密码(两次),然后按 <strong>Create</strong>。不要选择 Make <em>read-only</em>。<br>
+ <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li>
+</ol>
+
+<p>现在数据库已经创建好了,并且有一个可访问的 URL(带有用户名和密码):<code>mongodb://&lt;dbuser&gt;:&lt;dbpassword&gt;@ds019038.mlab.com:19038/local_library</code></p>
+
+<h2 id="安装_Mongoose">安装 Mongoose</h2>
+
+<p>打开终端,并转到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">LocalLibrary 站点骨架</a> 的目录。通过以下命令安装 Mongoose(及其依赖项),并将其添加至 <strong>package.json </strong>文件,若你在阅读 <strong>Mongoose 入门</strong> 时已经完成这一操作,请忽略本段。</p>
+
+<pre class="brush: bash notranslate">$ npm install mongoose
+</pre>
+
+<h2 id="连接到_MongoDB_2">连接到 MongoDB</h2>
+
+<p>打开 <strong>/app.js</strong>(位于项目根目录),并将以下代码复制到声明 Express 应用对象的位置(<code>var app = express();</code> 之后)。将数据库 URL 字符串('在此插入数据库_URL')替换为真实的 URL(<a href="#架设_MongoDB_数据库">设置自 mLab</a>)。</p>
+
+<pre class="brush: js notranslate">// 设置 Mongoose 连接
+const mongoose = require('mongoose');
+const mongoDB = '<em>在此插入数据库_URL</em>';
+mongoose.connect(mongoDB, { useNewUrlParser: true , useUnifiedTopology: true});
+mongoose.Promise = global.Promise;
+const db = mongoose.connection;
+db.on('error', console.error.bind(console, 'MongoDB 连接错误:'));</pre>
+
+<p>如上文 <a href="#Mongoose_入门">Mongoose 入门</a> 所讲,以上代码创建了与数据库的默认连接,并绑定了错误事件(错误信息将及时打印到控制台)。</p>
+
+<h2 id="定义_LocalLibrary_模式">定义 LocalLibrary 模式</h2>
+
+<p><a href="#一模型一文件">如上文所述</a>,我们将为每个模型定义单独的模块。首先在项目根目录中创建一个文件夹用来保存模型(<strong>/models</strong>),然后为每个模型创建单独的文件:</p>
+
+<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录
+ <strong>/models</strong>
+ <strong>author.js</strong>
+ <strong>book.js</strong>
+ <strong>bookinstance.js</strong>
+ <strong>genre.js</strong>
+</pre>
+
+<h3 id="作者模型(Author)">作者模型(Author)</h3>
+
+<p>将下方的 <code>Author</code> 模式代码复制粘贴至 <strong>./models/author.js</strong> 文件中。模式中定义了两个 <code>String</code> 模式类型来表示作者的姓氏和名字(这两个字段是必需的,且长度不能超过 100 字符),定义了两个 <code>Date</code> 字段做为作者的生卒日期。</p>
+
+<pre class="brush: js notranslate">const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+
+const 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>// 虚拟属性'name':表示作者全名
+AuthorSchema
+ .virtual('name')
+ .get(function () {
+ return this.family_name + ', ' + this.first_name;
+ });</strong>
+
+// 虚拟属性'lifespan':作者寿命
+AuthorSchema
+<strong> </strong>.virtual('lifespan')
+ .get(function () {
+ return (this.date_of_death.getYear() - this.date_of_birth.getYear()).toString();
+ });
+
+// 虚拟属性'url':作者 URL
+AuthorSchema
+ .virtual('url')
+ .get(function () {
+ return '/catalog/author/' + this._id;
+ });
+
+// 导出 Author 模型
+module.exports = mongoose.model('Author', AuthorSchema);
+</pre>
+
+<p>我们还为 <code>AuthorSchema</code> 声明了一个 "<code>url</code>" 虚拟属性,以返回模型特定实例的绝对 URL。在模板中需要获取特定作者的链接时可使用该属性。</p>
+
+<div class="note">
+<p><strong>注:</strong>有必要将 URL 声明为虚拟属性,因为这样,项目的 URL 就只需在一处进行更改。<br>
+ 此时,使用此 URL 的链接还不能工作,因为目前还没有设置任何路由,无法处理特定模型实例的代码。这个问题下节再讲。</p>
+</div>
+
+<p>模块的最后对模型进行导出。</p>
+
+<h3 id="藏书模型(Book)">藏书模型(Book)</h3>
+
+<p>将下方的 <code>Book</code> 模式代码复制粘贴至 <strong>./models/book.js</strong> 文件中。大体结构与作者模型相似,有三个字符串字段, 一个用于获取特定藏书记录 URL 的虚拟属性,代码最后对模型进行导出。</p>
+
+<pre class="brush: js notranslate">const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+
+const 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>
+});
+
+// 虚拟属性'url':藏书 URL
+BookSchema
+ .virtual('url')
+ .get(function () {
+ return '/catalog/book/' + this._id;
+ });
+
+// 导出 Book 模块
+module.exports = mongoose.model('Book', BookSchema);
+</pre>
+
+<p>主要区别在于:此处有两个字段是对其他模型的引用(黑体字所示):</p>
+
+<ul>
+ <li><code>author</code> 是对单一 <code>Author</code> 模型对象的引用,并且是必需的。</li>
+ <li><code>genre</code> 是对 <code>Genre</code> 模型(目前尚未声明)对象数组的引用。</li>
+</ul>
+
+<h3 id="藏书副本模型(BookInstance)">藏书副本模型(BookInstance)</h3>
+
+<p>最后将 <code>BookInstance</code> 模式代码复制粘贴至 <strong>./models/bookinstance.js</strong> 文件中。 <code>BookInstance</code> 表示可供借阅的藏书的特定副本,其中包含该副本是否可用、还书期限,“出版批次”或版本详细信息。</p>
+
+<pre class="brush: js notranslate">const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+
+const BookInstanceSchema = new Schema({
+ // 指向相关藏书的引用
+ book: { type: Schema.Types.ObjectId, ref: 'Book', required: true },
+ // 出版项
+ imprint: {type: String, required: true},
+ status: {
+ type: String,
+ required: true,
+ <strong>enum: </strong>['Available', 'Maintenance', 'Loaned', 'Reserved'],
+ <strong>default: </strong>'Maintenance'<strong>
+ </strong>},
+ due_back: {type: Date, <strong>default: Date.now</strong>}
+ }
+);
+
+// 虚拟属性'url':藏书副本 URL
+BookInstanceSchema
+ .virtual('url')
+ .get(function () {
+ return '/catalog/bookinstance/' + this._id;
+ });
+
+// 导出 BookInstancec 模型
+module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre>
+
+<p>以上代码有点儿新东西,即字段选项(黑体字):</p>
+
+<ul>
+ <li><code>enum</code>:可以设置字符串允许的值。本例中可指定书籍的状态。(使用枚举可以避免状态中出现错误拼写或不允许的值)</li>
+ <li><code>default</code>:用默认值可以设定新​建藏书实例的默认状态(为'馆藏维护'),还可以将默认还书期限(<code>due_back</code>)日期设置为今天(<code>now</code>)。(设置日期时请注意 <code>Date</code> 函数的用法!)</li>
+</ul>
+
+<p>其他内容和之前的模式大同小异。</p>
+
+<h3 id="图书种类模型(Genre)——挑战自我!">图书种类模型(Genre)——挑战自我!</h3>
+
+<p>打开 <strong>./models/genre.js </strong>文件,并创建一个模式来存储 <code>Genre</code>(书本的类别,例如它是小说类还是纪实类,是爱情题材还是军事史题材,等)。</p>
+
+<p>与之前模型的定义方式相似:</p>
+
+<ul>
+ <li>该模型应该有一个 <code>String</code> 模式类型,命名为 <code>name</code> ,用来描述图书种类。</li>
+ <li><code>name</code> 字段应该是必需的,并且有 3 到​ ​100 个字符。</li>
+ <li>声明一个 <a href="#虚拟属性">虚拟属性</a>,命名为 <code>url</code>,返回图书类型 URL。</li>
+ <li>导出模型。</li>
+</ul>
+
+<h2 id="测试——添加项目">测试——添加项目</h2>
+
+<p>好了,现在所有模型已准备完毕。</p>
+
+<p>为了测试这些模型(并添加一些示例藏书和项目,以便后续使用),我们来运行一个单独的脚本来为每种类型创建一些项目:</p>
+
+<ol>
+ <li>下载(或新建)文件 <a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>,保存在 express-locallibrary-tutorial 目录(<code>package.json</code> 所在位置) 。
+
+ <div class="note">
+ <p><strong>注:</strong>无需深究 <a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>,它只是为数据库添加一些示例数据。</p>
+
+ <p>译注:针对node.js3.0及以后版本,mlab使用“mongodb+srv://”链接而非“mongodb://”, 请对<a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>源码酌情修改,否则会报错而添加数据失败。</p>
+ </div>
+ </li>
+ <li>在项目根目录运行以下命令,以安装脚本所需的异步模块(后续教程再展开讲)
+ <pre class="brush: bash notranslate">$ npm install async</pre>
+ </li>
+ <li>在命令提示符下用 node 运行此脚本,并以 MongoDB 数据库的 URL 作为参数(同 <code>app.js</code> 中替换 <code>insert_your_database_url_here</code> 占位符的 URL):
+ <pre class="brush: bash notranslate">$ node populatedb &lt;mongodb url&gt;​​​​</pre>
+ </li>
+ <li>该脚本应一路运行至完成,并在终端中记录所创建的项目。</li>
+</ol>
+
+<div class="note">
+<p><strong>提示:</strong>打开 <a href="https://mlab.com/home">mLab</a> 数据库主页面,现在藏书、作者、种类和藏书副本的集合应该都可以打开了,也可以查看单个文档。</p>
+</div>
+
+<h2 id="小结">小结</h2>
+
+<p>本节介绍了数据库和 ORM(Node/Express 环境)的一些知识,以及定义 Mongoose 模式与模型的方法。随后为 LocalLibrary 网站设计并实现了 <code>Book</code>、<code>BookInstance</code>、<code>Author</code>、<code>Genre</code> 模型。</p>
+
+<p>本文最后(使用独立运行的脚本)创建了一些测试实例 。下一节将关注如何创建页面以显示这些对象。</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li><a href="http://expressjs.com.cn/guide/database-integration.html">数据库集成</a> (Express 文档)</li>
+ <li><a href="http://mongoosejs.com/">Mongoose 站点</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose 指南</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/validation.html">验证</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/models.html">模型</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/queries.html">查询</a> (Mongoose 文档)</li>
+ <li><a href="http://mongoosejs.com/docs/populate.html">填充</a> (Mongoose 文档)</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="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/routes/index.html b/files/zh-cn/learn/server-side/express_nodejs/routes/index.html
new file mode 100644
index 0000000000..eca1531fde
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/routes/index.html
@@ -0,0 +1,425 @@
+---
+title: Express 教程 4: 路由和控制器
+slug: learn/Server-side/Express_Nodejs/routes
+tags:
+ - Express
+ - Node
+ - nodejs
+ - 初学者
+ - 服务器端编程
+ - 路由
+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">本节将为 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 站点中所需的资源端点(Endpoint)配置路由。先用空的处理函数搭建起路由处理的模块结构(下节会将它们扩充为真实的处理函数)。并详细介绍了 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/Express_Nodejs/Introduction">Express/Node 入门</a>。 完成本教程之前小节(<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库 (Mongoose)</a> 等)。了解服务器端编程,了解正则表达式。</td>
+ </tr>
+ <tr>
+ <th scope="row">学习目标:</th>
+ <td>理解简单路由的创建方法。设置示例中所有 URL 端点。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">上节</a> 定义了与数据库交互的 Mongoose 模型,使用一个(独立)脚本创建了一些初始记录。现在可以编写代码来向用户展示这些信息。首先要确定页面中应显示哪些信息,然后定义适当的 URL 来返回这些资源。随后应创建路由(URL 处理器)和视图(模板)来显示这些页面。</p>
+
+<p>下图展示了 HTTP 请求/响应处理的主数据流和需要实现的行为。图中除视图(View)和路由(Route)外,还展示了控制器(Controller),它们是实际的请求处理函数,与路由请求代码是分开的。</p>
+
+<p>模型已经创建,现在要创建的主要是:</p>
+
+<ul>
+ <li>路由:把需要支持的请求(以及请求 URL 中包含的任何信息)转发到适当的控制器函数。</li>
+ <li>控制器:从模型中获取请求的数据,创建一个 HTML 页面显示出数据,并将页面返回给用户,以便在浏览器中查看。</li>
+ <li>视图(模板):供控制器用来渲染数据。</li>
+</ul>
+
+<p><img alt="Express HTTP 请求/响应 路径" src="https://mdn.mozillademos.org/files/16453/Express_MVC.png"></p>
+
+<p>因此我们需要页面来显示藏书、藏书种类、作者、藏书副本的列表和详细信息,还需要页面来创建、更新和删除记录。这些内容对于本节来说不算少,因此本节将主要集中在路由和控制器设置。本节编写的这些函数都只有框架,后续章节再扩展控制器方法,以使用模型数据。</p>
+
+<p>第一段提供了 Express 的 <a href="http://expressjs.com/en/4x/api.html#router">Router</a> 中间件的“入门”知识。后续设置 LocalLibrary 路由时将用到这些知识。</p>
+
+<h2 id="路由入门">路由入门</h2>
+
+<p>路由是一段 Express 代码,它将 HTTP 动词(<code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code> 等)、URL 路径/模式和处理函数三者关联起来。</p>
+
+<p>创建路由有几种方法。本教程将使 <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> 中间件,因为使用它可以将站点特定部分的路由处理程序打包,并使用通用路由前缀访问它们。我们会将所有与图书馆有关的路由保存在 <code>catalog</code> 模块中,在添加处理帐户或其他功能的路由时,可以分开保存。</p>
+
+<div class="note">
+<p><strong>注:</strong><a href="https://developer.mozilla.org/zh-CN/docs/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#%E5%88%9B%E5%BB%BA%E8%B7%AF%E7%94%B1%E5%A4%84%E7%90%86%E5%99%A8%EF%BC%88Route_handler%EF%BC%89">Express 简介 &gt; 创建路由处理程序</a> 简要讨论了 Express 应用的路由机制。使用 <code>Router</code> 可以保证更好的模块化(下文所述),且用法与直接在 Express 应用对象定义路由非常类似。</p>
+</div>
+
+<p>本段以下内容介绍使用 <code>Router</code> 定义路由的方法。</p>
+
+<h3 id="定义和使用单独的路由模块">定义和使用单独的路由模块</h3>
+
+<p>以下代码举例说明了如何创建路由模块,以及如何在 Express 应用中使用它。</p>
+
+<p>首先,在 <strong>wiki.js </strong>模块中创建一个维基路由。代码一开始导入 Express 应用对象,用它取得一个 <code>Router</code> 对象,然后用 <code>get()</code> 方法向其添加两个具体的路由。模块的最后导出该 <code>Router</code> 对象。</p>
+
+<pre class="brush: js notranslate">// wiki.js - 维基路由模块
+
+const express = require('express');
+const router = express.Router();
+
+// 主页路由
+router.get('/', (req, res) =&gt; {
+ res.send('维基主页');
+});
+
+// “关于页面”路由
+router.get('/about', (req, res) =&gt; {
+ res.send('关于此维基');
+});
+
+module.exports = router;</pre>
+
+<div class="note">
+<p><strong>注:</strong>上面的路由处理回调直接定义在了路由函数中。LocalLibrary 的回调将定义在单独的控制器模块中。</p>
+</div>
+
+<p>要在主应用中使用该路由模块,首先应 <code>require</code> 它(<strong>wiki.js</strong>),然后对 Express 应用对象调用 <code>use()</code>(指定路径‘/wiki’),即可将其添加到中间件处理路径。</p>
+
+<pre class="brush: js notranslate">const wiki = require('./wiki.js');
+// ...
+app.use('/wiki', wiki);</pre>
+
+<p>这时 <code>wiki</code> 模块中定义的两个路由就可以从 <code>/wiki/</code> 和 <code>/wiki/about/</code> 访问了。</p>
+
+<h3 id="路由函数">路由函数</h3>
+
+<p>上述模块定义了两个典型的路由函数。<code>Router.get()</code> 方法定义的 “about” 路由(下方重现的)仅响应 HTTP GET 请求。第一个参数是 URL 路径,第二个参数是一个回调,在收到带有路径的HTTP GET 请求将调用之。</p>
+
+<pre class="brush: js notranslate">router.get('/about', (req, res) =&gt; {
+ res.send('关于此维基');
+});
+</pre>
+
+<p>该回调有三个参数(通常命名为:<code>req</code>、<code>res</code>、<code>next</code>),分别是:HTTP 请求对象、HTTP 响应、中间件链中的下一个函数。</p>
+
+<div class="note">
+<p><strong>注:</strong>路由函数就是 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express 中间件</a>,这意味着它们必须(通过响应)结束请求,否则必须调用链中的 <code>next</code> 函数。上述示例使用<code>send()</code> 完成了请求,所以没有使用 <code>next</code> 参数(参数表中将其省略)。</p>
+
+<p>上述路由函数只需要一个回调,可以根据需要指定任意数量的回调参数,或一个回调函数数组。每个函数都将加入中间件链,并且将按添加顺序调用(若有回调完成请求则中止当前周期)。</p>
+</div>
+
+<p>此处的回调对响应对象调用 <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code>,当收到带有路径('<code>/about'</code>)的 GET 请求时将返回字符串“关于此维基”。还有许多其它可以结束请求/响应周期 <a href="https://expressjs.com/en/guide/routing.html#response-methods">响应方法</a>,例如,可调用 <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> 来发送 JSON 响应,或调用 <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> 来发送文件。构建 LocalLibrary 最常使用的响应方法是 <code><a href="https://expressjs.com/en/4x/api.html#res.render">render()</a></code>,它使用模板和数据创建并返回 HTML 文件。后续章节进一步讨论。</p>
+
+<h3 id="HTTP_动词">HTTP 动词</h3>
+
+<p>上面的示例使用 <code>Router.get()</code> 方法来响应特定路径的 HTTP GET 请求。</p>
+
+<p><code>Router</code> 还为所有其他 HTTP 动词提供路由方法,大都用法相同:<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>, 和 <code>connect()</code>。</p>
+
+<p>比如下方代码与上方 <code>/about</code> 路由行为一致,但只响应 HTTP POST 请求。</p>
+
+<pre class="brush: js notranslate">router.post('/about', (req, res) =&gt; {
+ res.send('关于此维基');
+});</pre>
+
+<h3 id="路由路径">路由路径</h3>
+
+<p>路由路径用于定义可请求的端点。之前示例中路径都是字符串,并且必须精确写为:'/'、'/ about'、'/ book',等等。</p>
+
+<p>路由路径也可以是字符串模式(String Pattern)。可用部分正则表达式语法来定义端点的模式。以下是所涉及的正则表达式(注意,连字符( <code>-</code>)和点(<code>.</code>)在字符串路径中解释为字面量,不能做为正则表达式):</p>
+
+<ul>
+ <li><code>?</code>:问号之前的一个字符只能出现零次或一次。例如,路由路径 <code>'/ab?cd'</code> 路径匹配端点 <code>acd</code> 或 <code>abcd</code>。</li>
+ <li><code>+</code>:加号之前的一个字符至少出现一次。例如,路径路径 <code>'/ab+cd'</code> 匹配端点 <code>abcd</code>、<code>abbcd</code>、<code>abbbcd</code>,等。</li>
+ <li><code>*</code>:星号可以替换为任意字符串。例如,路由路径 <code>'ab\*cd'</code> 匹配端点 <code>abcd</code>、<code>abXcd</code>、<code>abSOMErandomTEXTcd</code>,等。</li>
+ <li><code>()</code>:将一个字符串视为一体以执行 <code>?</code>、<code>+</code>、<code>*</code> 操作。例如。 <code>'/ab(cd)?e'</code> 将对 <code>(cd)</code> 进行匹配,将匹配到 <code>abe</code> 和 <code>abcde</code>。</li>
+</ul>
+
+<p>路由路径也可以是 JavaScript <a href="/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions">正则表达式</a>。例如,下面的路由路径将匹配 <code>catfish</code> 和 <code>dogfish</code>,但不会匹配 <code>catflap</code>、<code>catfishhead</code> 等。注意,正则表达式路径不再用引号 <code>"..."</code> 括起来,而是正则表达式语法 <code>/.../</code>。</p>
+
+<pre class="brush: js notranslate">app.get(/.*fish$/, (req, res) =&gt; {
+ ...
+});</pre>
+
+<div class="note">
+<p><strong>注:</strong>LocalLibrary 的大部分路由都只使用字符串,很少用字符串模式和正则表达式。接下来将讨论“路由参数”。</p>
+</div>
+
+<h3 id="路由参数">路由参数</h3>
+
+<p>路径参数是命名的 URL 段,用于捕获在 URL 中的位置指定的值。命名段以冒号为前缀,然后是名称(例如 <code>/<strong>:</strong>your_parameter_name/</code>。捕获的值保存在 <code>req.params</code> 对象中,键即参数名(例如 <code>req.params.your_parameter_name</code>)。</p>
+
+<p>举例说,一个包含用户和藏书信息的 URL:<code>http://localhost:3000/users/34/books/8989</code>,可以这样提取信息(使用 <code>userId</code> 和 <code>bookId</code> 路径参数):</p>
+
+<pre class="brush: js notranslate">app.get('/users/:userId/books/:bookId', (req, res) =&gt; {
+ // 通过 req.params.userId 访问 userId
+ // 通过 req.params.bookId 访问 bookId
+ res.send(req.params);
+});
+</pre>
+
+<p>路由参数名必须由“单词字符”(/<code>[A-Za-z0-9_]</code>/)组成。</p>
+
+<div class="note">
+<p><strong>注:</strong>URL <em><code>/book/create</code> 会匹配 </em><code>/book/:bookId</code> 这样的路由(将提取值为'<code>create</code>' 的 '<code>bookId</code>')。第一个与传入 URL 相匹配的路由会被使用,因此 <code>/book/create</code> 的路由处理器必须定义在<em> </em><code>/book/:bookId</code> 路由之前,才能妥善处理。</p>
+</div>
+
+<p>以上就是使用路由所有的预备知识。Express 文档中还能找到更多信息:<a href="http://expressjs.com/en/starter/basic-routing.html">基础路由</a> 和 <a href="http://expressjs.com/en/guide/routing.html">路由指南</a>。以下是 LocalLibrary 路由和控制器的设置过程。</p>
+
+<h2 id="LocalLibrary_所需路由">LocalLibrary 所需路由</h2>
+
+<p>以下是站点页面的完整 URL 列表。<code>&lt;object&gt;</code> 是模型名称(<code>book</code>、<code>bookinstance</code>、<code>genre</code>、<code>author</code>),<code>&lt;objects&gt;</code> 是一组模型,<code>&lt;id&gt;</code> 是每个 Mongoose 模型实例默认的标识字段(<code>_id</code>)。</p>
+
+<ul>
+ <li><code>catalog/</code>:主页。</li>
+ <li><code>catalog/&lt;objects&gt;/</code>:模型(藏书、藏书副本、藏书种类、作者)的完整列表(例如 /<code>catalog/books/</code>、/<code>catalog/genres/</code> 等)</li>
+ <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em></code><em>:具有</em> <code><em>_id</em></code><em> </em>字段值的特定模型的详细信息页面(例如 <code>/catalog/book/584493c1f4887f06c0e67d37</code>)。</li>
+ <li><code>catalog/&lt;object&gt;/create</code>:添加新模型的表单(例如 <code>/catalog/book/create</code>)。</li>
+ <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em>/update</code>:更新具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/update</code>)。</li>
+ <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em>/delete</code>:删除具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/delete</code>)。</li>
+</ul>
+
+<p>首页和列表页面没有包含任何额外信息。因此它们返回的结果只取决于模型类型和数据库内容,获取信息的查询操作是恒定不变的(类似地,创建对象的代码也没有较大改动)。</p>
+
+<p>与之相反,其他 URL 是用于处理特定文档/模型实例的,它们会将项目的标识嵌入 URL(上文的 <code><em>&lt;id&gt;</em></code>)。可以用路径参数来提取嵌入的信息,并传递给路由处理器(后续章节中用于动态获取数据库中的信息)。通过在 URL 中嵌入信息,使得每种类型的所有资源只需要一个路由(例如,所有藏书副本的显示操作只需要一个路由)。</p>
+
+<div class="note">
+<p><strong>注:</strong>Express 可以通过任何方式构造 URL,可以在 URL 正文中嵌入信息(如上文),或使用 URL <code>GET</code> 参数(例如 <code>/book/?id=6</code>)。无论哪种方法,URL 都应保持整洁、合理且易读(另请参阅 <a href="https://www.w3.org/Provider/Style/URI">W3C 相关建议</a>)。</p>
+</div>
+
+<p>下面我们为上述所有 URL 创建路由处理器回调函数和路由代码。</p>
+
+<h2 id="创建路由处理器回调函数">创建路由处理器回调函数</h2>
+
+<p>定义路由之前应先使用单独的“控制器”模块创建回调的结构骨架。(文件/模块结构没有限制,但以下结构很适合当前项目的规模)。</p>
+
+<p>首先在项目根目录新建一个存放控制器的文件夹(<strong>/controllers</strong>),然后为每个模型创建单独的控制器文件(模块):</p>
+
+<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录
+ <strong>/controllers</strong>
+ <strong>authorController.js</strong>
+ <strong>bookController.js</strong>
+ <strong>bookinstanceController.js</strong>
+ <strong>genreController.js</strong></pre>
+
+<div class="blockIndicator note">
+<p><strong>译注:</strong>上述四个文件可到 GitHub 下载 <a href="https://github.com/roy-tian/mdn-examples/blob/master/server/express-locallibrary-tutorial/controllers/dummyControllers.zip">dummyControllers.zip</a>。(链接已失效,请移步英文版查看具体代码)</p>
+</div>
+
+<h3 id="Author_控制器"><code>Author</code> 控制器</h3>
+
+<p>以下是 <strong>/controllers/authorController.js</strong> 文件的内容:</p>
+
+<pre class="brush: js notranslate">const Author = require('../models/author');
+
+// 显示完整的作者列表
+exports.author_list = (req, res) =&gt; { res.send('未实现:作者列表'); };
+
+// 为每位作者显示详细信息的页面
+exports.author_detail = (req, res) =&gt; { res.send('未实现:作者详细信息:' + req.params.id); };
+
+// 由 GET 显示创建作者的表单
+exports.author_create_get = (req, res) =&gt; { res.send('未实现:作者创建表单的 GET'); };
+
+// 由 POST 处理作者创建操作
+exports.author_create_post = (req, res) =&gt; { res.send('未实现:创建作者的 POST'); };
+
+// 由 GET 显示删除作者的表单
+exports.author_delete_get = (req, res) =&gt; { res.send('未实现:作者删除表单的 GET'); };
+
+// 由 POST 处理作者删除操作
+exports.author_delete_post = (req, res) =&gt; { res.send('未实现:删除作者的 POST'); };
+
+// 由 GET 显示更新作者的表单
+exports.author_update_get = (req, res) =&gt; { res.send('未实现:作者更新表单的 GET'); };
+
+// 由 POST 处理作者更新操作
+exports.author_update_post = (req, res) =&gt; { res.send('未实现:更新作者的 POST'); };
+</pre>
+
+<p>该模块首先导入了用于访问和更新数据的模型,然后为每个需要处理(添加、更新和删除表单,以及相应的 POST 请求,稍后在 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/forms">使用表单</a> 一节中讲解)的 URL 导出一个函数。</p>
+
+<p>所有函数都遵循 Express 中间件函数的标准形式,三个参数依次为:请求 <code>req</code>、响应 <code>res</code>、当前方法无法完成请求循环时(这里不存在这种情况)调用的 <code>next</code> 函数。上述方法只返回一个字符串,显示相关页面尚未创建。接收路径参数的控制器函数可将参数输出到消息字符串中(代码中出现的 <code>req.params.id</code> )。</p>
+
+<h3 id="BookInstance、Genre、Book_控制器"><code>BookInstance</code>、<code>Genre</code>、<code>Book</code> 控制器</h3>
+
+<p>这三个控制器与 <code>Author</code> 的模式完全相同,只是 <code>Book</code> 有一个用于显示站点欢迎页面的 <code>index()</code> 函数:</p>
+
+<pre class="brush: js notranslate">// /controllers/bookController.js
+
+const Book = require('../models/book');
+
+<strong>exports.index = (req, res) =&gt; { res.send('未实现:站点首页'); };
+</strong>
+...
+</pre>
+
+<h2 id="创建藏书编目_catalog_路由模组">创建藏书编目 <code>catalog</code> 路由模组</h2>
+
+<p>定义好控制器后,我们来为 LocalLibrary 网站创建完整的 URL 路由。</p>
+
+<p>站点骨架中有一个 <strong>./routes</strong> 文件夹,其中包含两个路由文件:index 和 user,在这里新建一个 <strong>catalog.js</strong> 路由文件,如下所示:</p>
+
+<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录
+ /routes
+ index.js
+ users.js
+ <strong>catalog.js</strong></pre>
+
+<div class="blockIndicator note">
+<p><strong>译注:</strong>可到 GitHub 下载完整的 <a href="https://raw.githubusercontent.com/roy-tian/mdn-examples/master/server/express-locallibrary-tutorial/routes/catalog.js">catalog.js</a>。</p>
+</div>
+
+<p><strong>/routes/</strong><strong>catalog.js</strong> 中有以下代码:</p>
+
+<pre class="brush: js notranslate">const express = require('express');
+const router = express.Router();<strong>
+</strong>
+// 导入控制器模块
+const book_controller = require('../controllers/bookController');
+const author_controller = require('../controllers/authorController');
+const genre_controller = require('../controllers/genreController');
+const book_instance_controller = require('../controllers/bookinstanceController');
+
+/// 藏书路由 ///
+
+// GET 获取藏书编目主页
+router.get('/', book_controller.index);
+
+// GET 请求添加新的藏书。注意此项必须位于显示藏书的路由(使用了 id)之前。
+router.get('/book/create', book_controller.book_create_get);
+
+// POST 请求添加新的藏书
+router.post('/book/create', book_controller.book_create_post);
+
+// GET 请求删除藏书
+router.get('/book/:id/delete', book_controller.book_delete_get);
+
+// POST 请求删除藏书
+router.post('/book/:id/delete', book_controller.book_delete_post);
+
+// GET 请求更新藏书
+router.get('/book/:id/update', book_controller.book_update_get);
+
+// POST 请求更新藏书
+router.post('/book/:id/update', book_controller.book_update_post);
+
+// GET 请求藏书
+router.get('/book/:id', book_controller.book_detail);
+
+// GET 请求完整藏书列表
+router.get('/books', book_controller.book_list);
+
+/// 藏书副本、藏书种类、作者的路由与藏书路由结构基本一致,只是无需获取主页 ///
+
+module.exports = router;
+</pre>
+
+<p>该模块导入了 <code>express</code> 并创建了一个 <code>Router</code> 对象 <code>router</code>。所有路由都设置在 <code>router</code> 上,最后将其导出。</p>
+
+<p>对 <code>router</code> 对象调用 <code>.get()</code> 或<code>.post()</code> 函数即可定义路由。这里所有路径都使用字符串定义(而不用字符串模式或正则表达式)。某些特定资源(如藏书)的路由使用路径参数从 URL 中获取对象标识。</p>
+
+<p>处理函数均导入自上文的控制器模块。</p>
+
+<h3 id="更新_index_路由模块">更新 index 路由模块</h3>
+
+<p>新路由已经设置完毕,还需要设置一下 <code>index</code> 模块。我们将网站的首页重定向(<code>redirect</code>)至刚创建的地址 '<code>/catalog</code>'。</p>
+
+<p>将 <strong>/routes/index.js</strong> 中的中间件修改一下:</p>
+
+<pre class="brush: js notranslate">// GET 请求主页
+router.get('/', (req, res) =&gt; {
+ res.redirect('/catalog');
+});</pre>
+
+<div class="note">
+<p><strong>注:</strong>这是我们第一次使用 <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> 响应方法。它会使路由重定向到指定的页面,默认发送 HTTP 状态代码“302 Found”。可以根据需要更改返回的状态代码、设置绝对或相对路径。</p>
+</div>
+
+<h3 id="更新_app.js">更新 app.js</h3>
+
+<p>最后一步是在 <code>app.js</code> 中将路由添加到中间件链。</p>
+
+<p>打开 <strong>app.js</strong>,在 <code>index</code> 和 <code>users</code> 路由下方添加 <code>catalog</code> 路由:</p>
+
+<pre class="brush: js notranslate">const indexRouter = require('./routes/index');
+const usersRouter = require('./routes/users');
+<strong>const catalogRouter = require('./routes/catalog'); // 导入 catalog 路由</strong></pre>
+
+<p>然后在已定义的路由下方将目录路由添加进中间件栈:</p>
+
+<pre class="brush: js notranslate">app.use('/', indexRouter);
+app.use('/users', usersRouter);
+<strong>app.use('/catalog', catalogRouter); // 将 catalog 路由添加进中间件链</strong></pre>
+
+<div class="note">
+<p><strong>注:</strong>我们将图书编目模块添加到了 <code>'/catalog'</code> 路径,该路径是 catalog 模块中所有路径的前缀。例如,访问藏书列表 的URL 为:<code>/catalog/books/</code>。</p>
+</div>
+
+<p>大功告成。LocalLibrary 网站所需的所有 URL 的路由和框架函数都已准备完毕。</p>
+
+<h3 id="测试路由">测试路由</h3>
+
+<p>要测试路由,先要启动网站</p>
+
+<ul>
+ <li>默认方法
+ <pre class="brush: bash notranslate"><code>$ DEBUG=express-locallibrary-tutorial:* npm start</code>
+</pre>
+ </li>
+ <li>如果设置过 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>,则可以使用:
+ <pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong>
+</pre>
+ </li>
+</ul>
+
+<div class="blockIndicator note">
+<p><strong>译注:</strong>以上命令只对 bash 有效,要在 Windows 上使用 bash,最简单的方法就是安装 Git。(详情参见 <a href="zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#使用应用生成器">教程 2 相关译注</a>)</p>
+</div>
+
+<p>接下来浏览上面一些 URL,确保不会收到错误页面(HTTP 404)。可以尝试以下示例:</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="小结">小结</h2>
+
+<p>网站的路由已创建完毕,接下来的教程将把完整的实现填入控制器框架中。藉此来学习 Express 路由的基础知识,以及组织路由和控制器的一些方式。</p>
+
+<p>下一节将使用视图 (模板) 和模型里的信息创建一个欢迎页面。</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li><a href="http://www.expressjs.com.cn/starter/basic-routing.html">路由基础</a> (Express 官方文档)</li>
+ <li><a href="http://expressjs.com/en/guide/routing.html">路由简介</a> (Express 官方文档)</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html b/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html
new file mode 100644
index 0000000000..077b7a3862
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html
@@ -0,0 +1,476 @@
+---
+title: Express 教程 2:创建站点框架
+slug: learn/Server-side/Express_Nodejs/skeleton_website
+tags:
+ - Express
+ - Node
+ - node.js
+ - npm
+ - 入门
+ - 初学者
+ - 学习
+ - 开发环境
+ - 服务器端
+ - 服务器端编程
+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">本节将演示如何创建一个可添加路由、模板/视图、和数据库调用的“骨架”站点。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识:</th>
+ <td><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">配置 Node 开发环境</a>。复习 Express 教程。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>掌握用 <strong>Express 应用生成器</strong> 创建站点的方法。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="概览">概览</h2>
+
+<p>本节演示了如何使用 <a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用生成器</a> 创建一个可添加路由、模板/视图和数据库调用的“骨架”网站。这里我们将使用该生成器为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆网站</a> 创建框架,以便在以后章节添加其它代码。过程非常简单,只需要在命令行运行 “生成器 + 项目名称” 即可,此外还可以指定站点的模板引擎和 CSS 生成器。</p>
+
+<p>以下内容介绍了应用生成器的用法,以及视图 / CSS 的一些不同选项。还介绍了骨架站点的组织结构。最后,我们将介绍站点的运行方法,从而对其进行验证。</p>
+
+<div class="note">
+<p><strong>注:</strong>Express 应用生成器并非唯一的 Express 应用生成工具,而且生成项目的结构也不是组织文件和目录的唯一可行方式。但生成项目具有易于扩展和理解的模块化结构。最简单的 Express 应用请参阅 <a href="http://expressjs.com.cn/starter/hello-world.html">Hello world 示例</a>(Express 镜像站)。</p>
+</div>
+
+<h2 id="使用应用生成器">使用应用生成器</h2>
+
+<div class="blockIndicator note">
+<p><strong>译注:</strong>本教程中命令操作基于 Linux/macOS 的 bash 终端,Windows 的命令提示符 cmd/PowerShell 与 bash 的概念和用法略有不同, 为在 Windows 上获得一致的体验,可以:</p>
+
+<ul>
+ <li>自己弄懂 cmd/PowerShell 与 bash 的区别。</li>
+ <li>使用 <a href="https://git-scm.com/">Git</a> 或 <a href="http://www.msys2.org/">MSYS2</a> 为 Windows 提供的 bash。(推荐)</li>
+ <li>使用 Windows 的 Linux 子系统。(到 Microsoft Store 中搜索“Linux”,安装喜欢的版本(Ubuntu 18.04、openSUSE 42、Debian 等),仅限 Windows 10,使用前需要先安装 <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">WSL</a>)</li>
+</ul>
+</div>
+
+<div class="blockIndicator note">
+<p><strong>译注:</strong>你可能已经发现国内用 NPM 太慢了!这是由众所周知的不可抗力造成的。可用淘宝提供的 <a href="https://npm.taobao.org/">CNPM</a> 代替之,功能和用法基本一致(只是不能上传自己的包)。</p>
+</div>
+
+<p>你应该已经安装好了生成器,它是 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node 开发环境</a> 的一部分。可以使用 NPM 来安装全局的生成器,如下所示:</p>
+
+<pre class="brush: bash notranslate">$ sudo npm install express-generator -g</pre>
+
+<p>生成器有许多选项,可以使用 <code>--help</code>(或 <code>-h</code>)命令进行查看:</p>
+
+<p><img alt="express 生成器的帮助信息" src="https://mdn.mozillademos.org/files/16407/express-help.png"></p>
+
+<p>大意如下:</p>
+
+<pre class="brush: bash notranslate">$ express --help
+
+ 用法:express [选项] [目录]
+
+ 选项:
+
+ --version 打印版本号
+ -e, --ejs 添加 ejs 引擎支持
+ --pug 添加 pug 引擎支持
+ --hbs 添加 handlebars 引擎支持
+ -H, --hogan 添加 hogan.js 引擎支持
+ -v, --view &lt;engine&gt; 添加 &lt;engine&gt; 试图引擎支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认为 jade)
+ -c, --css &lt;engine&gt; 添加 &lt;engine&gt; 样式表引擎支持 (less|stylus|compass|sass) (默认为纯 css)
+ --git 添加 .gitignore
+ -f, --force 对非空文件夹强制执行
+ -h, --help 打印帮助信息
+</pre>
+
+<p>可以直接运行 express 命令,将使用 Jade 视图引擎和纯 CSS 在当前目录中创建项目。(如果指定目录名,则在子目录中创建项目)。</p>
+
+<pre class="brush: bash notranslate">$ express</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 应用生成器支持多款流行的视图/模板引擎,包括 <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="http://expressjs.com.cn/guide/using-template-engines.html">在 Express 中使用模板引擎</a>(Express 文档)和所选视图引擎的文档。</p>
+</div>
+
+<p>一般来说,你应该选择一个大而全的模板引擎,可以尽快进入生产状态。就像你选择其他组件一样!选用模板引擎需要考虑以下因素:</p>
+
+<ul>
+ <li>进入生产状态的时间——如果你的团队已经有某个模板语言的经验,那么用它可能更快进入生产状态。否则你应该考虑所选模板引擎的学习曲线。</li>
+ <li>流行度和活跃度——要评估所选引擎的流行程度,以及它是否拥有活跃的社区。在网站的生命周期中遇到问题时,是否能够获得相关支持非常重要。</li>
+ <li>风格——某些模板引擎使用特定标记,来标识插入“普通” HTML 中的内容,而另一些模板引擎使用不同的语法(例如使用缩进和块名称)构造 HTML。</li>
+ <li>性能/渲染时间。</li>
+ <li>功能——你应该考虑所选引擎是否具有以下功能:
+ <ul>
+ <li>布局继承:可以定义基本模板,然后 “继承” 它的一部分,使不同页面可以有不同的呈现。这通常比包含大量所需组件,或每次从头开始构建模板更好。</li>
+ <li>“包含”支持:可以通过包含其他模板来构建新模板。</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 模板语言之一,且对 Express 生成器 <a href="https://github.com/expressjs/express/wiki#template-engines">开箱即用</a>。</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 本身不提供数据库管理机制)。</p>
+
+<p>我们后续讨论数据库集成问题。</p>
+
+<h2 id="创建项目">创建项目</h2>
+
+<p>我们为本地图书馆应用创建一个名为 express-locallibrary-tutorial 的项目,使用 Pug 模板库,不使用 CSS 引擎。</p>
+
+<p>首先,进入准备放置项目的目录,然后在命令提示符运行 Express 应用生成器,生成器将创建(并列出)项目的文件:</p>
+
+<p><img alt="用 express 生成器生成一个应用" src="https://mdn.mozillademos.org/files/16408/express.png"></p>
+
+<p>生成器在最后还告诉你如何安装(<strong>package.json</strong> 中所列的)依赖,以及如何运行该应用。</p>
+
+<h2 id="运行骨架网站">运行骨架网站</h2>
+
+<p>现在我们已经拥有一个完整的项目骨架。虽然这个网站现在还做不了什么,但运行一下,展示一下工作原理也是值得的。</p>
+
+<ol>
+ <li>首先,安装依赖项(<code>install</code> 命令将获取项目的 <strong>package.json</strong> 文件中列出的所有依赖项包)。
+
+ <pre class="brush: bash notranslate">$ npm install</pre>
+ </li>
+ <li>然后运行该应用。
+ <pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm start</pre>
+ </li>
+ <li>最后在浏览器中导航至 <a href="http://localhost:3000/">http://localhost:3000/</a> ,就可以访问该应用。你应该可以看到: <img alt="Express 应用生成器生成的应用启动成功" src="https://mdn.mozillademos.org/files/16410/success.png"></li>
+</ol>
+
+<p>一个 Express 应用就配置成功了,它托管于 localhost:3000。</p>
+
+<div class="note"><strong>注:</strong>指定 DEBUG 变量可启用控制台日志记录/调试。例如,当你访问上面的页面时,你会看到像这样的调试输出: <img alt="用 npm start 启动这个应用" src="https://mdn.mozillademos.org/files/16409/npm-start.png"> 直接通过 <code>npm start</code> 命令启动应用也可以,但不会看到调试信息。</div>
+
+<h2 id="文件改动时重启服务器">文件改动时重启服务器</h2>
+
+<p>只有重启服务器才能看到 Express 网站所做的改动。每次改动后手动启停服务器实在太烦人了,有必要花点时间让这项工作自动化。</p>
+
+<p><a href="https://github.com/remy/nodemon">nodemon</a> 是最简便的自动化工具之一。通常将其全局安装(因为它是一个“工具”):</p>
+
+<pre class="brush: bash notranslate">$ sudo npm install -g nodemon</pre>
+
+<p>这里还可以把它作为开发依赖将安装在本地,于是使用这个项目的开发人员只要安装这个应用就能自动获得。通过以下命令将其安装在骨架项目的根目录:</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.18.9"
+ }
+</pre>
+
+<p>如果没有全局安装该工具,就无法从命令行启动它(除非我们将其添加到路径中),但是可以在 NPM 脚本中调用它,因为 NPM 掌握所有已安装包的信息。找到 package.json 的 <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>现在可以用新建的 <code>devstart</code> 命令启动服务器:</p>
+
+<pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong></pre>
+
+<p>现在,如果编辑项目中的任何文件,服务器将自动重启(或者可以随时使用 rs 命令来重启)。查看更新后的页面需要点击浏览器的“刷新”按钮。</p>
+
+<div class="note">
+<p><strong>注:</strong>这里必须使用“<code>npm <strong>run <em>&lt;scriptname&gt;</em></strong></code>”命令,而不是 <code>npm start</code>,因为 “start” 本质上是映射到脚本的一条 NPM 命令。我们可以在 <code>start</code> 脚本中替换它,但我们只想在开发期间使用 nodemon,因此有必要创建一条新的脚本命令。</p>
+</div>
+
+<h2 id="生成的项目">生成的项目</h2>
+
+<p>我们来看看刚刚创建的项目。</p>
+
+<h3 id="目录结构">目录结构</h3>
+
+<p>安装好依赖项的生成项目具有如下文件结构(<strong>不</strong>带 “/” 前缀的是文件):</p>
+
+<pre class="notranslate">/express-locallibrary-tutorial
+ <strong>app.js</strong>
+ /bin
+ <strong>www</strong>
+ <strong>package.json</strong>
+ /node_modules
+ [约 4,500 个子文件夹和文件]
+ /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><strong>package.json </strong>文件定义依赖项和其他信息,以及一个调用应用入口(<strong>/bin/www</strong>,一个 JavaScript 文件)的启动脚本,脚本中还设置了一些应用的错误处理,加载 <strong>app.js </strong>来完成其余工作。<strong>/routes</strong> 目录中用不同模块保存应用路由。/<strong>views</strong> 目录保存模板。</p>
+
+<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": {
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.9",
+ "express": "~4.16.0",
+ "http-errors": "~1.6.2",
+ "morgan": "~1.9.0",
+ "pug": "2.0.0-beta11"
+ },
+ "devDependencies": {
+ "nodemon": "^1.18.9"
+ }
+}
+</pre>
+
+<p>依赖包括 <em>express</em> 包,和选用的视图引擎包(<em>pug</em>)。还有以下一些实用的包:</p>
+
+<ul>
+ <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>:用于解析 cookie 头来填充 <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/http-errors">http-errors</a>:处理错误中间件。</li>
+ <li><a href="https://www.npmjs.com/package/morgan">morgan</a>:node 专用 HTTP 请求记录器中间件。</li>
+</ul>
+
+<p>"<code>scripts</code>" 部分,定义了一个 "<code>start</code>" 脚本,当运行 <code>npm start</code> 时会调用它来启动服务器。在脚本定义中可以看到 <code>start</code> 实际上运行了 "node <strong>./bin/www"</strong>。还有一个 "<code>devstart</code>" 脚本,可以通过运行 <code>npm run devstart</code> 来运行 "nodemon <strong>./bin/www</strong>"。</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> “真实” 的应用入口(即项目根目录中的 <strong>app.js</strong> ),<strong>app.js </strong>会设置并返回 <code><a href="http://expressjs.com/en/api.html">express()</a></code><a href="http://expressjs.com/en/api.html"> </a>应用对象。</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>文件的其余部分先为 <code>app</code> 设置端口(环境变量中的预定义值或默认值 3000),再创建一个 HTTP 服务器,然后开始监听请求,报告服务器错误和连接信息。其它内容可暂时忽略(这里所有内容都是机器生成的模板),但感兴趣的话可以随时回来看看。</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>require()</code> 的 <code>app</code> 就是这里导出的 。</p>
+
+<p>我们来详细了解一下<strong> app.js</strong> 文件。首先,它使用 <code>require()</code> 导入了一些实用 node 库,其中包括之前用 NPM 下载的 <code>express</code>,<code>http-errors</code>,<code>morgan</code> 和 <em><code>cookie-parser</code>,还有一个 </em><em><code>path</code> </em>库,它是用于解析文件和目录的核心 node 库。</p>
+
+<pre class="brush: js notranslate">var express = require('express');
+var createError = require('http-errors');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var path = require('path');
+</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>下面我们用导入的 <code>express</code> 模块来创建 <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> 中间件将项目 <strong>/public</strong> 目录下所有静态文件托管至根目录。</p>
+
+<pre class="brush: js notranslate">app.use(logger('dev'));
+app.use(express.json());
+app.use(express.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>这些路径(<code>'/'</code> 和 '<code>/users'</code>)将作为导入路由的前缀。如果导入的模块 <code>users<font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4;">在</span></font></code><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
+// 捕获 404 并抛给错误处理器
+app.use(function(req, res, next) {
+ next(createError(404));
+});
+
+// error handler
+// 错误处理器
+app.use(function(err, req, res, next) {
+ // set locals, only providing error in development
+ // 设置 locals,只在开发环境提供错误信息
+ 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 应用对象(<code>app</code>)现已完成配置。最后一步是将其添加到 <code>exports</code> 模块(使它可以通过 <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> 略过不讲)。首先加载 <code>express</code> 模块​​并获取 <code>express.Router</code> 对象(命名为 <code>router</code>)。然后为 <code>router</code> 指定路由,最后导出 <code>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 启动该应用并访问 <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> 中可以看到,该路由使用 '<code>index</code>' 模板和一个模板变量 <code>title</code> 来渲染响应。</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>)。pug 语法稍后再详细讨论。现在只需要知道:<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> 中添加一个新路由,在URL <code>/users/cool/</code> 处显示文本 "你好酷"。运行服务器,并在浏览器中访问 <a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/ </a>,测试一下是否成功。</p>
+
+<ul>
+</ul>
+
+<h2 id="小结">小结</h2>
+
+<p>现在你已经为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆</a> 创建好了骨架,并且成功在 node 上运行起来。同时最重要的是,你了解了项目的结构,从而了解了如何为本地图书馆添加路由和视图。</p>
+
+<p>下一节我们开始修改骨架,让它具备一些图书馆网站的功能。</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li><a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用生成器</a> (Express 中文镜像文档)</li>
+ <li><a href="http://www.expressjs.com.cn/guide/using-template-engines.html">在 Express 中使用模板引擎</a>(Express 镜像文档)</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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html b/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html
new file mode 100644
index 0000000000..86714e0b8a
--- /dev/null
+++ b/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html
@@ -0,0 +1,93 @@
+---
+title: 'Express 教程: 本地图书馆网站'
+slug: learn/Server-side/Express_Nodejs/Tutorial_local_library_website
+tags:
+ - Express
+ - Node
+ - nodejs
+ - web框架
+ - 初学者
+ - 后端框架
+ - 学习教程
+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="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Expres 入门</a>。进行以后的小节还需要阅读 <a href="/zh-CN/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 web 服务器。</li>
+ <li>使用数据库存放应用数据。</li>
+ <li>创建路由来处理不同信息的请求,创建模板("视图")来渲染 HTML 数据以在浏览器中显示。</li>
+ <li>使用表单。</li>
+ <li>部署应用到生产环境。</li>
+</ul>
+
+<p>你可能已经学过(或之前接触过)其中的部分主题。学完列系教程后,你就拥有足够技能独立开发简单的 Express 应用了。</p>
+
+<h2 id="本地图书馆网站(LocalLibrary)">本地图书馆网站(LocalLibrary)</h2>
+
+<p>我们给本地图书馆网站起一个名字——LocalLibrary,这个名字将始终伴随本教程。顾名思义,此网站是为一家小型本地图书馆提供线上图书编目而建,用户可以能够浏览馆藏书目,还能管理自己的帐号。</p>
+
+<p>本示例是精心挑选的,它规模灵活,可以根据我们的需求进行自由调整。还能演示绝大多数 Express 特性。更重要的是,这里提供的指引对所有网站都适用:</p>
+
+<ul>
+ <li>教程前几节中我们将定义一个简单的、只能浏览的图书馆,会员可以在网站找书。通过这几节我们来学习大多数网站都会涉及的一项操作:从数据库读取并呈现内容。</li>
+ <li>随着教程的进展,图书馆的示例子会逐步扩充,以演示更高级的网站特征。比如我们会加入新增书目的功能,以此来演示表单和用户授权的用法。</li>
+</ul>
+
+<p>尽管这个示例具备相当可观的扩展度,但依然有理由把它叫做<strong>本地</strong>图书馆(<strong>Local</strong>Library)。 我们希望呈现给你最少的信息,从而帮助你尽快上手并运行 Express。因此,我们只保留书名、本数、作者以及其它关键信息。我们会省略掉其它可能用到的信息,也不会提供多图书馆架构或“大型图书馆"等特性的支持。</p>
+
+<h2 id="我被难住了,哪里有源代码">我被难住了,哪里有源代码?</h2>
+
+<p>本教程进行过程中,我们将在每个知识点为你提供适当的代码片段,其中一些内容我们希望你能(在一定指引下)自己填充。</p>
+
+<p>别总是复制粘贴这些片段,试着独立完成,长期来看这样做是有好处的,你下次编写类似代码时将更熟练。</p>
+
+<p>如果实在进行不下去,可以参考 <a href="https://github.com/mdn/express-locallibrary-tutorial">Github</a> 上的完整版本。</p>
+
+<div class="note">
+<p><strong>注:</strong>本教程中的代码,已在特定版本(项目的<a href="https://github.com/mdn/express-locallibrary-tutorial/blob/master/package.json"> package.json</a> 所列版本)的 node、Express 及其它模组的环境下通过测试。</p>
+</div>
+
+<h2 id="总结">总结</h2>
+
+<p>现在,你对 LocalLibrary 网站和即将学习的东西又多了解了一点,下面,我们开始创建一个用于存放它的 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">框架</a> 吧!</p>
+
+<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
+ <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html b/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html
new file mode 100644
index 0000000000..c8bb70acd3
--- /dev/null
+++ b/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html
@@ -0,0 +1,317 @@
+---
+title: 客户端服务端交互概述
+slug: learn/Server-side/First_steps/Client-Server_overview
+tags:
+ - 服务器端编程
+translation_of: Learn/Server-side/First_steps/Client-Server_overview
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div>
+
+<p class="summary">既然你已经了解了服务器端编程的目的和潜在的好处,接下来我们将非常细致地去说明当服务器接收到了来自浏览器的“动态请求”时到底发生了什么。因为大多数的服务器端代码通过相似的方式来处理请求并做出响应,这将帮助你理解当编写你自己的大量代码时你需要做什么。</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>到目前为止的讨论中还没有真正的代码,因为我们还没有选择一个web框架来写我们的代码呢!然而这个讨论仍旧十分重要,因为我们描述的行为必须通过你的服务器端代码来实现,不管你选择什么编程语言和web框架。</p>
+
+<h2 id="网络服务器和HTTP(入门)">网络服务器和HTTP(入门)</h2>
+
+<p>网络浏览器通过超文本标记语言传输协议(<a href="/en-US/docs/Web/HTTP">HTTP</a>)与网络服务器(<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server">web servers</a>)。 当你在网页上点击一个链接、提交一个表单、或者进行一次搜索的时候,浏览器发送一个HTTP请求给服务器。</p>
+
+<p>这个请求包含:</p>
+
+<ul>
+ <li>一个用来识别目标服务器和资源(比如一个HTML文档、存储在服务器上的一个特定的数据、或者一个用来运行的工具等)的URL。 </li>
+ <li>一个定义了请求行为的方法(比如,获得一个文档或者上传某些数据)。不同的方法/动作以及与他们相关的行为罗列如下:
+ <ul>
+ <li>GET:获取一份指定(比如一个包含了一个产品或者一系列产品相关信息的HTML文档)。</li>
+ <li>POST:创建一份新的资源(比如给wiki增加一片新的文章、给数据库增加一个新的节点)。</li>
+ <li><code>HEAD</code>:  获取有关指定资源的元数据信息,而不会得到像GET的内容部分。例如,您可以使用HEAD请求来查找上次更新资源的时间,然后仅使用(更“昂贵”)GET请求下载资源(如果已更改)。</li>
+ <li>PUT:更新一份已经存在的资源(或者在该资源不存在的情况下创建一份新的)。</li>
+ <li>DELETE:删除指定的资源。</li>
+ <li><code>TRACE</code>、<code>OPTIONS</code>、<code>CONNECT<font face="Open Sans, arial, sans-serif">、</font>PATCH</code>等动作是为一些不常见任务设计的,因此我们在这里的讲解不会涉及到他们。</li>
+ </ul>
+ </li>
+ <li>额外的信息可以和请求一起被编码(比如HTML表单数据)。信息可以被编码成如下:
+ <ul>
+ <li>URL参数:GET请求通过在URL末尾增加的键值对,来编码包含在发送给服务器的URL中的数据——比如,<code>http://mysite.com<strong>?name=Fred&amp;age=11</strong></code>,你经常会用到问号(?)来将URL剩余的部分和URL参数分隔开来,一个赋值符号(=)将名称和与之相关的值分隔开来,然后一个“&amp;”符号分割不同的键值对。当他们被用户改变然后提交时,URL参数具有与生俱来地“不安全性”。因此,一个URL参数或者GET请求是不会用来在服务器上更新数据的。</li>
+ <li>POST数据:POST请求会增加新的资源,这些数据将会在请求体中编码。</li>
+ <li>客户端cookie:cookies包含与客户相关的会话数据,服务器可以用这些数据来判断用户的登录状态以及用户是否有访问资源的权限。</li>
+ </ul>
+ </li>
+</ul>
+
+<p>网络服务器等待来自客户的请求信息,当请求到达时处理它们,然后发给浏览器HTTP响应消息。回应包含一个HTTP响应状态码(<a href="/en-US/docs/Web/HTTP/Status">HTTP Response status code</a>)来暗示请求是否成功 (比如 "<code>200 OK</code>" 连接成功, "<code>404 Not Found</code>" 资源没有找到, "<code>403 Forbidden</code>" 用户没有被授权查看资源, 等等).  一个成功的响应主体,会包含GET请求所请求的资源.</p>
+
+<p>当一个HTML页面被返时,页面会被网络浏览器呈现出来。作为处理工作的一部分,浏览器会发现指向其他资源的链接(比如,一个HTML页面通常会参考Javascript和CSS页面),并且会发送独立的HTTP请求来下载这些文件。</p>
+
+<p>静态网站和动态网站(在接下来的部分讨论到的)正是使用同一种通信协议/模式</p>
+
+<h3 id="GET请求响应举例">GET请求/响应举例</h3>
+
+<p>你可以通过点击一个链接或者在网站进行一次搜索(比如搜索引擎的首页)做出一次简单的GET请求。比如,当你在MDN上进行一次对“客户端概览”词条的搜索时,HTTP请求就被发送出去了,你将会看到正如下面一样被展示出来的文本信息(展示出来的信息不一定是相同的,因为其中一部分信息还取决于你的浏览器)。</p>
+
+<div class="note">
+<p>HTTP消息的格式是在“网络标准”(<a href="http://www.rfc-editor.org/rfc/rfc7230.txt">RFC7230</a>)中定义的。你不需要知道这个标准的细节,但是现在你至少得知道所有这些是来自哪儿的!</p>
+</div>
+
+<h4 id="请求">请求</h4>
+
+<p>每一行请求都包含着相关信息。第一部分被称为<strong>header</strong>,并且包含着关于这个请求的有用信息,同样地一个<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">HTML head</a>包含着关于HTML文档的有用信息(但是却没有自身的实际内容,内容在主体里面)。</p>
+
+<pre>GET https://developer.mozilla.org/en-
+US/search?q=client+server+overview&amp;topic=apps&amp;topic=html&amp;topic=css&amp;topic=js&amp;topic=api&amp;topic=webdev HTTP/1.1
+Host: developer.mozilla.org
+Connection: keep-alive
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Referer: https://developer.mozilla.org/en-US/
+Accept-Encoding: gzip, deflate, sdch, br
+<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code>
+Accept-Language: en-US,en;q=0.8,es;q=0.6
+Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true
+</pre>
+
+<p>第一行和第二行包含了我们在上面讨论过的大部分信息</p>
+
+<ul>
+ <li>请求类型(GET)。</li>
+ <li>目标资源的URL(<code>/en-US/search</code>)。</li>
+ <li>URL参数(<code>q=client%2Bserver%2Boverview&amp;topic=apps&amp;topic=html&amp;topic=css&amp;topic=js&amp;topic=api&amp;topic=webdev)。</code></li>
+ <li>目标网站(developer.mozilla.org)。</li>
+ <li>第一行的末尾也包含了一个简短的包含了标识协议版本的字符串(<code>HTTP/1.1</code>)。</li>
+</ul>
+
+<p>最后一行包括一些关于客户端cookies的信息——你可以看到在这种情况下cookies包含一个为处理远程会话准备的ID(<code>Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...</code>)。</p>
+
+<p>剩余几行包含着所使用的浏览器以及浏览器所能处理的回应类型等信息。比如,你可以在下面看到这些相关信息:</p>
+
+<ul>
+ <li>我的浏览器上(<code>User-Agent</code>)是火狐(<code>Mozilla/5.0</code>).</li>
+ <li>它可以接收gzip压缩信息(<code>Accept-Encoding: gzip</code>).</li>
+ <li>它可以接收的具体编码类型(<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code>)和语言(<code>Accept-Language: de,en;q=0.7,en-us;q=0.3</code>).</li>
+ <li>The <code>Referer</code> line提示包含资源链接的网络地址(或者说请求的来源是 <code>https://developer.mozilla.org/en-US/</code>).</li>
+</ul>
+
+<p>请求也可以有一个请求体,不过在这个例子中请求的请求体是空的。</p>
+
+<h4 id="回应">回应</h4>
+
+<p>针对这个请求的回应的第一部分内容展示如下。The header包含了如下信息:</p>
+
+<ul>
+ <li>第一行包括了回应状态码200 OK,这告诉我们请求是成功的。</li>
+ <li> 我们可以看到回应是文本<code>/html格式的</code>(<code>Content-Type</code>).</li>
+ <li>我们也可以看到它使用的是UTF-8字符集(<code>Content-Type: text/html; charset=utf-8</code>).</li>
+ <li>The head也告诉我们它有多大(<code>Content-Length: 41823</code>).</li>
+</ul>
+
+<p>在消息的末尾我们可以看到<strong>主体</strong>内容——包含了针对请求返回的真实的HTML。</p>
+
+<pre class="brush: html">HTTP/1.1 200 OK
+Server: Apache
+X-Backend-Server: developer1.webapp.scl3.mozilla.com
+Vary: Accept,Cookie, Accept-Encoding
+Content-Type: text/html; charset=utf-8
+Date: Wed, 07 Sep 2016 00:11:31 GMT
+Keep-Alive: timeout=5, max=999
+Connection: Keep-Alive
+X-Frame-Options: DENY
+Allow: GET
+X-Cache-Info: caching
+Content-Length: 41823
+
+
+
+&lt;!DOCTYPE html&gt;
+&lt;html lang="en-US" dir="ltr" class="redesign no-js"  data-ffo-opensanslight=false data-ffo-opensans=false &gt;
+&lt;head prefix="og: http://ogp.me/ns#"&gt;
+  &lt;meta charset="utf-8"&gt;
+  &lt;meta http-equiv="X-UA-Compatible" content="IE=Edge"&gt;
+  &lt;script&gt;(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);&lt;/script&gt;
+  ...
+</pre>
+
+<p>header的剩余部分还包括一些回应的其他信息(比如回应在什么时候生成的),有关服务器的信息,还有它期望浏览器如何处理这个包(比如, <code>X-Frame-Options: DENY</code> 告诉浏览器不允许这个网页嵌入在其他网站的HTML元素{{htmlelement("iframe")}}上。</p>
+
+<h3 id="POST_请求响应举例">POST 请求/响应举例</h3>
+
+<p>当你提交一个表单,并且希望表单所包含的信息存储到服务器的时候,你就生成了一次HTTP POST请求。</p>
+
+<h4 id="请求_2">请求</h4>
+
+<p>下面的文本展示了当用户在网站上提交新的文件的时候,生成的一个HTTP请求的格式和之前展示的GET请求是非常相似的,只是第一行标识这个请求为POST。</p>
+
+<pre class="brush: html">POST https://developer.mozilla.org/en-US/profiles/hamishwillee/edit HTTP/1.1
+Host: developer.mozilla.org
+Connection: keep-alive
+Content-Length: 432
+Pragma: no-cache
+Cache-Control: no-cache
+Origin: https://developer.mozilla.org
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
+Content-Type: application/x-www-form-urlencoded
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit
+Accept-Encoding: gzip, deflate, br
+Accept-Language: en-US,en;q=0.8,es;q=0.6
+Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true
+
+csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&amp;user-username=hamishwillee&amp;user-fullname=Hamish+Willee&amp;user-title=&amp;user-organization=&amp;user-location=Australia&amp;user-locale=en-US&amp;user-timezone=Australia%2FMelbourne&amp;user-irc_nickname=&amp;user-interests=&amp;user-expertise=&amp;user-twitter_url=&amp;user-stackoverflow_url=&amp;user-linkedin_url=&amp;user-mozillians_url=&amp;user-facebook_url=</pre>
+
+<p>最主要的不同在于URL不再包含任何参数。正如你所见,表单提交的信息被编码后放入消息主体中了。(比如:使用以下命令设置新的用户全名:<code>&amp;user-fullname=Hamish+Willee</code>)</p>
+
+<h4 id="响应">响应</h4>
+
+<p>请求的响应如下。状态码"302 FOUND"告知浏览器,服务端已收到它提交的post请求,它必须再发出第二个HTTP请求来加载<code>Location</code>字段中指定的页面。对于其他方面的信息含义,则与<code>GET</code>请求的响应信息类似。</p>
+
+<pre class="brush: html">HTTP/1.1 302 FOUND
+Server: Apache
+X-Backend-Server: developer3.webapp.scl3.mozilla.com
+Vary: Cookie
+Vary: Accept-Encoding
+Content-Type: text/html; charset=utf-8
+Date: Wed, 07 Sep 2016 00:38:13 GMT
+Location: https://developer.mozilla.org/en-US/profiles/hamishwillee
+Keep-Alive: timeout=5, max=1000
+Connection: Keep-Alive
+X-Frame-Options: DENY
+X-Cache-Info: not cacheable; request wasn't a GET or HEAD
+Content-Length: 0
+</pre>
+
+<div class="note">
+<p><strong>注意</strong>: 上面展示的HTTP请求和响应式通过Fiddler软件来捕获的,你也可以得到相似的信息通过使用网络嗅探器(比如<a href="http://web-sniffer.net/">http://web-sniffer.net/</a>)或者使用浏览器扩展例如 <a href="https://addons.mozilla.org/en-US/firefox/addon/httpfox/">HttpFox</a>。你可以自己尝试一下。使用任何一个上面链接的工具,浏览一个站点并修改主要信息来观察不同的请求和响应。更多的现代浏览器拥有网络监控工具(例如,在Firefox上的 <a href="/en-US/docs/Tools/Network_Monitor">Network Monitor</a> 工具)。</p>
+</div>
+
+<h2 id="静态网站">静态网站</h2>
+
+<p>静态网站是指每当请求一个特定的资源时,会从服务器返回相同的硬编码内容。因此,例如,如果您在 <code>/static/myproduct1.html</code> 有一个关于产品的页面,则该页面将返回给每个用户。如果您添加另一个类似的产品到您的网站,您将需要添加另一个页面(例如 <code>myproduct2.html</code> )等。这可能开始变得非常低效 :当您访问数千个产品页面时会发生什么——你会在每个页面(基本的页面模板,结构等等)上重复很多代码,如果你想改变页面结构的任何东西,比如添加一个新的“相关产品”部分,必须单独更改每个页面。</p>
+
+<div class="note">
+<p><strong>注释</strong>: 当你有少量页面时,向每个用户发送相同的内容时,静态网站是最佳选择, 然而随着页面数量的增加,它们的维护成本也会很高。</p>
+</div>
+
+<p>让我们回顾一下在上一篇文章中看到的静态网站架构图,看看它是如何工作的。</p>
+
+<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png"></p>
+
+<p>当用户想要导航到页面时,浏览器会发送一个指定HTML页面的URL的HTTP的<code>GET</code>请求。<br>
+ 服务器从它的文件系统中检索所请求的文档,并返回包含文档和HTTP响应状态码“<code>200 OK</code>”(表示成功)的HTTP响应。服务器可能会返回一个不同的状态码,例如,"<code>404 Not Found</code>"表明文件不在服务器上,或者"<code>301 Moved Permanently</code>"表明如果文件存在,则被重定向到另一个位置。</p>
+
+<p>静态站点的服务器只需要处理 GET 请求,因为服务器不存储任何可修改的数据。它也不会根据HTTP请求数据(例如 URL 参数或 cookie)更改响应。</p>
+
+<p>了解静态站点如何工作在学习服务器端编程时非常有用,因为动态站点以完全相同的方式处理对静态文件(CSS、JavaScript、静态图像等)的请求。</p>
+
+<h2 id="动态网站">动态网站</h2>
+
+<p>动态站点可以根据特定的请求 URL 和数据生成和返回内容(而不是总是返回同一个URL的硬编码文件)。使用产品网页的示例,服务器将把产品“数据”存储在数据库中,而不是单独的HTML文件。当接收到一个产品的HTTP <code>GET</code> 请求时,服务器将确定产品 ID,从数据库中获取数据,然后通过将数据插入到HTML模板中来构造响应的HTML页面。与静态站点相比,这有很大的优势</p>
+
+<p>通过使用数据库,可以有效地将产品信息存储在易于扩展、可修改和可搜索的方式中。</p>
+
+<p>使用 HTML 模板可以很容易地改变HTML结构,因为这只需要在一个模板中的某一处地方完成,而不需要跨越数千个静态页面。</p>
+
+<h3 id="剖析动态请求">剖析动态请求</h3>
+
+<p>本节将逐步概述“动态” HTTP 请求和响应周期,以更详细的内容构建我们在上一篇文章中所看到的内容。为了“让事情保持真实”,我们将使用一个体育团队经理网站的情景,在这个网站上,教练可以用 HTML 表单选择他们的球队名称和球队规模,并为他们的下一场比赛中获得建议的“最佳阵容”。</p>
+
+<p>下面的图表显示了“球队教练”网站的主要元素,以及当教练访问他们的“最佳团队”列表时,操作序列的编号。使其动态的站点的部分是 <em>Web 应用程序</em>(这就是我们将如何引用处理 HTTP 请求并返回 HTTP 响应的服务器端代码)数据库,该数据库包含关于球员、球队、教练及其关系以及HTML 模板的信息。</p>
+
+<p><img alt="This is a diagram of a simple web server with step numbers for each of step of the client-server interaction." src="https://mdn.mozillademos.org/files/13829/Web%20Application%20with%20HTML%20and%20Steps.png" style="height: 584px; width: 1226px;"></p>
+
+<p>在教练提交球员名单和球员人数后,其操作顺序为:</p>
+
+<ol>
+ <li>Web 浏览器使用资源的基本 URL(<code>/best</code>)来创建一个HTTP <code>GET</code>请求,将球队和球员编号附加到URL后面作为参数(例如 <code>/best?team = my_team_name&amp;show = 11</code>)或作为URL地址的一部分(例如 <code>/best/my_team_name/11/</code>)。使用<code>GET</code>请求是因为请求只是获取数据(而不是修改数据)。</li>
+ <li>Web 服务器检测到请求是“动态的”,并将其转发给 Web 应用程序(Web Application)进行处理( Web 服务器根据其配置中定义的模式匹配规则确定如何处理不同的 URL )。</li>
+ <li>Web 应用程序(Web Application)确定请求的意图是根据 URL(<code>/best/</code>)获得“最佳团队列表”,并从 URL 中找出所需的球队名称和球员人数。然后,Web 应用程序(Web Application)从数据库中获取所需的信息(使用额外的“内部”参数来定义哪些球员是“最好”的,并且可能还从客户端 cookie 获得登录教练的身份)。</li>
+ <li>Web应用程序(Web Application)通过将数据(来自数据库)放入 HTML 模板中的占位符中动态地创建 HTML页面。</li>
+ <li>Web应用程序(Web Application)将生成的HTML(通过Web服务器)和HTTP状态代码200(“成功”)返回到Web浏览器。如果有任何东西阻止HTML被返回,那么Web应用程序将返回另一个状态代码 - 例如“404”来表示球队不存在。</li>
+ <li>然后,Web 浏览器将开始处理返回的 HTML ,发送单独的请求以获取其引用的任何其他 CSS 或 JavaScript 文件(请参阅步骤7)。</li>
+ <li>Web 服务器从文件系统加载静态文件,并直接返回到浏览器(同样,正确的文件处理基于配置规则和URL模式匹配)。</li>
+</ol>
+
+<p>在服务器中,更新数据库中的记录的操作将被类似地与上述过程一样处理,但是更新数据库的这一类的操作,应该指定来自浏览器的HTTP请求为<code>POST</code>请求。</p>
+
+<h3 id="完成其他工作">完成其他工作</h3>
+
+<p>Web 应用程序(Web Application)的工作是接收 HTTP 请求并返回 HTTP 响应。虽然与数据库交互以获取或更新信息是非常常见的功能,但是代码也可能同时做其他事情,甚至不与数据库交互。<br>
+ <br>
+ 一个 Web 应用程序(Web Application)可能执行的额外任务的一个很好的例子就是发送一封电子邮件给用户,以确认他们在网站上的注册。该网站也可能执行日志记录或其他操作。</p>
+
+<h3 id="返回HTML以外的内容">返回HTML以外的内容</h3>
+
+<p>服务器端网站代码并非只能在响应中返回 HTML 代码片段/文件。它可以动态地创建和返回其他类型的文件(text,PDF,CSV 等)甚至是数据(JSON,XML等)。<br>
+ <br>
+ 将数据返回到 Web 浏览器以便它可以动态更新自己的内容(AJAX)的想法实现已经有相当长的一段时间了。最近,“单页面应用程序”已经变得流行起来,整个网站用一个 HTML 文件编写,在需要时动态更新。使用这种风格的应用程序创建的网站将大量的计算成本从服务器推向网络浏览器,并可能导致网站表现出更像本地应用程序(高度响应等)。</p>
+
+<h2 id="web框架简化服务器端的web编程">web框架简化服务器端的web编程</h2>
+
+<p>服务器端web框架使得编写解决我们上面描述的操作的代码变得简单得多。</p>
+
+<p>web 框架可以提供的一个最重要的功能就是,提供简单的机制,以将不同的资源和页面定位到具体的处理函数。这使得保持代码和各个不同形式的资源的联系变得简单。它也非常利于代码的维护,因为你可以直接改变在一个地方用来传输特定功能的URL,而不用改变处理函数。</p>
+
+<p>举个例子,我们来思考一下下面的 Django(python) 代码,这些代码将两个 URL 地址定位到两个页面。第一个地址确保了,一个包含了 <code>/best/</code> URL 的 HTTP 请求,可以被传递到一个在<code>views</code>模块的被命名为<code>index()</code>的函数。一个含有"<code>/best/junior</code>"的请求则会被传递到<code>junior()</code>视图函数。</p>
+
+<pre class="brush: python"># file: best/urls.py
+#
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+ # example: /best/
+ url(r'^$', views.index),
+ # example: /best/junior/
+ url(r'^junior/$', views.junior),
+]</pre>
+
+<div class="note">
+<p><strong>注意</strong>: 在<code>url()</code>函数中的第一个参数可能看起来有点古怪 (比如<code>r'^junior/$</code>)  因为他们使用一个叫做“正则表达式”(RegEx, or RE)的字符匹配机制。在这里,你还不需要知道正则表达式是如何工作的,除了要知道它们是如何允许我们在URL中匹配到字符的 (而不是像上面的硬编码) 并且知道如何在我们的视图函数中将它们用作参数。举个例子,一个真正简单的正则表达式可能会说“匹配一个大写字母,后面跟着4到7个小写字母”"</p>
+</div>
+
+<p>Web 框架还可以轻松地使用查看函数,从数据库获取信息。我们的数据结构是在模型中定义的,模型是定义要存储在底层数据库中的字段的Python类。如果我们有一个名为Team的模型,其中有一个“<em>team_type</em>”字段,那么我们可以使用一个简单的查询语法来取回所有具有特定类型的球队。</p>
+
+<p>下面的例子得到了所有字段team_type(区分大小写)为“junior”的所有球队的列表 - 注意格式:字段名称(team_type),后面跟着双下划线,然后是使用的匹配类型)。还有很多其他的匹配类型,我们可以组合他们。我们也可以控制返回结果的顺序和数量。</p>
+
+<pre class="brush: python">#best/views.py
+
+from django.shortcuts import render
+
+from .models import Team
+
+
+def junior(request):
+    list_teams = Team.objects.filter(team_type__exact="junior")
+    context = {'list': list_teams}
+    return render(request, 'best/index.html', context)
+</pre>
+
+<p><code>junior()</code>函数获得少年组列表后,它调用<code>render()</code>函数,传递原始的HttpRequest,一个HTML模板和一个定义要包含在模板中的信息的“context”对象。 <code>render()</code>函数是一个方便的函数,它使用上下文和HTML模板生成 HTML,并将其返回到 <code>HttpResponse</code> 对象中</p>
+
+<p>显然地 web 框架可以帮助你解决很多问题。我们在下一篇文章里将会大量讨论这些好处和一些流行的web框架。</p>
+
+<h2 id="总结">总结</h2>
+
+<p>到这里你应该对于服务器端代码不得不进行的操作有一个整体上的理解,并且知道一个服务器端web框架是从那些方面让这些变得更简单的。</p>
+
+<p>在接下来的模块里面我们会帮助你选择对于你的第一个网站来说最适合的web框架。</p>
+
+<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p>
diff --git a/files/zh-cn/learn/server-side/first_steps/index.html b/files/zh-cn/learn/server-side/first_steps/index.html
new file mode 100644
index 0000000000..c047603f72
--- /dev/null
+++ b/files/zh-cn/learn/server-side/first_steps/index.html
@@ -0,0 +1,46 @@
+---
+title: 服务端网站编程的第一步
+slug: learn/Server-side/First_steps
+tags:
+ - 初学者
+ - 学习
+ - 服务端编程
+ - 脚本编程
+translation_of: Learn/Server-side/First_steps
+---
+<div>{{LearnSidebar}}</div>
+
+<div>在我们的服务端编程板块中,我们回答了服务端编程的一系列基础问题—“它是什么?”,“它和客户端编程的区别是什么?”,还有“为什么它是有价值的?”。然后我们提供了对几个最受欢迎的服务端Web框架的概览,同时还有对如何选择最适合的框架来创建你第一个网站的指导。最后我们提供了一篇从高层次介绍Web服务器安全的文章。</div>
+
+<div></div>
+
+<h2 id="先决条件">先决条件</h2>
+
+<p>在开始这个模块之前,你不必知道任何关于服务端网站编程的知识,或者任何一种其他编程经验。</p>
+
+<p>你需要先知道“Web如何工作”,我们推荐你首先阅读下面几个话题:</p>
+
+<ul>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是一个Web服务器?</a></li>
+ <li><a href="/en-US/docs/Learn/Common_questions/What_software_do_I_need">建立一个网站需要什么软件?</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/Upload_files_to_a_web_server">如何向Web服务器上传文件?</a></li>
+</ul>
+
+<p>有了这些基础知识,你已经准备好开始这些模块的学习了。</p>
+
+<h2 id="引导">引导</h2>
+
+<dl>
+ <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Introduction">服务端简介</a></dt>
+ <dd>欢迎来到MDN提供的初学者服务端编程课程!在我们第一篇文章中,我们从一个很高的角度来审视服务端编程,回答的问题都是类似于:什么是服务端编程?它与客户端编程的区别在哪?以及为什么它如此有价值?在阅读本文之后,你将通过服务端编程了解更多关于网站开发的知识。</dd>
+ <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Client-Server_overview">客户端编程回顾</a></dt>
+ <dd>现在你应该了解服务端编程的目的以及它可能带来的好处,我们现在要去探究一些细节,当服务端接收到浏览器的“动态请求”时,到底发生了什么。大多数服务端代码都是用相似的方式来处理请求以及应答,这一点将帮助你更好地明白在编写你自己的代码时,你到底需要做些什么。</dd>
+ <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Web_frameworks">服务端web框架</a></dt>
+ <dd>最后一篇文章将会介绍当服务端web程序为了响应web浏览器发出的请求,它需要做些什么。现在我们来看看web框架是如何简化这些任务的,除此之外,本文还将帮助你为你自己的第一个服务端web程序选择一个合适的框架。</dd>
+ <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Website_security">网站安全性</a></dt>
+ <dd>在网站的设计与使用过程中,网站安全在方方面面都需要引起警惕。这篇引导性的文章不会让你成为网站安全方面的专家,但是它能够帮你了解为了强化你的web应用用以抵抗大多数常见的威胁时,你应该做的第一件重要的事是什么。</dd>
+</dl>
+
+<h2 id="评估">评估</h2>
+
+<p>这块“概览性”的内容并不会有任何评估练习,因为我们至今还没向你展示一句代码。我们确切地希望到了这里,你已经对服务端编程提供的各种功能有了一个很好的理解,并且已经为创建你自己的第一个web站点选好了合适的web框架。</p>
diff --git a/files/zh-cn/learn/server-side/first_steps/introduction/index.html b/files/zh-cn/learn/server-side/first_steps/introduction/index.html
new file mode 100644
index 0000000000..7f6a05f4e0
--- /dev/null
+++ b/files/zh-cn/learn/server-side/first_steps/introduction/index.html
@@ -0,0 +1,239 @@
+---
+title: 服务端编程介绍
+slug: learn/Server-side/First_steps/Introduction
+tags:
+ - 介绍
+ - 初学者
+ - 向导
+ - 学习
+ - 服务
+ - 服务器端编程
+ - 服务端编程
+translation_of: Learn/Server-side/First_steps/Introduction
+---
+<div>
+<div></div>
+
+<div>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</div>
+</div>
+
+<p class="summary">欢迎来到MDN为初学者准备的服务器端编程课程!在第一篇文章里面我们将会从一个较高的角度来看待服务器端编程,通过解答下面这些问题来实现这一点,比如:服务器端编程是什么?服务器端编程和客户端编程有何不同?还有,为什么服务器端编程这么有用?当你读完这篇文章后,你会理解通过服务器端编程实现的网站所能提供的额外的功能。</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>大多数的大型网站采用服务器端编程来在需要的时候动态展示不同的信息,这些信息通常会从服务器上的数据库中取出,然后发送给客户端,并通过一些代码(比如HTML和Javascript)展示在客户端。</p>
+
+<p>或许服务器端编程的最大益处在于它允许你对不同的用户个体展示不同的网站信息。动态网站可以高亮基于用户喜好和习惯的与用户相关度更高的内容。通过存储用户的偏好设置和个人信息使得网站更加易于使用——比如通过重复使用信用卡的详细信息来简化后续付款流程。</p>
+
+<p>它允许在页面中与用户进行交互,比如通过邮件或者其他渠道发送通知和更新信息。服务器端的所有的这些能力使得网站可以与用户有更深的联系。</p>
+
+<p>在现代的web开发中,学习服务器端编程是很被推荐的。</p>
+
+<h2 id="服务器端编程是什么?">服务器端编程是什么?</h2>
+
+<p>网络浏览器通过<strong>超文本传输协议</strong> ({{glossary("HTTP")}})来和<a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">网络服务器</a> 进行通信。当你在网页上点击一个链接,或提交一个表单,再或进行一次搜索时,一个<strong>HTTP请求</strong>就从你的浏览器发送到了目标服务器。</p>
+
+<p>这个请求包括一个标识所请求资源的URL,一个定义所需操作的方法(比如获取,删除或者发布资源),还可以包括编码在URL参数中的附加信息。附加信息以键值对(参数和它的值)的形式,通过一个<a href="https://en.wikipedia.org/wiki/Query_string">查询字符串</a>,作为POST数据(由<a href="/zh-CN/docs/Web/HTTP/Methods/POST">HTTP POST方法</a>发送)或存放在与之相关联的{{glossary("Cookie")}}中。</p>
+
+<p>网络服务器等待客户端的请求信息,在它们到达的时候处理它们,并且回复网络浏览器一个<strong>HTTP响应</strong>信息。这个响应包含一个表明该请求是否成功的状态行(比如“HTTP/1.1 200 OK”代表请求成功)。</p>
+
+<p>相应一个请求的成功回应包含被请求的资源(比如一个新的HTML页面,或者图片等),然后这些会被展示在客户端的网络浏览器上。</p>
+
+<h3 id="静态网站">静态网站</h3>
+
+<p>下面这张图展示了一个<em>静态网站</em>的基本架构。(静态网站是指无论何时当一个特定资源被请求的时候都返回相同的被硬编码的内容)当用户想要导航到某个页面时,浏览器会发送一个指定到这个页面的URL的HTTP“GET”请求。服务器从它的文件系统中检索被请求的文件,然后返回一个HTTP回应,该回应包括被请求的文件和一个<a href="/en-US/docs/Web/HTTP/Status#Successful_responses">状态码</a>(通常200代表操作成功)。如果出于某些原因被请求的文件无法检索到,就会返回错误码。(具体可以参照<a href="/zh-CN/docs/Web/HTTP/Status#Client_error_responses">客户端错误回应</a>和<a href="/zh-CN/docs/Web/HTTP/Status#Server_error_responses">服务器错误回应</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>动态网站是指,一些响应内容只有在被需要的时候才会生发的网站。在一个动态网站上,页面通常是通过将数据库的数据植入到HTML模板中的占位符中而产生的(这是一种比使用静态网站有效得多的存储大量内容的方式)。</p>
+
+<p>动态网站可以基于用户提供的个人信息或者偏好设置来返回不同的数据,并且可以展示作为返回一个回应的内容的一部分的其他操作(比如发送通知)。</p>
+
+<p>大多数支持动态网站的代码必须运行在服务器上。编写这些代码就是所谓的“<strong>服务器端编程</strong>”(有些时候也称“后端脚本编写”)。</p>
+
+<p>下面的图表展示了一个动态网站的简单架构。就像之前的图表那样,浏览器发送HTTP请求给服务器,然后服务器处理请求并且返回合适的HTTP响应。</p>
+
+<p>动态网站对于静态资源的请求的处理方式和静态网站是一样的(静态资源是指那些不会改变的文件——最典型的就是: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>(网络应用))。在处理“动态请求”时,服务器会首先解释请求,从数据库中读取被请求的信息,然后将这些被检索的信息组合到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>,并且主要涉及所呈现的网页的外观和行为的改进。这就包括选择和设计UI元素、布局、导航、表单验证等。相反的,服务器端网站编程主要涉及,对于相应的请求,选择所要返回给浏览器的内容。服务器端代码解决这样一些问题,比如验证提交的数据和请求、使用数据库来存储和检索信息及发送给用户正如他们所请求的的正确内容。</p>
+
+<p>客户端代码使用 <a href="/zh-CN/docs/Learn/HTML">HTML</a>,<a href="/zh-CN/docs/Learn/CSS">CSS</a>,和<a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 来编写——这些代码直接在网络浏览器中运行,并且几乎没有访问底层操作系统的路径(包括对文件系统访问的限制)。</p>
+
+<p>web开发者无法控制用户可能会使用哪一种浏览器来浏览网站——浏览器对客户端代码的兼容性支持水平不一致,客户端编程的一部分挑战就是如何优雅地处理浏览器兼容性问题。</p>
+
+<p>服务器端代码可以用任何一种编程语言进行编写——比较受欢迎的服务器端编程语言包括PHP、Python、Ruby和C#。服务器端代码有充分的权限访问服务器的操作系统,并且开发者可以选择他们希望使用的编程语言(和特定版本的语言)。</p>
+
+<p>开发者们通常会使用web框架来编写他们的代码。web框架是一个各种函数、对象、方法和其他代码结构的集合体,web框架被设计用来解决一些普遍问题,从而加速开发,并且简化在一个特定领域中面临的不同类型的任务。</p>
+
+<p>同样的,当客户端和服务器端代码使用框架时,它们的领域是不同的,因此框架也会不同。客户端web框架简化布局和演示任务,然而服务器端web框架提供大量的普通网络服务功能,不然的话你可能需要自己来实现这些功能(比如支持会话、支持用户和身份验证、简单的数据访问、模板库等)。</p>
+
+<div class="note">
+<p><strong>注意事项</strong>:客户端框架通常被用来帮助加速客户端代码的开发,但是你也可以选择手写所有的代码;事实上,如果你只需要一个小型的、简单的网站UI,手写自己的代码可能更快并且更高效。</p>
+
+<p>相反的,你应该从来没有考虑过不使用框架而直接编写web应用程序的服务器端组件——实现一个重要的功能比如HTTP服务器真的很难直接从头开始用Python语言构建,但是一些用Python语言写的web框架,比如Django提供了开箱即用的功能,同时还包含其他很多有用的工具。</p>
+</div>
+
+<div>
+<h2 id="你可以在服务器端做什么?">你可以在服务器端做什么?</h2>
+
+<p>服务器端编程是非常有用的,因为它允许我们高效地分发为个人用户制定的信息,从而创造了更佳的用户体验。</p>
+</div>
+
+<p>一些公司比如亚马逊使用服务器端编程来生成产品的搜索结果、根据客户的偏好和过去的购买习惯来推荐目标产品、简化购物流程等。</p>
+
+<p>银行使用服务器端编程来存储帐号信息,并且仅允许授权的用户查看和进行交易。其他的网络服务,比如Facebook、Twitter、Instagram、和Wikipedia,使用服务器端编程来突出、分享和控制对有趣内容的访问。</p>
+
+<p>服务器端编程的普遍使用和好处被罗列在了下方。你会发现二者有一些是重叠的!</p>
+
+<h3 id="信息的高效存储和传输">信息的高效存储和传输</h3>
+
+<p>想象一下,在亚马逊上提供着多少产品,在脸书上发布了多少帖子?为每一个产品和帖子都创建一个独立的静态页面将是完全不切实际的。</p>
+
+<p>服务器端网络编程则允许我们在数据库中存储信息,并且允许我们动态地创建和返回HTML和其他类型的文件(比如,PDF文件和图片等)。我们也可以简单地传输数据 ({{glossary("JSON")}}, {{glossary("XML")}}等),来让合适的客户端框架呈现(这样就减少了服务器的处理压力和需要被传输的数据总量)。</p>
+
+<p>服务器的工作内容不仅限于从数据库发送信息,可能还会选择性地返回软件工具的结果,或者来自聊天服务的数据。内容甚至可以被定位到接受它的信息的客户端设备的类型。</p>
+
+<p>因为数据被放在数据库中,因此更加容易被分享和更新到其他商业系统(比如,当产品在网上或者实体店卖掉之后,商店可以更新它的存货清单数据库)</p>
+
+<div class="note">
+<p><strong>注意</strong>:你不用很难就可以想到服务器端代码对于高效存储和传输信息的好处:</p>
+
+<ol>
+ <li>打开<a href="https://www.amazon.com">亚马逊</a>或者其他一些电子商务网站。</li>
+ <li>搜索一系列关键词,然后注意到页面结构并没有发生改变,尽管搜索结果发生了改变。</li>
+ <li>打开两到三个不同的产品。注意到它们是如何拥有一个相似的结构和布局的,但是不同产品的内容是从不同数据库中获取的。</li>
+</ol>
+
+<p>对于一个普通的搜索词条(比如“鱼”),你会看到数百万的返回值。使用数据库允许这些数据被高效地存储和分享,并且使得信息的展示就被控制在那一个特定的地方。</p>
+</div>
+
+<h3 id="定制用户体验">定制用户体验</h3>
+
+<p>服务器可以存储和使用客户的相关信息来提供一个定制化的用户体验。比如,很多网站存储信用卡信息来使得用户不必再次输入细节信息。有些网站,比如,谷歌地图使用家庭或者当前位置来提供路径信息,然后在搜索结果中突出本地商业。</p>
+
+<p>对用户习惯的更深层分析可以被用来预测用户的兴趣和更加深度地定制化回应和通知,比如,提供一张清单来展示曾经去过的地方,或者在地图上标识你可能想去的非常受欢迎的地点。</p>
+
+<div class="note">
+<p><strong>注意</strong>:谷歌地图会保存你的搜索,浏览的历史记录。频繁地浏览或者频繁地搜索地址将会使得它更加的醒目。</p>
+
+<p>谷歌搜索结果基于之前的搜索进行优化。</p>
+
+<p>1.访问谷歌搜索</p>
+
+<p>2.搜索 “足球”</p>
+
+<p>3.现在在搜索框中输入 “喜欢” ,你就会观察到搜索会自动补全</p>
+
+<p>真的是巧合嘛?这算不上什么!</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>注意</strong>:想想其他真实的限制了内容访问例子。比如,如果你直接访问你银行的网页,你可以看到什么?用你的帐号登录之后——你可以看到和修改什么额外的信息呢?有些什么信息是你只可以看到的而只有银行可以修改的?</p>
+</div>
+
+<h3 id="存储会话和状态信息">存储会话和状态信息</h3>
+
+<p>服务器端编程允许开发者们充分利用<strong>会话</strong>——简单来说就是一种机制,这种机制允许服务器存储一个网站现有用户信息,并且基于那些信息发送不同响应。</p>
+
+<p>这也就允许,比如说,一个网站知道一个用户曾经登录过并且展示他们邮箱的链接或者订单历史,或者可能存储一个简单游戏的状态来确保用户可以再次访问网站然后从上次留下来的地方继续。</p>
+
+<div class="note">
+<p><strong>注意</strong>:访问一个具有订阅模式的新闻网站,并且打开一系列标签(比如<a href="http://www.theage.com.au/">The Age</a>)。几个小时或者几天之后再来访问这个网站。最后你将开始被重定向到一个向你解释如何订阅的页面上,并且你将无法访问文章。这个信息就是一个session信息被存储在cookie中的例子</p>
+</div>
+
+<h3 id="通知和通讯">通知和通讯</h3>
+
+<p>服务器可以发送面向全体的或者面向指定用户的通知,通过网站自身或者通过邮箱、SMS、即时消息、视频会话或者其他的通讯服务。</p>
+
+<p>几个例子:</p>
+
+<ul>
+ <li>
+ <p>Facebook和Twitter发送邮件或者SMS消息来通知你一些新的交谈。</p>
+ </li>
+ <li>
+ <p>亚马逊定期的向你发送产品邮件并且向你推荐和你曾经买过的产品很相似的产品或者是他们觉得你可能感兴趣的产品。</p>
+ </li>
+ <li>一个网站的服务器可能向网站管理员发送警告消息来警告他们服务器内存不足或者可疑的用户行为。</li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>:最普通的一种通知类型就是“注册认证”。选择任何一个你感兴趣的大型网站(谷歌、亚马逊、Instagram等)并且用你的邮箱创建一个新的帐号。你很快会收到一封验证你的注册的邮件,或者需要你去激活帐号。</p>
+</div>
+
+<h3 id="数据分析">数据分析</h3>
+
+<p>一个网站可以收集到有关用户的大量的信息:他们搜索什么?他们买什么?他们分享什么?他们在每一个页面停留多久?服务器端编程可以被用来基于这些数据的分析而细化回应。</p>
+
+<p>比如,亚马逊和谷歌都基于过去的搜索(和购物)信息来为产品打广告。</p>
+
+<div class="note">
+<p><strong>注意</strong>:如果你使用Facebook,去看看你的main feed,然后看一下帖子流。注意到其中一些帖子不是按照数字进行排列的-拥有更多“喜欢”的帖子在列表中通常高于最近的帖子。</p>
+
+<p>也可以看一下你收到的广告是什么类型的——你或许会看到你在其他网站查看的商品。Facebook为突出内容和广告的算法或许还很令人疑惑,但是很明显的,它是依据你的喜好、品味和习惯的!</p>
+</div>
+
+<h2 id="总结">总结</h2>
+
+<p>恭喜,你已经看到了第一篇有关服务器端编程的文章的结尾处。</p>
+
+<p>你已经了解到的就是,服务器端代码在服务器上运行,它的主要角色是控制什么信息应该发送给用户(然而客户端代码只要解决给用户的数据的结构和展示)。</p>
+
+<p>你也应该理解服务器端代码是非常有用的,因为它允许我们创建,可以高效地向个体用户传输定制化的信息的,网站。另外,你还应该知道当你是一个服务器端程序员时可能能够做的一些事情。</p>
+
+<p>最后你应该理解服务器端代码可以用很多种编程语言进行编写,并且你应该使用一个web框架来使得这个过程更加容易一点。</p>
+
+<p>在接下来的文章中我们会帮助你选择一个对于你的第一个网站来说最好的web框架;但是,再接下来我们稍微详细一点地带你过一遍主要的客户端-服务器交互行为。</p>
+
+<p>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</p>
+
+<h2 id="在这个模块中">在这个模块中</h2>
+
+<ul>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li>
+</ul>
diff --git a/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html b/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html
new file mode 100644
index 0000000000..47b23da420
--- /dev/null
+++ b/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html
@@ -0,0 +1,298 @@
+---
+title: 服务端web框架
+slug: learn/Server-side/First_steps/Web_frameworks
+tags:
+ - web框架
+ - 介绍
+ - 初学者
+ - 学习
+ - 指导
+ - 服务器
+ - 服务器端编程
+ - 编码
+translation_of: Learn/Server-side/First_steps/Web_frameworks
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</div>
+
+<p class="summary">前面的文章向你展示了web客户端和服务器之间的通信是什么样子的,HTTP的请求和回应之间的性质,以及服务器端应用为了回应来自web浏览器的请求的需要做的事情。有了这些知识后,现在是时候来探索一个web框架是如何简化这些任务的,并且告诉你应该如何为你的第一个服务器端应用选择一个框架。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预先要求:</th>
+ <td>基础电脑素养。对于服务器端代码是如何处理并响应HTTP请求有深刻的理解。(参见<a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a>)</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>理解web框架是如何简化服务器端代码的开发和维护的,并且帮助读者思考如何为他们自己的开发项目选择一个框架。</td>
+ </tr>
+ </tbody>
+</table>
+
+<p>下面的部分通过使用一些来自web框架的代码段来说明一些要点。如果不能完全看懂代码也不用太在意。我们在“框架详解”模块会帮助你完全理解。</p>
+
+<h2 id="概览">概览</h2>
+
+<p>服务器端框架(亦称 "web 应用框架") 使编写、维护和扩展web应用更加容易。它们提供工具和库来实现简单、常见的开发任务, 包括 路由处理, 数据库交互, 会话支持和用户验证, 格式化输出 (e.g. HTML, JSON, XML), 提高安全性应对网络攻击.</p>
+
+<p>下一节将详细介绍web框架如何简化web应用程序开发。然后,我将阐述一些选择web框架的标准,并给你列出一些选项。</p>
+
+<h2 id="web框架可以为你做什么?">web框架可以为你做什么?</h2>
+
+<p>你并不是必须得使用一个服务器端的web框架,但是我们强烈建议你使用框架——框架会使得你的生活更美好。</p>
+
+<p>这个部分我们讲一下web框架通常会提供的功能(并不是说每一个框架一定会提供下面的所有功能!)</p>
+
+<h3 id="直接处理_HTTP_请求和响应">直接处理 HTTP 请求和响应</h3>
+
+<p>从上一篇文章中我们知道,web服务器和浏览器通过HTTP协议进行通信——服务器等待来自浏览器的HTTP请求然后在HTTP回应中返回相关信息。web框架允许你编写简单语法的代码,即可生成处理这些请求和回应的代码。这意味着你的工作变得简单、交互变得简单、并且使用抽象程度高的代码而不是底层代码。</p>
+
+<p>每一个“view”函数(请求的处理者)接受一个包含请求信息的<code>HttpRequest</code>对象,并且被要求返回一个包含格式化输出的<code>HttpResponse</code>(在下面的例子中是一个字符串)。</p>
+
+<pre class="brush: python"># Django view function
+from django.http import HttpResponse
+
+def index(request):
+ # Get an HttpRequest (request)
+ # perform operations using information from the request.
+  # Return HttpResponse
+ return HttpResponse('Output string to return')
+</pre>
+
+<h3 id="将请求路由到相关的handler中">将请求路由到相关的handler中</h3>
+
+<p>大多数的站点会提供一系列不同资源,通过特定的URL来访问。如果都放在一个函数里面,网站会变得很难维护。所以web框架提供一个简单机制来匹配URL和特定处理函数。这种方式对网站维护也有好处,因为你只需要改变用来传输特定功能的URL而不用改变任何底层代码。</p>
+
+<p>不同的框架使用不同机制进行匹配。比如Flask(Python)框架通过使用装饰器来增加视图的路由。</p>
+
+<pre class="brush: python">@app.route("/")
+def hello():
+ return "Hello World!"</pre>
+
+<p>然而,Django则期望开发者们定义一张URL pattern和视图函数URL的匹配列表。</p>
+
+<pre class="brush: python">urlpatterns = [
+ url(r'^$', views.index),
+ # example: /best/myteamname/5/
+ url(r'^(?P&lt;team_name&gt;\w.+?)/(?P&lt;team_number&gt;[0-9]+)/$', views.best),
+]
+</pre>
+
+<h3 id="使从请求中获得数据变得简单">使从请求中获得数据变得简单</h3>
+
+<p>数据在HTTP请求中的编码方式有很多种。一个从服务器获得文件或者数据的HTTP <code>GET</code>请求可能会按照URL参数中要求的或者URL结构中的方式进行编码。一个更新服务器上数据的HTTP <code>POST</code>请求则会在请求主体中包含像“POST data”这样的更新信息。HTTP请求也可能包含客户端cookie中的即时会话和用户信息。</p>
+
+<p>web框架提供一个获得这些信息的适合编程语言的机制。比如,Django传递给视图函数的<code>HttpRequest</code>对象包含着获得目标URL的方式和属性、请求的类型(比如一个HTTP <code>GET</code>)、<code>GET</code>或者<code>POST</code>参数、cookie或者session数据等等。Django也可以通过在URL匹配表中定义“抓取模式”来在URL结构中传递编码了的信息(如上面的编码片段中的最后一行)。</p>
+
+<h3 id="抽象和简化数据库接口">抽象和简化数据库接口</h3>
+
+<p>网站使用数据库来存储与用户分享的信息和用户个人信息。web框架通常会提供一个数据库层来抽象数据库的读、写、查询和删除操作。这一个抽象层被称作对象关系映射器(ORM)。</p>
+
+<p>使用对象关系映射器有两个好处:</p>
+
+<ul>
+ <li>你不需要改变使用数据库的代码就可以替换底层数据库。这就允许开发者依据用途优化不同数据库的特点。</li>
+ <li>简单的数据的验证可以被植入到框架中。这会使得检查数据是否按照正确的方式存储在数据库字段中或者是否是特定的格式变得简单(比如邮箱地址),并且不是恶意的(黑客可以利用特定的编码模式来进行一些如删除数据库记录的非法操作)。</li>
+</ul>
+
+<p>比如,Django框架提供一个对象关系映射,并且将用来定义数据库记录的结构称作模型。模型制定被存储的字段类型,可能也会提供那些要被存储的信息的验证(比如,一个email字段只允许合法email地址)。字段可能也会指明最大信息量、默认值、选项列表、帮助文档、表单标签等。这个模型不会申明任何底层数据库的信息,因为这是一个只能被我们的代码改变的配置信息。</p>
+
+<p>下面第一个代码片段展示了一个简单的为<code>Team</code>对象设计的Django模型。这个模型会使用字符字段来存储一个队伍的名字和级别,同时还指定了用来存储每一条记录的最大字符数量。<code>team_level</code>是一个枚举字段,所以我们也提供了一个被存储的数据和被展示出来的选项之间的匹配,同时指定了一个默认值。</p>
+
+<pre class="brush: python">#best/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 our other teams
+    )
+    team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
+</pre>
+
+<p>Django模型提供了简单的搜索数据库的查询API。这可以通过使用不同标准来同时匹配一系列的字段(比如精确、不区分大小写、大于等等),并且支持一些复杂的陈述(比如,你可以指定在U11水平的队伍中搜索队伍名字中以“Fr”开头或者“al”结尾的队伍)。</p>
+
+<p>第二个代码片段展示了一个视图函数(资源处理器),这个视图函数用来展示所有U09水平的队伍——通过指明过滤出所有<code>team_level</code>字段能准确匹配'U09'的队伍(注意过滤规则如何传递给<code>filter( )</code>,它被视为一个变量:<code>team_level__exact</code>,由字段名、匹配类型和分隔它们的双重下划线组成)。</p>
+
+<pre class="brush: python">#best/views.py
+
+from django.shortcuts import render
+from .models import Team
+
+def youngest(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>
+
+<h3 id="渲染数据">渲染数据</h3>
+
+<p>web框架经常提供模板系统。这些允许你制定输出文档的结构,使用为那些数据准备的将在页面生成时添加进去的占位符。模板经常是用来生成HTML的,但是也可以用来生成一些其他的文档。</p>
+
+<p>框架提供一个机制,使得从存储的数据中生成其他格式数据变得简单,包括{{glossary("JSON")}}和{{glossary("XML")}}。</p>
+
+<p>比如,Django模板允许你通过使用“双重花括号”(如<code>{</code><code>{ <em>variable_name</em> </code><code>}</code><code>}</code>)来指定变量,当页面被渲染出来时,这些变量会被从视图函数传递过来的值代替。模板系统也会提供表达支持(通过语法<code>{% <em>expression</em> %}来实现</code>),这样就允许模板进行一些简单的操作比如迭代传递给模板的值列表。</p>
+
+<div class="note">
+<p><strong>Note</strong>: 很多其他的模板系统使用相似的语法,比如:Jinja2 (Python), handlebars (JavaScript), moustache (JavaScript), 等。</p>
+</div>
+
+<p>下面的代码片段展示了它们如何工作的。下面的内容接着从上一个部分而来的“youngest team”实例,HTML模板通过视图函数传进一个叫做youngest_teams的值列表。在HTML骨架中我们有一个初步检查youngest_teams变量是否存在的表示,然后会在for循环里面进行迭代。在每一次迭代中模板会以列表元素的形式展示队伍的team_name值。</p>
+
+<pre class="brush: html">#best/templates/best/index.html
+
+&lt;!DOCTYPE html&gt;
+&lt;html lang="en"&gt;
+&lt;body&gt;
+
+ {% if youngest_teams %}
+    &lt;ul&gt;
+    {% for team in youngest_teams %}
+        &lt;li&gt;\{\{ team.team_name \}\}&lt;/li&gt;
+    {% endfor %}
+    &lt;/ul&gt;
+{% else %}
+    &lt;p&gt;No teams are available.&lt;/p&gt;
+{% endif %}
+
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+<h2 id="如何选择一个web框架">如何选择一个web框架</h2>
+
+<p>几乎对于你想要使用的每一种语言都有大量的web框架(我们在下面的部分列举了一些比较受欢迎的框架)。有这么多选择,导致很难决定选择哪个框架为你的新web应用提供最好的开端。</p>
+
+<p>一些影响你决定的因素有:</p>
+
+<ul>
+ <li><strong>学习代价</strong>:学习一个web框架取决于你对底层语言的熟悉程度,它的API的一致性与否,文档质量,社区的体量和活跃程度。如果你完全没有编程基础的话,那就考虑Django吧(它是基于上面几条标准来看最容易学习的了)。如果你已经成为开发团队的一部分,而那个开发团队对某一种语言或者某一个框架有着很重要的开发经历,那么就坚持相关框架。</li>
+ <li><strong>效率</strong>:效率是指一旦你熟悉某一个框架之后,你能够多块地创造一个新功能的衡量方式,包括编写和维护代码的代价(因为当前面的功能崩掉之后,你没法编写新的功能)。影响效率的大多数因素和学习代价是类似的——比如,文档,社区,编程经历等等。——其他因素还有:
+ <ul>
+ <li>框架目的/起源:一些框架最初是用来解决某一类特定问题的,并且最好在生成app的时候顾及到这些约束。比如,Django是用来支持新闻网站的,因此非常适合博客或者其他包含发布内容的网站。相反的,Flask是一个相对来说轻量级的框架,因此适合用来生成一些在嵌入式设备上运行的app。</li>
+ <li><em>Opinionated vs unopinionated</em>:一个opinionated的框架是说,解决某一个特定问题时,总有一个被推荐的最佳的解决方法。opinionated的框架在你试图解决一些普通问题的时候,更加趋向于产品化,因为它们会将你引入正确的方向,尽管有些时候并不那么灵活。</li>
+ <li>一些web框架默认地包含了开发者们能遇到的任何一个问题的工具/库,而一些轻量级的框架希望开发者们自己从分离的库中选择合适的解决方式(Django是其前者的一个实例,而Flask则是轻量级的一个实例)。包含了所有东西的框架通常很容易上手因为你已经有了你所需要的任何东西,并且很可能它已经被整合好了,并且文档也写得很完善。然而一个较小型的框架含有你所需要(或者以后需要)的各种东西,它将只能在受更多限制的环境中运行,并且需要学习更小的、更简单的子集学习。</li>
+ <li>是否选择一个鼓励良好开发实例的框架:比如,一个鼓励 <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model-View-Controller</a> 结构来将代码分离到逻辑函数上的框架将会是更加易于维护的代码,想比与那些对开发者没有此期待的框架而言。同样的,框架设计也深刻影响了测试和重复使用代码的难易程度。</li>
+ </ul>
+ </li>
+ <li><strong>框架/编程语言的表现:</strong> 通常来讲,“速度”并不是选择中最重要的因素,甚至,相对而言,运行速度很缓慢的Python对于一个在中等硬盘上跑的中等大小的网站已经足够了。其他语言(C++/Javascript)的明显的速度优势很有可能被学习和维护的代价给抵消了。</li>
+ <li><strong>缓存支持:</strong>当你的网站之间变得越来越成功之后,你可能会发现它已经无法妥善处理它收到的大量请求了。在这个时候,你可能会开始考虑添加缓存支持。缓存是一种优化,是指你将全部的或者大部分的网站请求保存起来,那么在后继请求中就不需要重新计算了。返回一个缓存请求比重新计算一次要快得多。缓存可以被植入你的代码里面,或者是服务器中(参见<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>)。web框架对于定义可缓存内容有着不同程度的支持。</li>
+ <li><strong>可扩展性:</strong>一旦你的网站非常成功的时候,你会发现缓存的好处已经所剩无几了,甚至垂直容量到达了极限(将程序运行在更加有力的硬件上面)。在这个时候,你可能需要水平扩展(将你的网站分散到好几个服务器和数据库上来加载)或者“地理上地”扩展, 因为你的一些客户距离你的服务器很远。你所选择的框架将会影响到扩展你的网站的难易程度。</li>
+ <li><strong>网络安全:</strong>一些web框架对于解决常见的网络攻击提供更好的支持。例如,Django消除所有用户从HTML输入的东西。因此从用户端输入的Javascript不会被运行。其他框架也提供相似的功能,但是通常在默认情况下是不直接开启的。</li>
+</ul>
+
+<p>可能还有其他一些原因,包括许可证、框架是否处于动态发展过程中等等。</p>
+
+<p>如果你是一个完全的初学者,那么你可能会基于“易于学习”来选择你的框架。除了语言本身的“易于学习”之外,帮助新手的高质量的文档/教程和一个活跃的社区是你最有价值的资源。在后续课程中,我们选取了Djnago(Python)和Express(Node/Javascript)来编写我们的实例,主要因为它们很容易上手并且有强大的支持。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 我们可以去 <a href="https://www.djangoproject.com/">Django</a> (Python) 和 <a href="http://expressjs.com/">Express</a> (Node/JavaScript) 的主页上去看看它们的文档和社区。</p>
+
+<ol>
+ <li>导航至主页 (上面已给出链接)
+ <ul>
+ <li>点击文档菜单的链接 (通常都叫做 "Documentation(文档), Guide(指南), API Reference(API参考), Getting Started(快速开始)"之类的。)</li>
+ <li>你能看到如何设置URL路由、模板、数据库/数据模型的主题吗?</li>
+ <li>文档说得够清楚吗?</li>
+ </ul>
+ </li>
+ <li>导航至各个站点的邮件列表(从社区的链接访问)
+ <ul>
+ <li>近几天提出了多少问题?</li>
+ <li>有多少问题得到了回应?</li>
+ <li>他们是否有一个活跃的社区?</li>
+ </ul>
+ </li>
+</ol>
+</div>
+
+<h2 id="几个还不错的框架?">几个还不错的框架?</h2>
+
+<p>让我们继续,来讨论几个特定的服务器端框架。</p>
+
+<p>下面的服务器端框架体现了现在最受欢迎的几个。它们有你需要用来提升效率的一切东西——它们是开源的,一直保持发展的态势,有着富有激情的社区,社区里的人创作出文档并且在讨论板上帮助使用者,并且被使用在很多高质量的网站上。当然还有很多其他非常棒的框架,你可以使用搜索引擎探索一下。</p>
+
+<div class="note">
+<p><strong>注意:(部分)解释来自框架的官方网站!</strong></p>
+</div>
+
+<h3 id="Django_Python">Django (Python)</h3>
+
+<p><a href="https://www.djangoproject.com/">Django</a>是一个高水平的python web框架,它鼓励快速的开发和简洁、务实的设计。它由非常有经验的开发者创建的,考虑到了web开发中会遇到的大多数难题,所以你无需重复造轮就能够专心编写你的应用。 </p>
+
+<p>Django遵循“Batteries included”哲学,并且提供了几乎所有大多开发者们想要“开箱即用”的东西。因为它已经包含了所有东西,它作为一个整体一起工作,遵循着一致的设计原则,并且有扩展的、持续更新的文档。它也是非常快、安全和易于扩展的。基于python,Django代码非常容易阅读和维护。</p>
+
+<p>使用Django的主流网站(从Django官网首页看到的)包括: Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, Open Stack.</p>
+
+<h3 id="Flask_Python">Flask (Python)</h3>
+
+<p><a href="http://flask.pocoo.org/">Flask</a>是python的一个微型框架</p>
+
+<p>虽然体量很小,Flask却可以开箱即用地创造出完备网站。它包含一个开发服务器和调试器,并且包含对于 <a href="https://github.com/pallets/jinja">Jinja2</a> 模板的支持, 安全的cookie, <a href="https://en.wikipedia.org/wiki/Unit_testing">unit testing</a>, 和 <a href="http://www.restapitutorial.com/lessons/restfulresourcenaming.html">RESTful</a> request dispatching。它有很好的文档和一个活跃的社区。</p>
+
+<p>Flask已经非常火爆了,部分因为那些需要在小型的、资源受限的系统中提供web服务的开发者们。(比如,在<a href="https://www.raspberrypi.org/">Raspberry Pi</a>, <a href="http://blogtarkin.com/drone-definitions-learning-the-drone-lingo/">Drone controllers</a>等上面运行服务器)。</p>
+
+<h3 id="Express_Node.jsJavaScript">Express (Node.js/JavaScript)</h3>
+
+<p><a href="http://expressjs.com/">Express</a> 针对 <a href="https://nodejs.org/en/">Node.js</a> 的快速的、unopinioned、灵活的、小型的web框架(node是用来运行Javascript的无浏览器的环境)。它为web和移动应用提供强大的系列功能,并且传输有用的HTTP工具、方法和<a href="/en-US/docs/Glossary/Middleware">middleware</a>.</p>
+
+<p>Express非常受欢迎,主要因为它减轻了客户端Javascript程序到服务器端开发的迁移,并且部分因为它是资源节约型(底层的node环境在单线程中使用轻量级多任务处理,而不是为每个web请求提供单独的进程)。</p>
+
+<p>因为Express是一个小型的web框架,它几乎不包含任何你可能想要使用的组件(比如,数据库接口和对用户和会话的支持通过独立的库来完成)。有很多独立的、非常好的组件,但是有时候你可能很难决定对于特定目的而言哪一个是最好的! </p>
+
+<p> 很多非常受欢迎的服务器端编程和全栈框架(同时包括服务器端和客户端框架),包括 <a href="http://feathersjs.com/">Feathers</a>, <a href="https://www.itemsapi.com/">ItemsAPI</a>, <a href="http://keystonejs.com/">KeystoneJS</a>, <a href="http://krakenjs.com/">Kraken</a>, <a href="http://lean-stack.io/">LEAN-STACK</a>, <a href="http://loopback.io/">LoopBack</a>, <a href="http://mean.io/">MEAN</a>, 和 <a href="http://sailsjs.org/">Sails</a>.</p>
+
+<p>大量的profile company使用Express,包括优步、Accenture、IBM等(<a href="http://expressjs.com/en/resources/companies-using-express.html">这里</a>是一张列表).</p>
+
+<h3 id="Ruby_on_Rails_Ruby">Ruby on Rails (Ruby)</h3>
+
+<p><a href="http://rubyonrails.org/">Rails</a> (通常被称作"Ruby on Rails")是一个为Ruby语言编写的web框架。</p>
+
+<p>Rails遵循了和Django非常相似的设计哲学。正如Django一样,它提供了检索URLs的标准机制、从数据库中访问数据、从模板中生成HTML页面、格式化数据{{glossary("JSON")}} 或者 {{glossary("XML")}}。同样的,它也鼓励如 DRY (不要重复你自己)的设计模板——尽可能地只写一次代码、MVC(模板-视图-控制中心)以及很多其他的一些。</p>
+
+<p>当然,还有很多由于因为具体设计决定和语言的特性导致的差异。</p>
+
+<p>Rails被用在很多站点中,包括:<strong> </strong><a href="https://basecamp.com/">Basecamp</a>, <a href="https://github.com/">GitHub</a>,<a href="https://shopify.com/">Shopify</a>, <a href="https://airbnb.com/">Airbnb</a>, <a href="https://twitch.tv/">Twitch</a>, <a href="https://soundcloud.com/">SoundCloud</a>,<a href="https://hulu.com/">Hulu</a>, <a href="https://zendesk.com/">Zendesk</a>, <a href="https://square.com/">Square</a>, <a href="https://highrisehq.com/">Hi</a></p>
+
+<h3 id="ASP.NET">ASP.NET</h3>
+
+<p><a href="http://www.asp.net/">ASP.NET</a> 是一个由微软开发的开源Web框架,用于构建现代的Web应用程序和服务。通过ASP.NET你能快速创建基于HTML、CSS、JavaScript的网站,并且能满足大量用户的需求,还可以很容易地添加诸如Web API、数据表单、即时通讯的功能。</p>
+
+<p>ASP.NET的特点之一就是它建立在 <a href="https://en.wikipedia.org/wiki/Common_Language_Runtime">Common Language Runtime</a> (CLR公共语言运行时)之上。这使得程序员可以使用任何支持的.NET语言(如C#、Visual Basic)来编写ASP.NET代码。和很多微软的产品一样,它得益于出色的开发工具(通常是免费的)、活跃的开发者社区,以及详尽的文档。 </p>
+
+<p>ASP.NET被微软、Xbox、Stack Overflow等采用。</p>
+
+<h3 id="Mojolicious_Perl">Mojolicious (Perl)</h3>
+
+<p><a href="http://mojolicious.org/">Mojolicious</a>是为Perl语言设计的新一代Web框架。 </p>
+
+<p>在Web的早期阶段,许多人都为了一个叫做 <a href="https://metacpan.org/module/CGI">CGI</a> 的优秀的Perl库而学过Perl。它简单到即使你不是太懂这门语言也可以开始使用,而且也强大到足以让你可以用下去。Mojolicious通过最新的技术实现了这个想法。</p>
+
+<p>Mojolicious提供的一些功能是:</p>
+
+<ul>
+ <li><strong>实时Web框架</strong>,可轻松将单个文件原型,生成为结构良好的MVC Web应用程序;</li>
+ <li>RESTful路由,插件,命令,Perl-ish模板,内容协商,会话管理,表单验证,测试框架,静态文件服务器,CGI /<a href="http://plackperl.org/">PSGI</a> 检测,一流的Unicode支持;</li>
+ <li>全栈式 HTTP 和 WebSocket 客户机/服务器架構,由以下技术支持与实作-IPv6,TLS,SNI,IDNA,HTTP / SOCKS5 代理,UNIX 域套接字,Comet(长轮询),保持活动,连接池,超时,cookie,multipart,支持 gzip 压缩</li>
+ <li>具有CSS选择器支持的 JSON 和 HTML / XML 解析器和生成器;</li>
+ <li>非常干净,可移植且面向对象的纯 Perl API,没有任何隐藏的魔法;</li>
+ <li>全新的代码基于多年的经验,免费和开源。</li>
+</ul>
+
+<h2 id="总结">总结</h2>
+
+<p>这篇文章展示了web框架如何使得编写和维护服务器端代码变得简单。它也提供了对于几个流行的框架的评价,还讨论了选择一个web框架的标准。你现在至少应该了解了如何为你的服务器端开发选择一个web框架。如果还没有,也不要担心——接下来我们给你一个详细的Django和Express教程,从而让你有一些使用web框架的实战经验。</p>
+
+<p>这个模块的下一章节我们会稍微转变一下思路,我们会讨论一下网络安全。</p>
+
+<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</p>
diff --git a/files/zh-cn/learn/server-side/first_steps/website_security/index.html b/files/zh-cn/learn/server-side/first_steps/website_security/index.html
new file mode 100644
index 0000000000..3b6e400257
--- /dev/null
+++ b/files/zh-cn/learn/server-side/first_steps/website_security/index.html
@@ -0,0 +1,165 @@
+---
+title: 站点安全
+slug: learn/Server-side/First_steps/Website_security
+tags:
+ - 安全
+ - 站点安全
+translation_of: Learn/Server-side/First_steps/Website_security
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div>
+
+<p class="summary">站点安全需要在网站设计和使用的各个方面保持警惕。这篇入门文章不会让你成为一个网站安全专家,但是可以帮助你理解威胁的来源以及如何保护你的Web应用来远离这些常见的攻击。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">准备内容:</th>
+ <td>计算机基础知识.</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>
+ <p>了解针对Web应用常见的攻击方式和用来减少网站被黑客攻击的风险的方法。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="什么是站点安全">什么是站点安全?</h2>
+
+<p>互联网很危险!我们经常听到网站因为拒绝服务攻击或主页显示被修改的(通常是有害的)内容而无法使用。在一些出名的案例中,上百万的密码、邮件地址和信用卡信息被泄露给了公众,导致网站用户面临个人尴尬和财务威胁。</p>
+
+<p>站点安全的目的就是为了防范这些(或者说所有)形式的攻击。更正式点说,站点安全就是为保护站点不受未授权的访问、使用、修改和破坏而采取的行为或实践。</p>
+
+<p>有效的站点安全需要在对整个站点进行设计:包括Web应用编写、Web服务器的配置、密码创建和更新的策略以及客户端代码编写等过程。尽管这听起来很凶险,好消息是如果你使用的是服务器端的Web服务框架,那么多数情况下它默认已经启用了健壮而深思熟虑的措施来防范一些较常见的攻击。其它的攻击手段可以通过站点的Web服务器配置来减轻威胁,例如启用HTTPS. 最后,可以用一些公开可用的漏洞扫描工具来协助发现你是否犯了一些明显的错误。</p>
+
+<p>文章的剩余部分列举了一些常见威胁的细节以及用来保护站点的一些简单措施。</p>
+
+<div class="note">
+<p><strong>Note</strong>: 这只是一篇介绍性的主题,旨在帮你开始思考站点安全。它并不详尽。</p>
+</div>
+
+<h2 id="站点安全威胁">站点安全威胁</h2>
+
+<p>这个部分列举了常见网站攻击手段以及如何减轻它们带来的危害。当你读的时候请注意,这些攻击是如何得手的,当web应用相信这些来自浏览器的信息或者不够坚持自己的时候。</p>
+
+<h3 id="跨站脚本_XSS">跨站脚本 (XSS)</h3>
+
+<p>XSS是一个术语,用来描述一类允许攻击者通过网站将客户端脚本代码注入到其他用户的浏览器中的攻击手段。由于注入到浏览器的代码来自站点,其是可信赖的,因此可以做类似将该用户用于站点认证的cookie发送给攻击者的事情。一旦攻击者拿到了这个cookie,他们就可以登陆到站点,就好像他们就是那个用户,可以做任何那个用户能做的事情。根据站点的不同,这些可能包括访问他们的信用卡信息、查看联系人、更改密码等。</p>
+
+<div class="note">
+<p><strong>Note</strong>: XSS 攻击在历史上较其他类型更为常见。</p>
+</div>
+
+<p>有两种主要的方法可以让站点将注入的脚本返回到浏览器 -- 通常被称做 反射型 和 持久型 XSS攻击。</p>
+
+<ul>
+ <li>反射型 XSS 攻击发生在当传递给服务器的用户数据被立即返回并在浏览器中原样显示的时候 -- 当新页面载入的时候原始用户数据中的任何脚本都会被执行!<br>
+ <br>
+ 举个例子,假如有个站点搜索函数,搜索项被当作URL参数进行编码,这些搜索项将随搜索结果一同显示。攻击者可以通过构造一个包含恶意脚本的搜索链接作为参数(例如 <code>http://mysite.com?q=beer&lt;script%20src="http://evilsite.com/tricky.js"&gt;&lt;/script&gt; </code>),然后把链接发送给另一个用户。如果目标用户点击了这个链接,当显示搜索结果时这个脚本就会被执行。正如上述讨论的,这促使攻击者获取了所有需要以目标用户进入站点的信息 -- 可能会购买物品或分享联系人信息。<br>
+  </li>
+ <li>持久型 XSS 攻击: 恶意脚本存储在站点中,然后再原样地返回给其他用户,在用户不知情的情况下执行。<br>
+ <br>
+ 举个例子,接收包含未经修改的HTML格式评论的论坛可能会存储来自攻击者的恶意脚本。这个脚本会在评论显示的时候执行,然后向攻击者发送访问该用户账户所需的信息。这种攻击类型及其常见而且有效,因为攻击者不需要与受害者有任何直接的接触。<br>
+ <br>
+ 尽管 <code>POST</code> 和 <code>GET</code> 方式获取到的数据是XSS攻击最常见的攻击来源,任何来自浏览器的数据都可能包含漏洞(包括浏览器渲染过的Cookie数据以及用户上传和显示的文件等).</li>
+</ul>
+
+<p> 防范 XSS 攻击的最好方式就是删除或禁用任何可能包含可运行代码指令的标记。对 HTML 来说,这些包括类似 <code>&lt;script&gt;</code>, <code>&lt;object&gt;</code>, <code>&lt;embed&gt;</code>,和 <code>&lt;link&gt; </code>的标签。</p>
+
+<div>
+<p>修改用户数据使其无法用于运行脚本或其它影响服务器代码执行的过程被称作输入过滤。许多Web框架默认情况下都会对来自HTML表单的用户数据进行过滤。</p>
+</div>
+
+<h3 id="SQL_注入">SQL 注入</h3>
+
+<p>SQL 注入漏洞使得恶意用户能够通过在数据库上执行任意SQL代码,从而允许访问、修改或删除数据,而不管该用户的权限如何。成功的注入攻击可能会伪造身份信息、创建拥有管理员权限的身份、访问服务器上的任意数据甚至破坏/修改数据使其变得无法使用。</p>
+
+<p>如果传递给底层SQL语句的用户输入可以修改该语句的语义,这种漏洞便是存在的。例如下面一段代码,本来是用来根据HTML表单提供的特定名字(<code>userName</code>)来列出所有的用户:</p>
+
+<pre class="brush: sql notranslate">statement = "SELECT * FROM users WHERE name = '" + <strong>userName</strong> + "';"</pre>
+
+<p>如果用户输入了真实的名字,这段代码会如预想的运行。然而一个恶意用户可以完全将这个SQL语句的行为改变为下面的新语句的行为,只要通过将 <code>userName</code>指定为下列 “<strong>粗体</strong>” 的文本。修改后的代码创建了一个合法的SQL语句,该语句删除了整个<code> users</code> 表,然后从 <code>userinfo</code> 表中获取了所有数据(所有用户的信息都被暴露了)。这是有效的,因为注入的文本的第一部分(<code>a';</code>)结束了原来的语句( ' 在SQL语句中是用来描述字符串常量的) 。</p>
+
+<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'</strong>;
+</pre>
+
+<p>避免此种攻击的方法就是确保任何传递给SQL查询语句的用户数据都无法更改查询的本来用意。有种方式便是将用户输入中任何在SQL语句中有特殊含义的字符进行转义。</p>
+
+<div class="note">
+<p><strong>Note</strong>: SQL语句把 ' 号作为一个字符串常量的开头的结尾。通过在前面放置一个斜杠,我们把单引号进行了转义( \' ),然后 SQL 就会将其视为一个字符(作为字符串的一部分)。</p>
+</div>
+
+<p>在下面的语句中我们对 ' 字符进行了转义。SQL会将<strong>粗体</strong>显示的整段字符串解释为 name(这个name很古怪,但至少是没有危害的!)</p>
+
+<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'</strong>;
+
+</pre>
+
+<p>Web框架通常会为你进行这种转义操作。例如 Django,可以确保任何传递给查询集合 (model查询)的用户数据都是已经转义过的。</p>
+
+<div class="note">
+<p><strong>Note</strong>: 本章节引用了大量来自 <a href="https://en.wikipedia.org/wiki/SQL_injection">Wikipedia </a>的内容.</p>
+</div>
+
+<h3 id="跨站请求伪造_CSRF">跨站请求伪造 (CSRF)</h3>
+
+<p>CSRF 攻击允许恶意用户在另一个用户不知情的情况下利用其身份信息执行操作。</p>
+
+<p>这种形式的攻击用实例来解释最好。John是一个恶意用户,他知道某个网站允许已登陆用户使用包含了账户名和数额的HTTP <code>POST</code>请求来转帐给指定的账户。John 构造了包含他的银行卡信息和某个数额做为隐藏表单项的表单,然后通过Email发送给了其它的站点用户(还有一个伪装成到 “快速致富”网站的链接的提交按钮).</p>
+
+<p>如果某个用户点击了提交按钮,一个 HTTP <code>POST</code> 请求就会发送给服务器,该请求中包含了交易信息以及浏览器中与该站点关联的所有客户端cookie(将相关联的站点cookie信息附加发送是正常的浏览器行为) 。服务器会检查这些cookie,以判断对应的用户是否已登陆且有权限进行上述交易。</p>
+
+<p>最终的结果就是任何已登陆到站点的用户在点击了提交按钮后都会进行这个交易。John发财啦!</p>
+
+<div class="note">
+<p><strong>Note</strong>: 这里的诀窍是,John 不需要访问那些用户的cookie(或者说身份信息) -- 用户的浏览器存储了这些信息,而且会自动将其包含在发送给对应服务器的请求中。</p>
+</div>
+
+<p>杜绝此类攻击的一种方式是在服务器端要求每个 POST 请求都包含一个用户特定的由站点生成的密钥( 这个密钥值可以由服务器在发送用来传输数据的网页表单时提供)。这种方式可以使John无法创建自己的表单,因为他必须知道服务器提供给那个用户的密钥值。即使他找出了那个密钥值,并为那个用户创建了表单,他也无法用同样的表单来攻击其他的所有用户。</p>
+
+<p>Web 框架通常都会包含一些类似的CSRF 防范技巧。</p>
+
+<h3 id="其他威胁">其他威胁</h3>
+
+<p>其它常见的攻击/漏洞利用方式包括:</p>
+
+<ul>
+ <li><a href="https://www.owasp.org/index.php/Clickjacking">劫持 </a>. 通过这种方式,恶意用户劫持了对可见上层站点的点击,然后将其转发给下层隐藏的页面。这种技术例如可以用来显示一个合法的银行网站,但是将登陆认证信息截获到由攻击者控制的隐藏的{{htmlelement("iframe")}}中。另外也可以用于促使用户点击可见网页的按钮,实际上却在不知情的情况点击了一个完全不同的按钮。作为防范手段,你的站点可以通过设置适当的HTTP 头来防止其被嵌入到另一个站点的iframe中。</li>
+ <li><a href="/en-US/docs/Glossary/Distributed_Denial_of_Service">拒绝服务</a> (DoS). Dos 通常通过使用伪造的请求淹没站点,这样合法用户的访问就会被中断。这些请求可能仅仅是数量巨大或者是单独消耗了大量资源 (如 延缓读, 上传大文件等) 。DoS 防护通常通过识别并堵塞 “恶意”的网络数据来工作,同时允许合法信息通过。 这些防护一般都是在Web服务器之前或服务器中进行(它们并非web应用本身所为).</li>
+ <li><a href="https://en.wikipedia.org/wiki/Directory_traversal_attack">目录遍历</a>(File and disclosure). 在这种攻击中,攻击者会尝试访问Web服务器文件系统中他们本不该访问的部分。这种漏洞会在用户可以传递包含文件系统导航字符的文件名时出现(比如 ../../ )。解决方法就是在使用前对用户输入进行过滤。</li>
+ <li><a href="https://en.wikipedia.org/wiki/File_inclusion_vulnerability">文件包含</a>. 在此攻击方式中,用户在传递给服务器的数据中指定一个“非故意”的文件来显示或执行。一旦载入成功,这个文件就可以在服务器或客户端(造成 XSS 攻击)执行。解决方式就是在使用前对输入进行过滤。</li>
+ <li><a href="https://www.owasp.org/index.php/Command_Injection">命令行注入</a>. 命令行注入攻击允许恶意用户在主机操作系统中执行任意系统命令。解决方法就是在系统调用中使用前对用户输入进行过滤。</li>
+</ul>
+
+<p>还有很多的方式。要查看更全面的列表,请访问 <a href="https://en.wikipedia.org/wiki/Category:Web_security_exploits">Category:Web security exploits</a> (Wikipedia) 和 <a href="https://www.owasp.org/index.php/Category:Attack">Category:Attack</a> (Open Web Application Security Project).</p>
+
+<h2 id="一些关键信息">一些关键信息</h2>
+
+<p>当Web应用信任来自浏览器的数据时,上述章节里提到的大多数攻击利用手段才能成功。无论你做什么其它的事情来提升你的网站的安全性能,在将信息展示在浏览器之前、在使用SQL语句进行查询之前、在传递给一个操作系统或者文件系统之前,你应该过滤掉所有的用户源信息。</p>
+
+<div class="warning">
+<p>重要:在你可以了解到的有关网站安全大多数 课程之中,最重要的就是<strong>不要相信来自浏览器的数据</strong>。包括在URL参数中的GET请求、POST请求、HTTP头、cookies、用户上传的文件等等。一定要每次都检查用户输入的信息。每次都预想最坏的结果。</p>
+</div>
+
+<p>你可以采取一些简单的步骤:</p>
+
+<ul>
+ <li>采取更加强大的密码管理措施。当密码频繁更换时鼓励更加健壮的密码。采取双因素认证,也就是说除了密码,用户还应该输入另一种认证码(通常是只有唯一一个用户拥有的通过一些物理硬件传输的,比如发送给用户手机的验证短信)。</li>
+ <li>将你的服务器配制成 <a href="/en-US/docs/Glossary/https">HTTPS</a> 和 <a href="/en-US/docs/Web/Security/HTTP_strict_transport_security">HTTP Strict Transport Security</a> (HSTS)。HTTPS 会加密你的用户和服务器之间传输的信息。这使得登录认证、cookise、POST数据及头信息不易被攻击者获得。</li>
+ <li>持续追踪那些常见的网络攻击 (the <a href="/en-US/docs/">current OWASP list is here</a>),先解决最脆弱的部分。</li>
+ <li>使用 <a href="https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools">vulnerability scanning tools</a> 来对你的网站进行一些安全测试(然后,你的非常受欢迎的网站还可以靠提供赏金来寻找bug,就像Mozilla这样(<a href="https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/">like Mozilla does here</a>)。</li>
+ <li>只存储和展示你不得不需要的东西。比如,如果你的用户不得不存储一些敏感信息(如信用卡详明),只展示足以让用户识别卡号的几位数字即可,却不足以让黑客复制之后在另一个站点使用。现今最常见的是只展示信用卡卡号后4位数字。</li>
+</ul>
+
+<p>web框架可以帮助抵御很多常见的攻击。</p>
+
+<h2 id="总结">总结</h2>
+
+<p>这篇文章介绍了有关网络安全的概念和你应该避免的一些常见的攻击。最重要的是,你应该明白一个web应用不可以相信任何来自网络服务器的数据!所有的用户数据在展示、使用SQL查询或者回应系统之前应该被过滤。</p>
+
+<p>这也是<a href="/en-US/docs/Learn/Server-side/First_steps">这个模块</a>的结尾,涵盖了你之前在服务器端编程学到的知识。我们希望你非常享受这个学习基础概念的过程,并且你现在已经准备好选择一个web框架开始编程了。</p>
+
+<p>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p>
diff --git a/files/zh-cn/learn/server-side/index.html b/files/zh-cn/learn/server-side/index.html
new file mode 100644
index 0000000000..f0e6e64501
--- /dev/null
+++ b/files/zh-cn/learn/server-side/index.html
@@ -0,0 +1,41 @@
+---
+title: 服务器端网页编程
+slug: learn/Server-side
+translation_of: Learn/Server-side
+---
+<div>{{LearnSidebar}}</div>
+
+<p class="summary"><strong><em>动态网页——服务器端编程 [Dynamic Websites </em></strong>–<em><strong> Server-side programming]</strong></em> 这主题是一系列的模块来演示如何创建动态的网页;可以交付自定义的信息来回应 HTTP 请求的网页。这些模块为服务器端编程提供了一个通用的介绍,以及如何使用 Django (Python) 和 Express (Node.js/JavaScript) 去创建基础应用的具体的入门指导。</p>
+
+<p>大多数的主流网页使用一类服务器端的技术去动态地显示所要求的不同数据。举个例子,想象一下 Amazon 上有多少可购买的产品,以及 FaceBook 上有多少帖子?用完全不同的静态页面去显示所有的这些内容会彻底地低效,所以取而代之的是这些网站展示的是静态的模板 [templates] (用 <a href="/zh-CN/docs/Learn/HTML">HTML</a>, <a href="/zh-CN/docs/Learn/CSS">CSS</a>, 和 <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 构建),然后在有需要时动态地在这些模板中更新数据展示,比如说当你想要在 Amazon 上浏览一个不同的产品。</p>
+
+<p>在现代的网页开发世界里,学习服务器端开发是高度推荐的。</p>
+
+<p><strong style="color: #4d4e53; font-size: 2.143rem; font-weight: 700; letter-spacing: -1px;">学习路径</strong></p>
+
+<p>开始服务器端编程通常比客户端编程要简单,因为动态的页面倾向于执行非常类似的操作(从数据库中获取数据然后显示到一个页面中,确认用户输入的数据以及保存到一个数据库中,检查用户的权限和登陆用户,以及更多),并且它是用能使这些和其他的常见网页服务端操作变简单的网页框架来构建的。</p>
+
+<p>知道一些关于编程概念(或者关于一个特定的编程语言)的基础知识会很实用,但不是必要的。类似的,精通客户端编程也不是必修的,但一些基本知识会帮助你和创建你的客户端的 “前端” 开发者更融洽地工作。</p>
+
+<p>你会需要去理解 ”网页是如何工作的“。我们推荐你先去阅读以下主题:</p>
+
+<ul>
+ <li>什么是一个网页服务器 [<a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">What is a web server</a>]</li>
+ <li>我需要什么软件去构建一个网页? [<a href="/zh-CN/docs/Learn/Common_questions/What_software_do_I_need">What software do I need to build a website?</a>]</li>
+ <li>你怎样上传文件到一个网页服务器? [<a href="/zh-CN/docs/Learn/Common_questions/Upload_files_to_a_web_server">How do you upload files to a web server?</a>]</li>
+</ul>
+
+<p>拥有这些基础理解,你会做好完成在这节中的模块的准备。 </p>
+
+<h2 id="模块">模块</h2>
+
+<p>这个主题包含了以下的模块。你应该从第一个模块开始,然后接着到后面的任一模块,后面的模块演示了如何使用两个应用了合适的网页框架的非常流行的服务器端语言。</p>
+
+<dl>
+ <dt>服务器端编程的第一步 [<a href="/zh-CN/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a>]</dt>
+ <dd>这个模块提供了关于服务器端网页编程的服务器技术无关的信息 [server-technology-agnostic information],包括了关于服务器端编程的根本问题的答案——”它是什么“,”它跟客户端编程的区别“,和 ”为什么它很实用“——以及关于一些流行的服务器端框架的概述和如何为你的网站选择最合适的框架的指南。最后我们提供了一个关于网页服务器安全的介绍性部分。</dd>
+ <dt>Django 网页框架 [<a href="/zh-CN/docs/Learn/Server-side/Django">Django Web Framework (Python)</a>]</dt>
+ <dd>Django 是一个非常流行以及功能齐全的服务器端网页框架,它是用 Python 编写的。这个模块讲解了为什么 Django 是一个这么好的网页服务器框架,如何设立一个开发环境以及如何使用它来执行常见的任务。</dd>
+ <dt>Express 网页框架 [<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs">Express Web Framework (Node.js/JavaScript)</a>]</dt>
+ <dd>Express 是用 JavaScript 编写并在 node.js 运行时环境中托管的一个流行的网页框架。这个模块讲解了这个框架的一些主要优点,如何设立你的开发环境以及如何执行常见的网页开发和部署的任务。</dd>
+</dl>