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
|
---
title: 'Django Tutorial Part 8: User authentication and permissions'
slug: Learn/Server-side/Django/Authentication
translation_of: Learn/Server-side/Django/Authentication
---
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</div>
<p class="summary">在本教程中,我們將會展示如何允許用戶使用自己的帳戶登入到您的網站,以及如何根據用戶是否已登入和權限的不同來控制他們可以執行和查看的內容。作為展示的一部分,我們會擴展 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 網站,添加登入頁面和登出頁面,以及用來查看已借閱的圖書的頁面 - 分為用戶與員工兩種不同頁面。</p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">前提:</th>
<td>完成至 <a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 線上教學 7: 會話(Sessions)框架</a>為止的所有主題。</td>
</tr>
<tr>
<th scope="row">目標:</th>
<td>了解如何設定與運用使用者驗證與權限機制。</td>
</tr>
</tbody>
</table>
<h2 id="大綱">大綱</h2>
<p>Django提供認證和授權(“ permission”)系統,該系統建立在<a href="/zh-TW/docs/Learn/Server-side/Django/Sessions">上一教程</a>中討論的會話框架的基礎上。透過它可以驗證用戶憑證並定義個別用戶能夠執行的操作。 該框架包括用於<code>Users</code> 和<code>Groups</code> 的內置模型(一般常用來一次性套用權限於一群用戶上的方式),用於指定用戶是否可以執行任務的權限/旗標,用於登入用戶的表單和視圖,以及 查看用於限制內容的工具。</p>
<div class="note">
<p><strong>注意</strong>: 從Django角度而言,身份驗證系統需要做到非常通用,因此不提供其他網頁身份驗證系統中提供的某些功能。 需要解決一些常見問題的話可以透過第三方軟件包。 例如,限制登錄嘗試和透過第三方進行身份驗證(例如OAuth)。</p>
</div>
<p>在本教程中,我們將會展示如何在<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>網站中啟用用戶身份驗證,並建立自己的登入和登出頁面,為模型添加權限以及控制對頁面的訪問。 我們將根據身份驗證/權限顯示為用戶或是圖書館員設計的已借出書籍列表。</p>
<p>身份驗證系統非常有彈性,您可以根據需要從頭開始構建URL,表單,視圖和模板,只透過提供的API來登入用戶。 但是,在本文中,我們將為登入與登出頁面使用Django的“ stock”身份驗證視圖和表單。 我們仍然需要建立一些模板,但這很簡單。</p>
<p>我們還將向您展示如何建立權限,並在視圖和模板中檢查登入狀態和權限。</p>
<h2 id="Enabling_authentication">Enabling authentication</h2>
<p>當我們<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/skeleton_website">創建框架網站</a>時(在教程2中),身份驗證已自動啟用,因此您此時無需執行任何其他操作。</p>
<div class="note">
<p><strong>注意</strong>: 當我們使用<code>django-admin startproject</code>命令創建應用程序時,所有必要的配置都為我們完成了。 用戶和模型權限的數據庫表是在我們首次調用<code>python manage.py migrate</code>時創建的。</p>
</div>
<p>該配置是在項目文件(<strong>locallibrary/locallibrary/settings.py</strong>)的<code>INSTALLED_APPS</code> 和<code>MIDDLEWARE</code> 部分中設置的,如下所示:</p>
<pre class="brush: python notranslate">INSTALLED_APPS = [
...
<strong> 'django.contrib.auth', </strong>#Core authentication framework and its default models.
<strong> 'django.contrib.contenttypes', #</strong>Django content type system (allows permissions to be associated with models).
....
MIDDLEWARE = [
...
<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> #Manages sessions across requests
...
<strong> 'django.contrib.auth.middleware.AuthenticationMiddleware',</strong> #Associates users with requests using sessions.
....
</pre>
<h2 id="Creating_users_and_groups">Creating users and groups</h2>
<p>當我們在教程4中查看<a href="https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Admin_site">Django管理站</a>點時,您已經創建了第一個用戶(這是一個超級用戶,使用命令p<code>python manage.py createsuperuser</code>創建)。 我們的超級用戶已經通過身份驗證,並且具有所有權限,因此我們需要創建一個測試用戶來代表普通站點用戶。 我們將使用管理站點來創建本地圖書館組和網站登錄名,因為這是最快的方法之一。</p>
<div class="note">
<p><strong>注意</strong>: 您還可以通過編程方式創建用戶,如下所示。 例如,如果要開發一個界面以允許用戶創建自己的登錄名,則必須這樣做(您不應授予用戶訪問管理站點的權限)。</p>
<pre class="brush: python notranslate">from django.contrib.auth.models import User
# Create user and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')
# Update fields and then save again
user.first_name = 'John'
user.last_name = 'Citizen'
user.save()
</pre>
</div>
<p>在下面,我們將首先創建一個組,然後創建一個用戶。 即使我們還沒有添加庫成員的任何權限,但是如果以後需要添加,將它們一次添加到組中要比分別添加到每個成員要容易得多。</p>
<p>啟動開發服務器,然後在本地Web瀏覽器(<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>)中導航到管理站點。 使用您的超級用戶帳戶的憑據登錄到該站點。 管理站點的頂層顯示所有模型,按“ django應用程序”排序。 在“<strong>Authentication and Authorisation</strong>”部分,您可以單擊<strong>Users</strong> 或<strong>Groups</strong>鏈接以查看其現有記錄。</p>
<p><img alt="Admin site - add groups or users" src="https://mdn.mozillademos.org/files/14091/admin_authentication_add.png" style="border-style: solid; border-width: 1px; display: block; height: 364px; margin: 0px auto; width: 661px;"></p>
<p>首先,讓我們為圖書館成員創建一個新組。</p>
<ol>
<li>單擊<strong>Add</strong>按鈕(在組旁邊)以創建一個新組; 輸入該組的名稱“Library Members”。<br>
<img alt="Admin site - add group" src="https://mdn.mozillademos.org/files/14093/admin_authentication_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 561px; margin: 0px auto; width: 800px;"></li>
<li>我們不需要該組的任何權限,因此只需按<strong>SAVE</strong> (您將被帶到組列表)。</li>
</ol>
<p>現在讓我們創建一個用戶:</p>
<ol>
<li>導航回到管理站點的主頁</li>
<li>單擊“用戶”旁邊的“添加”按鈕以打開“添加用戶”對話框。<br>
<img alt="Admin site - add user pt1" src="https://mdn.mozillademos.org/files/14095/admin_authentication_add_user_prt1.png" style="border-style: solid; border-width: 1px; display: block; height: 409px; margin: 0px auto; width: 800px;"></li>
<li>輸入適合您的測試用戶的用戶名和密碼/密碼確認</li>
<li>按<strong>SAVE</strong>創建用戶。<br>
管理站點將創建新用戶,並立即將您帶到“更改用戶”視窗,您可以在其中更改用戶名並為用戶模型的可選字段添加信息。 這些字段包括名字,姓氏,電子郵件地址,用戶狀態和權限(僅應設置“活動”標誌)。 在更下方的位置,您可以指定用戶的組和權限,並查看與該用戶相關的重要日期(例如,他們的加入日期和上次登錄日期)。<br>
<img alt="Admin site - add user pt2" src="https://mdn.mozillademos.org/files/14097/admin_authentication_add_user_prt2.png" style="border-style: solid; border-width: 1px; display: block; height: 635px; margin: 0px auto; width: 800px;"></li>
<li>在“組”部分中,從“可用組”列表中選擇“<strong>Library Member</strong>”組,然後按框之間的右箭頭將其移至“選擇的組”框中。<img alt="Admin site - add user to group" src="https://mdn.mozillademos.org/files/14099/admin_authentication_user_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 414px; margin: 0px auto; width: 933px;"></li>
<li>我們在這裡不需要執行任何其他操作,因此只需再次選擇<strong>SAVE</strong> 即可進入用戶列表。</li>
</ol>
<p>就是這樣而已! 現在,您將擁有一個“普通庫成員”帳戶,您將可以使用該帳戶進行測試(一旦我們實現了頁面以使其能夠登錄)。</p>
<div class="note">
<p><strong>注意</strong>:您應該嘗試創建另一個庫成員用戶。 另外,為圖書館員創建一個組,並為其添加用戶!</p>
</div>
<h2 id="Setting_up_your_authentication_views">Setting up your authentication views</h2>
<p>Django提供了創建身份驗證頁面所需的幾乎所有內容,以處理“開箱即用”的登錄,註銷和密碼管理。 這包括URL映射器,視圖和表單,但不包括模板-我們必須創建自己的模板!</p>
<p>在本節中,我們顯示如何將默認系統集成到LocalLibrary網站中並創建模板。 我們將它們放在主項目URL中。</p>
<div class="note">
<p><strong>注意</strong>: 您不必使用任何代碼,但是您可能想要使用它,因為它使事情變得容易得多。 如果您更改用戶模型(一個高級主題!),幾乎可以肯定需要更改表單處理代碼,但是即使如此,您仍然可以使用庫存視圖功能。</p>
</div>
<div class="note">
<p><strong>注意: </strong>在這種情況下,我們可以合理地將身份驗證頁面(包括URL和模板)放入目錄應用程序中。 但是,如果我們有多個應用程序,最好將這種共享的登錄行為分開,並使其在整個站點中都可用,這就是我們在此處顯示的內容!</p>
</div>
<h3 id="Project_URLs">Project URLs</h3>
<p>將以下內容添加到項目urls.py文件(<strong>locallibrary/locallibrary/urls.py</strong>)文件的底部:</p>
<pre class="brush: python notranslate">#Add Django site authentication urls (for login, logout, password management)
urlpatterns += [
path('accounts/', include('django.contrib.auth.urls')),
]
</pre>
<p>導航到<a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a> URL(注意尾隨斜杠!),然後Django將顯示一個錯誤,指出找不到此URL,並列出了它嘗試的所有URL。 從中您可以看到將起作用的URL,例如:</p>
<div class="note">
<p><strong>注意: </strong>使用上述方法會在方括號中添加以下網址,這些網址可用於反轉網址映射。 您無需執行其他任何操作-上面的url映射會自動映射以下提到的URL。</p>
</div>
<div class="note">
<pre class="brush: python notranslate">accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password_change']
accounts/ password_change/done/ [name='password_change_done']
accounts/ password_reset/ [name='password_reset']
accounts/ password_reset/done/ [name='password_reset_done']
accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/ reset/done/ [name='password_reset_complete']</pre>
</div>
<p>現在嘗試導航到登錄URL(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>)。 這將再次失敗,但是會顯示一條錯誤消息,告訴您我們在模板搜索路徑上缺少必需的模板(<strong>registration/login.html</strong>)。 您會在頂部黃色部分看到以下幾行:</p>
<pre class="brush: python notranslate">Exception Type: TemplateDoesNotExist
Exception Value: <strong>registration/login.html</strong></pre>
<p>下一步是在搜索路徑上創建註冊目錄,然後添加<strong>login.html</strong>文件。</p>
<h3 id="Template_directory">Template directory</h3>
<p>我們剛剛添加的url(和隱式視圖)期望在模板搜索路徑中某個目錄<strong>/registration/</strong> 中找到它們的關聯模板。</p>
<p>對於這個網站,我們將HTML頁面放在<strong>templates/registration/</strong>目錄中。 此目錄應位於您的項目根目錄中,即與<strong>catalog</strong> 和<strong>locallibrary</strong> 文件夾相同的目錄中)。 請立即創建這些文件夾。</p>
<div class="note">
<p><strong>Note:</strong> Your folder structure should now look like the below:<br>
locallibrary (django project folder)<br>
|_catalog<br>
|_locallibrary<br>
|_templates <strong>(new)</strong><br>
|_registration</p>
</div>
<p>為了使這些目錄對模板加載器可見(即將該目錄放置在模板搜索路徑中),請打開項目設置(<strong>/locallibrary/locallibrary/settings.py</strong>),並更新<code>TEMPLATES</code> 部分的<code>DIRS</code>行,如圖所示。</p>
<pre class="brush: python notranslate">TEMPLATES = [
{
...
<strong> 'DIRS': ['./templates',],</strong>
'APP_DIRS': True,
...
</pre>
<h3 id="Login_template">Login template</h3>
<div class="warning">
<p><strong>重要信息</strong>:本文提供的身份驗證模板是Django演示登錄模板的非常基本/稍作修改的版本。 您可能需要自定義它們以供自己使用!</p>
</div>
<p>創建一個名為/<strong>locallibrary/templates/registration/login.html</strong>的新HTML文件。 為其提供以下內容:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<div>
<td>\{{ form.username.label_tag }}</td>
<td>\{{ form.username }}</td>
</div>
<div>
<td>\{{ form.password.label_tag }}</td>
<td>\{{ form.password }}</td>
</div>
<div>
<input type="submit" value="login" />
<input type="hidden" name="next" value="\{{ next }}" />
</div>
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}</pre>
<p>該模板與我們之前看到的模板有一些相似之處-它擴展了我們的基本模板並覆蓋了內容塊。 其餘代碼是相當標準的表單處理代碼,我們將在以後的教程中進行討論。 現在您只需要知道的是,這將顯示一個表格,您可以在其中輸入用戶名和密碼,並且如果輸入無效的值,則在頁面刷新時會提示您輸入正確的值。</p>
<p>保存模板後,導航回到登錄頁面(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>),您應該看到類似以下內容:</p>
<p><img alt="Library login page v1" src="https://mdn.mozillademos.org/files/14101/library_login.png" style="border-style: solid; border-width: 1px; display: block; height: 173px; margin: 0px auto; width: 441px;"></p>
<p>如果嘗試登錄將成功,並且您將被重定向到另一個頁面(默認情況下為<a href="http://127.0.0.1:8000/accounts/profile/">http://127.0.0.1:8000/accounts/profile/</a>)。 這裡的問題是,默認情況下,Django期望登錄後將您帶到個人資料頁面,情況可能與否。 由於您尚未定義此頁面,因此會出現另一個錯誤!</p>
<p>打開項目設置(<strong>/locallibrary/locallibrary/settings.py</strong>) ,然後將下面的文本添加到底部。 現在,當您登錄時,默認情況下應將您重定向到網站主頁。</p>
<pre class="brush: python notranslate"># Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'
</pre>
<h3 id="Logout_template">Logout template</h3>
<p>如果您導航到登出URL (<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>) ,則會看到一些奇怪的行為-您的用戶將被確定地註銷,但是您將被帶到<strong>Admin</strong> 註銷頁面。 那不是您想要的,僅僅是因為該頁面上的登錄鏈接將您帶到<strong>Admin</strong> 登錄屏幕(並且僅對具有<code>is_staff</code> 權限的用戶可用)。</p>
<p>創建並打開 /<strong>locallibrary/templates/registration/logged_out.html</strong>。 複製以下文本:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
<p>Logged out!</p>
<a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}</pre>
<p>這個模板非常簡單。 它僅顯示一條消息,通知您已註銷,並提供一個鏈接,您可以按此鏈接返回登錄屏幕。 如果再次進入註銷URL,您應該看到以下頁面:</p>
<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p>
<h3 id="Password_reset_templates">Password reset templates</h3>
<p>默認的密碼重置系統使用電子郵件向用戶發送重置鏈接。 您需要創建表格以獲取用戶的電子郵件地址,發送電子郵件,允許他們輸入新密碼並在整個過程完成時註明。</p>
<p>以下模板可以用作起點。</p>
<h4 id="密碼重設表格">密碼重設表格</h4>
<p>這是用於獲取用戶電子郵件地址(用於發送密碼重置電子郵件)的表格。 創建<strong>/locallibrary/templates/registration/password_reset_form.html</strong>,並為其提供以下內容:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{% if form.email.errors %}
{{ form.email.errors }}
{% endif %}
<p>\{{ form.email }}</p>
<input type="submit" class="btn btn-default btn-lg" value="Reset password">
</form>
{% endblock %}
</pre>
<h4 id="密碼重置完成">密碼重置完成</h4>
<p>收集您的電子郵件地址後,將顯示此表單。創建 <strong>/locallibrary/templates/registration/password_reset_done.html</strong>,並為其提供以下內容:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
<p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
{% endblock %}
</pre>
<h4 id="密碼重置電子郵件">密碼重置電子郵件</h4>
<p>該模板提供了HTML電子郵件的文本,其中包含我們將發送給用戶的重置鏈接。 創建<strong>/locallibrary/templates/registration/password_reset_email.html</strong>,並為其提供以下內容:</p>
<pre class="brush: html notranslate">Someone asked for password reset for email \{{ email }}. Follow the link below:
\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
</pre>
<h4 id="密碼重置確認">密碼重置確認</h4>
<p>單擊密碼重置電子郵件中的鏈接後,即可在此頁面輸入新密碼。 創建 <strong>/locallibrary/templates/registration/password_reset_confirm.html</strong>,並為其提供以下內容:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
{% if validlink %}
<p>Please enter (and confirm) your new password.</p>
<form action="" method="post">
<div style="display:none">
<input type="hidden" value="\{{ csrf_token }}" name="csrfmiddlewaretoken">
</div>
<table>
<tr>
<td>\{{ form.new_password1.errors }}
<label for="id_new_password1">New password:</label></td>
<td>\{{ form.new_password1 }}</td>
</tr>
<tr>
<td>\{{ form.new_password2.errors }}
<label for="id_new_password2">Confirm password:</label></td>
<td>\{{ form.new_password2 }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Change my password" /></td>
</tr>
</table>
</form>
{% else %}
<h1>Password reset failed</h1>
<p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
{% endif %}
{% endblock %}
</pre>
<h4 id="密碼重置完成_2">密碼重置完成</h4>
<p>這是最後一個密碼重設模板,密碼重設成功後將顯示此模板以通知您。 創建<strong>/locallibrary/templates/registration/password_reset_complete.html</strong>,並為其提供以下內容:</p>
<pre class="brush: html notranslate">{% extends "base_generic.html" %}
{% block content %}
<h1>The password has been changed!</h1>
<p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}</pre>
<h3 id="Testing_the_new_authentication_pages">Testing the new authentication pages</h3>
<p>現在您已經添加了URL配置並創建了所有這些模板,身份驗證頁面現在應該可以正常工作了!</p>
<p>您可以通過嘗試使用以下URL登錄然後註銷超級用戶帳戶來測試新的身份驗證頁面:</p>
<ul>
<li><a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a></li>
<li><a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a></li>
</ul>
<p>您可以通過登錄頁面中的鏈接測試密碼重置功能。 <strong>請注意,Django只會將重置電子郵件發送到已經存儲在其數據庫中的地址(用戶)!</strong></p>
<div class="note">
<p><strong>筆記</strong>:密碼重設系統要求您的網站支持電子郵件,這不在本文的討論範圍之內,因此該部分尚無法使用。 要進行測試,請將以下行放在settings.py文件的末尾。 這將記錄發送到控制台的所有電子郵件(因此您可以從控制台複製密碼重置鏈接)。</p>
<pre class="brush: python notranslate">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
</pre>
<p>有關更多信息,請參閱發送電子郵件(<a href="https://docs.djangoproject.com/en/2.0/topics/email/">Sending email</a>Django文檔)。</p>
</div>
<h2 id="針對經過身份驗證的用戶進行測試">針對經過身份驗證的用戶進行測試</h2>
<p>本節介紹如何根據用戶是否登錄來有選擇地控制用戶看到的內容。</p>
<h3 id="在模板中測試">在模板中測試</h3>
<p>您可以使用 <code>\{{ user }}</code>模板變量在模板中獲取有關當前登錄用戶的信息(默認情況下,就像我們在框架中一樣設置項目時,該信息會添加到模板上下文中)。</p>
<p>通常,您將首先針對 <code>\{{ user.is_authenticated }}</code>模板變量進行測試,以確定該用戶是否有資格查看特定內容。 為了演示這一點,接下來,我們將更新邊欄,以在用戶註銷時顯示“登錄”鏈接,在用戶登錄時顯示“註銷”鏈接。</p>
<p>打開基礎模板。 (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) ,然後將以下文本複製到<code>sidebar</code> 塊中,緊接在<code>endblock</code> 模板標籤之前。</p>
<pre class="brush: html notranslate"> <ul class="sidebar-nav">
...
<strong>{% if user.is_authenticated %}</strong>
<li>User: <strong>\{{ user.get_username }}</strong></li>
<li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
<strong>{% else %}</strong>
<li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
<strong>{% endif %} </strong>
</ul></pre>
<p>如您所見,我們使用 <code>if</code>-<code>else</code>-<code>endif</code> 模板標籤根據 <code>\{{ user.is_authenticated }}</code> \ {{user.is_authenticated}}是否為真來有條件地顯示文本。 如果用戶通過了身份驗證,那麼我們知道我們有一個有效的用戶,因此我們調用 <strong>\{{ user.get_username }} </strong>來顯示其名稱。</p>
<p>我們使用<code>url</code> 模板標記和相應URL配置的名稱來創建登錄和註銷鏈接URL。 還要注意我們如何將<code>?next=\{{request.path}}</code>附加到URL的末尾。 這是在鏈接的URL的末尾添加一個URL參數,其中包含當前頁面的地址(URL)。 用戶成功登錄/註銷後,視圖將使用此``<code>next</code>''值將用戶重定向到他們首先單擊 login/logout 鏈接的頁面。</p>
<div class="note">
<p><strong>注意</strong>:試試看! 如果您在主頁上,然後單擊側欄中的“Login/Logout”,那麼在操作完成後,您應該回到同一頁面。</p>
</div>
<h3 id="在視圖中測試">在視圖中測試</h3>
<p>如果您使用的是基於函數的視圖,則限制訪問函數的最簡單方法是將<code>login_required</code> 裝飾器應用於視圖函數,如下所示。 如果用戶已登錄,則您的視圖代碼將正常執行。 如果用戶未登錄,它將重定向到項目設置(<code>settings.LOGIN_URL</code>)中定義的登錄URL,並將當前的絕對路徑作為<code>next</code> URL參數傳遞。 如果用戶成功登錄,則他們將返回此頁面,但這次已通過身份驗證。</p>
<pre class="brush: python notranslate">from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...</pre>
<div class="note">
<p><strong>注意:</strong> 您可以通過在<code>request.user.is_authenticated</code>上進行測試來手動執行相同的操作,但是裝飾器要方便得多!</p>
</div>
<p>同樣,在基於類的視圖中限制對登錄用戶的訪問權限的最簡單方法是從 <code>LoginRequiredMixin</code>. 派生。 您需要首先在父類列表中,在主視圖類之前聲明此混合。</p>
<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
...</pre>
<p>它具有與 <code>login_required</code> 裝飾器完全相同的重定向行為。 如果用戶未通過身份驗證,也可以指定其他位置來重定向用戶 (<code>login_url</code>),並使用URL參數名稱代替“ next”來插入當前的絕對路徑(<code>redirect_field_name</code>).。</p>
<pre class="brush: python notranslate">class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
</pre>
<p>有關更多詳細信息,請在此處查看<a href="https://docs.djangoproject.com/en/2.0/topics/auth/default/#limiting-access-to-logged-in-users">Django文檔</a>。</p>
<h2 id="範例—列出當前用戶的書籍">範例—列出當前用戶的書籍</h2>
<p>現在,我們知道瞭如何將頁面限制為特定用戶,讓我們創建當前用戶借閱的書籍的視圖。</p>
<p>不幸的是,我們還沒有任何方式讓用戶借書! 因此,在創建圖書清單之前,我們將首先擴展<code>BookInstance</code> 模型以支持借用的概念,並使用Django Admin應用程序將大量圖書借給我們的測試用戶。</p>
<h3 id="模型">模型</h3>
<p>首先,我們將必須使用戶可以藉用<code>BookInstance</code> (我們已經具有<code>status</code> 和<code>due_back</code> ,但是在該模型和User之間還沒有任何關聯。我們將創建 一個使用<code>ForeignKey</code> (一對多)字段的方法,我們還需要一種簡單的機制來測試借出的書是否過期。<br>
<br>
打開<strong>catalog/models.py</strong>,然後從 <code>django.contrib.auth.models</code>導入<code>User</code> 模型(將其添加到文件頂部的前一個導入行下面,因此<code>User</code> 可供使用它的後續代碼使用):</p>
<pre class="brush: python notranslate">from django.contrib.auth.models import User
</pre>
<p>Ne接下來,將<code>borrower</code> 字段添加到<code>BookInstance</code> 模型中:</p>
<pre class="notranslate">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
</pre>
<p>當我們在這裡時,讓我們添加一個屬性,我們可以從模板中調用該屬性,以告知特定的圖書實例是否過期。 儘管我們可以在模板本身中進行計算,但是使用如下所示的<a href="https://docs.python.org/3/library/functions.html#property">屬性</a>會更加高效。</p>
<p>將此添加到文件頂部附近:</p>
<pre class="brush: python notranslate">from datetime import date</pre>
<p class="brush: python">現在,在<code>BookInstance</code>類中添加以下屬性定義:</p>
<pre class="brush: python notranslate">@property
def is_overdue(self):
if self.due_back and date.today() > self.due_back:
return True
return False</pre>
<div class="note">
<p><strong>Note</strong>: 在進行比較之前,我們首先要驗證<code>due_back</code>是否為空。 空的 <code>due_back</code>字段將導致Django拋出錯誤而不是顯示頁面:空值不可比。 這不是我們希望用戶體驗的東西!</p>
</div>
<p>現在,我們已經更新了模型,我們需要在項目上進行新的遷移,然後應用這些遷移:</p>
<pre class="brush: bash notranslate">python3 manage.py makemigrations
python3 manage.py migrate
</pre>
<h3 id="Admin">Admin</h3>
<p>現在打開<strong>catalog/admin.py</strong>,然後將<code>list_display</code> 和<code>fieldsets</code> 中的<code>borrower</code> 字段添加到<code>BookInstanceAdmin</code> 類中,如下所示。 這將使該字段在“管理”部分中可見,以便我們可以在需要時將<code>User</code> 分配給<code>BookInstance</code> 。</p>
<pre class="brush: python notranslate">@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id')
list_filter = ('status', 'due_back')
fieldsets = (
(None, {
'fields': ('book','imprint', 'id')
}),
('Availability', {
'fields': ('status', 'due_back'<strong>,'borrower'</strong>)
}),
)</pre>
<h3 id="Loan_a_few_books">Loan a few books</h3>
<p>現在可以將書借給特定用戶了,然後借出許多<code>BookInstance</code> 記錄。 將他們的<code>borrowed</code> 字段設置為測試用戶,<code>status</code> 為“借用”,並設置將來和將來的到期日。</p>
<div class="note">
<p><strong>注意</strong>:我們不會詳細說明該過程,因為您已經知道如何使用管理網站!</p>
</div>
<h3 id="On_loan_view">On loan view</h3>
<p>現在,我們將添加一個視圖,以獲取已借給當前用戶的所有書籍的列表。 我們將使用我們熟悉的相同的通用的基於類的列表視圖,但是這次我們還將導入並從<code>LoginRequiredMixin</code>派生,以便只有登錄的用戶才能調用此視圖。 我們還將選擇聲明<code>template_name</code>,而不使用默認值,因為我們最終可能會擁有一些不同的BookInstance記錄列表,並具有不同的視圖和模板。<br>
<br>
將以下內容添加到<strong>catalog / views.py</strong>:</p>
<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin
class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
"""Generic class-based view listing books on loan to current user."""
model = BookInstance
template_name ='catalog/bookinstance_list_borrowed_user.html'
paginate_by = 10
def get_queryset(self):
return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre>
<p>為了將查詢限制為僅針對當前用戶的<code>BookInstance</code> 對象,我們重新實現了 <code>get_queryset()</code>,如上所示。 請注意,“ o”是“借出”的存儲代碼,我們在<code>due_back</code> 日期之前訂購,以便最先顯示最早的項目。</p>
<h3 id="URL_conf_for_on_loan_books">URL conf for on loan books</h3>
<p>現在打開<strong>/catalog/urls.py</strong>並添加指向上面視圖的<code>path()</code>(您可以將下面的文本複製到文件末尾)。</p>
<pre class="brush: python notranslate">urlpatterns += [
path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]</pre>
<h3 id="Template_for_on_loan_books">Template for on loan books</h3>
<p>現在,我們需要為此頁面添加模板。 首先,創建模板文件 <strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong> 並為其提供以下內容:</p>
<pre class="brush: python notranslate">{% extends "base_generic.html" %}
{% block content %}
<h1>Borrowed books</h1>
{% if bookinstance_list %}
<ul>
{% for bookinst in bookinstance_list %}
<li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
<a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books borrowed.</p>
{% endif %}
{% endblock %}</pre>
<p>該模板與我們先前為<code>Book</code> 和<code>Author</code> 物件創建的模板非常相似。 這裡唯一的“新內容”是我們檢查在模型中添加的方法(<code>bookinst.is_overdue</code>),並使用它來更改過期項目的顏色。</p>
<p>開發服務器運行時,現在應該可以在瀏覽器中的 <a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a> 上查看已登錄用戶的列表。 在您的用戶登錄和註銷後進行嘗試(在第二種情況下,應將您重定向到登錄頁面)。</p>
<h3 id="Add_the_list_to_the_sidebar">Add the list to the sidebar</h3>
<p>最後一步是將此新頁面的鏈接添加到側欄中。 我們將其放在同一部分中,在該部分中為登錄用戶顯示其他信息。</p>
<p>打開基本模板 (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) 並將粗體顯示的行添加到側邊欄中,如圖所示。</p>
<pre class="brush: python notranslate"> <ul class="sidebar-nav">
{% if user.is_authenticated %}
<li>User: \{{ user.get_username }}</li>
<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong>
<li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
{% else %}
<li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
{% endif %}
</ul>
</pre>
<h3 id="What_does_it_look_like">What does it look like?</h3>
<p>當任何用戶登錄後,他們將在邊欄中看到“<em>My Borrowed</em> ”,並且書的列表顯示如下(第一本書沒有截止日期,這是我們希望在以後的教程中解決的錯誤!) 。</p>
<p><img alt="Library - borrowed books by user" src="https://mdn.mozillademos.org/files/14105/library_borrowed_by_user.png" style="border-style: solid; border-width: 1px; display: block; height: 215px; margin: 0px auto; width: 530px;"></p>
<h2 id="Permissions">Permissions</h2>
<p>權限與模型相關聯,並定義了具有權限的用戶可以在模型實例上執行的操作。 默認情況下,Django會自動為所有模型賦予添加,更改和刪除權限,從而允許具有權限的用戶通過管理站點執行關聯的操作。 您可以定義自己的模型權限,並將其授予特定用戶。 您還可以更改與同一模型的不同實例關聯的權限。</p>
<p>這樣,對視圖和模板中的權限進行的測試就非常類似於對身份驗證狀態的測試(實際上,對權限的測試也對身份驗證進行了測試)。</p>
<h3 id="Models">Models</h3>
<p>使用<code>permissions</code> 字段在模型“<code>class Meta</code>”部分中完成權限的定義。 您可以在元組中根據需要指定任意數量的權限,每個權限本身都在嵌套的元組中定義,其中包含權限名稱和權限顯示值。 例如,我們可以定義一個權限,以允許用戶標記已退回一本書,如下所示:</p>
<pre class="brush: python notranslate">class BookInstance(models.Model):
...
class Meta:
...
<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre>
<p>然後,我們可以將權限分配給管理站點中的“圖書管理員”組。</p>
<p>打開<strong>catalog/models.py,</strong>然後添加權限,如上所示。 您將需要重新運行遷移(調用 <code>python3 manage.py makemigrations</code> 和<code>python3 manage.py migrate</code>)以適當地更新數據庫。</p>
<h3 id="模板">模板</h3>
<p>當前用戶的權限存儲在名為 <code>\{{ perms }}</code>. 的模板變量中。 您可以使用關聯的Django "app"“應用”中的特定變量名稱來檢查當前用戶是否具有特定權限,例如 如果用戶具有此權限,則 <code>\{{ perms.catalog.can_mark_returned }}</code> 將為 <code>True</code> ,否則為<code>False</code>。 我們通常使用模板 <code>{% if %}</code> 標籤測試權限,如下所示:</p>
<pre class="brush: python notranslate">{% if perms.catalog.<code>can_mark_returned</code> %}
<!-- We can mark a BookInstance as returned. -->
<!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
</pre>
<h3 id="視圖">視圖</h3>
<p>可以在功能視圖中使用<code>permission_required</code> 裝飾器來測試權限,或者在基於類的視圖中使用<code>PermissionRequiredMixin</code>. 來測試權限。 模式和行為與登錄身份驗證的模式和行為相同,儘管當然您可能必須合理地添加多個權限。</p>
<p>視圖裝飾器函數:</p>
<pre class="brush: python notranslate">from django.contrib.auth.decorators import permission_required
@permission_required('catalog.<code>can_mark_returned</code>')
@permission_required('catalog.<code>can_edit</code>')
def my_view(request):
...</pre>
<p>基於類的視圖需要權限的混合。</p>
<pre class="brush: python notranslate">from django.contrib.auth.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, View):
permission_required = 'catalog.<code>can_mark_returned</code>'
# Or multiple permissions
permission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit')
# Note that 'catalog.can_edit' is just an example
# the catalog application doesn't have such permission!</pre>
<h3 id="範例">範例</h3>
<p>我們不會在這裡更新LocalLibrary; 也許在下一個教程中!</p>
<h2 id="挑戰自己">挑戰自己</h2>
<p>在本文的前面,我們向您展示瞭如何為當前用戶創建一個頁面,列出他們所借用的書。 現在的挑戰是創建一個僅對圖書館員可見的相似頁面,該頁面顯示所有已借書的書,其中包括每個借書人的名字。</p>
<p>您應該能夠遵循與其他視圖相同的模式。 主要區別在於您只需要將視圖限制為圖書館員即可。 您可以根據用戶是否是工作人員來執行此操作(函數裝飾器:<code>staff_member_required</code>,模板變量: <code>user.is_staff</code>),但是我們建議您改用<code>can_mark_returned</code> 權限和<code>PermissionRequiredMixin</code>,如上一節所述。</p>
<div class="warning">
<p><strong>重要</strong>:請記住不要將您的超級用戶用於基於權限的測試(即使尚未定義權限,權限檢查也始終對超級用戶返回true!)。 而是創建一個圖書管理員用戶,並添加所需的功能。</p>
</div>
<p>完成後,您的頁面應類似於以下屏幕截圖。<img alt="All borrowed books, restricted to librarian" src="https://mdn.mozillademos.org/files/14115/library_borrowed_all.png" style="border-style: solid; border-width: 1px; display: block; height: 283px; margin: 0px auto; width: 500px;"></p>
<ul>
</ul>
<h2 id="總結">總結</h2>
<p>出色的工作-您現在已經創建了一個網站,圖書館成員可以登錄並查看他們自己的內容,館員(具有正確的權限)可以用來查看所有借出的書及其借書人。 目前,我們仍在查看內容,但是當您要開始修改和添加數據時,將使用相同的原理和技術。</p>
<p>在下一篇文章中,我們將研究如何使用Django表單來收集用戶輸入,然後開始修改一些存儲的數據。</p>
<h2 id="也可以看看">也可以看看</h2>
<ul>
<li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/">User authentication in Django</a> (Django docs)</li>
<li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/default//">Using the (default) Django authentication system</a> (Django docs)</li>
<li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views">Introduction to class-based views > Decorating class-based views</a> (Django docs)</li>
</ul>
<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p>
<h2 id="In_this_module">In this module</h2>
<ul>
<li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li>
<li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li>
</ul>
|