目錄
Django2實戰示例 第一章 創建博客應用
Django2實戰示例 第二章 增強博客功能
Django2實戰示例 第三章 擴展博客功能
Django2實戰示例 第四章 創建社交網站
Django2實戰示例 第五章 內容分享功能
Django2實戰示例 第六章 追蹤用戶行為
Django2實戰示例 第七章 創建電商網站
Django2實戰示例 第八章 管理支付與訂單
Django2實戰示例 第九章 擴展商店功能
Django2實戰示例 第十章 創建在線教育平台
Django2實戰示例 第十一章 渲染和緩存課程內容
Django2實戰示例 第十二章 創建API
Django2實戰示例 第十三章 上線
第四章 創建社交網站
在之前的章節學習了如何創建站點地圖、訂閱信息和創建一個全文搜索引擎。這一章我們來開發一個社交網站。會創建用戶登錄、登出、修改和重置密碼功能,為用戶創建額外的用戶信息,以及使用第三方身份認證登錄。
本章包含以下內容:
- 使用Django內置驗證模塊
- 創建用戶注冊視圖
- 使用自定義的用戶信息表擴展用戶模型
- 添加第三方身份認證系統
我們來創建本書的第二個項目。
1社交網站
我們將創建一個社交網站,讓用戶可以把網上看到的圖片分享到網站來。這個社交網站包含如下功能:
- 一個供用戶注冊、登錄、登出、修改和重置密碼的用戶身份驗證系統,還能夠讓用戶自行填寫用戶信息
- 關注系統,讓用戶可以關注其他用戶
- 一個JS小書簽工具,讓用戶可以將外部的圖片分享(上傳)到本站
- 一個追蹤系統,讓用戶可以看到他所關注的用戶的上傳內容
本章涉及到其中的第一個內容:用戶身份驗證系統。
1.1啟動社交網站項目
啟動系統命令行,輸入下列命令創建並激活一個虛擬環境:
mkdir env
virtualenv env/bookmarks
source env/bookmarks/bin/activate
終端會顯示當前的虛擬環境,如下:
(bookmarks)laptop:~ zenx$
在終端中安裝Django並啟動bookmarks
項目:
pip install Django==2.0.5
django-admin startproject bookmarks
然后到項目根目錄內創建account
應用:
cd bookmarks/
django-admin startapp account
然后在settings.py
中的INSTALLED_APPS
設置中激活該應用:
INSTALLED_APPS = [
'account.apps.AccountConfig',
# ...
]
這里將我們的應用放在應用列表的最前邊,原因是:我們稍后會為自己的應用編寫驗證系統的模板,Django內置的驗證系統自帶了一套模板,如此設置可以讓我們的模板覆蓋其他應用中的模板設置。Django按照INSTALLED_APPS
中的順序尋找模板。
之后執行數據遷移過程。
譯者注:新創建的Django項目默認依然使用Python的SQLlite數據庫,建議讀者為每個項目配置一個新創建的數據庫。推薦使用上一章的PostgreSQL,因為本書之后還會使用PostgreSQL。
2使用Django內置驗證框架
django提供了一個驗證模塊框架,具備用戶驗證,會話控制(session),權限和用戶組功能並且自帶一組視圖,用於控制常見的用戶行為如登錄、登出、修改和重置密碼。
驗證模塊框架位於django.contrib.auth
,也被其他Django的contrib
庫所使用。在第一章里創建超級用戶的時候,就使用到了驗證模塊。
使用startproject
命令創建一個新項目時,驗證模塊默認已經被設置並啟用,包括INSTALLED_APPS
設置中的django.contrib.auth
應用,和MIDDLEWARE
設置中的如下兩個中間件:
AuthenticationMiddleware
:將用戶與HTTP請求聯系起來SessionMiddleware
:處理當前HTTP請求的session
中間件是一個類,在接收HTTP請求和發送HTTP響應的階段被調用,在本書的部分內容中會使用中間件,第十三章上線中會學習開發自定義中間件。
驗證模塊還包括如下數據模型:
User
:一個用戶數數據表,包含如下主要字段:username
,password
,email
,first_name
,last_name
和is_active
。Group
:一個用戶組表格Permission
:存放用戶和組的權限清單
驗證框架還包括默認的驗證視圖以及對應表單,稍后會使用到。
2.1創建登錄視圖
從這節開始使用Django的驗證模塊,一個登錄視圖需要如下功能:
- 通過用戶提交的表單獲取用戶名和密碼
- 將用戶名和密碼與數據庫中的數據進行匹配
- 檢查用戶是否處於活動狀態
- 通過在HTTP請求上附加session,讓用戶進入登錄狀態
首先需要創建一個登錄表單,在account
應用內創建forms.py
文件,添加以下內容:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
這是用戶輸入用戶名和密碼的表單。由於一般密碼框不會明文顯示,這里采用了widget=forms.PasswordInput
,令其在頁面上顯示為一個type="password"
的INPUT
元素。
然后編輯account
應用的views.py
文件,添加如下代碼:
from django.shortcuts import render, HttpResponse
from django.contrib.auth import authenticate, login
from .forms import LoginForm
def user_login(request):
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
user = authenticate(request, username=cd['username'], password=cd['password'])
if user is not None:
if user.is_active:
login(request, user)
return HttpResponse("Authenticated successfully")
else:
return HttpResponse("Disabled account")
else:
return HttpResponse("Invalid login")
else:
form = LoginForm()
return render(request, 'account/login.html', {'form': form})
這是我們的登錄視圖,其基本邏輯是:當視圖接受一個GET
請求,通過form = LoginForm()
實例化一個空白表單;如果接收到POST
請求,則進行如下工作:
- 通過
form = LoginForm(request.POST)
,使用提交的數據實例化一個表單對象。 - 通過調用
form.is_valid()
驗證表單數據。如果未通過,則將當前表單對象展示在頁面中。 - 如果表單數據通過驗證,則調用內置
authenticate()
方法。該方法接受request
對象,username
和password
三個參數,之后到數據庫中進行匹配,如果匹配成功,會返回一個User
數據對象;如果未找到匹配數據,返回None
。在匹配失敗的情況下,視圖返回一個登陸無效信息。 - 如果用戶數據成功通過匹配,則根據
is_active
屬性檢查用戶是否為活動用戶,這個屬性是Django內置User
模型的一個字段。如果用戶不是活動用戶,則返回一個消息顯示不活動用戶。 - 如果用戶是活動用戶,則調用
login()
方法,在會話中設置用戶信息,並且返回登錄成功的消息。
注意區分內置的authenticate()
和login()
方法。authenticate()
僅到數據庫中進行匹配並且返回User
數據對象,其工作類似於進行數據庫查詢。而login()
用於在當前會話中設置登錄狀態。二者必須搭配使用才能完成用戶名和密碼的數據驗證和用戶登錄的功能。
現在需要為視圖設置路由,在account
應用下創建urls.py
,添加如下代碼:
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.user_login, name='login'),
]
然后編輯項目的根ulrs.py
文件,導入include
並且增加一行轉發到account應用的二級路由配置:
from django.conf.urls import path, include
from django.contrib import admin
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('account.urls')),
]
之后需要配置模板。由於項目還沒有任何模板,可以先創建一個母版,在account
應用下創建如下目錄和文件結構:
templates/
account/
login.html
base.html
編輯base.html
,添加下列代碼:
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<span class="logo">Bookmarks</span>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
</body>
</html>
這是這個項目使用的母版。和上一個項目一樣使用了CSS文件,你需要把static
文件夾從源碼復制到account
應用目錄下。這個母版有一個title
塊和一個content
塊用於繼承。
譯者注:原書第一章使用了{% load static %}
,這里的模板使用了{% load staticfiles %}
,作者並沒有對這兩者的差異進行說明,讀者可以參考What is the difference between {% load staticfiles %} and {% load static %}。
之后編寫account/login.html
:
{% extends 'base.html' %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
<p>Please, use the following form to log-in:</p>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Log in"></p>
</form>
{% endblock %}
這是供用戶填寫登錄信息的頁面,由於表單通過Post
請求提交,所以需要{% csrf_token %}
。
我們的站點還沒有任何用戶,建立一個超級用戶,然后使用超級用戶到http://127.0.0.1:8000/admin/登錄,會看到默認的管理后台:
使用管理后台添加一個用戶,然后打開http://127.0.0.1:8000/account/login/,可以看到如下登錄界面:
填寫剛創建的用戶信息並故意留空表單然后提交,可以看到錯誤信息如下:
注意和第一章一樣,很可能一些現代瀏覽器會阻止表單提交,修改模板關閉表單的瀏覽器驗證即可。
再進行一些實驗,如果輸入不存在的用戶名或密碼,會得到無效登錄的提示,如果輸入了正確的信息,就會看到如下的登錄成功信息:
2.2使用內置驗證視圖
Django內置很多視圖和表單可供直接使用,上一節的登錄視圖就是一個很好的例子。在大多數情況下都可以使用Django內置的驗證模塊而無需自行編寫。
Django在django.contrib.auth.views
中提供了如下基於類的視圖供使用:
LoginView
:處理登錄表單填寫和登錄功能(和我們寫的功能類似)LogoutView
:退出登錄PaswordChangeView
:處理一個修改密碼的表單,然后修改密碼PasswordChangeDoneView
:成功修改密碼后執行的視圖PasswordResetView
:用戶選擇重置密碼功能執行的視圖,生成一個一次性重置密碼鏈接和對應的驗證token,然后發送郵件給用戶PasswordResetDoneView
:通知用戶已經發送給了他們一封郵件重置密碼PasswordResetConfirmView
:用戶設置新密碼的頁面和功能控制PasswordResetCompleteView
:成功重置密碼后執行的視圖
上邊的視圖列表按照一般處理用戶相關功能的順序列出相關視圖,在編寫帶有用戶功能的站點時可以參考使用。這些內置視圖的默認值可以被修改,比如渲染的模板位置和使用的表單等。
可以通過官方文檔https://docs.djangoproject.com/en/2.0/topics/auth/default/#all-authentication-views了解更多內置驗證視圖的信息。
2.3登錄與登出視圖
由於直接使用內置視圖和內置數據模型,所以不需要編寫模型與視圖,來為內置登錄和登出視圖配置URL,編輯account
應用的urls.py
文件,注釋掉之前的登錄方法,改成內置方法:
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
# path('login/', views.user_login, name='login'),
path('login/',auth_views.LoginView.as_view(),name='login'),
path('logout/',auth_views.LogoutView.as_view(),name='logout'),
]
現在我們把登錄和登出的URL導向了內置視圖,然后需要為內置視圖建立模板
在templates
目錄下新建registration
目錄,這個目錄是內置視圖默認到當前應用的模板目錄里尋找具體模板的位置。
django.contrib.admin
模塊中自帶一些驗證模板,用於管理后台使用。我們在INSTALLED_APPS
中將account
應用放到admin
應用的上邊,令django默認使用我們編寫的模板。
在templates/registration
目錄下創建login.html
並添加如下代碼:
{% extends 'base.html' %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
{% if form.errors %}
<p>
Your username and password didn't match.
Please try again.
</p>
{% else %}
<p>Please, use the following form to log-in:</p>
{% endif %}
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
<p><input type="submit" value="Log-in"></p>
</form>
</div>
{% endblock %}
這個模板和剛才自行編寫登錄模板很類似。內置登錄視圖默認使用django.contrib.auth.forms
里的AuthenticationForm
表單,通過檢查{% if form.errors %}
可以判斷驗證信息是否錯誤。注意我們添加了一個name
屬性為next
的隱藏<input>
元素,這是內置視圖通過Get
請求獲得並記錄next
參數的位置,用於返回登錄前的頁面,例如http://127.0.0.1:8000/account/login/?next=/account/
。
next
參數必須是一個URL地址,如果具有這個參數,登錄視圖會在登錄成功后將用戶重定向到這個參數的URL。
在registration
目錄下創建logged_out.html
:
{% extends 'base.html' %}
{% block title %}
Logged out
{% endblock %}
{% block content %}
<h1>Logged out</h1>
<p>You have been successfully logged out. You can <a href="{% url 'login' %}">log-in again</a>.</p>
{% endblock %}
這是用戶登出之后顯示的提示頁面。
現在我們的站點已經可以使用用戶登錄和登出的功能了。現在還需要為用戶制作一個登錄成功后自己的首頁,打開account
應用的views.py
文件,添加如下代碼:
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
return render(request, 'account/dashboard.html', {'section': 'dashboard'})
使用@login_required
裝飾器,表示被裝飾的視圖只有在用戶登錄的情況下才會被執行,如果用戶未登錄,則會將用戶重定向至Get
請求附加的next
參數指定的URL。這樣設置之后,如果用戶在未登錄的情況下,無法看到首頁。
還定義了一個參數section
,可以用來追蹤用戶當前所在的功能板塊。
現在可以創建首頁對應的模板,在templates/account/
目錄下創建dashboard.html
:
{% extends 'base.html' %}
{% block title %}
Dashboard
{% endblock %}
{% block content %}
<h1>Dashboard</h1>
<p>Welcome to your dashboard.</p>
{% endblock %}
然后在account
應用的urls.py
里增加新視圖對應的URL:
urlpatterns = [
# ...
path('', views.dashboard, name='dashboard'),
]
還需要在settings.py里增加如下設置:
LOGIN_REDIRECT_URL = 'dashboard'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'
這三個設置分別表示:
- 如果沒有指定
next
參數,登錄成功后重定向的URL - 用戶需要登錄的情況下被重定向到的URL地址(例如
@login_required
重定向到的地址) - 用戶需要登出的時候被重定向到的URL地址
這里都使用了path()
方法中的name
屬性,以動態的返回鏈接。在這里也可以硬編碼URL。
總結一下我們現在做過的工作:
- 為項目添加內置登錄和登出視圖
- 為兩個視圖編寫模板並編寫了首頁視圖和對應模板
- 為三個視圖配置了URL
最后需要在母版上添加登錄和登出相關的展示。為了實現這個功能,必須根據當前用戶是否登錄,決定模板需要展示的內容。在內置函數LoginView
成功執行之后,驗證模塊的中間件在HttpRequest
對象上設置了用戶對象User
,可以通過request.user
訪問用戶信息。在用戶未登錄的情況下,request.user
也存在,是一個AnonymousUser
類的實例。判斷當前用戶是否登錄最好的方式就是判斷User
對象的is_authenticated
只讀屬性。
編輯base.html
,修改ID為header
的<div>
標簽:
<div id="header">
<span class="logo">Bookmarks</span>
{% if request.user.is_authenticated %}
<ul class="menu">
<li {% if section == 'dashboard' %}class="selected"{% endif %}><a href="{% url 'dashboard' %}">My dashboard</a></li>
<li {% if section == 'images' %}class="selected"{% endif %}><a href="#">Images</a></li>
<li {% if section == 'people' %}class="selected"{% endif %}><a href="#">People</a></li>
</ul>
{% endif %}
<span class="user">
{% if request.user.is_authenticated %}
Hello {{ request.user.first_name }},{{ request.user.username }},<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Log-in</a>
{% endif %}
</span>
</div>
上邊的視圖只顯示站點的菜單給已登錄用戶。還添加了了根據section
的內容為<li>
添加CSS類selected
的功能,用於顯示高亮當前的板塊。最后對登錄用戶顯示名稱和登出鏈接,對未登錄用戶則顯示登錄鏈接。
現在啟動項目,到http://127.0.0.1:8000/account/login/,會看到登錄頁面,輸入有效的用戶名和密碼並點擊登錄按鈕,之后會看到如下頁面:
可以看到當前的 My dashboard 應用了selected
類的CSS樣式。當前用戶的信息顯示在頂部的右側,點擊登出鏈接,會看到如下頁面:
可以看到用戶已經登出,頂部的菜單欄已經不再顯示,右側的鏈接變為登錄鏈接。
如果這里看到Django內置的管理站點樣式的頁面,檢查settings.py
文件中的INSTALLED_APPS
設置,確保account
應用在django.contrib.admin
應用的上方。由於內置的視圖和我們自定義的視圖使用了相同的相對路徑,Django的模板加載器會使用先找到的模板。
2.4修改密碼視圖
在用戶登錄之后需要允許用戶修改密碼,我們在項目中集成Django的內置修改密碼相關的視圖。編輯account
應用的urls.py
文件,添加如下兩行URL:
path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
asswordChangeView
視圖會控制渲染修改密碼的頁面和表單,PasswordChangeDoneView
視圖在成功修改密碼之后顯示成功消息。
之后要為兩個視圖創建模板,在templates/registration/
目錄下創建password_change_form.html
,添加如下代碼:
{% extends 'base.html' %}
{% block title %}
Change your password
{% endblock %}
{% block content %}
<h1>Change your password</h1>
<p>Use the form below to change your password.</p>
<form action="." method="post" novalidate>
{{ form.as_p }}
<p><input type="submit" value="Change"></p>
{% csrf_token %}
</form>
{% endblock %}
password_change_form.html
模板包含修改密碼的表單,再在同一目錄下創建password_change_done.html
:
{% extends 'base.html' %}
{% block title %}
Password changed
{% endblock %}
{% block content %}
<h1>Password changed</h1>
<p>Your password has been successfully changed.</p>
{% endblock %}
password_change_done.html
模板包含成功創建密碼后的提示消息。
啟動服務,到http://127.0.0.1:8000/account/password_change/,成功登錄之后可看到如下頁面:
填寫表單並修改密碼,之后可以看到成功消息:
之后登出再登錄,驗證是否確實成功修改密碼。
2.5重置密碼視圖
編輯account
應用的urls.py
文件,添加如下對應到內置視圖的URL:
path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
然后在account
應用的templates/registration/
目錄下創建password_reset_form.html
:
{% extends 'base.html' %}
{% block title %}
Reset your password
{% endblock %}
{% block content %}
<h1>Forgotten your password?</h1>
<p>Enter your e-mail address to obtain a new password.</p>
<form action="." method="post" novalidate>
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Send e-mail"></p>
</form>
{% endblock %}
在同一目錄下創建發送郵件的頁面password_reset_email.html
,添加如下代碼:
Someone asked for password reset for email {{ email }}. Follow the link
below:
{{ protocol }}://{{ domain }}{% url "password_reset_confirm" uidb64=uid token=token %}
Your username, in case you've forgotten: {{ user.get_username }}
這個模板用來渲染向用戶發送的郵件內容。
之后在同一目錄再創建password_reset_done.html
,表示成功發送郵件的頁面:
{% extends 'base.html' %}
{% block title %}
Reset your password
{% endblock %}
{% block content %}
<h1>Reset your password</h1>
<p>We've emailed you instructions for setting your password.</p>
<p>If you don't receive an email, please make sure you've entered the
address you registered with.</p>
{% endblock %}
然后創建重置密碼的頁面password_reset_confirm.html
,這個頁面是用戶從郵件中打開鏈接后經過視圖處理后返回的頁面:
{% extends 'base.html' %}
{% block title %}Reset your password{% endblock %}
{% block content %}
<h1>Reset your password</h1>
{% if validlink %}
<p>Please enter your new password twice:</p>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Change my password"/></p>
</form>
{% else %}
<p>The password reset link was invalid, possibly because it has
already been used. Please request a new password reset.</p>
{% endif %}
{% endblock %}
這個頁面里有一個變量validlink
,表示用戶點擊的鏈接是否有效,由PasswordResetConfirmView
視圖傳入模板。如果有效就顯示重置密碼的表單,如果無效就顯示一段文字說明鏈接無效。
在同一目錄內建立password_reset_complete.html
:
{% extends "base.html" %}
{% block title %}Password reset{% endblock %}
{% block content %}
<h1>Password set</h1>
<p>Your password has been set. You can <a href="{% url "login" %}">log in
now</a></p>
{% endblock %}
最后編輯registration/login.html
,在<form>
元素之后加上如下代碼,為頁面增加重置密碼的鏈接:
<p><a href="{% url 'password_reset' %}">Forgotten your password?</a></p>
之后在瀏覽器中打開http://127.0.0.1:8000/account/login/,點擊Forgotten your password?鏈接,會看到如下頁面:
這里必須在settings.py
中配置SMTP服務器,在第二章中已經學習過配置STMP服務器的設置。如果確實沒有SMTP服務器,可以增加一行:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
以讓Django將郵件內容輸出到命令行窗口中。
返回瀏覽器,填入一個已經存在的用戶的電子郵件地址,之后點SEND E-MAIL按鈕,會看到如下頁面:
此時看一下啟動Django站點的命令行窗口,會打印如下郵件內容(或者到信箱中查看實際收到的電子郵件):
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on 127.0.0.1:8000
From: webmaster@localhost
To: user@domain.com
Date: Fri, 15 Dec 2017 14:35:08 -0000
Message-ID: <20150924143508.62996.55653@zenx.local>
Someone asked for password reset for email user@domain.com. Follow the link
below:
http://127.0.0.1:8000/account/reset/MQ/45f-9c3f30caafd523055fcc/
Your username, in case you've forgotten: zenx
這個郵件的內容就是password_reset_email.html
經過渲染之后的實際內容。其中的URL指向視圖動態生成的鏈接,將這個URL復制到瀏覽器中打開,會看到如下頁面:
這個頁面使用password_reset_confirm.html
模板生成,填入一個新密碼然后點擊CHANGE MY PASSWORD按鈕,Django會用你輸入的內容生成加密后的密碼保存在數據庫中,然后會看到如下頁面:
現在就可以使用新密碼登錄了。這里生成的鏈接只能使用一次,如果反復打開該鏈接,會收到無效鏈接的錯誤。
我們現在已經集成了Django內置驗證模塊的主要功能,在大部分情況下,可以直接使用內置驗證模塊。也可以自行編寫所有的驗證程序。
在第一個項目中,我們提到為應用配置單獨的二級路由,有助於應用的復用。現在的account
應用的urls.py
文件中所有配置到內置視圖的URL,可以用如下一行來代替:
urlpatterns = [
# ...
path('', include('django.contrib.auth.urls')),
]
可以在github上看到django.contrib.auth.urls
的源代碼:https://github.com/django/django/blob/stable/2.0.x/django/contrib/auth/urls.py。
3用戶注冊與用戶信息
已經存在的用戶現在可以登錄、登出、修改和重置密碼了。現在需要建立一個功能讓用戶注冊。
3.1用戶注冊
為用戶注冊功能創建一個簡單的視圖:先建立一個供用戶輸入用戶名、姓名和密碼的表單。編輯account
應用的forms.py
文件,添加如下代碼:
from django.contrib.auth.models import User
class userRegistrationForm(forms.ModelForm):
password = forms.CharField(label='password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('username','first_name','email')
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError(r"Password don't match.")
return cd['password2']
這里通過用戶模型建立了一個模型表單,只包含username
,first_name
和email
字段。這些字段會根據User
模型中的設置進行驗證,比如如果輸入了一個已經存在的用戶名,則驗證不會通過,因為username
字段被設置了unique=True
。添加了兩個新的字段password
和password2
,用於用戶輸入並且確認密碼。定義了一個clean_password2()
方法用於檢查兩個密碼是否一致,這個方法是一個驗證器方法,會在調用is_valid()
方法的時候執行。可以對任意的字段采用clean_<fieldname>()
方法名創建一個驗證器。Forms類還擁有一個clean()
方法用於驗證整個表單,可以方便的驗證彼此相關的字段。
譯者注:這里必須了解表單的驗證順序。clean_password2()
方法中使用了cd['password2']
;為什么驗證器還沒有執行完畢的時候,cleaned_data
中已經存在password2
數據了呢?這里有一篇介紹django驗證表單順序的文章,可以看到,在執行自定義驗證器之前,已經執行了每個字段的clean()
方法,這個方法僅針對字段本身的屬性進行驗證,只要這個通過了,cleaned_data
中就有了數據,之后才執行自定義驗證器,最后執行form.clean()
完成驗證。如果過程中任意時候拋出ValidationError
,cleaned_data
里就會只剩有效的值,errors
屬性內就有了錯誤信息。
關於用戶注冊,Django提供了一個位於django.contrib.auth.forms
的UserCreationForm
表單供使用,和我們自行編寫的表單非常類似。
編輯account
應用的views.py
文件,添加如下代碼:
from .forms import LoginForm, UserRegistrationForm
def register(request):
if request.method == "POST":
user_form = UserRegistrationForm(request.POST)
if user_form.is_valid():
# 建立新數據對象但是不寫入數據庫
new_user = user_form.save(commit=False)
# 設置密碼
new_user.set_password(user_form.cleaned_data['password'])
# 保存User對象
new_user.save()
return render(request, 'account/register_done.html', {'new_user': new_user})
else:
user_form = UserRegistrationForm()
return render(request, 'account/register.html', {'user_form': user_form})
這個視圖邏輯很簡單,我們使用了set_password()
方法設置加密后的密碼。
再配置account
應用的urls.py
文件,添加如下的URL匹配:
path('register/', views.register, name='register'),
在templates/account/
目錄下創建模板register.html
,添加如下代碼:
{% extends 'base.html' %}
{% block title %}
Create an account
{% endblock %}
{% block content %}
<h1>Create an account</h1>
<p>Please, sign up using the following form:</p>
<form action="." method="post" novalidate>
{{ user_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Register"></p>
</form>
{% endblock %}
在同一目錄下創建register_done.html
模板,用於顯示注冊成功后的信息:
{% extends 'base.html' %}
{% block title %}
Welcome
{% endblock %}
{% block content %}
<h1>Welcome {{ new_user.first_name }}!</h1>
<p>Your account has been successfully created. Now you can <a href="{% url 'login' %}">log in</a>.</p>
{% endblock %}
現在可以打開http://127.0.0.1:8000/account/register/,看到注冊界面如下:
填寫表單並點擊CREATE MY ACCOUNT按鈕,如果表單正確提交,會看如下成功頁面:
3.2擴展用戶模型
Django內置驗證模塊的User模型只有非常基礎的字段信息,可能需要額外的用戶信息。最好的方式是建立一個用戶信息模型,然后通過一對一關聯字段,將用戶信息模型和用戶模型聯系起來。
編輯account
應用的models.py
文件,添加以下代碼:
from django.db import models
from django.conf import settings
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date_of_birth = models.DateField(blank=True, null=True)
photo = models.ImageField(upload_to='user/%Y/%m/%d/', blank=True)
def __str__(self):
return "Profile for user {}".format(self.user.username)
為了保持代碼通用性,使用get_user_model()
方法來獲取用戶模型;當定義其他表與內置User
模型的關系時使用settings.AUTH_USER_MODEL
指代User
模型。
這個Profile
模型的user
字段是一個一對一關聯到用戶模型的關系字段。將on_delete
設置為CASCADE
,當用戶被刪除時,其對應的信息也被刪除。這里還有一個圖片文件字段,必須安裝Python的Pillow
庫才能使用圖片文件字段,在系統命令行中輸入:
pip install Pillow==5.1.0
由於我們要允許用戶上傳圖片,必須配置Django讓其提供媒體文件服務,在settings.py
中加入下列內容:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL
表示存放和提供用戶上傳文件的URL路徑,MEDIA_ROOT
表示實際媒體文件的存放目錄。這里都采用相對地址動態生成URL。
來編輯一下bookmarks
項目的根urls.py
,修改其中的代碼如下:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('account.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
這樣設置后,Django開發服務器在DEBUG=True
的情況下會提供媒體文件服務。
static()
方法僅用於開發環境,在生產環境中,不要用Django提供靜態文件服務(而是用Web服務程序比如NGINX等提供靜態文件服務)。
建立了新的模型之后需要執行數據遷移過程。之后將新的模型加入到管理后台,編輯account
應用的admin.py
文件,將Profile
模型注冊到管理后台中:
from django.contrib import admin
from .models import Profile
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
list_display = ['user', 'date_of_birth', 'photo']
啟動站點,打開http://127.0.0.1:8000/admin/,可以在管理后台中看到新增的模型:
現在需要讓用戶填寫額外的用戶信息,為此需要建立表單,編輯account
應用的forms.py
文件:
from .models import Profile
class UserEditForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class ProfileEditForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('date_of_birth', 'photo')
這兩個表單解釋如下:
UserEditForm
:這個表單依據User
類生成,讓用戶輸入姓,名和電子郵件。ProfileEditForm
:這個表單依據Profile
類生成,可以讓用戶輸入生日和上傳一個頭像。
之后建立視圖,編輯account
應用的views.py
文件,導入Profile
模型:
from .models import Profile
然后在register
視圖的new_user.save()
下增加一行:
Profile.objects.create(user=new_user)
當用戶注冊的時候,會自動建立一個空白的用戶信息關聯到用戶。在之前創建的用戶,則必須在管理后台中手工為其添加對應的Profile
對象
還必須讓用戶可以編輯他們的信息,在同一個文件內添加下列代碼:
from .forms import LoginForm, UserRegistrationForm, UserEditForm, ProfileEditForm
@login_required
def edit(request):
if request.method == "POST":
user_form = UserEditForm(instance=request.user, data=request.POST)
profile_form = ProfileEditForm(instance=request.user.profile, data=request.POST, files=request.FILES)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
else:
user_form = UserEditForm(instance=request.user)
profile_form = ProfileEditForm(instance=request.user.profile)
return render(request, 'account/edit.html', {'user_form': user_form, 'profile_form': profile_form})
這里使用了@login_required
裝飾器,因為用戶必須登錄才能編輯自己的信息。我們使用UserEditForm表單存儲內置的User類的數據,用ProfileEditForm
存放Profile
類的數據。然后調用is_valid()
驗證兩個表單數據,如果全部都通過,將使用save()
方法寫入數據庫。
譯者注:原書沒有解釋instance
參數。instance
用於指定表單類實例化為某個具體的數據對象。在這個例子里,將UserEditForm``instance
指定為request.user表示該對象是數據庫中當前登錄用戶那一行的數據對象,而不是一個空白的數據對象,ProfileEditForm
的instance
屬性指定為當前用戶對應的Profile
類中的那行數據。這里如果不指定instance
參數,則變成向數據庫中增加兩條新記錄,而不是修改原有記錄。
之后編輯account
應用的urls.py
文件,為新視圖配置URL:
path('edit/', views.edit, name='edit'),
最后,在templates/account/
目錄下創建edit.html
,添加如下代碼:
{#edit.html#}
{% extends 'base.html' %}
{% block title %}
Edit your account
{% endblock %}
{% block content %}
<h1>Edit your account</h1>
<p>You can edit your account using the following form:</p>
<form action="." method="post" enctype="multipart/form-data" novalidate>
{{ user_form.as_p }}
{{ profile_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Save changes"></p>
</form>
{% endblock %}
由於這個表單可能處理用戶上傳頭像文件,所以必須設置enctype="multipart/form-data
。我們采用一個HTML表單同時提交user_form
和profile_form
表單。
啟動站點,注冊一個新用戶,然后打開http://127.0.0.1:8000/account/edit/,可以看到頁面如下:
現在可以在用戶登錄后的首頁加上修改用戶信息的鏈接了,打開account/dashboard.html
,找到下邊這行:
<p>Welcome to your dashboard.</p>
將其替換為:
<p>Welcome to your dashboard. You can <a href="{% url 'edit' %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>
用戶現在可以通過登錄后的首頁修改用戶信息,打開http://127.0.0.1:8000/account/然后可以看到新增了修改用戶信息的鏈接,頁面如下:
3.2.1使用自定義的用戶模型
Django提供了使用自定義的模型替代內置User
模型的方法,需要編寫自定義的類繼承AbstractUser
類。這個AbstractUser
類提供了默認的用戶模型的完整實現,作為一個抽象類供其他類繼承。關於模型的繼承將在本書最后一個項目中學習。可以在https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#substituting-a-custom-user-model找到關於自定義用戶模型的詳細信息。
使用自定義用戶模型比起默認內置用戶模型可以更好的滿足開發需求,但需要注意的是會影響一些使用Django內置用戶模型的第三方應用。
3.3使用消息框架
當用戶在我們的站點執行各種操作時,在一些關鍵操作可能需要通知用戶其操作是否成功。Django有一個內置消息框架可以給用戶發送一次性的通知。
消息模塊位於django.contrib.messages
,並且已經被包含在初始化的INSTALLED_APPS
設置中,還有一個默認啟用的中間件叫做django.contrib.messages.middleware.MessageMiddleware
,共同構成了消息系統。
消息框架提供了非常簡單的方法向用戶發送通知:默認在cookie中存儲消息內容(根據session的存儲設置),然后會在下一次HTTP請求的時候在對應的響應上附加該信息。導入消息模塊並且在視圖中使用很簡單的語句就可以發送消息,例如:
from django.contrib import messages
messages.error(request, 'Something went wrong')
這樣就在請求上附加了一個錯誤信息。可以使用add_message()
或如下的方法創建消息:
success()
:一個動作成功之后發送的消息info()
:通知性質的消息warning()
:警告性質的內容,所謂警告就是還沒有失敗但很可能失敗的情況error()
:錯誤信息,通知操作失敗debug()
:除錯信息,給開發者展示,在生產環境中需要被移除
在我們的站點中增加消息內容。由於消息是貫穿整個網站的,所以打算將消息顯示的部分設置在母版中,編輯base.html
,在ID為header
的<div>
標簽和ID為content
的<div>
標簽之間增加下列代碼:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">{{ message|safe }}<a href="#" class="close">X</a></li>
{% endfor %}
</ul>
{% endif %}
在模板中使用了messages
變量,在后文可以看到視圖並未向模板傳入該變量。這是因為在settings.py
中的TEMPLATES
設置中,context_processors
的設置中包含django.contrib.messages.context_processors.messages
這個上下文管理器,從而為模板傳入了messages
變量,而無需經過視圖。默認情況下可以看到還有debug
,request
和auth
三個上下文處理器。其中后兩個就是我們在模板中可以直接使用request.user
而無需傳入該變量,也無需為request
對象添加user
屬性的原因。
之后來修改account
應用的views.py
文件,導入messages
,然后編輯edit
視圖:
from django.contrib import messages
@login_required
def edit(request):
if request.method == "POST":
user_form = UserEditForm(instance=request.user, data=request.POST)
profile_form = ProfileEditForm(instance=request.user.profile, data=request.POST, files=request.FILES)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, 'Profile updated successfully')
else:
messages.error(request, "Error updating your profile")
else:
user_form = UserEditForm(instance=request.user)
profile_form = ProfileEditForm(instance=request.user.profile)
return render(request, 'account/edit.html', {'user_form': user_form, 'profile_form': profile_form})
為視圖增加了兩條語句,分別在成功登錄之后顯示成功信息,在表單驗證失敗的時候顯示錯誤信息。
瀏覽器中打開http://127.0.0.1:8000/account/edit/,編輯用戶信息,之后可以看到成功信息如下:
故意填寫通不過驗證的數據,則可以看到錯誤信息如下:
關於消息框架的更多信息,可以查看官方文檔:https://docs.djangoproject.com/en/2.0/ref/contrib/messages/。
4創建自定義驗證后端
Django允許對不同的數據來源采用不同的驗證方式。在settings.py
里有一個AUTHENTICATION_BACKENDS
設置列出了項目中可使用的驗證后端。其默認是:
['django.contrib.auth.backends.ModelBackend']
默認的ModelBackend
通過django.contrib.auth
后端進行驗證,這對於大部分項目已經足夠。然而我們也可以創建自定義的驗證后端,用於滿足個性化需求,比如LDAP目錄或者來自於其他系統的驗證。
關於自定義驗證后端可以參考官方文檔:https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#other-authentication-sources。
每次使用內置的authenticate()
函數時,Django會按照AUTHENTICATION_BACKENDS
設置中列出的順序,依次執行其中的驗證后端進行驗證工作,直到有一個驗證后端返回成功為止。如果列表中的后端全部返回失敗,則這個用戶就不會被認證通過。
Django提供了一個簡單的規則用於編寫自定義驗證后端:一個驗證后端必須是一個類,至少提供如下兩個方法:
authenticate()
:參數為request
和用戶驗證信息,如果用戶驗證信息有效,必須返回一個user
對象,否則返回None
。request
參數必須是一個HttpRequest
對象或者是None
get_user()
:參數為用戶的ID,返回一個user
對象
我們來編寫一個采用電子郵件(而不是username
字段)和密碼登錄的驗證后端,編寫驗證后端就和編寫一個Python的類沒有什么區別:
from django.contrib.auth.models import User
class EmailAuthBakcend:
"""
Authenticate using an e-mail address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return None
以上代碼是一個簡單的驗證后端。authenticate()
方法接受request
對象和username
及password
作為可選參數,這里可以用任何自定義的參數名稱,我們使用username
及password
是為了可以與內置驗證框架配合工作。兩個方法工作流程如下:
authenticate()
:嘗試使用電子郵件和密碼獲取用戶對象,采用check_password()
方法驗證加密后的密碼。get_user()
:通過user_id
參數獲取用戶ID,在會話存續Django會使用內置的驗證后端去驗證並取得User
對象。
編輯settings.py文件增加:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'account.authentication.EmailAuthBackend',
]
在上邊的設置里,我們將自定義驗證后端加到了默認驗證的后邊。打開http://127.0.0.1:8000/account/login/,注意Django嘗試使用所有的驗證后端,所以我們現在可以使用用戶名或者電子郵件來登錄,填寫的信息會先交給ModelBackend
進行驗證,如果沒有得到用戶對象,就會使用我們的EmailAuthBackend
進行驗證。
AUTHENTICATION_BACKENDS
中的順序很重要,如果一個用戶信息對於多個驗證后端都有效,Django會停止在第一個成功驗證的后端處。
5第三方認證登錄
很多社交網站除了注冊用戶之外,提供了鏈接可以快速的通過第三方平台的用戶信息進行登錄,我們也可以為自己的站點添加例如Facebook,Twitter或Google的第三方認證登錄功能。Python Social Auth是一個提供第三方認證登錄的模塊。使用這個模塊可以讓用戶以第三方網站的信息進行登錄,而無需先注冊本網站的用戶。這個模塊的源碼在https://github.com/python-social-auth。
這個模塊支持很多不同的Python Web框架,其中也包括Django,通過以下命令安裝:
pip install social-auth-app-django==2.1.0
然后將應用名social_django
添加到settings.py
文件的INSTALLED_APPS
設置中:
INSTALLED_APPS = [
#...
'social_django',
]
該應用自帶了數據模型,所以需要執行數據遷移過程。執行之后可以在數據庫中看到新增social_auth
開頭的一系列數據表。Python 的social auth模塊具體支持的第三方驗證服務,可以查看官方文檔:https://python-social-auth.readthedocs.io/en/latest/backends/index.html#supported-backends。
譯者注:Facebook,Twitter和Google的第三方驗證均通過OAuth2認證,而且操作方式基本相同。以下僅以Google為例子進行翻譯:
需要先把第三方認證的URL添加到項目中,編輯bookmarks
項目的根urls.py
:
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('account.urls')),
path('social-auth/', include('social_django.urls', namespace='social')),
]
一些網站的第三方驗證接口不允許將驗證后的地址重定向到類似127.0.0.1
或者localhost
這種本地地址,為了正常使用第三方驗證服務,需要一個正式域名,可以通過修改Hosts文件。如果是Linux或macOS X下,可以編輯/etc/hosts
加入一行:
127.0.0.1 mysite.com
這樣會將mysite.com
域名對應到本機地址。如果是Windows環境,可以在C:\Windows\System32\Drivers\etc\hosts
找到hosts
文件。
為了測試該設置是否生效,啟動站點然后在瀏覽器中打開http://mysite.com:8000/account/login/,會得到如下錯誤信息:
這是因為Djanog在settings.py
中的ALLOWED_HOSTS
設置中,僅允許對此處列出的域名提供服務,這是為了防止HTTP請求頭攻擊。關於該設置可以參考官方文檔:https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts。
編輯settings.py
文件然后修改ALLOWED_HOSTS
為如下:
ALLOWED_HOSTS = ['mysite.com', 'localhost', '127.0.0.1']
在mysite.com
之外,我們增加了localhost
和127.0.0.1
,其中localhost
是在DEBUG=True
和ALLOWED_HOSTS
留空情況下的默認值,現在就可以通過http://mysite.com:8000/account/login/正常訪問開發網站了。
5.1使用Google第三方認證
Google提供OAuth2認證,詳細文檔可以參考:https://developers.google.com/identity/protocols/OAuth2。
為使用Google的第三方認證服務,將以下驗證后端添加到settings.py
的AUTHENTICATION_BACKENDS
中:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'account.authentication.EmailAuthBackend',
'social_core.backends.google.GoogleOAuth2',
]
譯者注:由於Google API的界面在原書成書后已經改變,以下在Google網站的操作步驟和截圖來自於譯者實際操作過程。
需要到Google開發者網站創建一個API key,按照以下步驟操作:
- 打開https://console.developers.google.com/apis/credentials,點擊屏幕左上方
Google APIs
字樣右邊的選擇項目
,會彈出項目對話框,點擊右上方的新建項目
,如圖所示:
- 填寫新建項目的信息,項目名稱為
Bookmarks
,位置可以不選,之后點擊創建按鈕,如下圖所示:
- 之后與步驟1中的步驟類似,點開
選擇項目
,選中剛建立的Bookmarks
項目,然后點擊右下方的打開
。 - 會自動跳轉到一個頁面提示尚未創建API憑據,點擊頁面中的
創建憑據
按鈕,並選擇第二項OAuth客戶端ID
,如下圖所示:
- 之后會進入一個界面,要求必須配置OAuth同意屏幕,如下圖所示:
點擊右側的配置同意屏幕
按鈕。
- 之后進入到OAuth同意屏幕,里邊有一系列設置。在應用名稱中填入
Bookmarks
,默認支持電子郵件
為你自己的電子郵件地址,可以修改為其他地址,在已獲授權的網域
中填入mysite.com
,之后點擊保存,如圖所示:
- 此時會跳轉到步驟5的問題頁面,選擇
網頁應用
,之后會被要求填寫輔助信息,在名稱
中填寫Bookmarks
,已獲授權的重定向 URI
中填寫http://mysite.com:8000/social-auth/complete/google-oauth2/
,如下圖所示:
- 點擊
創建
按鈕,即可在頁面中看到當前API的ID和密鑰,如圖所示:
- 將API ID 和密鑰填寫到settings.py文件中,增加如下兩行:
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'XXX' # API ID
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'XXX' # 密鑰
- 點擊
確認
關閉對話框,之后在左側菜單的憑據
菜單內可以回到此處查看ID和密鑰。現在點擊左側菜單的庫
,會跳轉到歡迎使用新版API庫的界面,在其中找到Google+ API,如圖所示:
- 點擊Google+ API,在彈出的頁面中選擇啟用,如圖所示:
在Google中的配置就全部結束了,生成了一個OAuth2認證的ID和密鑰,之后我們就將采用這些信息與Google進行通信。
然后編輯account
應用的registration/login.html
模板,在content
塊的內部最下方增加用於進行Google第三方認證登錄的鏈接:
<div class="social">
<ul>
<li class="google"><a href="{% url 'social:begin' 'google-oauth2' %}">Log in with Google</a></li>
</ul>
</div>
打開http://mysite.com:8000/account/login/,可以看到如下頁面:
點擊Login with Google按鈕,使用Google賬戶登錄后,就會被重定向到我們網站的登錄首頁。
我們現在就為項目增加了第三方認證登錄功能,即使是沒有在本站注冊的用戶,也可以快捷的進行登錄了。
譯者注:這里有一個小問題,就是通過第三方登錄進來的用戶,檢查auth_user
表會發現其實用戶信息已經被寫入到了該表里,但是Profile
表沒有寫入對應的外鍵字段,導致第三方認證用戶在修改用戶信息時會報錯。很多網站的做法是:通過第三方驗證進來的用戶,必須捆綁到本站已經存在的賬號中。這里我們簡化一下處理,當用戶修改字段的Get
請求進來時,檢測Profile
表中該用戶的外鍵是不是存在,如果不存在,就新建對應該用戶的Profile
對象,然后再用這個數據對象返回表單實例供填寫。修改后的edit
視圖如下:
@login_required
def edit(request):
if request.method == "POST":
user_form = UserEditForm(instance=request.user, data=request.POST)
profile_form = ProfileEditForm(instance=request.user.profile, data=request.POST, files=request.FILES)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, 'Profile updated successfully')
else:
messages.error(request, "Error updating your profile")
else:
try:
Profile.objects.get(user=request.user)
except Profile.DoesNotExist:
Profile.objects.create(user=request.user)
user_form = UserEditForm(instance=request.user)
profile_form = ProfileEditForm(instance=request.user.profile)
return render(request, 'account/edit.html', {'user_form': user_form, 'profile_form': profile_form})
總結
這一章學習了使用內置框架快捷的建立用戶驗證系統,以及建立自定義的用戶信息,還學習了為網站添加第三方認證。
下一章中將學習建立一個圖片分享系統,生成圖片縮略圖,以及在Djanog中使用AJAX技術。