Django之快取、訊號和驗證碼
阿新 • • 發佈:2018-12-16
一、 快取
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())