由於使用者認證在太多網站都會用到,Django 在出廠時就會附上預設的 django.contrib.auth
模組。雖然是預設,但這個模組非常好用,其實沒什麼可以挑剔的地方。當你前面用到 createsuperuser
與 admin 介面時,其實就是用這個模組來登入。之前檢查資料庫結構時也有看到相關的 tables(名稱以 auth 開頭的就是)。
Django 的權限管理系統包含以下資料:
- 一個 table 保存所有使用者資料。
- 權限系統。每個使用者都可以擁有建立、修改、刪除某個 model 的權限。
- 每個使用者都有一個
is_staff
與is_superuser
欄位。只有 staff 可以進入後台,而 superuser 會擁有所有權限。 - 可以把使用者加入某個群組。改變群組權限可以直接改變內部使用者權限。
首先我們必須建立一個登入頁面。你可以用前面的方法建立 form、view、template,但 Django 其實有內建 form 與 view 可以用;你只要設定 URL patterns 與 templates 就行。Django 提供了以下的 views:
view name | 用途 |
---|---|
login |
登入。 |
logout |
登出。 |
password_change |
更換密碼。 |
password_change_done |
更換密碼完成後會導向這裡。 |
password_reset |
重設密碼(會寄 email 給使用者)。 |
password_reset_done |
重設密碼完成後會導向這裡。 |
password_reset_confirm |
使用者收到重設密碼 email 時,裡面會包含這個網址。 |
password_reset_complete |
使用者按下 email 中的重設連結,重設完成後,會被導向這裡。 |
由於篇幅緣故,註冊和修改密碼的頁面就直接跳過;有興趣請參照官方文件。
把 URL patterns 導向內建 views:
# lunch/urls.py
urlpatterns = patterns(
# ...
url(r'^accounts/', include('django.contrib.auth.urls')),
# ...
)
接著在 base/templates/base.html
加上登入按鈕:
<!-- ... -->
<nav class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{% url 'home' %}">午餐系統</a>
</div>
<div>
<ul class="nav navbar-nav">
<li><a href="{% url 'store_list' %}">店家列表</a></li>
</ul>
<form class="navbar-right navbar-form" method="post" action="{% url 'logout' %}">
{% if user.is_authenticated %}
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'home' %}">
<input class="btn btn-default" type="submit" value="登出">
{% else %}
<a class="btn btn-default" href="{% url 'login' %}">登入</a>
{% endif %}
</form>
</div>
</div>
</nav>
<!-- ... -->
我們之後會談到為什麼這邊要用 form。目前就先這樣。
如果你現在按下登入按鈕,應該會看到一個 TemplateNotFound
錯誤頁面,因為 Django 只提供了 views(與 URL patterns),但沒有提供 templates,我們要自己做。前面提過,這種不知道要放哪裡的東西我都丟到 pages
裡。所以新增 pages/templates/registration/login.html
:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block body %}
{{ block.super }}
<div class="container">
<div class="row">
<div class="col-lg-4 col-md-6 col-lg-offset-4 col-md-offset-3">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="submit" value="登入" class="btn btn-primary">
</form>
</div>
</div>
</div>
{% endblock body %}
這樣你應該可以看到登入頁面了。輸入你的帳號密碼,然後送出看看。呃,出現了一個 Page Not Found 錯誤。這是因為 Django 預設會在登入後把使用者導向至 profile page,而我們並沒有那種東西。我們其實不需要那種東西,只要在登入後把使用者導到首頁就好。所以我們要在 lunch/settings/base.py
加入下面的設定:
from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('home')
設定本身沒什麼,不過什麼是 reverse_lazy
?我們前面談過 reverse
這個函式可以把 URL pattern name 轉回 URL。但我們不能直接在這裡用 reverse
,因為當設定檔還沒被讀進 Django 時,我們還無法使用 Django 內部的功能,包含 reverse
。幸好,Django 的很多函式都有一個 lazy 版本——這些 lazy 函式不會立刻執行,而是會在需要結果時(以這個例子就是在 login view 真的需要重導向時)才會實際給出結果。這就避免了需要在設定載入前使用 Django 內部功能的狀況。
如果你現在登入,應該就會被導入到首頁。不過好像看不出來什麼區別。我們要修改 template 來反應登入狀態:
{# base/templates/base.html #}
<form class="navbar-right navbar-form" method="post" action="{% url 'logout' %}">
{% if user.is_authenticated %}
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'home' %}">
<input class="btn btn-default" type="submit" value="登出">
{% else %}
<a class="btn btn-default" href="{% url 'login' %}">登入</a>
{% endif %}
</form>
除了我們在 view 裡傳入的 context data 外,Django 預設也會設定一些全域的 template 變數。這裡用到的 user
就是其中之一。如果當下的 request 已經登入,則這個變數會是一個 user object(來自 auth
模組裡的 User
model);如果沒登入,則 Django 會使用一個叫做 AnonymousUser
的 mock object,來讓 is_authenticated
回傳 False
。所以當使用者登入時,就會顯示登出按鈕,而非登入。根據 best practice,我們在登出時應該使用 POST 而非 GET。所以你知道為什麼我們要用 form 了吧!
登出時,Django 預設會使用 registration/logged_out.html
這個 template 來顯示「你已經登出」頁面。不過我們這裡不需要這麼做,只要在登出時把使用者導回首頁即可。所以我們多加了一個隱藏欄位 next
;如果你有加這一欄,Django 會使用裡面的內容來重導向,而不是顯示預設頁面。(登入頁面其實也有這個 next
參數可以用;如果你想要做動態導向,例如在登入後導回「使用者原本所在頁面」,而不是固定的頁面,就可以用這個參數。)
這樣就把登入登出頁面完成了!明天我們會正式使用這個權限機制,並且實作 delete view。