《Web介面開發與自動化測試基於Python語言》–第5章
《Web介面開發與自動化測試基於Python語言》–讀書筆記
第5章 Django模板
Ps:我下載了,但是和書中出入比較大,建議還是自己按照書碼字吧。
5.1 Django-bootstrap3
Bootstrap:源於Twitter,是目前很受歡迎的前端框架。Bootstrap是基於HTML、CSS、Javascript的,它簡潔靈活,使得Web開發更加快捷。它由Twitter的設計師Mark Otto、Jacob Thornton合作開發,是一個CSS/HTML框架。Bootstrap提供了優雅的HTML和CSS規範,它由動態CSS語言Less寫成。
Django-bootstrap3:是整合到Django的一個應用,使用如下命令安裝:
pip install django-bootstrap3
修改/guest/settings.py檔案,增加bootstrap3應用:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'sign',
'bootstrap3' ,
]
5.2 釋出會管理
5.2.1 釋出會列表
修改/guest/sign/views.py檔案:
# 從sign應用中匯入models中的Event類
from sign.models import Event
# 釋出會管理
@login_required
def event_manage(request):
# 增加發佈會列表
event_list = Event.objects.all() # 查詢所有釋出會物件(資料)
#username = request.COOKIES.get('user', '') # 讀取瀏覽器Cookie
username = request.session.get('user', '') # 讀取瀏覽器Session
# 通過render()方法附加在event_mange.html頁面返回給客戶端
return render(request, "event_manage.html", {"user":username, "events":event_list})
修改/guest/sign/templates/event_manage.html檔案:
<!DOCTYPE html>
<html>
<head>
<!-- 載入bootstrap3應用、CSS、Javascript檔案,{% %}為Django模板標籤語言 -->
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<meta charset="utf-8">
<!-- 設定頁面標題 -->
<title>Guest Manage Page</title>
</head>
<div style="float:right;">
<a>嘿!{{ user }} 歡迎</a><hr/>
</div>
<body role="document">
<!-- 導航欄 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/event_manage/">Guest Manage System</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<!-- 設定頁面導航欄,class=active表示當前選單處於選中狀態 -->
<li class="active"><a href="#">釋出會</a><li>
<!-- href=/guest_manage/用於跳轉到嘉賓管理頁面 -->
<li><a href="/guest_manage/">嘉賓</a><li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!-- {{}}為Django模板語言,用於定義顯示變數,user為客戶端獲取的瀏覽器sessionid對應的登入使用者名稱 -->
<li><a href="#">{{user}}</a></li>
<!-- href=/logout/用於跳轉到退出路徑 -->
<li><a href="/logout/">退出</a></li>
</ul>
</div>
</div>
</nav>
<!-- 釋出會列表 -->
<!-- style屬性中padding-top用於設定元素的上內邊距,如果不設定該屬性,釋出會列表可能會被導航欄遮擋 -->
<div class="row" style="padding-top: 80px;">
<div class="col-md-6">
<table class="table table-striped">
<thead>
<tr>
<th>id</th><th>名稱</th><th>狀態</th><th>地址</th><th>時間</th>
</tr>
</thead>
<tbody>
<!-- 通過Django模板語言,使用for迴圈,迴圈打印發佈會的id、name等欄位,注意for迴圈語句需要有對應的endfor來表示語句的結束 -->
{% for event in events %}
<tr>
<td>{{event.id}}</td>
<td>{{event.name}}</td>
<td>{{event.status}}</td>
<td>{{event.address}}</td>
<td>{{event.start_time}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!--<h1>Login Success!</h1>-->
</body>
</html>
通過頁面訪問效果如下:
5.2.2 搜尋功能
搜尋功能需要分別修改:html模板、url路徑、views函式,如下:
修改/guest/sign/templats/event_manage.html檔案:
<!-- 釋出會搜尋表單 -->
<div class="page-header" style="padding-top: 60px;">
<div id="navbar" class="navbar-collapse collapse">
<!-- method=get為HTTP請求方式,action=/search_name/為搜尋請求路徑 -->
<form class="navbar-form" method="get" action="/search_name/">
<div class="form-group">
<!-- name=name為搜尋輸入框name的屬性值 -->
<input name="name" type="text" placeholder="名稱" class="form-control">
</div>
<button type="submit" class="btn btn-success">搜尋</button>
</form>
</div>
</div>
修改/guest/urls.py檔案:
urlpatterns = [
……
# 增加search_name的路徑
url(r'^search_name/$', views.search_name),
]
修改/guest/sign/views.py檔案:
# 釋出會名稱搜尋
@login_required
def search_name(request):
username = request.session.get('user', '')
# 通過get()方法獲取name關鍵字
search_name = request.GET.get("name", "")
# 在Event中匹配name欄位
event_list = Event.objects.filter(name__contains=search_name)
# 將匹配到的釋出會列表注意這裡是列表不是物件,返回給客戶端
return render(request, "event_manage.html", {"user":username, "events":event_list})
搜尋功能:通過GET方法接收搜尋關鍵字,並通過模糊查詢,匹配發佈會name欄位,然後把匹配到的釋出會列表返回給客戶端。
5.3 嘉賓管理
嘉賓管理頁面的開發與釋出會管理頁面基本一致!
5.3.1 嘉賓列表
首先修改/guest/sign/templates/guest_manage.html檔案:
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<! -- 還是先載入bootstrap應用 -->
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<!-- 設定頁面標題 -->
<title>Guest Manage Page</title>
</head>
<body>
<!-- 導航欄 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/guest_manage/">Guest Manage System</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<!-- 為釋出選單設定跳轉路徑href=/event_manage/ -->
<li><a href="/event_manage/">釋出會</a></li>
<!-- 當前處理嘉賓管理頁面所以設定嘉賓選單處於選中狀態class=active -->
<li class="active"><a href="#about">嘉賓</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!-- {{}}為Django模板語言,用於定義顯示變數,user為客戶端獲取的瀏覽器sessionid對應的登入使用者名稱 -->
<li><a href="#">{{user}}</a></li>
<!-- href=/logout/用於跳轉到退出路徑 -->
<li><a href="/logout/">退出</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<!-- 嘉賓列表 -->
<div class="row" style="padding-top: 80px;">
<div class="col-md-6">
<table class="table table-striped">
<thead>
<tr>
<th>id</th><th>名稱</th><th>手機</th><th>Email</th><th>簽到</th>
<th>釋出會</th>
</tr>
</thead>
<tbody>
<!-- 通過Django模板語言for迴圈讀取嘉賓列表,並顯示id、realname等欄位 -->
{% for guest in guests %}
<tr>
<td>{{ guest.id }}</td>
<td>{{ guest.realname }}</td>
<td>{{ guest.phone}}</td>
<td>{{ guest.email }}</td>
<td>{{ guest.sign }}</td>
<td>{{ guest.event }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
修改/guest/urls.py檔案:
urlpatterns = [
……
url(r'^guest_manage/$', views.guest_manage),
]
修改/guest/sign/views.py檔案:
# 從sign應用中匯入models中的Guest類
from sign.models import Event, Guest
# 嘉賓管理
@login_required
def guest_manage(request):
username = request.session.get('user', '')
# 通過Guest.objects.all獲取全部嘉賓物件
guest_list = Guest.objects.all()
# 通過render()方法附加在guest_manage.html頁面返回給客戶端
return render(request, "guest_manage.html", {"user": username, "guests": guest_list})
給嘉賓管理頁面增加搜尋功能,可以完全參考上面釋出會管理頁面的,分別在如下檔案中增加程式碼:
/guest/sign/templates/guest_manage.html:
<!-- 釋出會搜尋表單 -->
<div class="page-header" style="padding-top: 60px;">
<div id="navbar" class="navbar-collapse collapse">
<!-- 為了和釋出會表的搜尋不衝突,修改action路徑為search_realname -->
<form class="navbar-form" method="get" action="/search_realname/">
<div class="form-group">
<!-- 注意這裡需要將name設定為realname匹配guest表裡的欄位 -->
<input name="realname" type="text" placeholder="名稱" class="form-control">
</div>
<button type="submit" class="btn btn-success">搜尋</button>
</form>
</div>
</div>
/guest/sign/views.py:
# 嘉賓名稱搜尋
@login_required
def search_realname(request):
username = request.session.get('user', '')
search_realname = request.GET.get("realname", "")
# 注意這裡需要將過濾器的名稱也修改為realname__contains
guest_list = Guest.objects.filter(realname__contains=search_realname)
return render(request, "guest_manage.html", {"user": username, "guests": guest_list})
/guest/urls.py:
urlpatterns = [
……
# 增加search_realname的路徑
url(r'^search_realname/$', views.search_realname),
]
5.3.2 分頁器
對顯示內容過多的情況下使用分頁功能。
Django的分頁功能Paginator類的基本功能演示:
root@TEST:/home/test/guest# python manage.py shell
>>> from django.core.paginator import Paginator # 匯入Paginator類
>>> from sign.models import Guest # 匯入sign應用models下的Guest表
>>> guest_list = Guest.objects.all() # 獲取guest的全部資料
>>> p = Paginator(guest_list,2) # 建立每頁2條資料的分頁器
>>> p.count # 檢視共有多少條資料
6
>>> p.page_range # 檢視公有多少分頁(每頁2條資料,迴圈結果為1、2、3,共3頁)
xrange(1, 4)
>>> page1 = p.page(1) # 獲取第1頁資料
>>> page1 # 當前是第幾頁
<Page 1 of 3>
>>> page1.object_list # 獲取當前頁的物件
<QuerySet [<Guest: 崔宇>, <Guest: Andy>]>
>>> for g in page1: # 使用for迴圈列印第1頁嘉賓的realname
... g.realname
...
u'\u5d14\u5b87'
u'Andy'
>>>
>>> page2 = p.page(2) # 獲取第2頁資料
>>> page2.start_index() # 本頁第一條資料的索引
3
>>> page2.end_index() # 本頁最後一條資料的索引
4
>>> page2.has_previous() # 本頁是否有上一頁
True
>>> page2.has_next() # 本頁是否有下一頁
True
>>> page2.previous_page_number() # 本頁的上一頁是第幾頁
1
>>> page2.next_page_number() # 本頁的下一頁是第幾頁
3
>>> page3 = p.page(3) # 獲取第3頁資料
>>> page3.has_next() # 本頁是否有下一頁
False
>>> page3.has_previous() # 本頁是否有上一頁
True
>>> page3.has_other_pages() # 是否還有其他頁
True
>>> page3.previous_page_number() # 本頁的上一頁是第幾頁
2
>>>
對嘉賓管理頁面,增加分頁器。
修改/guest/sign/views.py檔案:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
# 嘉賓管理
@login_required
def guest_manage(request):
username = request.session.get('user', '')
guest_list = Guest.objects.all() # 獲取Guest全部資料物件
paginator = Paginator(guest_list, 2) # 把查詢出來的所有嘉賓列表guest_list放到Paginator類中,劃分每頁顯示2條資料
page = request.GET.get('page') # 通過GET請求得到當前要現實第幾頁的資料
try:
contacts = paginator.page(page) # 獲取第page頁的資料
except PageNotAnInteger:
contacts = paginator.page(1) # 如果page不是整數,取第一頁面資料
except EmptyPage:
contacts = paginator.page(paginator.num_pages) # 如果page不在範圍內,取最後一頁面資料
return render(request, "guest_manage.html", {"user": username, "guests": contacts}) # 將得到的某一頁資料返回至嘉賓管理頁面上
修改/guest/sign/templates/guest_manage.html檔案:
<!-- 列表分頁器 -->
<div class="pagination">
<span class="step-links">
{% if guests.has_previous %}
<a href="?page={{ guests.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ guests.number}} of {{ guests.paginator.num_pages }}.
</span>
{% if guests.has_next %}
<a href="?page={{ guests.next_page_number }}">next</a>
{% endif %}
</span>
</div>
上面的程式碼就不做過多解釋了,因為蟲師在書中也沒有做詳細的說明,可能是覺得大家的技術水平,對於閱讀HTML程式碼,應該不成問題,我這裡,簡單說明,先是判斷是否有上一頁,有則previous可以點選,並且url頁面會跳轉到上一頁,當前只顯示當前頁和總頁數,如果有下一頁,則next可以點選,並且url頁面會跳轉到下一頁。
這裡,蟲師又說到,如果開發了嘉賓搜尋功能:“那麼同樣需要在搜尋檢視中新增分頁功能。”
但是,書中並沒有給出相關程式碼,我這裡出於好奇,也是覺得既然自己開發了嘉賓搜尋功能,那麼這個分頁器似乎也不太難,照搬應該就能給搜尋功能同樣新增一個分頁器吧。
於是,自己參考上面的程式碼,開始給搜尋功能增加分頁器,問題就接踵而至了(Ps:筆者半路出家,對前端只瞭解那麼可憐的一丟丟),下面就是自己遇到的問題,以及解決的辦法,對於我來說,能夠靠自己的仔細觀察和動手實踐解決這個問題,真的讓自己著實開心了好一下,感覺自己的堅持學習,總算是有收穫的!
首先,自己是完全沒有分析,想當然的,把上面的程式碼直接插入到了嘉賓搜尋的views.py中:
# 嘉賓名稱搜尋
@login_required
def search_realname(request):
username = request.session.get('user', '')
search_realname = request.GET.get("realname", "")
guest_list = Guest.objects.filter(realname__contains=search_realname)
paginator = Paginator(guest_list, 2)
page = request.GET.get('page')
try:
contacts = paginator.page(page)
except PageNotAnInteger:
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
return render(request, "guest_manage.html", {"user": username, "guests": contacts})
然後, html程式碼沒做任何改變,嘗試是否生效,為了驗證搜尋結果大於一頁,我在嘉賓列表裡添加了如下資料:
這樣如果我在嘉賓列表頁面搜尋“Andy”就會模糊匹配到三條結果,分頁器的效果就能展示出來了,每頁展示2條,那麼會有總共2頁:
當看到這個效果的時候,我很開心啊,顯示的結果完全符合預期,這很簡單啊,但是,當我點選next按鈕時,按理來說,應該展示搜尋結果的第二頁內容,也就是隻展示一條Andy3才對,可實際卻是,當我點選next按鈕後,頁面重新整理,返回到了嘉賓列表頁面,嘉賓列表和分頁器全部和搜尋之前一樣了。
神啊,為什麼會這樣,為什麼在搜尋的第一頁是正確的,但是點選了next就完全不對了???
看來真的不是這麼簡單,完全照搬程式碼,是不對的!還是要分析下分頁器的功能到底在嘉賓列表和嘉賓搜尋結果兩者之間有什麼區別!
下面就是自己分析以及解決的過程:
分頁器無論是在嘉賓列表頁面還是在嘉賓搜尋頁面,分頁器在展示效果上應該是同一套,只是展示結果不同,嘉賓列表要根據嘉賓總數及每頁展示數量進行展示,而嘉賓搜尋結果改變了嘉賓總數,對於這一點,上述程式碼沒有問題,是符合預期的,也就是說html裡的分頁器程式碼並沒錯;
如果我們在搜尋結果中點選next按鈕,雖然路徑仍然是“/search_realname/”,但是明顯是缺少了過濾條件“?realname=Andy”,所以才導致雖然點選的是下一頁“?page=2”,但是最終結果仍然是全部嘉賓列表;
現在問題是知道了,接下來就是如何去解決了:
先要理清一些對應關係:
http://127.0.0.1:8000/search_realname/?page=2&realname=Andy
/search_realname/:對應views.py檔案裡的def search_realname函式;
?page=2:對應guest_manage.html裡分頁器a標籤href指定的URL;
return render(request,"guest_manage.html", {"user": username, "guests": contacts})
- guests:對應guest_manage.html裡分頁器a標籤href指定的page值,即page={{ guests.next_page_number }}
理清上面這點,才能知道怎麼去改,也就是說,我們要想達到在嘉賓搜尋列表下分頁,需要同時修改views.py以及guest_manage.html兩個檔案:
首先修改/guest/sign/views.py檔案:
# 嘉賓名稱搜尋
@login_required
def search_realname(request):
username = request.session.get('user', '')
search_realname = request.GET.get("realname", "") # 通過requests的get()方法獲取realname
guest_list = Guest.objects.filter(realname__contains=search_realname) # 將realname作為過濾條件,獲取指定名稱的資料列表
paginator = Paginator(guest_list, 2) # 將過濾結果按每頁2條進行分頁
page = request.GET.get('page') # 通過GET請求得到當前要現實第幾頁的資料
try:
contacts = paginator.page(page) # 獲取第page頁的資料
except PageNotAnInteger:
contacts = paginator.page(1) # 如果page不是整數,取第一頁面資料
except EmptyPage:
contacts = paginator.page(paginator.num_pages) # 如果page不在範圍內,取最後一頁面資料
return render(request, "guest_manage.html", {"user": username, "guests": contacts, "search_realname": search_realname}) # 這裡最重要,我們增加了一個返回值,即search_realname,也就是指定過濾的嘉賓名稱,並將這個值和guest一起傳遞給guest_manage.html
然後修改/guest/sign/templates/guest_manage.html檔案,不過修改之前,我們還需要理清一個點就是,分頁器雖然是一段程式碼,但是嘉賓展示列表和嘉賓搜尋結果列表,都要同時使用這一段分頁器程式碼,所以我們就必須要區分出,什麼時候該展示嘉賓列表的分頁器,什麼時候該展示嘉賓搜尋結果列表的分頁器,使用Django模板標籤裡的{% if %}對搜尋名稱來判斷,應該是合理的,最終程式碼如下:
<!-- 列表分頁器 -->
<div class="pagination">
<span class="step-links">
<!-- 如果搜尋名稱為真則代表使用了嘉賓搜尋功能,則展示搜尋後的分頁 -->
{% if search_realname %}
{% if guests.has_previous %}
<!-- 這裡對返回給guest_manage.html的URL增加了一個realname值為搜尋的嘉賓名 -->
<a href="?page={{ guests.previous_page_number }}&realname={{ search_realname }}">previous</a>
{% endif %}
<!-- 這裡依然還是展示當前頁和總頁數 -->
<span class="current">
Page {{ guests.number}} of {{ guests.paginator.num_pages }}.
</span>
{% if guests.has_next %}
<!-- 同樣這裡也是增加了返回值realname為搜尋的嘉賓名,只有這兩個約束條件同時滿足才能保證分頁是針對嘉賓搜尋結果的 -->
<a href="?page={{ guests.next_page_number }}&realname={{ search_realname }}">next</a>
{% endif %}
<!-- 如果search_name為False,則代表並沒有使用嘉賓搜尋功能,那麼下面就正常展示嘉賓列表的搜尋頁和分頁器 -->
{% else %}
{% if guests.has_previous %}
<a href="?page={{ guests.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ guests.number}} of {{ guests.paginator.num_pages }}.
</span>
{% if guests.has_next %}
<a href="?page={{ guests.next_page_number }}">next</a>
{% endif %}
{% endif %}
</span>
</div>
完成上述操作後,就實現了,既能正確展示嘉賓列表的分頁,也能正確展示嘉賓搜尋結果的分頁,並且分頁內容及功能都正確了。
5.4 簽到功能
5.4.1 添加簽到連結
釋出會與簽到功能,是一一對應的關係。
給每個釋出會都提供一個“簽到”連結:
修改/guest/sign/templates/event_manage.html檔案:
<!-- 釋出會列表 -->
<div class="row" style="padding-top: 80px;">
<div class="col-md-6">
<table class="table table-striped">
<thead>
<tr>
<!-- 增加一個簽到列 -->
<th>id</th><th>名稱</th><th>狀態</th><th>地址</th><th>時間</th><th>簽到</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.status }}</td>
<td>{{ event.address }}</td>
<td>{{ event.start_time }}</td>
<td>
<!-- 增加簽到跳轉連結,target這個屬性意味著要在新視窗開啟連結 -->
<a href="/sign_index/{{ event.id }}/" target="{{ event.id }}_blank">sign</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
通過上述程式碼實現的功能就是:當點選“sign”連結時,路徑會自動跳轉到與event.id相對應的簽到頁面(當前還未開發)。
增加簽到頁面的路徑:
修改/guest/urls.py檔案:
from sign import views
urlpatterns = [
……
url(r'^sign_index/(?P<eid>[0-9]+)/$', views.sign_index),
]
與之前略有不同,括號內的內容匹配發佈會id,限制只能是數字,匹配的數字eid將會作為sign_index()檢視函式的引數。
5.4.2 簽到頁面
首先,建立簽到頁面的sign_index()檢視函式:
修改/guest/sign/views.py:
from django.shortcuts import render, get_object_or_404
# 簽到頁面
@login_required
def sign_index(request, eid):
event = get_object_or_404(Event, id=eid)
return render(request, 'sign_index.html', {'event': event})
get_object_or_404()方法:預設呼叫Django的table.objects.get()方法,如果查詢的物件不存在,則會丟擲一個HTTP 404異常,這樣就省去了對table.objects.get()方法做異常斷言。
然後,建立簽到頁面模板html:
修改/guest/sign/templates/sign_index.html檔案:
<!DOCTYPE html>
<html>
<head>
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<meta charset="utf-8">
<title>簽到頁面</title>
</head>
<body>
<!-- 導航欄 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<!-- 將頁面標題設定為釋出會名稱 -->
<a class="navbar-brand" href="#">{{ event.name }}</a>
</div>
<div id="navbar" class="collapse nvabar-collapse">
<ul class=