在本教程中,我們將向您展示如何允許用戶使用自己的帳戶登錄到您的網(wǎng)站,以及如何根據(jù)用戶是否已登錄及其權(quán)限來控制他們可以執(zhí)行和查看的內(nèi)容。作為演示的一部分,我們將擴(kuò)展LocalLibrary網(wǎng)站,添加登錄頁面和注銷頁面,以及用戶和員工特定的頁面以查看已借閱的圖書。
Django 提供了一個(gè)身份驗(yàn)證和授權(quán)(“權(quán)限”)系統(tǒng),該系統(tǒng)構(gòu)建在上一個(gè)教程中討論的會話框架之上,允許您驗(yàn)證用戶憑據(jù),并定義每個(gè)用戶可允許執(zhí)行的操作。該框架包括用戶Users 和分組Groups 的內(nèi)置模型(一次向多個(gè)用戶應(yīng)用權(quán)限的通用方法),用于登錄用戶的權(quán)限/標(biāo)志,以指定用戶是否可以執(zhí)行任務(wù),表單和視圖,以及查看限制內(nèi)容的工具。
注意: Django身份驗(yàn)證系統(tǒng)的目標(biāo)非常通用,因此不提供其他Web身份驗(yàn)證系統(tǒng)中,所提供的某些功能。某些常見問題的解決方案,可作為第三方軟件包提供。例如,限制登錄嘗試,和針對第三方的身份驗(yàn)證(例如 OAuth)。
在本教程中,我們將向您展示,如何在LocalLibrary網(wǎng)站中,啟用用戶身份驗(yàn)證,創(chuàng)建您自己的登錄和注銷頁面,為模型添加權(quán)限,以及控制對頁面的訪問。我們將使用身份驗(yàn)證/權(quán)限,來顯示用戶和圖書館員借用圖書的列表。
身份驗(yàn)證系統(tǒng)非常靈活,您可以根據(jù)需要,從頭開始構(gòu)建 URLs,表單,視圖和模板,只需調(diào)用提供的API,即可登錄用戶。但是,在本文中,我們將在登錄和注銷頁面,使用 Django 的“庫存” 身份驗(yàn)證視圖和表單。我們?nèi)匀恍枰獎(jiǎng)?chuàng)建一些模板,但這很簡單。
我們還將向您展示如何創(chuàng)建權(quán)限,以及檢查視圖和模板中的登錄狀態(tài)和權(quán)限。
我們在創(chuàng)建框架網(wǎng)站時(shí)(在教程2中),自動(dòng)啟用了身份驗(yàn)證,因此您此時(shí),無需再執(zhí)行任何操作。
注意: 當(dāng)我們使用 django-admin startproject 命令,以創(chuàng)建應(yīng)用程序時(shí),所有必要的配置都已完成。當(dāng)我們第一次調(diào)用 python manage.py migrate 時(shí),創(chuàng)建了用戶和模型權(quán)限的數(shù)據(jù)庫表。
配置在項(xiàng)目文件(locallibrary/locallibrary/settings.py)的INSTALLED_APPS 和MIDDLEWARE 部分中設(shè)置,如下所示:
INSTALLED_APPS = [
...
'django.contrib.auth', #Core authentication framework and its default models.
'django.contrib.contenttypes', #Django content type system (allows permissions to be associated with models).
....
MIDDLEWARE = [
...
'django.contrib.sessions.middleware.SessionMiddleware', #Manages sessions across requests
...
'django.contrib.auth.middleware.AuthenticationMiddleware', #Associates users with requests using sessions.
....
創(chuàng)建用戶和分組節(jié)
在教程 4 中,當(dāng)我們查看 Django 管理站點(diǎn)時(shí),您已經(jīng)創(chuàng)建了第一個(gè)用戶(這是一個(gè)超級用戶,使用命令 python manage.py createsuperuser 創(chuàng)建)。我們的超級用戶已經(jīng)過身份驗(yàn)證,并擁有所有權(quán)限,因此我們需要?jiǎng)?chuàng)建一個(gè)測試用戶,來代表普通網(wǎng)站用戶。我們將使用管理站點(diǎn),來創(chuàng)建我們的 locallibrary 組別和網(wǎng)站登錄,因?yàn)檫@是最快的方法之一。
注意: 您還可以用編程方式創(chuàng)建用戶,如下所示。您會必須這樣做,例如,如果要開發(fā)一個(gè)界面,能允許用戶創(chuàng)建自己的登錄(您不應(yīng)該授予用戶訪問管理站點(diǎn)的權(quán)限)。
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()
下面,我們首先創(chuàng)建一個(gè)分組,然后創(chuàng)建一個(gè)用戶。即使我們還沒有為我們的圖書館成員添加任何權(quán)限,如果我們以后需要,將它們添加到分組中,要比單獨(dú)添加到每個(gè)成員要容易得多。
啟動(dòng)開發(fā)服務(wù)器,并到本地 Web 瀏覽器中的管理站點(diǎn)(http://127.0.0.1:8000/admin/)。使用超級用戶帳戶的憑據(jù),登錄該站點(diǎn)。 Admin 站點(diǎn)的最上級顯示所有模型,按 “django application” 排序。在 “身份驗(yàn)證和授權(quán)” Authentication and Authorisation 部分 ,您可以單擊用戶 Users ,或分組 Groups 鏈接,以查看其現(xiàn)有記錄。

首先,我們?yōu)閳D書館成員,創(chuàng)建一個(gè)新的分組。
- 單擊“添加” Add 按鈕(“分組” Group 旁邊)以創(chuàng)建新的分組;在分組的名稱 Name ,輸入“Library Members”。

- 我們不需要該組的任何權(quán)限,因此只需按SAVE(您將進(jìn)入分組列表)。
現(xiàn)在讓我們創(chuàng)建一個(gè)用戶:
- 回到管理站點(diǎn)的主頁
- 單擊“用戶”旁邊的“添加”按鈕 Add,以打開“添加用戶”對話框。

- 為測試用戶輸入適當(dāng)?shù)挠脩裘?strong>Username)和密碼/密碼確認(rèn)(Password/Password confirmation )
- 按 SAVE 創(chuàng)建用戶。
管理站點(diǎn)將創(chuàng)建新用戶,并立即轉(zhuǎn)到 “更改用戶” 屏幕,您可以在其中更改用戶名(username),并添加用戶模型的可選字段的信息。這些字段包括名字,姓氏,電子郵件地址,用戶狀態(tài)和權(quán)限(僅應(yīng)設(shè)置活動(dòng)標(biāo)志 Active)。再往下,您可以指定用戶的分組和權(quán)限,并查看與用戶相關(guān)的重要日期(例如,他們的加入日期和上次登錄日期)。

- 在“分組”(Groups)部分中,從“可用分組”(Available groups)列表中,選擇“圖書館成員”分組 Library Member,然后點(diǎn)擊這些框之間的右箭頭,將其移動(dòng)到“選擇的分組”(Chosen groups)框中。

- 我們不需要在此處執(zhí)行任何其他操作,因此只需再次選擇 SAVE ,即可轉(zhuǎn)到用戶列表。
就是這樣!現(xiàn)在您有一個(gè) “普通的圖書館成員” 帳戶,您可以使用該帳戶進(jìn)行測試(一旦我們實(shí)現(xiàn)了頁面,使他們能夠登錄)。
注意: 您應(yīng)該嘗試創(chuàng)建另一個(gè)圖書館用戶。此外,為圖書館管理員創(chuàng)建一個(gè)分組,并添加一個(gè)用戶!
設(shè)置身份驗(yàn)證視圖節(jié)
Django 提供了創(chuàng)建身份驗(yàn)證頁面所需的幾乎所有功能,讓處理登錄,注銷和密碼管理等工作,都能 “開箱即用”。這些相關(guān)功能包括了 url 映射器,視圖和表單,但它不包括模板 - 我們必須創(chuàng)建自己的模板!
在本節(jié)中,我們將展示如何將默認(rèn)系統(tǒng),集成到 LocalLibrary 網(wǎng)站并創(chuàng)建模板。我們將它們放在主項(xiàng)目的 URL 當(dāng)中。
注意: 您不必一定要使用這些代碼,但您可能希望這樣做,因?yàn)樗故虑樽兊酶菀住H绻挠脩裟P停ǜ呒壷黝}?。鷰缀蹩隙ㄐ枰谋韱翁幚泶a。但即便如此,您仍然可以使用先前已經(jīng)有的視圖功能。
注意: 在這種情況下,我們可以合理地將認(rèn)證頁面(包括URL和模板)放在我們的目錄應(yīng)用程序中。但是,如果我們有多個(gè)應(yīng)用程序,最好將這個(gè)共享登錄行為分開,并讓它在整個(gè)站點(diǎn)上可用,這就是我們在這里展示的內(nèi)容!
項(xiàng)目網(wǎng)址節(jié)
將以下內(nèi)容,添加到項(xiàng)目 urls.py(locallibrary/locallibrary/urls.py)文件的底部:
# 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')),
]
打開 URL http://127.0.0.1:8000/accounts/ (注意前面的斜杠!),Django將顯示一個(gè)錯(cuò)誤,它無法找到此URL,并列出它嘗試過的所有URL。從中您可以看到可以使用的URL,例如:
注意: 使用上面的方法,添加以下帶有方括號中的名稱的 URL,可用于反轉(zhuǎn) URL 映射。您不必實(shí)現(xiàn)任何其他內(nèi)容 - 上面的 url 映射,會自動(dòng)映射下面提到的URL。
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']
現(xiàn)在嘗試打開登錄 URL(http://127.0.0.1:8000/accounts/login/)。這將再次失敗,但有一個(gè)錯(cuò)誤告訴您,我們在模板搜索路徑上缺少必需的模板(registration/login.html)。您將在頂部的黃色部分中,看到以下文字:
Exception Type: TemplateDoesNotExist
Exception Value: registration/login.html
下一步是在搜索路徑上創(chuàng)建注冊目錄,然后添加 login.html 文件。
我們希望在模板搜索路徑中的目錄 /registration/ 某處,找到剛剛添加的 url(以及隱式視圖)的關(guān)聯(lián)模板。
對于此站點(diǎn),我們將 HTML 頁面,放在 templates/registration/ 目錄中。此目錄應(yīng)該位于項(xiàng)目的根目錄中,即與 catalog 和 locallibrary 文件夾相同的目錄)。請立即創(chuàng)建這些文件夾。
注意: 您的文件夾結(jié)構(gòu),現(xiàn)在應(yīng)如下所示:
locallibrary (django project folder)
|_catalog
|_locallibrary
|_templates (new)
|_registration
要使這些目錄對模板加載器可見(即將此目錄放在模板搜索路徑中),請打開項(xiàng)目設(shè)置(/locallibrary/locallibrary/settings.py),并更新TEMPLATES 部分的 “DIRS ” 那一行,如下所示。
TEMPLATES = [
{
...
'DIRS': ['./templates',],
'APP_DIRS': True,
...
重要說明: 本文提供的身份驗(yàn)證模板,是 Django 演示登錄模板的基本/略微修改版本。您可能需要自定義它們,以供自己使用!
創(chuàng)建一個(gè)名為 /locallibrary/templates/registration/login.html 的新HTML文件。為它加入以下內(nèi)容:
{% 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 %}
此模板與我們之前看到的模板,有一些相似之處 - 它擴(kuò)展了我們的基本模板,并覆蓋了內(nèi)容區(qū)塊 content 。其余代碼,是相當(dāng)標(biāo)準(zhǔn)的表單處理代碼,我們將在后面的教程中討論。您現(xiàn)在需要知道的是,這將顯示一個(gè)表單,您可以在其中輸入您的用戶名和密碼,如果您輸入的值無效,則會在頁面刷新時(shí),提示您輸入正確的值。
保存模板后,回到登錄頁面(http://127.0.0.1:8000/accounts/login/),您應(yīng)該看到如下內(nèi)容:

如果您嘗試登錄,將會成功,并且您將被重定向到另一個(gè)頁面(默認(rèn)情況下,這將是 http://127.0.0.1:8000/accounts/profile/)。這里的問題是,默認(rèn)情況下,Django希望在登錄后,你可能會被帶到個(gè)人資料頁面,這可能是,也可能不是。由于您還沒有定義此頁面,您將收到另一個(gè)錯(cuò)誤!
打開項(xiàng)目設(shè)置(/locallibrary/locallibrary/settings.py),并將下面的文本添加到底部?,F(xiàn)在登錄時(shí),您應(yīng)該默認(rèn)重定向到站點(diǎn)主頁。
# Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'
如果您打開登出網(wǎng)址(http://127.0.0.1:8000/accounts/logout/),那么您會看到一些奇怪的行為 - 您所屬的用戶肯定會被登出,但您將被帶到管理員登出頁面。這不是您想要的,只是因?yàn)樵擁撁嫔系牡卿涙溄?,將您帶到管理員登錄屏幕(并且僅對具有is_staff 權(quán)限的用戶可用)。
創(chuàng)建并打開 /locallibrary/templates/registration/logged_out.html。將下面的文字,復(fù)制到文檔中:
{% extends "base_generic.html" %}
{% block content %}
<p>Logged out!</p>
<a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}
這個(gè)模板非常簡單。它只顯示一條消息,通知您已登出,并提供一個(gè)鏈接,您可以點(diǎn)擊此按鈕,返回登錄屏幕。如果再次回到登出 URL,您應(yīng)該看到此頁面:

默認(rèn)密碼重置系統(tǒng),使用電子郵件向用戶發(fā)送重置鏈接。您需要?jiǎng)?chuàng)建表單,以獲取用戶的電子郵件地址,發(fā)送電子郵件,允許他們輸入新密碼,以及記錄整個(gè)過程的完成時(shí)間。
以下模板可作為起點(diǎn)。
密碼重置表單
這是用于獲取用戶電子郵件地址的表單(用于發(fā)送密碼重置電子郵件)。創(chuàng)建 /locallibrary/templates/registration/password_reset_form.html,并為其提供以下內(nèi)容:
{% 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 %}
密碼重置完成
收集您的電子郵件地址后,會顯示此表單。創(chuàng)建 /locallibrary/templates/registration/password_reset_done.html,并為其提供以下內(nèi)容:
{% 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 %}
密碼重置電子郵件
此模板提供 HTML 電子郵件的文本,其中包含我們將發(fā)送給用戶的重置鏈接。創(chuàng)建 /locallibrary/templates/registration/password_reset_email.html,并為其提供以下內(nèi)容:
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
密碼重置確認(rèn)
點(diǎn)擊密碼重置電子郵件中的鏈接后,您可以在此頁面輸入新密碼。創(chuàng)建 /locallibrary/templates/registration/password_reset_confirm.html,并為其提供以下內(nèi)容:
{% 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 %}
密碼重置完成
這是最后一個(gè)密碼重置模板,顯示該模板,以在密碼重置成功時(shí)通知您。創(chuàng)建 /locallibrary/templates/registration/password_reset_complete.html,并為其提供以下內(nèi)容:
{% extends "base_generic.html" %}
{% block content %}
<h1>The password has been changed!</h1>
<p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}
測試新的身份驗(yàn)證頁面節(jié)
現(xiàn)在您已經(jīng)添加了 URL 配置,并創(chuàng)建了所有模板,現(xiàn)在認(rèn)證頁面應(yīng)該可以正常工作了!
您可以嘗試登錄,然后使用以下 URL 登出超級用戶帳戶,來測試新的身份驗(yàn)證頁面:
您將能夠從登錄頁面中的鏈接,測試密碼重置功能。請注意,Django只會向已存儲在其數(shù)據(jù)庫中的地址(用戶)發(fā)送重置電子郵件!
注意: 密碼重置系統(tǒng),要求您的網(wǎng)站支持電子郵件,這超出了本文的范圍,因此該部分將無法使用。要測試此功能,請將以下一行放在 settings.py 文件的末尾。這會記錄發(fā)送到命令行控制臺的所有電子郵件(因此您可以從命令行控制臺,復(fù)制密碼重置鏈接)。
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
有關(guān)更多信息,請參閱發(fā)送電子郵件(Django文檔)。
測試已驗(yàn)證身份的用戶節(jié)
本節(jié)介紹如何根據(jù)用戶是否登錄,來有選擇地控制用戶看到的內(nèi)容。
您可以使用{{ user }} 模板變量,以獲取有關(guān)模板中,當(dāng)前登錄用戶的信息(默認(rèn)情況下,在我們在骨架中設(shè)置項(xiàng)目時(shí),會將其添加到模板上下文中)。
通常,您將首先針對 {{ user.is_authenticated }} 模板變量進(jìn)行測試,以確定用戶是否有資格查看特定內(nèi)容。為了展示這一點(diǎn),接下來我們將更新側(cè)邊欄,以在用戶登出時(shí),顯示“登錄”鏈接,如果他們已登錄,則顯示“登出”鏈接。
打開基本模板(/locallibrary/catalog/templates/base_generic.html),并將以下文本,復(fù)制到側(cè)邊欄區(qū)塊sidebar 中,緊接在endblock 模板標(biāo)記之前。
<ul class="sidebar-nav">
...
{% if user.is_authenticated %}
<li>User: {{ user.get_username }}</li>
<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>
如您所見,我們使用 if -else -endif 模板標(biāo)簽,根據(jù) {{ user.is_authenticated }} 是否為 true ,來有條件地顯示文本。如果用戶已通過身份驗(yàn)證,那么我們知道,我們擁有有效用戶,因此我們會調(diào)用 {{ user.get_username }} ,來顯示其名稱。
我們使用 url 模板標(biāo)記,和相應(yīng) URL 配置的名稱,創(chuàng)建登錄和登出鏈接 URL。另外請注意,我們?nèi)绾螌?“?next={{request.path}} 附加到URL的末尾。這樣做,是將包含當(dāng)前頁面地址(URL)的URL參數(shù),添加到鏈接URL的末尾。用戶成功登錄/登出后,視圖將使用此“下一個(gè)”值,將用戶重定向,回到他們首次單擊登錄/登出鏈接的頁面。
注意: 試試吧!如果您在主頁上,并單擊側(cè)欄中的“登錄/登出”,在操作完成后,您應(yīng)該返回到同一頁面。
如果您正在使用基于函數(shù)的視圖,則限制訪問函數(shù)的最簡單方法,是將login_required 裝飾器,應(yīng)用于您的視圖函數(shù),如下所示。如果用戶已登錄,則您的視圖代碼將正常執(zhí)行。
如果用戶未登錄,則會重定向到項(xiàng)目設(shè)置 (settings.LOGIN_URL )中定義的登錄URL,并將當(dāng)前絕對路徑,作為URL參數(shù)("下一個(gè)"next )來傳遞。如果用戶成功登錄,則會返回到此頁面,但這次會進(jìn)行身份驗(yàn)證。
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...
注意: 您可以通過request.user.is_authenticated ,測試手動(dòng)執(zhí)行類似的操作,但裝飾器更方便!
同樣,在基于類別的視圖中,限制對登錄用戶的訪問的最簡單方法,是從LoginRequiredMixin 派生。您需要在主視圖類之前的超類列表中,首先聲明此 mixin。
from django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
...
這與login_required 裝飾器,具有完全相同的重定向行為。如果用戶未經(jīng)過身份驗(yàn)證(login_url ),還可以指定一個(gè)替代位置,以將用戶重定向到該位置,并使用URL參數(shù)名稱,而不是“next ”,來插入當(dāng)前絕對路徑(redirect_field_name )。
class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
有關(guān)其他詳細(xì)信息,請查看Django文檔。
示例 - 列出當(dāng)前用戶的書本節(jié)
現(xiàn)在我們知道,如何將頁面限制為特定用戶,讓我們?yōu)楫?dāng)前用戶借閱的書本,創(chuàng)建一個(gè)視圖。
不幸的是,我們還沒有辦法讓用戶借書!因此,在我們創(chuàng)建圖書清單之前,我們首先會擴(kuò)展BookInstance 模型,以支持借閱的概念,并使用Django Admin應(yīng)用程序,借給測試用戶一些書。
首先,我們必須讓用戶可以借用書本實(shí)例BookInstance (我們已經(jīng)擁有狀態(tài)status 和還書日期due_back ,但這個(gè)模型和用戶之間,沒有任何關(guān)聯(lián)。我們將使用ForeignKey (一對多)字段,來創(chuàng)建一個(gè)。我們還需要一個(gè)簡單的機(jī)制,來測試借出的書是否過期。
打開 catalog/models.py,然后從 django.contrib.auth.models 導(dǎo)入 User 模型(在文件頂部的上一個(gè)導(dǎo)入行的正下方添加它,好讓后續(xù)代碼可以使用 User ):
from django.contrib.auth.models import User
接下來將借用者字段borrower ,添加到BookInstance 模型:
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
當(dāng)我們在這里,讓我們添加一個(gè)屬性,我們可以從模板中調(diào)用它,來判斷特定的書本實(shí)例是否過期。雖然我們可以在模板本身中計(jì)算這一點(diǎn),但使用如下所示的屬性會更有效率。將其添加到文件的底部:
from datetime import date
@property
def is_overdue(self):
if self.due_back and date.today() > self.due_back:
return True
return False
注意: 在進(jìn)行比較之前,我們首先驗(yàn)證due_back 是否為空??盏?code>due_back字段,會導(dǎo)致Django拋出錯(cuò)誤,而不是顯示頁面:空值不具有可比性。這不是我們希望用戶體驗(yàn)到的東西!
現(xiàn)在我們已經(jīng)更新了模型,我們需要對項(xiàng)目進(jìn)行新的遷移,然后應(yīng)用這些遷移:
python3 manage.py makemigrations
python3 manage.py migrate
現(xiàn)在打開 catalog/admin.py,并將borrower 字段,添加到BookInstanceAdmin 類別中的list_display 和fieldsets ,如下所示。這將使該字段在Admin部分中可見,以便我們可以在需要時(shí)將User 分配給BookInstance 。
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
list_display = ('book', 'status', 'borrower', 'due_back', 'id')
list_filter = ('status', 'due_back')
fieldsets = (
(None, {
'fields': ('book','imprint', 'id')
}),
('Availability', {
'fields': ('status', 'due_back','borrower')
}),
)
現(xiàn)在可以將書本借給特定用戶,然后借出一些BookInstance 記錄。將他們的借用字段borrowed ,設(shè)置為您的測試用戶,將狀態(tài)status 設(shè)置為 “On loan”,并在將來和過去設(shè)置截止日期。
注意: 我們不會一步一步說明這個(gè)流程,因?yàn)槟呀?jīng)知道如何使用管理站點(diǎn)!
現(xiàn)在我們將添加一個(gè)視圖,以獲取已經(jīng)借給當(dāng)前用戶的所有書本列表。我們將使用我們熟悉的、基于類的通用類列表視圖,但這次我們還將導(dǎo)入并派生自LoginRequiredMixin ,以便只有登錄用戶才能調(diào)用此視圖。我們還將選擇聲明template_name ,而不是使用默認(rèn)值,因?yàn)槲覀冏罱K可能會有幾個(gè)不同的 BookInstance 記錄列表,其中包含不同的視圖和模板。
將以下內(nèi)容添加到 catalog/views.py:
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')
為了將查詢,限制為當(dāng)前用戶的BookInstance 對象,我們重新實(shí)現(xiàn)了get_queryset() ,如上所示。請注意,“o”是表示借出當(dāng)中“on loan”的存儲代碼,我們按due_back 日期排序,以便首先顯示最舊的項(xiàng)目。
借書的 URL 設(shè)置節(jié)
現(xiàn)在打開/catalog/urls.py,并添加指向上面視圖的path() (您只需將下面的文本復(fù)制到文件末尾)。
urlpatterns += [
path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]
現(xiàn)在,我們需要為此頁面添加一個(gè)模板。首先,創(chuàng)建模板文件/catalog/templates/catalog/bookinstance_list_borrowed_user.html,并為其提供以下內(nèi)容:
{% 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 %}
此模板與我們之前為 Book 和 Author 對象創(chuàng)建的模板非常相似。這里唯一新的東西,是我們檢查在模型中添加的方法(bookinst.is_overdue ),并使用它,來更改過期項(xiàng)目的顏色。
當(dāng)開發(fā)服務(wù)器運(yùn)行時(shí),您現(xiàn)在應(yīng)該能夠在瀏覽器中,查看登錄用戶的列表,網(wǎng)址為http://127.0.0.1:8000/catalog/mybooks/。在您的用戶登錄并登出后,嘗試此操作(在第二種情況下,您應(yīng)該被重定向到登錄頁面)。
最后一步,是將這個(gè)新頁面的鏈接,添加到側(cè)邊欄中。我們將把它放在我們?yōu)榈卿浻脩麸@示其他信息的同一部分。
打開基本模板(/locallibrary/catalog/templates/base_generic.html),并將粗體標(biāo)識的那一行,添加到側(cè)邊欄區(qū)塊,如下所示。
<ul class="sidebar-nav">
{% if user.is_authenticated %}
<li>User: {{ user.get_username }}</li>
<li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>
<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>
當(dāng)任何用戶登錄時(shí),他們會在側(cè)欄中看到 My Borrowed 鏈接,并顯示如下所示的書本列表(第一本書沒有截止日期,這是我們希望在以后的教程中修復(fù)的錯(cuò)誤!) 。

權(quán)限與模型相關(guān)聯(lián),并定義可以由具有權(quán)限的用戶,在模型實(shí)例上執(zhí)行的操作。默認(rèn)情況下,Django會自動(dòng)為所有模型提供添加,更改和刪除權(quán)限,這允許具有權(quán)限的用戶,通過管理站點(diǎn)執(zhí)行相關(guān)操作。您可以為模型定義自己的權(quán)限,并將其授予特定用戶。您還可以更改與同一模型的不同實(shí)例關(guān)聯(lián)的權(quán)限。
對于視圖和模板中的權(quán)限測試,非常類似于對身份驗(yàn)證狀態(tài)的測試(實(shí)際上,測試權(quán)限也會測試身份驗(yàn)證)。
在模型“class Meta ”部分上,使用 permissions 字段,完成權(quán)限定義。您可以在元組中指定所需的權(quán)限,每個(gè)權(quán)限本身,都在包含權(quán)限名稱和權(quán)限顯示值的嵌套元組中被定義。例如,我們可能會定義一個(gè)權(quán)限,允許用戶標(biāo)記已歸還的圖書,如下所示:
class BookInstance(models.Model):
...
class Meta:
...
permissions = (("can_mark_returned", "Set book as returned"),)
然后,我們可以將權(quán)限分配給管理站點(diǎn)中的圖書管理員“Librarian”分組。打開 catalog/models.py,然后添加權(quán)限,如上所示。您需要重新運(yùn)行遷移(調(diào)用 python3 manage.py makemigrations 和 python3 manage.py migrate ),以適當(dāng)?shù)馗聰?shù)據(jù)庫。
當(dāng)前用戶的權(quán)限,存在名為 {{ perms }} 的模板變量中。您可以使用關(guān)聯(lián)的Django “app” 中的特定變量名,來檢查當(dāng)前用戶是否具有特定權(quán)限 - 例如,如果用戶具有此權(quán)限,則 {{ perms.catalog.can_mark_returned }} 將為True,否則為False。我們通常使用模板標(biāo)記 {% if %} 測試權(quán)限,如下所示:
{% if perms.catalog.can_mark_returned %}
<!-- We can mark a BookInstance as returned. -->
<!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
在功能視圖中,可以使用 permission_required 裝飾器,或在基于類別的視圖中,使用 PermissionRequiredMixin 測試權(quán)限。模式和行為與登錄身份驗(yàn)證相同,但當(dāng)然您可能需要添加多個(gè)權(quán)限。
功能視圖裝飾器:
from django.contrib.auth.decorators import permission_required
@permission_required('catalog.can_mark_returned ')
@permission_required('catalog.can_edit ')
def my_view(request):
...
基于類別視圖的權(quán)限要求 mixin。
from django.contrib.auth.mixins import PermissionRequiredMixin
class MyView(PermissionRequiredMixin, View):
permission_required = 'catalog.can_mark_returned '
# Or multiple permissions
permission_required = ('catalog.can_mark_returned ', 'catalog.can_edit')
# Note that 'catalog.can_edit' is just an example
# the catalog application doesn't have such permission!
我們不會在這里更新 LocalLibrary;也許在下一個(gè)教程中!
在本文前面,我們向您展示了,如何為當(dāng)前用戶創(chuàng)建一個(gè)頁面,列出他們借用的書本。現(xiàn)在的挑戰(zhàn),是創(chuàng)建一個(gè)只對圖書館員可見的類似頁面,它顯示所有借用的書本,其中包括每個(gè)借用人的名字。
您應(yīng)該能夠遵循與其他視圖相同的模式。主要區(qū)別在于,您需要將視圖限制為僅限圖書館員。您可以根據(jù)用戶是否是工作人員(函數(shù)裝飾器:staff_member_required ,模板變量:user.is_staff )來執(zhí)行此操作,但我們建議您改為使用can_mark_returned 權(quán)限,和 PermissionRequiredMixin ,如上一節(jié)所述。
重要: 請記住,不要使用超級用戶進(jìn)行基于權(quán)限的測試(即使尚未定義權(quán)限,權(quán)限檢查也會對超級用戶返回 true)。而是要?jiǎng)?chuàng)建一個(gè)圖書管理員用戶,并添加所需的功能。
完成后,您的頁面應(yīng)該類似于下面的屏幕截圖。

做的太好了 — 你已經(jīng)創(chuàng)造了一個(gè)網(wǎng)站,圖書館用戶可以登入并檢視他們擁有的內(nèi)容,圖書管理員(有正確的授權(quán))可以檢視所有借出的書本以及借閱者。目前,我們?nèi)匀恢皇遣榭磧?nèi)容,但是當(dāng)您想要開始修改和添加數(shù)據(jù)時(shí),會使用相同的原則和技術(shù)。
在我們的下一篇文章,我們將介紹如何使用Django 表單,收集使用者輸入,然后開始修改我們儲存的一些資料。
|