用戶認證組件的學習
用戶認證是通過取表單數據根數據庫對應表存儲的值做比對,比對成功就返回一個頁面,不成功就重定向到登錄頁面。我們自己寫的話當然也是可以的,只不過多寫了幾個視圖,冗余代碼多,當然我們也可以封裝成函數,簡單代碼。不過推薦使用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})
2.7 Django自定義 auth_user
1,導入AbstractUser
from django.contrib.auth.models import AbstractUser
2,創建類Userprofile並繼承 AbstractUser
class UserProfile(AbstractUser)
3,創建自己需要的UserProfile字段
class UserProfile(AbstractUser): nick_name = models.CharField(max_length=50, verbose_name=u'昵稱', default='') birthday = models.DateField(verbose_name=u'生日', null=True, blank=True) gender = models.CharField(choices=(('male', u'男'), ('female', u'女')), default='male', verbose_name=u'性別') address = models.CharField(max_length=100, verbose_name=u'地址')
4,在settings.py中重載 AUTH_USER_MODEL 方法
AUTH_USER_MODEL = 'users.UserProfile'
5,注意:如果在migrate時候報錯,刪除數據庫的表,重新遷移。
報錯內容如下:
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency users.0001_initial on database 'default'.
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常用方法
HTTP的應用信息是通過請求報文和響應報文傳遞的。
其中請求報文由客戶端發送,其中包含和許多的信息,而Django將這些信息封裝成了HTTPRequest對象,該對象由HTTPRequest類創建,每一個請求都會生成一個HttpRequest對象,Django會將這個對象自動傳遞給響應的視圖函數,一般視圖函數約定俗成使用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') 裝飾你的視圖 以讓響應能夠正確地緩存。
補充:HttpRequest.path_info
一個字符串,在某些Web服務器配置下,主機名后的URL部分被分成腳本前綴部分和路徑信息部分。path_info屬性將始終包含路徑信息部分,無論使用的什么Web服務器,使用它代替path 可以讓代碼在測試和開發環境中更容易的切換。
例如,如果應用的WSGIScriptAlias 設置為"/minfo",那么當 path 是"/minfo/music/bands/the_beatles/" 時path_info 將是"/music/bands/the_beatles/"。
4,HttpResponse對象
響應對象主要有三種形式:
- HttpResponse()
- render()
- redirect()
4.1 HttpResponse()
對於HttpRequest對象來說,是由Django自動創建的,但是HttpResponse對象就必須由我們自己創建,每個view請求處理方法必須返回一個HttpRequest對象。
但是需要注意的是,無論是下面的render() 還是 redirect() 最終還是調用HttpResponse,只不過在過程中使用Django的模板語法封裝了內容。
我們下面展示一下render() redirect()的源碼:
下面直接看着兩個響應對象。
4.2 render()
在實際運用中,加載模板,傳遞參數,返回HttpResponse對象是一整套再長不過的操作了,為了節省力氣,Django提供了一個快捷方式:render()函數,一步到位。
render(request, template_name[, context]) 結合一個給定的模板和一個給定的上下文字典,並返回一個渲染后的 HttpResponse 對象。
參數意義:
- request: 用於生成相應的請求對象
- template_name:要使用的模板的完整名稱,可選的參數
- context:添加到模板上下文的一個字典,默認是一個空字典。如果字典中的某個值是可調用的,視圖將在渲染模板之前調用它。
總之,render方法就是將一個模板頁面中的模板語法進行渲染,最終渲染成一個HTML頁面作為響應體。render() 函數第一個位置參數就是請求對象(就是view函數的第一個參數),第二個位置參數是模板,還可以有一個可選擇的第三個參數,一個字典,包含需要傳遞給模板的數據,最后render函數返回一個經過字典數據渲染過的模板封裝而成的HttpResponse對象。
舉個例子:
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')
4.4 render()和 redirect() 的區別
1,if render 的頁面需要模板語言渲染,需要的將數據庫的數據加載到HTML,那么所有的這一部分除了寫在視圖函數中,必須還要寫在login中,沒有解耦。
2,render()是渲染變量到模板中,而redirect是HTTP中的1個跳轉的函數,一般會生成302 狀態碼。
4.5 返回404 錯誤
下面我們編寫一個具體問卷文本內容的視圖,如果訪問的話,請求問卷的ID不存在,那么會彈出一個Http404 錯誤。
from django.http import Http404 from django.shortcuts import render from .models import Question # ... def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") return render(request, 'polls/detail.html', {'question': question})
當然,就像render函數一樣,Django同樣為我們提供一個偷懶的方式,替代上面的多行代碼。
快捷方式 : get_object_or_404()
修改代碼入下:
from django.shortcuts import get_object_or_404, render from .models import Question # ... def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})
首先,get_object_or_404()方法也是需要從Django內置的快捷方式模塊中導出。
其次,get_object_or_404()方法將一個Django模型作為第一個位置參數,后面可以跟上任意個數的關鍵字參數,如果對象不存在則彈出Http404錯誤。
同樣,還有一個 get_list_or_404() 方法,和上面的 get_object_or_404() 類似,只不過用來替代 filter() 函數,當查詢列表為空時彈出404錯誤。
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
(這里主要是做了自己的學習筆記,用來記錄於此)