《Web接口开发与自动化测试基于Python语言》–第5章

来源:互联网 发布:洞主的淘宝店 编辑:程序博客网 时间:2024/05/16 06:51

《Web接口开发与自动化测试基于Python语言》–读书笔记

第5章 Django模板

项目代码: https://github.com/defnngj/guest

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_requireddef 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_requireddef 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_requireddef 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_requireddef 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_requireddef 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_requireddef 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就完全不对了???

看来真的不是这么简单,完全照搬代码,是不对的!还是要分析下分页器的功能到底在嘉宾列表和嘉宾搜索结果两者之间有什么区别!

下面就是自己分析以及解决的过程:

  1. 分页器无论是在嘉宾列表页面还是在嘉宾搜索页面,分页器在展示效果上应该是同一套,只是展示结果不同,嘉宾列表要根据嘉宾总数及每页展示数量进行展示,而嘉宾搜索结果改变了嘉宾总数,对于这一点,上述代码没有问题,是符合预期的,也就是说html里的分页器代码并没错;

  2. 但是为什么会出现点击了next按钮后,页面就返回到了嘉宾列表页面呢,看来问题是出在这个next按钮上,我们仔细观察URL的变化:

    • 正常展示嘉宾列表时URL为:http://127.0.0.1:8000/guest_manage/

    • 此时next按钮的URL为:http://127.0.0.1:8000/guest_manage/?page=2

    • 当使用搜索按钮过滤“Andy”时URL为:http://127.0.0.1:8000/search_realname/?realname=Andy

    • 此时next按钮的URL为:http://127.0.0.1:8000/search_realname/?page=2

  3. 如果我们在搜索结果中点击next按钮,虽然路径仍然是“/search_realname/”,但是明显是缺少了过滤条件“?realname=Andy”,所以才导致虽然点击的是下一页“?page=2”,但是最终结果仍然是全部嘉宾列表;

  4. 那是不是我们把过滤条件和分页器组合一下,结果就对了呢?尝试在浏览器中输入URL:http://127.0.0.1:8000/search_realname/?page=2&realname=Andy,结果真的是只展示了搜索条件下的第二页内容:

这里写图片描述

现在问题是知道了,接下来就是如何去解决了:

先要理清一些对应关系:

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_requireddef 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 viewsurlpatterns = [    ……    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_requireddef 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="nav navbar-nav">                        <!-- 设置发布会、嘉宾的导航链接 -->                        <li><a href="/event_manage/">发布会</li>                        <li><a href="/guest_manage/">嘉宾</li>                    </ul>                </div>            </div>        </nav>        <!-- 签到功能 -->        <div class="page-header" style="padding-top: 80px;">            <div id="navbar" class="navbar-collapse collapse">                <!-- 签到表单通过POST请求将签到手机号提交到/sign_index_action/{{ event.id }}路径,{{ event.id }}会替换为发布会的id -->                <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>

添加完html页面后,点击sign跳转到签到页面,结果完全没有样式,才想起来,忘记加载bootstrap3了。在代码head部分加上。

html各功能解释参考备注。

5.4.3 签到动作

继续完善签到功能,上面是完成了sign_index签到页面,接下来就是对签到动作也就是sign_index_action相关功能。

首先,添加签到动作的路径:

修改/guest/urls.py文件:

from sign import viewsurlpatterns = [    ……    url(r'^sign_index_action/(?P<eid>[0-9]+)/$', views.sign_index_action),]

其次,增加签到动作的视图函数:

修改/guest/sign/views.py文件:

# 签到动作@login_requireddef sign_index_action(request, eid):    event = get_object_or_404(Event, id=eid)                   # 还是利用get_object_or_404()方法通过eid获取对应发布会,没有就报404异常    phone = request.POST.get('phone', '')                      # 利用request的POST请求获取用户输入的手机号    print (phone)    result = Guest.objects.filter(phone=phone)                 # 根据用户输入的手机号查询在Guest表中的记录    if not result:                                             # 如果用户输入的手机号在Guest表不存在,则提示用户“phone error.”        return render(request, 'sign_index.html', {'event': event, 'hint': 'phone error.'})    result = Guest.objects.filter(phone=phone, event_id=eid)   # 通过用户输入的手机号和对应的发布会id查找Guest表,则说明手机号与发布会不匹配,提示用户“event id or phone error.”    if not reusult:        return render(request, 'sign_index.html', {'event': event, 'hint': 'event id or phone error.'})    result = Guest.objects.get(phone=phone, event_id=eid)   # 还是通过用户输入的手机号和发布会id获取数据对象    if result.sign:                                         # 从数据对象中获取sign字段的值,如果为真(1),则说明嘉宾已经签到过了,提示用户“user has sign in.”         return render(request, 'sign_index.html', {'event': event, 'hint': 'user has sign in.'})    else:                                                   # 否则,说明嘉宾未签到,修改签到状态为1并提示用户“sign in success”,同时显示嘉宾的姓名和手机号        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})

签到动作最关键的就是对输入的手机号以及手机号对应的发布会的对应关系的一个判断,所以sign_index_action里最多的就是if判断,详细见备注。

这里虫师又给大家挖了个坑!!!

当我按照上面的代码进行编写后,在签到页面输入一个手机号,点击签到按钮,系统居然提示403错误:

这里写图片描述

这是神马情况,明显是身份认证的错误,这在之前做登录认证页面的时候,好像也见过。
虫师之前说过的一段话很好,就是要仔细阅读错误信息,也许答案就在其中,再回头看签到功能相关代码,我们发现问题出现在两个地方:

问题1:
我们在视图里定义sign相关函数的时候,都加了装饰器@login_required,但是在返回给页面的时候,没有加用户信息。

问题2:
我们在模板sign_index.html里,在定义签到功能的时候,使用了POST请求,而POST请求必须有{% csrf_token %}标签才可以。

综合以上两点,做出修改如下修改:

首先是/guest/sign/views.py文件:

# 签到页面@login_requireddef sign_index(request, eid):    username = request.session.get('user', '')    # 增加用户身份信息    event = get_object_or_404(Event, id=eid)    return render(request, 'sign_index.html', {"user": username, "event": event})    # 增加用户身份信息# 签到动作@login_requireddef sign_index_action(request, eid):    username = request.session.get('user', '')    # 增加用户身份信息    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', {'user': username, 'event': event, 'hint': 'phone error.'})                                         # 增加用户身份信息    result = Guest.objects.filter(phone=phone, event_id=eid)    if not result:        return render(request, 'sign_index.html', {'user': username, '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', {'user': username, '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', {'user': username, 'event': event, 'hint': 'sign in success!', 'guest': result})                   # 增加用户身份信息

然后是/guest/sign/templates/sign_index.html文件:

<!-- 签到功能 -->        <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>                    <!-- 对POST动作增加身份认证信息 -->                    {% csrf_token %}                    <font color="red">                        <br>{{ hint }}                        <br>{{ guest.realname }}                        <br>{{ guest.phone }}                    </font>                </form>            </div>        </div>

经过上述修改后,再次尝试使用手机号进行签到,效果就和书中一样了:

这里写图片描述

这里写图片描述

这里写图片描述

虫师留的作业:

要求是:如何将当前发布的嘉宾数和已签到数显示在签到页面上。签到嘉宾数固定,每成功签到一位嘉宾,就将签到数加1,这样我们就可以实时知道当前发布会还有多少嘉宾未签到了。

首先,分析需求,需求仅要求我们将几个重要的数据展示出来:

  • 一个发布会总的嘉宾数量,固定值,通过查询嘉宾数据表,指定发布会id即可;

  • 一个已签到的嘉宾数量,可变值,每当成功签到一位嘉宾,该值就加1,已签到嘉宾可通过查询嘉宾数据表,指定sign字段为1即可;

将上述信息展示在签到页面,格式无所谓,我理解虫师的本意,初始情况,应该是先获取一个总共的嘉宾数,再获取一个已签到嘉宾数,将这两个值展示出来,但是当用户使用手机号进行签到后,就要将已签到嘉宾数加1展示,也就是已签到嘉宾数要初始获取一个固定值,每当签到成功后,该值加1,签到不成功,不加1,理清了这些需求,剩下就是实现了。

同样还是需要对两处进行改动:

  • 一是视图里的sign_indext_action函数,增加这两个值的处理;

  • 二是模板里的sign_index.html函数,增加展示的相关内容;

修改/guest/sign/views.py文件:

from django.shortcuts import render, get_object_or_404, get_list_or_404# 签到页面@login_requireddef sign_index(request, eid):    username = request.session.get('user', '')    event = get_object_or_404(Event, id=eid)    guest_list = len(get_list_or_404(Guest, event_id=eid))    guest_sign = len(get_list_or_404(Guest, event_id=eid, sign=1))    return render(request, 'sign_index.html', {"user": username, "event": event, 'guest_list': guest_list, 'guest_sign': guest_sign})# 签到动作@login_requireddef sign_index_action(request, eid):    username = request.session.get('user', '')    event = get_object_or_404(Event, id=eid)    guest_list = len(get_list_or_404(Guest, event_id=eid))    guest_sign = len(get_list_or_404(Guest, event_id=eid, sign=1))    phone = request.POST.get('phone', '')    print (phone)    result = Guest.objects.filter(phone=phone)    if not result:        return render(request, 'sign_index.html', {'user': username, 'event': event, 'hint': 'phone error.', 'guest_list': guest_list, 'guest_sign': guest_sign})    result = Guest.objects.filter(phone=phone, event_id=eid)    if not result:        return render(request, 'sign_index.html', {'user': username, 'event': event, 'hint': 'event id or phone error.', 'guest_list': guest_list, 'guest_sign': guest_sign})    result = Guest.objects.get(phone=phone, event_id=eid)    if result.sign:        return render(request, 'sign_index.html', {'user': username, 'event': event, 'hint': 'user has sign in.', 'guest_list': guest_list, 'guest_sign': guest_sign})    else:        Guest.objects.filter(phone=phone, event_id=eid).update(sign='1')        guest_sign = guest_sign + 1        return render(request, 'sign_index.html', {'user': username, 'event': event, 'hint': 'sign in success!', 'guest': result, 'guest_list': guest_list, 'guest_sign': guest_sign})

这里最关键的有三点:

第一,是我们使用了新的获取数据表对象列表的方法get_list_or_404(),该方法会返回查询数据结果列表,如果没有查询到指定数据就返回404错误页面;

第二,我们希望是进入到签到页面就展示嘉宾总数和已签到嘉宾数,当只有签到成功后,才变更已签到嘉宾数,所以在sign_index函数和sign_index_action两个函数都使用这两个变量;

第三,我们希望只在签到成功才变更已签到数量,其余时候都只是返回当前的嘉宾总数和已签到嘉宾数;

最终效果如下:

这里写图片描述

这里写图片描述

5.5 退出系统

最后来实现退出系统功能。

修改/guest/urls.py文件,增加退出路径:

from sign import viewsurlpatterns = [    ……    url(r'^logout/$', views.logout),]

修改/guest/sign/views.py文件,增加logout()视图函数:

# 退出动作@login_requireddef logout(request):    auth.logout(request)    response = HttpResponseRedirect('/index/')    return response

Django的auth.login()方法用于验证登录用户信息,同时也提供了auth.logout()方法用于退出系统,它可以清除浏览器保存的用户信息,这样用户就不用考虑如何删除浏览器的cookie信息。退出成功后,页面跳转至/index/首页。

总结:

第五章可以说是对发布会、嘉宾、签到三者功能上的一个大概实现。通过本章,该功能已经初步可用。代码比较多,我自己也加了很多的注释,通过发现书中的问题,以及解决,对Django的MTV开发模式有了更清楚的认识,清楚了三者之间如何联动,如何实现。知识点主要集中在视图函数的几个库函数的使用,以及简单使用bootstrap3来装扮html的样式。

可以说,多自己动手,多自己思考,才是消化这章的最佳办法。因为有些问题是我自己处理的,如果大家在阅读过程中发现有错误,或者更好的办法,欢迎交流指正。

阅读全文
1 0