一. 中間件的簡介css
Django默認有七個中間件, 可是Django暴露給用戶能夠自定義中間件而且裏面能夠寫五種方法html
ps: 1.請求來的時候會依次執行每個中間件裏面的process_request方法(若是沒有直接跳過)前端
2. 響應走的時候會依次執行每個中間件裏面的process_response方法(若是沒有直接跳過)python
Django的生命請求週期:jquery
二. 中間件必掌握的知識點ajax
上面咱們已經知道能夠自定義一箇中間件,因而咱們就快馬加鞭的來建立一個,首先先了解下怎麼去建立一個自定義的中間件,先在settings裏面這樣設置,由於咱們不知道建立中間件所須要繼承的父類,因此就要看下源碼,那怎麼看源碼呢,就是我上面所說的在settings先這樣複製一下數據庫
複製以上任意一箇中間件,而後按最上面那種方式,前面加一個from, 中間添加一個import,這樣就好了,而後用鼠標點擊最後面的SecurityMiddleware,點進去就能看見這個是繼承的哪一個類,多話不說,我先建立一個類,看下圖:django
這就是我自定義的一箇中間件,咱們再視圖函數寫一個程序,運行結果以下:後端
經過運行結果能夠看出,無論怎麼樣,都首先是執行我自定義的中間件函數的,那咱們若是在中間件裏面返回一個HTTPresponse對象,那結果會如何呢,閒話不說,看下面圖緩存
結果又是什麼樣子的呢?看下圖
看到上圖你會想到什麼?我擦,上面不是咱們打印了好多函數麼,怎麼會只有一種輸出結果,什麼狀況?來,聽我給你們解釋:仍是看下面圖
上面咱們不是說執行順序是依次往下執行的麼,可是若是在process_request方法中返回了HTTPpresponse對象,那麼就會當即返回,不在去執行其餘的中間件
從這裏能夠看出Django中間件可以幫助咱們實現網站全局的身份驗證,黑名單,白名單, 訪問頻率限制,反爬相關,總之一句話,Django用來幫咱們全局相關的功能校驗
三. csrf跨站請求僞造
例子:釣魚網站, 經過製做一個跟正兒八經的網站如出一轍的頁面,騙取用戶輸入信息,轉帳交易,從而作一些手腳,轉帳交易的請求確確實實的是發給了銀行,帳戶的錢確實也少了,惟一不同的地方就是收款人帳戶不同.
內部原理:讓用戶輸入對方帳戶的那個input上面作手腳,給這個input不設置name屬性,在內部隱藏一個實現寫好的name和value屬性的input框,這個value的值就是釣魚網站受益人帳號
看下面例子:
正經網站路由層
正經網站前端
正經網站視圖層
釣魚端網頁路由層:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^transfer/', views.transfer),
釣魚端網頁視圖層:
def transfer(request):
return render(request,'ttt.html')
釣魚端網站前端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<h2>這是釣魚網站</h2>
<form action="http://127.0.0.1:8000/transfer/" method="post">
<p>本人用戶名:<input type="text" name="username"></p>
<p>轉帳金額:<input type="text" name="money"></p>
<p>對方帳戶:<input type="text"></p> # 在這裏不寫name屬性
<input type="text" name="target_user" value="jason" style="display: none"> # 從新在這邊寫一個input框,value值寫成釣魚網站受益人帳號,而後進行隱藏
<input type="submit">
</form>
</body>
</html>
產生的結果
egon原本打算給攀少轉錢,釣魚網站作的樣子跟正規網站的樣子是同樣的,egon粗枝大葉點進了一個釣魚網站進行轉帳,結果egon帳戶的錢少了,攀少帳戶沒有進錢,結果把錢所有轉進製做釣魚網站的Jason帳戶裏, 那麼這時候怎麼辦呢,沒有辦法,只能請求銀行方面的進行技術支持,銀行方面瞭解之後採起進一步的措施,採起的措施就是在form表單里加一個惟一標識,隨機的字符串,下一次若是轉帳的話,程序就會自動帶着這個惟一標識與轉帳的網站字符串進行對比,正確的話就能夠進行轉帳,字符串不同就會被攔截
具體解決方式: 在正規網站加一個{% csrf_token %}就能夠解決這個 問題,下圖是沒有加這個標識以前
從這個圖中能夠看出只有三個光禿禿的input標籤,其餘的什麼都麼有.而後在看看下面加了標識以後的圖片
能夠很明顯的看出在第一個input框裏有一個value值,隨機的字符串,網頁每刷新一次,就會產生有不一樣的字符串.
另外發送post請求的還有ajax請求,那麼如何避免CSRF校驗呢?咱們這裏有三種方法
1. 先在頁面上寫{% csrf_token %},利用標籤查找, 獲取到該input鍵值信息
2. 直接書寫''{{ csrf_token}}''
3. 能夠將該獲取隨機鍵值對的方法寫到一個js文件中,以後導入該文件就行
分別是他們三個的書寫方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<h2>這是正兒八經的網站</h2>
<form action="/lll/" method="post">
{# {% csrf_token %}#}
<p>本人用戶名:<input type="text" name="username"></p>
<p>轉帳金額:<input type="text" name="money"></p>
<p>對方帳戶:<input type="text" name="target_user"></p>
<input type="submit">
</form>
<button id="b1">發送ajax請求</button>
{% load static %}
<script src="{% static 'setjs.js' %}"></script>
<script>
$('#b1').click(function () {
$.ajax({
url:'',
type:'post',
// 第一種方式
data:{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
// 第二種方式#}
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'
// 第三種方式 :直接引入js文件
data:{'username':'jason'},
success:function (data) {
alert(data)
}
})
{})
</script>
</body>
</html>
第三種方式在寫的時候要引入js靜態文件,咱們只需建一個'static'靜態文件加,將下面的代碼複製在這個文件夾下的js文件裏面
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
跨站請求僞造裝飾器:
咱們如今把settings裏面的csrf配置打開了,意味着什麼呢,全部的網站請求都得去校驗, 那我如今有這麼一個需求, 我想讓某一個視圖函數不進行校驗,該如何處理呢, 還有一個需求就是,當網站全局不校驗csrf的時候,有幾個須要校驗又是該如何處理呢,假如我想讓login不進行校驗呢,這個時候呢就要用它內部的裝飾器,他能夠裝飾那些不須要校驗,那些須要校驗,導入模塊
from django.views.decorators.csrf import csrf_exempt,csrf_protect
上面導入的是一個保護,一個不保護,假如咱們想讓login不校驗,就給他裝飾一個@scrf_exempt,這樣的話login就不會再校驗csrf了, 這個意思就是說當個人全部網站進行校驗的時候我不想讓某個視圖函數校驗就用@csrf_exempt來進行裝飾,那麼咱們剛開始問題上說若是全局都不校驗,我想讓某個視圖函數進行校驗時,就用裝飾器@csrf_protect來進行裝飾,這種狀況是針對FBVl來解決的,CBV的話就有點門道了,來,例子進行簡單說明一下,,,,,
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator # 固定的方法裝飾器,來專門幫助咱們裝飾CBV這種形式的
# 第一種方式
# @method_decorator(csrf_protect,name='post') # 有效的
# @method_decorator(csrf_exempt,name='post') # 無效的
@method_decorator(csrf_exempt,name='dispatch') # 第二種能夠不校驗的方式
class MyView(View):
# @method_decorator(csrf_exempt) # 第一種能夠不校驗的方式
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
res = super().dispatch(request, *args, **kwargs)
return res
def get(self,request):
return HttpResponse('get')
# 第二種方式
# @method_decorator(csrf_exempt) # 無效的
# @method_decorator(csrf_protect) # 有效的
def post(self,request):
return HttpResponse('post')
總結: 總結 裝飾器中只有csrf_exempt是特例,其餘的裝飾器在給CBV裝飾的時候 均可以有三種方式
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt,csrf_protect # 這兩個裝飾器在給CBV裝飾的時候 有必定的區別 若是是csrf_protect 那麼有三種方式 # 第一種方式 # @method_decorator(csrf_protect,name='post') # 有效的 class MyView(View): # 第三種方式 # @method_decorator(csrf_protect) def dispatch(self, request, *args, **kwargs): res = super().dispatch(request, *args, **kwargs) return res def get(self,request): return HttpResponse('get') # 第二種方式 # @method_decorator(csrf_protect) # 有效的 def post(self,request): return HttpResponse('post') 若是是csrf_exempt 只有兩種(只能給dispatch裝) 特例 @method_decorator(csrf_exempt,name='dispatch') # 第二種能夠不校驗的方式 class MyView(View): # @method_decorator(csrf_exempt) # 第一種能夠不校驗的方式 def dispatch(self, request, *args, **kwargs): res = super().dispatch(request, *args, **kwargs) return res def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post')
四. auth模塊
若是在開發過程當中用auth,就要用auth的全套,全部的方法都是一套,用戶的都是一套.
跟用戶相關的功能模塊, 用戶的註冊, 登陸, 驗證, 修改密碼等等
執行數據庫遷移命令以後,會生成不少表,其中的auth_user是一張用戶相關的表格
添加數據:
createssuperuser 建立超級用戶, 這個超級用戶就能夠擁有Django admin後臺管理的權限
咱們先建立一個用戶,先進行登陸
後端代碼:
from django.contrib import auth
def xxx(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 取數據庫查詢當前用戶數據
# models.User.objects.filter(username=username,password=password).first() # 不能用這種方式進行查詢,由於在這裏獲取的密碼是明文,而數據庫中的密碼是密文,這樣查詢的話查到海枯石爛,雷峯塔倒都查詢不到,並且如今的這張表不是models裏面的,是auth模塊裏面的
user_obj = auth.authenticate(username=username,password=password) # 必需要用 由於數據庫中的密碼字段是密文的 而你獲取的用戶輸入的是明文,這句話就至關於上面models.User.objects.filter(username=username,password=password).first()的意思
print(user_obj) # 打印出的是一個對象,內部置有__str__方法
# print(user_obj)
# print(user_obj.username)
# print(user_obj.password) # 打印出的是一個密文
# 保存用戶狀態
# request.session['user'] = user_obj
auth.login(request,user_obj) # 將用戶狀態記錄到session中
"""只要執行了這一句話 就能夠在後端任意位置經過request.user獲取到當前用戶對象"""
return render(request,'xxx.html')
若是用戶沒有進行登陸,咱們用request.user打印出的結果不是一個對象,也不是一個none,而是一個匿名用戶AnonymousUser
判斷用戶是否登陸:
後端代碼:
def yyy(request):
print(request.user) # 若是沒有執行auth.login那麼拿到的是匿名用戶
print(request.user.is_authenticated) # 判斷用戶是否登陸 若是是匿名用戶會返回False,若是是用戶對象就會返回一個True
return HttpResponse('yyy')
修改密碼:
後端代碼:
from django.contrib.auth.decorators import login_required # auth幫咱們提供的 # 修改用戶密碼 @login_required # 自動校驗當前用戶是否登陸 若是沒有登陸 默認跳轉到 一個莫名其妙的登錄頁面
# login_required(login_url='')能夠跳轉到我本身的頁面,這樣是局部配置,還能夠在全局配置,若是不在全局配置的話,再增長一個功能時,咱們還得手動添加 def set_password(request): if request.method == 'POST': old_password = request.POST.get('old_password') new_password = request.POST.get('new_password') # 先判斷原密碼是否正確 is_right = request.user.check_password(old_password) # 將獲取的用戶密碼 自動加密 而後去數據庫中對比當前用戶的密碼是否一致 if is_right: print(is_right) # 打印True或者false # 修改密碼 request.user.set_password(new_password) request.user.save() # 修改密碼的時候 必定要save保存 不然沒法生效 return render(request,'set_password.html')
在settings裏面的隨意的地方,寫LOGIN_URL = 'XXX',這個xxx就是咱們本身網站的路由,咱們想登錄以後跳到那個頁面,就寫哪一個路由,若是在settings裏面配置了之後就不要在視圖函數中的@login_requred(login_url='')括號裏面的
前端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username" value="{{ request.user.username }}" disabled></p> //這樣操做的話用戶就只能改密碼了
<p>old_password:<input type="password" name="old_password"></p>
<p>new_password:<input type="password" name="new_password"></p>
<input type="submit">
</form>
</body>
</html>
註銷功能:
@login_required
def logout(request):
# request.session.flush() # session的註銷方法
auth.logout(request)
return HttpResponse("logout")
註冊功能:
from django.contrib.auth.models import User
def register(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = User.objects.filter(username=username)
if not user_obj:
# User.objects.create(username =username,password=password) # 針對auth模塊,平時用模型層的時候咱們還能夠用create的,建立用戶名的時候 千萬不要再使用create 了,這樣建立的話密碼在數據庫裏面是個明文
# User.objects.create_user(username =username,password=password) # 建立普通用戶
User.objects.create_superuser(username =username,password=password,email='123@qq.com') # 建立超級用戶 ,郵箱是必填的
return render(request,'register.html')
五. auth自定義用戶表
好比咱們想在原來數據庫表中另外添加字段,咱們能夠在models裏面建立從新建立一個表,可使用類的繼承的方法,咱們不在繼承models.Model而是使用AbstractUser
from django.contrib.auth.models import AbstractUser
# 第一種 使用一對一關係 不考慮
# 第二種方式 使用類的繼承
class Userinfo(AbstractUser):
# 千萬不要跟原來表中的字段重複 只能創新
phone = models.BigIntegerField()
avatar = models.CharField(max_length=32)
在這裏,建好表之後,要去settings裏面告訴Django orm再也不使用auth默認的表,而是自定義的表, AUTH_USER_MODEL='app01.Userinfo' 就是'應用名.模型層中的類名', 在新建的表中再也不有原來數據庫表中auth_user,而是咱們新建的auth_userinfo,在這個userinfo裏面,字段名比原來數據庫中字段名多了兩個字段名,這兩個也就是咱們新建的phone,avatar兩個字段,新增的兩個字段擁有原來表中字段的全部功能,和原來表中的字段的功能都是同樣的.
額外的思想,基於Django中間件思想實現功能插拔式配置
要求: 開發一個基於微信,短信,qq的通知功能
第一種方式:函數式版本:
def wechat(content):
print('微信通知:%s'%content)
def msg(content):
print('短信通知:%s'%content)
def email(content):
print('郵件通知:%s'%content)
from lowb版本.notify import *
def run(content):
wechat(content)
msg(content)
email(content)
if __name__ == '__main__':
run('國慶八天假 我該去哪玩?')
第二種方式:參照Django中間件的方式,把全部的功能都寫成配置文件的形式,一旦這個功能不用了,就將這個功能註釋掉就行,不瞎雞兒動,只動一個地方就行.先建一個文件夾notify,再根據功能的不一樣建不一樣的py文件,email.py. msg.py, wechat.py 這三個統一秉承Python的鴨子類型機制,寫成類的形式,在每一個類中定義一個方法send.
__init__文件
import settings
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: # 1.拿出一個個的字符串 'notify.email.Email'
module_path,class_name = path_str.rsplit('.',maxsplit=1) # 2.從右邊開始 按照點切一個 ['notify.email','Email']
module = importlib.import_module(module_path) # from notity import msg,email,wechat
cls = getattr(module,class_name) # 利用反射 一切皆對象的思想 從文件中獲取屬性或者方法 cls = 一個個的類名
obj = cls() # 類實例化生成對象
obj.send(content) # 對象調方法
importlib.
import_module
(name, package=None)
導入一個模塊。參數 name 指定了以絕對或相對導入方式導入什麼模塊 (好比要麼像這樣 pkg.mod
或者這樣 ..mod
)。若是參數 name 使用相對導入的方式來指定,那麼那個參數 packages 必須設置爲那個包名,這個包名做爲解析這個包名的錨點 (好比 import_module('..mod', 'pkg.subpkg')
將會導入 pkg.mod
)。
import_module()
函數是一個對 importlib.__import__()
進行簡化的包裝器。 這意味着該函數的全部主義都來自於 importlib.__import__()
。 這兩個函數之間最重要的不一樣點在於 import_module()
返回指定的包或模塊 (例如 pkg.mod
),而 __import__()
返回最高層級的包或模塊 (例如 pkg
)。
若是動態導入一個自從解釋器開始執行以來被建立的模塊(即建立了一個 Python 源代碼文件),爲了讓導入系統知道這個新模塊,可能須要調用 invalidate_caches()
。
在 3.3 版更改: 父包會被自動導入。
importlib.
find_loader
(name, path=None)
查找一個模塊的加載器,可選擇地在指定的 path 裏面。若是這個模塊是在 sys.modules
,那麼返回 sys.modules[name].__loader__
(除非這個加載器是 None
或者是沒有被設置, 在這樣的狀況下,會引發 ValueError
異常)。 不然使用 sys.meta_path
的一次搜索就結束。若是未發現加載器,則返回 None
。
點狀的名稱沒有使得它父包或模塊隱式地導入,由於它須要加載它們而且可能不須要。爲了適當地導入一個子模塊,須要導入子模塊的全部父包而且使用正確的參數提供給 path。
3.3 新版功能.
在 3.4 版更改: 若是沒有設置 __loader__
,會引發 ValueError
異常,就像屬性設置爲 None
的時候同樣。
3.4 版後已移除: 使用 importlib.util.find_spec()
來代替。
importlib.
invalidate_caches
()
使查找器存儲在 sys.meta_path
中的內部緩存無效。若是一個查找器實現了 invalidate_caches()
,那麼它會被調用來執行那個無效過程。 若是建立/安裝任何模塊,同時正在運行的程序是爲了保證全部的查找器知道新模塊的存在,那麼應該調用這個函數
email文件
class Email(object):
def __init__(self):
pass # 發送郵件須要的代碼配置
def send(self,content):
print('郵件通知:%s'%content)
msg文件
class Msg(object):
def __init__(self):
pass # 發送短信須要的代碼配置
def send(self,content):
print('短信通知:%s' % content)
qq文件:
class QQ(object):
def __init__(self):
pass # 發送qq須要的代碼準備
def send(self,content):
print('qq通知:%s'%content)
WeChat文件
class WeChat(object):
def __init__(self):
pass # 發送微信須要的代碼配置
def send(self,content):
print('微信通知:%s'%content)
settings的配置
NOTIFY_LIST = [ 'notify.email.Email', 'notify.msg.Msg', 'notify.wechat.WeChat', 'notify.qq.QQ', ]
這裏面至關於一個個路徑
start文件
import notify
notify.send_all('國慶放假了 記住放八天哦')