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
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
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/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/ reset/done/ [name='password_reset_complete']</pre>
</div>
<p>现在尝试打开登录 URL(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>)。这将再次失败,但有一个错误告诉您,我们在模板搜索路径上缺少必需的模板(<strong>registration/login.html</strong>)。您将在顶部的黄色部分中,看到以下文字:</p>
<pre class="brush: python">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 %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<div>
<td>\{{ form.username.label_tag }}</td>
<td>\{{ form.username }}</td>
</div>
<div>
<td>\{{ form.password.label_tag }}</td>
<td>\{{ form.password }}</td>
</div>
<div>
<input type="submit" value="login" />
<input type="hidden" name="next" value="\{{ next }}" />
</div>
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}</pre>
<p>此模板与我们之前看到的模板,有一些相似之处 - 它扩展了我们的基本模板,并覆盖了内容区块 <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 %}
<p>Logged out!</p>
<a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}</pre>
<p>这个模板非常简单。它只显示一条消息,通知您已登出,并提供一个链接,您可以点击此按钮,返回登录屏幕。如果再次回到登出 URL,您应该看到此页面:</p>
<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p>
<h3 id="密码重置模板">密码重置模板</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 %}
<form action="" method="post">{% csrf_token %}
{% if form.email.errors %} \{{ form.email.errors }} {% endif %}
<p>\{{ form.email }}</p>
<input type="submit" class="btn btn-default btn-lg" value="Reset password" />
</form>
{% endblock %}
</pre>
<h4 id="密码重置完成">密码重置完成</h4>
<p>收集您的电子邮件地址后,会显示此表单。创建 <strong>/locallibrary/templates/registration/password_reset_done.html</strong>,并为其提供以下内容:</p>
<pre class="brush: html">{% extends "base_generic.html" %}
{% block content %}
<p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
{% endblock %}
</pre>
<h4 id="密码重置电子邮件">密码重置电子邮件</h4>
<p>此模板提供 HTML 电子邮件的文本,其中包含我们将发送给用户的重置链接。创建 <strong>/locallibrary/templates/registration/password_reset_email.html</strong>,并为其提供以下内容:</p>
<pre class="brush: html">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 %}
<p>Please enter (and confirm) your new password.</p>
<form action="" method="post">
<div style="display:none">
<input type="hidden" value="\{{ csrf_token }}" name="csrfmiddlewaretoken">
</div>
<table>
<tr>
<td>\{{ form.new_password1.errors }}
<label for="id_new_password1">New password:</label></td>
<td>\{{ form.new_password1 }}</td>
</tr>
<tr>
<td>\{{ form.new_password2.errors }}
<label for="id_new_password2">Confirm password:</label></td>
<td>\{{ form.new_password2 }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Change my password" /></td>
</tr>
</table>
</form>
{% else %}
<h1>Password reset failed</h1>
<p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
{% endif %}
{% endblock %}
</pre>
<h4 id="密码重置完成_2">密码重置完成</h4>
<p>这是最后一个密码重置模板,显示该模板,以在密码重置成功时通知您。创建 <strong>/locallibrary/templates/registration/password_reset_complete.html</strong>,并为其提供以下内容:</p>
<pre class="brush: html">{% extends "base_generic.html" %}
{% block content %}
<h1>The password has been changed!</h1>
<p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}</pre>
<h3 id="测试新的身份验证页面">测试新的身份验证页面</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"> <ul class="sidebar-nav">
...
<strong>{% if user.is_authenticated %}</strong>
<li>User: <strong>\{{ user.get_username }}</strong></li>
<li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
<strong>{% else %}</strong>
<li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
<strong>{% endif %} </strong>
</ul></pre>
<p>如您所见,我们使用 <code>if</code>-<code>else</code>-<code>endif</code>模板标签,根据 <code>\{{ user.is_authenticated }}</code> 是否为 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() > 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 %}
<h1>Borrowed books</h1>
{% if bookinstance_list %}
<ul>
{% for bookinst in bookinstance_list %}
<li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
<a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books borrowed.</p>
{% endif %}
{% endblock %}</pre>
<p>此模板与我们之前为 <code>Book </code>和 <code>Author</code>对象创建的模板非常相似。这里唯一新的东西,是我们检查在模型中添加的方法(<code>bookinst.is_overdue</code>),并使用它,来更改过期项目的颜色。</p>
<p>当开发服务器运行时,您现在应该能够在浏览器中,查看登录用户的列表,网址为<a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a>。在您的用户登录并登出后,尝试此操作(在第二种情况下,您应该被重定向到登录页面)。</p>
<p> </p>
<h3 id="将列表添加到侧栏">将列表添加到侧栏</h3>
<p>最后一步,是将这个新页面的链接,添加到侧边栏中。我们将把它放在我们为登录用户显示其他信息的同一部分。</p>
<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>),并将粗体标识的那一行,添加到侧边栏区块,如下所示。</p>
<pre class="brush: python"> <ul class="sidebar-nav">
{% if user.is_authenticated %}
<li>User: \{{ user.get_username }}</li>
<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong>
<li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
{% else %}
<li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
{% endif %}
</ul>
</pre>
<h3 id="它看起来是什么样子的?">它看起来是什么样子的?</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> %}
<!-- We can mark a BookInstance as returned. -->
<!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
</pre>
<h3 id="视图">视图</h3>
<p>在功能视图中,可以使用 <code>permission_required</code>装饰器,或在基于类别的视图中,使用 <code>PermissionRequiredMixin</code>测试权限。模式和行为与登录身份验证相同,但当然您可能需要添加多个权限。</p>
<p>功能视图装饰器:</p>
<pre class="brush: python">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">介绍从基于类别的视图 > 到使用装饰器的基于类别的视图</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>
|