zoukankan      html  css  js  c++  java
  • 玩转Django2.0---Django笔记建站基础十(二)(常用的Web应用程序)

    10.3  CSRF防护

      CSRF(跨站请求伪造)也成为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用,窃取网站的用户信息来制作恶意请求。

      Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:

        1、在用户访问网站时,Django在网页的表单中生成一个隐含字段csrftoken,这个值是在服务器端随机生成的。

        2、当用户提交表单时,服务器检验表单的csrftoken是否和自己保存的csrftoken一致,用来判断当前请求是否合法。

        3、如果用户被CSRF攻击并从其他地方发生攻击请求,由于其他地方不可能知道隐藏的csrftoken信息,因此导致网站后台校验csrftoken失败,攻击就被成功防御。

      在Django中使用CSRF防护功能,首先在配置文件settings.py中设置防护功能的配置信息。功能的开启由配置文件的中间件django.middleware.csrf.CsrfViewMiddleware实现,在创建项目时已默认开启,如下图:

    设置CSRF防护

      CSRF防护只作用于POST请求,并不防护GET请求,因为GET请求以只读形式访问网站资源,并不破坏和篡改网站数据。以MyDjango为例,在模板user.html的表单<form>标签中加入内置标签csrf_token即可实现CSRF防护,代码如下:

    #user/user.html部分代码
    <div class="flex-center">
        <div class="container">
        <div class="flex-center">
        <div class="unit-1-2 unit-1-on-mobile">
            <h1>MyDjango Auth</h1>
                {% if tips %}
            <div>{{ tips }}</div>
                {% endif %}
            <form class="form" action="" method="post">
                {% csrf_token %}
                <div>用户名:<input type="text" name='username'></div>
                <div>密 码:<input type="password" name='password'></div>
                <button type="submit" class="btn btn-primary btn-block">确定</button>
            </form>
        </div>
        </div>
        </div>
    </div>

      启动运行MyDjango,在浏览器中打开用户登录页面,然后查看页面的源码,可以发现表单新增隐藏域,隐藏域是由模板语法{%  csrf_token  %}所生成的,网站生成的csrftoken都会记录在隐藏域的value属性中。当用户每次提交表单时,csrftoken都会随之变化,如下图:

    csrftoken信息

      如果想要取消表单的CSRF防护,可以在模板上删除{%  csrf_token  %},并且在相应的视图函数中添加装饰器@csrf_exempt,代码如下:

    from django.views.decorators.csrf import csrf_exempt
    
    #取消CSRF防护
    @csrf_exempt
    def registerView(request):
        pass
        return render(request, 'user.html', locals())

      如果只是在模板上删除{%  csrf_token  %},并没有在相应的视图函数中设置过滤器@csrf_exempt,那么当用户提交表单时,程序因CSRF验证失败而抛出403异常的页面,如下图:

    CSRF验证失败

      最后还有一种比较特殊的情况,如果在配置文件settings.py中删除中间件CsrfViewMiddleware,这样使整个网站都取消CSRF防护。在全站没有CSRF防护的情况下,又想对某些请求设置CSRF防护,那么在模板上添加模板语法{%  csrf_token  %},然后在相应的视图函数中添加装饰器@csrf_protect即可实现,代码如下:

    from django.views.decorators.csrf import csrf_protect, csrf_exempt
    
    #添加CSRF防护
    @csrf_protect
    def registerView(request):
        pass
        return render(request, 'user.html', locals())

      值得注意的是,在日常开发中,如果网页某些数据时使用前端的Ajax实现表单提交的,那么Ajax向服务器发送POST请求时,请求参数必须添加csrftoken的信息,否则服务器会视该请求是恶意请求。实现代码如下:

    <script>
        function submitForm() {
            var csrf = $('input[name="csrfmiddlewaretoken"]').val();
            var user = $('#user').val();
            var password = $('#password').val();
            $.ajax({
                url: '/csrf1.html',
                type: 'POST',
                data: {'user': user,
                       'password': password,
                       'csrfmiddlewaretoken': csrf,}
                success: function (arg) {
                    console.log(arg);
                }
            })
        }
    </script>

    10.4  消息提示

      在网页应用中,当处理完表单或完成其他信息输入后,网站会有相应的操作提示。Django有内置消息提示功能供开发者直接使用,信息提示功能由中间件SessionMiddleware、MessageMiddleware和INSTALLED_APPS的django.contrib.messages共同实现。在创建Django项目时,消息提示功能已默认开启,如下图:

    消息提示功能配置

      消息提示必须依赖中间件SessionMiddleware,因为消息提示的引擎默认是SessionStorage,而SessionStorage是在Session的基础上实现的,同时说明了中间件SessionMiddleware为什么设置在MessageMiddleware的前面。

      使用信息提示功能之前,需要了解消息提示的类型,Django提供了5种消息类型,说明如下表所示:

    类型 说明
    DEBUG 提示开发过程中的相关调式信息
    INFO 提示信息,如用户信息
    SUCCESS 提示当前操作执行成功
    WARNING 警告当前操作存在风险
    ERROR 提示当前操作错误

    Django提供的5种消息类型

      若想在开发中使用消息提示,首先在视图函数中生成相关的信息内容,然后在模板中将信息内容展现在网页上。因此,在index中定义相关的URL地址和相应的视图函数,代码如下:

    from django.urls import path
    from . import views
    urlpatterns = [
        # 首页的URL
        path('', views.index, name='index'),
        # 购物车
        path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar'),
        # 消息提示
        path('message.html', views.messageView, name='message'),
    ]
    
    #index/index.html
    from django.template import RequestContext
    from django.contrib import messages
    
    # 消息提示
    def messageView(request):
        # 信息添加方法一
        messages.info(request, '信息提示')
        messages.success(request, '信息正确')
        messages.warning(request, '信息警告')
        messages.error(request, '信息错误')
        # 信息添加方法二
        messages.add_message(request, messages.INFO, '信息提示')
        return render(request, 'message.html', locals(), RequestContext(request))

      在视图函数messageView中可以看到添加信息有两种方式,两者实现的功能是一样的。在函数返回时,必须设置RequestContext(request),这是Django的上下文处理器,确保信息messages对象能在模板中使用。最后在index的template中创建模板message.html,模板代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>信息提示</title>
    </head>
    <body>
        {% if messages %}
            <ul>
                {% for message in messages %}
                    {# message.tags代表信息类型 #}
                    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
                {% endfor %}
            </ul>
        {% else %}
            <script>alert('暂无信息');</script>
        {% endif %}
    </body>
    </html>

      在上述例子中,视图函数messageView将对象messages通过上下文处理器RequestContext(request)传递给模板变量messages,然后将模板变量messages的内容遍历输出,最后通过模板引擎解析生成HTML网页。在浏览器上访问http://127.0.0.1:8000/message.html,网页信息如下图:

    消息提示功能应用

     

     

    10.5  分页功能

      在网页上浏览数据的时候,数据列表的下方都能看到翻页功能,而且每一页的数据都不相同。比如在淘宝上搜索某商品的关键字,淘宝会根据用户提供的关键字返回符合条件的商品信息,并且对这些商品信息进行分页处理,用户可以在商品信息的下方单击相应的页数按钮查看。

      如果要实现数据的分页功能,需要考虑多方面因素:

        1、当前用户访问的页数是否存在上(下)一页。

        2、访问的页数是否超出页数上限。

        3、数据如何按页截取,如何设置每页的数据量

      Django已为开发者提供了内置的分页功能,开发者无须自己实现数据分页功能,只需调用Django内置分页功能的函数即可实现。在实现网站数据分页之前,首先了解Django的分页功能为开发者提供了那些方法与函数,在PyCharm的Terminal中开启Django的shell模式,函数使用说明如下:

    (py3_3) E:	est5MyDjango>python manage.py shell
    
    #导入分页功能模块
    In [1]: from django.core.paginator import Paginator
    #生成数据列表
    In [2]: objects = [chr(x) for x in range(97,107)]
    
    In [3]: objects
    Out[3]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
    #将数据列表以每三个元素分为一页
    In [4]: p = Paginator(objects, 3)
    #输出全部数据,即整个数据列表
    In [5]: p.object_list
    Out[5]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
    #获取数据列表的长度
    In [6]: p.count
    Out[6]: 10
    
    #分页后的总页数
    In [7]: p.num_pages
    Out[7]: 4
    #将页数转换成range循环对象
    In [8]: p.page_range
    Out[8]: range(1, 5)
    #获取第二页的数据信息
    In [9]: page2 = p.page(2)
    #判断第二页是否存在上一页
    In [10]: page2.has_previous()
    Out[10]: True
    #如果当前页数存在上一页,就输出上一页的页数,否则抛出EmptyPage异常
    In [11]: page2.previous_page_number()
    Out[11]: 1
    #判断第二页是否存在下一页
    In [12]: page2.has_next()
    Out[12]: True
    
    #如果当前页数存在下一页,就输出下一页的页数,否则抛出EmptyPage异常
    In [13]: page2.next_page_number()
    Out[13]: 3
    #输出第二页所对应的数据内容
    In [14]: page2.object_list
    Out[14]: ['d', 'e', 'f']
    #输出第二页的第一条数据在整个数据列表的位置,数据位置从1开始计算
    In [15]: page2.start_index()
    Out[15]: 4
    #输出第二页的最后一条数据在整个数据列表的位置,数据位置从1开始计算
    In [16]: page2.end_index()
    Out[16]: 6

      上述代码是Django分页功能的使用方法,根据对象类型可以将代码分为两部分:分页对象p和某分页对象page2,两者说明如下:

        (1)分页对象p:由模块Paginator实例化生成。在Paginator实例化时,需要传入参数object和per_page,参数object是待分页的数据对象,参数per_page用于设置每页的数据量。对象p提供表所示的函数:

    函数 说明
    object_list 输出被分页的全部数据,即数据列表objects
    Count 获取当前被分页的数据总量,即数据列表objects的长度
    num_pages 获取分页后的总页数
    page_range 将总页数转换成range循环对象
    page(number) 获取某一页的数据对象,参数number代表页数

         (2)某分页对象page2:由对象p使用函数page所生成的对象。page2提供表所示的函数:

    函数 说明
    has_previous() 判断当前页数是否存在上一页
    previous_page_number() 如果当前页数存在上一页,输出上一页的页数,否则抛出EmptyPage异常
    has_next() 判断当前页数是否存在下一页
    next_page_number() 如果当前页数存在下一页,输出下一页的页数,否则抛出EmptyPage异常
    object_list 输出当前分页的数据信息
    start_index() 输出当前分页的第一条数据在整个数据列表的位置,数据位置以1开始计算
    end_index() 输出当前分页的最后一条数据在整个数据列表的位置,数据位置以1开始计算

      我们通过一个示例来讲述如何在开发过程中使用Django内置分页功能。在MyDjango的数据库中分别对数据表index_type和index_product添加数据信息,数据来源于第6章,可以在本书源码中找到数据文件,如下图所示:

       然后在index中添加模板pagination.html,模板的样式文件common.css和pagination.css存放在index的静态文件夹static中,如下图:

      完成项目的环境搭建后,本示例的分页功能需要由index的urls.py、views.py和pagination.html共同实现,首先在urls.py和views.py中分别添加以下代码:

    #index/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        # 首页
        path('', views.index, name='index'),
        # 购物车
        path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar'),
        # 分页功能
        path('pagination/<int:page>.html', views.paginationView, name='pagination'),
    ]
    
    #index/views.py
    #views.py的paginationView函数
    #导入paginator模块
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    #分页功能
    def paginationView(request, page):
        # 获取数据表index_product全部数据
        Product_list = Product.objects.all()
        # 设置每一页的数据量为3
        paginator = Paginator(Product_list, 3)
        try:
            pageInfo = paginator.page(page)
        except PageNotAnInteger:
            # 如果参数page的数据类型不是整型,则返回第一页数据
            pageInfo = paginator.page(1)
        except EmptyPage:
            # 用户访问的页数大于实际页数,则返回最后一页的数据
            pageInfo = paginator.page(paginator.num_pages)
        return render(request, 'pagination.html', locals())

      上述代码设置了分页功能的URL地址和相应的视图函数,其说明如下:

        1、URL地址设置了动态变量page,该变量代表用户当前访问的页数。

        2、函数paginationView首先获取数据表index_product中的全部数据,生成变量Product_list。

        3、通过分页模块paginator对变量Product_list进行分页,以每3条数据划分为一页。

        4、使用函数page获取分页对象paginator中某一页分页的数据信息。

        5、当变量page传入函数page时,如果变量page不是整型,程序就会抛出PageNotAnInteger异常,然后返回第一页的数据。

        5、如果变量page的数值大于总页数,程序就会抛出EmptyPage异常,然后返回最后一页的数据。异常PageNotAnInteger和EmptyPage都来自于模块paginator。

      将视图函数处理的结果传给模板文件pagination.html,然后把分页后的数据展示在网页中。模板文件pagination.html的代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>分页功能</title>
        {# 导入CSS样式文件 #}
        {% load staticfiles %}
        <link type="text/css" rel="stylesheet" href="{% static "css/common.css" %}">
        <link type="text/css" rel="stylesheet" href="{% static "css/pagination.css" %}">
    </head>
    <body>
    <div class="wrapper clearfix" id="wrapper">
    <div class="mod_songlist">
        <ul class="songlist__header">
            <li class="songlist__header_name">产品名称</li>
            <li class="songlist__header_author">重量</li>
            <li class="songlist__header_album">尺寸</li>
            <li class="songlist__header_other">产品类型</li>
        </ul>
        <ul class="songlist__list">
            {# 列出当前分页所对应的数据内容 #}
            {% for item in pageInfo %}
            <li class="js_songlist__child" mid="1425301" ix="6">
                <div class="songlist__item">
                    <div class="songlist__songname">{{item.name}}</div>
                    <div class="songlist__artist">{{item.weight}}</div>
                    <div class="songlist__album">{{item.size}}</div>
                    <div class="songlist__other">{{ item.type }}</div>
                </div>
            </li>
            {% endfor %}
        </ul>
        {# 分页导航 #}
        <div class="page-box">
        <div class="pagebar" id="pageBar">
        {# 上一页的URL地址 #}
        {% if pageInfo.has_previous %}
            <a href="{% url 'pagination' pageInfo.previous_page_number %}" class="prev"><i></i>上一页</a>
        {% endif %}
        {# 列出所有的URL地址 #}
        {% for num in pageInfo.paginator.page_range %}
            {% if num == pageInfo.number %}
                <span class="sel">{{ pageInfo.number }}</span>
            {% else %}
                <a href="{% url 'pagination' num %}" target="_self">{{num}}</a>
            {% endif %}
        {% endfor %}
        {# 下一页的URL地址 #}
        {% if pageInfo.has_next %}
            <a href="{% url 'pagination' pageInfo.next_page_number %}" class="next">下一页<i></i></a>
        {% endif %}
        </div>
        </div>
    </div><!--end mod_songlist-->
    </div><!--end wrapper-->
    </body>
    </html>
    pagination.html

      完成urls.py、views.py和pagination.html的代码编写后,最后测试功能是否正常运行。启动项目并在浏览器上访问http://127.0.0.1:8000/pagination/1.html,单击分页导航时,程序会字段跳转到相应的URL地址并返回对应的数据信息,运行结果如下图:

    10.6  本章小结

      Django为开发者提供了常见的Web应用程序,如会话控制、高速缓存、CSRG防护、消息提示和分页功能。内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而且也提高了开发者的开发效率。

      Django提供5种不同的缓存方式,每种缓存方式说明如下:

        1、Memcached:一个高性能的分布式内存对象缓存系统,用于动态网站,以减轻数据库负载。通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站的响应速度。使用Memcached需要安装系统服务器,Django通过python-memcached或pylibmc模块调用Memcached系统服务器,实现缓存读写操作,适合超大型网站使用。

        2、数据库缓存:缓存信息存储在网站数据库的缓存表中,缓存表可以在项目的配置文件中配置,适合大中型网站使用。

        3、文件系统缓存:缓存信息以文本文件格式保存,适合中小型网站使用。

        4、本地内存缓存:Django默认的缓存保存方式,将缓存存放在项目所在系统的内存中,只适用于项目开发测试。

        5、虚拟缓存:Django内置的虚拟缓存,实际上只提供缓存接口,并不能存储缓存数据,只适用于项目开发测试。

      Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:

        1、在用户访问网站时,Django在网页的表单中生成一个隐含字段csrftoken,这个值是在服务器端随机生成的。

        2、当用户提交表单时,服务器检验表单的csrftoken是否和自己保存的csrftoken一致,用来判断当前请求是否合法。

        3、如果用户被CSRF攻击并从其他地方发生攻击请求,由于其他地方不可能知道隐藏的csrftoken信息,因此导致网站后台校验csrftoken失败,攻击就被成功防御。

      消息提示必须依赖中间件SessionMiddleware,因为消息提示的引擎默认是SessionStorage,而SessionStorage是在Session的基础上实现的,同时说明了中间件SessionMiddleware为什么设置在MessageMiddleware的前面。

      使用信息提示功能之前,需要了解消息提示的类型,Django提供了5种消息类型,说明如下表所示:

    类型 说明
    DEBUG 提示开发过程中的相关调式信息
    INFO 提示信息,如用户信息
    SUCCESS 提示当前操作执行成功
    WARNING 警告当前操作存在风险
    ERROR 提示当前操作错误

    Django提供的5种消息类型

    上述代码是Django分页功能的使用方法,根据对象类型可以将代码分为两部分:分页对象p和某分页对象page2,两者说明如下: 

    函数 说明
    object_list 输出被分页的全部数据,即数据列表objects
    Count 获取当前被分页的数据总量,即数据列表objects的长度
    num_pages 获取分页后的总页数
    page_range 将总页数转换成range循环对象
    page(number) 获取某一页的数据对象,参数number代表页数
    函数 说明
    has_previous() 判断当前页数是否存在上一页
    previous_page_number() 如果当前页数存在上一页,输出上一页的页数,否则抛出EmptyPage异常
    has_next() 判断当前页数是否存在下一页
    next_page_number() 如果当前页数存在下一页,输出下一页的页数,否则抛出EmptyPage异常
    object_list 输出当前分页的数据信息
    start_index() 输出当前分页的第一条数据在整个数据列表的位置,数据位置以1开始计算
    end_index() 输出当前分页的最后一条数据在整个数据列表的位置,数据位置以1开始计算

  • 相关阅读:
    SpringBoot_04springDataJPA
    SpringBoot_03mybatisPlus
    SpringBoot_02通用mapper
    SpringBoot_01
    MySQL索引背后的数据结构及算法原理
    learnVUE-note
    Java集合
    Java虚拟机的类加载机制
    设计模式中类之间的关系
    设计模式——创建型模式
  • 原文地址:https://www.cnblogs.com/zhaop8078/p/11615035.html
Copyright © 2011-2022 走看看