第十一章——常用的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的生命周期,传输方式或保存路径等

待补

Session的读写操作

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

待补

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

他的会话存储实现两个页面事件的数据传递,这个我并未弄清楚

笔记待补

缓存机制

当网站访问过大时,网站的响应速度必然大大降低,知识和可以在网站上使用缓存机制。
缓存是将一个请求的响应内容保存到内存、数据库、文件或者高速缓存系统(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),三者的设置顺序和代码格式是固定不变的。

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

CSRF防护

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


    

隐藏控件的属性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的请求的功能代码如下

待补(很重要)

消息框架

在网页应用中,当用户完成某个功能操作时,网站会有相应的消息提示。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)
 in 
----> 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,该文件用于定义国际化的模板标签。


    {% load i18n %}
    {% trans "Choice language" %}


{#将配置属性LANGUAGES进行列举,显示网站支持的语言#} {% get_available_languages as languages %} {% trans "Choice language" %} {% for lang_code, lang_name in languages %} {% language lang_code %} {{ lang_name }} {% endlanguage %} {% endfor %}

{% blocktrans %} The language is {{ language }} {% endblocktrans %}

设置本地化

现在已经完成了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个中间件

待补

一个完整的中间件设有5个钩子函数,Django将用户请求到网站响应的过程进行阶段划分,每个阶段对应执行某个钩子函数,每个钩子函数的运行说明如下:

__init__
process_request生成请求对象后,路由匹配之前"
 process_view (功能待补) 路由匹配后,视图函数调用之前
process_exception 视图函数发生异常时
process_response 视图函数执行后,响应内容返回浏览器之前
待补

完整的自定义中间件

执行逻辑和返回的内容均为处理,只是打印他的执行过程

import time
from importlib import import_module

from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date


class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore

    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)

    def process_response(self, request, response):
        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie or delete
        the session cookie if the session has been emptied.
        """
        try:
            accessed = request.session.accessed
            modified = request.session.modified
            empty = request.session.is_empty()
        except AttributeError:
            pass
        else:
            # First check if we need to delete this cookie.
            # The session should be deleted only if the session is entirely empty
            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
                response.delete_cookie(
                    settings.SESSION_COOKIE_NAME,
                    path=settings.SESSION_COOKIE_PATH,
                    domain=settings.SESSION_COOKIE_DOMAIN,
                )
            else:
                if accessed:
                    patch_vary_headers(response, ('Cookie',))
                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
                    if request.session.get_expire_at_browser_close():
                        max_age = None
                        expires = None
                    else:
                        max_age = request.session.get_expiry_age()
                        expires_time = time.time() + max_age
                        expires = http_date(expires_time)
                    # Save the session data and refresh the client cookie.
                    # Skip session save for 500 responses, refs #3881.
                    if response.status_code != 500:
                        try:
                            request.session.save()
                        except UpdateError:
                            raise SuspiciousOperation(
                                "The request's session was deleted before the "
                                "request completed. The user may have logged "
                                "out in a concurrent request, for example."
                            )
                        response.set_cookie(
                            settings.SESSION_COOKIE_NAME,
                            request.session.session_key, max_age=max_age,
                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
                            path=settings.SESSION_COOKIE_PATH,
                            secure=settings.SESSION_COOKIE_SECURE or None,
                            httponly=settings.SESSION_COOKIE_HTTPONLY or None,
                            samesite=settings.SESSION_COOKIE_SAMESITE,
                        )
        return response

中间件实现反爬虫

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

视图函数代码

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

def myCookie(request):
    return HttpResponse('Done')

中间件代码

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