1. 程式人生 > >Django之快取、訊號和驗證碼

Django之快取、訊號和驗證碼

一、 快取

1、 介紹

快取通俗來說:就是把資料先儲存在某個地方,下次再讀取的時候不用再去原位置讀取,讓訪問速度更快。
快取機制圖解

 

2、Django中提供了6種快取方式

  1. 開發除錯
  2. 記憶體
  3. 檔案
  4. 資料庫
  5. Memcache快取(python-memcached模組)
  6. Memcache快取(pylibmc模組)

 

3、 配置快取(在setting中配置)

配置快取
    1. 開發除錯
    # 此為開始除錯用,實際內部不做任何操作
    CACHES = {
        
'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎 'TIMEOUT': 300, # 快取超時時間(預設300,None表示永不過期,0表示立即過期) 'OPTIONS':{ 'MAX_ENTRIES': 300, # 最大快取個數(預設300)
'CULL_FREQUENCY': 3, # 快取到達最大個數之後,剔除快取個數的比例,即:1/CULL_FREQUENCY(預設3) }, 'KEY_PREFIX': '', # 快取key的字首(預設空) 'VERSION': 1, #
快取key的版本(預設1) 'KEY_FUNCTION' 函式名 # 生成key的函式(預設函式會生成為:【字首:版本:key】) } } # 自定義key def default_key_func(key, key_prefix, version): """ Default function to generate keys. Constructs the key used by all other methods. By default it prepends 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) def get_key_func(key_func): """ Function to decide which key function to use. Defaults to ``default_key_func``. """ if key_func is not None: if callable(key_func): return key_func else: return import_string(key_func) return default_key_func 2. 記憶體 # 此快取將內容儲存至記憶體的變數中 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', 'TIMEOUT': 300, # 快取超時時間(預設300,None表示永不過期,0表示立即過期) 'OPTIONS': { 'MAX_ENTRIES': 300, # 最大快取個數(預設300) 'CULL_FREQUENCY': 3, # 快取到達最大個數之後,剔除快取個數的比例,即:1/CULL_FREQUENCY(預設3) }, } } 3. 檔案 # 此快取將內容儲存至檔案 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', # 檔案路徑 } } # 注:其他配置同開發除錯版本 4. 資料庫 # 此快取將內容儲存至資料庫 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', # 資料庫表 } } # 注:執行建立表命令 python manage.py createcachetable 5. Memcache快取(python-memcached模組) # 此快取使用python-memcached模組連線memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } } 6. Memcache快取(pylibmc模組) # 此快取使用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', ] } }

 

4、 簡單的應用

1. 給單獨的檢視應用快取: 粒度適中
    方式一:views.py
    from django.views.decorators.cache import cache_page

    @cache_page(15)  # 快取15秒後失效
    def user_list(request):
        print('user_list')
        users = models.User.objects.all()
        return render(request, 'user_list.html', {'users': users})


    方式二:urls.py
    from myapp.views import user_list
    from django.views.decorators.cache import cache_page

    urlpatterns = [
        url(r'^user_list/$', cache_page(15)(user_list)),
    ]


2. 全站應用: 粒度最大(settings.py)
    使用中介軟體,經過一系列的認證等操作,如果內容在快取中存在,則使用FetchFromCacheMiddleware獲取內容並返回給使用者,
    當返回給使用者之前,判斷快取中是否已經存在,如果不存在則UpdateCacheMiddleware會將快取儲存至快取,從而實現全站快取

    MIDDLEWARE = [
         # 站點快取 , 注意必須在第一個位置
        'django.middleware.cache.UpdateCacheMiddleware',

        # 其他中介軟體...

        # 站點快取 , 注意必須在最後一個位置
        'django.middleware.cache.FetchFromCacheMiddleware',
    ]

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = 300  # 快取有效時間
    CACHE_MIDDLEWARE_KEY_PREFIX = ""


3. 區域性檢視(在HTML頁面設定哪些需要快取):粒度最細
    
    a. 引入TemplateTag

        {% load cache %}

    b. 使用快取

        {% cache 300 '快取key' %}  # 快取key的名字可以是隨意的
            快取內容
        {% endcache %}

 

二、 序列化

1、介紹

關於Django中的序列化主要應用在將資料庫中檢索的資料返回給客戶端使用者,特別的Ajax請求一般返回的為Json格式。

 

2、serializers

from django.core import serializers
def get_value(request):
    users = models.User.objects.all()
    ret = serializers.serialize('json', users)
    
    return HttpResponse(ret)

 

3、自定義序列化

由於json模組並不能轉換時間型別的資料,因此需要我們自定義一個類來出來時間型別的資料

import json
from datetime import datetime, date

data = [  # data資料中有datetime型別的值, json不能直接序列化
    {"pk": 1, "name": "\u83b9\u83b9", "age": 18, 'birth': datetime.now()},
    {"pk": 2, "name": "\u5c0f\u5fae", "age": 16, 'birth': datetime.now()},
    {"pk": 3, "name": "\u5c0f\u9a6c\u54e5", "age": 8, 'birth': datetime.now()},
    {"pk": 4, "name": "qqq", "age": 5, 'birth': datetime.now()},
    {"pk": 5, "name": "www", "age": 5, 'birth': datetime.now()}
]
# json序列化的時候是呼叫JSONEncoder這個類的default方法進行序列化的
class JsonCustomEncoder(json.JSONEncoder):  # 自定義一個類,重新json.dumps的default方法

    def default(self, field):  # 迴圈每個欄位的值
        
        if isinstance(field, datetime):  # 如果這個值是datetime型別,我們自己把它轉成字串型別的時間
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, date):  # 如果這個值是date型別,我們自己把它轉成字串型別的時間
            return field.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, field)  # 如果這個值不是時間型別,呼叫其父類原本的default方法進行序列化

print(json.dumps(data,cls=JsonCustomEncoder))  # cls指定序列化的時候去執行這個類

 

三、 訊號

1、介紹

Django中提供了“訊號排程”,用於在框架執行操作時解耦。通俗來講,就是一些動作發生的時候,訊號允許特定的傳送者去提醒一些接受者。

 

2、內建訊號

Model signals
    pre_init                    # django的model執行其構造方法前,自動觸發
    post_init                   # django的model執行其構造方法後,自動觸發
    pre_save                    # django的model物件儲存前,自動觸發
    post_save                   # django的model物件儲存後,自動觸發
    pre_delete                  # django的model物件刪除前,自動觸發
    post_delete                 # django的model物件刪除後,自動觸發
    m2m_changed                 # django的model中使用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          # 建立資料庫連線時,自動觸發
    

 

3、使用

1. 場景:資料庫增加一條資料時,就記錄一條日誌,若不使用訊號,則需要在每個建立語句下面寫記錄日誌的語句。

2. 介紹
對於Django內建的訊號,僅需註冊指定訊號,當程式執行相應操作時,自動觸發註冊函式
註冊訊號,寫入與project同名的資料夾下的_init_.py檔案中,也是換資料庫引擎的地方。

3. 註冊訊號步驟
1. 匯入需要的訊號模組(這裡列出全部模組,實際開發的時候需要哪個就匯入哪個)
from django.core.signals import request_finished
from django.core.signals import request_started
from django.core.signals import got_request_exception

from django.db.models.signals import class_prepared
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 pre_migrate, post_migrate

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

from django.db.backends.signals import connection_created

2. 定義函式來處理訊號
# 方法一
from django.db.models.signals import post_save
# 函式名可隨意,但是引數(sender, **kwargs)是固定的,就這兩個引數
def callback(sender, **kwargs):  
    print("xxoo_callback")
    print(sender, kwargs)


post_save.connect(callback)  
# 註冊post_save訊號:django的model物件儲存後,自動觸發callback函式
# post_save訊號中,render就是觸發訊號的那一個ORM類(表)
# kwargs就是這個類的一些引數:instance是這個類的例項,created:是否是建立操作


# 方法二
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save)
def my_callback(sender, **kwargs):
    print("xxoo_callback")
    print(sender, kwargs)


# 引伸:指定觸發者
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import MyModel
# 指定只有MyModel這個類才能觸發這個函式
@receiver(post_save, sender=MyModel)
def my_callback(sender, **kwargs):
    print("xxoo_callback")
    print(sender, kwargs)

# 或者
post_save.connect(callback, sender=MyModel)  

 

4、自定義訊號

a. 定義訊號
在某py檔案中定義訊號。

import django.dispatch
# pizza_done是訊號名
# providing_args是傳給訊號繫結的函式的kwargs
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])


b. 註冊訊號
在_init_.py 中註冊訊號
from 路徑 import pizza_done

def callback(sender, **kwargs):
    print("callback")
    print(sender,kwargs)
 
pizza_done.connect(callback)


c. 觸發訊號
from 路徑 import pizza_done
 
pizza_done.send(sender='seven',toppings=123, size=456)
由於內建訊號的觸發者已經整合到Django中,所以其會自動呼叫,而對於自定義訊號則需要開發者在任意位置觸發。

 

 

四、 ORM效能相關

0、 表結構
    class Role(models.Model):
        name = models.CharField(max_length=32)
        

    class User(models.Model):
        name = models.CharField(max_length=32)
        age = models.IntegerField()
        role = models.ForeignKey('Role', null=True, blank=True)


1、 直接查詢--> [ 物件 ]
    用的時候注意,只拿自己表中的欄位,別跨表,比如all_users有3條資料,user表通過外來鍵關聯role表,
    如果要跨表拿到role表的name欄位:
    all_users = models.User.objects.all()
    for user in all_users:
        print(user.name, user.age, user.role.name)

    其實一個進行了四次查詢,第一次查詢出all_users,然後每次的user.role.name都去role表查


2、 要用到跨表字段的時候,使用values或values_list查詢速度更快,只需一次查詢即可--> [{}]
    all_users = models.User.objects.all().values('name','age','role__name')
    for user in all_users:
        print(user['name'], user['age'], user['role__name'])


3、 select_related:外來鍵、一對一才有,主動去連表
    查詢的時候把關聯的表也一起查了,也是一次查詢出結果,跟values不同的是,可以直接用點取欄位
    all_users = models.User.objects.all().select_related('role')
    for user in all_users:
        print(user.name, user.age, user.role.name)
    

4、 prefetch_related:多對多欄位和一對多欄位
    all_users = models.User.objects.all().prefetch_related('role')
    for user in all_users:
        print(user.name, user.age, user.role.name)
    

5、 only:將指定的欄位查詢加載出來,後續再訪問指定的欄位就不需要再查詢資料庫
    all_users = models.User.objects.all().only('name')
    用的時候注意,只拿自己指定的欄位


6、 defer:將除了指定的欄位查詢加載出來,後續再訪問指定的欄位就不需要再查詢資料庫(only的反義詞)
    all_users = models.User.objects.all().defer('name')

 

五、 驗證碼

1、隨機驗證碼python程式碼

import random


def get_code():
    code = ''
    for i in range(6):
        num = str(random.randint(0, 9))  # 數字
        lower = chr(random.randint(97, 122))  # 小寫字母
        upper = chr(random.randint(65, 90))  # 大寫字母
        c = random.choice([num, lower, upper])  # 隨機選取一個
        code += str(c)
    
    return code

 

2、如何生成圖片

1. 驗證碼的形式
回想一下,平時我們輸入驗證碼的時候,是不是都是看著一張圖片,圖片上顯示驗證碼,我們看著圖片輸入驗證碼。
當然現在還有滑動的,點選等等,這裡我們先學習圖片的形式。


2. 實現步驟
1,
準備一張沒有任何內容的圖片

2,
安裝python專門處理圖片的第三方包
pip install Pillow

3,
包的匯入
from PIL import Image, ImageDraw, ImageFont

4,
Image:生成一張圖片
ImageDraw:生成一個畫筆,用於在圖片上畫驗證碼
ImageFont:字型的格式和大小

5,示例
from PIL import Image, ImageDraw, ImageFont


# 返回隨機的RGB數字
def random_color():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)


def get_code():
    with open('1.png', 'wb') as f:
        # 第一步:生成一張圖片(畫布)
        # 建立一個隨機顏色的圖片物件
        # 引數:顏色模式,圖片大小,圖片顏色
        img_obj = Image.new('RGB', (250, 35), random_color())

        # 第二步:在該圖片物件上生成一個畫筆物件
        draw_obj = ImageDraw.Draw(img_obj)

        # 使用什麼字型,字型大小
        font_obj = ImageFont.truetype('static/font/kumo.ttf', 28)

        # 生成驗證碼
        code = ''
        for i in range(6):
            num = str(random.randint(0, 9))  # 數字
            lower = chr(random.randint(97, 122))  # 小寫字母
            upper = chr(random.randint(65, 90))  # 大寫字母
            c = random.choice([num, lower, upper])  # 隨機選取一個
            code += str(c)

            # 用畫筆把驗證碼畫到圖片上
            # 引數:xy:座標,畫在哪個位置;text:畫的內容;fill:畫什麼顏色;font:字型格式
            draw_obj.text((35 + i*30, 0), c, fill=random_color(), font=font_obj)
        # 儲存圖片
        img_obj.save(f)

get_code()
    

6,缺點
上面的程式碼是在你的硬碟上存了一張圖片,如果要在頁面上展示,你還得進行檔案的讀,
這樣的話不僅浪費硬碟空間,效率還不夠高,因此我們應該把圖片寫到記憶體,從記憶體中取,效率就快很多了,
然後把圖片的驗證碼資料存到session,這樣登入的時候就可以校驗了。

 

 

3、在檢視中使用驗證碼

1. urls
urlpatterns = [
    # 獲取圖片的路由
    url(r'^login/', views.login),
    url(r'^v_code/', views.v_code),
]


2. 在頁面中點選驗證碼圖片,重新整理驗證碼
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="content-Type" charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>

<form action='' method='POST'>
    {% csrf_token %}
    <input type='text' name='username'>使用者名稱
    <input type='password' name='password'>密碼
    <img src="/v_code/" alt="圖片載入失敗" id="v_code">
    <button type="submit">登入</button>
</form>

<script>
    img = document.getElementById('v_code');
    img.onclick = function () {
        img.src += '?'
    }
</script>

</body>
</html>


3. 驗證碼檢視函式
from PIL import Image, ImageDraw, ImageFont
import random

def random_color():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)


def v_code(request):

    # 第一步:生成一張圖片(畫布)
    # 建立一個隨機顏色的圖片物件
    # 引數:顏色模式,圖片大小,圖片顏色
    img_obj = Image.new('RGB', (250, 35), random_color())

    # 第二步:在該圖片物件上生成一個畫筆物件
    draw_obj = ImageDraw.Draw(img_obj)

    # 使用什麼字型,字型大小
    font_obj = ImageFont.truetype('static/font/kumo.ttf', 28)

    # 生成驗證碼
    code = ''
    for i in range(6):
        num = str(random.randint(0, 9))  # 數字
        lower = chr(random.randint(97, 122))  # 小寫字母
        upper = chr(random.randint(65, 90))  # 大寫字母
        c = random.choice([num, lower, upper])  # 隨機選取一個
        code += str(c)

        # 用畫筆把驗證碼畫到圖片上
        # 引數:xy:座標,畫在哪個位置;text:畫的內容;fill:畫什麼顏色;font:字型格式
        draw_obj.text((35 + i*30, 0), c, fill=random_color(), font=font_obj)

    # 把圖片裡面的驗證碼的內容寫到session,且忽略大小寫
    request.session['v_code'] = code.upper()

    # 把圖片寫到記憶體
    from io import BytesIO
    f1 = BytesIO()  # 類似於檔案的檔案控制代碼:f1 = open()
    # 把圖片儲存到記憶體
    img_obj.save(f1, format="PNG")
    # 從記憶體中取資料
    img_data = f1.getvalue()
    
    return HttpResponse(img_data, content_type='image/png')


4. 登入檢視函式
def login(request):
    err_msg = ''
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        v_code = request.POST.get('v_code', '').upper()
        
        if v_code == request.session.get('v_code'):
            obj = auth.authenticate(request, username=username, password=password)
            if obj:
                auth.login(request, obj)
                # 認證成功 初始化許可權資訊
                ret = init_permission(request, obj)
                if ret:
                    return ret
                return redirect(reverse('my_customer'))
            err_msg = '使用者名稱或密碼錯誤'
        else:
            err_msg = '驗證碼錯誤'
    
    return render(request, 'login.html', {'err_msg': err_msg})

 

4、驗證碼的額外小知識

畫完驗證碼後,可以新增一些干擾
就是在 draw_obj.text((35 + i*30, 0), c, fill=random_color(), font=font_obj)之後加

1. 加干擾線
width = 250  # 圖片寬度(防止越界)
height = 35
for i in range(5):
    x1 = random.randint(0, width)
    x2 = random.randint(0, width)
    y1 = random.randint(0, height)
    y2 = random.randint(0, height)
    draw_obj.line((x1, y1, x2, y2), fill=random_color())

2. 加干擾點
for i in range(40):
    draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=random_color())
    x = random.randint(0, width)
    y = random.randint(0, height)
    draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=random_color())