Python自动化开发学习22-Django上
session
上節已經講了使用Cookie來做用戶認證,但是
Cookie的問題
缺點:敏感信息不適合放在cookie里,敏感信息只能放在服務器端
優勢:把部分用戶數據分散的存放在每個客戶端,減輕服務端的壓力
Cookie是保存在用戶瀏覽器端的鍵值對,Session是保存在服務器端的鍵值對。
Session依賴Cookie,Cookie保存隨機字符串,憑借這個隨機字符串獲取到服務器端Session里的內容。
用Session來優化用戶登錄:用戶登錄后,生成一個隨機字符串,通過Cookie發送給客戶端保存。服務器端維護一個字典,字典的key就是這個隨機生成的字符串,字典的value是另外一個字典,在服務器端保存各種用戶的敏感信息。
Django的sessiong默認是保存在數據庫中的,所以要使用session需要先執行生成數據庫的2條命令:
即使你沒有寫任何一張表,也會默認生成一些表,其中 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'] = userrequest.session['is_login'] = Truereturn 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'] :刪除值
對所有鍵、值、鍵值對的操作:
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,正??蛻舳颂峤粩祿翘峤唤o當前訪問的網站。但是也是可以提交到別的網站去的(你可以提交到任何地方)。雖然你可以提交過去,但是這種方式你沒有目標網站給你的加密字符串的,所以開啟CSRF之后,直接就把這部分請求攔截了。
開啟CSRF
django為用戶實現防止跨站請求偽造的功能,通過中間件 django.middleware.csrf.CsrfViewMiddleware 來完成。而對于django中設置防跨站請求偽造功能有分為全局和局部。
全局,settings.py 文件中的MIDDLEWARE(中間件)里,是一個列表,之前都是先把下面的這行注釋掉的:
局部
導入模塊:from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_protect :為當前函數強制設置防跨站請求偽造功能,即便settings中沒有設置全局中間件。
@csrf_exempt :取消當前函數防跨站請求偽造功能,即便settings中設置了全局中間件。
CSRF Token相關裝飾器在CBV只能加到dispatch方法上
關于dispatch方法,可以看講CBV時記得筆記:https://blog.51cto.com/steed/2091163
這里要做的就是繼承父類的dispatch方法,然后加上裝飾器:
應用-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 頁面跳轉:
處理函數也在原來的基礎上添加。通過 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'] = userrequest.session['is_login'] = Trueprint(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檢查是否需要加csrfif (!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文件:
上面寫了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' 就是緩存存放的位置:
默認的 '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, prependthe `key_prefix'. KEY_FUNCTION can be used to specify an alternatefunction 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 createcachetableMemcache緩存(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模塊,所以引擎要變一下。
應用
準備測試環境
使用文件的形式來測試,在項目下創建一個cache文件夾來存放緩存文件,配置如下:
然后寫一個顯示當前時間戳的處理函數:
def cache(request):import timectime = time.time()return render(request, 'cache.html', {'ctime': ctime})頁面只要把時間戳顯示出來:
<body> <h1>{{ ctime }}</h1> <h1>{{ ctime }}</h1> </body>現在訪問頁面會看到一個時間戳,并且刷新頁面時間戳也會刷新,說明沒有走緩存。因為上面我們只是把緩存配置上了,還是還沒應用上。
全站應用緩存
這個太粗糙了,下面有更加精細的設置。
要給所有的請求都應用上緩存,這個功能適合放在中間件里。中間件里需要寫2個中間件
查緩存,收到用戶請求先查找緩存,如果命中則直接返回緩存的內容。這個中間件要放在后面的位置,只有通過了一系列驗證的中間件后才能給用戶返回數據。
寫緩存,返回給用戶之前沒有緩存的內容的同時,需要把這個內容寫到緩存里去,那么下次就有緩存了。這個中間件要放在靠前的位置,通過了一系列其他中間件加工之后,把最終返回的內容緩存起來。
視圖應用緩存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)), ]局部視圖緩存:
就是在模板語言里聲明,頁面里的哪些內容是要應用緩存的
首先要load緩存,然后在要應用緩存的內容前后用標簽包起來。這里需要定義timeout時間和key。
這個局部的應用場景還是很多的,只緩存頁面中不會頻繁發生變化的內容,而經常會變的內容則不緩存
補充
在后面的模塊寫項目的時候遇到了,補充進來:
在后臺調試緩存
測試臨時存儲的有失效性的 key 和 value。下面的例子中,在緩存里存了一個值,時間是5秒。有效時間內通過key可以獲取到值,超過了之后,返回就是None。
超時時間的小結
在CACHES里可以加上TIMEOUT設置超時時間,這個是引擎的超時時間。要使用緩存首先要設置一個引擎。
設置完引擎后,就是要應用緩存了。上面介紹了不同顆粒度的應用方式:中間件(全局)、url、視圖函數、局部視圖。每次應用的時候是沒有選擇引擎的,所以貌似引擎只能用CACHES設置好的那一個。但是每個方法都有自己的方式來設置超時時間。
關于超時時間的優先級,最后使用前測試一下。下面是猜測不是結論,url、視圖函數、局部視圖的優先級應該高于引擎的。中間件和引擎的超時設置都在配置文件里,具體是什么情況最好是測試一下。
信號
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_preparedfrom django.db.models.signals import pre_migrate, post_migratefrom django.core.signals import request_started from django.core.signals import request_finished from django.core.signals import got_request_exceptionfrom django.test.signals import setting_changed from django.test.signals import template_renderedfrom django.db.backends.signals import connection_created注冊函數1
將函數名作為參數完成注冊
注冊函數2
為函數加上裝飾器也能完成注冊:
自定義信號
要自定義信號需要3步:
內置信號只需要注冊。而自定義的信號,需要在注冊前先定義好這個信號。之后去你需要的位置加上觸發信號的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)轉載于:https://blog.51cto.com/steed/2104127
總結
以上是生活随笔為你收集整理的Python自动化开发学习22-Django上的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue-electron 写一个mark
- 下一篇: Python 工具——Anaconda+