Django 教程 第 8 部分:使用者認證和許可權

在本教程中,我們將向您展示如何允許使用者使用自己的帳戶登入您的網站,以及如何根據他們是否已登入以及他們的許可權來控制他們可以執行的操作和檢視的內容。作為此演示的一部分,我們將擴充套件LocalLibrary 網站,新增登入和登出頁面,以及供使用者和員工檢視已借書籍的特定頁面。

先決條件 完成所有之前的教程主題,直至幷包括Django 教程第 7 部分:會話框架
目標 瞭解如何設定和使用使用者身份驗證和許可權。

概述

Django 提供了一個身份驗證和授權(“許可權”)系統,構建在前面教程中討論的會話框架之上,允許您驗證使用者憑據並定義每個使用者允許執行的操作。該框架包括用於UsersGroups 的內建模型(一種將許可權一次應用於多個使用者的通用方法)、用於指定使用者是否可以執行任務的許可權/標誌、用於登入使用者的表單和檢視,以及用於限制內容的檢視工具。

注意:根據 Django 的說法,身份驗證系統旨在非常通用,因此不提供其他 Web 身份驗證系統中提供的一些功能。一些常見問題的解決方案可作為第三方軟體包獲得。例如,登入嘗試的限制和針對第三方的身份驗證(例如 OAuth)。

在本教程中,我們將向您展示如何在LocalLibrary 網站中啟用使用者身份驗證,建立您自己的登入和登出頁面,向您的模型新增許可權,以及控制對頁面的訪問。我們將使用身份驗證/許可權來顯示使用者和管理員已借書籍的列表。

身份驗證系統非常靈活,如果您願意,您可以從頭開始構建您的 URL、表單、檢視和模板,只需呼叫提供的 API 來登入使用者即可。但是,在本文中,我們將使用 Django 的“預設”身份驗證檢視和表單來建立我們的登入和登出頁面。我們仍然需要建立一些模板,但這非常簡單。

我們還將向您展示如何建立許可權,以及如何在檢視和模板中檢查登入狀態和許可權。

啟用身份驗證

當我們建立網站骨架(在教程 2 中)時,身份驗證已自動啟用,因此您現在無需執行任何其他操作。

注意:當我們使用django-admin startproject 命令建立應用程式時,所有必要的配置都已為我們完成。當我們第一次呼叫python manage.py migrate 時,建立了使用者和模型許可權的資料庫表。

該配置設定在專案檔案的INSTALLED_APPSMIDDLEWARE 部分(django-locallibrary-tutorial/locallibrary/settings.py)中,如下所示

python
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.
    # …

建立使用者和組

當我們在教程 4 中檢視Django 管理站點時,您已經建立了您的第一個使用者(這是一個超級使用者,使用命令python manage.py createsuperuser 建立)。我們的超級使用者已透過身份驗證並擁有所有許可權,因此我們需要建立一個測試使用者來表示普通站點使用者。我們將使用管理站點來建立我們的locallibrary 組和網站登入,因為這是最快的方法之一。

注意:您也可以按如下所示以程式設計方式建立使用者。例如,如果您正在開發一個介面以允許“普通”使用者建立自己的登入資訊(您不應該授予大多數使用者訪問管理站點的許可權),則必須執行此操作。

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 = 'Tyrone'
user.last_name = 'Citizen'
user.save()

但是,強烈建議在啟動專案時設定一個自定義使用者模型,因為如果需要,您可以在將來輕鬆地對其進行自定義。如果使用自定義使用者模型,則建立相同使用者的程式碼如下所示

python
# Get current user model from settings
from django.contrib.auth import get_user_model
User = get_user_model()

# Create user from model and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')

# Update fields and then save again
user.first_name = 'Tyrone'
user.last_name = 'Citizen'
user.save()

有關更多資訊,請參閱啟動專案時使用自定義使用者模型(Django 文件)。

下面我們將首先建立一個組,然後建立一個使用者。即使我們還沒有任何要為我們的圖書館成員新增的許可權,但如果我們以後需要,將其一次新增到組中而不是單獨新增到每個成員中會容易得多。

啟動開發伺服器並在本地 Web 瀏覽器中導航到管理站點(http://127.0.0.1:8000/admin/)。使用超級使用者帳戶的憑據登入站點。管理站點的頂級顯示所有模型,按“Django 應用程式”排序。在身份驗證和授權部分,您可以單擊使用者連結以檢視其現有記錄。

Admin site - add groups or users

首先讓我們為我們的圖書館成員建立一個新組。

  1. 單擊新增按鈕(在組旁邊)以建立一個新的;為該組輸入名稱“圖書館成員”。管理站點 - 新增組
  2. 我們不需要該組的任何許可權,因此只需按儲存即可(您將被帶到一個組列表)。

現在讓我們建立一個使用者

  1. 導航回管理站點的首頁
  2. 單擊使用者旁邊的新增按鈕以開啟新增使用者對話方塊。管理站點 - 新增使用者 pt1
  3. 為您的測試使用者輸入適當的使用者名稱密碼/密碼確認
  4. 儲存建立使用者。管理站點將建立新使用者並立即將您帶到一個更改使用者螢幕,您可以在其中更改您的使用者名稱併為 User 模型的可選欄位新增資訊。這些欄位包括姓氏、名字、電子郵件地址以及使用者的狀態和許可權(僅應設定活動標誌)。在下面,您可以指定使用者的組和許可權,並檢視與使用者相關的重要的日期(例如,他們的加入日期和上次登入日期)。管理站點 - 新增使用者 pt2
  5. 部分,從可用組列表中選擇圖書館成員組,然後按框之間的右箭頭將其移動到已選組框中。管理站點 - 將使用者新增到組
  6. 我們不需要在這裡做任何其他事情,因此只需再次選擇儲存即可轉到使用者列表。

就是這樣!現在您擁有了一個“普通圖書館成員”帳戶,您將能夠將其用於測試(一旦我們實現了啟用他們登入的頁面)。

注意:您應該嘗試建立另一個圖書館成員使用者。此外,為管理員建立一個組,並將使用者也新增到該組中!

設定身份驗證檢視

Django 提供了您建立身份驗證頁面以處理登入、登出和密碼管理“開箱即用”所需的大部分內容。這包括 URL 對映器、檢視和表單,但不包括模板——我們必須自己建立!

在本節中,我們將展示如何將預設系統整合到LocalLibrary 網站並建立模板。我們將把它們放在主專案 URL 中。

注意:您不必使用任何這些程式碼,但您很可能希望這樣做,因為它使事情變得更容易。如果您更改了使用者模型,您幾乎肯定需要更改表單處理程式碼,但即使如此,您仍然可以使用預設的檢視函式。

注意:在這種情況下,我們可以合理地將身份驗證頁面(包括 URL 和模板)放在我們的 catalog 應用程式中。但是,如果我們有多個應用程式,最好將這種共享登入行為分離出來,並使其在整個站點中可用,因此這就是我們在這裡展示的內容!

專案 URL

將以下內容新增到專案 urls.py 檔案(django-locallibrary-tutorial/locallibrary/urls.py)檔案的底部

python
# Add Django site authentication urls (for login, logout, password management)

urlpatterns += [
    path('accounts/', include('django.contrib.auth.urls')),
]

導航到http://127.0.0.1:8000/accounts/ URL(注意尾部的正斜槓!)。Django 將顯示一條錯誤訊息,指出它找不到此 URL 的對映,並列出它嘗試的所有 URL。由此您可以看到建立模板後可用的 URL。

注意:如上所示新增accounts/ 路徑會新增以下 URL 以及名稱(方括號中給出),這些名稱可用於反轉 URL 對映。您無需實現任何其他內容——上述 URL 對映會自動對映下面提到的 URL。

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']

現在嘗試導航到登入 URL(http://127.0.0.1:8000/accounts/login/)。這將再次失敗,但會顯示一條錯誤訊息,告訴您我們在模板搜尋路徑上缺少所需的模板(registration/login.html)。您將在頂部的黃色部分看到以下幾行列出

python
Exception Type:    TemplateDoesNotExist
Exception Value:    registration/login.html

下一步是為模板建立一個名為“registration”的目錄,然後新增login.html 檔案。

模板目錄

我們剛剛新增的 URL(以及隱式檢視)期望在模板搜尋路徑中的某個位置的/registration/ 目錄中找到其關聯的模板。

對於此站點,我們將 HTML 頁面放在templates/registration/ 目錄中。此目錄應位於您的專案根目錄中,即與cataloglocallibrary 資料夾相同的目錄。請立即建立這些資料夾。

注意:您的資料夾結構現在應如下所示

django-locallibrary-tutorial/   # Django top level project folder
  catalog/
  locallibrary/
  templates/
    registration/

為了使templates 目錄對模板載入器可見,我們需要將其新增到模板搜尋路徑中。開啟專案設定(/django-locallibrary-tutorial/locallibrary/settings.py)。

然後匯入os 模組(如果該檔案不存在,請在檔案頂部附近新增以下行)。

python
import os # needed by code below

如所示更新TEMPLATES 部分的'DIRS'

python
    # …
    TEMPLATES = [
      {
       # …
       'DIRS': [os.path.join(BASE_DIR, 'templates')],
       'APP_DIRS': True,
       # …

登入模板

警告:本文提供的身份驗證模板是 Django 演示登入模板的一個非常基本/略微修改的版本。您可能需要根據自己的需要對其進行自定義!

建立一個名為/django-locallibrary-tutorial/templates/registration/login.html 的新 HTML 檔案,併為其提供以下內容

django
{% 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 %}
    <table>
      <tr>
        <td>{{ form.username.label_tag }}</td>
        <td>{{ form.username }}</td>
      </tr>
      <tr>
        <td>{{ form.password.label_tag }}</td>
        <td>{{ form.password }}</td>
      </tr>
    </table>
    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next }}">
  </form>

  {# Assumes you set up the password_reset view in your URLconf #}
  <p><a href="{% url 'password_reset' %}">Lost password?</a></p>

{% endblock %}

此模板與我們之前看到的模板有一些相似之處——它擴充套件了我們的基本模板並覆蓋了content 塊。其餘程式碼是相當標準的表單處理程式碼,我們將在以後的教程中討論。您現在只需要知道它將顯示一個表單,您可以在其中輸入您的使用者名稱和密碼,並且如果您輸入無效的值,頁面重新整理時將提示您輸入正確的值。

儲存模板後,導航回登入頁面(http://127.0.0.1:8000/accounts/login/),您應該會看到類似以下內容

Library login page v1

如果您使用有效的憑據登入,您將被重定向到另一個頁面(預設情況下,這將是http://127.0.0.1:8000/accounts/profile/)。問題是,預設情況下,Django 期望登入後您希望轉到個人資料頁面,這可能是也可能不是這種情況。由於您尚未定義此頁面,因此您將收到另一條錯誤訊息!

開啟專案設定(/django-locallibrary-tutorial/locallibrary/settings.py)並在底部新增下面的文字。現在,當您登入時,預設情況下應該會重定向到站點首頁。

python
# Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'

登出模板

如果您導航到登出 URL(http://127.0.0.1:8000/accounts/logout/),那麼您將收到一條錯誤訊息,因為 Django 5 不允許使用GET 登出,只允許使用POST。我們將在稍後新增一個可用於登出的表單,但首先我們將建立使用者登出後轉到的頁面。

建立並開啟/django-locallibrary-tutorial/templates/registration/logged_out.html。複製下面的文字

django
{% extends "base_generic.html" %}

{% block content %}
  <p>Logged out!</p>
  <a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}

此模板非常簡單。它只顯示一條訊息,告知您已登出,並提供一個連結,您可以點選該連結返回登入螢幕。螢幕呈現如下(登出後)

Library logout page v1

重置密碼模板

預設的密碼重置系統使用電子郵件向用戶傳送重置連結。您需要建立表單以獲取使用者的電子郵件地址、傳送電子郵件、允許他們輸入新密碼以及記錄整個過程何時完成。

以下模板可用作起點。

重置密碼錶單

這是用於獲取使用者電子郵件地址(用於傳送密碼重置電子郵件)的表單。建立** /django-locallibrary-tutorial/templates/registration/password_reset_form.html **,並賦予其以下內容

django
{% 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 %}

重置密碼完成

收集您的電子郵件地址後,將顯示此表單。建立** /django-locallibrary-tutorial/templates/registration/password_reset_done.html **,並賦予其以下內容

django
{% 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 電子郵件的文字,我們將傳送給使用者。建立** /django-locallibrary-tutorial/templates/registration/password_reset_email.html **,並賦予其以下內容

django
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

確認重置密碼

點選密碼重置電子郵件中的連結後,您可以在此頁面輸入新密碼。建立** /django-locallibrary-tutorial/templates/registration/password_reset_confirm.html **,並賦予其以下內容

django
{% extends "base_generic.html" %}

{% block content %}
    {% if validlink %}
        <p>Please enter (and confirm) your new password.</p>
        <form action="" method="post">
        {% csrf_token %}
            <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 %}

重置密碼完成

這是最後一個密碼重置模板,顯示以通知您密碼重置已成功。建立** /django-locallibrary-tutorial/templates/registration/password_reset_complete.html **,並賦予其以下內容

django
{% extends "base_generic.html" %}

{% block content %}
  <h1>The password has been changed!</h1>
  <p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}

測試新的身份驗證頁面

現在您已新增 URL 配置並建立了所有這些模板,身份驗證頁面(登出除外)現在應該可以正常工作了!

您可以透過首先嚐試使用 URL `http://127.0.0.1:8000/accounts/login/` 登入您的超級使用者帳戶來測試新的身份驗證頁面。您將能夠從登入頁面中的連結測試密碼重置功能。**請注意,Django 僅會將重置電子郵件傳送到其資料庫中已儲存的地址(使用者)!**

請注意,您還無法測試帳戶登出,因為登出請求必須作為 `POST` 而不是 `GET` 請求傳送。

**注意:**密碼重置系統要求您的網站支援電子郵件,這超出了本文的範圍,因此此部分**尚無法使用**。為了允許測試,在 `settings.py` 檔案的末尾新增以下行。這會將傳送到控制檯的任何電子郵件記錄下來(以便您可以從控制檯中複製密碼重置連結)。

python
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

有關更多資訊,請參閱 傳送電子郵件(Django 文件)。

針對已認證的使用者進行測試

本節介紹了我們可以如何根據使用者是否登入來選擇性地控制使用者看到的內容。

在模板中測試

您可以使用 `{{ user }}` 模板變數獲取有關當前登入使用者的資訊(當您按照我們在框架中所做的那樣設定專案時,預設情況下會將此新增到模板上下文中)。

通常,您首先會針對 `{{ user.is_authenticated }}` 模板變數進行測試,以確定使用者是否有資格檢視特定內容。為了演示這一點,接下來我們將更新我們的側邊欄,如果使用者已登出則顯示“登入”連結,如果使用者已登入則顯示“登出”連結。

開啟基本模板(** /django-locallibrary-tutorial/catalog/templates/base_generic.html **)並將以下文字複製到 `sidebar` 塊中,緊接在 `endblock` 模板標記之前。

django
  <ul class="sidebar-nav">{% if user.is_authenticated %}
     <li>User: {{ user.get_username }}</li>
     <li>
       <form id="logout-form" method="post" action="{% url 'logout' %}">
         {% csrf_token %}
         <button type="submit" class="btn btn-link">Logout</button>
       </form>
     </li>
   {% else %}
     <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}</ul>

如您所見,我們使用 `if` / `else` / `endif` 模板標記根據 `{{ user.is_authenticated }}` 是否為真來有條件地顯示文字。如果使用者已透過身份驗證,那麼我們知道我們有一個有效的使用者,因此我們呼叫 `{{ user.get_username }}` 來顯示他們的名稱。

我們使用 `url` 模板標記和 `login` URL 配置的名稱建立登入連結 URL。還要注意我們如何在 URL 末尾附加了 `?next={{ request.path }}`。這樣做是在連結的 URL 末尾新增一個 URL 引數 `next`,其中包含當前頁面的地址(URL)。在使用者成功登入後,檢視將使用此“`next`”值將使用者重定向回他們最初點選登入連結的頁面。

登出模板程式碼有所不同,因為從 Django 5 開始,您必須使用帶有按鈕的表單 `POST` 到 `admin:logout` URL 以登出。預設情況下,這將呈現為一個按鈕,但您可以將按鈕樣式設定為顯示為連結。在此示例中,我們使用的是Bootstrap,因此我們透過應用 `class="btn btn-link"` 使按鈕看起來像連結。您還需要將以下樣式附加到** /django-locallibrary-tutorial/catalog/static/css/styles.css **,以便正確地將登出連結放置在所有其他側邊欄連結旁邊

css
#logout-form {
  display: inline;
}
#logout-form button {
  padding: 0;
  margin: 0;
}

透過點選側邊欄中的登入/登出連結試一試。您應該會被帶到您在上面的 模板目錄 中定義的登出/登入頁面。

在檢視中測試

如果您使用的是基於函式的檢視,則限制對函式訪問的最簡單方法是將 `login_required` 裝飾器應用於您的檢視函式,如下所示。如果使用者已登入,則您的檢視程式碼將照常執行。如果使用者未登入,這將重定向到專案設定中定義的登入 URL(`settings.LOGIN_URL`),並將當前絕對路徑作為 `next` URL 引數傳遞。如果使用者成功登入,則他們將被返回到此頁面,但這次是經過身份驗證的。

python
from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # …

**注意:**您可以透過手動測試 `request.user.is_authenticated` 來執行相同的事情,但裝飾器更方便!

類似地,在基於類的檢視中限制對已登入使用者的訪問的最簡單方法是從 `LoginRequiredMixin` 派生。您需要首先在超類列表中宣告此混合,然後宣告主檢視類。

python
from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    # …

這與 `login_required` 裝飾器具有完全相同的重定向行為。您還可以指定一個替代位置來重定向未經身份驗證的使用者(`login_url`),以及一個 URL 引數名稱而不是“`next`”來插入當前絕對路徑(`redirect_field_name`)。

python
class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

有關其他詳細資訊,請檢視 此處的 Django 文件

示例 - 列出當前使用者的書籍

現在我們知道如何將頁面限制為特定使用者,讓我們建立一個當前使用者借閱的書籍檢視。

不幸的是,我們還沒有任何方法讓使用者借閱書籍!因此,在建立書籍列表之前,我們將首先擴充套件 `BookInstance` 模型以支援借閱的概念,並使用 Django Admin 應用程式將一些書籍借給我們的測試使用者。

模型

首先,我們必須使使用者能夠借閱 `BookInstance`(我們已經有 `status` 和 `due_back` 日期,但我們還沒有此模型與特定使用者之間的任何關聯。我們將使用 `ForeignKey`(一對多)欄位建立一個。我們還需要一種簡單的機制來測試借出的書籍是否已逾期。

開啟** catalog/models.py **,並從 `django.conf` 中匯入 `settings`(將其新增到檔案頂部的上一行匯入語句下方,以便後續使用它們的程式碼可以使用它們)

python
from django.conf import settings

接下來,將 `borrower` 欄位新增到 `BookInstance` 模型中,將鍵的使用者模型設定為設定 `AUTH_USER_MODEL` 的值。由於我們沒有使用 自定義使用者模型 覆蓋設定,因此它對映到來自 `django.contrib.auth.models` 的預設 `User` 模型。

python
borrower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)

**注意:**以這種方式匯入模型可以減少以後發現需要自定義使用者模型時所需的工作量。本教程使用預設模型,因此您可以使用以下行直接匯入 `User` 模型

python
from django.contrib.auth.models import User
python
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)

趁此機會,讓我們新增一個屬性,我們可以從模板中呼叫它來判斷特定的書籍例項是否已逾期。雖然我們可以在模板本身中計算這一點,但使用如下所示的 屬性 將效率更高。

將其新增到檔案的頂部附近

python
from datetime import date

現在將以下屬性定義新增到 `BookInstance` 類中

**注意:**以下程式碼使用 Python 的 `bool()` 函式,該函式評估物件或表示式的結果物件,並在結果不為“假”時返回 `True`,否則返回 `False`。在 Python 中,如果物件為(評估為 `False`),則為:空(如 `[]`、`()`、`{}`)、`0`、`None` 或 `False`。

python
@property
def is_overdue(self):
    """Determines if the book is overdue based on due date and current date."""
    return bool(self.due_back and date.today() > self.due_back)

**注意:**在進行比較之前,我們首先驗證 `due_back` 是否為空。空的 `due_back` 欄位會導致 Django 丟擲錯誤而不是顯示頁面:空值不可比較。這不是我們希望使用者體驗到的!

現在我們已更新了模型,我們需要對專案進行新的遷移,然後應用這些遷移

bash
python3 manage.py makemigrations
python3 manage.py migrate

管理

現在開啟** catalog/admin.py **,並將 `borrower` 欄位新增到 `BookInstanceAdmin` 類中的 `list_display` 和 `fieldsets` 中,如下所示。這將使該欄位在管理部分可見,允許我們在需要時將 `User` 分配給 `BookInstance`。

python
@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')
        }),
    )

借幾本書

現在可以將書籍借給特定使用者了,請借出一些 `BookInstance` 記錄。將它們的 `borrowed` 欄位設定為您的測試使用者,將 `status` 設定為“已借出”,並分別在將來和過去設定到期日期。

**注意:**我們不會詳細說明此過程,因為您已經知道如何使用管理站點!

已借出檢視

現在,我們將新增一個檢視,用於獲取已借給當前使用者的所有書籍列表。我們將使用我們熟悉的相同的基於泛型的列表檢視,但這次我們還將匯入並從 `LoginRequiredMixin` 派生,以便只有登入使用者才能呼叫此檢視。我們還將選擇宣告一個 `template_name`,而不是使用預設值,因為我們最終可能會有幾個不同的 `BookInstance` 記錄列表,具有不同的檢視和模板。

將以下內容新增到 catalog/views.py 中

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')
        )

為了將我們的查詢限制為僅當前使用者的 `BookInstance` 物件,我們重新實現了 `get_queryset()`,如上所示。請注意,“o”是“已借出”的儲存程式碼,我們按 `due_back` 日期排序,以便首先顯示最舊的專案。

已借出書籍的 URL 配置

現在開啟** /catalog/urls.py **並新增一個指向上述檢視的 `path()`(您可以將下面的文字複製到檔案的末尾)。

python
urlpatterns += [
    path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]

已借出書籍的模板

現在,我們只需要為此頁面新增一個模板。首先,建立模板檔案** /catalog/templates/catalog/bookinstance_list_borrowed_user.html **並賦予其以下內容

django
{% 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` 物件建立的模板非常相似。這裡唯一“新”的東西是我們檢查了我們在模型中新增的方法 `(bookinst.is_overdue)` 並使用它來更改逾期專案的顏色。

當開發伺服器正在執行時,您現在應該能夠在瀏覽器中以 `http://127.0.0.1:8000/catalog/mybooks/` 檢視登入使用者的列表。在使用者登入和登出的情況下試一試(在第二種情況下,您應該會被重定向到登入頁面)。

將列表新增到側邊欄

最後一步是為這個新頁面在側邊欄中新增一個連結。我們將把它放在顯示登入使用者其他資訊相同的部分。

開啟基本模板(** /django-locallibrary-tutorial/catalog/templates/base_generic.html**)並在下面所示的位置將“我的借閱”行新增到側邊欄。

django
 <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>
     <form id="logout-form" method="post" action="{% url 'admin:logout' %}">
       {% csrf_token %}
       <button type="submit" class="btn btn-link">Logout</button>
     </form>
   </li>
   {% else %}
   <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}
 </ul>

它是什麼樣子的?

當任何使用者登入時,他們將在側邊欄中看到“我的借閱”連結,以及如下所示的圖書列表(第一本書沒有到期日,這是一個我們希望在後續教程中修復的錯誤!)。

Library - borrowed books by user

許可權

許可權與模型相關聯,並定義具有該許可權的使用者可以對模型例項執行的操作。預設情況下,Django 會自動向所有模型授予新增更改刪除許可權,這允許具有這些許可權的使用者透過管理員站點執行相關操作。您可以為模型定義自己的許可權並將其授予特定使用者。您還可以更改與同一模型的不同例項關聯的許可權。

然後,在檢視和模板中測試許可權與測試身份驗證狀態非常相似(實際上,測試許可權也會測試身份驗證)。

模型

許可權的定義是在模型“class Meta”部分使用permissions欄位完成的。您可以在元組中指定任意數量的許可權,每個許可權本身都在一個巢狀元組中定義,該元組包含許可權名稱和許可權顯示值。例如,我們可以定義一個許可權,允許使用者標記一本書已歸還,如下所示

python
class BookInstance(models.Model):
    # …
    class Meta:
        # …
        permissions = (("can_mark_returned", "Set book as returned"),)

然後,我們可以在管理員站點中將許可權分配給“圖書管理員”組。

開啟**catalog/models.py**,並按上述方式新增許可權。您需要重新執行遷移(呼叫python3 manage.py makemigrationspython3 manage.py migrate)以適當地更新資料庫。

模板

當前使用者的許可權儲存在一個名為{{ perms }}的模板變數中。您可以使用關聯的 Django “應用程式”內的特定變數名稱來檢查當前使用者是否具有特定許可權——例如,如果使用者具有此許可權,則{{ perms.catalog.can_mark_returned }}將為True,否則為False。我們通常使用模板{% if %}標籤來測試許可權,如下所示

django
{% 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來測試許可權。模式與登入身份驗證相同,當然,您可能需要新增多個許可權。

函式檢視裝飾器

python
from django.contrib.auth.decorators import permission_required

@permission_required('catalog.can_mark_returned')
@permission_required('catalog.can_edit')
def my_view(request):
    # …

基於類的檢視的許可權必需混合。

python
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.change_book')
    # Note that 'catalog.change_book' is permission
    # Is created automatically for the book model, along with add_book, and delete_book

注意:上述行為存在細微的預設差異。對於具有許可權違規的登入使用者,**預設**情況下

  • @permission_required重定向到登入螢幕(HTTP 狀態 302)。
  • PermissionRequiredMixin返回 403(HTTP 狀態禁止)。

通常,您需要PermissionRequiredMixin行為:如果使用者已登入但沒有正確的許可權,則返回 403。要對函式檢視執行此操作,請使用@login_required@permission_required以及raise_exception=True,如下所示

python
from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('catalog.can_mark_returned', raise_exception=True)
def my_view(request):
    # …

示例

我們不會在此處更新LocalLibrary;也許在下一個教程中!

挑戰自我

在本文的前面,我們向您展示瞭如何為當前使用者建立一個頁面,列出他們借閱的圖書。現在的挑戰是建立一個僅對圖書管理員可見的類似頁面,該頁面顯示所有已借閱的圖書,幷包括每個借閱者的姓名。

您應該能夠遵循與其他檢視相同的模式。主要區別在於您需要將檢視限制為僅圖書管理員。您可以根據使用者是否是工作人員(函式裝飾器:staff_member_required,模板變數:user.is_staff)來執行此操作,但我們建議您改為使用can_mark_returned許可權和PermissionRequiredMixin,如上一節所述。

警告:請記住不要將超級使用者用於基於許可權的測試(即使尚未定義許可權,超級使用者的許可權檢查也始終返回 true!)。相反,建立一個圖書管理員使用者,並新增所需的功能。

完成後,您的頁面應類似於下面的螢幕截圖。

All borrowed books, restricted to librarian

總結

出色的工作——您現在建立了一個網站,圖書館成員可以在其中登入並檢視自己的內容,並且圖書管理員(具有正確的許可權)可以檢視所有借出的圖書及其借閱者。目前,我們只是在檢視內容,但是當您想要開始修改和新增資料時,將使用相同的原則和技術。

在我們的下一篇文章中,我們將瞭解如何使用 Django 表單收集使用者輸入,然後開始修改我們儲存的一些資料。

另請參閱