本系列是對入門書籍《Python編程:從入門到實踐》的筆記整理,屬於初級內容。標題順序採用書中標題。
本篇記錄如何創建用戶註冊系統,如何實現用戶輸入自己的數據。
1. 前言
在本篇中,我們將:
實現一個身份驗證系統。
2. 讓用戶能夠輸入數據
2.1 添加新主題
和之前創建網頁的步驟一樣:定義URL,編寫視圖函數,編寫模板。主要區別是,這裡需要一個包含表單的模塊forms.py
2.1.1 創建forms.py模塊
用戶輸入信息時,需要進行驗證,確保提交的信息是正確的數據類型,且不是惡意信息,如中斷服務器的代碼。然後再處理信息,並保存到數據庫中。當然,這些工作很多都由Django自動完成。
在models.py所在的目錄中新建forms.py模塊。創建表單的最簡單方法是繼承Django的ModelForm類:
最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪個模型創建表單,以及在表單中包含哪些字段。第6行,我們根據Topic創建一個表單,該表單只包含字段text(第7行),並不為該字段生成標籤(第8行)。
2.1.2 URL模式new_topic
當用戶要添加新主題時,將切換到http://localhost:8000/new_topic/ 。在learning_logs/urls.py中添加如下代碼:
2.1.3 視圖函數new_topic()
該函數需要處理兩種情形:①剛進入new_topic網頁,顯示一個空表單;②對提交的表單數據進行處理,並將用戶重定向到網頁topics。修改views.py文件:
2.1.4 GET請求和POST請求
創建Web應用程序時,將用到兩種主要數據請求類型:GET請求和POST請求。從這倆英文單詞可以看出,如果只從服務器讀取數據頁面,則使用GET請求;如果要提交用戶填寫的表單,通常使用POST請求。當然還有一些其他的請求類型,但這個項目中沒有使用。本項目中處理表單都使用POST方法。
request.method存儲了請求的類型(第7行代碼)。
當不是POST請求時,我們生成一個空表單傳遞給模板new_topic.html,然後返回給用戶;當請求是POST時,我們從request.POST這個變量中獲取用戶提交的數據,並暫存到form變量中。
通過is_valid()方法驗證表單數據是否滿足要求:用戶是否填寫了所有必不可少的字段(表單字段默認都是必填的),且輸入的數據與字段類型是否一致。當然這些驗證都是Django自動進行的。如果表單有效,在通過form的save()方法存儲到數據庫,然後通過reverse()函數獲取頁面topics的URL,並將其傳遞給HTTPResponseRedirect()以重定向到topics頁面。如果表單無效,把這些數據重新傳回給用戶。
2.1.5 模板new_topic.html
模板繼承了base.html,因此其基本結構和項目中的其他頁面相同。第6行中,參數action告訴服務器將提交的表單數據送到什麼位置去處理,參數method讓瀏覽器以POST請求的方式提交數據。
Django顯示錶單非常方便:只需要使用模板變量{{ form.as_p }},修飾符as_p讓Django以段落格式渲染所有表單元素,這是一種整潔地顯示錶單的簡單方法。
Django不自動創建提交表單的按鈕,需自行創建。
2.1.6 鏈接到頁面new_topic
在頁面topics.html中添加一個到頁面new_topic的鏈接:
2.1.7 效果
以下是實際效果圖:
通過這個頁面,隨意添加幾個主題,如下:
2.2 添加新條目
和前面的步驟相似:創建條目表單,添加URL,添加視圖,添加模板,鏈接到頁面
2.2.1 創建條目表單
創建一個與模型Entry相關聯的表單,但這個表單的自定義程度比TopicForm要高些,依然是在剛才創建的forms.py中添加:
代碼中定義了屬性widgets。小部件(widget)是一個HTML表單元素,如單行文本框、多行文本框或下拉列表。通過設置屬性widgets可以覆蓋Django選擇的默認小部件。通過Django的forms.Textarea定製字段“text”的輸入小部件,將文本框的寬度設置為80列,而不是默認的40列。
2.2.2 添加URL模式new_entry
修改learning_logs/urls.py:
該URL模式與形式為http://localhost:8000/new_entry/topi_id/ 的URL匹配,其中topic_id是主題的ID。
2.2.3 視圖函數new_entry()
與函數new_topic()很像:
new_entry()的定義包含形參topic_id,用於存儲從URL中獲得的值。
在調用save()時傳遞了參數commit=False(第14行),它讓Django創建一個新的條目對象,但並不立刻提交數據庫,而是暫時存儲在變量new_entry中,待為這個新條目對象添加了屬性topic之後再提交數據庫。
在重定向時,reverse()函數中傳遞了兩個參數,URL模式的名稱以及列表args,args包含要包含在URL中的所有參數。
2.2.4 模板new_entry.html
類似於new_topic:
注意第4行代碼,改行代碼返回到特定主題頁面。
2.2.5 鏈接到頁面new_entry
在顯示特定主題的頁面中添加到頁面new_entry的鏈接,修改topic.html:
2.2.6 效果
下圖是實際效果,請隨意添加一些條目:
2.3.1 URL模式edit_entry
修改learning_logs/urls.py:
2.3.2 視圖函數edit_entry()
首先獲取要被修改的entry以及與該條目相關的主題。處理GET請求時,通過參數instance=entry創建EntryForm實例,該參數讓Django創建一個表單,並使用既有條目對象中的信息填充它。處理POST請求時,還傳入了data=request.POST參數,Django根據POST中的相關數據對entry進行修改。
2.3.3 模板edit_entry.html
2.3.4 鏈接到頁面edit_entry.html
在顯示特定主題的頁面中,需要給每個條目添加到頁面edit_entry.html的鏈接,為此,修改topic.html:
2.3.5 效果
以下是實際效果圖:
3. 創建用戶賬戶
現在開始建立一個用戶註冊和身份驗證系統。為此將創建一個新的應用程序,其中包含處理用戶賬戶相關的所有功能。對Topic模型也要做稍許修改,讓每個主題都歸屬於特定用戶。
3.1 創建應用程序users
希望大家還記得如何使用startapp命令還創建APP:
將APP添加到settings.py中
在APP根目錄下創建urls.py文件,並添加命名空間:
為APP定義URL,修改項目根目錄中的urls.py:
3.2 登陸頁面
使用Django提供的默認登陸視圖,URL模式會有所不同。在users中的urls.py中添加如下代碼:
代碼中,我們使用Django自帶的login視圖函數(注意,參數是login,而不是views.login)。從之前的例子可以看出,我們渲染模板的代碼都是在自己寫的視圖函數中。但這裡使用了自帶的視圖函數,無法自行編寫進行渲染的代碼。所以,我們還傳了一個字典給path,告訴Django到哪裡查找我們要用到的模板。注意,該模板在users中,而不是在learning_logs中。
3.2.1 新建模板login.html
在learning_log/users/templates/users中創建login.html:
如果表單的errors屬性被設置,則顯示一條提示賬號密碼錯誤的信息。
3.2.2 鏈接到登陸頁面
在base.html中添加到登陸頁面的鏈接,讓所有頁面都包含它。將這個鏈接嵌套在一個 if 標籤中,用戶已登錄時隱藏掉該鏈接:
在Django身份驗證系統中,每個模板都可使用變量user,這個變量有一個is_authenticated屬性:如果用戶已登錄,該屬性將為True,否則為False。
3.2.3 使用登陸頁面
首先訪問localhost:8000/admin註銷超級用戶,再訪問localhost:8000/users/login/,得到如下頁面:
3.3 註銷
並不為註銷創建單獨的頁面,而是讓用戶單擊一個連接用於註銷並返回主頁。因此,需要做如下工作:註銷URL模式,新建視圖,鏈接到註銷視圖。
在users/urls.py中添加與http://localhost:8000/users/logout/ 匹配的URL模式:
編寫視圖函數logout_view(),其中,直接調用Django自帶的logout()函數,該函數要求request作為參數:
在base.html中添加註銷鏈接:
3.4 註冊頁面
使用Django提供的表單UserCreationFrom,但編寫自己的視圖函數和模板。URL->view->template->link。
首先,創建註冊頁面的URL模式,修改users/urls.py:
其次,創建視圖register():
以上代碼在用戶成功創建了用戶後會自動登陸,該功能由login()函數實現。該函數將會為通過了身份驗證的用戶對象創建會話(session)。最後上述代碼重定向到主頁。
然後,編寫註冊頁面的模板register.html:
最後,在頁面中顯示註冊鏈接,修改base.html,在用戶沒有登錄時顯示註冊鏈接:
下面是實際效果:
這是直接點register按鈕時的反饋,不過這裡有點疑惑,從上面的register.html中看到,其實代碼很簡單,但這裡有個浮動效果,而且在註冊模板中並沒有像前面那樣的{% form.errors %}模板標籤,但依然有未註冊成功時的反應,而且註冊的視圖函數也是自己寫的,並不是用的自帶的註冊函數,所以不知道是不是和form.as_p有關。之後再慢慢研究吧,
4. 讓用戶擁有自己的數據
用戶應該能夠輸入其專有的數據,所以應該創建一個系統,確定各項數據所屬的用戶,再限制對頁面的訪問,使得用戶只能使用自己的數據,即訪問控制。
4.1 使用@login_required限制訪問
Django提供了裝飾器@login_required,使得能輕鬆實現用戶只能訪問自己能訪問的頁面。
限制對topics.html的訪問:
每個主題都歸特定用戶所有,所以需要加限制,修改learning_logs/views.py:
裝飾器也是一個函數,python在運行topics()前會先運行login_required()的代碼。
login_required()函數檢查用戶是否登錄,僅當用戶已登錄時,Django才運行topics()函數,若未登錄,就重定向到登陸界面。而為了實現這個重定向,還需要修改項目的settings.py文件,在該文件中添加這樣一個常量(其實也是變量),一般在文件末尾添加:
全面限制對項目“學習筆記”的訪問 :
Django能輕鬆地限制對頁面的訪問,但得自己設計需要限制哪些頁面。一般先確定哪些頁面不需要保護,再限制對其他頁面的訪問。在該項目中,我們不限制對主頁、註冊頁面和註銷鏈接的訪問,其他頁面均限制。在learning_logs/views.py中,除了index()外,每個視圖函數都加上@login_required。
4.2 將數據關聯到用戶
為了禁止用戶訪問其他用戶的數據,需要將用戶與數據關聯。只需要將最高層的數據關聯到用戶,這樣更低層的數據將自動關聯到用戶。下面修改Topic模型和相關視圖:
修改模型後,還需要遷移數據庫。此時,需要將主題與用戶關聯。這裡並沒有通過代碼進行關聯,我們在遷移數據庫時手動進行關聯。為此,我們需要先知道有哪些用戶,以及這些用戶的ID。我們通過Django shell查詢用戶信息,當然也可以直接查看數據庫,這裡不再演示。我們將主題都關聯到超級用戶ll_admin上,它的ID是1。現在我們執行數據遷移:
Django指出試圖給既有模型Topic添加一個必不可少(不可為空)的字段,而該字段沒有默認值,需要我們採取措施:要麼現在提供默認值,要麼退出並在models.py中添加默認值。我們選擇了直接輸入默認值。接下來,Django使用這個值來遷移數據庫,並生成了遷移文件0003_topic_owner.py,它在模型Topic中添加字段owner。
現在執行遷移命令:
執行後可以在Django shell中驗證是否遷移成功,這裡不再驗證。
4.3 只允許用戶訪問自己的主題
目前不管以哪個用戶身份登錄,都能看到所有主題。現在我們添加限制,讓用戶只能看到自己的主題。在views.py中,對topics()做如下修改:
用戶登錄後,request對象將有一個user屬性,這個屬性存儲了有關該用戶的信息。第5行代碼讓Django只從數據庫中讀取特定用戶的數據。
4.4 保護用戶的主題
上述代碼做到了登錄後只顯示相應用戶的數據,但是,如果登錄後直接通過URL訪問,如直接輸入http://localhost:8000/topics/1/ ,依然可以訪問不屬於自己的特定主題頁面。下面修改views.py中的topic()函數來加以限制:
4.5 保護頁面edit_entry
此時用戶也可以像上面一樣,登陸後直接通過URL來訪問edit_entry.html,現在我們對這個頁面也加以限制:
4.6 最後一步:將新主題關聯到當前用戶
當前用於添加新主題的頁面存在問題,因為它沒有將新主題關聯到特定用戶。如果此時嘗試添加新主題,將看到錯誤信息IntegrityError,指出learning_logs_topic.user_id不能為NULL,下面修改new_topic()函數:
現在,這個項目允許任何用戶註冊,而每個用戶想添加多少新主題都可以,每個用戶只能訪問自己的數據,無論是查看數據、輸入新數據還是修改舊數據時都是如此。
5. 小結
閱讀更多 VPointer701 的文章