一.Django中的視圖函數views
一個視圖函數(類),簡稱視圖,是一個簡單的Python 函數(類),它接受Web請求並且返回Web響應。
響應可以是一張網頁的HTML內容,一個重定向,一個404錯誤,一個XML文檔,或者一張圖片。
無論視圖本身包含什麼邏輯,都要返回響應。代碼寫在哪裏也無所謂,只要它在你當前項目目錄下面。除此之外沒有更多的要求了——可以說“沒有什麼神奇的地方”。爲了將代碼放在某處,大家約定成俗將視圖放置在項目(project)或應用程序(app)目錄中的名爲views.py的文件中。
一個簡單的視圖函數
from django.http import HttpResponse
#從shortcuts 裏面導入HttpResponse 也可以 http 和shortcuts 都是繼承於HttpResponseBase
from django.shortcuts import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
讓我們來逐行解釋下上面的代碼:
首先,我們從 django.http模塊導入了HttpResponse類,以及Python的datetime庫。
接着,我們定義了current_datetime函數。它就是視圖函數。每個視圖函數都使用HttpRequest對象作爲第一個參數,並且通常稱之爲request。
注意,視圖函數的名稱並不重要;不需要用一個統一的命名方式來命名,以便讓Django識別它。我們將其命名爲current_datetime,是因爲這個名稱能夠比較準確地反映出它實現的功能。
這個視圖會返回一個HttpResponse對象,其中包含生成的響應。每個視圖函數都負責返回一個HttpResponse對象。
Django使用請求和響應對象來通過系統傳遞狀態。
當瀏覽器向服務端請求一個頁面時,Django創建一個HttpRequest對象,該對象包含關於請求的元數據。然後,Django加載相應的視圖,將這個HttpRequest對象作爲第一個參數傳遞給視圖函數。
每個視圖負責返回一個HttpResponse對象。
視圖層,熟練掌握兩個對象即可:請求對象(request)和響應對象(HttpResponse)
二.request對象的方法和屬性response的三個方法
官方文檔
1.request請求相關的屬性和方法:
示例:
def index(request): #http請求相關信息 封裝的HttpRequest對象
#比較常用的
if request.method == 'GET':
print(request.GET) # 包含所有HTTP GET參數的類字典對象
print(request.META) #請求頭相關內容
print(request.path) #一個字符串,表示請求的路徑組件(不含域名)。例如:"/music/bands/the_beatles//app02/index/"
print(request.path_info) #返回用戶訪問url,不包括域名 /app02/index/
print(request.get_full_path()) #/app02/index/?dazhuang=123 除了域名和端口 其他的都要
print(request.body) # 請求體,byte類型 request.POST的數據就是從body裏面提取到的 原始數據
return render(request, 'index.html')
else:
print(request.body) # 請求體,byte類型 request.POST的數據就是從body裏面提取到的 原始數b'username=avdsv'
print(request.method) #請求中使用的HTTP方法的字符串表示,全大寫表示。
print(request.POST) #包含所有HTTP POST參數的類字典對象
return HttpResponse('ok')
屬性:
django將請求報文中的請求行、頭部信息、內容主體封裝成 HttpRequest 類中的屬性。
除了特殊說明的之外,其他均爲只讀的。
0.HttpRequest.scheme(後面再學)
表示請求方案的字符串(通常爲http或https)
1.HttpRequest.body(後面再學)
一個字符串,代表請求報文的主體。在處理非 HTTP 形式的報文時非常有用,例如:二進制圖片、XML,Json等。
但是,如果要處理表單數據的時候,推薦還是使用 HttpRequest.POST ,因爲POST中的數據從 request.body裏獲取數據 然後再通過json.loads(request.body)組成一個QueryDict對象。
另外,我們還可以用 python 的類文件方法去操作它,詳情參考 HttpRequest.read() 。
2.HttpRequest.path
一個字符串,表示請求的路徑組件(不含域名)。
例如:"/music/bands/the_beatles/"
3.HttpRequest.method
一個字符串,表示請求使用的HTTP 方法。必須使用大寫。
例如:“GET”、“POST”
4.HttpRequest.encoding
一個字符串,表示提交的數據的編碼方式(如果爲 None 則表示使用 DEFAULT_CHARSET 的設置,默認爲 ‘utf-8’)。
這個屬性是可寫的,你可以修改它來修改訪問表單數據使用的編碼。
接下來對屬性的任何訪問(例如從 GET 或 POST 中讀取數據)將使用新的 encoding 值。
如果你知道表單數據的編碼不是 DEFAULT_CHARSET ,則使用它。
5.HttpRequest.GET
一個類似於字典的對象,包含 HTTP GET 的所有參數。詳情請參考 QueryDict 對象。
6.HttpRequest.POST
一個類似於字典的對象,如果請求中包含表單數據,則將這些數據封裝成 QueryDict 對象。
POST 請求可以帶有空的 POST 字典 —— 如果通過 HTTP POST 方法發送一個表單,但是表單中沒有任何的數據,QueryDict 對象依然會被創建。
因此,不應該使用 if request.POST 來檢查使用的是否是POST 方法;應該使用 if request.method == “POST”
另外:如果使用 POST 上傳文件的話,文件信息將包含在 FILES 屬性中。
7.HttpRequest.COOKIES
一個標準的Python 字典,包含所有的cookie。鍵和值都爲字符串。
8.HttpRequest.FILES
一個類似於字典的對象,包含所有的上傳文件信息。
FILES 中的每個鍵爲 中的name,值則爲對應的數據。
注意,FILES 只有在請求的方法爲POST 且提交的 帶有enctype=“multipart/form-data” 的情況下才會
包含數據。否則,FILES 將爲一個空的類似於字典的對象。
9.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 鍵。
10.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。
11.HttpRequest.session
一個既可讀又可寫的類似於字典的對象,表示當前的會話。只有當Django 啓用會話的支持時纔可用。
完整的細節參見會話的文檔。
2.response響應相關的方法:
與由Django自動創建的HttpRequest對象相比,HttpResponse對象是我們的職責範圍了。我們寫的每個視圖都需要實例化,填充和返回一個HttpResponse。
def response(request):
HttpResponse ----回覆字符串的時候來用
HttpResponse.content:響應內容
HttpResponse.charset:響應內容的編碼
HttpResponse.status_code:響應的狀態碼
render -------回覆一個html頁面的時候使用
參數:
request: 用於生成響應的請求對象。
template_name:要使用的模板的完整名稱,可選的參數
context:添加到模板上下文的一個字典。默認是一個空字典。如果字典中的某個值是可調用的,視圖將在渲染模板之前調用它。
content_type:生成的文檔要使用的MIME類型。默認爲 DEFAULT_CONTENT_TYPE 設置的值。默認爲'text/html'
status:響應的狀態碼。默認爲200。
useing: 用於加載模板的模板引擎的名稱。
redirect ----重定向給瀏覽器了一個30x的狀態碼
參數可以是:
1. 一個模型:將調用模型的get_absolute_url() 函數
2.一個視圖,可以帶有參數:將使用urlresolvers.reverse 來反向解析名稱
3.一個絕對的或相對的URL,將原封不動的作爲重定向的位置。
默認返回一個臨時的重定向;傳遞permanent=True 可以返回一個永久的重定向。
示例:
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'test' and password == 'test123456':
# return render(request, 'home.html')
return redirect('app02:home')
else:
return HttpResponse('輸入錯誤!!!')
#首頁
def home(request):
return render(request,'home.html')
實際上response中的redirect和render最後走的還是HttpResponse,來看下源碼:
redirect源碼分析:
if kwargs.pop('permanent', False):
redirect_class = HttpResponsePermanentRedirect #這裏是調用我們的HTTPResponse相關類
else:
redirect_class = HttpResponseRedirect
return redirect_class(resolve_url(to, *args, **kwargs)) #將我們的app02:home傳遞進去
我們再看HttpResponsePermanentRedirect 和HttpResponseRedirect有沒有父類
這裏可以看到這兩個類一個是臨時重定向(302)和永久重定向(301),並且同時都是繼承自HttpResponseRedirectBase這個類,我們看HttpResponseRedirectBase這個類繼承自誰:
這裏就可以看到了,最後還是繼承自我們的HttpResponse這個類。
render源碼分析
最後調用的還是我們HttpResponse。
3. 301和302
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頁面錯誤信息,訪問流量白白喪失;再者某些註冊了多個域名的
網站,也需要通過重定向讓訪問這些域名的用戶自動跳轉到主站點等。
臨時重定向(響應狀態碼:302)和永久重定向(響應狀態碼:301)對普通用戶來說是沒什麼區別的,它主要面向的是搜索引擎的機器人。
A頁面臨時重定向到B頁面,那搜索引擎收錄的就是A頁面。
A頁面永久重定向到B頁面,那搜索引擎收錄的就是B頁面。
三.CBV和FBV兩種視圖邏輯的開發模式
FBV(function base views) 就是在視圖裏使用函數處理請求。
之前都是FBV模式寫的代碼,所以就不寫例子了。
CBV(class base views) 就是在視圖裏使用類處理請求。
Python是一個面向對象的編程語言,如果只用函數來開發,有很多面向對象的優點就錯失了(繼承、封裝、多態)。所以Django在後來加入了Class-Based-View。可以讓我們用類寫View。這樣做的優點主要下面兩種:
1.提高了代碼的複用性,可以使用面嚮對象的技術,比如Mixin(多繼承)
2.可以用不同的函數針對不同的HTTP方法處理,而不是通過很多if判斷,提高代碼可讀性
示例:
views.py中寫法:
# CBV
from django.views import View
class LoginView(View):
def get(self,request): #方法名必須要是get
return render(request,'login2.html')
def post(self,request):
username = request.POST.get('username')
password = request.POST.get('password')
print(username)
print(password)
return HttpResponse('ok')
urls.py中寫法:
urlpatterns = [
# CBV
url(r'^login2/', views.LoginView.as_view(),name='login2'),
]
考慮一下,爲什麼get請求和post請求就能找到get方法和post方法,請求過程時什麼樣的,這裏給大家分析一下:
上面我們urls.py文件中配置的路徑是:url(r’^login2/’, views.LoginView.as_view(),name=‘login2’),這裏有個as_view()方法,意思是一個請求過來是要先執行這個as_view()方法,我們之前寫的那種是先請求然後內部再調用,而as_view()在調用之前已經是執行了,所以一個請求過來肯定會執行as_view()裏面的邏輯,我們寫的類裏面並沒有寫as_view()這個方法,肯定父類裏面有這個方法,現在我們來看一as_view()這個方法:
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs) #實例化一個LoginView視圖類的對象
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
源碼裏面可以看到是有as_view()這個方法的,然後給我們的視圖類LoginView實例化一個對象self,最後又調用了一個dispatch這個方法,我們來看一下dispatch這個方法:
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
#反射
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
我們看一下dispatch這個方法幹了哪些事情,先判斷你的請求方法在不在http_method_names這個裏面,看一下http_method_names這裏裏面都是什麼:
裏面定義的都是我們常用的請求方法,如果在這個裏面就執行下面的這行代碼,handler = getattr(self, request.method.lower(), self.http_method_not_allowed) ,這行代碼什麼意思,利用反射去獲取我們這個對象裏面的方法,如果有就返回去供as_view這個方法使用,如果沒有就執行self.http_method_not_allowed這裏面的內容,我們看一下這個裏面邏輯:
關鍵點:
可以看到是一個日誌,warning級別的日誌,提示你不允許的方法,說明是請求錯誤,我們這裏將我們視圖類中的方法名故意寫錯試試看:
這裏是將get和post都改成get1和post1:
瀏覽器中我們可以看到是請求不成功的,並且給我們返回一個405的狀態碼,和我們self.http_method_not_allowed中提示的一樣。
重寫dispatch方法
如果我們類裏面定義了dispatch方法,首先會執行我們類裏面的dispatch方法,從上面的分析我們可以看到dispatch會在分發請求之前先執行dispatch這個方法,是不是我們可以在視圖類中重寫這個方法,然後在他前後拓展一些內容,一個請求過來肯定先走dispatch這個方法,在分發之前我做一些事情是不是可以,大家想想是不是這樣:
修改後:
from django.views import View
class LoginView(View):
def dispatch(self, request, *args, **kwargs):
print('請求來了') #拓展內容
ret = super().dispatch(request, *args, **kwargs)
print('請求走了') #拓展內容
return ret
def get(self,request): #方法名必須要是get
print('get方法執行了')
return render(request,'login2.html')
def post(self,request):
username = request.POST.get('username')
password = request.POST.get('password')
print(username)
print(password)
return HttpResponse('ok')
爲什麼重寫dispatch要返回一個值,dispatch最終還是要返回獲取到的方法進行執行:
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
‘’‘
handler的值是不是我們獲取到的方法名,然後加括號是不是執行,這句話就類似於
return render(request,'login2.html')最後又被as_view調用,
因爲as_view裏面實例化了視圖類LoginView,視圖類要用到dispatch這個方法進行請求分發,
所以最後會被as_view所調用,而每個視圖函數都要返回一個HTTPResponse對象,
所以在視圖裏面重寫dispatch方法的時候必須要返回一個值
’‘’
上面代碼執行結果:
如果想給單獨某個視圖函數前後加些內容,可以使用裝飾器:
FBV加裝飾器:
def wrapper(f):
def inner(*args,**kwargs):
print('請求之前')
ret = f(*args,**kwargs)
print('請求之後')
return ret
return inner
@wrapper
def app02(request):
print(request.path_info)
return render(request,'app02.html')
結果:
請求之前
/app02/
請求之後
CBV加裝飾器:
from django.views import View
from django.utils.decorators import method_decorator #先導入裝飾器方法 方法裝飾器
@method_decorator(wrapper,name='get') #方式三:視圖類上面加,name指定給那個請求加裝飾器
class LoginView(View):
# @method_decorator(wrapper) #方式二:給所有請求加
def dispatch(self, request, *args, **kwargs):
# print('請求來了')
ret = super().dispatch(request, *args, **kwargs)
# print('請求走了')
return ret
# @method_decorator(wrapper) #方式一:單獨給get請求加
def get(self,request): #方法名必須要是get
print('get方法執行了')
return render(request,'login2.html')
def post(self,request):
print('post方法執行了')
username = request.POST.get('username')
password = request.POST.get('password')
print(username)
print(password)
return HttpResponse('ok')