iT邦幫忙

0

Django學習紀錄 13.用戶的登入與登出

之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
https://ithelp.ithome.com.tw/upload/images/20190724/20118889bj9fH1vhuR.jpg
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼

目錄:django系列文章-Django學習紀錄

13. 用戶的登入與登出

13.1 用戶

使用django內建的用戶權限系統
settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth', # <-確認此行有加
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'restaurants',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware', # <-確認此行有加
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

如果是第一次安裝記得同步資料庫

python manage.py migrate

13.2 登入

13.2.1 確認使用者身分

首先設定一個登入頁面
mysite/templates/login.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登入</title>
    </head>
    <body>
        <form action="" method="post">
            <label for="username">用戶名稱:</label>
            <input type="text" name="username" value="" id="username"> <br/>
            <label for="password">用戶密碼:</label>
            <input type="password" name="password" value="" id="password"> <br/>
            <input type="submit" value="登入" />
        </form>
    </body>
</html>

views.py

...
from django.contrib import auth

def login(request):
    if request.user.is_authenticated:
        return HttpResponseRedirect('/index/')
    username = request.POST.get('username', '')
    password = request.POST.get('password', '')
    user = auth.authenticate(username=username, password=password)
    if user is not None and user.is_active:
        auth.login(request, user)
        return HttpResponseRedirect('/index/')
    else:
        return render(request, 'login.html', locals())
...

HttpRequest物件中包含了一個user屬性,代表了當前的使用者
如果用戶已經登入,則HttpRequest.user是一個User物件,也就是具名用戶
如果使用者尚未登入,HttpRequest.user是一個AnonymousUser物件,也就是匿名用戶
User物件中的常用屬性:

屬性 說明
username 使用者的帳號,由字母、數字和底線組成
is_anonymous 是否是匿名用戶,永遠回傳False,若為AnonymousUser物件,則永遠回傳True
is_authenticated 用戶是否認證過,永遠回傳True,若為AnonymousUser物件,則永遠回傳False
first_name 名字
last_name 姓氏
email 電子郵箱
password 加密(經編碼)過後的密碼
is_staff 真假值,若為True,該用戶可登入admin後端
is_active 真假值,若為True,該用戶可登入
is_superuser 真假值,若為True,該用戶擁有全權限
last_login 用戶上一次登入的日期與時間
date_joined 用戶被創建的日期與時間

User物件中的常用方法:

屬性 說明
get_username() 取得用戶帳號
get_full_name() 回傳完整的姓名
get_short_name() 只回傳名字
set_password(password) 設定密碼,會自動編碼加密,不包含User物件的儲存
check_password(password) 確認密碼,正確會回傳True,會自動編碼加密才比較

如果使用者已經認證過,我們將它重導回首頁(等等我們會設計index.html)
如果還是匿名用戶,我們試圖從request.POST中拿取表單中的帳密資訊(如果POST中沒有username或是password的話,我們填給他一個空值,讓他在接下來的檢查中產生失敗),並且使用auth中的authenticate方法來確認用戶
auth.authenticate方法接受兩個引數username、password
如果帳密正確會回傳具名用戶的User物件
如果不正確則回傳None
user.is_active確認該帳戶有沒有被凍結
只要任何檢查有問題就回到登入頁面讓使用者輸入帳密
如果檢查都通過就使用auth.login方法來登入

13.2.2 登入使用者並保持其登入

auth.login需要一個HttpRequest物件和一個User物件當做引數,他會利用Django的session將這個具名用戶保存在該session中,這也代表了該用戶的登入狀態得以跨頁面的保存直到session結束或登出
Django中所謂的已經登入,代表在當下的HttpRequest中的user屬性是一個具名的User物件

13.2.3 根據用戶身分,確認並規範使用者的操作功能

製作首頁
mysite/templates/index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Restaurant King</title>
    </head>
    <body>
        <h2>歡迎來到餐廳王</h2>
        {% if request.user.is_authenticated %}
            <p>{{request.user}} 您已經登入囉</p>
            <a href="/restaurants_list/">餐廳列表</a>
        {% else %}
            <p>您尚未登入喔~<a href="/accounts/login/">登入</a></p>
        {% endif %}
    </body>
</html>

views.py

...
def index(request):
    return render(request, 'index.html')
...

urls.py

from restaurants.views import login, index
urlpatterns = [
    ...
    path('index/', index),
    path('accounts/login/', login),
    ...
]

至於為何要使用/accounts/login/這個pattern
因為/accounts/login/是Django默認的登入pattern(對於登出而言,/accounts/logout/也是默認值),這個pattern對於某些Django的函式而言是參數的預設值,使用默認的pattern可以使得我們在使用到這些函式時減少一些負擔,不過如果沒有其他考量的話,使用任何想要的pattern都是可以的
我們也可以在settings.py來設定此一默認值,LOGIN_URL可以修改成任意想要的默認pattern
https://ithelp.ithome.com.tw/upload/images/20190721/201188898sxVCTFcJb.png
https://ithelp.ithome.com.tw/upload/images/20190721/20118889kTHUBBPlpC.png

13.3 登出

views.py

...
def logout(request):
    auth.logout(request)
    return HttpResponseRedirect('/index/')
...

這裡的auth.logout方法會將用戶登出
只是要注意的是這個方法用在匿名用戶上也不會產生錯誤
index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Restaurant King</title>
    </head>
    <body>
        <h2>歡迎來到餐廳王</h2>
        {% if request.user.is_authenticated %}
            <p>
                {{request.user}} 您已經登入囉~
                <a href="/accounts/logout/">登出</a>
            </p>
            <a href="/restaurants_list/">餐廳列表</a>
        {% else %}
            <p>您尚未登入喔~<a href="/accounts/login/">登入</a></p>
        {% endif %}
    </body>
</html>

urls.py

from restaurants.views import login, index, logout

urlpatterns = [
    ...
    path('index/', index),
    path('accounts/login/', login),
    path('accounts/logout/', logout),
    ...
]

https://ithelp.ithome.com.tw/upload/images/20190721/20118889RzQz8FsUyS.png
https://ithelp.ithome.com.tw/upload/images/20190721/20118889aS9wfakLRz.png

13.4 使用內建的login/logout視圖

Django其實已經撰寫好了登出與登入的視圖函式
使用方法:
urls.py

...
from django.contrib.auth import views

urlpatterns = [
    ...
    path('index/', index),
    path('accounts/login/', views.LoginView.as_view()),
    path('accounts/logout/', views.LogoutView.as_view()),
    path('accounts/profile/', index),
    ...
]

這兩個視圖函式預設使用的模板分別是在
registration/login.html和registration/logged_out.html
所以把mysite/templates/login.html複製到registration資料夾中
把mysite/templates/index.html複製到registration資料夾並重新命名為logged_out.html
就可以正常使用了

13.4.2 內建login提供給呼叫模板的變量

提供給模板的主要有下列變量

變量 說明
form AuthenticationForm的物件,用來做authenticate的確認的,有username和password兩個欄位
next 用在登入成功後重導的URL,可能包含查詢字串

mysite/templates/registration/login.html

...
    <body>
        <form action="{% url 'login' %}" method="post">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        <input type="submit" value="登入" />
        </form>
    </body>
...

在url pattern中加入name這個參數

path('accounts/login/', views.LoginView.as_view(), name='login'),
path('accounts/logout/', views.LogoutView.as_view(), name='logout'),

https://ithelp.ithome.com.tw/upload/images/20190721/20118889O1XRY8IpEF.png
介紹一下{% url '字串' %}的用法
django會將這裡的字串對應到符合的name參數的url pattern
例如{% url 'login' %}即為對應到path('accounts/login/', views.LoginView.as_view())
這是一個極佳的"反查"手段避免我們將URL寫死在模板裡

13.4.3 提供重導URL給內建login

使用者登入之後,我們經常要幫他重導回某個特定的頁面,譬如首頁
內建的login預設會重導回/accounts/profile/
這就是為什麼我們要加入

path('accounts/profile/', index)

如果刪去這行
但是仍要讓內建的login能夠照我們的意思來重導
有以下幾種方法:

於settings.py中設定LOGIN_REDIRECT_URL

LOGIN_REDIRECT_URL = "/index/"

透過POST方法傳送next(或是其他的REDIRECT_FIELD_NAME)欄位及其值

mysite/templates/registration/login.html

...
        <input type="submit" value="login" />
        <input type="hidden" name="next" value="{{ next }}" /> <!--利用此行-->
    </form>
...

利用名為next的隱藏元件來傳送重導URL,而這個URL是來自內建login提供的變量{{ next }}

透過GET方法傳送next(或是其他的REDIRECT_FIELD_NAME)欄位及其值

直接透過在登入URL: /accounts/login/?next=/index/中附加查詢字串來提供next欄位
mysite/templates/registration/login.html

...
<body>
        <form action="{% url 'login' %}?next=/index/" method="post">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        <input type="submit" value="登入" />
        </form>
    </body>
...

不過第二種方法還是依據第一種方式的設定,只能算是解決next值傳遞的方式而不算是設定一個next欄位的方法
對於預設的重導行為會偏向使用第一種方式進行設定,對於重導回登入前一個頁面的行為會偏向使用第三個方法
如果沒有特殊需求的話,建議各位遵循使用next這個欄位名稱,可以在使用上避免一些需要調整的負擔,也能有較好的一致性

13.4.4 對於內建的login視圖使用別的模板名稱

path('accounts/login/', views.LoginView.as_view(template_name='login.html'), name='login'),
path('accounts/logout/', views.LogoutView.as_view(template_name='index.html'), name='logout'),

加入template_name這個參數便可以使用模板目錄底下任意的模板了

上一篇:Django學習紀錄 12.Cookies與Sessions

下一篇:Django學習紀錄 14.權限與註冊


尚未有邦友留言

立即登入留言