一,項目題目: 開發用戶注冊與登錄系統
該項目主要練習使用Django開發一個用戶注冊與登錄的系統,通過這個項目然后鞏固自己這段時間所學習的Django知識。
在Django學習筆記(16)——擴展Django自帶User模型,實現用戶注冊與登錄,這篇博文中,我完成了使用Django自帶的Auth模型下的User模型而重新建立了自己的用戶模型。有興趣的可以看一下。
二,項目需求:
開發一個簡單的用戶登錄與注冊系統 要求: 有用戶注冊頁面 有用戶登錄頁面 有用戶登錄成功的頁面 有注冊和登錄的驗證碼提示
三,編碼規范需求:
編碼規范需求:15% 1. 代碼規范遵守pep8 (https://python.org/dev/peps/pep-0008/) 2. 函數有相應的注釋 3. 程序有文檔說明文件(README.md參考:https://github.com/csrftoken/vueDrfDemo) 4. 程序的說明文檔必須包含的內容:程序的開發環境(django版本)、程序的實現的功能、程序的啟動方式、登錄用戶信息、程序的運行效果 5. 程序設計的流程圖:
四,項目思路
4.1:設計數據模型
- 數據庫模式設計
- 設置數據庫為MySQL
- 數據庫遷移
4.2:admin后台
- 在admin中注冊模型
- 創建超級管理員
4.3:url路由和視圖
- 路由設計
- 架構初步視圖
- 創建HTML頁面文件
4.4:前端頁面設計
- 原生HTML頁面
- 引入Bootstrap
- 創建base.html模板
- 創建頁面導航條
- 使用Bootsrap靜態文件
- 設計登錄頁面
4.5:登錄視圖
- 登錄視圖
- 數據驗證
- 添加提示信息
4.6:Django表單
- 創建表單模型
- 修改視圖
- 修改login界面
- 手動渲染表單
4.7:圖片驗證碼
- 安裝captcha
- 添加url路由
- 修改myforms.py
- 修改login.html
4.8:session會話
- 使用session
- 完善頁面
4.9:注冊視圖
- 創建myforms
- 完善register.html
- 注冊視圖
- 密碼加密
五,注意事項
5.1:創建項目和APP
1,創建項目和APP django-admin startproject mysite python manage.py startapp project1 2,設置時區和語言 Django默認使用美國時間和英語,在項目的settings文件中,如下圖所示: LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True 我們將其改為 亞洲/上海 時間和中文 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False 3,啟動工程 (當完成Django的配置文件設置,最后啟動工程) python manage.py runserver
5.2 關於Bootstrap 和JS
Bootstrap3.3.7的下載地址:請點擊我
JQuery 的下載地址:請點擊我
注意:{% static '相對路徑' %} 這個Django為我們提供的靜態文件加載方法,可以將頁面與靜態文件鏈接起來。
簡要說明:
通過頁面頂端的{% load staticfiles %}加載后,才可以使用static方法; 通過{% block title %}base{% endblock %},設置了一個動態的頁面title塊; 通過{% block css %}{% endblock %},設置了一個動態的css加載塊; 通過{% block content %}{% endblock %},為具體頁面的主體內容留下接口; 通過{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}將樣式文件指 向了我們的實際靜態文件。
5.3 表單驗證的小技巧
Python內置了一個locals()函數,它返回當前所有的本地變量字典,我們可以偷懶將這作為render函數的數據字典參數值,就不用費勁去構造一個形如{'message':message, 'login_form':login_form} 的字典了。這樣做的好處就是大大的方便了我們,但是同時也可能往模板傳入了一些多余的變量數據,造成了數據冗余降低效率。
return render(request, 'user19/login.html', {'message':message, 'login_form':login_form} 大約等同於 return render(request, 'user19/login.html', locals())
5.4 圖片驗證碼
為了防止機器人頻繁登陸網站或者破壞分子惡意登陸,很多用戶登錄和注冊系統都提供了圖形驗證碼功能。
驗證碼(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自動區分計算機和人類的圖靈測試)的縮寫,是一種區分用戶是計算機還是人的公共全自動程序。可以防止惡意破解密碼、刷票、論壇灌水,有效防止某個黑客對某一個特定注冊用戶用特定程序暴力破解方式進行不斷的登陸嘗試。
圖形驗證碼的歷史比較悠久,到現在已經有點英雄末路的味道了。因為機器學習、圖像識別的存在,機器人已經可以比較正確的識別圖像內的字符了。但不管怎么說,作為一種防御手段,至少還是可以抵擋一些低級入門的攻擊手段,抬高了攻擊者的門檻。
在Django中實現圖片驗證碼功能非常簡單,有現成的第三方庫可以使用,我們不必自己開發(但是也需要自己能開發的出來)。這個庫叫做django-simple-captcha.
安裝captcha
直接使用pip按照
pip install django--simple-captcha
Django 自動幫我們安裝了相關的依賴庫 six, olefile 和 pillow ,其中Pillow是大名鼎鼎的繪圖模塊。
5.5 Session會話
因為因特網HTTP協議的特性,每一次來自於用戶瀏覽器的請求(request)都是無狀態的,獨立的。通俗的說,就是無法保存用戶狀態,后台服務器根本就不知道當前請求和以前以及以后請求是否來自同一用戶。對於靜態網站,這可能不是個問題,但是對於動態網站,尤其是京東,天貓,銀行等購物或者金融網站,無法識別用戶並保持用戶狀態是致命的,根本就無法提供服務。你可以嘗試將瀏覽器的cookie功能關閉,你會發現將無法在京東登錄和購物。
為了實現連接狀態的保持功能,網站會通過用戶的瀏覽器在用戶機器內被限定的硬盤位置中寫入一些數據,也就是所謂的Cookie,通過Cookie可以保存一些諸如用戶名,瀏覽記錄,表單記錄,登錄和注銷等各種數據。但是這種方式非常不安全,因為Cookie可以保存一些諸如用戶名,瀏覽記錄,表單記錄,登錄和注銷等各種數據。但是這種方式非常不安全,因為Cookie保存在用戶的機器上,如果Cookie被偽造,篡改或者修改,就會造成極大的安全威脅。因此,現在網站設計通常將Cookie用來保存一些不重要的內容,實際的用戶數據和狀態還是以Session會話的方式保存在服務器端。
Session依賴Cookie! 但是與Cookie不同的地方在於Session將所有的數據都放在服務器端,用戶瀏覽器的Cookie中只會保存一個非明文的識別信息,比如哈希值。
Django提供了一個通用的Session框架,並且可以使用多種session數據的保存方式:
- 保存在數據庫內
- 保存到緩存
- 保存到文件內
- 保存到cookie內
通常情況下,沒有特別需求的話,請使用保存在數據庫內的方式,盡量不要保存在Cookie內。
Django的session框架默認啟動,並且已經注冊在APP設置內,如果真的沒有啟用,那么參考下面的內容添加有說明的那兩行,再執行migrate命令創建數據表,就可以使用session了。
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', # 這一行 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', # 這一行 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
當session啟用后,傳遞給視圖request參數的HTTPRequest對象將包含一個session屬性,就像一個字典對象一樣,我們可以在Django的任何地方讀寫request.sesssion屬性,或者多次編輯使用它。
下面是session使用參考:
class backends.base.SessionBase # 這是所有會話對象的基類,包含標准的字典方法: __getitem__(key) Example: fav_color = request.session['fav_color'] __setitem__(key, value) Example: request.session['fav_color'] = 'blue' __delitem__(key) Example: del request.session['fav_color'] # 如果不存在會拋出異常 __contains__(key) Example: 'fav_color' in request.session get(key, default=None) Example: fav_color = request.session.get('fav_color', 'red') pop(key, default=__not_given) Example: fav_color = request.session.pop('fav_color', 'blue')
類似於字典數據類型的內置方法:
# 類似字典數據類型的內置方法 keys() items() setdefault() clear() # 它還有下面的方法: flush() # 刪除當前的會話數據和會話cookie。經常用在用戶退出后,刪除會話。 set_test_cookie() # 設置一個測試cookie,用於探測用戶瀏覽器是否支持cookies。由於 cookie的工作機制,你只有在下次用戶請求的時候才可以測試。 test_cookie_worked() # 返回True或者False,取決於用戶的瀏覽器是否接受測試cookie。你 必須在之前先調用set_test_cookie()方法。 delete_test_cookie() # 刪除測試cookie。 set_expiry(value) # 設置cookie的有效期。可以傳遞不同類型的參數值: • 如果值是一個整數,session將在對應的秒數后失效。例如 request.session.set_expiry(300) 將在300秒后失效. • 如果值是一個datetime或者timedelta對象, 會話將在指定的日期失效 • 如果為0,在用戶關閉瀏覽器后失效 • 如果為None,則將使用全局會話失效策略 失效時間從上一次會話被修改的時刻開始計時。 get_expiry_age() # 返回多少秒后失效的秒數。對於沒有自定義失效時間的會話,這等同於 SESSION_COOKIE_AGE. # 這個方法接受2個可選的關鍵字參數 • modification:會話的最后修改時間(datetime對象)。默認是當前時間。 •expiry: 會話失效信息,可以是datetime對象,也可以是int或None get_expiry_date() # 和上面的方法類似,只是返回的是日期 get_expire_at_browser_close() # 返回True或False,根據用戶會話是否是瀏覽器關閉后就結束。 clear_expired() # 刪除已經失效的會話數據。 cycle_key() # 創建一個新的會話秘鑰用於保持當前的會話數據。 django.contrib.auth.login() 會調用這個方法。
5.6 遺留問題
如何才能在登錄后,顯示 當前。。在線, 登出,
到現在沒發現代碼的問題。
六,筆記
6.1:設計數據模型
6.1.1 數據庫模型設計
作為一個用戶登錄和注冊項目,需要保存的都是各種用戶的相關信息。很顯然,我們至少需要一個用戶表User,在用戶表里需要保存下面的信息:
- 用戶名
- 密碼
- 郵箱地址
- 性別
- 創建時間
進入user19/models.py,寫入代碼,代碼如下:
from django.db import models class User(models.Model): '''用戶表''' gender = ( ('male','男'), ('female','女'), ) name = models.CharField(max_length=128,unique=True) password = models.CharField(max_length=256) email = models.EmailField(unique=True) sex = models.CharField(max_length=32,choices=gender,default='男') c_time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Meta: ordering = ['c_time'] verbose_name = '用戶' verbose_name_plural = '用戶'
各字段含義:
- name必填,最長不超過128個字符,並且唯一,也就是不能有相同名字;
- password必填,最長不超過256個字符(實際上可能不需要這么長);
- email使用Django內置的郵箱類型,並且唯一;
- 性別使用了一個choice,只能選擇男女,默認為男;
- 使用__str__幫助人性化顯示對象信息;
- 元數據里定義用戶按創建時間的反序排列,也就是最近的最先顯示;
注意:這里的用戶名指的是網絡上注冊的用戶名,不要等同於現實中的真實姓名,所以采用了唯一機制。如果是現實中可以重復的人名,那么肯定不能設置unique的。
6.1.2 設置數據庫為MySQL
在settings中修改DATABASES:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django', #數據庫名字 'USER': 'root', #賬號 'PASSWORD': '123456', #密碼 'HOST': '127.0.0.1', #IP 'PORT': '3306', #端口 }
在__init__.py里面導入pymysql模塊
import pymysql pymysql.install_as_MySQLdb()
6.1.3 數據庫遷移
注冊APP(Mysite_project/settings.py)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user19', ]
遷移到數據庫
python manage.py makemigrations python manage.py migrate
6.2:URl路由和視圖函數
前面我們已經創建好數據模型了,下面我們就要設計好站點URL路由,對應的處理視圖函數以及使用的前端模板了。
6.2.1 路由設計
初步設想需要下面的四個URL:
這里采用的是二級路由的方法(並沒有直接在根路由下直接編寫路由條目):
Mysite_project/urls.py
from django.contrib import admin from django.urls import path from django.conf.urls import url, include urlpatterns = [ path('admin/', admin.site.urls), path('user19/', include('user19.urls')), # 增加驗證碼的路徑 path('captcha', include('captcha.urls')) ]
user19/urls.py
from django.urls import path, include from user19 import views from django.contrib import admin app_name = 'user19' urlpatterns = [ path('admin', admin.site.urls), path('login/', views.login), path('index/', views.index), path('register/', views.register), path('logout/', views.logout), ]
6.2.2 架構初步視圖
路由寫好了,我們就進入視圖文件user19/views.py編寫視圖的框架,代碼如下:
from django.shortcuts import render,redirect def index(request): pass return render(request,'user19/index.html') def login(request): pass return render(request,'user19/login.html') def register(request): pass return render(request,'user19/register.html') def logout(request): pass return redirect('/user19/index/')
我們先不着急完成視圖內部的具體細節,而是先把框架搭起來。
6.2.3 創建HTML頁面文件
在項目的根路徑下創建一個template目錄,再在template目錄里創建一個login目錄,在里面創建三個文件夾,如下:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> <h1>首頁</h1> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄</title> </head> <body> <h1>登錄頁面</h1> </body> </html>
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注冊</title> </head> <body> <h1>注冊頁面</h1> </body> </html>
6.3:admin 后台
6.3.1 在admin中注冊模型(我這里是user19/admin.py)
from django.contrib import admin from . import models admin.site.register(models.User)
6.3.2 創建超級管理員
python manmage.py createsuperuser
我們創建好之后,可以進入后台增加測試用戶。
進入后台的方法(前提是項目URL等東西要配置好): 1,啟動項目 python manage.py runserver 2,在瀏覽器中打開地址: 127.0.0.1:8000/user19/admin
然后我們可以增加幾個測試用戶
6.4:前端頁面設計
6.4.1 原生HTML頁面
login.html 文件中的內容,寫入下面的代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄</title> </head> <body> <div style="margin: 15% 40%;"> <h1>歡迎登錄!</h1> <form action="/login/" method="post"> <p> <label for="id_username">用戶名:</label> <input type="text" id="id_username" name="username" placeholder="用戶名" autofocus required /> </p> <p> <label for="id_password">密碼:</label> <input type="password" id="id_password" placeholder="密碼" name="password" required > </p> <input type="submit" value="確定"> </form> </div> </body> </html>
頁面如下:
6.4.2 引入Bootstrap
根目錄下新建一個static目錄,並將解壓后的bootstrap-3.3.7目錄,整體拷貝到static目錄中,而且由於Bootstrap依賴於JQuery,所以我們需要引入JQuery,並且在static目錄下,新建一個CSS和JS目錄,作為以后的樣式文件和JS文件的存放地,最后如下圖所示:
然后打開項目的settings文件,在最下面添加配置,用於指定靜態文件的搜索目錄:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ]
6.4.3 創建base.html模板
既然要將前端頁面做的像個樣子,那么就不能和前面一頁,每個頁面都各寫各的,單打獨斗。一個網站要有自己的統一風格和公共部分,可以將這部分內容集中到一個基礎模板base.html中。現在,在根目錄下的template中新建一個base.html文件作為站點的基礎模板。
在Bootstrap文檔中,為我們提供了一個非常簡單而且又實用的基本模板,代碼如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <title>Bootstrap 101 Template</title> <!-- Bootstrap --> <link href="css/bootstrap.min.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <h1>你好,世界!</h1> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> </body> </html>
將其整體拷貝到base.hml文件中。
6.4.4 創建頁面導航條
Bootstrap提供了現成的導航條組件
<nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Brand</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li> <li><a href="#">Link</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Link</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav>
其中有一部分,比如搜索框是我們目前還不需要的,需要將多余的內容裁減掉。同時,有一些名稱和URL地址等需要按我們的實際內容修改。最終導航條的代碼如下:
<nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" aria-expanded="false"> <span class="sr-only">切換導航條</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Mysite</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="my-nav"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">主頁</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/login/">登錄</a></li> <li><a href="/register/">注冊</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav>
6.4.5 使用Bootstrap靜態文件
{% static '相對路徑' %} 這個Django為我們提供的靜態文件加載方法,可以將頁面與靜態文件鏈接起來。
最后,base.html內容如下:
{% load staticfiles %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <title>{% block title %}base{% endblock %}</title> <!-- Bootstrap --> <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> {% block css %}{% endblock %} </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" aria-expanded="false"> <span class="sr-only">切換導航條</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Mysite</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="my-nav"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">主頁</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/login/">登錄</a></li> <li><a href="/register/">注冊</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> {% block content %}{% endblock %} <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="{% static 'js/jquery-3.2.1.js' %}"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> </body> </html>
6.4.6 設計登錄頁面
Bootstrap 提供了一個基本的表單樣式,代碼如下:
<form> <div class="form-group"> <label for="exampleInputEmail1">Email address</label> <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email"> </div> <div class="form-group"> <label for="exampleInputPassword1">Password</label> <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password"> </div> <div class="form-group"> <label for="exampleInputFile">File input</label> <input type="file" id="exampleInputFile"> <p class="help-block">Example block-level help text here.</p> </div> <div class="checkbox"> <label> <input type="checkbox"> Check me out </label> </div> <button type="submit" class="btn btn-default">Submit</button> </form>
我們結合Bootstrap和前面自己寫的form表單,修改自己的login.html,最后修改的代碼如下:
{% extends 'login/base.html' %} {% load staticfiles %} {% block title %}登錄{% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class='form-login' action="/login/" method="post"> <h2 class="text-center">歡迎登錄</h2> <div class="form-group"> <label for="id_username">用戶名:</label> <input type="text" name='username' class="form-control" id="id_username" placeholder="Username" autofocus required> </div> <div class="form-group"> <label for="id_password">密碼:</label> <input type="password" name='password' class="form-control" id="id_password" placeholder="Password" required> </div> <button type="reset" class="btn btn-default pull-left">重置</button> <button type="submit" class="btn btn-primary pull-right">提交</button> </form> </div> </div> <!-- /container --> {% endblock %}
代碼說明:
- 通過{% extends 'base.html'} 繼承了 'base.html' 模板的內容
- 通過{% block title%}登錄 {%endblock%}設置了專門的title
- 通過block css 引入了針對性的 login.css樣式文件
- 添加了一個重置按鈕。
在static/css 目錄下新建一個login.css樣式文件,這里簡單的寫了一些樣式:
body { background-color: #eee; } .form-login { max-width: 330px; padding: 15px; margin: 0 auto; } .form-login .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-login .form-control:focus { z-index: 2; } .form-login input[type="text"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-login input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; }
6.5:登錄視圖
6.5.1 登錄視圖
根據我們在路由中的設計,用戶通過login.html中的表單填寫的用戶名和密碼,並以POST的方式發送到服務器的/login/地址。服務器通過user19/views.py中的login()視圖函數,接受並處理這一請求。
我們可以通過下面的方法接收和處理請求:
def login(request): if request.method == "POST": username = request.POST.get('username') password = request.POST.get('password') return redirect('/index/') return render(request, 'login/login.html')
還需要在前端頁面的form表單內添加一個{% csrf_token%} 標簽:
<form class='form-login' action="/login/" method="post"> {% csrf_token %} <h2 class="text-center">歡迎登錄</h2> <div class="form-group"> ...... </form>
進入登錄頁面,輸入用戶名,密碼。然后跳轉到index頁面。
6.5.2 數據驗證
通過唯一的用戶名,使用Django的ORM去數據庫中查詢用戶數據,如果有匹配項,則進行密碼對比,如果沒有匹配項,說明用戶名不存在。如果密碼比對錯誤,說明密碼不正確。
def login(request): if request.method == "POST": username = request.POST.get('username', None) password = request.POST.get('password', None) if username and password: # 確保用戶名和密碼都不為空 username = username.strip() # 用戶名字符合法性驗證 # 密碼長度驗證 # 更多的其它驗證..... try: user = models.User.objects.get(name=username) except: return render(request, 'login/login.html') if user.password == password: return redirect('/index/') return render(request, 'user19/login.html')
6.5.3 添加提示信息
上面的代碼還缺少很重要的一部分內容,提示信息!無論是登錄成功還是失敗,用戶都沒有得到任何提示信息,這顯然不行。
修改login視圖:
def login(request): if request.method == "POST": username = request.POST.get('username', None) password = request.POST.get('password', None) message = "所有字段都必須填寫!" if username and password: # 確保用戶名和密碼都不為空 username = username.strip() # 用戶名字符合法性驗證 # 密碼長度驗證 # 更多的其它驗證..... try: user = models.User.objects.get(name=username) if user.password == password: return redirect('/index/') else: message = "密碼不正確!" except: message = "用戶名不存在!" return render(request, 'user19/login.html', {"message": message}) return render(request, 'user19/login.html')
增加了message變量,用於保存提示信息。當有錯誤信息的時候,將錯誤信息打包成一個字典,然后作為第三個參數提供給render()方法。這個數據字典在渲染模板的時候會傳遞到模板里供你調用。
為了在前端頁面顯示信息,還需要對login.html進行修改:
{% extends 'base.html' %} {% load staticfiles %} {% block title %}登錄{% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class='form-login' action="/login/" method="post"> {% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">歡迎登錄</h2> <div class="form-group"> <label for="id_username">用戶名:</label> <input type="text" name='username' class="form-control" id="id_username" placeholder="Username" autofocus required> </div> <div class="form-group"> <label for="id_password">密碼:</label> <input type="password" name='password' class="form-control" id="id_password" placeholder="Password" required> </div> <button type="reset" class="btn btn-default pull-left">重置</button> <button type="submit" class="btn btn-primary pull-right">提交</button> </form> </div> </div> <!-- /container --> {% endblock %}
將index.html 主頁模板也修改一下,刪除原有內容,添加下面的代碼:
{% extends 'login/base.html' %} {% block title %}主頁{% endblock %} {% block content %} <h1>歡迎回來!</h1> {% endblock %}
6.6 Django表單
Django的表單給我們提供了下面三個主要功能:
- 准備和重構數據用於頁面渲染
- 未數據創建HTML表單元素
- 接受和處理用戶從表單發送過來的數據
6.6.1 創建表單模型
在user19下面創建一個myforms.py文件,在里面添加下面代碼:
from django import forms class UserForm(forms.Form): username = forms.CharField(label="用戶名", max_length=128) password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput)
說明:
- 要先導入forms模板
- 所有的表單類都要繼承forms.Form類
- 每個表單字段都有自己的字段類型比如CharField,它們分別對應一種HTML語言中<form>內的一個input元素。這一點和Django模板系統的設計非常相似。
- label參數用於設置<label>標簽
- max_length 限制字段輸入的最大長度。它同時起到兩個作用,一是在瀏覽器頁面限制用戶輸入不可超過字符數,二是在后端服務器驗證用戶輸入的長度也不可超過。
- widget = forms.PasswordInput 用於指定該字段在form表單里表現為 <input type='password'> ,也就是密碼輸入框。
6.6.2 修改視圖
使用Django的表單后,就要在視圖中進行相應的修改:
from django.shortcuts import render,redirect from . import models from .forms import UserForm def index(request): pass return render(request,'user19/index.html') def login(request): if request.method == "POST": login_form = UserForm(request.POST) message = "請檢查填寫的內容!" if login_form.is_valid(): username = login_form.cleaned_data['username'] password = login_form.cleaned_data['password'] try: user = models.User.objects.get(name=username) if user.password == password: return redirect('/index/') else: message = "密碼不正確!" except: message = "用戶不存在!" return render(request, 'user19/login.html', locals()) login_form = UserForm() return render(request, 'user19/login.html', locals())
說明:
- 對於非POST方法發送數據時,比如GET方法請求頁面,返回空的表單,讓用戶可以填入數據
- 對於POST方法,接收表單數據,並驗證
- 使用表單類自帶的is_valid() 方法一步完成數據驗證工作
- 驗證成功后可以從表單對象的cleaned_data數據字典中獲取表單的具體值
- 如果驗證不通過,則返回一個包含先前數據的表單給前端頁面,方便用戶修改。也就是說,他會幫你保留先前填寫的數據內容,而不是返回一個空表!
6.6.3 修改login界面
Django的表單很重要的一個功能就是自動生成HTML的form表單內容。現在我們需要修改一下原來的login.html 文件:
{% extends 'base.html' %} {% load staticfiles %} {% block title %}登錄{% endblock %} {% block css %}<link href="{% static 'css/login.css' %}" rel="stylesheet"/>{% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class='form-login' action="/login/" method="post"> {% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">歡迎登錄</h2> {{ login_form }} <button type="reset" class="btn btn-default pull-left">重置</button> <button type="submit" class="btn btn-primary pull-right">提交</button> </form> </div> </div> <!-- /container --> {% endblock %}
這樣,啟動程式,打開瀏覽器。則會生成新的HTML源碼。(如果想看源碼,則打開瀏覽器,刷新頁面,進入源碼查看)。
6.6.4 手動渲染表單
直接{{ login_form }} 雖然好,啥都不用操心,但是界面真的不盡人意,往往不是我們想要的,如果要使用CSS , JS ,Bootstrap框架怎么辦? 其實不用擔心,這些都需要對表單內的input元素進行額外控制,所以需要我們手動渲染字段。
可以通過{{ login_form.name_of_field }} 獲取每一個字段,然后分別渲染,如下例所示:
<div class="form-group"> {{ login_form.username.label_tag }} {{ login_form.username}} </div> <div class="form-group"> {{ login_form.password.label_tag }} {{ login_form.password }} </div>
然后,在form類里添加Attr屬性即可,如下所示修改 user19.myforms.py
from django import forms class UserForm(forms.Form): username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'})) password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
再次刷新頁面,就顯示正常了。
6.7 圖片驗證碼功能
6.7.1 注冊captcha
在settings.py中,將‘captcha’注冊到APP列表里:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user19', 'captcha', ]
captcha需要在數據庫中建立自己的數據表,所以需要執行migrate命令生成數據表:
python manage.py migrate
6.7.2 添加URL路由
根目錄下的urls.py文件添加captcha對應的網址:
from django.contrib import admin from django.urls import path from django.conf.urls import url, include urlpatterns = [ path('admin/', admin.site.urls), path('user19/', include('user19.urls')), # 增加驗證碼的路徑 path('captcha', include('captcha.urls')) ]
6.7.3 修改myforms.py
如果上面都OK了,就可以直接在我們的myforms.py文件中添加CaptchaField了。
from django import forms from captcha.fields import CaptchaField class UserForm(forms.Form): username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'})) password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'})) captcha = CaptchaField(label='驗證碼')
需要提前導入form captcha.fields import CaptchaField ,然后就像寫普通的form字段一樣添加一個captcha字段就可以了。
6.7.4 修改login.html文件
由於我們前面是手動生成的form表單,所以還是需要修改一下,添加captcha的相關內容,如下所示:
{% extends 'base.html' %} {% load staticfiles %} {% block title %}登錄{% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class='form-login' action="/login/" method="post"> {% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">歡迎登錄</h2> <div class="form-group"> {{ login_form.username.label_tag }} {{ login_form.username}} </div> <div class="form-group"> {{ login_form.password.label_tag }} {{ login_form.password }} </div> <div class="form-group"> {{ login_form.captcha.errors }} {{ login_form.captcha.label_tag }} {{ login_form.captcha }} </div> <button type="reset" class="btn btn-default pull-left">重置</button> <button type="submit" class="btn btn-primary pull-right">提交</button> </form> </div> </div> <!-- /container --> {% endblock %}
這里額外的添加了一條 {{ login_form.captcha.errors }} 用於明確指示用戶,你的驗證碼不正確。其中驗證圖形碼是否正確的工作都是在后台自動完成的。只需要使用is_valid()這個myforms內置的驗證方法就一起進行了,完全不需要再視圖函數中添加任何的驗證代碼,非常方便快捷!
6.8 Session的使用
6.8.1 使用session
首先,修改login/views.py中的login()視圖函數:
def login(request): if request.session.get('is_login',None): return redirect('/index') if request.method == "POST": login_form = UserForm(request.POST) message = "請檢查填寫的內容!" if login_form.is_valid(): username = login_form.cleaned_data['username'] password = login_form.cleaned_data['password'] try: user = models.User.objects.get(name=username) if user.password == password: request.session['is_login'] = True request.session['user_id'] = user.id request.session['user_name'] = user.name return redirect('/index/') else: message = "密碼不正確!" except: message = "用戶不存在!" return render(request, 'user/login.html', locals()) login_form = UserForm() return render(request, 'user/login.html', locals())
通過下面的if語句,我們不允許重復登錄:
if request.session.get('is_login',None): return redirect("/index/")
通過下面的語句,我們往session字典內寫入用戶狀態和數據:
request.session['is_login'] = True request.session['user_id'] = user.id request.session['user_name'] = user.name
我們完全可以往里面寫任何數據,不僅僅限於用戶相關!
既然有了session記錄用戶登錄狀態,那么就可以完善我們的登出視圖函數了:
def logout(request): if not request.session.get('is_login', None): # 如果本來就未登錄,也就沒有登出一說 return redirect("/index/") request.session.flush() # 或者使用下面的方法 # del request.session['is_login'] # del request.session['user_id'] # del request.session['user_name'] return redirect("/index/")
flush() 方法是比較安全的一種做法,而且一次性將session中的所有內容全部清空,確保不留后患。但是也有不好的地方,那就是如果你在session中夾帶了一點“私貨”,會被一並刪除,這一點一定要注意。
6.8.2 完善頁面
有了用戶狀態,就可以根據用戶登錄與否,展示不同的頁面,比如導航條內容。
首先,修改base.html文件:
<div class="collapse navbar-collapse" id="my-nav"> <ul class="nav navbar-nav"> <li class="active"><a href="/index/">主頁</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.session.is_login %} <li><a href="#">當前在線:{{ request.session.user_name }}</a></li> <li><a href="/logout/">登出</a></li> {% else %} <li><a href="/login/">登錄</a></li> <li><a href="/register/">注冊</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid -->
通過if判斷,當登錄時,顯示當前用戶名和登出按鈕。未登錄時,顯示登錄和注冊按鈕。
注意其中的模板語言,{{ request }} 這個變量會被默認傳入模板中,可以通過原點的調用方式,獲取它內部的{{ request.session }} ,再進一步的獲取session中的內容。其實{{ request }} 的數據遠不知此,例如 {{ request.apth }} 就可以獲取先前的URL地址。
再修改一下index.html 頁面,根據登錄與否的不同,顯示不同的內容:
{% extends 'base.html' %} {% block title %}主頁{% endblock %} {% block content %} {% if request.session.is_login %} <h1>你好,{{ request.session.user_name }}!歡迎回來!</h1> {% else %} <h1>你尚未登錄,只能訪問公開內容!</h1> {% endif %} {% endblock %}
6.9 注冊視圖
6.9.1 創建myforms.py
在/user19/myforms.py中添加一個新的表單類:
class RegisterForm(forms.Form): gender = ( ('male', "男"), ('female', "女"), ) username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'})) password1 = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'})) password2 = forms.CharField(label="確認密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'})) email = forms.EmailField(label="郵箱地址", widget=forms.EmailInput(attrs={'class': 'form-control'})) sex = forms.ChoiceField(label='性別', choices=gender) captcha = CaptchaField(label='驗證碼')
說明:
- gender和User模型中的一樣,其實可以拉出來作為常量共用,為了直觀,特意重寫一遍
- password1和password2,用於輸入兩遍密碼,並進行比較,防止誤輸入密碼
- email是一個郵箱輸入框
- sex是一個select下拉框
6.9.2 完善register.html
同樣的,類似於login.html文件,我們再register.html中編寫forms相關條目:
{% extends 'login/base.html' %} {% block title %}注冊{% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class='form-register' action="/register/" method="post"> {% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">歡迎注冊</h2> <div class="form-group"> {{ register_form.username.label_tag }} {{ register_form.username}} </div> <div class="form-group"> {{ register_form.password1.label_tag }} {{ register_form.password1 }} </div> <div class="form-group"> {{ register_form.password2.label_tag }} {{ register_form.password2 }} </div> <div class="form-group"> {{ register_form.email.label_tag }} {{ register_form.email }} </div> <div class="form-group"> {{ register_form.sex.label_tag }} {{ register_form.sex }} </div> <div class="form-group"> {{ register_form.captcha.errors }} {{ register_form.captcha.label_tag }} {{ register_form.captcha }} </div> <button type="reset" class="btn btn-default pull-left">重置</button> <button type="submit" class="btn btn-primary pull-right">提交</button> </form> </div> </div> <!-- /container --> {% endblock %}
6.9.3 注冊視圖
進入/user19/views.py 文件,現在來完善我們的register()視圖:
def register(request): if request.session.get('is_login', None): # 登錄狀態不允許注冊。你可以修改這條原則! return redirect("/index/") if request.method == "POST": register_form = RegisterForm(request.POST) message = "請檢查填寫的內容!" if register_form.is_valid(): # 獲取數據 username = register_form.cleaned_data['username'] password1 = register_form.cleaned_data['password1'] password2 = register_form.cleaned_data['password2'] email = register_form.cleaned_data['email'] sex = register_form.cleaned_data['sex'] if password1 != password2: # 判斷兩次密碼是否相同 message = "兩次輸入的密碼不同!" return render(request, 'user19/register.html', locals()) else: same_name_user = models.User.objects.filter(name=username) if same_name_user: # 用戶名唯一 message = '用戶已經存在,請重新選擇用戶名!' return render(request, 'user19/register.html', locals()) same_email_user = models.User.objects.filter(email=email) if same_email_user: # 郵箱地址唯一 message = '該郵箱地址已被注冊,請使用別的郵箱!' return render(request, 'user19/register.html', locals()) # 當一切都OK的情況下,創建新用戶 new_user = models.User.objects.create() new_user.name = username new_user.password = password1 new_user.email = email new_user.sex = sex new_user.save() return redirect('/login/') # 自動跳轉到登錄頁面 register_form = RegisterForm() return render(request, 'user19/register.html', locals())
從大體邏輯上,也是先實例化一個RegisterForm的對象,然后使用 is_valid()驗證數據,再從cleaned_data中獲取數據。
重點在於注冊邏輯,首先兩次輸入的密碼必須相同,其次不能存在相同用戶名和郵箱,最后如果條件都滿足,利用ORM的API,創建一個用戶實例,然后保存到數據庫內。
6.10 密碼加密
用戶注冊的密碼應該加密才對
對於如何加密密碼,有很多不同的途徑,其安全程度也高低不等。這里我們使用Python內置的hashlib庫,使用哈希值的方式加密密碼,可能安全等級不夠高,但足夠簡單,方便使用。
首先, user19/views.py 中編寫一個hash函數:
import hashlib def hash_code(s, salt='mysite'):# 加點鹽 h = hashlib.sha256() s += salt h.update(s.encode()) # update方法只接收bytes類型 return h.hexdigest()
然后,我們還要對login()和register()視圖進行一下修改:
#login.html if user.password == hash_code(password): # 哈希值和數據庫內的值進行比對 #register.html new_user.password = hash_code(password1) # 使用加密密碼
user19/views.py
from django.shortcuts import render,redirect from . import models from .forms import UserForm,RegisterForm import hashlib def index(request): pass return render(request,'user19/index.html') def login(request): if request.session.get('is_login', None): return redirect("/index/") if request.method == "POST": login_form = UserForm(request.POST) message = "請檢查填寫的內容!" if login_form.is_valid(): username = login_form.cleaned_data['username'] password = login_form.cleaned_data['password'] try: user = models.User.objects.get(name=username) if user.password == hash_code(password): # 哈希值和數據庫內的值進行比對 request.session['is_login'] = True request.session['user_id'] = user.id request.session['user_name'] = user.name return redirect('/index/') else: message = "密碼不正確!" except: message = "用戶不存在!" return render(request, 'user19/login.html', locals()) login_form = UserForm() return render(request, 'user19/login.html', locals()) def register(request): if request.session.get('is_login', None): # 登錄狀態不允許注冊。你可以修改這條原則! return redirect("/index/") if request.method == "POST": register_form = RegisterForm(request.POST) message = "請檢查填寫的內容!" if register_form.is_valid(): # 獲取數據 username = register_form.cleaned_data['username'] password1 = register_form.cleaned_data['password1'] password2 = register_form.cleaned_data['password2'] email = register_form.cleaned_data['email'] sex = register_form.cleaned_data['sex'] if password1 != password2: # 判斷兩次密碼是否相同 message = "兩次輸入的密碼不同!" return render(request, 'user19/register.html', locals()) else: same_name_user = models.User.objects.filter(name=username) if same_name_user: # 用戶名唯一 message = '用戶已經存在,請重新選擇用戶名!' return render(request, 'user19/register.html', locals()) same_email_user = models.User.objects.filter(email=email) if same_email_user: # 郵箱地址唯一 message = '該郵箱地址已被注冊,請使用別的郵箱!' return render(request, 'user19/register.html', locals()) # 當一切都OK的情況下,創建新用戶 new_user = models.User.objects.create() new_user.name = username new_user.password = hash_code(password1) # 使用加密密碼 new_user.email = email new_user.sex = sex new_user.save() return redirect('/login/') # 自動跳轉到登錄頁面 register_form = RegisterForm() return render(request, 'user19/register.html', locals()) def logout(request): if not request.session.get('is_login',None): return redirect('/index/') request.session.flush() return redirect('/index/') def hash_code(s, salt='mysite_login'): h = hashlib.sha256() s += salt h.update(s.encode()) # update方法只接收bytes類型 return h.hexdigest()
重啟服務器,進入注冊頁面,新建一個用戶,就可以進入后台查看用戶密碼情況。然后我們可以再使用該用戶登錄。
七,結果展示
1,展示整個項目的結構
2,后台展示我們管理的界面
3,展示我們的登錄界面
4,展示登錄出現驗證碼錯誤頁面:
5,展示我們的注冊頁面
6,下面注冊一個harden,注冊成功后 可以在admin后台看到注冊的用戶
7,在后台查看注冊的harden信息如下:
8,在數據庫查看其注冊的數據如下:
我們可以看到密碼長度跟你哈希算法的不同,已經變得很長了,所以前面model中設置password字段時候,不要將max_length 設置為16這么小的數字。
9,登錄harden的信息,登錄成功界面顯示如下:
八,代碼
這里只粘貼關鍵的重要的代碼。
Mysite_project/Mysite_project/urls.py
from django.contrib import admin from django.urls import path from django.conf.urls import url, include urlpatterns = [ path('admin/', admin.site.urls), path('user19/', include('user19.urls')), # 增加驗證碼的路徑 path('captcha', include('captcha.urls')) ]
Mysite_project/static/css/login.css
body{ background-color: #eee; } .form-login{ max-width: 330px; padding: 15px; margin: 0 auto; } .form-login .form-control{ position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-login .form-control:focus{ z-index: 2; } .form-login input[type='text']{ margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-login input[type='password']{ margin-bottom: 10px; border-top-right-radius: 0; border-top-left-radius: 0; }
Mysite_project/template/user19/login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> {#通過下面語句 繼承了base.html 模板的內容#} {% extends 'base.html' %} {% load staticfiles %} {% block title %}登錄{% endblock %} {% block css %} <link rel="stylesheet" href="{% static 'css/login.css' %}"> {% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class="form-login" action="/user19/login/" method="post"> {% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">Welcome to login!</h2> <div class="form-group"> {{ login_form.username.label_tag }} {{ login_form.username }} </div> <div class="form-group"> {{ login_form.password.label_tag }} {{ login_form.password }} </div> <div class="form-group"> {{ login_form.captcha.errors }} {{ login_form.captcha.label_tag }} {{ login_form.captcha }} </div> <button type="reset" class="btn btn-default pull-left">reset</button> <button type="submit" class="btn btn-default pull-right">submit</button> </form> </div> </div> {% endblock %} </body> </html>
Mysite_project/template/user19/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {# Mysite/template/user19/index.html#} {% extends 'base.html' %} {% block title %}主頁{% endblock %} {% block content %} {% if request.session.is_login %} <h1>hello,{{ request.session.user_name }} ! Welcome to back!</h1> {% else %} <h1> 你尚未登錄,只能訪問公開內容</h1> {% endif %} {% endblock %} </body> </html>
Mysite_project/template/user19/register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% extends 'base.html' %} {% block title %}注冊{% endblock %} {% block content %} <div class="container"> <div class="col-md-4 col-md-offset-4"> <form class="form-register" action="/user19/register/" method="post"> {% if messgae %} <div class="alert alert-warning">{{ message }}</div> {% endif %} {% csrf_token %} <h2 class="text-center">歡迎注冊</h2> <div class="form-group"> {{ register_form.username.label_tag }} {{ register_form.username }} </div> <div class="form-group"> {{ register_form.password1.label_tag }} {{ register_form.password1 }} </div> <div class="form-group"> {{ register_form.password2.label_tag }} {{ register_form.password2 }} </div> <div class="form-group"> {{ register_form.email.label_tag }} {{ register_form.email }} </div> <div class="form-group"> {{ register_form.sex.label_tag }} {{ register_form.sex }} </div> <div class="form-group"> {{ register_form.cpatcha.errors }} {{ register_form.captcha.label_tag }} {{ register_form.captcha }} </div> <button type="reset" class="btn btn-default pull-left">reset</button> <button type="submit" class="btn btn-primary pull-right">submit</button> </form> </div> </div> <!-- /container --> {% endblock %} </body> </html>
Mysite_project/template/base.html
{% load staticfiles %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <title>{% block title %}base{% endblock %}</title> <!-- Bootstrap --> <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> {% block css %}{% endblock %} </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">切換導航條</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">MySite</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="my-nav"> <ul class="nav navbar-nav"> <li class="active"><a href="/user19/index/">主頁</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> {% if requset.session.is_login %} <li><a href="/user19/login/">當前在線:{{ request.session.user_name }}</a> </li> <li><a href="/user19/logout/">登出</a> </li> {% else %} <li><a href="/user19/login/">登錄</a> </li> <li><a href="/user19/register/">注冊</a> </li> {% endif %} </ul> </div> </div><!-- /.container-fluid --> </nav> {% block content %}{% endblock %} <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> </body> </html>
Mysite_project/user19/admin.py
from django.contrib import admin # Register your models here. from . import models admin.site.register(models.User)
Mysite_project/user19/models.py
from django.db import models from django import forms # Create your models here. class User(models.Model): # 用戶表 gender = ( ('male', '男'), ('female', '女'), ) # 最長不超過128個字符,並且唯一,也就是不能有相同的姓名 username = models.CharField(max_length=128, unique=True) password = models.CharField(max_length=256) email = models.EmailField(unique=True) sex = models.CharField(max_length=32, choices=gender, default='男') c_time = models.DateTimeField(auto_now_add=True) def __str__(self): # 幫助人性化顯示對象信息 return self.username class Meta: # 元數據里定義用戶按創建時間的反序排列,也就是最近的最先顯示 ordering = ['c_time'] verbose_name = '用戶' verbose_name_plural = '用戶'
Mysite_project/user19/myforms.py
from django import forms from captcha.fields import CaptchaField class UserForm(forms.Form): username = forms.CharField(label='用戶名', max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'})) password = forms.CharField(label='密碼', max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'})) captcha = CaptchaField(label='驗證碼') class RegisterForm(forms.Form): gender = ( ('male', '男'), ('female', '女'), ) username = forms.CharField(max_length=128, label='用戶名', widget=forms.TextInput(attrs={'class': 'form-control'})) password1 = forms.CharField(max_length=256, label='密碼', widget=forms.PasswordInput(attrs={'class': 'form-control'})) password2 = forms.CharField(max_length=256, label='確認密碼', widget=forms.PasswordInput(attrs={'class': 'form-control'})) email = forms.EmailField(label='郵箱地址', widget=forms.EmailInput(attrs={'class': 'form-control'})) sex = forms.ChoiceField(choices=gender, label='性別') captcha = CaptchaField(label="驗證碼")
Mysite_project/user19/urls.py
from django.urls import path, include from user19 import views from django.contrib import admin app_name = 'user19' urlpatterns = [ path('admin', admin.site.urls), path('login/', views.login), path('index/', views.index), path('register/', views.register), path('logout/', views.logout), ]
Mysite_project/user19/views.py
from django.shortcuts import render, HttpResponse, redirect # Create your views here. from user19 import models from .myforms import UserForm, RegisterForm def login(request): # if request.session.get('is_login', None): # return redirect('/user19/index') if request.method == 'POST': login_form = UserForm(request.POST) message = '請檢查填寫的內容' if login_form.is_valid(): username = login_form.cleaned_data['username'] password = login_form.cleaned_data['password'] try: user = models.User.objects.get(username=username) # 哈希值和數據庫內的值進行比對 if user.password == hash_code(password): # if user.password == password: request.session['is_login'] = True request.session['user_id'] = user.id request.session['user_name'] = user.username return redirect('/user19/index/') else: message = 'password is incorrect' except: message = 'username is not exist' return render(request, 'user19/login.html', locals()) login_form = UserForm() # return render(request, 'user19/login.html') return render(request, 'user19/login.html', locals()) def index(request): pass return render(request, 'user19/index.html') def register(request): # if request.session.get('is_login', None): # # 登錄狀態不允許注冊,你可以修改這條原則 # return redirect('/user19/index/') if request.method == 'POST': register_form = RegisterForm(request.POST) message = '請檢查填寫的內容' # 獲取數據 if register_form.is_valid(): username = register_form.cleaned_data['username'] password1 = register_form.cleaned_data['password1'] password2 = register_form.cleaned_data['password2'] email = register_form.cleaned_data['email'] sex = register_form.cleaned_data['sex'] # 判斷兩次密碼是否相同 if password1 != password2: message = '兩次輸入的密碼不同' return render(request, 'user19/register.html', locals()) else: same_name_user = models.User.objects.filter(username=username) print(same_name_user) # 用戶名唯一 if same_name_user: message = '用戶名已經存在,請重新選擇用戶名!' # return HttpResponse("用戶名已經存在") return render(request, 'user19/register.html', locals()) same_email_user = models.User.objects.filter(email=email) if same_email_user: message = '該郵箱地址已經被注冊,請使用別的郵箱' # return HttpResponse("郵箱地址已經注冊") return render(request, 'user19/register.html', locals()) # 當一切都OK的情況下,創建了新用戶 new_user = models.User.objects.create() new_user.username = username # 使用加密密碼 new_user.password = hash_code(password1) # new_user.password = password1 new_user.email = email new_user.sex = sex new_user.save() return redirect('/user19/login/') # 如果請求不是POST,則渲染的是一個空的表單。 register_form = RegisterForm() # 如果用戶通過表單提交數據,但是數據驗證、不合法,則渲染的是一個帶有錯誤信息的表單 return render(request, 'user19/register.html', locals()) def logout(request): if not request.session.get('is_login', None): # 如果沒登錄,也就沒有登出一說 return redirect('/user19/index/') request.session.flush() # 或者使用下面的方法 # del request.session['is_login'] # del request.session['user_id'] # del request.session['user_name'] return redirect('/user19/index/') # 密碼加密 import hashlib # 加點鹽 def hash_code(s, salt='Mysite_project'): h = hashlib.sha256() s += salt # update方法只接受bytes類型 h.update(s.encode()) return h.hexdigest()
參考文獻:https://blog.csdn.net/laikaikai/article/details/80563387#commentBox
(備注:此次項目,主要參考上面文獻,目的是鞏固學習Django的知識點)