之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼
使用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
首先設定一個登入頁面
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 | 姓氏 |
電子郵箱 | |
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 方法來登入 |
auth.login需要一個HttpRequest物件和一個User物件當做引數,他會利用Django的session將這個具名用戶保存在該session中,這也代表了該用戶的登入狀態得以跨頁面的保存直到session結束或登出
Django中所謂的已經登入,代表在當下的HttpRequest中的user屬性是一個具名的User物件
製作首頁
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
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),
...
]
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
就可以正常使用了
提供給模板的主要有下列變量
變量 | 說明 |
---|---|
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'),
介紹一下{% url '字串' %}
的用法
django會將這裡的字串對應到符合的name
參數的url pattern
例如{% url 'login' %}
即為對應到path('accounts/login/', views.LoginView.as_view())
這是一個極佳的"反查"手段避免我們將URL寫死在模板裡
使用者登入之後,我們經常要幫他重導回某個特定的頁面,譬如首頁
內建的login預設會重導回/accounts/profile/
這就是為什麼我們要加入
path('accounts/profile/', index)
如果刪去這行
但是仍要讓內建的login能夠照我們的意思來重導
有以下幾種方法:
LOGIN_REDIRECT_URL = "/index/"
mysite/templates/registration/login.html
...
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" /> <!--利用此行-->
</form>
...
利用名為next的隱藏元件來傳送重導URL,而這個URL是來自內建login提供的變量{{ next }}
直接透過在登入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這個欄位名稱,可以在使用上避免一些需要調整的負擔,也能有較好的一致性
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
這個參數便可以使用模板目錄底下任意的模板了