session

上节已经讲了使用Cookie来做用户认证,但是
Cookie的问题
缺点:敏感信息不适合放在cookie里,敏感信息只能放在服务器端
优势:把部分用户数据分散的存放在每个客户端,减轻服务端的压力
Cookie是保存在用户浏览器端的键值对,Session是保存在服务器端的键值对。
Session依赖Cookie,Cookie保存随机字符串,凭借这个随机字符串获取到服务器端Session里的内容。
用Session来优化用户登录:用户登录后,生成一个随机字符串,通过Cookie发送给客户端保存。服务器端维护一个字典,字典的key就是这个随机生成的字符串,字典的value是另外一个字典,在服务器端保存各种用户的敏感信息。
Django的sessiong默认是保存在数据库中的,所以要使用session需要先执行生成数据库的2条命令:

python manage.py makemigrations
python manage.py migrate

即使你没有写任何一张表,也会默认生成一些表,其中 django_session 表中就是存放session信息的。

操作

session里存放的就是键值对,所以操作起来也跟字典一样。随便写一个登录的页面,用下面的处理函数进行操作:

USER_INFO = {
    'test': {'pass': "test123"},
    'user': {'pass': "user123"},
}

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    elif request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        user_obj = USER_INFO.get(user)
        if user_obj and user_obj.get('pass') == pwd:
            # 1.生成随机字符串
            # 2.写到用户浏览器Cookie
            # 3.把随机字符串作为key保存到session中
            # 4.设置对应的value
            # 理论上是要完成上面4个步骤
            # 但是都封装好了,用的时候只需要1步,就能完成上面的操作,下面是设置session的值
            request.session['username'] = user
            request.session['is_login'] = True
            return redirect('/welcome/')
        else:
            return render(request, 'login.html')

def welcome(request):
    # 获取session的值
    if request.session.get('is_login'):
        return HttpResponse('Welcome %s' % request.session['username'])
    else:
        return HttpResponse('登录失败')

登录页面:

<form action="/login/" method="POST">
    <p><input type="text" name="user" placeholder="用户名"></p>
    <p><input type="password" name="pwd" placeholder="密码"></p>
    <p><input type="submit" value="登录"></p>
</form>

上面用到了设置数据的操作和获取数据的操作。其他的操作方法见下面整理的内容。
获取、设置、删除Session中数据:

  • request.session['k1'] :获取值,如果key不存在会报错
  • request.session.get('k1',None) :获取值,如果key不存在,在获取到默认值。默认值默认是None
  • request.session['k1'] = 123 :设置值
  • request.session.setdefault('k1',123) :设置默认值,就是如果这个key有值就不改变,没有值就设置为第二个参数的值
  • del request.session['k1'] :删除值

对所有键、值、键值对的操作:

  1. request.session.keys()
  2. request.session.values()
  3. request.session.items()
  4. request.session.iterkeys()
  5. request.session.itervalues()
  6. request.session.iteritems()

iterkeys 和 keys 的区别,iterkeys 返回一个迭代器,而 keys 返回一个列表。
其他:

  • request.session.session_key :获取当前用户的随机字符串。但是一般这个用不着,不过是可以获取到的
  • request.session.clear_expired() :将所有Session失效日期小于当前日期的数据删除。过期的session,数据库是没有自动清除的机制的。不过缓存系统是有的
  • request.session.exists("session_key") :检查作为参数的随机字符串在数据库中是否存在。一般也用不着,因为获取值的时候已经包含这个判断了,没有会返回None
  • request.session.delete("session_key") :删除参数的Session的所有数据
  • request.session.clear() :删除当前用户的所有Session数据。和delete方法比较,delete需要提供session_key。而clear方法会先去获取到当前用户的key,然后delete

request.session.set_expiry(value) :设置超时时间

  • 如果value是个整数,session会在些秒数后失效。
  • 如果value是个datatime或timedelta,session就会在这个时间后失效。
  • 如果value是0,用户关闭浏览器session就会失效。
  • 如果value是None,session会依赖全局session失效策略。

下面的内容在 Lib/site-packages/django/conf/global_settings.py 这个文件里,是默认配置。要修改的话,就在我们的 settings.py 里设置一个值:

############
# SESSIONS #
############

# Cache to store session data if using the cache session backend.
SESSION_CACHE_ALIAS = 'default'
# Cookie name. This can be whatever you want.
SESSION_COOKIE_NAME = 'sessionid'  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
# Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2  # Session的cookie失效日期,这里默认的是2周
# A string like "example.com", or None for standard domain cookie.
SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名
# Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_SECURE = False  # 是否Session的cookie只支持http传输
# The path of the session cookie.
SESSION_COOKIE_PATH = '/'  # Session的cookie保存的路径
# Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others)
SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输
# Whether to save the session data on every request.
SESSION_SAVE_EVERY_REQUEST = False  # 是否每次请求都保存Session,默认修改之后才保存
# Whether a user's session cookie expires when the Web browser is closed.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期
# The module to store session data
SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # 默认引擎
# Directory to store session files if using the file session module. If None,
# the backend will use a sensible default.
SESSION_FILE_PATH = None  # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址
# class to serialize session data
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'

CSRF(跨站请求伪造)

客户端第一次发起GET请求后,不仅返回了页面,同时还会返回一串加密字符串。之后客户端再提交数据的时候,需要把之前收到的字符串一并提交,这样服务器端的CSRF就会对字符串进行验证,确认收到的数据是之前发起请求的客户端提交上来的。
无CSRF的隐患,如果没有CSRF,正常客户端提交数据是提交给当前访问的网站。但是也是可以提交到别的网站去的(你可以提交到任何地方)。虽然你可以提交过去,但是这种方式你没有目标网站给你的加密字符串的,所以开启CSRF之后,直接就把这部分请求拦截了。

开启CSRF

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。
全局,settings.py 文件中的MIDDLEWARE(中间件)里,是一个列表,之前都是先把下面的这行注释掉的:

'django.middleware.csrf.CsrfViewMiddleware',

局部
导入模块:from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_protect :为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
@csrf_exempt :取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

CSRF Token相关装饰器在CBV只能加到dispatch方法上
关于dispatch方法,可以看讲CBV时记得笔记:http://blog.51cto.com/steed/2091163
这里要做的就是继承父类的dispatch方法,然后加上装饰器:

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

class HomeView(View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(HomeView, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return render(request, "home.html")

    def post(self, request):
        print("Home View POST method...")
        return redirect("/index/")

应用-Form方式提交

第一次请求获取到的字符串在哪里,如何在提交form表单的时候加上获取的字符串,从而通过CSRF的验证。做一个简单的登录页面,上节有,然后去 settings.py 文件里开启之前一直注释掉的CSRF,按下面的方式提交:

<form action="/login/" method="POST">
    {# 加上下面的标签就可以了 #}
    {% csrf_token %}
    <p><input type="text" name="username" placeholder="用户名"></p>
    <p><input type="password" name="password" placeholder="密码"></p>
    <p><input type="submit" value="登录"></p>
</form>

如果form不带 csrf_token ,那么会返回403错误。这里查看客户端form标签内的源码可以发现,系统帮我们生成了一个隐藏的input标签

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

应用-Ajax方式提交

使用Ajax方式提交,需要先获取到csrf的字符串,客户端收到服务器端的字符串后,是保存在cookie里的,所以可以到cookie里去获取到。提交的时候也要带上这个csrf字符串,加一条请求头: headers: {'X-CSRFtoken': csrftoken},
注意:下面的例子里专门导入了一个 jquery.cookie.js 的库,支持 $.cookie 的方法。这是纯前端的做法,如果用模板语言,可以用 ‘{{ csrf_token }}' 直接拿到csrf的字符串。
在原来的页面里,添加Ajax请求的按钮,并且绑定事件。验证不通过重新加载页面,验证通过则用 location.href 页面跳转:

<body>
<form action="/login/" method="POST">
    {% csrf_token %}
    <p><input type="text" name="user" placeholder="用户名"></p>
    <p><input type="password" name="pwd" placeholder="密码"></p>
    <p>
        <input type="submit" value="登录">
        <input id="btn" type="button" value="Ajax提交">
    </p>
</form>
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery.cookie.js"></script>
<script>
    $(function () {
        $('#btn').click(function () {
            var csrftoken = $.cookie('csrftoken');  // cookie里可能没有把
            var csrftoken = ‘{{ csrf_token }}';  // 模板语言里也可以直接拿到
            var user = $("input[name='user']").val();
            var pwd = $("input[name='pwd']").val();
            $.ajax({
                url: '/login/',
                type: 'POST',
                data: {'user': user, 'pwd': pwd, 'ajax': true},
                // data: {'csrfmiddlewaretoken': '{{ csrf_token }}', 'user': user, 'pwd': pwd, 'ajax': true},
                // 可以放在headers里,也可以放在data里
                headers: {'X-CSRFtoken': csrftoken},
                success: function (arg) {
                    if(arg === 'OK'){
                        location.href = '/welcome/'
                    }else{
                        location.reload()
                    }
                }
            })
        })
    })
</script>
</body>

处理函数也在原来的基础上添加。通过 is_ajax = request.POST.get('ajax') 判断是否是ajax提交的请求。最后在return的时候返回不同的结果,其他都不变:

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    elif request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        is_ajax = request.POST.get('ajax')
        user_obj = USER_INFO.get(user)
        if user_obj and user_obj.get('pass') == pwd:
            # 1.生成随机字符串
            # 2.写到用户浏览器Cookie
            # 3.把随机字符串作为key保存到session中
            # 4.设置对应的value
            # 但是只需要一步,就能完成上面的操作,下面是设置session的值
            request.session['username'] = user
            request.session['is_login'] = True
            print(request.session)
            return HttpResponse('OK') if is_ajax else redirect('/welcome/')
        else:
            print('error')
            return HttpResponse('error') if is_ajax else render(request, 'login.html')

统一设置ajax的csrf,如果页面里有多个ajax请求,可以统一进行设置。再或者比如之前的练习,已经写好了不带csrf的ajax请求,现在也不要去里面改了,用下面的方法统一加上。就是在ajax发送前,设置请求头加上X-CSRFtoken这个key并且设置值:

<script>
    $(function () {
        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                // xhr 就是 XHLHttpRequest 是一个对象
                xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'))  // 设置请求头
            }
        });
        $('#btn').click(function () {
            // var csrftoken = $.cookie('csrftoken');
            var user = $("input[name='user']").val();
            var pwd = $("input[name='pwd']").val();
            $.ajax({
                url: '/login/',
                type: 'POST',
                data: {'user': user, 'pwd': pwd, 'ajax': true},
                // headers: {'X-CSRFtoken': csrftoken},
                success: function (arg) {
                    if(arg === 'OK'){
                        location.href = '/welcome/'
                    }else{
                        location.reload()
                    }
                }
            })
        })
    })
</script>

上面的代码还要再优化一个,只有POST请求需要加csrf,GET请求是不需要加的。但是在上面的代码里所有的ajax请求的前面都会在请求头加上csrf,这里要再做个判断。除了POST和GET还有其他好几种请求,一起做判断:

<script>
    function csrfSafeMethod(method) {
        // 下面的这几类HTTP请求是不需要加CSRF验证的,这个是官网的一个例子的用法
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    $(function () {
        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                // xhr 就是 XHLHttpRequest 是一个对象
                // settings 里就是下面的$.ajax({})大括号里的内容,这里要获取type检查是否需要加csrf
                if (!csrfSafeMethod(settings.type) && !this.crossDomain){
                    // 满足上面的条件才需要设置请求头
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'))  // 设置请求头
                }

            }
        });
        $('#btn').click(function () {
            // var csrftoken = $.cookie('csrftoken');
            var user = $("input[name='user']").val();
            var pwd = $("input[name='pwd']").val();
            $.ajax({
                url: '/login/',
                type: 'POST',
                data: {'user': user, 'pwd': pwd, 'ajax': true},
                // headers: {'X-CSRFtoken': csrftoken},
                success: function (arg) {
                    if(arg === 'OK'){
                        location.href = '/welcome/'
                    }else{
                        location.reload()
                    }
                }
            })
        })
    })
</script>

中间件

在settings.py文件里有一个列表,里面列个各种中间件,包括上面将过的csrf。下面是原始的settings.py文件里的中间件的内容:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

用户发起请求,到请求到达Views之前,首先要通过这写中间件。这些中间件就是一个类,经过就是要调用这些中间件类里的方法,process_request方法。请求的返回也要先通过这些中间件,调用process_response方法,然后再发送给用户。
其他名称在其他的Web框架里,也有中间件,但是名字可能叫做:管道(Pipeline),HttpHandler。

自定义中间件

先以csrf的中间件举例,代码中引用的字符串是:'django.middleware.csrf.CsrfViewMiddleware' 。这个就是路径和类名。
可以到python的模块中找到这个文件和类。在 \Lib\site-packages\django\middleware\csrf.py 这个文件里有一个类 class CsrfViewMiddleware(MiddlewareMixin): ,这个类里就有process_request方法和process_response方法。并且可以看到这个类是继承了MiddlewareMixin这个类的,查看这个父类,可以看到这个类里就是调用上面的2个方法。
要写自己的中间件,就是要定义一个类,继承MiddlewareMixin。并且在类里写上process_request方法和process_response方法。
在项目下创建Middle文件夹来存放自定义的中间件,创建m1.py文件:

from django.utils.deprecation import MiddlewareMixin

class Row1(MiddlewareMixin):
    def process_request(self, request):
        print('ROW1_reuquest')

    def process_response(self, request, response):
        print('ROW1_response')
        return response

class Row2(MiddlewareMixin):
    def process_request(self, request):
        print('ROW2_reuquest')

    def process_response(self, request, response):
        print('ROW2_response')
        return response

上面写了2个类,就是2个中间件了。然后去setting.py里注册一下你的中间件:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'Middle.m1.Row1',
    'Middle.m1.Row2',
]

现在再访问你之前的页面,就能看到中间件print的信息了,并且可以看到触发的顺序:

ROW1_reuquest
ROW2_reuquest
[11/Apr/2018 11:24:04] "GET /login/ HTTP/1.1" 200 492
ROW2_response
ROW1_response

先触发request方法,并且是写在前面的中间件先触发。然后再是收到GET请求。最后触发response方法。
中间件的应用场景:通过中间件可以对所有的请求做一个统一的操作,比如黑名单过滤。
中间件中可以定义以下方法:

  • process_request(self, request) :处理请求前最先处理
  • process_view(self, request, callback, callback_args, callback_kwargs) :在process_request全部执行之后执行,还是从上到下
  • process_template_response(self, request, response) :如果views返回对象中有render方法,则会执行
  • process_exception(self, request, exception) :当views里出现异常时触发。exception就是异常的信息
  • process_response(self, request, response) :返回结果后到客户端收到响应前处理

常用的就2个,其他了解一下

缓存

由于Django是动态网站,所以每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用缓存。缓存是将某个views的返回值保存至内存或者memcache中,5分钟内(默认设置)再有人来访问时,则不再去执行views中的函数,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。
Django中提供了6种缓存方式:

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

配置

开发调试以及默认配置
实际内部不做任何操作,就是调试用的。只有第一个引擎是必须要设置的,其他都有默认配置(上面也是照着默认设的),不写就是使用默认设置,或者写上你要修改的设置。其它缓存方式的配置也是一样的,就是把引擎换一下,再加一个 'LOCATION' 就是缓存存放的位置:

# 此为开始调试用,实际内部不做任何操作。
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',  # 引擎
        'TIMEOUT': 300,  # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
        'OPTIONS': {
            'MAX_ENTRIES': 300,  # 最大缓存个数(默认300,就是5分钟)
            'CULL_FREQUENCY': 3,  # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认1/3)
        },
        'KEY_PREFIX': '',  # 缓存key的前缀(默认空)
        'VERSION': 1,  # 缓存key的版本(默认1)
        'KEY_FUNCTION': default_key_func  # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
    }
}

默认的 'KEY_FUNCTION' 是在 Lib\site-packages\django\core\cache\backends\base.py 文件里的default_key_func函数,就是生成一个包含 key_prefix, version, key 这3个变量的字符串返回。这里可以用我们自己的写的函数生成自己想要的样式:

def default_key_func(key, key_prefix, version):
    """
    Default function to generate keys.

    Construct the key used by all other methods. By default, prepend
    the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
    function with custom key making behavior.
    """
    return '%s:%s:%s' % (key_prefix, version, key)

内存

# 此缓存将内容保存至内存的变量中
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',  # 这个必须是全局唯一的变量名,实际就是在内存中维护一个字典,这个是变量名
    }
}

文件

# 此缓存将内容保存至文件
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',  # 缓存文件的路径
    }
}

数据库

# 此缓存将内容保存至数据库
# 配置:
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',  # 数据库表,这里是表名,使用前先要创建表,命令在下面
    }
}
# 注意:使用前先执行创建表命令 python manage.py createcachetable

Memcache缓存(python-memcached模块)

# 此缓存使用python-memcached模块连接memcache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',  # ip地址和端口
    }
}
# 或者是连接本地文件
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}
# 或者是使用分布式的memcache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}
# 或者是使用分布式的memcache,还可以带权重(权重是memcache做的)
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            ('172.19.26.240:11211', 2),
            ('172.19.26.242:11211', 3),
        ]
    }
}

Memcache缓存(pylibmc模块)
和上面差不多,只是用的是不同的python模块,所以引擎要变一下。

# 此缓存使用pylibmc模块连接memcache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
    }
}
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '/tmp/memcached.sock',
    }
}
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}
# 权重也是有的

应用

准备测试环境
使用文件的形式来测试,在项目下创建一个cache文件夹来存放缓存文件,配置如下:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': os.path.join(BASE_DIR, 'cache'),  # 缓存文件的路径
    }
}

然后写一个显示当前时间戳的处理函数:

def cache(request):
    import time
    ctime = time.time()
    return render(request, 'cache.html', {'ctime': ctime})

页面只要把时间戳显示出来:

<body>
<h1>{{ ctime }}</h1>
<h1>{{ ctime }}</h1>
</body>

现在访问页面会看到一个时间戳,并且刷新页面时间戳也会刷新,说明没有走缓存。因为上面我们只是把缓存配置上了,还是还没应用上。

全站应用缓存
这个太粗糙了,下面有更加精细的设置。
要给所有的请求都应用上缓存,这个功能适合放在中间件里。中间件里需要写2个中间件
查缓存,收到用户请求先查找缓存,如果命中则直接返回缓存的内容。这个中间件要放在后面的位置,只有通过了一系列验证的中间件后才能给用户返回数据。
写缓存,返回给用户之前没有缓存的内容的同时,需要把这个内容写到缓存里去,那么下次就有缓存了。这个中间件要放在靠前的位置,通过了一系列其他中间件加工之后,把最终返回的内容缓存起来。

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    # 其他中间件...
    'django.middleware.cache.FetchFromCacheMiddleware',
]

视图应用缓存1:处理函数前加装饰器

from django.views.decorators.cache import cache_page
@cache_page(10)  # timeout必填,单位秒
def func(request):
    pass

视图应用缓存2:修改urls.py的

from django.views.decorators.cache import cache_page
urlpatterns = [
    path('admin/', admin.site.urls),
    path('cache/', cache_page(5)(views.cache)),
]

局部视图缓存:
就是在模板语言里声明,页面里的哪些内容是要应用缓存的

<body>
{#要应用缓存,先load一下cache#}
{% load cache %}
<h1>{{ ctime }}</h1>
{#然后在需要应用缓存的内容的前后加上标签#}
{#下面的c1是自定义的key,这里缓存的值的key貌似无法根据配置自动生成#}
{% cache 5 c1 %}
<h1>{{ ctime }}</h1>
{% endcache %}
</body>

首先要load缓存,然后在要应用缓存的内容前后用标签包起来。这里需要定义timeout时间和key。
这个局部的应用场景还是很多的,只缓存页面中不会频繁发生变化的内容,而经常会变的内容则不缓存

补充

还有别的用法,在后面的模块写项目的时候遇到了,补充进来:
临时存储有失效性的 key 和 value
下面的例子中,在缓存里存了一个值,时间是5秒。有效时间内通过key可以获取到值,超过了之后,返回就是None。

G:\>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.core.cache import cache
>>> cache.set('k1', 'v1', 5)
>>> cache.get('k1')
'v1'
>>> cache.get('k1')
>>>

信号

Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。

Django内置信号

  • Model signals
    • pre_init : django的modal执行其构造方法前,自动触发
    • post_init : django的modal执行其构造方法后,自动触发
    • pre_save : django的modal对象保存前,自动触发
    • post_save : django的modal对象保存后,自动触发
    • pre_delete : django的modal对象删除前,自动触发
    • post_delete : django的modal对象删除后,自动触发
    • m2m_changed : django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
    • class_prepared : 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
  • Management signals
    • pre_migrate : 执行migrate命令前,自动触发
    • post_migrate : 执行migrate命令后,自动触发
  • Request/response signals
    • request_started : 请求到来前,自动触发
    • request_finished : 请求结束后,自动触发
    • got_request_exception : 请求异常后,自动触发
  • Test signals
    • setting_changed : 使用test测试修改配置文件时,自动触发
    • template_rendered : 使用test测试渲染模板时,自动触发
  • Database Wrappers
    • connection_created : 创建数据库连接时,自动触发

使用之前先要把对应的模块导入:

from django.db.models.signals import pre_init, post_init
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import pre_delete, post_delete
from django.db.models.signals import m2m_changed
from django.db.models.signals import class_prepared

from django.db.models.signals import pre_migrate, post_migrate

from django.core.signals import request_started
from django.core.signals import request_finished
from django.core.signals import got_request_exception

from django.test.signals import setting_changed
from django.test.signals import template_rendered

from django.db.backends.signals import connection_created

注册函数1
将函数名作为参数完成注册

# 导入模块
from django.core.signals import request_started

# 准备好要触发的函数
def callback(sender, **kwargs):
    print('callback')
    print(sender, kwargs)

request_started.connect(callback)  # 注册你的函数
# pre_init.connect(callback2)  # 可以注册多个,先注册的先执行

注册函数2
为函数加上装饰器也能完成注册:

# 导入模块
from django.core.signals import request_started
from django.dispatch import receiver

# 准备好要触发的函数,并且加上装饰器
@receiver(request_started)
def callback(sender, **kwargs):
    print('callback')
    print(sender, kwargs)

自定义信号

要自定义信号需要3步:

  1. 定义信号 :这里单独创建一个文件(名字随意比如sg.py)来存放自定义的信号
  2. 注册信号 :定义完成好,直接就注册吧,注册方法和注册内置信号一样
  3. 触发信号 :在你要触发的位置,执行一个send()方法。比如下面的例子中是在处理函数中加入了这个信号的触发

内置信号只需要注册。而自定义的信号,需要在注册前先定义好这个信号。之后去你需要的位置加上触发信号的send()方法:

# sg.py 里的内容
# 第一步:定义信号
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])  # 这里要求2个参数

# 第二步:注册信号
def callback(sender, **kwargs):
    print('callback')
    print(sender, kwargs)
pizza_done.connect(callback)

# views.py 里的内容,当然可以是任何地方
# 第三步:触发信号
# 导入信号
from sg import pizza_done
def my_sig(request):
    # 发送信号,第一个参数是发送者,后面是你自定义的函数要求的参数
    pizza_done.send(sender='seven', topping=123, size=456)