第十一章——常用的Web应用程序

django常用的Web程序

会话机制

当用户第一次访问网站时,网站的服务器将自动创建一个Session对象,该Session对象相当于该用户在网站的一个身份凭证,而且Session能存储该用户的数据信息。当用户在网站的页面之间跳转时,存储在Session对象中的数据不会丢失,只有Session过期或被清理时,服务器才将Session中存储的数据清空,并终止该Session.

当获取某用户的Session数据时,首先从用户传递的Cookie里获取sessionid,然后根据sessionid在网站服务器找到相应的Session。

Session存放在服务器的内存中,因此存放在Session中的数据不能过于庞大。

每个用户的Session通过Django的中间件MIDDLEWARE接收和调度处理

django.contrib.sessions.middleware.SessionMiddleware

当访问网站时,所有的HTTP请求都经过中间件处理,中间件SessionMiddleware
相当于HTTP请求接收器,根据请求信息做出相应的调度,而程序的执行则由settings.py的配置属性INSTALLED_APPS中django.contrib.sessions
完成。
django.contrib.sessions实现了Session的创建和操作处理,如创建或存储用户的Session对象,管理Session的生命周期等,它默认使用数据库存储Session信息,数据库名为django_session

变更Session的存储方式

Session默认存储在数据库中,变更的话可在settings.py中添加配置信息SESSION_ENGINE,该配置可以指定Session的保存方式。
django提供5种Session的保存方式

# 数据库存储方式,django默认,无需再settings.py中设置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'

# 以文件形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
# 使用文本保存可设置文件保存路径,有相对路径和绝对路径
SESSION_FILE_PATH = '/MyDjango' # /MyDjango代表文本保存在项目MyDjango的根目录

# 以缓存形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 设置缓存名,默认是内存缓存方式,此处的设置与缓存机制的设置有关
SESSION_CACHE_ALIAS = 'default'

# 以数据库+缓存形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

# 以Cookie形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

设置浏览器中的Cookie相关信息

也是通过服务器后端的Session设置的
比如Cookie的生命周期,传输方式或保存路径等,都是自setting.py中进行配置属性。

# Session配置相关Cookie的配置信息
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 是否关闭浏览器使得Cookie过期,默认值为False,该配置在第一次登陆时才有效,缘由
SESSION_COOKIE_NAME = "sessionid" # 浏览器的Cookie以键值对的形式保存数据表django_session的session_key,该配置是设置session_key的键,默认值为sessionid
SESSION_COOKIE_PATH = "/"#设置浏览器的Cookie的生效路径,默认值为"/"
SESSION_COOKIE_DOMAIN = None # 设置浏览器的Cookie生效域名
SESSION_COOKIE_SECURE = False # 设置传输方式,若为false,则http,true,则https
SESSION_COOKIE_HTTPONLY = True # 是否只能使用HTTP协议传输
SESSION_COOKIE_AGE = 1209600 # 设置Cookie的有效期,默认时间为两周
SESSION_SAVE_EVERY_REQUEST = False # 是否每次发送完保存Cookie,默认为False

Session的读写操作

Session的数据类型可理解为Python的字典类型,主要在视图函数中进行读写,从用户请求中获取(来自视图函数的request)

# request为视图函数中的请求参数
# 获取存储在session的数据idList,若不存在则报错,最好这里用逻辑判断
request.session['idList']

# 获取存储在session的数据idList,若不存在则赋值
request.session.get('idList', [])
# setdefault()与get()实现的功能一致
request.session.setdefault('idList', [])

# 设置session的数据,
request.session['idList'] = idList # idList为对象,或者字符串,数字

# 删除session中的数据
del request.session['idList']
# 清空session中的数据
request.session.clear()
# 获取session中的键
request.session.keys()
# 获取session中的值
request.session.values()
# 获取Session中的session_key(上方Session的S均大写),即数据表django_session的字段session_key
request.session.session_key()

实例

首先一来就需要登录,以便可以读写Session,接着页面肯定是没有session的,刚登录,你点击商品时候,就会把商品的Session_id写入到一个idList列表中
这时候就可以在订单页面看到你点击的商品了,用的是从数据库中读取的。

from django.shortcuts import render, redirect
from .models import Product
from django.contrib.auth.decorators import login_required

@login_required(login_url='/admin/login') # 以来判断登录
def index(request):
    # 获取GET请求参数
    id = request.GET.get('id', '')
    if id:
        # 获取存储在Session的idList
        # 如果Session不存在idList,则返回一个空列表
        idList = request.session.get('idList', []) #idList是Session中的键 
        # 判断当前请求参数是否已存储在Session
        if not id in idList:
            # 将商品的主键id存储在idList
            idList.append(id) # 这里吧idz加进去的
        # 更新Session的idList
        request.session['idList'] = idList
        # return redirect('/') # 重定向到主页,这里的主页使用了index.html后缀
        # return redirect('/index.html') # 重定向到主页,
        return redirect('/order.html') # 重定向到订单页面
       
    # 查询所有商品信息
    products = Product.objects.all()
    return render(request, 'index.html', locals())

# ## 若不存在id,这里是如何把id写入idlist,进而实现if id的操作
# 订单确认
def orderView(request):
    # 获取存储在Session的idList
    # 如果Session不存在idList,则返回一个空列表
    idList = request.session.get('idList', []) # 确实像字典的get
    # 获取GET请求参数,如果没有请求参数,返回空值
    del_id = request.GET.get('id', '') # GET.get为获取到值就返回空么
    # 判断是否为空,若非空,删除Session里的商品信息
    # # 为什么Session中会存储商品的id号呢,缘由是他加入商品是通过把商品在数据库中的id号传入到session中,达到维持会话的目的
    if del_id in idList:
        # 为什么要这样操作
        # 删除Session里某个商品的主键;点击的请求用在这里的删除上,好精妙。
        idList.remove(del_id)
        # 将删除后的数据覆盖原来的Session
        request.session['idList'] = idList
    # 根据idList查询商品的所有信息
    products = Product.objects.filter(id__in=idList)
    return render(request, 'order.html', locals())

使用会话实现商品抢购(重点)

他的会话存储实现两个页面事件的数据传递,这个我并未弄清楚
页面之间的数据传递由Session实现
详细说明如下,但我能自己写出来么,输出

1.有2个页面,商城首页;首页实现Session的读取与写入,首先获取Session的idList,
如果当前存在请求参数id,就判断Session的idList是否存在请求参数id的值
,若不存在,则将请求参数的值写入Session的idList中
2.订单信息也实现Session的读取和删除,首先获取Session的idList,
如果当前请求存在请求参数id,就判断Session的idList是否存在该id,,
若存在,则删除请求参数id的值,达到删除订单详情页商品数据的要求

这里我需要弄清楚的是session的设置为什么可以删除该数据,该cookie与Session之间是如何建立关系的

笔记待补

缓存机制

当网站访问过大时,网站的响应速度必然大大降低,知识和可以在网站上使用缓存机制。
缓存是将一个请求的响应内容保存到内存、数据库、文件或者高速缓存系统(Memcache)中,若某个事件再次接收同一个请求,则不再执行该请求的响应过程,而是直接从内存或者高速缓存系统中获取该请求的响应内容返回给用户。

缓存的方式与配置

django提供5种不同的缓存方式

待补

每种缓存方式都需结合网站的实际情况而定。若在项目中使用缓存机制,则首先在配置文件settings.py中设置缓存的相关配置.

# 每种缓存方式的配置

缓存配置的参数BACKEND和LOCATION是必选参数,其余的配置参数可自行选择。
完整的数据库缓存配置

CACHES = {
    # 默认缓存数据表
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
        # TIMEOUT设置缓存的生命周期,以秒为单位,若为None,则永不过期
        'TIMEOUT': 60,
        'OPTIONS': {
            # MAX_ENTRIES代表最大缓存记录的数量
            'MAX_ENTRIES': 1000,
            # 当缓存到达最大数量之后,设置剔除缓存的数量
            'CULL_FREQUENCY': 3,
        }
    },
    # 设置多个缓存数据表
    'MyDjango': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'MyDjango_cache_table',
    }
}

DJango允许同时配置和使用多种不同类型的缓存方式,配置方法与多数据库的配置方式类似。缓存数据表的创建依赖于数据库配置settings.py中的DATABASES,如果有多个数据库,则默认在DATABASES的default的数据库中生成。

缓存的使用

缓存的使用方式有4种,主要根据不同的适用对象进行划分。

全站缓存
视图缓存
路由缓存
模板缓存

全站缓存在django的中间件中配置,

MIDDLEWARE = [
    # 配置全站缓存
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 使用中文
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 配置全站缓存
    'django.middleware.cache.FetchFromCacheMiddleware',
]
# 设置缓存的生命周期
CACHE_MIDDLEWARE_SECONDS = 15
# 设置缓存数据保存在数据表my_cache_table中
# 属性值default来自于缓存配置CACHES的default属性
CACHE_MIDDLEWARE_ALIAS = 'default' # 设置缓存的路径
# 设置缓存表字段cache_key的值
# 用于同一个Django项目多个站点之间的共享缓存
CACHE_MIDDLEWARE_KEY_PREFIX = 'MyDjango' # 指定某个站点的名称

视图缓存

在视图函数或视图类的执行过程中生成缓存数据,视图使用装饰器生成,并保存缓存数据。
装饰器设有参数timeout,cache,key_prefix,参数timeout是必选参数,其余两个参数是可选参数。参数的作用与全局缓存的配置属性相同

from django.shortcuts import render
# 导入cache_page
from django.views.decorators.cache import cache_page
# 参数cache与配置属性CACHE_MIDDLEWARE_ALIAS相同
# 参数key_prefix与配置属性CACHE_MIDDLEWARE_KEY_PREFIX相同
# 参数timeout与配置属性CACHE_MIDDLEWARE_SECONDS相同
# CACHE_MIDDLEWARE_SECONDS的优先级高于参数timeout
@cache_page(timeout=10, cache='MyDjango', key_prefix='MyView')
def index(request):
    return render(request, 'index.html')

路由缓存

在路由文件urls.py中生成和保存的,路由缓存也是使用缓存函数cache_page实现的。

from django.views.decorators.cache import cache_page

urlpatterns = [
    # 网站首页设置路由缓存
    path('', cache_page(timeout=10, cache='MyDjango', key_prefix='MyURL')(views.index), name='index'),
    # path('', views.index, name='index'),
]

模板缓存

模板缓存是通过Django的缓存标签实现的,缓存标签可以设置缓存的生命周期、缓存的关键词和缓存数据表(函数cache_page的参数timeout、key_prefix和cache),三者的设置顺序和代码格式是固定不变的。

<div>
    {# 设置模版缓存 #}
    {% load cache %}
    {# 10代表生命周期 #}
    {# MyTemp代表缓存数据的cache_key字段 #}
    {# using="MyDjango"代表缓存数据表 #}
    {% cache 10 MyTemp using="MyDjango" %}
        <div>Hello Django</div>
    {# 缓存结束 #}
    {% endcache %}
    </div>

CSRF防护

CSRF防护只适用于POST请求,并不防护GET请求,因为GET请求是以只读形式访问网站资源的,一般情况下不会破坏和篡改网络数据。
防护是采用模板语法{% csrf_token %}生成一个隐藏的控件

<input type="hidden" name="csrfmiddlewaretoken" value="zUJfhT41OyX80zCqbbytV4Lc5hoQgATmg5wTeh4XnpnBrbaSytkvw0WUcNC0Dtfd">

隐藏控件的属性value值实用Django随机生成的,当用户提交表单时,django会校验表单的csrfmiddlewaretoken是否于自己保存的csrfmiddlewardtoken一致,用户每次提交表单时,隐藏控件的属性value都会随之变化。

取消CSRF防护

删除模板文件的{% csrf_token %},并且在对应的视图函数中添加装饰器@csrf_exempt

from django.views.decorators.csrf import csrf_protect
from django.views.decorators.csrf import csrf_exempt

# 添加CSRF防护
# @csrf_protect
# 取消CSRF防护
@csrf_exempt
def index(request):
    return render(request, 'index.html')

若并未在对应视图函数中添加装饰器@csrf_exempt
,那么用户在提交表单时,程序会因CSRF验证失败而抛出403异常的页面。
若想全面取消整个网站的CSRF防护,那么可以在settings.py中MIDDLEWARE注释掉

django.middleware.csrf.CsrfViewMiddleware

但若想在某些请求上设置CSRF防护,在对应的html文件中加入{% csrf_token %},并在对应的视图函数中添加装饰器@csrf_protect

@csrf_protect
def index(request):
    return render(request, 'index.html')

如果网页表单时使用前端的Ajax向Django提交表单数据的,Django设置了CSRF防护功能,Ajax发送POST请求就必须设置请求参数csrfiddlewaretoken,否则Django会将当前请求视为恶意请求。Ajax发送POST的请求的功能代码如下


<!--post的ajax-->
<script>
    function submitForm(){
        var csrf = $('input[name="csrfmiddlewaretoken"]').val();//这是用js获取csrf防护隐藏按键的值
        var user = $('#username').val();
        var password = $('#password').val();
        $.ajax({//jquery封装了ajax的底层
            url:'/index.html',
            type:'POST',
        data:{
            'user':user,
            'password':password,
            'csrfmiddlewaretoken':csrf
        },
        success:function(arg){
            console.log(arg);
        }

        })
    }
</script>

消息框架

在网页应用中,当用户完成某个功能操作时,网站会有相应的消息提示。Django内置的消息框架可以供开发者直接调用,它允许开发者设置功能引擎、消息类型和消息类容。
消息框架由中间件SessionMiddleware,MessageMiddleware和INSTALLED_APPS中的django.contrib.sessions,django.contrib.messages共同实现。

具体实现

MessageMiddleware(essionMiddleware,,是哪一个中间件)的功能引擎默认使用FallbackStorage,而FallbackStorage是在session的基础上实现的,所以SessionMiddleware中间件必须设置在MessageMiddleware前面。
相关资料展现的有些许不同
MessageMiddleware的源码位置

待补

MessageMiddleware源码文件所包含的文件及作用

待补

消息框架的使用

消息框架主要在视图和模板中使用,而且消息框架在视图函数和视图类有着不一样的使用方式

设置消息框架的功能引擎
# MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
# MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# FallbackStorage是默认使用,可以无需设置
MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'

设置url

待补

定义对应的视图函数
这里有个上下文处理器,值得注意

待补

如履随行

分页功能

分页功能需要考虑的因素:
1.当前用户访问的页数是否存在上(下)页
2.访问的页数是否超出页数上限
3.数据如何按页截取,如何设置每页的数据量

主要实现分页的Paginator类一共定义了4个初始化参数和8个类方法,每个初始化参数和类方法的说明如下

待补

我们将Paginator实例化之后,在由实例化对象调用get_page()即可得到Page类的实例化对象。在源码文件paginator.py中可以找到Page类的定义过程,它一共定义了3个初始化参数和7个类方法

待补

使用django的shell模式,简述使用分页功能

In [1]: # 导入分页功能模块

In [2]: from django.core.paginator import Paginator

In [3]: # 生成数据列表

In [4]: objects = [chr(x) for x in range(97,107)]

In [5]: objects
Out[5]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

In [6]: # 将数据列表以每3个元素分页

In [7]: p = Paginator(objects,3)

In [8]: # 输出全部数据,即整个数据列表

In [9]: p.object_list
Out[9]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

In [10]: # 获取数据列表长度

In [11]: p.count
Out[11]: 10

In [12]: 分页后的总页数
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-12-b9a482fe307d> in <module>
----> 1 分页后的总页数

NameError: name '分页后的总页数' is not defined

In [13]: # 分页后的总页数

In [14]: p.num_pages
Out[14]: 4

In [15]: # 将页数转换为range循环对象

In [16]: p.page_range
Out[16]: range(1, 5)

In [17]: # 获取第二页的数据信息

In [18]: page2 = p.get_page(2)

In [19]: # 判断第二页上是否还有上一页

In [20]: page2.has_previous()
Out[20]: True

In [21]: # 判断第二页是否存在下一页

In [22]: page2.has_next()
Out[22]: True

In [23]: # 如果当前页存在下一页,就输出下一页的页数

In [24]: # 否则抛出EmptyPage异常

In [25]: page2.next_page_number()
Out[25]: 3

In [26]: # 如果当前页存在上一页,就输出上一页的页数

In [27]: # 否则抛出EmptyPage异常

In [28]: page2.previous_page_number()
Out[28]: 1

In [29]: # 判断当前页是否存在上一页或者下一页

In [30]: page2.has_other_pages()
Out[30]: True

In [31]: # 输出第二页所对应的数据内容

In [32]: page2.has_other_pages()
Out[32]: True

In [33]: page2.object_list
Out[33]: ['d', 'e', 'f']

In [34]: # 输出第二页的第一行数据在整个数据列表的位置

In [35]: # 数据位置从1开始计算

In [36]: page2.start_index()
Out[36]: 4

In [37]: # 输出第二页的最后一行数据在整个数据列表的位置

In [38]: # 数据位置从1开始计算

In [39]: page2.end_index()
Out[39]: 6

diango分页功能的具体使用,主要在views.py中的视图函数中使用,一般需要在视图函数中传入自定义参数来当个page变量实例化,作用在模板文件上

待补

国际化和本地化

国际化和本地化是指使用不同语言的用户在访问同一网页时能够看到符合其自身语言的网页内容。国际化是指在Django的路由、视图、模板、模型和表单里使用内置函数配置翻译功能。
本地化是根据国际化的配置内容进行翻译处理。

环境搭建与配置

django默认开启此配置,不需要使用此功能,可以在settings.py中设置USE_I18N = False即可,这样Django在运行时将执行某些优化。
国际化和本地化需要依赖GNU Gettext工具(操作系统不同,版本不同)
GNU Gettext安装成功之后,现在项目根目录下创建个文件夹laguage,然后再配置文件settings.py的MIDDLEWARE中添加中间件django.middleware.locale.LocaleMiddleware(和平时后台中文化一样),邢增配置属性(单独写在settings.py中)


LANGUAGE_CODE = 'en-us'
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'language'), # 指向用于存储语言的文件夹
)
LANGUAGES = ( # 用于定义Django支持翻译的语言,每种语言的定义格式以元组表示,一般采用各国语言的缩写
    ('en', ('English')),
    ('zh', ('中文简体')),
)

设置国际化

在路由urlpatterns中使用函数i18n_patterns即可。

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path('', include(('index.urls', 'index'), namespace='index')),
)

视图函数Index更具当前请求的语言类型来设置变量lanuage的值

def index(request):
    # 判断该request.LANGUAGE_CODE属性的值得当前用户选择的语言类型
    if request.LANGUAGE_CODE == 'zh': # LANGUAGE_CODE由中间件LocaleMiddleware生成
        language = gettext('Chinese') #  gettext设置变量值
        # language = gettext_lazy('Chinese')
    else:
        language = gettext('English')
        # language = gettext_lazy('English')
    print(language)
    return render(request, 'index.html', locals())

模板文件中数据展示,数据分为中文和英文模式,当选择某个语言模式后,浏览器跳转到相应的路由地址并将数据转换成相应的语言模式.
模板文件导入Djnago内置文件i18n.py,该文件用于定义国际化的模板标签。

<head>
    {% load i18n %}
    <title>{% trans "Choice language" %}</title><!--trans使标签内的数据支持语言转换,但不支持模板上下文与字符串组合使用-->
</head>
<body>
<div>
    {#将配置属性LANGUAGES进行列举,显示网站支持的语言#}
    {% get_available_languages as languages %} <!--get_available_languages获取配置属性LANGUAGES的值 -->
    {% trans "Choice language" %}
    {% for lang_code, lang_name in languages %}
        {% language lang_code %} <!--language 在模板里生成语言选择的功能-->
            <a href="{% url 'index:index'%}">{{ lang_name }}</a>
        {% endlanguage %}
    {% endfor %}
</div>
<br>
<div>
{% blocktrans %} <!--与trans功能相同,但支持模板上下文与字符串组合使用-->
    The language is {{ language }} <!--language 在模板里生成语言选择的功能-->
{% endblocktrans %}
</div>
</body>

设置本地化

现在已经完成了GNU Gettext安装,settings.py,urls,views,html的国际化配置。
执行命令

python manage.py makemessages -l zh

执行命令之后,打开language文件夹可以看到兴建的文件django.po文件,我们需要为视图函数index和模板文件index.html的国际化设置编写翻译内容。
在django.po中一个完整的信息包含3行数据(下方数据每行后都有注释)

#: .\index\views.py:7 # 代表国际化设置的位置信息
msgid "Chinese" # 代表国际化设置的名称,标记和区分每个国际化设置,如index视图函数gettext('Chinese'),这里最好同名
msgstr "简体中文" # 代表国际化设置的翻译内容,默认值为空,需要翻译人员逐条填写```
完成本地翻译编写的内容后,再次执行指令

python manage.py compilemessages


将django.po文件编译成语言文件django.mo(二进制文件)。

单元测试

对每个开发的功能进行单元测试,只需运行操作命令就可以测试网站功能是否正常。
单元测试还可以驱动功能开发比如我们知道需要实现的功能,但并不知道代码如何编写,这时就可利用测试驱动开发。首先完成单元测试的代码编写,然后编写网站的功能代码,直到功能代码不再报错,这样就完成网站功能的开发。

定义测试类

单元测试在项目应用中的tests.py里定义。每个单元测试以类的形式表示,每个类方法代表一个测试用例。以MyDjango为例,打开项目应用index的tests.py发现,该文件导入TestCase类,用于实现单元测试的定义。
TestCase继承父类TransactionTestCase,而父类是通过递进方式继承SimpleTestCase,unittest.TestCase,unittest.TestCase是Python内置的单元测试框架,也就是说Django使用Python的标准库unittest实现单元测试的功能开发。
定义的单元测试主要是为了测试视图的业务逻辑和模型的读写操作。模型的读写还需在该文件中导入相关模型的包,

from django.test import TestCase
from .models import PersonInfo
from django.contrib.auth.models import User
from django.test import Client # 实例化生成HTTP请求对象的包


class PersonInfoTest(TestCase):
    # 添加数据
    def setUp(self): # 父类TestCase的方法,重写setUp()为虚拟数据库添加数据,为测试用例提供数据支持
        PersonInfo.objects.create(name='Lucy', age=10)
        PersonInfo.objects.create(name='May', age=12)

    # 编写测试用例
    def test_personInfo_age(self): # 自定义测试用例,函数开头必须以test开头
        # 编写用例
        name1 = PersonInfo.objects.get(name='Lucy')
        name2 = PersonInfo.objects.get(name='May')
        # 判断测试用例的执行结果
        # name1.age是查询结果,10是预测结果
        self.assertEqual(name1.age, 10) # assertEqual()是父类TestCase的内置方法,这是将查询结果与预测结果进行对比
        self.assertEqual(name2.age, 12)

    # 编写测试用例
    def test_api(self):
        # 编写用例
        c = Client() # 实例化生成HTTP请求对象
        response = c.get('/api/')
        # 判断测试用例的执行结果
        self.assertIsInstance(response.json(), dict) # assertIsInstance内置方法,判断响应内容是否为字典

    # 编写测试用例
    def test_html(self):
        # 编写用例
        c = Client() # 实例化生成HTTP请求对象
        response = c.get('/?id=2')
        name = response.context['person'].name
        # 判断测试用例的执行结果
        self.assertEqual(name, 'May')
        self.assertTemplateUsed(response, 'index.html') # assertTemplateUsed判断响应内容是否由模板index.html生成


class UserTest(TestCase):
    # 测试内置模型User的功能逻辑,这个可复用
    # 添加数据
    @classmethod
    def setUpTestData(cls):
        User.objects.create_user(username='test', password='test', email='1@1.com')

    # 编写测试用例
    def test_user(self):
        # 编写用例
        r = User.objects.get(username='test')
        # 判断测试用例的执行结果
        # 分别验证账户密码是否正确
        self.assertEquals(r.email, '123@456.com')
        self.assertTrue(r.password)

    # 编写测试用例
    def test_login(self):
        # 编写用例
        c = Client()
        # login()方法实现用户登录
        r = c.login(username='test', password='test')
        # 判断测试用例的执行结果
        self.assertTrue(r) # 判断用户登录是否成功

通过setUp()或setUpTestData()为测试用例提供数据支持。测试用例是将业务逻辑执行实体化操作,即通过输入具体的数据得出运行结果,然后再将运行结果与预测结果进行对比,从而验证网页功能是否符合开发需求。
TestCase类与其父类SimpleTestCase定义了多种对比的方法,比较运行结果与预测结果。

待补的方法

运行测试用例

django的测试类是由内置test指令执行的,test指令设有多种方式执行测试类,比如执行整个项目的测试类、某个应用的所有测试类、某个应用的某个测试类。
执行整个项目的测试类

python manage.py test

整个过程分为4个部分

1.创建虚拟数据库
Creating test database for alias 'default'...
2.执行测试用例,失败则会报错,并显示报错的位置
3.执行完测试用例,对执行情况汇总,有执行时间和测试用例运行失败的数量
Ran 5 tests in 0.813s
FAILED (failures=1)
4.执行语句销毁虚拟数据库,释放计算机的内存资源
Destroying test database for alias 'default'...

执行某个测试类或这某个测试类的测试用例

# 执行项目应用index的所有测试类
python manage.py test  index
# 执行项目应用index的tests.py定义的测试类
# 由于测试类没有硬性规定在tests.py定义
# 因此Django允许在其他.py文件中定义测试类
python manage.py test index.tests
# 执行项目应用index的测试类PersonInfoTest
python manage.py test  index.tests.PersonInfoTest
# 执行项目应用index的测试类PersonInfoTest的测试用例test_api
python manage.py test index.tests.PersonInfoTest.test_api
# 使用正则表达式查找整个项目带有tests开头的.py文件
# 运行整个项目带有tests开头的.py文件所定义的测试类
python manage.py test --pattern='tests*.py'

自定义中间件

中间件是一个处理请求和响应的钩子框架,他是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入输出。
Django默认的7个中间件

SecurityMiddleware: # 内置的安全机制,保护用户与网站的通信安全
SessionMiddleware', # 会话Session功能
LoacleMiddleware', # 国际化和本地化功能
CommonMiddleware',# 处理请求信息,规范化请求内容
CsrfViewMiddleware',  # 开启CSRF防护功能
AuthenticationMiddleware', # 开启内置的用户认证系统
MessageMiddleware', # 开启内置的信息提示功能
XFrameOptionsMiddleware', # 防止恶意程序单击劫持

中间件的钩子函数运行过程

一个完整的中间件设有5个钩子函数,Django将用户请求到网站响应的过程进行阶段划分,每个阶段对应执行某个钩子函数,每个钩子函数的运行说明如下:
总之就是用用户有个访问请求,该请求的需要经过中间件的流程是先要初始化中间件内部的函数,然后完成请求对象的创建,接着在路由地址匹配,接着在传递给视图函数,接着还有视图函数执行时候的监控,接着是视图函数执行后,但将响应返回给浏览器前。

__init__():初始化函数,运行Django将自动执行该函数
process_request:完成请求对象的创建,但用户访问的网址尚未与网址的路由地址匹配  (生成请求对象后,路由匹配之前")
process_view: 完成用户访问的网址与路由地址的匹配,但尚未执行视图函数 (路由匹配后,视图函数调用之前)
process_exception 视图函数发生异常时s
process_response 视图函数执行后,响应内容返回浏览器之前

完整的自定义中间件

执行逻辑和返回的内容均为处理,只是打印他的执行过程
这里自定义的中间件继承父类MiddlewareMixin,其实很多中间件的父类都是MiddlewareMixin

from django.utils.deprecation import MiddlewareMixin # 这里自定义的中间件继承父类

class MyMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        """运行Django将自动执行"""
        self.get_response = get_response # 必须这样设置,否则访问时会出现AttributeError异常
        print('This is __init__')

    def process_request(self, request): # 完成请求对象的创建,request代表用户的请求对象
        """生成请求对象后,路由匹配之前"""
        print('This is process_request') # 该函数可以获取请求对象并判断用户请求对象是否合法

    def process_view(self, request, func, args, kwargs): # func代表视图函数或视图类的名称,args和kwargs是路由信息传递给视图函数或视图类的变量对象
        """路由匹配后,视图函数调用之前"""
        print('This is process_view')

    def process_exception(self, request, exception): # exception代表异常信息
        """视图函数发生异常时"""
        print('This is process_exception')

    def process_response(self, request, response): # response代表响应内容
        """视图函数执行后,响应内容返回浏览器之前"""
        print('This is process_response')
        return response
自定义中间件注意事项

一般都需要继承自MiddlewareMixin类,然后需要编写5个钩子函数用来处理用户的请求,
最主要的函数后面4个,分别代表请求的创建,生成请求对象,路由匹配,视图函数调用执行时的异常,视图函数调用后响应返回给浏览器之前

中间件实现反爬虫

也可在视图函数的基础上加以实现的。
视图函数代码

def index(request):
    if request.GET.get('id', ''):
        raise Http404
    return render(request, 'index.html')

def myCookie(request):
    return HttpResponse('Done')
中间件代码

中间件重写了钩子函数process_view和process_response,分别实现了
1.判断参数func的__name__属性是否为index,如果属性__name__的值不等于index,就说明当前请求不是由视图函数index处理的,从当前请求获取Cookie并进行解密处理。若解密成功,则说明当前请求是合法请求;否则判为非法请求并提示404异常。
2.响应内容返回给浏览器之前,是在响应内容里添加Cookie信息,这是为下一次请求提供Cookie信息,确保每次请求的Cookie信息都是动态变化的,并将Cookie的生命周期设为10秒。

from django.utils.deprecation import MiddlewareMixin
from django.utils.timezone import now
from django.shortcuts import Http404


class MyMiddleware(MiddlewareMixin):
    # 重写了钩子函数process_view()process_response()函数
    def process_view(self, request, func, args, kwargs):
        if func.__name__ != 'index': # 不等于的话就说明当前请求不是由视图函数index处理的
            salt = request.COOKIES.get('value', '') # 从当前请求获取Cookie
            try:
                request.get_signed_cookie('MySign', salt=salt) # 进行Coolie解密,相等则合法请求
                # 这里是反爬虫关键,第二次请求,由于时间不一样,所以解密失败,就会返回404页面
            except Exception as e:
                print(e)
                # Http404('xxx')可以把字段显示在404页面上
                raise Http404('当前Cookie无效哦!') # 否则报错404

    def process_response(self, request, response):
        # 每次请求的Cookie是动态变化的
        salt = str(now()) # 设置当前时间为cookie
        response.set_signed_cookie( # 设定Cookie信息
            'MySign',
            'sign',
            salt=salt,
            max_age=10 # 生命周期10秒
        )
        response.set_cookie('value', salt)
        return response

笔记来源:Django Web应用开发实战

posted @ 2021-11-27 11:58  索匣  阅读(103)  评论(1编辑  收藏  举报