使用者認證元件的學習
使用者認證是通過取表單資料根資料庫對應表儲存的值做比對,比對成功就返回一個頁面,不成功就重定向到登入頁面。我們自己寫的話當然也是可以的,只不過多寫了幾個檢視,冗餘程式碼多,當然我們也可以封裝成函式,簡單程式碼。不過推薦使用Django提供的一套使用者認證元件,原理其實類似,只不過功能更強大。
1,使用者認證——auth模組
在進行使用者登入驗證的時候,如果是自己寫程式碼,就必須要先查詢資料庫,看使用者輸入的使用者名稱是否存在於資料庫中;如果使用者存在於資料庫中,然後在驗證使用者輸入的密碼,這樣一來,自己就需要編寫大量的程式碼。
事實上,Django已經提供了內建的使用者認證功能,在使用“python manage.py makemigrations” 和 “python manage.py migrate” 遷移完成資料庫之後,根據配置檔案settings.py中的資料庫段生成的資料表中已經包含了6張進行認證的資料表,分別是:
而要進行使用者認證的資料表示auth_user。
要使用Django自帶的認證功能,首先匯入auth模組:
# auth主認證模組 from django.contrib.auth.models import auth # 對應資料庫,可以建立新增記錄 from django.contrib.auth.models import User
django.contrib.auth中提供了許多方法,這裡主要介紹其中三個:
1.1 authenticate()
提供了使用者認證,即驗證使用者名稱以及密碼是否正確,一般需要username,password 兩個關鍵字引數。
如果認證資訊有效,會返回一個user物件。authenticate()會在User物件上設定一個屬性標識那種認證後端認證了該使用者,且該資訊在後面的登入過程中是需要的。當我們試圖登入一個從資料庫中直接取出來不經過authenticate()的User物件會報錯的!
user=authenticate(username="uaername",password="password") login(HttpResponse,user)
這個函式接受一個HTTPRequest物件,以及一個通過authenticate() 函式認證的User物件。
1.2 login(HttpRequest,user)
該函式接受一個HttpRequest物件 ,以及一個認證了的User物件,此函式使用django的session框架給某個已認證的使用者附加上session id等資訊。
from django.shortcuts import redirect, HttpResponse from django.contrib.auth import authenticate, login def auth_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user is not None: login(request, user) # Redirect to a success page. return redirect("/index/") else: # Return an 'invalid login' error message. return HttpResponse("username or password is incorrect")
該函式實現一個使用者登入的功能。它本質上會在後端為該使用者生成相關session資料。
1.3 logout(request)登出使用者
from django.contrib.auth import logout def logout_view(request): logout(request) # Redirect to a success page.
該函式接受一個HttpRequest物件,無返回值。當呼叫該函式時,當前請求的session資訊會全部清除。該使用者即使沒有登入,使用該函式也不會報錯。
雖然使用的logout()函式,但是其本質上還是使用的是fulsh() 。我們可以檢視 auth.logout()函式的原始碼
從原始碼中發現,驗證完之後,還是使用 request.session.flush() 進行刪除session資訊。
2,User物件
User物件屬性:username,password(必填項) password用雜湊演算法儲存到資料庫。
django Auth模組自帶User模型所包含欄位
username:使用者名稱 email: 電子郵件 password:密碼 first_name:名 last_name:姓 is_active: 是否為活躍使用者。預設是True is_staff: 是否為員工。預設是False is_superuser: 是否為管理員。預設是False date_joined: 加入日期。系統自動生成。
2.1 user物件的is_authenticated()
如果是真正的User物件,返回值恆為True,用於檢查使用者是否已經通過了認證。通過認證並不意味著使用者認證擁有任何許可權,甚至也不檢查該使用者是否處於啟用狀態,這只是表明使用者成功的通過了認證。這個方法很重要,在後臺用request.user.is_authenticated()判斷使用者是否已經登入,如果true則可以向前臺展示request.user.name。
要求:
- 1,使用者登入後才能訪問某些頁面
- 2,如果使用者沒有登入就訪問該頁面的話直接跳到登入頁面
- 3,使用者在跳轉的登入介面中完成登入後,自動訪問跳轉到之前訪問的地址
方法一:
def my_view(request): if not request.user.is_authenticated(): return redirect("%s?next=%s"%(settings.LOGIN_URL, request.path))
方法二:
django已經為我們設計好了一個用於此種情況的裝飾器:login_required()
login_required():用來快捷的給某個檢視新增登入校驗的裝飾器工具。
from django.contrib.auth.decorators import login_required @login_required def my_view(request): ...
若使用者沒有登入,則會跳轉到django預設的登入URL ‘/accounts/login/’並傳遞當前訪問url的絕對路徑(登入成功後,會重定向到該路徑)。
如果需要自定義登入的URL,則需要在settings.py檔案中通過LOGIN_URL進行修改。
LOGIN_URL = '/login/' # 這裡配置成你專案登入頁面的路由
2.2 建立使用者
使用create_user輔助函式建立使用者
from django.contrib.auth.models import User user = User.objects.create_user(username=" " , password =" ", email=" ")
使用create_superuser()建立超級使用者
from django.contrib.auth.models import User user = User.objects.create_superuser(username='使用者名稱',password='密碼',email='郵箱',...)
is_authenticated()用來判斷當前請求是否通過了認證。
用法:
def my_view(request): if not request.user.is_authenticated(): return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
2.3 檢查密碼是否正確
使用check_password(passwd)來檢查密碼是否正確,密碼正確的話返回True,否則返回False。
ok = user.check_password('密碼')
2.4 修改密碼
使用set_password() 來修改密碼,接受要設定的新密碼作為引數。
使用者需要修改密碼的時候,首先讓他輸入原來的密碼,如果給定的字串通過了密碼檢查,返回True
注意:設定完一定要呼叫使用者物件的save方法
user = User.objects.get(username = ' ' ) user.set_password(password ='') user.save
修改密碼示例:
from django.contrib.auth.models import User from django.shortcuts import HttpResponse def register(request): # 建立使用者 user_obj = User.objects.create_user(username='james', password='123') # 檢查密碼(一般用於修改密碼前驗證) ret = user_obj.check_password('123') print(ret) # 返回False # 修改密碼 user_obj.set_password('1234') # 修改後儲存 user_obj.save() # 修改後檢查 ret = user_obj.check_password('1234') print(ret) # 返回True return HttpResponse("OK")
2.5 示例一:使用set_password() 方法來修改密碼
from django.shortcuts import render,redirect,HttpResponse from django.contrib.auth.models import User def create_user(request): msg=None if request.method=="POST": username=request.POST.get("username"," ") # 獲取使用者名稱,預設為空字串 password=request.POST.get("password"," ") # 獲取密碼,預設為空字串 confirm=request.POST.get("confirm_password"," ") # 獲取確認密碼,預設為空字串 if password == "" or confirm=="" or username=="": # 如果使用者名稱,密碼或確認密碼為空 msg="使用者名稱或密碼不能為空" elif password !=confirm: # 如果密碼與確認密碼不一致 msg="兩次輸入的密碼不一致" elif User.objects.filter(username=username): # 如果資料庫中已經存在這個使用者名稱 msg="該使用者名稱已存在" else: new_user=User.objects.create_user(username=username,password=password) #建立新使用者 new_user.save() return redirect("/index/") return render(request,"login.html",{"msg":msg})
2.6 示例二:使用login_required 裝飾器 來修改密碼
from django.shortcuts import render, redirect, HttpResponse from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User @login_required def change_passwd(request): user = request.user # 獲取使用者名稱 msg = None if request.method == 'POST': old_password = request.POST.get("old_password", "") # 獲取原來的密碼,預設為空字串 new_password = request.POST.get("new_password", "") # 獲取新密碼,預設為空字串 confirm = request.POST.get("confirm_password", "") # 獲取確認密碼,預設為空字串 if user.check_password(old_password): # 到資料庫中驗證舊密碼通過 if new_password or confirm: # 新密碼或確認密碼為空 msg = "新密碼不能為空" elif new_password != confirm: # 新密碼與確認密碼不一樣 msg = "兩次密碼不一致" else: user.set_password(new_password) # 修改密碼 user.save() return redirect("/index/") else: msg = "舊密碼輸入錯誤" return render(request, "change_passwd.html", {"msg": msg})
3,基於使用者認證元件的示例
功能就是用session記錄登入驗證狀態,但是前提是要使用Django自帶的auth_user,所以我們需要建立超級使用者。下面都會說到。其使用者認證元件最大的優點就是 request.user 是一個全域性變數,在任何檢視和模板中都能直接使用。
重點是下面:
if not: auth.logout(request, user) #此時返回的物件 request.user == AnonymousUser() else: request.user == 登入物件
3.1 基於使用者認證元件的登入驗證資訊儲存功能
下面我們完成一個登陸驗證資訊儲存功能,我們使用的就是上面講到的auth模組的authenticate()函式。上面也說到了,authenticate()提供了使用者認證,即驗證使用者名稱和密碼是否正確,如果認證資訊有效的話,會返回一個user物件。
由於User表不是我們建立的,而且人家密碼是加密的,如何加密我們並不知道。所以提取認證方式只能使用人家設定的認證方式。
我們要的是,建立一個超級使用者,然後寫一個登入驗證函式,如果驗證成功,進入索引介面。如果驗證失敗,則繼續留在驗證頁面。
首先,我們建立一個超級使用者:
python manage.py createsuperuser
名稱為 james, 密碼為 123。我們在資料庫的 auth_user中檢視:
然後我們完成登入驗證函式和簡單的索引函式。
from django.shortcuts import render, HttpResponse, redirect # Create your views here. from django.contrib import auth from django.contrib.auth.models import User from auth_demo import models def login(request): if request.method == 'POST': user = request.POST.get('user') pwd = request.POST.get('pwd') # if 驗證成功返回 user 物件,否則返回None user = auth.authenticate(username=user, password=pwd) if user: # request.user : 當前登入物件 auth.login(request, user) # return HttpResponse("OK") return redirect('/auth/index') return render(request, 'auth/login.html') def index(request): print('request.user', request.user.username) print('request.user', request.user.id) # 下面是判斷是是否是匿名 print('request.user', request.user.is_anonymous) if request.user.is_anonymous: # if not request.user.authenticated(): return redirect('/auth/login') username = request.user.username return render(request, 'auth/index.html', locals())
下面寫一個簡單的login頁面 和 index頁面。
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>login page</h1> <form action="" method="post"> {% csrf_token %} <p>username:<input type="text" name="user" ></p> <p>password:<input type="password" name="pwd"></p> <p><input type="submit" value="提交"></p> </form> </body> </html>
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>this is index page</h1> <p>{{ username }}</p> </body> </html>
下面進入登入介面,如果登入成功,則進入索引介面,我們輸入正確的賬號和密碼。
點選提交如下:
3.2 基於使用者認證元件的登出功能
上面我們也說了,使用者登出的話,我們可以使用request.session.fulsh()。但是Django自帶了auth.logout()函式,為什麼使用這個呢?其實我們前面也看了原始碼。在進行驗證後,使用request.session.fulsh(),但是他最後使用了匿名函式 AnonymousUser()。
下面我們寫一個登出函式:
def logout(request): auth.logout(request) return redirect(request, '/auth/login/')
其實非常簡單,登出後,將頁面重定向到登入頁面。我們在前臺索引頁面,加上一個登出的功能:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>this is index page</h1> <p>{{ username }}</p> <a href="/auth/login">登出</a> </body> </html>
點進去索引介面如下:
我們點選登出,則返回到登入頁面,如下:
點選登出後,我們可以去資料庫檢視 django_session 的內容,會發現,登出後,一條記錄就沒有了。
3.3 基於使用者認證元件的註冊使用者功能
在上面使用者登入的時候,我們會發現有一行程式碼,是
user = auth.authenticate(username=user, password=pwd)
也就是說,資料庫已經存在了資料,那麼要是沒資料的話,我們怎麼辦?
下面我們演示一個註冊使用者的功能。
我們將資料從前臺拿過來,我們下面就是插入資料到User表中,這裡注意的是,我們不能直接插入,比如下面:
user = User.objects.create(username=user, password=pwd)
上面插入時是明文插入,但是我們不能這樣,我們需要插入的是加密後的密碼,所以使用下面程式碼:
user = User.objects.create_user(username=user, password=pwd)
OK,說完注意點,我們寫註冊檢視函式:
def register(request): if request.method == 'POST': user = request.POST.get('user') pwd = request.POST.get('pwd') user = User.objects.create_user(username=user, password=pwd) return HttpResponse("OK") return render(request, 'auth/register.html')
當我們註冊成功後,顯示OK即可(簡單方便)。
註冊頁面和登入類似,我們展示如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>register page</h1> <form action="" method="post"> {% csrf_token %} <p>username:<input type="text" name="user" ></p> <p>password:<input type="password" name="pwd"></p> <p><input type="submit" value="提交"></p> </form> </body> </html>
註冊頁面如下:
我們註冊一個 durant,我們在 auth_user 資料庫中檢視結果:
註冊成功如下:
auth_user 資料表如下:
這表明我們註冊成功了。
3.4 基於使用者認證元件的認證裝飾器功能
為什麼要用認證裝飾器呢?
在以後的程式碼中,我們肯定需要很多認證登入,如果驗證成功,則進入登入頁面,如果驗證不成功,則返回到登入頁面。那麼為了避免程式碼的冗餘,我們可以寫一個裝飾器的東西,不過Django已經為我們準備好了,我們只需要用就行。
驗證裝飾器:看那些頁面需要登入才能訪問,如果沒有登入跳轉設定的頁面去。
注意:在settings.py中設定如下:
# 用於auth模組 裝飾器校驗返回頁面 LOGIN_URL = '/login/'
在django專案中,經常看有下面的程式碼:
from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User @login_required def my_view(request): pass
裡面有一個@login_required標籤。其作用就是告訴程式,使用這個方法是要求使用者登入的。
下面舉個例子:
def index(request): username = request.user.username return render(request, 'auth/index.html', locals())
我們訪問上面已經完成的 index頁面,我們會發現:
當我們給 index 檢視函式加上裝飾器,程式碼如下:
from django.contrib.auth.decorators import login_required @login_required def index(request): username = request.user.username return render(request, 'auth/index.html', locals())
我們再來訪問:
下面說一下,URL是什麼意思呢?
1,如果使用者還沒有登入,預設會跳轉到'/accounts/login/'。這個值可以在settings檔案中通過LOGIN_URL引數來設定。(後面還會自動加上你請求的url作為登入後跳轉的地址,如:/accounts/login/?next=/auth/index/ 登入完成之後,會去請求)
# 如果不新增該行,則在未登陸狀態開啟頁面的時候驗證是否登陸的裝飾器跳轉到/accounts/login/下面 # 第一張方法就是修改settings.py 中的 LOGIN_URL LOGIN_URL = "/login/"
如下:
為什麼會報錯呢?因為我們沒有設定其路徑。
我們在settings.py中設定登入URL(當然這是我自己的路由地址):
# 這裡配置成專案登入頁面的路由 LOGIN_URL = '/auth/login'
下面訪問 index則如下:
沒有使用者沒有登入的話,則直接跳轉到登入頁面。
我們不能講登入檢視函式的程式碼寫死,這裡改進一下,如下:
def login(request): if request.method == 'POST': user = request.POST.get('user') pwd = request.POST.get('pwd') # if 驗證成功返回 user 物件,否則返回None user = auth.authenticate(username=user, password=pwd) if user: # request.user : 當前登入物件 auth.login(request, user) next_url = request.GET.get('next', 'auth/index') return redirect(next_url) return render(request, 'auth/login.html')
如果驗證成功,我們跳轉到 next_url,如果獲取不到,則跳轉到index頁面。
2,如果使用者登入了,那麼該方法就可以正常執行
如果LOGIN_URL使用預設值,那麼在urls.py中還需要進行如下設定:
# 第二種解決方法是在 url 中匹配該url (r'^accounts/login/$', 'django.contrib.auth.views.login'),
這樣的話,如果未登入,程式會預設跳轉到“templates/registration/login/html” 這個模板。
如果想換個路徑,那就在加個template_name引數,如下:
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}),
這樣程式就會跳轉到 template/myapp/login.html 中。
Django檢視層的學習
我在之前的Django學習筆記(2):模板後臺管理和檢視的學習 中學習了檢視層,並對其有了大概的理解。現在再進一層的學習檢視層中request屬性和HttpResponse物件的方法。
1,檢視函式
一個檢視函式,簡稱檢視,是一個簡單的Python函式,它接受Web請求並返回Web響應。響應可以是一張網頁的HTML內容,一個重定向,一個404錯誤,一個XML文件,或者一張圖片...是任何東西都可以。無論檢視本身包含什麼邏輯,都要返回響應。為了將程式碼放在某處,約定是將檢視放置在專案或者應用程式目錄中的名為 views.py 的問卷中。
下面是一個返回當前日期和時間作為HTML 文件的檢視:
from django.shortcuts import render, HttpResponse, HttpResponseRedirect, redirect import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
我們來逐行閱讀上面的程式碼:
- 首先,我們從django.shortcuts 模組匯入了 HttpResponse類,以及Python的datetime庫。
- 接著,我們定義了current_datetime函式。它就是檢視函式,每個檢視函式都使用HttpRequest物件作為第一個引數,並且通常稱之為 request。
- 注意:檢視函式的名稱並不重要,不需要用一個統一的命名方式來命名。以便讓Django能夠識別它,我們將其命名為current_datetime,是因為這個名稱能夠精準的反映出其功能*(我認為啊)
- 這個檢視會返回一個HttpResponse物件。其中包含生成的響應。每個檢視函式都負責返回一個HttpResponse物件。
而檢視層中,熟練掌握兩個物件:請求物件(request)和響應物件(HttpResponse)
2,request屬性
django將請求報文中的請求行,首部資訊,內容主體封裝成 HttpRequest 類中的屬性。除了特殊說明之外,其他的均為只讀。
1.HttpRequest.GET 一個類似於字典的物件,包含 HTTP GET 的所有引數。詳情請參考 QueryDict 物件。 2.HttpRequest.POST 一個類似於字典的物件,如果請求中包含表單資料,則將這些資料封裝成 QueryDict 物件。 POST 請求可以帶有空的 POST 字典 —— 如果通過 HTTP POST 方法傳送一個表單,但是 表單中沒有任何的資料,QueryDict 物件依然會被建立。 因此,不應該使用 if request.POST 來檢查使用的是否是POST 方法; 應該使用 if request.method == "POST" 另外:如果使用 POST 上傳檔案的話,檔案資訊將包含在 FILES 屬性中。 注意:鍵值對的值是多個的時候,比如checkbox型別的input標籤,select標籤,需要用: request.POST.getlist("hobby") 3.HttpRequest.body 一個字串,代表請求報文的主體。在處理非 HTTP 形式的報文時非常有用, 例如:二進位制圖片、XML,Json等。 但是,如果要處理表單資料的時候,推薦還是使用 HttpRequest.POST 。 4.HttpRequest.path 一個字串,表示請求的路徑元件(不含域名)。 例如:"/music/bands/the_beatles/" 5.HttpRequest.method 一個字串,表示請求使用的HTTP 方法。必須使用大寫。 例如:"GET"、"POST" 6.HttpRequest.encoding 一個字串,表示提交的資料的編碼方式(如果為 None 則表示使用 DEFAULT_CHARSET 的設定,預設為 'utf-8')。 這個屬性是可寫的,你可以修改它來修改訪問表單資料使用的編碼。 接下來對屬性的任何訪問(例如從 GET 或 POST 中讀取資料)將使用新的 encoding 值。 如果你知道表單資料的編碼不是 DEFAULT_CHARSET ,則使用它。 7.HttpRequest.META 一個標準的Python 字典,包含所有的HTTP 首部。具體的頭部資訊取決於客戶端 和伺服器,下面是一些示例: CONTENT_LENGTH —— 請求的正文的長度(是一個字串)。 CONTENT_TYPE —— 請求的正文的MIME 型別。 HTTP_ACCEPT —— 響應可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 響應可接收的編碼。 HTTP_ACCEPT_LANGUAGE —— 響應可接收的語言。 HTTP_HOST —— 客服端傳送的HTTP Host 頭部。 HTTP_REFERER —— Referring 頁面。 HTTP_USER_AGENT —— 客戶端的user-agent 字串。 QUERY_STRING —— 單個字串形式的查詢字串(未解析過的形式)。 REMOTE_ADDR —— 客戶端的IP 地址。 REMOTE_HOST —— 客戶端的主機名。 REMOTE_USER —— 伺服器認證後的使用者。 REQUEST_METHOD —— 一個字串,例如"GET" 或"POST"。 SERVER_NAME —— 伺服器的主機名。 SERVER_PORT —— 伺服器的埠(是一個字串)。 從上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,請求中的任何 HTTP 首部轉換為 META 的鍵時,都會將所有字母大寫並將連線符替換為下劃線最後加上 HTTP_ 字首。 所以,一個叫做 X-Bender 的頭部將轉換成 META 中的 HTTP_X_BENDER 鍵。 8.HttpRequest.FILES 一個類似於字典的物件,包含所有的上傳檔案資訊。 FILES 中的每個鍵為<input type="file" name="" /> 中的name,值則為對應的資料。 注意,FILES 只有在請求的方法為POST 且提交的<form> 帶有 enctype="multipart/form-data" 的情況下才會包含資料。否則,FILES 將為一個空的 類似於字典的物件。 9.HttpRequest.COOKIES 一個標準的Python 字典,包含所有的cookie。鍵和值都為字串。 10.HttpRequest.session 一個既可讀又可寫的類似於字典的物件,表示當前的會話。只有當Django 啟用會話的支援時才可用。 完整的細節參見會話的文件。 11.HttpRequest.user(使用者認證元件下使用) 一個 AUTH_USER_MODEL 型別的物件,表示當前登入的使用者。 如果使用者當前沒有登入,user 將設定為 django.contrib.auth.models.AnonymousUser 的一個例項。你可以通過 is_authenticated() 區分它們。 例如: if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users. user 只有當Django 啟用 AuthenticationMiddleware 中介軟體時才可用。 -------------------------------------------------------------------------------------
匿名函式
匿名使用者 class models.AnonymousUser django.contrib.auth.models.AnonymousUser 類實現了 django.contrib.auth.models.User 介面,但具有下面幾個不同點: id 永遠為None。 username 永遠為空字串。 get_username() 永遠返回空字串。 is_staff 和 is_superuser 永遠為False。 is_active 永遠為 False。 groups 和 user_permissions 永遠為空。 is_anonymous() 返回True 而不是False。 is_authenticated() 返回False 而不是True。 set_password()、check_password()、save() 和delete() 引發 NotImplementedError。 New in Django 1.8: 新增 AnonymousUser.get_username() 以更好地模擬 django.contrib.auth.models.User。
3,request常用方法
1.HttpRequest.get_full_path() 返回 path,如果可以將加上查詢字串。 例如:"/music/bands/the_beatles/?print=true" 2.HttpRequest.is_ajax() 如果請求是通過XMLHttpRequest 發起的,則返回True,方法是檢查 HTTP_X_REQUESTED_WITH 相應的首部是否是字串'XMLHttpRequest'。 大部分現代的 JavaScript 庫都會傳送這個頭部。如果你編寫自己的 XMLHttpRequest 呼叫(在瀏覽器端),你必須手工設定這個值來讓 is_ajax() 可以工作。 如果一個響應需要根據請求是否是通過AJAX 發起的,並且你正在使用某種形 式的快取例如Django 的 cache middleware, 你應該使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 裝飾你的檢視 以讓響應能夠正確地快取。
4,HttpResponse物件
響應物件主要有三種形式:
- HttpResponse()
- render()
- redirect()
4.1 HttpResponse()
HttpResponse() 括號內直接跟一個具體的字串作為響應體,比較直接很簡單,所以這裡不多說。
但是需要注意的是,無論是下面的render() 還是 redirect() 最終還是呼叫HttpResponse,只不過在過程中使用Django的模板語法封裝了內容。
我們下面展示一下render() redirect()的原始碼:
下面直接看著兩個響應物件。
4.2 render()
render(request, template_name[, context]) 結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的 HttpResponse 物件。
引數意義:
- request: 用於生成相應的請求物件
- template_name:要使用的模板的完整名稱,可選的引數
- context:新增到模板上下文的一個字典,預設是一個空字典。如果字典中的某個值是可呼叫的,檢視將在渲染模板之前呼叫它。
總之,render方法就是將一個模板頁面中的模板語法進行渲染,最終渲染成一個HTML頁面作為響應體。
舉個例子:
def logout(request): # del request.session['is_login'] request.session.flush() return render(request, 'session/a.html')
4.3 redirect()
傳遞要重定向的一個硬編碼的URL
def my_view(request): ... return redirect('/some/url/')
也可以是一個完整的URL:
def my_view(request): ... return redirect('http://example.com/')
key:兩次請求
1)301和302的區別。 301和302狀態碼都表示重定向,就是說瀏覽器在拿到伺服器返回的這個狀態碼 後會自動跳轉到一個新的URL地址,這個地址可以從響應的Location首部中獲取 (使用者看到的效果就是他輸入的地址A瞬間變成了另一個地址B)——這是它們的共同點。 他們的不同在於。301表示舊地址A的資源已經被永久地移除了(這個資源不可訪問了), 搜尋引擎在抓取新內容的同時也將舊的網址交換為重定向之後的網址; 302表示舊地址A的資源還在(仍然可以訪問),這個重定向只是臨時地從舊地址A跳轉 到地址B,搜尋引擎會抓取新的內容而儲存舊的網址。 SEO302好於301 2)重定向原因: (1)網站調整(如改變網頁目錄結構); (2)網頁被移到一個新地址; (3)網頁副檔名改變(如應用需要把.php改成.Html或.shtml)。 這種情況下,如果不做重定向,則使用者收藏夾或搜尋引擎資料庫中舊地址只能讓訪問 客戶得到一個404頁面錯誤資訊,訪問流量白白喪失;再者某些註冊了多個域名的 網站, 也需要通過重定向讓訪問這些域名的使用者自動跳轉到主站點等。
舉個例子:
def order(request): if not request.user.is_authenticated: return redirect('auth/login/') return render(request, 'auth/order.html')
ORM中QuerySet API的學習
Django ORM用到三個類:Manager,QuerySet,Model。
Manager定義表級方法(表級方法就是影響一條或多條記錄的方法),我們可以以model.Manager為父類,定義自己的manager,增加表級方法;
QuerySet:Manager類的一些方法會返回QuerySet例項,QuerySet是一個可遍歷結構,包含一個或多個元素,每個元素都是一個Model例項,它裡面的方法也是表級方法;
Model是指django.db.models模組中的Model類,我們定義表的model時,就是繼承它,它的功能很強大,通過自定義model的instance可以獲取外來鍵實體等,它的方法都是基類級方法,不要在裡面定義類方法,比如計算記錄的總數,檢視所有記錄,這些應該放在自定義的manager類中。
1,QuerySet簡介
每個Model都有一個預設的manager例項,名為objects,QuerySet有兩種來源:通過manager的方法得到,通過QuerySet的方法得到。manager的方法和QuerySet的方法大部分同名,同意思,如 filter(),update()等,但是也有些不同,如 manager有 create(),get_or_create(),而QuerySet有delete() 等,看原始碼就可以很容易的清楚Manager類和QuerySet類的關係,Manager類的絕大部分方法是基於QuerySet的。一個QuerySet包含一個或多個model instance。QuerySet類似於Python中的list,list的一些方法QuerySet也有,比如切片,遍歷。
注意:object和QuerySet的區別!!
QuerySet是查詢集,就是傳到伺服器上的url裡面的內容,Django會對查詢返回的結果集QuerySet進行快取,這是為了提高查詢效率。也就是說,在建立一個QuerySet物件的時候,Django不會立即向資料庫發出查詢命令,只有在你需要用到這個QuerySet的時候才會去資料庫查詢。
object是Django實現的MCV框架中的資料層(model)M,django中的模型類都有一個object物件,他是django中定義的QuerySet型別的物件,他包含了模型物件的例項。
簡單來說,object是單個物件,QuerySet是多個物件。
1.1 QuerySet 何時被提交
在內部,建立,過濾,切片和傳遞一個QuerySet不會真實運算元據庫,在你對查詢集提交之前,不會發生任何實際的資料庫操作。
可以使用下列方法對QuerySet提交查詢操作。
-
迭代
QuerySet是可迭代的,在首次迭代查詢集中執行的實際資料庫查詢。例如,下面的語句會將資料庫中所有entry的headline列印出來。
for e in Entry.objects.all(): print(e.headline)
-
切片:如果使用切片的”step“引數,Django 將執行資料庫查詢並返回一個列表。
-
Pickling/快取
-
repr()
-
len():當你對QuerySet呼叫len()時, 將提交資料庫操作。
-
list():對QuerySet呼叫list()將強制提交操作
entry_list = list(Entry.objects.all())
-
bool()
測試布林值,像這樣:
if Entry.objects.filter(headline="Test"): print("There is at least one Entry with the headline Test")
注:如果你需要知道是否存在至少一條記錄(而不需要真實的物件),使用exists() 將更加高效。
1.2 QuerySet的定義
下面對QuerySet正式定義:
class QuerySet(model=None, query=None, using=None)[source]
- QuerySet類具有兩個公有屬性用於內省:
- ordered:如果QuerySet是排好序的則為True,否則為False。
- db:如果現在執行,則返回使用的資料庫。
1.3 惰性機制
所謂惰性機制:Publisher.objects.all() 或者.filter()等都只是返回了一個QuerySet(查詢結果集物件),它並不會馬上執行sql,而是當呼叫QuerySet的時候才執行。
QuerySet特點:
- 1,可迭代的
- 2,可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一個行物件 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) # print(objs[1:4]) # print(objs[::-1])
1.4 QuerySet的高效使用
<1>Django的queryset是惰性的 Django的queryset對應於資料庫的若干記錄(row),通過可選的查詢來過濾。 例如,下面的程式碼會得到資料庫中名字為‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave")上面的程式碼並沒有執行任何的資料庫查詢。 你可以使用person_set,給它加上一些過濾條件,或者將它傳給某個函式,這些操作都不 會傳送給資料庫。這是對的,因為資料庫查詢是顯著影響web應用效能的因素之一。 <2>要真正從資料庫獲得資料,你可以遍歷queryset或者使用if queryset,總之你用到 資料時就會執行sql.為了驗證這些,需要在settings里加入 LOGGING(驗證方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 當你遍歷queryset時,所有匹配的記錄會從資料庫獲取,然後轉換成Django的model。 這被稱為執行(evaluation).這些model會儲存在queryset內建的cache中,這樣如果你 再次遍歷這個queryset, 你不需要重複執行通用的查詢。 obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) ## models.Book.objects.filter(id=3).update(title="GO") ## obj_new=models.Book.objects.filter(id=3) # for i in obj: # print(i) #LOGGING只會列印一次 <4> 簡單的使用if語句進行判斷也會完全執行整個queryset並且把資料放入cache,雖然你並 不需要這些資料!為了避免這個,可以用exists()方法來檢查是否有資料: obj = Book.objects.filter(id=4) # exists()的檢查可以避免資料放入queryset的cache。 if obj.exists(): print("hello world!") <5>當queryset非常巨大時,cache會成為問題 處理成千上萬的記錄時,將它們一次裝入記憶體是很浪費的。更糟糕的是,巨大的queryset 可能會鎖住系統 程式,讓你的程式瀕臨崩潰。要避免在遍歷資料的同時產生queryset cache, 可以使用iterator()方法 來獲取資料,處理完資料就將其丟棄。 objs = Book.objects.all().iterator() # iterator()可以一次只從資料庫獲取少量資料,這樣可以節省記憶體 for obj in objs: print(obj.name) #BUT,再次遍歷沒有列印,因為迭代器已經在上一次遍歷(next)到最後一次了,沒得遍歷了 for obj in objs: print(obj.name) #當然,使用iterator()方法來防止生成cache,意味著遍歷同一個queryset時會重複執行 查詢。所以使用iterator()的時候要當心,確保你的程式碼在操作一個大的queryset時沒有重複執行查詢 總結: queryset的cache是用於減少程式對資料庫的查詢,在通常的使用下會保證只有在需要的時候 才會查詢資料庫。使用exists()和iterator()方法可以優化程式對記憶體的使用。不過,由於它們並 不會生成queryset cache,可能會造成額外的資料庫查詢。
2,Django中不返回QuerySet的API
以下的方法不會返回QuerySets,但是作用非常強大,尤其是粗體顯示的方法,需要背下來。
1,get()
get(**kwargs)
返回按照查詢引數匹配到的單個物件,引數的格式應該符合Field lookups的要求。
如果匹配到的物件個數不止一個的話,觸發MultipleObjectsReturned異常
如果根據給出的引數匹配不到物件的話,觸發DoesNotExist異常。例如:
Entry.objects.get(id='foo') # raises Entry.DoesNotExist
DoesNotExist異常從django.core.exceptions.ObjectDoesNotExist 繼承,可以定位多個DoesNotExist異常,例如:
from django.core.exceptions import ObjectDoesNotExist try: e = Entry.objects.get(id=3) b = Blog.objects.get(id=1) except ObjectDoesNotExist: print("Either the entry or blog doesn't exist.")
如果希望查詢器只返回一行,則可以使用get() 而不使用任何引數來返回該行的物件:
entry = Entry.objects.filter(...).exclude(...).get()
2,create()
create(**kwargs)
在一步操作中同時建立並且儲存物件的便捷方法:
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
等於:
p = Person(first_name="Bruce", last_name="Springsteen") p.save(force_insert=True)
引數force_insert表示強制建立物件。如果model中有一個你手動設定的主鍵,並且這個值已經存在了資料庫中,呼叫create()將會失敗,並且觸發IntegrityError因為主鍵必須是唯一的。如果你手動設定了主鍵,做好異常處理的準備。
3,get_or_create()
get_or_create(defaults=None, **kwargs)
通過kwargs來查詢物件的便捷方法(如果模型中的所有欄位都有預設值,可以為空),如果該物件不存在則建立一個新物件。
該方法返回一個由(object,created)組成的元組,元組中的object是一個查詢到的或者是被建立的物件,created是一個表示是否建立了新的物件的布林值。
對於下面的程式碼:
try: obj = Person.objects.get(first_name='John', last_name='Lennon') except Person.DoesNotExist: obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) obj.save()
如果模型的欄位數量較大的話,這種模式就變的非常不易使用了。上面的示例可以用get_or_create()重寫:
obj, created = Person.objects.get_or_create( first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}, )
任何傳遞給get_or_create()的關鍵字引數,除了一個可選的defaults,都將傳遞給get()呼叫,如果查到一個物件,返回一個包含匹配到的物件以及False組成的元組。如果查到的物件超過一個以上,將引發MultipleObjectsReturned。如果查詢不到物件,get_or_create() 將會例項化並儲存一個新的物件,返回一個由新的物件以及True組成的元組。新的物件將會按照以下的邏輯建立:
params = {k: v for k, v in kwargs.items() if '__' not in k} params.update({k: v() if callable(v) else v for k, v in defaults.items()}) obj = self.model(**params) obj.save()
它表示從非'defaults' 且不包含雙下劃線的關鍵字引數開始。然後將defaults的內容新增進來,覆蓋必要的鍵,並使用結果作為關鍵字引數傳遞給模型類。
如果有一個名為defaults_exact 的欄位,並且想在 get_or_create() 時用它作為精確查詢,只需要使用defaults,像這樣:
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
當你使用手動指定的主鍵時,get_or_create()方法與create()方法有相似的錯誤行為。如果需要建立一個物件而該物件的主鍵早已存在於資料庫中,IntergrityError異常將會被觸發。
這個方法假設進行的是原子操作,並且正確地配置了資料庫和正確的底層資料庫行為。如果資料庫級別沒有對get_or_create
中用到的kwargs強制要求唯一性(unique和unique_together),方法容易導致競態條件,可能會有相同引數的多行同時插入。(簡單理解,kwargs必須指定的是主鍵或者unique屬性的欄位才安全。)
最後建議只在Django檢視的POST請求中使用get_or_create(),因為這是一個具有修改性質的動作,不應該使用在GET請求中,那樣不安全。
可以通過ManyToManyField屬性和反向關聯使用get_or_create()
。在這種情況下,應該限制查詢在關聯的上下文內部。 否則,可能導致完整性問題。
例如下面的模型:
class Chapter(models.Model): title = models.CharField(max_length=255, unique=True) class Book(models.Model): title = models.CharField(max_length=256) chapters = models.ManyToManyField(Chapter)
可以通過Book的chapters欄位使用get_or_create(),但是它只會獲取該Book內部的上下文:
>>> book = Book.objects.create(title="Ulysses") >>> book.chapters.get_or_create(title="Telemachus") (<Chapter: Telemachus>, True) >>> book.chapters.get_or_create(title="Telemachus") (<Chapter: Telemachus>, False) >>> Chapter.objects.create(title="Chapter 1") <Chapter: Chapter 1> >>> book.chapters.get_or_create(title="Chapter 1") # Raises IntegrityError
發生這個錯誤是因為嘗試通過Book “Ulysses”獲取或者建立“Chapter 1”,但是它不能,因為它與這個book不關聯,但因為title 欄位是唯一的它仍然不能建立。
在Django1.11在defaults中增加了對可呼叫值的支援。
4,update_or_create()
update_or_create(defaults=None, **kwargs)
類似於上面的 get_or_create()
通過給出的kwargs來更新物件的便捷方法, 如果沒找到物件,則建立一個新的物件。defaults是一個由 (field, value)對組成的字典,用於更新物件。defaults中的值可以是可呼叫物件(也就是說函式等)。
該方法返回一個由(object, created)組成的元組,元組中的object是一個建立的或者是被更新的物件, created是一個標示是否建立了新的物件的布林值。
update_or_create
方法嘗試通過給出的kwargs 去從資料庫中獲取匹配的物件。 如果找到匹配的物件,它將會依據defaults 字典給出的值更新欄位。
像下面的程式碼:
defaults = {'first_name': 'Bob'} try: obj = Person.objects.get(first_name='John', last_name='Lennon') for key, value in defaults.items(): setattr(obj, key, value) obj.save() except Person.DoesNotExist: new_values = {'first_name': 'John', 'last_name': 'Lennon'} new_values.update(defaults) obj = Person(**new_values) obj.save()
如果模型的欄位數量較大的話,這種模式就變的非常不易用了。上面的示例可以用update_or_create()重寫:
obj, created = Person.objects.update_or_create( first_name='John', last_name='Lennon', defaults={'first_name': 'Bob'}, )
和get_or_create()
一樣,這個方法也容易導致競態條件,如果資料庫層級沒有前置唯一性會讓多行同時插入。
在Django1.11在defaults中增加了對可呼叫值的支援。
5,bulk.create()
bulk_create(objs , batch_size = None)
以高效的方式(通常只有一個查詢,無論有多少物件)將提供的物件列表插入到資料庫中:
>>> Entry.objects.bulk_create([ ... Entry(headline='This is a test'), ... Entry(headline='This is only a test'), ... ])
注意事項:
- 不會呼叫模型的save()方法,並且不會傳送
pre_save
和post_save
訊號。 - 不適用於多表繼承場景中的子模型。
- 如果模型的主鍵是AutoField,則不會像save()那樣檢索並設定主鍵屬性,除非資料庫後端支援。
- 不適用於多對多關係。
batch_size引數控制在單個查詢中建立的物件數。
6,count()
count()
返回在資料庫中對應的QuerySet物件的個數,count()永遠不會引發異常。
例如:
# 返回總個數. Entry.objects.count() # 返回包含有'Lennon'的物件的總數 Entry.objects.filter(headline__contains='Lennon').count()
7,in_bulk()
in_bulk(id_list = None)
獲取主鍵值的列表,並返回將每個主鍵值對映到具有給定ID的物件的例項的字典。 如果未提供列表,則會返回查詢集中的所有物件。
例如:
>>> Blog.objects.in_bulk([1]) {1: <Blog: Beatles Blog>} >>> Blog.objects.in_bulk([1, 2]) {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>} >>> Blog.objects.in_bulk([]) {} >>> Blog.objects.in_bulk() {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
如果向in_bulk()傳遞一個空列表,會得到一個空的字典。
在舊版本中,id_list是必須的引數,現在是一個可選引數。
8,iterator()
iterator()
提交資料庫操作,獲取QuerySet,並返回一個迭代器。
Q uerySet通常會在內部快取其結果,以便在重複計算時不會導致額外的查詢。而iterator()將直接讀取結果,不在QuerySet級別執行任何快取。對於返回大量只需要訪問一次的物件的QuerySet,這可以帶來更好的效能,顯著減少記憶體使用。
請注意,在已經提交了的iterator()上使用QuerySet會強制它再次提交資料庫操作,進行重複查詢。此外,使用iterator()會導致先前的prefetch_related()
呼叫被忽略,因為這兩個一起優化沒有意義。
9,latest()
latest(field_name=None)
使用日期欄位field_name,按日期返回最新物件。
下例根據Entry的'pub_date'欄位返回最新發布的entry:
Entry.objects.latest('pub_date')
如果模型的Meta指定了get_latest_by
,則可以將latest()引數留給earliest()或者field_name
。 預設情況下,Django將使用get_latest_by
中指定的欄位。
earliest()和latest()可能會返回空日期的例項,可能需要過濾掉空值:
Entry.objects.filter(pub_date__isnull=False).latest('pub_date')
10,earliest()
earliest(field_name = None)
類同latest()
11,first()
first()
返回結果集的第一個物件,當沒有找到時候,返回None,如果QuerySet沒有設定排序,則將自動按主鍵進行排序。例如:
p = Article.objects.order_by('title', 'pub_date').first()
first()是一個簡便方法,下面的例子和上面的程式碼效果是一樣:
try: p = Article.objects.order_by('title', 'pub_date')[0] except IndexError: p = None
12,last()
last()
工作方式類似於first() ,只是返回的是查詢集中最後一個物件。
13,aggregate()
aggregate(*args, **kwargs)
返回彙總值的字典(平均值,總和等),通過QuerySet進行計算。每個引數指定返回的字典中將要包含的值。
使用關鍵字引數指定的聚合將使用關鍵字引數的名稱作為Annotation 的名稱。 匿名引數的名稱將基於聚合函式的名稱和模型欄位生成。 複雜的聚合不可以使用匿名引數,必須指定一個關鍵字引數作為別名。
例如,想知道Blog Entry 的數目:
>>> from django.db.models import Count >>> q = Blog.objects.aggregate(Count('entry')) {'entry__count': 16}
通過使用關鍵字引數來指定聚合函式,可以控制返回的聚合的值的名稱:
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) {'number_of_entries': 16}
14,exists()
exists()
如果QuerySet包含任何結果,則返回True,否則返回False。
查詢具有唯一性欄位(例如primary_key)的模型是否在一個QuerySet中的最高效的方法是:
entry = Entry.objects.get(pk=123) if some_queryset.filter(pk=entry.pk).exists(): print("Entry contained in queryset")
它將比下面的方法快很多,這個方法要求對QuerySet求值並迭代整個QuerySet:
if entry in some_queryset: print("Entry contained in QuerySet")
若要查詢一個QuerySet是否包含任何元素:
if some_queryset.exists(): print("There is at least one object in some_queryset")
將快於:
if some_queryset: print("There is at least one object in some_queryset")
15,update()
update(**kwargs)
對指定的欄位執行批量更新操作,並返回匹配的行數(如果某些行已具有新值,則可能不等於已更新的行數)。
例如,要對2010年釋出的所有部落格條目啟用評論,可以執行以下操作:
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
可以同時更新多個欄位 (沒有多少欄位的限制)。 例如同時更新comments_on和headline欄位:
>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')
update()方法無需save操作。唯一限制是它只能更新模型主表中的列,而不是關聯的模型,例如不能這樣做:
>>> Entry.objects.update(blog__name='foo') # Won't work!
仍然可以根據相關欄位進行過濾:
>>> Entry.objects.filter(blog__id=1).update(comments_on=True)
update()方法返回受影響的行數:
>>> Entry.objects.filter(id=64).update(comments_on=True) 1 >>> Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True) 0 >>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False) 132
如果你只是更新一下物件,不需要為物件做別的事情,最有效的方法是呼叫update(),而不是將模型物件載入到記憶體中。 例如,不要這樣做:
e = Entry.objects.get(id=10) e.comments_on = False e.save()
建議如下操作:
Entry.objects.filter(id=10).update(comments_on=False)
用update()還可以防止在載入物件和呼叫save()之間的短時間內資料庫中某些內容可能發生更改的競爭條件。
如果想更新一個具有自定義save()方法的模型的記錄,請迴圈遍歷它們並呼叫save(),如下所示:
for e in Entry.objects.filter(pub_date__year=2010): e.comments_on = False e.save()
16,delete()
delete()
批量刪除QuerySet中的所有物件,並返回刪除的物件個數和每個物件型別的刪除次數的字典。
elete()動作是立即執行的。
不能在QuerySet上呼叫delete()。
例如,要刪除特定部落格中的所有條目:
>>> b = Blog.objects.get(pk=1) # Delete all the entries belonging to this Blog. >>> Entry.objects.filter(blog=b).delete() (4, {'weblog.Entry': 2, 'weblog.Entry_authors': 2})
預設情況下,Django的ForeignKey使用SQL約束ON DELETE CASCADE,任何具有指向要刪除的物件的外來鍵的物件將與它們一起被刪除。 像這樣:
>>> blogs = Blog.objects.all() # This will delete all Blogs and all of their Entry objects. >>> blogs.delete() (5, {'weblog.Blog': 1, 'weblog.Entry': 2, 'weblog.Entry_authors': 2})
這種級聯的行為可以通過的ForeignKey的on_delete引數自定義。(什麼時候要改變這種行為呢?比如日誌資料,就不能和它關聯的主體一併被刪除!)
delete()會為所有已刪除的物件(包括級聯刪除)發出pre_delete
和post_delete
訊號。
17,as_manager()
classmethod as_manager()
一個類方法,返回Manager的例項與QuerySet的方法的副本。
詳情參考django官方文件:https://django-chinese-docs-14.readthedocs.io/en/latest/ref/models/options.html
https://www.cnblogs.com/yuanchenqi/articles/8876856.html
https://www.cnblogs.com/feixuelove1009/p/8425054.html
(這裡主要是做了自己的學習筆記,用來記錄於此)