1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
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><form>...</form></code> 标记内的元素集合,包含至少一个<code>type="submit"</code>的<code>input</code> 输入元素。</p>
<pre class="brush: html"><form action="/team_name_url/" method="post">
<label for="team_name">Enter name: </label>
<input id="team_name" type="text" name="name_field" value="Default name for team.">
<input type="submit" value="OK">
</form></pre>
<p>虽然在这里,我们只有一个文本字段,用于输入团队名称,但表单可能包含任意数量的其他输入元素,及其相关标签。字段的<code>type</code> 属性,定义将显示哪种窗口小部件。该字段的名称<code>name</code> 和 <code>id</code>,用于标识 JavaScript / CSS / HTML中的字段,而<code>value</code> 定义字段首次显示时的初始值。匹配团队标签使用<code style="font-style: normal; font-weight: normal;">label</code> 标签指定(请参阅上面的“输入名称” Enter name),其中<code style="font-style: normal; font-weight: normal;">for</code> 字段包含相关<code style="font-style: normal; font-weight: normal;">input</code>输入的<code style="font-style: normal; font-weight: normal;">id</code> 值。</p>
<p>提交输入<code>submit</code> 将显示为一个按钮(默认情况下),用户可以按下该按钮,将表单中所有其他输入元素中的数据,上传到服务器(在本例中,只有<code>team_name</code>)。表单属性定义用于发送数据的 HTTP <code>method</code> 方法,和服务器上数据的目标(<code>action</code>): </p>
<ul>
<li><code>action</code>: 提交表单时,要发送数据以进行处理的资源 /URL。如果未设置(或设置为空字符串),则表单将提交回当前页面 URL。</li>
<li><code>method</code>: 用于发送数据的HTTP方法: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><fieldname></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 < datetime.date.today():
raise ValidationError(_('Invalid date - renewal in past'))
#Check date is in range librarian allowed to change (+4 weeks).
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
# Remember to always return the cleaned data.
return data</strong></pre>
<p>有两件重要的事情需要注意。首先,我们使用<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/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'),
]</pre>
<p>URL 配置会将格式为 <strong>/catalog/book/<em><bookinstance id></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 %}
<h1>Renew: \{{bookinst.book.title}}</h1>
<p>Borrower: \{{bookinst.borrower}}</p>
<p{% if bookinst.is_overdue %} class="text-danger"{% endif %}>Due date: \{{bookinst.due_back}}</p>
<strong> <form action="" method="post">
{% csrf_token %}
<table>
\{{ form }}
</table>
<input type="submit" value="Submit" />
</form></strong>
{% endblock %}</pre>
<p>这里大部分内容,和以前的教程都是完全类似的。我们扩展基本模板,然后重新定义内容块。我们能够引用 <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"><tr>
<th><label for="id_renewal_date">Renewal date:</label></th>
<td>
<input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required />
<br />
<span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
</td>
</tr>
</pre>
<div class="note">
<p><strong>注意:</strong> 它可能并不明显,因为我们只有一个字段,但默认情况下,每个字段都在其自己的表格行中定义(这就是变量在上面的<code>table </code>表格标记内部的原因)。如果您引用模板变量<code>\{{ form.as_table }}</code>,会提供相同的渲染。</p>
</div>
<p>如果您输入无效日期,您还会获得页面中呈现的错误列表(下面以<strong>粗体</strong>显示)。</p>
<pre class="brush: html"><tr>
<th><label for="id_renewal_date">Renewal date:</label></th>
<td>
<strong> <ul class="errorlist">
<li>Invalid date - renewal in past</li>
</ul></strong>
<input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required />
<br />
<span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
</td>
</tr></pre>
<h4 id="使用表单模板变量的其他方法">使用表单模板变量的其他方法</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">使用表单>手动呈现字段</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 %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% 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/<bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em><bookinstance_id></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 < datetime.date.today():
raise ValidationError(_('Invalid date - renewal in past'))
#Check date is in range librarian allowed to change (+4 weeks)
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
# Remember to always return the cleaned data.
return data
</strong>
class Meta:
model = BookInstance
fields = ['due_back',]
labels = { 'due_back': _('Renewal date'), }
help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), }
</pre>
<p>下面的 <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 %}
<form action="" method="post">
{% csrf_token %}
<table>
\{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{% 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 %}
<h1>Delete Author</h1>
<p>Are you sure you want to delete the author: \{{ author }}?</p>
<form action="" method="POST">
{% csrf_token %}
<input type="submit" action="" value="Yes, delete." />
</form>
{% endblock %}
</pre>
<h3 id="URL配置">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/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author_update'),
path('author/<int:pk>/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 > 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>
|