基本框架搭建好了后,我們就要開始豐富頁面內容了。最起碼,得有一個用戶登錄的表單不是么?(注冊的事情我們先放一邊。)
一、 原生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>
簡單解釋一下:
- form標簽主要確定目的地url和發送方法;
- p標簽將各個輸入框分行;
- label標簽為每個輸入框提供一個前導提示,還有助於觸屏使用;
- placeholder屬性為輸入框提供默認值;
- autofocus屬性為用戶名輸入框自動聚焦
- required表示該輸入框必須填寫
- passowrd類型的input標簽不會顯示明文密碼
啟動服務器,訪問http://127.0.0.1:8000/login/
,可以看到如下圖的頁面:
二、引入Bootstrap
大多數使用Django的人都不具備多高的前端水平,通常也沒有專業的前端工程師配合,自己寫的CSS和JS往往慘不忍睹。怎么辦?沒關系,我們有現成的開源前端框架!
Bootstrap就是最好的框架之一!
前往Bootstrap中文網下載當前最新的v3.3.7
版本代碼,下載用於生產環境的 Bootstrap。
在Django的根目錄下新建一個static目錄,並將解壓后的bootstrap-3.3.7-dist
目錄,整體拷貝到static目錄中,如下圖所示:
由於Bootstrap依賴JQuery,所以我們需要提前下載並引入JQuery,我這里使用的是jquery-3.2.1.js
,當然別的版本也是可以的。(請自行下載JQuery)
在static目錄下,新建一個css和js目錄,作為以后的樣式文件和js文件的存放地,將我們的jquery文件拷貝到static/js
目錄下。
三、靜態文件設置
關於靜態文件的設置,是很多初學者吐槽最多的地方。以后我們會一點一點講解,這里先按步驟進行。
打開項目的settings文件,在最下面添加這么一行配置,用於指定靜態文件的搜索目錄:
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ]
四、創建base.html模板
既然要將前端頁面做得像個樣子,那么就不能和前面一樣,每個頁面都各寫各的,單打獨斗。一個網站有自己的統一風格和公用部分,可以把這部分內容集中到一個基礎模板base.html
中。現在,在根目錄下的templates中新建一個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.html
文件中。然后修改login/templates/login/login.html
文件,刪除原來的所有內容,只寫入如下一行代碼:
{% extends 'base.html' %}
模板語言{% extends 'base.html' %}
,表示當前頁面繼承base.html
文件中的所有內容。
啟動服務器,訪問localhost:8000/login/
,暫時只能看見下面的內容,但沒有關系,這表示我們的模板繼承系統工作正常。
五、創建頁面導航條
下面我們為頁面增加一個導航條,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>
砍掉了很多內容,不過沒關系,有需要的話隨時可以增加。
下面用上面的代碼,替換掉base.html
中的h1
標簽。再次刷新頁面,顯示效果如下:
為什么會這樣,因為Bootstrap的CSS和JS文件沒有被正常導入!
六、使用Boostrap靜態文件
通過{% static '相對路徑' %}
這個Django為我們提供的靜態文件加載方法,可以將頁面與靜態文件鏈接起來。當然,還有一種辦法是使用CDN,如果有可靠的源,也是可以使用的。
最后,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>
要點:
- 通過頁面頂端的
{% 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' %}
將樣式文件指向了我們的實際靜態文件,下面的js腳本也是同樣的道理。
更多的前端頁面知識,實在難以一言述盡,只能一點點解說,所以需要大家具備一定的基礎。做Django開發,其實就是全棧開發,沒有一定的前端能力,是做不好的。
好了,現在刷新下我們的頁面,可以看到如下的效果:
由於Bootstrap以移動端優先的特性,我們的這個頁面是可以動態調整的,嘗試縮放一下屏幕大小,導航條會自動折疊和展開,也就是說你寫的前端頁面可以在手機、ipad上正常訪問,但是IE.....,我們忘記它吧,哭。
七、設計登錄頁面
當前的登錄頁面只有網站的整體導航條,還沒有添加最主要的登錄表單。
Bootstarp提供了一個基本的表單樣式,代碼如下:
<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/templates/login/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"> <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
樣式文件; - 主體內容定義在
block content
內部 - 添加了一個重置按鈕。
在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; }
刷新頁面,效果如下:
這個UI算不上好看,同樣可以稱為簡陋,但是比較純粹,你完全可以在這個基礎上,定制一些自己的CSS樣式,比如添加個背景圖。
這些工作不是本項目實戰的重點,大家可以自行設計。
八、登錄視圖
根據我們在路由中的設計,用戶通過login.html
中的表單填寫用戶名和密碼,並以POST的方式發送到服務器的/login/
地址。
服務器通過login/views.py
中的login()
視圖函數,接收並處理這一請求。
我們可以通過下面的方法接收和處理請求:
def login(request): if request.method == "POST": username = request.POST.get('username') password = request.POST.get('password') print(username, password) return redirect('/index/') return render(request, 'login/login.html')
說明:
- 每個視圖函數都至少接收一個參數,並且是第一位置參數,該參數封裝了當前請求的所有數據;
- 通常將第一參數命名為request,當然也可以是別的;
request.method
中封裝了數據請求的方法,如果是“POST”(全大寫),將執行if語句的內容,如果不是,直接返回最后的render()結果;request.POST
封裝了所有POST請求中的數據,這是一個字典類型,可以通過get方法獲取具體的值。- 類似
get('username')
中的鍵‘username’是HTML模板中表單的input元素里‘name’屬性定義的值。所以在編寫form表單的時候一定不能忘記添加name屬性。 - 利用print函數在開發環境中驗證數據;
- 利用redirect方法,將頁面重定向到index頁。
啟動服務器,然后在http://127.0.0.1:8000/login/
的表單中隨便填入用戶名和密碼,然后點擊提交。然而,頁面卻出現了錯誤提示,如下圖所示:
錯誤原因是CSRF驗證失敗,請求被中斷。CSRF(Cross-site request forgery)跨站請求偽造,是一種常見的網絡攻擊手段,具體原理和技術內容請自行百科。Django自帶對許多常見攻擊手段的防御機制,CSRF就是其中一種,還有XSS、SQL注入等。
為了解決這個問題,我們需要在前端頁面的form表單內添加一個{% csrf_token %}
標簽:
這個標簽必須放在form表單內部,但是內部的位置可以隨意。
重新刷新login頁面,確保csrf的標簽生效,然后再次輸入內容並提交。這次就可以成功地在Pycharm開發環境中看到接收的用戶名和密碼,同時瀏覽器頁面也跳轉到了首頁。
二、數據驗證
要對用戶發送的數據進行驗證。數據驗證分前端頁面驗證和后台服務器驗證。前端驗證可以通過專門的插件或者自己寫JS代碼實現,也可以簡單地使用HTML5的新特性。這里,我們使用的是HTML5的內置驗證功能,如下圖所示:
它幫我們實現了下面的功能:
- 必填字段不能為空
- 密碼部分用圓點替代
如果你還想要更強大和豐富的驗證功能,比如限定密碼長度不低於8位,用戶名不能包含特殊字符等等,可以搜索並使用一些插件。
前端頁面的驗證都是用來給守法用戶做提示和限制的,並不能保證絕對的安全,后端服務器依然要重新對數據進行驗證。
我們前面的視圖函數,沒有對數據進行任何的驗證,如果你在用戶名處輸入個空格,是可以正常提交的,這顯然不行。
甚至,如果跳過瀏覽器偽造請求,那么用戶名是None也可以發送過來。
通常,除了數據內容本身,我們至少需要保證各項內容都提供了且不為空,對於用戶名、郵箱、地址等內容往往還需要剪去前后的空白,防止用戶未注意到的空格。
修改一下前面的代碼:
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() # 用戶名字符合法性驗證 # 密碼長度驗證 # 更多的其它驗證..... return redirect('/index/') return render(request, 'login/login.html')
- 通過
get('username', None)
的調用方法,確保當數據請求中沒有username鍵時不會拋出異常,而是返回一個我們指定的默認值None; - 通過
if username and password:
確保用戶名和密碼都不為空; - 通過strip()方法,將用戶名前后無效的空格剪除;
- 更多的數據驗證需要根據實際情況增加,原則是以最低的信任度對待發送過來的數據。
三、驗證用戶名和密碼
數據驗證通過了,不代表用戶就可以合法登錄了,因為最基本的密碼對比還未進行。
通過唯一的用戶名,使用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, 'login/login.html')
說明:
- 首先要在頂部導入models模塊;
- 使用try異常機制,防止數據庫查詢失敗的異常;
- 如果未匹配到用戶,則執行except中的語句;
models.User.objects.get(name=username)
是Django提供的最常用的數據查詢API,不再贅述;- 通過
user.password == password
進行密碼比對,成功則跳轉到index頁面,失敗則什么都不做。
重啟服務器,然后在登錄表單內,使用錯誤的用戶名和密碼,以及我們先前在admin中創建的合法的測試用戶,分別提交試試。
四、 添加提示信息
上面的代碼還缺少很重要的一部分內容,提示信息!無論是登錄成功還是失敗,用戶都沒有得到任何提示信息,這顯然是不行的。
修改一下login視圖:
from django.shortcuts import render
from django.shortcuts import redirect
from . import models
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, 'login/login.html', {"message": message}) return render(request, 'login/login.html')
增加了message變量,用於保存提示信息。
當有錯誤信息的時候,將錯誤信息打包成一個字典,然后作為第三個參數提供給render()方法。這個數據字典在渲染模板的時候會傳遞到模板里供你調用。
為了在前端頁面顯示信息,還需要對login.html
進行修改:
{% if message %} <div class="alert alert-warning">{{ message }}</div> {% endif %}
Django的模板語言{% if xxx %}{% endif %}
非常類似Python的if語句,也可以添加{% else %}
分句。例子中,通過判斷是message變量是否不為空,也就是是否有錯誤提示信息,如果有,就顯示出來!這里使用了Bootstrap的警示信息類alert,你也可以自定義CSS或者JS。
順便我們把index.html
主頁模板也修改一下,刪除原有內容,添加下面的代碼:
{% extends 'base.html' %} {% block title %}主頁{% endblock %} {% block content %} <h1>歡迎回來!</h1> {% endblock %}
好了,重啟服務器,嘗試用錯誤的和正確的用戶名及密碼登錄,看看頁面效果吧!下面是錯誤信息的展示: