内容来源于《Web接口开发与自动化测试——基于Python语言》虫师编著,如有涉及版权问题,归虫师本人所有。请大家支持虫师的著作:http://www.broadview.com.cn/book/4811
源码下载:https://github.com/defnngj/guest
内容:这一章需要重新开发发布会管理和嘉宾管理、以及签到等页面。
5.1 Django-bootstrap3
本章将使用Bootstrap前端框架结合Django来开发Web页面。
什么是Bootstrap?
Bootstrap来自Twitter,是目前很受欢迎的前端框架。Bootstrap是基于HTML、CSS、JavaScript的,它简洁灵活,使得Web开发更加快捷。它由Twitter的设计师Mark otto和Jacob Thornton合作开发,是一个CSS/HTML框架。Bootstrap提供了优雅的HTML和CSS规范,它由动态CSS语言Less写成。
Bootstrap中文网:http://www.bootcss.com
什么是Django-bootstrap3?
Django-bootstrap3项目是将Bootstrap3(3表示Bootstrap的版本号)集成到Django中,作为Django的一个应用提供。好处是在Django中用Bootstrap会变得更加方便。
Django-bootstrap3 在PyPI仓库的地址:https://pypi.python.org/pypi/django-bootstrap3。
安装完成,在.../guest/settings.py文件中添加“bootstrap3”应用。
先安装:
(venv) liujindeMacBook-Pro:guest liujin$ pip3 install django-bootstrap3
Collecting django-bootstrap3
Downloading https://files.pythonhosted.org/packages/18/a8/f12d8491155c7f237084b883b8600faf722e3a46e54f17a25103b0fb9641/django-bootstrap3-10.0.1.tar.gz (40kB)
100% |████████████████████████████████| 40kB 65kB/s
Building wheels for collected packages: django-bootstrap3
Running setup.py bdist_wheel for django-bootstrap3 ... done
Stored in directory: /Users/liujin/Library/Caches/pip/wheels/db/cf/08/e8c774135d7b68a54f678387996c1e542af5405a3fd0a4e6e8
Successfully built django-bootstrap3
Installing collected packages: django-bootstrap3
Successfully installed django-bootstrap3-10.0.1
settings.py中增加以下配置:
# Application definition
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 发布会列表
继续回到视图层的开发,打开.../sign/views.py文件,修改event_manage()视图函数。
......
from sign.models import Event
......
# 发布会管理
@login_required()
def event_manage(request):
# username = request.COOKIES.get('user', '') # 读取浏览器cookie
event_list = Event.objects.all()
username = request.session.get('user', '') # 读取浏览器session
return render(request, "event_manage.html", {"user": username, "events": event_list})
导入Model中的Event类,通过Event.objects.all()查询所有发布会对象(数据),并通过render()方法附加在event_manage.html页面返回给客户端。
编辑event_manage.html页面。
<!DOCTYPE html>
<html>
<head>
<!-- 加载bootstrap3应用、CSS、Javascript文件,% %为Django模板标签语言 -->
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<meta charset="utf-8">
<!-- 设置页面标题 -->
<title>Guest Manage</title>
</head>
<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>
</body>
</html>
接下来分段解析页面中的代码。
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
加载bootstrap3应用、CSS和JavaScript文件。{% %}为Django模板语言的标签。
<title>Guest Manage</title>
设置页面标题为Guest Manager。
<li class="active"><a href="#">发布会</a><li>
<li><a href="/guest_manage/">嘉宾</a><li>
设置页面导航栏,class="active"表示当前菜单处于选中状态。href="/guest_manager/"用于跳转到嘉宾管理页,稍后开发该页面。
<li><a href="#">{{user}}</a></li>
<li><a href="/logout/">退出</a></li>
{{ }}为Django的模板语言标签,用于定义显示变量。user为客户端获取的浏览器sessionid对应的登录用户名。href="/logout/"定义退出路径,稍后开发该功能。
<div class="row" style="padding-top: 80px;">
在style属性中,padding-top用于设置元素的上内边距,如果不设置该属性,那么发布会列表可能会被导航栏遮挡。
{% 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 %}
通过Django模板语言,循环打印发布的id、name、status、address和start_time等字段。Django模板语言与Python语法并非完全一样。for循环语句需要有对应的endfor来表示语句的结束;同样,if分支语句也需要有endif来表示语句的结束。
通过http://127.0.0.1:8000/event_manage/访问页面:
5.2.2 搜索功能
在event_manager.html文件中添加搜索表单。
<!-- 发布会搜索表单 -->
<div class="page header" style="padding-top: 60px;">
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form" method="get" action="/search_name/">
<div class="form-group">
<input name="name" type="text" placeholder="名称" class="form-control">
</div>
<button type="submit" class="btn btn-success">搜索</button>
</form>
</div>
</div>
action="/search_name/"搜索请求路径;name="name"搜索输入框的name属性值。
在urls.py中添加搜索路径的路由。
urlpatterns = [
.......
re_path(r'^search_name/$', views.search_name)
]
在views.py中创建search_name()视图函数。
# 发布会名称搜索
@login_required
def search_name(request):
username = request.session.get('user', '')
search_name = request.GET.get("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 嘉宾列表
新建.../templates/guest_manage.html页面。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
{% load bootstrap3 %}
{% bootstrap_css %}
{% bootstrap_javascript %}
<meta charset="UTF-8">
<title>Guest Manage</title>
</head>
<body>
<!-- 导航栏 -->
<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">
<li><a href="/event_manage/">发布会</a></li>
<li class="active"><a href="#">嘉宾</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{user}}</a></li>
<li><a href="/logout/">退出</a></li>
</ul>
</div>
</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>
{% for guest in guest %}
<tr>
<td>{{ guest.id }}</td>
<td>{{ guest.realname }}</td>
<td>{{ guest.phone }}</td>
<td>{{ guest.eamil }}</td>
<td>{{ guest.sign }}</td>
<td>{{ guest.event }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
与event_manage.html页面结构基本相同,不过依然要注意两个地方:
<title>Guest Manage</title>
页面标题为Guest Manage。
<li><a href="/event_manage/">发布会</a></li>
<li class="active"><a href="#">嘉宾</a></li>
当前处理嘉宾管理页面,所以设置嘉宾菜单处于选中状态class="active"。为发布会菜单设置跳转路径href="/event_manage/"。
{% for guest in guest %}
<tr>
<td>{{ guest.id }}</td>
<td>{{ guest.realname }}</td>
<td>{{ guest.phone }}</td>
<td>{{ guest.eamil }}</td>
<td>{{ guest.sign }}</td>
<td>{{ guest.event }}</td>
</tr>
{% endfor %}
通过Django模板语言的for循环读取嘉宾列表,并显示这些字段。
在urls.py文件添加嘉宾路径的路由。
urlpatterns = [
......
re_path(r'^guest_manage/$', views.guest_manage),
]
打开views.py文件,创建guest_mansge()视图函数。
# 嘉宾管理
from sign.models import Event, Guest
......
@login_required
def guest_manage(request):
username = request.session.get('user', '')
guest_list = Guest.objects.all()
return render(request, "guest_manage.html", {"user": username, "guests": guest_list})
导入Model中的Guest类,通过Guest.objects.all()查询所有嘉宾对象数据,并通过render()方法附加在guest_manage.html页面,并返回给客户端。
嘉宾管理页面见下图:
下面写嘉宾搜索功能。
先修改guest_manage.html文件。
<!-- 嘉宾搜索表单 -->
<div class="page header" style="padding-top: 60px;">
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form" method="get" action="/guest_search/">
<div class="form-group">
<input name="realname" type="text" placeholder="请输入嘉宾名称" class="form-control">
</div>
<button type="submit" class="btn btn-success">搜索</button>
</form>
</div>
</div>
再修改urls.py。
urlpatterns = [
.......
re_path(r'^guest_search/$', views.guest_search),
]
最后修改views.py,创建guest_search()视图函数。
# 嘉宾名称搜索
@login_required
def guest_search(request):
username = request.session.get('user', '')
guest_search = request.GET.get("realname", "")
guest_list = Guest.objects.filter(realname__contains=guest_search)
return render(request, "guest_manage.html", {"user": username, "guests": guest_list})
最后的效果图如下:
5.3.2 分页器
对于嘉宾数据量比较大,所以需要分页功能。
Django提供了Paginator类来实现分页功能。分页功能略微复杂,首先进入Django的shell练习。
(venv) liujindeMacBook-Pro:guest liujin$ python3 manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.core.paginator import Paginator # 导入Paginator类
>>> from sign.models import Guest # Guest下的所有表
>>> guest_list = Guest.objects.all() # 查询Guest表的所有数据
>>> p = Paginator(guest_list, 2) # 创建每页2条数据的分页器
<string>:1: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'sign.models.Guest'> QuerySet.
>>> p.count # 查看共有多少条数据
6
>>> p.page_range # 查看分多少页,每页2条数据的话
range(1, 4)
# 第一页
>>> page1 = p.page(1) # 获取第一页数据
>>> page1 # 当前第几页
<Page 1 of 3>
>>> page1.object_list # 当前页的对象
<QuerySet [<Guest: 张三>, <Guest: 李四>]>
>>> for g in page1: # 循环打印第一页嘉宾的realname
... g.realname
...
'张三'
'李四'
# 第二页
>>> page2 = p.page(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) # 获取第三页数据
>>> page3.has_next() # 是否有下一页
False
>>> page3.has_previous() # 是否有上一页
True
>>> page3.has_other_pages() # 是否有其他页
True
>>> page3.previous_page_number() # 上一页是第几页
2
打开views.py文件,修改guest_manage()视图函数。
5.4 签到功能
对于发布会签到系统来说,最重要的功能就是签到,而发布会管理功能和嘉宾管理功能,都要服务于签到功能。
5.4.1 添加签到链接
最好的方式就是在发布列表中,给每一条发布会都提供一个“签到”链接,用来打开对于的签到页面。
在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>
<a href="/sign_index/{{ event.id }}/" target="{{ event.id }}_blank">sign</a>
</td>
</tr>
{% endfor %}
</tbody>
......
这样,发布会管理列表多了一列签到链接。
当单击“sign”链接时,路径默认跳转到“/sign_index/{{ event_id }}/”路径。其中,{{ event_id }}为发布会id。target="{{ event.id }}_blank"属性设置链接在新窗口打开。
接下来在urls.py文件中添加签到页面路径的路由。
urlpatterns = [
......
re_path(r'^sign_index/(?P<eid>[0-9]+)/$', views.sign_index),
]
这里的(?P<eid>[0-9]+)匹配发布会的id,而且必须为数字。
5.4.2 签到页面
打开views.py文件,创建sign_index()视图函数。
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})
sign_index()函数获取从URL配置得到的eid,作为发布会id的查询条件。
创建..../templates/sign_index.html签到页面。
<!DOCTYPE html>
<html lang="zh-CN">
<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 navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/event_manage/">发布会</a></li>
<li><a href="/guest_manage/">嘉宾</a></li>
</ul>
</div>
</div>
</nav>
<!-- 签到功能 -->
<div class="page-header" style="padding-top: 80px;">
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form" method="post" action="/sign_index_action/{{ event.id}}/">
<div class="form-group">
<input name="phone" type="text" placeholder="请输入手机号" class="form-control">
</div>
<button type="submit" class="btn btn-success">签到</button>
<font color="red">
<br>{{ hint }}
<br>{{ guest.realname }}
<br>{{ guest.phone }}
</font>
</form>
</div>
</div>
</body>
</html>
<a class="navbar-brand" href="#">{{ event.id }}</a>
将页面标题设置为发布会名称。
<li><a href="/event_manage/">发布会</a></li>
<li><a href="/guest_manage/">嘉宾</a></li>
设置发布会与嘉宾导航链接。
<form class="navbar-form" method="post" action="/sign_index_action/{{ event.id}}/">
签到表单通过POST请求将签到手机号提交到/sign_index_action/{{ event.id}}/路径。
5.4.3 签到动作
打开urls.py文件,添加签到动作路径的路由。
urlpatterns = [
......
re_path(r'^sign_index_action/(?P<eid>[0-9]+)/$', views.sign_index_action)
]
打开views.py文件,创建sign_index_action()视图函数。
from django.views.decorators.csrf import csrf_exempt
......
# 签到动作
@login_required
@csrf_exempt
def sign_index_action(request, eid):
event = get_object_or_404(Event, id=eid)
phone = request.POST.get('phone', '')
print(phone)
result = Guest.objects.filter(phone=phone)
if not result:
return render(request, 'sign_index.html', {'event': event, 'hint': 'phone error.'})
result = Guest.objects.filter(phone=phone, event_id=eid)
if not result:
return render(request, 'sign_index.html', {'event': event, 'hint': 'event id or phone error.'})
result = Guest.objects.get(phone=phone, event_id=eid)
if result.sign:
return render(request, 'sign_index.html', {'event': event, 'hint': "user has sign in."})
else:
Guest.objects.filter(phone=phone, event_id=eid).update(sign='1')
return render(request, 'sign_index.html', {'event': event, 'hint': 'sign in success!', 'guest': result})
通过一些列的判断来展示对应的结果。
下图是签到成功的截图:
5.5 退出系统
打开uls.py,添加退出路径的路由。
urlpatterns = [
......
re_path(r'^logout/$', views.logout),
]
打开views.py,创建logout()视图函数。
# 退出登录
@login_required
def logout(request):
auth.logout(request)
response = HttpResponseRedirect('/index/')
return response
退出后就到登录界面。