目錄
一、wsgi接口
二、中間件
三、URL路由系統
四、Template模板
五、Views視圖
六、Model&ORM
七、Admin相關
八、Http協議
九、COOKIE 與 SESSION
十、Django的用戶認證
十一、From與ModelForm
十二、分頁
十三、緩存
十四、信號
十五、序列化
十六、Ajax
十七、數據庫性能相關
0、Django的生命周期

一、wsgi接口
了解了HTTP協議和HTML文檔,我們其實就明白了一個Web應用的本質就是:
-
瀏覽器發送一個HTTP請求;
-
服務器收到請求,生成一個HTML文檔;
-
服務器把HTML文檔作為HTTP響應的Body發送給瀏覽器;
-
瀏覽器收到HTTP響應,從HTTP Body取出HTML文檔並顯示。
所以,最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。Apache、Nginx、Lighttpd等這些常見的靜態服務器就是干這件事情的。
如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規范。
正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專注於生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應格式,所以,需要一個統一的接口,讓我們專心用Python編寫Web業務。
這個接口就是WSGI:Web Server Gateway Interface。
WSGI接口定義非常簡單,它只要求Web開發者實現一個函數,就可以響應HTTP請求。我們來看一個最簡單的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
上面的application()函數就是符合WSGI標准的一個HTTP處理函數,它接收兩個參數:
-
environ:一個包含所有HTTP請求信息的
dict對象; -
start_response:一個發送HTTP響應的函數。
在application()函數中,調用:
start_response('200 OK', [('Content-Type', 'text/html')])
就發送了HTTP響應的Header,注意Header只能發送一次,也就是只能調用一次start_response()函數。start_response()函數接收兩個參數,一個是HTTP響應碼,一個是一組list表示的HTTP Header,每個Header用一個包含兩個str的tuple表示。
通常情況下,都應該把Content-Type頭發送給瀏覽器。其他很多常用的HTTP Header也應該發送。
然后,函數的返回值'<h1>Hello, web!</h1>'將作為HTTP響應的Body發送給瀏覽器。
有了WSGI,我們關心的就是如何從environ這個dict對象拿到HTTP請求信息,然后構造HTML,通過start_response()發送Header,最后返回Body。
整個application()函數本身沒有涉及到任何解析HTTP的部分,也就是說,底層代碼不需要我們自己編寫,我們只負責在更高層次上考慮如何響應請求就可以了。
不過,等等,這個application()函數怎么調用?如果我們自己調用,兩個參數environ和start_response我們沒法提供,返回的str也沒法發給瀏覽器。
所以application()函數必須由WSGI服務器來調用。有很多符合WSGI規范的服務器,我們可以挑選一個來用。但是現在,我們只想盡快測試一下我們編寫的application()函數真的可以把HTML輸出到瀏覽器,所以,要趕緊找一個最簡單的WSGI服務器,把我們的Web應用程序跑起來。
好消息是Python內置了一個WSGI服務器,這個模塊叫wsgiref,它是用純Python編寫的WSGI服務器的參考實現。所謂“參考實現”是指該實現完全符合WSGI標准,但是不考慮任何運行效率,僅供開發和測試使用。
運行WSGI服務
我們先編寫hello.py,實現Web應用程序的WSGI處理函數:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
然后,再編寫一個server.py,負責啟動WSGI服務器,加載application()函數:
# server.py
# 從wsgiref模塊導入:
from wsgiref.simple_server import make_server
# 導入我們自己編寫的application函數:
from hello import application
# 創建一個服務器,IP地址為空,端口是8000,處理函數是application:
httpd = make_server('', 8000, application)
print ("Serving HTTP on port 8000...")
# 開始監聽HTTP請求:
httpd.serve_forever()
確保以上兩個文件在同一個目錄下,然后在命令行輸入python server.py來啟動WSGI服務器:

注意:如果8000端口已被其他程序占用,啟動將失敗,請修改成其他端口。
啟動成功后,打開瀏覽器,輸入http://localhost:8000/,就可以看到結果了:
整個application()函數本身沒有涉及到任何解析HTTP的部分,也就是說,底層代碼不需要我們自己編寫, 我們只負責在更高層次上考慮如何響應請求就可以了。 application()函數必須由WSGI服務器來調用。有很多符合WSGI規范的服務器,我們可以挑選一個來用。 Python內置了一個WSGI服務器,這個模塊叫wsgiref application()函數就是符合WSGI標准的一個HTTP處理函數,它接收兩個參數: //environ:一個包含所有HTTP請求信息的dict對象; //start_response:一個發送HTTP響應的函數。 在application()函數中,調用: start_response('200 OK', [('Content-Type', 'text/html')]) 就發送了HTTP響應的Header,注意Header只能發送一次,也就是只能調用一次start_response()函數。 start_response()函數接收兩個參數,一個是HTTP響應碼,一個是一組list表示的HTTP Header,每 個Header用一個包含兩個str的tuple表示。 通常情況下,都應該把Content-Type頭發送給瀏覽器。其他很多常用的HTTP Header也應該發送。 然后,函數的返回值b'<h1>Hello, web!</h1>'將作為HTTP響應的Body發送給瀏覽器。 有了WSGI,我們關心的就是如何從environ這個dict對象拿到HTTP請求信息,然后構造HTML, 通過start_response()發送Header,最后返回Body。
二、中間件
django 中的中間件(middleware),在django中,中間件其實就是一個類,在請求到來和結束后,django會根據自己的規則在合適的時機執行中間件中相應的方法。
在django項目的settings模塊中,有一個 MIDDLEWARE_CLASSES 變量,其中每一個元素就是一個中間件
中間件中一共有五個方法:
process_request - 有,直接執行當前中間件和上方中間件的process_response - 應用: 用戶登錄授權(排除不需要登錄的url) process_view process_exception - process_tempalte_response - 必須有返回值 - 必須對象中要有render方法 process_response - 必須有返回值
中間件之process_request,process_response
process_request(self,request)
process_response(self, request, response)
當用戶發起請求的時候會依次經過所有的的中間件,這個時候的請求時process_request,最后到達views的函數中,views函數處理后,在依次穿過中間件,這個時候是process_response,最后返回給請求者
在django中叫中間件,在其他web框架中,有的叫管道,httphandle

中間件之process_view執行過程:

當最后一個中間的process_request到達路由關系映射之后,返回到中間件1的process_view,然后依次往下,到達views函數,最后通過process_response依次返回到達用戶
中間件之process_exception
process_exception(self, request, exception)
當views的函數中出現錯誤時,就會執行process_exception方法
如果在中間中添加了process_exception方法,工作圖示為:

這樣當用戶發起請求的時候到達中間件3的process_request之后會到達urls路由關系映射這里,如果匹配到了就會到中間件1的process_view,然后依次傳遞到中間件3的process_view,到達view函數。如果view函數中有報錯,則會從中間件3依次向上判斷每個中間件的process_exception是否能匹配到這個錯誤信息,如果匹配到則直接返回到最后一個中間件,這里即中間件3的process_response,然后依次返回到用戶,如果沒有匹配到這個錯誤則直接在頁面顯示錯誤信息。如果view函數中沒有錯誤,則到中間3即最后一個中間件3的process_response,然后依次向上,傳到用戶
中間件之process_template_responseprocess
process_template_response(self,request,response)
只有當views函數中返回的對象中具有render方法,是就會直接process_template_responseprocess
中間件的應用
所有請求統一做處理時使用
- 登錄驗證
-添加訪問日志等
自定義中間件
上述圖中的中間件都是django中的,我們也可以自己定義一個中間件,我們可以自己寫一個類,但是必須繼承MiddlewareMixin
所以需要導入:from django.utils.deprecation import MiddlewareMixin
我們在項目文件下創建一個Middle目錄,並在下面創建m1.py代碼例子如下
#AUTHOR:FAN from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class Row1(MiddlewareMixin): def process_request(self,request): print("中間件1請求") def process_response(self,request,response): print("中間件1返回") return response class Row2(MiddlewareMixin): def process_request(self,request): print("中間件2請求") # return HttpResponse("走") def process_response(self,request,response): print("中間件2返回") return response class Row3(MiddlewareMixin): def process_request(self,request): print("中間件3請求") def process_response(self,request,response): print("中間件3返回") return response
配置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', 'Middle.m1.Row1', 'Middle.m1.Row2', 'Middle.m1.Row3', ]
三、URL路由系統(URLconf)
URL配置(URLconf)就像Django 所支撐網站的目錄。它的本質是URL與要為該URL調用的視圖函數之間的映射表;你就是以這種方式告訴Django,對於這個URL調用這段代碼,對於那個URL調用那段代碼。
''' urlpatterns = [ url(正則表達式, views視圖函數,參數,別名), ] 參數說明: 一個正則表達式字符串 一個可調用對象,通常為一個視圖函數或一個指定視圖函數路徑的字符串 可選的要傳遞給視圖函數的默認參數(字典形式) 一個可選的name參數 '''
3.1 簡單配置
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
''' NOTE: 1 一旦匹配成功則不再繼續 2 若要從URL 中捕獲一個值,只需要在它周圍放置一對圓括號。 3 不需要添加一個前導的反斜杠,因為每個URL 都有。例如,應該是^articles 而不是 ^/articles。 4 每個正則表達式前面的'r' 是可選的但是建議加上。 一些請求的例子: /articles/2005/3/ 不匹配任何URL 模式,因為列表中的第三個模式要求月份應該是兩個數字。 /articles/2003/ 將匹配列表中的第一個模式不是第二個,因為模式按順序匹配,第一個會首先測試是否匹配。 /articles/2005/03/ 請求將匹配列表中的第三個模式。Django 將調用函數 views.month_archive(request, '2005', '03')。 #設置項是否開啟URL訪問地址后面不為/跳轉至帶有/的路徑 APPEND_SLASH=True '''
3.2 有名分組(named group)
上面的示例使用簡單的、沒有命名的正則表達式組(通過圓括號)來捕獲URL 中的值並以位置 參數傳遞給視圖。在更高級的用法中,可以使用命名的正則表達式組來捕獲URL 中的值並以關鍵字 參數傳遞給視圖。
在Python 正則表達式中,命名正則表達式組的語法是(?P<name>pattern),其中name 是組的名稱,pattern 是要匹配的模式。
下面是以上URLconf 使用命名組的重寫:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ]
這個實現與前面的示例完全相同,只有一個細微的差別:捕獲的值作為關鍵字參數而不是位置參數傳遞給視圖函數。例如:
/articles/2005/03/ 請求將調用views.month_archive(request, year='2005', month='03')函數 /articles/2003/03/03/ 請求將調用函數views.article_detail(request, year='2003', month='03', day='03')。
在實際應用中,這意味你的URLconf 會更加明晰且不容易產生參數順序問題的錯誤 —— 你可以在你的視圖函數定義中重新安排參數的順序。當然,這些好處是以簡潔為代價;有些開發人員認為命名組語法丑陋而繁瑣。
3.3 URLconf 在什么上查找
URLconf 在請求的URL 上查找,將它當做一個普通的Python 字符串。不包括GET和POST參數以及域名。
例如,http://www.example.com/myapp/ 請求中,URLconf 將查找myapp/。
在http://www.example.com/myapp/?page=3 請求中,URLconf 仍將查找myapp/。
URLconf 不檢查請求的方法。換句話講,所有的請求方法 —— 同一個URL的POST、GET、HEAD等等 —— 都將路由到相同的函數。
3.4 捕獲的參數永遠是字符串
每個捕獲的參數都作為一個普通的Python 字符串傳遞給視圖,無論正則表達式使用的是什么匹配方式。例如,下面這行URLconf 中:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
views.year_archive() 的year 參數將是一個字符串
3.5 指定視圖參數的默認值
有一個方便的小技巧是指定視圖參數的默認值。 下面是一個URLconf 和視圖的示例
# URLconf from django.conf.urls import url from . import views urlpatterns = [ url(r'^blog/$', views.page), url(r'^blog/page(?P<num>[0-9]+)/$', views.page), ] # View (in blog/views.py) def page(request, num="1"): ...
在上面的例子中,兩個URL模式指向同一個視圖views.page —— 但是第一個模式不會從URL 中捕獲任何值。如果第一個模式匹配,page() 函數將使用num參數的默認值"1"。如果第二個模式匹配,page() 將使用正則表達式捕獲的num 值。
3.1.6 路由分發( Including other URLconfs)
在項目中,若需要將某一應用下url統一跳轉,則可以使用 Including other URLconfs
#At any point, your urlpatterns can “include” other URLconf modules. This #essentially “roots” a set of URLs below other ones. #For example, here’s an excerpt of the URLconf for the Django website itself. #It includes a number of other URLconfs: from django.conf.urls import include, url urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/', include('blog.urls')), ]
四、Template模板
python的模板:HTML代碼+邏輯控制代碼
4.1 模板支持的語法
4.1.1 變量(使用雙大括號來引用變量)
語法格式: {{var_name}}
Django 模板解析非常快捷。 大部分的解析工作都是在后台通過對簡短正則表達式一次性調用來完成。 這和基於 XML 的模板引擎形成鮮明對比,那些引擎承擔了 XML 解析器的開銷,且往往比 Django 模板渲染引擎要慢上幾個數量級。
from django.shortcuts import render,HttpResponse from django.template.loader import get_template #記得導入 # Create your views here. import datetime from django.template import Template,Context # def current_time(req): #原始的視圖函數 # now=datetime.datetime.now() # html="<html><body>現在時刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) # def current_time(req): #django模板修改的視圖函數 # now=datetime.datetime.now() # t=Template('<html><body>現在時刻是:<h1 style="color:red">{{current_date}}</h1></body></html>') #t=get_template('current_datetime.html') # c=Context({'current_date':now}) # html=t.render(c) # return HttpResponse(html) #另一種寫法(推薦) def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
4.1.2 深度變量的查找(萬能的句點號)
在到目前為止的例子中,我們通過 context 傳遞的簡單參數值主要是字符串,然而,模板系統能夠非常簡潔地處理更加復雜的數據結構,例如list、dictionary和自定義的對象。在 Django 模板中遍歷復雜數據結構的關鍵是句點字符 (.)。
#最好是用幾個例子來說明一下。 # 首先,句點可用於訪問列表索引,例如: >>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) 'Item 2 is carrots.' #假設你要向模板傳遞一個 Python 字典。 要通過字典鍵訪問該字典的值,可使用一個句點: >>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) 'Sally is 43 years old.' #同樣,也可以通過句點來訪問對象的屬性。 比方說, Python 的 datetime.date 對象有 #year 、 month 和 day 幾個屬性,你同樣可以在模板中使用句點來訪問這些屬性: >>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) 'The month is 5 and the year is 1993.' # 這個例子使用了一個自定義的類,演示了通過實例變量加一點(dots)來訪問它的屬性,這個方法適 # 用於任意的對象。 >>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) 'Hello, John Smith.' # 點語法也可以用來引用對象的方法。 例如,每個 Python 字符串都有 upper() 和 isdigit() # 方法,你在模板中可以使用同樣的句點語法來調用它們: >>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) 'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) '123 -- 123 -- True' # 注意這里調用方法時並* 沒有* 使用圓括號 而且也無法給該方法傳遞參數;你只能調用不需參數的 # 方法。
4.1.3 變量的過濾器(filter)的使用
#語法格式: {{obj|filter:param}}
# 1 add : 給變量加上相應的值
# 2 addslashes : 給變量中的引號前加上斜線
# 3 capfirst : 首字母大寫
# 4 cut : 從字符串中移除指定的字符
# 5 date : 格式化日期字符串
# 6 default : 如果值是False,就替換成設置的默認值,否則就是用本來的值
# 7 default_if_none: 如果值是None,就替換成設置的默認值,否則就使用本來的值
#實例:
#value1="aBcDe"
{{ value1|upper }}<br>
#value2=5
{{ value2|add:3 }}<br>
#value3='he llo wo r ld'
{{ value3|cut:' ' }}<br>
#import datetime
#value4=datetime.datetime.now()
{{ value4|date:'Y-m-d' }}<br>
#value5=[]
{{ value5|default:'空的' }}<br>
#value6='<a href="#">跳轉</a>'
{{ value6 }}
{% autoescape off %}
{{ value6 }}
{% endautoescape %}
{{ value6|safe }}<br>
{{ value6|striptags }}
#value7='1234'
{{ value7|filesizeformat }}<br>
{{ value7|first }}<br>
{{ value7|length }}<br>
{{ value7|slice:":-1" }}<br>
#value8='http://www.baidu.com/?a=1&b=3'
{{ value8|urlencode }}<br>
value9='hello I am yuan'
4.1.4自定義filter和simple_tag
a、在app中創建templatetags模塊(必須的)
b、創建任意 .py 文件,如:my_tags.py
from django import template from django.utils.safestring import mark_safe register = template.Library() #register的名字是固定的,不可改變 @register.filter def filter_multi(v1,v2): return v1 * v2 @register.simple_tag def simple_tag_multi(v1,v2): return v1 * v2 @register.simple_tag def my_input(id,arg): result = "<input type='text' id='%s' class='%s' />" %(id,arg,) return mark_safe(result)
c、在使用自定義simple_tag和filter的html文件中導入之前創建的 my_tags.py :{% load my_tags %}
d、使用simple_tag和filter(如何調用)
-------------------------------.html {% load my_tags %} #首行 # num=12 {{ num|filter_multi:2 }} #24 {{ num|filter_multi:"[22,333,4444]" }} {% simple_tag_multi 2 5 %} 參數不限,但不能放在if for語句中 {% simple_tag_multi num 5 %}
e、在settings中的INSTALLED_APPS配置當前app,不然django無法找到自定義的simple_tag.
注意:
filter可以用在if等語句后,simple_tag不可以
{% if num|filter_multi:30 > 100 %}
{{ num|filter_multi:30 }}
{% endif %}
4.2 標簽(tag)的使用(使用大括號和百分比的組合來表示使用tag)
語法格式: {% tags %}
{% if %} 的使用
{% if %}標簽計算一個變量值,如果是“true”,即它存在、不為空並且不是false的boolean值,系統則會顯示{% if %}和{% endif %}間的所有內容
{% if num >= 100 and 8 %}
{% if num > 200 %}
<p>num大於200</p>
{% else %}
<p>num大於100小於200</p>
{% endif %}
{% elif num < 100%}
<p>num小於100</p>
{% else %}
<p>num等於100</p>
{% endif %}
{% if %} 標簽接受and,or或者not來測試多個變量值或者否定一個給定的變量
{% if %} 標簽不允許同一標簽里同時出現and和or,否則邏輯容易產生歧義,例如下面的標簽是不合法的:
{% if obj1 and obj2 or obj3 %}
{% for %}的使用
{% for %}標簽允許你按順序遍歷一個序列中的各個元素,每次循環模板系統都會渲染{% for %}和{% endfor %}之間的所有內容
<ul> {% for obj in list %} <li>{{ obj.name }}</li> {% endfor %} </ul> #在標簽里添加reversed來反序循環列表: {% for obj in list reversed %} ... {% endfor %} #{% for %}標簽可以嵌套: {% for country in countries %} <h1>{{ country.name }}</h1> <ul> {% for city in country.city_list %} <li>{{ city }}</li> {% endfor %} </ul> {% endfor %} #系統不支持中斷循環,系統也不支持continue語句,{% for %}標簽內置了一個forloop模板變量, #這個變量含有一些屬性可以提供給你一些關於循環的信息 1,forloop.counter表示循環的次數,它從1開始計數,第一次循環設為1: {% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %} 2,forloop.counter0 類似於forloop.counter,但它是從0開始計數,第一次循環設為0 3,forloop.revcounter 4,forloop.revcounter0 5,forloop.first當第一次循環時值為True,在特別情況下很有用: {% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %} # 富有魔力的forloop變量只能在循環中得到,當模板解析器到達{% endfor %}時forloop就消失了 # 如果你的模板context已經包含一個叫forloop的變量,Django會用{% for %}標簽替代它 # Django會在for標簽的塊中覆蓋你定義的forloop變量的值 # 在其他非循環的地方,你的forloop變量仍然可用 #{% empty %} {{li }} {% for i in li %} <li>{{ forloop.counter0 }}----{{ i }}</li> {% empty %} <li>this is empty!</li> {% endfor %} # [11, 22, 33, 44, 55] # 0----11 # 1----22 # 2----33 # 3----44 # 4----55
csrf_token標簽
用於生成csrf_token的標簽,用於防治跨站攻擊驗證。 其實,這里是會生成一個input標簽,和其他表單標簽一起提交給后台的。
{% url %}
引用路由配置的地址
<form action="{% url "bieming"%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>
{% with %}
用更簡單的變量名替代復雜的變量名
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
4.3 extend模板繼承
步驟:
a、新建 base.html,放在template目錄下
b、使用模板標簽: {% block %}分塊,用於子模板繼承
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
c、子模版集成(即重載block塊)
{% extends "base.html" %}
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
繼承的常見三層法:
<1> 創建 base.html 模板,在其中定義站點的主要外觀感受。 這些都是不常修改甚至從不修改的部分。 <2> 為網站的每個區域創建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。這些模板對base.html 進行拓展, 並包含區域特定的風格與設計。 <3> 為每種類型的頁面創建獨立的模板,例如論壇頁面或者圖片庫。 這些模板拓展相應的區域模板。
模板繼承的一些訣竅
<1>如果在模板中使用 {% extends %} ,必須保證其為模板中的第一個模板標記。 否則,模板繼承將不起作用。 <2>一般來說,基礎模板中的 {% block %} 標簽越多越好。 記住,子模板不必定義父模板中所有的代碼塊,因此 你可以用合理的缺省值對一些代碼塊進行填充,然后只對子模板所需的代碼塊進行(重)定義。 俗話說,鈎子越 多越好。 <3>如果發覺自己在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的某個 {% block %} 中。 如果你需要訪問父模板中的塊的內容,使用 {{ block.super }}這個標簽吧,這一個魔法變量將會表現出父模 板中的內容。 如果只想在上級代碼塊基礎上添加內容,而不是全部重載,該變量就顯得非常有用了。 <4>不允許在同一個模板中定義多個同名的 {% block %} 。 存在這樣的限制是因為block 標簽的工作方式是雙向的。 也就是說,block 標簽不僅挖了一個要填的坑,也定義了在父模板中這個坑所填充的內容。如果模板中出現了兩個 相同名稱的 {% block %} 標簽,父模板將無從得知要使用哪個塊的內容。
五、Views視圖
django中請求處理方式有2種:FBV 和 CBV
5.1 CBV(class base views)
CBV(class base views) 就是在視圖里使用類處理請求。
5.1.1. 基本使用
使用方法為:urls.py 修改為如下:
from mytest import views urlpatterns = [ # url(r‘^index/‘, views.index), url(r‘^index/‘, views.Index.as_view()), ] #注:url(r‘^index/‘, views.Index.as_view()), 是固定用法。
views.py 修改為如下:
from django.views import View class Index(View): def dispatch(self, request, *args, **kwargs): res = super(AuthView,self).dispatch(request, *args, **kwargs) return res def get(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘) def post(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘) #注:1、類要繼承 View ,類中函數名必須小寫。 2、類中首先執行的是 dispatch方法
5.1.2. 基於dispatch和繼承實現用戶登錄代碼
1、寫一個登錄驗證類
class AuthView(object): def dispatch(self, request, *args, **kwargs): if not request.session.get('user_info'): return redirect('/login.html') res = super(AuthView,self).dispatch(request, *args, **kwargs) return res
2、修改views.py並繼承驗證
from django.views import View class Index(AuthView,View): def get(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘) def post(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘) #繼承AuthView驗證類即可
5.1.3. 裝飾器
1、get,post方法上,給任意函數添加
from django.utils.decorators import method_decorator from django.views import View def test(func): def inner(*args,**kwargs): return func(*args,**kwargs) return inner class Index(View): def dispatch(self, request, *args, **kwargs): res = super(AuthView,self).dispatch(request, *args, **kwargs) return res @method_decorator(test) def get(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘) @method_decorator(test def post(self, req): print(‘method is :‘ + req.method) return render(req, ‘index.html‘)
2、dispatch方法上,同時給get、post添加裝飾器
from django.shortcuts import render,HttpResponse,redirect from django.views import View from django.utils.decorators import method_decorator class LoginView(View): @method_decorator(test) def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self,request): return render(request,'login.html') def post(self,request): return redirect('/index.html')
3、類上
from django.shortcuts import render,HttpResponse,redirect from django.views import View from django.utils.decorators import method_decorator @method_decorator(test,name='get') class LoginView(View): def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self,request): return render(request,'login.html') def post(self,request): return redirect('/index.html') #類上添加裝飾器,name后面有哪些函數,哪些函數就添加了裝飾器
4、特殊:CSRF Token只能加到dispatch
from django.shortcuts import render,HttpResponse,redirect from django.views import View from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt,csrf_protect class LoginView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self,request): return render(request,'login.html') def post(self,request): return redirect('/index.html') #csrf_exempt 其他需要,加此裝飾器的不需要 #csrf_protect 其他不需要需要,加此裝飾器的需要
5.2 FBV(function base views)
FBV(function base views) 就是在視圖里使用函數處理請求。
5.2.1. 基本使用
1、使用方法為:urls.py 修改為如下:
from django.conf.urls import url, include # from django.contrib import admin from mytest import views urlpatterns = [ # url(r‘^admin/‘, admin.site.urls), url(r‘^index/‘, views.index), ]
2、views.py 修改為如下:
from django.shortcuts import render def index(req): if req.method == ‘POST‘: print(‘method is :‘ + req.method) elif req.method == ‘GET‘: print(‘method is :‘ + req.method) return render(req, ‘index.html‘) #注意此處定義的是函數【def index(req):】
3、index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <form action="" method="post"> <input type="text" name="A" /> <input type="submit" name="b" value="提交" /> </form> </body> </html>
5.2.2快捷函數
render函數
---------------render(request, template_name[, context])
結合一個給定的模板和一個給定的上下文字典,並返回一個渲染后的 HttpResponse 對象。
參數:
request: 用於生成響應的請求對象。
template_name:要使用的模板的完整名稱,可選的參數
context:添加到模板上下文的一個字典。默認是一個空字典。如果字典中的某個值是可調用的,視圖將在渲染模板之前調用它。
content_type:生成的文檔要使用的MIME類型。默認為DEFAULT_CONTENT_TYPE 設置的值。
status:響應的狀態碼。默認為200。
復制代碼
redirect函數
-----------------------------------url.py url(r"login", views.login), url(r"yuan_back", views.yuan_back), -----------------------------------views.py def login(req): if req.method=="POST": if 1: # return redirect("/yuan_back/") name="yuanhao" return render(req,"my backend.html",locals()) return render(req,"login.html",locals()) def yuan_back(req): name="苑昊" return render(req,"my backend.html",locals()) -----------------------------------login.html <form action="/login/" method="post"> <p>姓名<input type="text" name="username"></p> <p>性別<input type="text" name="sex"></p> <p>郵箱<input type="text" name="email"></p> <p><input type="submit" value="submit"></p> </form> -----------------------------------my backend.html <h1>用戶{{ name }}你好</h1> #總結: render和redirect的區別: # 1 if render的頁面需要模板語言渲染,需要的將數據庫的數據加載到html,那么所有的這一部分 # 除了寫在yuan_back的視圖函數中,必須還要寫在login中,代碼重復,沒有解耦. # 2 the most important: url沒有跳轉到/yuan_back/,而是還在/login/,所以當刷新后 # 又得重新登錄.
六、Model&ORM
6.1、創建表
1、基本結構
from django.db import models class userinfo(models.Model): name = models.CharField(max_length=30) email = models.EmailField() memo = models.TextField()
AutoField(Field) - int自增列,必須填入參數 primary_key=True BigAutoField(AutoField) - bigint自增列,必須填入參數 primary_key=True 注:當model中如果沒有自增列,則自動會創建一個列名為id的列 from django.db import models class UserInfo(models.Model): # 自動創建一個列名為id的且為自增的整數列 username = models.CharField(max_length=32) class Group(models.Model): # 自定義自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整數 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整數 0 ~ 32767 IntegerField(Field) - 整數列(有符號的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整數 0 ~ 2147483647 BigIntegerField(IntegerField): - 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807 自定義無符號整數字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值為字段在數據庫中的屬性,Django字段默認的值為: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布爾值類型 NullBooleanField(Field): - 可以為空的布爾值 CharField(Field) - 字符類型 - 必須提供max_length參數, max_length表示字符長度 TextField(Field) - 文本類型 EmailField(CharField): - 字符串類型,Django Admin以及ModelForm中提供驗證機制 IPAddressField(Field) - 字符串類型,Django Admin以及ModelForm中提供驗證 IPV4 機制 GenericIPAddressField(Field) - 字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6 - 參數: protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定為True,則輸入::ffff:192.0.2.1時候,可解析為192.0.2.1,開啟刺功能,需要protocol="both" URLField(CharField) - 字符串類型,Django Admin以及ModelForm中提供驗證 URL SlugField(CharField) - 字符串類型,Django Admin以及ModelForm中提供驗證支持 字母、數字、下划線、連接符(減號) CommaSeparatedIntegerField(CharField) - 字符串類型,格式必須為逗號分割的數字 UUIDField(Field) - 字符串類型,Django Admin以及ModelForm中提供對UUID格式的驗證 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功能 - 參數: path, 文件夾路徑 match=None, 正則匹配 recursive=False, 遞歸下面的文件夾 allow_files=True, 允許文件 allow_folders=False, 允許文件夾 FileField(Field) - 字符串,路徑保存在數據庫,文件上傳到指定目錄 - 參數: upload_to = "" 上傳文件的保存路徑 storage = None 存儲組件,默認django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路徑保存在數據庫,文件上傳到指定目錄 - 參數: upload_to = "" 上傳文件的保存路徑 storage = None 存儲組件,默認django.core.files.storage.FileSystemStorage width_field=None, 上傳圖片的高度保存的數據庫字段名(字符串) height_field=None 上傳圖片的寬度保存的數據庫字段名(字符串) DateTimeField(DateField) - 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 時間格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 長整數,時間間隔,數據庫中按照bigint存儲,ORM中獲取的值為datetime.timedelta類型 FloatField(Field) - 浮點型 DecimalField(Field) - 10進制小數 - 參數: max_digits,小數總長度 decimal_places,小數位長度 BinaryField(Field) - 二進制類型
null 數據庫中字段是否可以為空 db_column 數據庫中字段的列名 db_tablespace default 數據庫中字段的默認值 primary_key 數據庫中字段是否為主鍵 db_index 數據庫中字段是否可以建立索引 unique 數據庫中字段是否可以建立唯一索引 unique_for_date 數據庫中字段【日期】部分是否可以建立唯一索引 unique_for_month 數據庫中字段【月】部分是否可以建立唯一索引 unique_for_year 數據庫中字段【年】部分是否可以建立唯一索引 verbose_name Admin中顯示的字段名稱 blank Admin中是否允許用戶輸入為空 editable Admin中是否可以編輯 help_text Admin中該字段的提示信息 choices Admin中顯示選擇框的內容,用不變動的數據放在內存中從而避免跨表操作 如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1) error_messages 自定義錯誤信息(字典類型),從而定制想要顯示的錯誤信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{'null': "不能為空.", 'invalid': '格式錯誤'} validators 自定義錯誤驗證(列表類型),從而定制想要的驗證規則 from django.core.validators import RegexValidator from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length=32, error_messages={ 'c1': '優先錯信息1', 'c2': '優先錯信息2', 'c3': '優先錯信息3', }, validators=[ RegexValidator(regex='root_\d+', message='錯誤了', code='c1'), RegexValidator(regex='root_112233\d+', message='又錯誤了', code='c2'), EmailValidator(message='又錯誤了', code='c3'), ] )
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 數據庫中生成的表名稱 默認 app名稱 + 下划線 + 類名 db_table = "table_name" # 聯合索引 index_together = [ ("pub_date", "deadline"), ] # 聯合唯一索引 unique_together = (("driver", "restaurant"),) # admin中顯示的表名稱 verbose_name # verbose_name加s verbose_name_plural 更多:https://docs.djangoproject.com/en/1.10/ref/models/options/
1.觸發Model中的驗證和錯誤提示有兩種方式: a. Django Admin中的錯誤信息會優先根據Admiin內部的ModelForm錯誤信息提示,如果都成功,才來檢查Model的字段並顯示指定錯誤信息 b. 調用Model對象的 clean_fields 方法,如: # models.py class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) email = models.EmailField(error_messages={'invalid': '格式錯了.'}) # views.py def index(request): obj = models.UserInfo(username='11234', email='uu') try: print(obj.clean_fields()) except Exception as e: print(e) return HttpResponse('ok') # Model的clean方法是一個鈎子,可用於定制操作,如:上述的異常處理。 2.Admin中修改錯誤提示 # admin.py from django.contrib import admin from model_club import models from django import forms class UserInfoForm(forms.ModelForm): username = forms.CharField(error_messages={'required': '用戶名不能為空.'}) email = forms.EmailField(error_messages={'invalid': '郵箱格式錯誤.'}) age = forms.IntegerField(initial=1, error_messages={'required': '請輸入數值.', 'invalid': '年齡必須為數值.'}) class Meta: model = models.UserInfo # fields = ('username',) fields = "__all__" class UserInfoAdmin(admin.ModelAdmin): form = UserInfoForm admin.site.register(models.UserInfo, UserInfoAdmin)
2、連表結構
- 一對多:models.ForeignKey(其他表)
- 多對多:models.ManyToManyField(其他表)
- 一對一:models.OneToOneField(其他表)
應用場景:
一對多:當一張表中創建一行數據時,有一個單選的下拉框(可以被重復選擇)
例如:創建用戶信息時候,需要選擇一個用戶類型【普通用戶】【金牌用戶】【鉑金用戶】等。
多對多:在某表中創建一行數據是,有一個可以多選的下拉框
例如:創建用戶信息,需要為用戶指定多個愛好
一對一:在某表中創建一行數據時,有一個單選的下拉框(下拉框中的內容被用過一次就消失了
例如:原有含10列數據的一張表保存相關信息,經過一段時間之后,10列無法滿足需求,需要為原來的表再添加5列數據
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要進行關聯的表名 to_field=None, # 要關聯的表中的字段名稱 on_delete=None, # 當刪除關聯表中的數據時,當前表與其關聯的行的行為 - models.CASCADE,刪除關聯數據,與之關聯也刪除 - models.DO_NOTHING,刪除關聯數據,引發錯誤IntegrityError - models.PROTECT,刪除關聯數據,引發錯誤ProtectedError - models.SET_NULL,刪除關聯數據,與之關聯的值設置為null(前提FK字段需要設置為可空) - models.SET_DEFAULT,刪除關聯數據,與之關聯的值設置為默認值(前提FK字段需要設置默認值) - models.SET,刪除關聯數據, a. 與之關聯的值設置為指定值,設置:models.SET(值) b. 與之關聯的值設置為可執行對象的返回值,設置:models.SET(可執行對象) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to="User", to_field="id" on_delete=models.SET(func),) related_name=None, # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作時,使用的連接前綴,用於替換【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中顯示關聯數據時,提供的條件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') db_constraint=True # 是否在數據庫中創建外鍵約束 parent_link=False # 在Admin中是否顯示關聯數據 OneToOneField(ForeignKey) to, # 要進行關聯的表名 to_field=None # 要關聯的表中的字段名稱 on_delete=None, # 當刪除關聯表中的數據時,當前表與其關聯的行的行為 ###### 對於一對一 ###### # 1. 一對一其實就是 一對多 + 唯一索引 # 2.當兩個類之間有繼承關系時,默認會創建一個一對一字段 # 如下會在A表中額外增加一個c_ptr_id列且唯一: class C(models.Model): nid = models.AutoField(primary_key=True) part = models.CharField(max_length=12) class A(C): id = models.AutoField(primary_key=True) code = models.CharField(max_length=1) ManyToManyField(RelatedField) to, # 要進行關聯的表名 related_name=None, # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作時,使用的連接前綴,用於替換【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中顯示關聯數據時,提供的條件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') symmetrical=None, # 僅用於多對多自關聯時,symmetrical用於指定內部是否創建反向操作的字段 # 做如下操作時,不同的symmetrical會有不同的可選字段 models.BB.objects.filter(...) # 可選字段有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) # 可選字段有: bb, code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, # 自定義第三張表時,使用字段用於指定關系表 through_fields=None, # 自定義第三張表時,使用字段用於指定關系表中那些字段做多對多關系表 from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64) db_constraint=True, # 是否在數據庫中創建外鍵約束 db_table=None, # 默認創建第三張表時,數據庫中表的名稱
6.2、操作表
1、基本操作
# 增 # # models.Tb1.objects.create(c1='xx', c2='oo') 增加一條數據,可以接受字典類型數據 **kwargs # obj = models.Tb1(c1='xx', c2='oo') # obj.save() # 查 # # models.Tb1.objects.get(id=123) # 獲取單條數據,不存在則報錯(不建議) # models.Tb1.objects.all() # 獲取全部 # models.Tb1.objects.filter(name='seven') # 獲取指定條件的數據 # 刪 # # models.Tb1.objects.filter(name='seven').delete() # 刪除指定條件的數據 # 改 # models.Tb1.objects.filter(name='seven').update(gender='0') # 將指定條件的數據更新,均支持 **kwargs # obj = models.Tb1.objects.get(id=1) # obj.c1 = '111' # obj.save() # 修改單條數據 # save是更改所有字段,即使更改一個字段,也會將所有字段重新賦值, 不推薦 # update更改,只更改修改的字段,推薦使用 # update方式修改不能用get的原因是:update是QuerySet對象的方法,get返回的是一個model對象,它沒有update方法,而filter返回的是一個QuerySet對象(filter里面的條件可能有多個條件符合,比如name='alvin',可能有兩個name='alvin'的行數據)
2、查詢相關API
# 查詢相關API: # <1>filter(**kwargs): 它包含了與所給篩選條件相匹配的對象 # <2>all(): 查詢所有結果 # <3>get(**kwargs): 返回與所給篩選條件相匹配的對象,返回結果有且只有一個,如果符合篩選條件的對象超過一個或者沒有都會拋出錯誤。 #-----------下面的方法都是對查詢的結果再進行處理:比如 objects.filter.values()-------- # <4>values(*field): 返回一個ValueQuerySet——一個特殊的QuerySet,運行后得到的並不是一系列 model的實例化對象,而是一個可迭代的字典序列 # <5>exclude(**kwargs): 它包含了與所給篩選條件不匹配的對象 # <6>order_by(*field): 對查詢結果排序 # <7>reverse(): 對查詢結果反向排序 # <8>distinct(): 從返回結果中剔除重復紀錄 # <9>values_list(*field): 它與values()非常相似,它返回的是一個元組序列,values返回的是一個字典序列 # <10>count(): 返回數據庫中匹配查詢(QuerySet)的對象數量。 # <11>first(): 返回第一條記錄 # <12>last(): 返回最后一條記錄 # <13>exists(): 如果QuerySet包含數據,就返回True,否則返回False
3、進階操作(了不起的雙下划線)
利用雙下划線將字段和對應的操作連接起來
# 獲取個數 # # models.Tb1.objects.filter(name='seven').count() # 大於,小於 # # models.Tb1.objects.filter(id__gt=1) # 獲取id大於1的值 # models.Tb1.objects.filter(id__gte=1) # 獲取id大於等於1的值 # models.Tb1.objects.filter(id__lt=10) # 獲取id小於10的值 # models.Tb1.objects.filter(id__lte=10) # 獲取id小於10的值 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 獲取id大於1 且 小於10的值 # in # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 獲取id等於11、22、33的數據 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # isnull # Entry.objects.filter(pub_date__isnull=True) # contains # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感 # models.Tb1.objects.exclude(name__icontains="ven") # range # # models.Tb1.objects.filter(id__range=[1, 2]) # 范圍bettwen and # 其他類似 # # startswith,istartswith, endswith, iendswith, # order by # # models.Tb1.objects.filter(name='seven').order_by('id') # asc # models.Tb1.objects.filter(name='seven').order_by('-id') # desc # group by # # from django.db.models import Count, Min, Max, Sum # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" # limit 、offset # # models.Tb1.objects.all()[10:20] # regex正則匹配,iregex 不區分大小寫 # # Entry.objects.get(title__regex=r'^(An?|The) +') # Entry.objects.get(title__iregex=r'^(an?|the) +') # date # # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year # # Entry.objects.filter(pub_date__year=2005) # Entry.objects.filter(pub_date__year__gte=2005) # month # # Entry.objects.filter(pub_date__month=12) # Entry.objects.filter(pub_date__month__gte=6) # day # # Entry.objects.filter(pub_date__day=3) # Entry.objects.filter(pub_date__day__gte=3) # week_day # # Entry.objects.filter(pub_date__week_day=2) # Entry.objects.filter(pub_date__week_day__gte=2) # hour # # Event.objects.filter(timestamp__hour=23) # Event.objects.filter(time__hour=5) # Event.objects.filter(timestamp__hour__gte=12) # minute # # Event.objects.filter(timestamp__minute=29) # Event.objects.filter(time__minute=46) # Event.objects.filter(timestamp__minute__gte=29) # second # # Event.objects.filter(timestamp__second=31) # Event.objects.filter(time__second=2) # Event.objects.filter(timestamp__second__gte=31)
4、連表操作(了不起的雙下划線)
利用雙下划線和 _set 將表之間的操作連接起來
class UserProfile(models.Model): user_info = models.OneToOneField('UserInfo') username = models.CharField(max_length=64) password = models.CharField(max_length=64) def __unicode__(self): return self.username class UserInfo(models.Model): user_type_choice = ( (0, u'普通用戶'), (1, u'高級用戶'), ) user_type = models.IntegerField(choices=user_type_choice) name = models.CharField(max_length=32) email = models.CharField(max_length=32) address = models.CharField(max_length=128) def __unicode__(self): return self.name class UserGroup(models.Model): caption = models.CharField(max_length=64) user_info = models.ManyToManyField('UserInfo') def __unicode__(self): return self.caption class Host(models.Model): hostname = models.CharField(max_length=64) ip = models.GenericIPAddressField() user_group = models.ForeignKey('UserGroup') def __unicode__(self): return self.hostname
user_info_obj = models.UserInfo.objects.filter(id=1).first() print user_info_obj.user_type print user_info_obj.get_user_type_display() print user_info_obj.userprofile.password user_info_obj = models.UserInfo.objects.filter(id=1).values('email', 'userprofile__username').first() print user_info_obj.keys() print user_info_obj.values()
類似一對一 1、搜索條件使用 __ 連接 2、獲取值時使用 . 連接
user_info_obj = models.UserInfo.objects.get(name=u'武沛齊') user_info_objs = models.UserInfo.objects.all() group_obj = models.UserGroup.objects.get(caption='CEO') group_objs = models.UserGroup.objects.all() # 添加數據 #group_obj.user_info.add(user_info_obj) #group_obj.user_info.add(*user_info_objs) # 刪除數據 #group_obj.user_info.remove(user_info_obj) #group_obj.user_info.remove(*user_info_objs) # 添加數據 #user_info_obj.usergroup_set.add(group_obj) #user_info_obj.usergroup_set.add(*group_objs) # 刪除數據 #user_info_obj.usergroup_set.remove(group_obj) #user_info_obj.usergroup_set.remove(*group_objs) # 獲取數據 #print group_obj.user_info.all() #print group_obj.user_info.all().filter(id=1) # 獲取數據 #print user_info_obj.usergroup_set.all() #print user_info_obj.usergroup_set.all().filter(caption='CEO') #print user_info_obj.usergroup_set.all().filter(caption='DBA')
5、聚合查詢和分組查詢
<1> aggregate(*args,**kwargs):
通過對QuerySet進行計算,返回一個聚合值的字典。aggregate()中每一個參數都指定一個包含在字典中的返回值。即在查詢集上生成聚合。
from django.db.models import Avg,Min,Sum,Max 從整個查詢集生成統計值。比如,你想要計算所有在售書的平均價錢。Django的查詢語法提供了一種方式描述所有 圖書的集合。 >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} aggregate()子句的參數描述了我們想要計算的聚合值,在這個例子中,是Book模型中price字段的平均值 aggregate()是QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的 標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。如果你想要為聚合值指定 一個名稱,可以向聚合子句提供它: >>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} 如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢: >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
<2> annotate(*args,**kwargs):
可以通過計算查詢結果中每一個對象所關聯的對象集合,從而得出總計值(也可以是平均值或總和),即為查詢集的每一項生成聚合。
#1、統計每一個出版社的最便宜的書 方法一: publishList=Publish.objects.annotate(MinPrice=Min("book__price")) for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice) 方法二: queryResult=Book.objects.values("publish__name").annotate(MinPrice=Min('price')) #2、統計每一本以py開頭的書籍的作者個數 queryResult=Book.objects .filter(title__startswith="Py") .annotate(num_authors=Count('authors')) #3、 統計不止一個作者的圖書: queryResult=Book.objects .annotate(num_authors=Count('authors')) .filter(num_authors__gt=1) #4、根據一本圖書作者數量的多少對查詢集 QuerySet進行排序: Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors') #5、查詢各個作者出的書的總價格: # 按author表的所有字段 group by queryResult=Author.objects .annotate(SumPrice=Sum("book__price")) .values_list("name","SumPrice") print(queryResult) #按authors__name group by queryResult2=Book.objects.values("authors__name") .annotate(SumPrice=Sum("price")) .values_list("authors__name","SumPrice") print(queryResult2)
6、F查詢和Q查詢
# F 使用查詢條件的值,專門取對象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q 構建搜索條件 from django.db.models import Q #1 Q對象(django.db.models.Q)可以對關鍵字參數進行封裝,從而更好地應用多個查詢 q1=models.Book.objects.filter(Q(title__startswith='P')).all() print(q1)#[<Book: Python>, <Book: Perl>] # 2、可以組合使用&,|操作符,當一個操作符是用於兩個Q的對象,它產生一個新的Q對象。 Q(title__startswith='P') | Q(title__startswith='J') # 3、Q對象可以用~操作符放在前面表示否定,也可允許否定與不否定形式的組合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 4、應用范圍: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects.get( Q(title__startswith='P'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) #sql: # SELECT * from polls WHERE question LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # import datetime # e=datetime.date(2005,5,6) #2005-05-06 # 5、Q對象可以與關鍵字參數查詢一起使用,不過一定要把Q對象放在關鍵字參數查詢的前面。 # 正確: Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P') # 錯誤: Book.objects.get( question__startswith='P', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
七、Admin相關
django amdin是django提供的一個后台管理頁面,改管理頁面提供完善的html和css,使得你在通過Model創建完數據庫表之后,就可以對數據進行增刪改查,而使用django admin 則需要以下步驟:
- 創建后台管理員
- 配置url
- 注冊和配置django admin后台管理頁面
1、創建后台管理員
python manage.py createsuperuser
2、配置后台管理url
url(r'^admin/', include(admin.site.urls))
3、注冊和配置django admin 后台管理頁面
a、在admin中執行如下配置
from django.contrib import admin from app01 import models admin.site.register(models.UserType) admin.site.register(models.UserInfo) admin.site.register(models.UserGroup) admin.site.register(models.Asset)
b、設置數據表名稱
class UserType(models.Model): name = models.CharField(max_length=50) class Meta: verbose_name = '用戶類型' verbose_name_plural = '用戶類型'
c、一些常用的設置技巧(在admin.py文件中配置)
list_display: 指定要顯示的字段
search_fields: 指定搜索的字段
list_filter: 指定列表過濾器
ordering: 指定排序字段
d、打開表之后,設定默認顯示,需要在model中作如下配置(在admin.py文件中配置)
class UserType(models.Model): name = models.CharField(max_length=50) def __unicode__(self): return self.name from django.contrib import admin from app01 import models class UserInfoAdmin(admin.ModelAdmin): list_display = ('username', 'password', 'email') admin.site.register(models.UserType) admin.site.register(models.UserInfo,UserInfoAdmin) admin.site.register(models.UserGroup) admin.site.register(models.Asset)
e、為數據表添加搜索功能(在admin.py文件中配置)
from django.contrib import admin from app01 import models class UserInfoAdmin(admin.ModelAdmin): list_display = ('username', 'password', 'email') search_fields = ('username', 'email') admin.site.register(models.UserType) admin.site.register(models.UserInfo,UserInfoAdmin) admin.site.register(models.UserGroup) admin.site.register(models.Asset)
f、添加快速過濾(在admin.py文件中配置)
from django.contrib import admin from app01 import models class UserInfoAdmin(admin.ModelAdmin): list_display = ('username', 'password', 'email') search_fields = ('username', 'email') list_filter = ('username', 'email') admin.site.register(models.UserType) admin.site.register(models.UserInfo,UserInfoAdmin) admin.site.register(models.UserGroup) admin.site.register(models.Asset)
八、Http協議
8.1 HTTP概述
HTTP(hypertext transport protocol),即超文本傳輸協議。這個協議詳細規定了瀏覽器和萬維網服務器之間互相通信的規則。
HTTP就是一個通信規則,通信規則規定了客戶端發送給服務器的內容格式,也規定了服務器發送給客戶端的內容格式。其實我們要學習的就是這個兩個格式!客戶端發送給服務器的格式叫“請求協議”;服務器發送給客戶端的格式叫“響應協議”。
特點:
- HTTP叫超文本傳輸協議,基於請求/響應模式的!
- HTTP是無狀態協議。
URL:統一資源定位符,就是一個網址:協議名://域名:端口/路徑,例如:http://www.oldboy.cn:80/index.html
8.2 請求協議
請求協議的格式如下:
請求首行; // 請求方式 請求路徑 協議和版本,例如:GET /index.html HTTP/1.1 請求頭信息;// 請求頭名稱:請求頭內容,即為key:value格式,例如:Host:localhost 空行; // 用來與請求體分隔開 請求體。 // GET沒有請求體,只有POST有請求體。為一個個鍵值對 實例: GET請求: "GET /index.html http1.1\r\nUser-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x6..\r\n\r\nAccept-Encoding:gzip\r\n\r\n" POST請求: "POST /index.html http1.1\r\nUser-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x6..\r\n\r\nAccept-Encoding:gzip\r\n\r\nuser=cqz&hobby=lihao" 請求頭以\r\n划分 請求體以\r\n\r\n划分
瀏覽器發送給服務器的內容就這個格式的,如果不是這個格式服務器將無法解讀!在HTTP協議中,請求有很多請求方法,其中最為常用的就是GET和POST。
8.2.1 GET請求
HTTP默認的請求方法就是GET
* 沒有請求體 * 數據必須在1K之內! * GET請求數據會暴露在瀏覽器的地址欄中
GET請求常用的操作:
1. 在瀏覽器的地址欄中直接給出URL,那么就一定是GET請求 2. 點擊頁面上的超鏈接也一定是GET請求 3. 提交表單時,表單默認使用GET請求,但可以設置為POST
8.2.2 POST請求
(1). 數據不會出現在地址欄中 (2). 數據的大小沒有上限 (3). 有請求體 (4). 請求體中如果存在中文,會使用URL編碼!
我們都知道Http協議中參數的傳輸是"key=value"這種簡直對形式的,如果要傳多個參數就需要用“&”符號對鍵值對進行分割。如"?name1=value1&name2=value2",這樣在服務端在收到這種字符串的時候,會用“&”分割出每一個參數,然后再用“=”來分割出參數值。 針對“name1=value1&name2=value2”我們來說一下客戶端到服務端的概念上解析過程: 上述字符串在計算機中用ASCII嗎表示為: 6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532。 6E616D6531:name1 3D:= 76616C756531:value1 26:& 6E616D6532:name2 3D:= 76616C756532:value2 服務端在接收到該數據后就可以遍歷該字節流,首先一個字節一個字節的吃,當吃到3D這字節后,服務端就知道前面吃得字節表示一個key,再想后吃,如果遇到26,說明從剛才吃的3D到26子節之間的是上一個key的value,以此類推就可以解析出客戶端傳過來的參數。 現在有這樣一個問題,如果我的參數值中就包含=或&這種特殊字符的時候該怎么辦。 比如說“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么實際在傳輸過程中就會變成這樣“name1=va&lu=e1”。我們的本意是就只有一個鍵值對,但是服務端會解析成兩個鍵值對,這樣就產生了奇異。 如何解決上述問題帶來的歧義呢?解決的辦法就是對參數進行URL編碼 URL編碼只是簡單的在特殊字符的各個字節前加上%,例如,我們對上述會產生奇異的字符進行URL編碼后結果:“name1=va%26lu%3D”,這樣服務端會把緊跟在“%”后的字節當成普通的字節,就是不會把它當成各個參數或鍵值對的分隔符。 為什么要進行URL編碼
8.3 接收自定義http頭部(headers)
用Python的httplib庫來做模擬客戶端,參考網上寫出模擬代碼如下:
#coding=utf8 import httplib httpClient = None try: myheaders = { "category": "Books", "id": "21", 'My-Agent': "Super brower" } httpClient = httplib.HTTPConnection('10.14.1XX.XXX',8086,timeout=30) httpClient.request('GET','/headinfo/',headers=myheaders) response = httpClient.getresponse() print response.status print response.reason print response.read() except Exception, e: print e finally: if httpClient: httpClient.close()
其中'/headinfo/'為服務器的響應目錄。
然后是服務端的響應代碼,一個能夠返回客戶端自定義頭部的模塊
from django.http import HttpResponse def headinfo(request): category = request.META.get('HTTP_CATEGORY', 'unkown') id = request.META.get('HTTP_ID','unkown') agent = request.META.get('MY-AGENT','unkown') html = "<html><body>Category is %s, id is %s, agent is %s</body></html>" % (category, id, agent) return HttpResponse(html)
結果:
注意點:自定義請求頭時,Django自動在頭部META字典中的key值都會被加上“HTTP_”的前綴,category 會變成 HTTP_CATEGORY ,auth-api 會變成 HTTP_AUTH_API
8.4 http請求request詳解
屬性 描述 path 表示提交請求頁面完整地址的字符串, 不包括域名,如 "/music/bands/the_beatles/" 。 method 表示提交請求使用的HTTP方法。 它總是大寫的。例如: if request.method == 'GET': do_something() elif request.method == 'POST': do_something_else() GET 一個類字典對象,包含所有的HTTP的GET參數的信息。 見 QueryDict 文檔。 POST 一個類字典對象,包含所有的HTTP的POST參數的信息。 見 QueryDict 文檔。 通過POST提交的請求有可能包含一個空的 POST 字典, 也就是說, 一個通過POST方法提交的表單可能不包含數據。 因此,不應該使用 if request.POST 來判斷POST方法的使用, 而是使用if request.method == "POST" (見表中的 method 條目)。 注意: POST 並 不 包含文件上傳信息。 見 FILES 。 REQUEST 為了方便而創建,這是一個類字典對象,先搜索 POST , 再搜索 GET 。 靈感來自於PHP的$_REQEUST 。 例如, 若 GET = {"name": "john"} , POST = {"age": '34'} , REQUEST["name"] 會是 "john" , REQUEST["age"] 會是 "34" 。 強烈建議使用 GET 和 POST ,而不是 REQUEST 。 這是為了向前兼容和更清楚的表示。 COOKIES 一個標准的Python字典,包含所有cookie。 鍵和值都是字符串。cookie使用的更多信息見第12章。 FILES 一個類字典對象,包含所有上傳的文件。 FILES 的鍵來自 <input type="file" name="" />中的 name 。 FILES 的值是一個標准的Python字典, 包含以下三個鍵: filename :字符串,表示上傳文件的文件名。 content-type :上傳文件的內容類型。 content :上傳文件的原始內容。 注意 FILES 只在請求的方法是 POST ,並且提交的 <form> 包含enctype="multipart/form-data" 時 才包含數據。否則, FILES 只是一個空的類字典對象。 META 一個標准的Python字典,包含所有有效的HTTP頭信息。 有效的頭信息與客戶端和服務器有關。 這里有幾個例子: CONTENT_LENGTH CONTENT_TYPE QUERY_STRING :未解析的原始請求字符串。 REMOTE_ADDR :客戶端IP地址。 REMOTE_HOST :客戶端主機名。 SERVER_NAME :服務器主機名。 SERVER_PORT :服務器端口號。 在 META 中有效的任一HTTP頭信息都是帶有 HTTP_ 前綴的 鍵,例如: HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_HOST :客戶端發送的 Host 頭信息。 HTTP_REFERER :被指向的頁面,如果存在的。 HTTP_USER_AGENT :客戶端的user-agent字符串。 HTTP_X_BENDER : X-Bender 頭信息的值, 如果已設的話。 user 一個 django.contrib.auth.models.User 對象表示 當前登錄用戶。 若當前用戶尚未登錄,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 時有效。 關於認證和用戶的完整細節,見第12章。 session 一個可讀寫的類字典對象,表示當前session。 僅當Django已激活session支持時有效。 見 http://djangobook.py3k.cn/chapter12/ raw_post_data POST的原始數據。 用於對數據的復雜處理。
8.5 HttpRequest 的方法
方法 描述 __getitem__(key) 請求所給鍵的GET/POST值,先查找POST,然后是GET。 若鍵不存在,則引發異常KeyError 。該方法使用戶可以以訪問字典方式來訪問一個 HttpRequest 實例。例如, request["foo"] 和先檢查 request.POST["foo"] 再檢查request.GET["foo"] 一 樣。 has_key() 返回 True 或 False , 標識 request.GET 或 request.POST 是否包含所給的 鍵。 get_full_path() 返回 path ,若請求字符串有效,則附加於其后。 例如,"/music/bands/the_beatles/?print=true" 。 is_secure() 如果請求是安全的,則返回 True 。 也就是說,請求是以HTTPS的形式提交的。
九、COOKIE 與 SESSION
9.1簡介
1、cookie不屬於http協議范圍,由於http協議無法保持狀態,但實際情況,我們卻又需要“保持狀態”,因此cookie就是在這樣一個場景下誕生。 cookie的工作原理是:由服務器產生內容,瀏覽器收到請求后保存在本地;當瀏覽器再次訪問時,瀏覽器會自動帶上cookie,這樣服務器就能通過cookie的內容來判斷這個是“誰”了。 2、cookie雖然在一定程度上解決了“保持狀態”的需求,但是由於cookie本身最大支持4096字節,以及cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節,並且他保存在服務器,有較高的安全性。這就是session。 問題來了,基於http協議的無狀態特征,服務器根本就不知道訪問者是“誰”。那么上述的cookie就起到橋接的作用。 我們可以給每個客戶端的cookie分配一個唯一的id,這樣用戶在訪問時,通過cookie,服務器就知道來的人是“誰”。然后我們再根據不同的cookie的id,在服務器上保存一段時間的私密資料,如“賬號密碼”等等。 3、總結而言:cookie彌補了http無狀態的不足,讓服務器知道來的人是“誰”;但是cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過cookie識別不同的用戶,對應的在session里保存私密的信息以及超過4096字節的文本。 4、另外,上述所說的cookie和session其實是共通性的東西,不限於語言和框架
9.2認證機制
制作一個登陸頁面,在驗證了用戶名和密碼的正確性后跳轉到后台的頁面。但是測試后也發現,如果繞過登陸頁面。直接輸入后台的url地址也可以直接訪問的。這個顯然是不合理的。其實我們缺失的就是cookie和session配合的驗證。有了這個驗證過程,我們就可以實現和其他網站一樣必須登錄才能進入后台頁面了。 先說一下這種認證的機制。每當我們使用一款瀏覽器訪問一個登陸頁面的時候,一旦我們通過了認證。服務器端就會發送一組隨機唯一的字符串(假設是123abc)到瀏覽器端,這個被存儲在瀏覽端的東西就叫cookie。而服務器端也會自己存儲一下用戶當前的狀態,比如login=true,username=hahaha之類的用戶信息。但是這種存儲是以字典形式存儲的,字典的唯一key就是剛才發給用戶的唯一的cookie值。那么如果在服務器端查看session信息的話,理論上就會看到如下樣子的字典 {'123abc':{'login':true,'username:hahaha'}} 因為每個cookie都是唯一的,所以我們在電腦上換個瀏覽器再登陸同一個網站也需要再次驗證。那么為什么說我們只是理論上看到這樣子的字典呢?因為處於安全性的考慮,其實對於上面那個大字典不光key值123abc是被加密的,value值{'login':true,'username:hahaha'}在服務器端也是一樣被加密的。所以我們服務器上就算打開session信息看到的也是類似與以下樣子的東西 {'123abc':dasdasdasd1231231da1231231} 知道了原理,我們下面就來用代碼實現
9.3認證應用
第一步: 先在templates目錄下創建兩個html,login.html負責登錄頁面。backend頁面代表后台頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link rel="stylesheet" href="http://830909.blog.51cto.com/static/plugins/bootstrap-3.3.5-dist/css/bootstrap.min.css"> </head> <body> <div class="container"> <form action="login.html" method="post"> <div class="form-group"> <label class="sr-only">username</label> <input type="text" class="form-control" name="username" placeholder="用戶名"/> </div> <div class="form-group"> <label class="sr-only">Password</label> <input type="password" class="form-control" name="passwd" placeholder="密碼"/> </div> <div class="form-group"> <input class="btn btn-primary" type="submit" value="http://830909.blog.51cto.com/8311014/Submit"> </div> </form> </div> <script type="application/Javascript" src="http://830909.blog.51cto.com/static/js/jquery-2.2.1.min.js"></script> <script type="application/javascript" src="http://830909.blog.51cto.com/static/plugins/bootstrap-3.3.5-dist/js/bootstrap.min.js"></script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>backend</title> <link rel="stylesheet" href="http://830909.blog.51cto.com/static/plugins/bootstrap-3.3.5-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="http://830909.blog.51cto.com/static/css/commons.css"> </head> <body> <div class="container"> <h2>cookie 內容是 {{ cookie_content }}</h2> <h2>session 內容是 {{ session_content }}</h2> <h2>登錄用戶名 :{{ username }}</h2> <a href="http://830909.blog.51cto.com/logout/">注銷</a> </div> <script type="application/javascript" src="http://830909.blog.51cto.com/static/js/jquery-2.2.1.min.js"></script> <script type="application/javascript" src="http://830909.blog.51cto.com/static/plugins/bootstrap-3.3.5-dist/js/bootstrap.min.js"></script> </body> </html>
第二步:編輯app01應用下的views.py文件,編寫代碼邏輯部分
# /usr/bin/env python # coding:utf-8 from django.shortcuts import render from django.shortcuts import redirect def login(request): if request.method=="POST": username=request.POST['username'] pwd=request.POST['passwd'] if username=='abc' and pwd=='123': #設置session內部的字典內容 request.session['is_login']='true' request.session['username']='abc' #登錄成功就將url重定向到后台的url return redirect('/backend/') #登錄不成功或第一訪問就停留在登錄頁面 return render(request,'login.html') def backend(request): """ 這里必須用讀取字典的get()方法把is_login的value缺省設置為False, 當用戶訪問backend這個url先嘗試獲取這個瀏覽器對應的session中的 is_login的值。如果對方登錄成功的話,在login里就已經把is_login 的值修改為了True,反之這個值就是False的 """ is_login=request.session.get('is_login',False) #如果為真,就說明用戶是正常登陸的 if is_login: #獲取字典的內容並傳入頁面文件 cookie_content=request.COOKIES session_content=request.session username=request.session['username'] return render(request,'backend.html', { 'cookie_content':cookie_content, 'session_content':session_content, 'username':username }) else: """ 如果訪問的時候沒有攜帶正確的session, 就直接被重定向url回login頁面 """ return redirect('/login/') def logout(request): """ 直接通過request.session['is_login']回去返回的時候, 如果is_login對應的value值不存在會導致程序異常。所以 需要做異常處理 """ try: #刪除is_login對應的value值 del request.session['is_login'] except KeyError: pass #點擊注銷之后,直接重定向回登錄頁面 return redirect('/login/')
第三步,編輯mydjango目錄下的urls.py文件。設置函數與頁面的綁定關系
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), url(r'^backend/', views.backend), url(r'^logout/', views.logout), ]
最后打開瀏覽器直接訪問/backend/頁面的時候直接就被重定向到了/login/
總結
1、login頁面正確登錄的話,后台頁面可以獲取到瀏覽器攜帶的cookie的。 2、第一行的sessionid其實就是cookie值 3、session的內容是加密的,從客戶端獲取不到session的內容 4、服務端可以通過預設的key值取出session的內容並打印到前段 5、django的session默認是存儲在數據庫里的,我們再到數據庫查看一下真正session內容
9.4cookie知識點:
# 1、獲取Cookie: # request.COOKIES['key'] # request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) # 參數: # default: 默認值 # salt: 加密鹽 # max_age: 后台控制過期時間 # 2、設置Cookie: # rep = HttpResponse(...) 或 rep = render(request, ...) # # rep.set_cookie(key,value,...) # rep.set_signed_cookie(key,value,salt='加密鹽',...) # 參數: # key, 鍵 # value='', 值 # max_age=None, 超時時間 # expires=None, 超時時間(IE requires expires, so set it if hasn't been already.) # path='/', Cookie生效的路徑,/ 表示根路徑,特殊的:跟路徑的cookie可以被任何url的頁面訪問 # domain=None, Cookie生效的域名 # secure=False, https傳輸 # httponly=False 只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到也可以被覆蓋) # 由於cookie保存在客戶端的電腦上,所以,JavaScript和jquery也可以操作cookie。 # <script src='/static/js/jquery.cookie.js'></script> # $.cookie("list_pager_num", 30,{ path: '/' });
9.5session知識點:
Django中默認支持Session,其內部提供了5種類型的Session供開發者使用:
1、數據庫Session(默認)
Django默認支持Session,並且默認是將Session數據存儲在數據庫中,即:django_session 表中。 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默認) SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認) SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑(默認) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默認) SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie(默認) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸(默認) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默認) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過期(默認) SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改之后才保存(默認) b. 使用 def index(request): # 獲取、設置、刪除Session中數據 request.session['k1'] request.session.get('k1',None) request.session['k1'] = 123 request.session.setdefault('k1',123) # 存在則不設置 del request.session['k1'] # 所有 鍵、值、鍵值對 request.session.keys() request.session.values() request.session.items() request.session.iterkeys() request.session.itervalues() request.session.iteritems() # 用戶session的隨機字符串 request.session.session_key # 將所有Session失效日期小於當前日期的數據刪除 request.session.clear_expired() # 檢查 用戶session的隨機字符串 在數據庫中是否 request.session.exists("session_key") # 刪除當前用戶的所有Session數據 request.session.delete("session_key") ...
2、緩存Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的緩存別名(默認內存緩存,也可以是memcache),此處別名依賴緩存的設置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過期 SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改之后才保存 b. 使用 同上
3、文件Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 緩存文件路徑,如果為None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過期 SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改之后才保存 b. 使用 同上
4、緩存+數據庫Session
數據庫用於做持久化,緩存用於提高效率 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 b. 使用 同上
5、加密cookie Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 b. 使用 同上
擴展:Session用戶驗證
def login(func): def wrap(request, *args, **kwargs): # 如果未登陸,跳轉到指定頁面 if request.path == '/test/': return redirect('http://www.baidu.com') return func(request, *args, **kwargs) return wrap
十、Django的用戶認證
10.1認證登錄
from django.contrib import auth
django.contrib.auth中提供了許多方法,這里主要介紹其中的三個:
10.1.1 authenticate()
提供了用戶認證,即驗證用戶名以及密碼是否正確,一般需要username password兩個關鍵字參數 如果認證信息有效,會返回一個 User 對象。authenticate()會在User 對象上設置一個屬性標識那種認證后端認證了該用戶,且該信息在后面的登錄過程中是需要的。當我們試圖登陸一個從數據庫中直接取出來不經過authenticate()的User對象會報錯的!! user=authenticate(username='someone',password='somepassword')
10.1.2 login(HttpRequest, user)
該函數接受一個HttpRequest對象,以及一個認證了的User對象 此函數使用django的session框架給某個已認證的用戶附加上session id等信息。 from django.contrib.auth import authenticate, login def my_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. ... else: # Return an 'invalid login' error message. ...
10.1.3 logout(request) 注銷用戶
from django.contrib.auth import logout def logout_view(request): logout(request) # Redirect to a success page. 該函數接受一個HttpRequest對象,無返回值。當調用該函數時,當前請求的session信息會全部清除。該用戶即使沒有登錄,使用該函數也不會報錯
10.1.4 user對象的 is_authenticated()
要求:
1 用戶登陸后才能訪問某些頁面, 2 如果用戶沒有登錄就訪問該頁面的話直接跳到登錄頁面 3 用戶在跳轉的登陸界面中完成登陸后,自動訪問跳轉到之前訪問的地址
方法1:
def my_view(request):
if not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
方法2:login_required函數
django已經為我們設計好了一個用於此種情況的裝飾器:login_requierd()
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...
若用戶沒有登錄,則會跳轉到django默認的 登錄URL '/accounts/login/ ' (這個值可以在settings文件中通過LOGIN_URL進行修改)。並傳遞 當前訪問url的絕對路徑 (登陸成功后,會重定向到該路徑)。
+++++++++++++++++++
10.2 User對象
User 對象屬性:username, password(必填項)password用哈希算法保存到數據庫
is_staff : 用戶是否擁有網站的管理權限.
is_active : 是否允許用戶登錄, 設置為``False``,可以不用刪除用戶來禁止 用戶登錄
10.3 User 對象方法
10.3.1 is_authenticated()
如果是真正的 User 對象,返回值恆為 True 。 用於檢查用戶是否已經通過了認證。
通過認證並不意味着用戶擁有任何權限,甚至也不檢查該用戶是否處於激活狀態,這只是表明用戶成功的通過了認證。 這個方法很重要, 在后台用request.user.is_authenticated()判斷用戶是否已經登錄,如果true則可以向前台展示request.user.name
10.3.2 創建用戶
使用 create_user 輔助函數創建用戶: from django.contrib.auth.models import User user = User.objects.create_user(username='',password='',email='')
10.3.3 set_password(passwd)
這個方法是用來更改密碼的,使用步驟: user=User.objects.get(username='') user.set_password(passeord='') user.save
10.3.4 check_password(passwd)
用戶需要修改密碼的時候 首先要讓他輸入原來的密碼 ,如果給定的字符串通過了密碼檢查,返回 True
10.3.5 修改密碼
使用 set_password() 來修改密碼 user = User.objects.get(username='') user.set_password(password='') user.save
示例:注冊
def sign_up(request): state = None if request.method == 'POST': password = request.POST.get('password', '') repeat_password = request.POST.get('repeat_password', '') email=request.POST.get('email', '') if password == '' or repeat_password == '': state = 'empty' elif password != repeat_password: state = 'repeat_error' else: username = request.POST.get('username', '') if User.objects.filter(username=username): state = 'user_exist' else: new_user = User.objects.create_user(username=username, password=password,email=email) new_user.save() new_my_user = MyUser(user=new_user, telephone=request.POST.get('telephone', '')) new_my_user.save() return redirect('/book/') content = { 'state': state, 'user': None, } return render(request, 'book/sign_up.html', content)
示例:改密碼
@login_required def set_password(request): user = request.user state = None if request.method == 'POST': old_password = request.POST.get('old_password', '') new_password = request.POST.get('new_password', '') repeat_password = request.POST.get('repeat_password', '') if user.check_password(old_password): if not new_password: state = 'empty' elif new_password != repeat_password: state = 'repeat_error' else: user.set_password(new_password) user.save() return redirect("/log_in/") else: state = 'password_error' content = { 'user': user, 'state': state, } return render(request, 'book/set_password.html', content)
十一、Form與ModelForm
11.1 From
Django會將表單的提交變得更加簡單和安全,包括重構數據后在頁面間傳遞,創建前端的HTML頁面以及接收和處理客戶端傳來的數據。實施上,你只需要事先定義好form表單的各種屬性,在前端頁面簡單調用即可。當然,Django為Form提供了許多屬性,方便自定義,甚至你可以重寫其中的一些方法。
應用:使用注冊表完成驗證並注冊
1、urls.py
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^register/', views.register), ]
2、models.py
from django.db import models from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用戶信息 """ nid = models.BigAutoField(primary_key=True) nickname = models.CharField(verbose_name='昵稱', max_length=32) telephone = models.CharField(max_length=11, blank=True, null=True, unique=True, verbose_name='手機號碼') avatar = models.FileField(verbose_name='頭像', upload_to='upload/avatar/',default="/upload/avatar/default_avatar.jpg") create_time = models.DateTimeField(verbose_name='創建時間',auto_now_add=True) fans = models.ManyToManyField(verbose_name='粉絲們', to='UserInfo', through='UserFans', through_fields=('user', 'follower')) def __str__(self): return self.username
3、在app 目錄下新建 form.py
from django import forms from django.core.exceptions import NON_FIELD_ERRORS, ValidationError class RegForm(forms.Form): username=forms.CharField(min_length=2,error_messages={"required":"*用戶名不能為空!","min_length":"*用戶名太短!"}, widget=forms.TextInput(attrs={"id":"username","placeholder":"用戶為2到12位數字或字母"})) password=forms.CharField(error_messages={"required":"*密碼不能為空!"}, widget=forms.PasswordInput(attrs={"id":"passwordsignup","placeholder":"eg. 密碼不能少於6位且不能為純數字"})) repeat_password=forms.CharField(error_messages={"required":"*密碼不能為空!"}, widget=forms.PasswordInput(attrs={"id":"passwordsignup_confirm","placeholder":"eg. 密碼不能少於6位且不能為純數字"})) email=forms.EmailField(error_messages={"required":"*郵箱不能為空!","invalid":"*郵箱格式不正確"}, widget=forms.EmailInput(attrs={"id":"emailsignup","placeholder":"mysupermail@mail.com"})) validcode=forms.CharField(error_messages={"required":"*驗證碼不能為空!"}, widget=forms.TextInput(attrs={"class":"validcode_input","id":"validcode","placeholder":"valid_code"})) def __init__(self,request,*args,**kwargs): super(RegForm,self).__init__(*args,**kwargs) self.request=request def clean_username(self): # if self.cleaned_data["username"].isdigit() or self.cleaned_data["username"].isalpha(): # raise ValidationError("*用戶名必須包含數字與字母!") # if len(self.cleaned_data["username"]) <2: # raise ValidationError("*用戶名太短!") if len(self.cleaned_data["username"]) >12: raise ValidationError("*用戶名太長!") else: return self.cleaned_data["username"] def clean_password(self): # 關於密碼字段的鈎子 if len(self.cleaned_data["password"]) <5: raise ValidationError("*密碼不能少於6位!") elif self.cleaned_data["password"].isdigit(): raise ValidationError("*密碼不能為純數字!") else: return self.cleaned_data["password"] def clean_repeat_password(self): # 關於密碼字段的鈎子 if len(self.cleaned_data["repeat_password"]) < 5: raise ValidationError("*密碼不能少於6位!") elif self.cleaned_data["repeat_password"].isdigit(): raise ValidationError("*密碼不能為純數字!") else: return self.cleaned_data["repeat_password"] def clean(self): if self.cleaned_data.get("password") == self.cleaned_data.get("repeat_password"): #self.cleaned_data.get("password") 與self.cleaned_data["password"] 的區別是后者取值是當值不存在時沒有值會報錯 return self.cleaned_data else: raise ValidationError("*密碼不一致") def clean_validcode(self): if self.cleaned_data["validcode"].upper() == self.request.session["validcode"].upper(): return self.cleaned_data["validcode"] else: raise ValidationError("驗證碼錯誤!")
4、views.py
def register(request): if request.is_ajax(): data = {"tag": False, "error_message": ""} form_obj=forms.RegForm(request,request.POST) if form_obj.is_valid(): username=form_obj.cleaned_data["username"] email = form_obj.cleaned_data["email"] password = form_obj.cleaned_data["password"] repeat_password = form_obj.cleaned_data["repeat_password"] user_validcode = form_obj.cleaned_data["validcode"].upper() validcode = request.session['validcode'].upper() file_obj = request.FILES.get("img") models.UserInfo.objects.create_user(username=username,email=email,password=password,avatar=file_obj) data["tag"]=True else: errors=form_obj.errors data["error_message"] = errors return HttpResponse(json.dumps(data)) form_obj=forms.RegForm(request) return render(request,"register.html",{"form_obj":form_obj})
5、register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加個人信息</title> <style> .formset{ color: rebeccapurple; border: dashed cadetblue; } </style> </head> <body> <form action="/register/" method="post"> {% csrf_token %} <h1>博客園-注冊</h1> <p> <label for="username" class="uname" data-icon="u">用戶名</label> {{ form_obj.username }} <span id="username_error" class="ajax_errors"></span> </p> <p> <label for="emailsignup" class="youmail" data-icon="e"> 郵箱</label> {{ form_obj.email }} <span id="emailsignup_error" class="ajax_errors"></span> </p> <p> <label for="passwordsignup" class="youpasswd" data-icon="p">密碼 </label> {{ form_obj.password }} <span id="passwordsignup_error" class="ajax_errors"></span> </p> <p> <label for="passwordsignup_confirm" class="youpasswd" data-icon="p">確認密碼</label> {{ form_obj.repeat_password }} <span id="passwordsignup_confirm_error" class="ajax_errors"></span> </p > <p class="file_avatar"><label for="avatar" >頭像</label> <img src="{% static 'images/default_avatar.jpg' %}" alt="" id="file_img" class="sol"> <input type="file" id="file_choose" class="sol"></p> <p> <label for="validcode" class="validcode" data-icon="p" style="display: block"> 驗證碼 </label> {{ form_obj.validcode }} <span class="valid_img_box"> <img src="/validcode/" alt="" class="valid_img"> <span>看不清楚?</span> </span> </p> <p style="margin-bottom: 45px!important"> <spanp id="validcode_error" class="ajax_errors"></spanp> </p> <p class="signin button"> <input type="button" class="register_but" value="注冊" style="float: left"/> <a href="/index/"><input type="button" value="返回"/></a> </p> <p class="change_link"> 已經有賬號 ? <a href="/login/" class="to_register"> 去登錄 </a> </p> </form> </body> </html>
11.2 ModelForm
11.2.1ModelForm參數和使用
ModelForm a. class Meta: model, # 對應Model的 fields=None, # 字段 exclude=None, # 排除字段 labels=None, # 提示信息 help_texts=None, # 幫助提示信息 widgets=None, # 自定義插件 error_messages=None, # 自定義錯誤信息(整體錯誤信息from django.core.exceptions import NON_FIELD_ERRORS) field_classes=None # 自定義字段類 (也可以自定義字段) localized_fields=('birth_date',) # 本地化,如:根據不同時區顯示數據 如: 數據庫中 2016-12-27 04:10:57 setting中的配置 TIME_ZONE = 'Asia/Shanghai' USE_TZ = True 則顯示: 2016-12-27 12:10:57 b. 驗證執行過程 is_valid -> full_clean -> 鈎子 -> 整體錯誤 c. 字典字段驗證 def clean_字段名(self): # 可以拋出異常 # from django.core.exceptions import ValidationError return "新值" d. 用於驗證 model_form_obj = XXOOModelForm() model_form_obj.is_valid() model_form_obj.errors.as_json() model_form_obj.clean() model_form_obj.cleaned_data e. 用於創建 model_form_obj = XXOOModelForm(request.POST) #### 頁面顯示,並提交 ##### # 默認保存多對多 obj = form.save(commit=True) # 不做任何操作,內部定義 save_m2m(用於保存多對多) obj = form.save(commit=False) obj.save() # 保存單表信息 obj.save_m2m() # 保存關聯多對多信息 f. 用於更新和初始化 obj = model.tb.objects.get(id=1) model_form_obj = XXOOModelForm(request.POST,instance=obj) ... PS: 單純初始化 model_form_obj = XXOOModelForm(initial={...})
11.2.2 通過ModelForm完成三張表格的數據新增:
1) 在app01目錄下建立forms.py文件,今后將所有的form都寫在這個文件中,這個文件需要在views.py中導入,方便導入相應的FORM
2) 創建ModelForm:
forms.py:
# -*- coding: UTF-8 -*- from django.forms import ModelForm from .models import Node,Line,Device #定義Node的Form,Form名字為 模式名+Form class NodeForm(ModelForm): #自定義ModelForm的內容 class Meta: #該ModelForm參照Model: Node model = Node #在Form中不顯示node_signer這個字段 exclude = ['node_signer'] class LineForm(ModelForm): class Meta: model = Line exclude = ['line_signer'] class DeviceForm(ModelForm): class Meta: model = Device exclude = ['device_signer']
建立url與view的關聯關系
url(r'^add/', echo.views.add),
在views.py中建立相應的函數
views.py:
# -*- coding: UTF-8 -*- from .models import Node,Line,Device from forms import NodeForm,LineForm,DeviceForm from django.shortcuts import render, redirect def add(request): #獲取來自NodeForm的表單數據 form = NodeForm(request.POST or None) #判斷form是否有效 if form.is_valid(): #創建實例,需要做些數據處理,暫不做保存 instance = form.save(commit=False) #將登錄用戶作為登記人 instance.node_signer = request.user #保存該實例 instance.save() #跳轉至列表頁面 return redirect('/lists/') #創建context來集中處理需要傳遞到頁面的數據 context = { 'form': form, } #如果沒有有效提交,則仍留在原來頁面 return render(request, 'add.html', context)
在templates文件夾下建立HTML文件,add.html
add.html:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <form method='POST' action=''>{% csrf_token %} {{ form }} <input type='submit' value='提交' /> </form> </body> </html>
十二、分頁
12.1分頁器的使用
>>> from django.core.paginator import Paginator >>> objects = ['john', 'paul', 'george', 'ringo'] >>> p = Paginator(objects, 2) >>> p.count #數據總數 4 >>> p.num_pages #總頁數 2 >>> type(p.page_range) # `<type 'rangeiterator'>` in Python 2. <class 'range_iterator'> >>> p.page_range #頁碼的列表 range(1, 3) # =========[1,2] >>> page1 = p.page(1) #第1頁的page對象 >>> page1 <Page 1 of 2> >>> page1.object_list #第1頁的數據 ['john', 'paul'] >>> page2 = p.page(2) >>> page2.object_list #第2頁的數據 ['george', 'ringo'] >>> page2.has_next() #是否有下一頁 False >>> page2.has_previous() #是否有上一頁 True >>> page2.has_other_pages() #是否有其他頁 True >>> page2.next_page_number() #下一頁的頁碼 Traceback (most recent call last): ... EmptyPage: That page contains no results >>> page2.previous_page_number() #上一頁的頁碼 1 >>> page2.start_index() # 本頁第一條記錄的序數(從1開始) 3 >>> page2.end_index() # 本頁最后錄一條記錄的序數(從1開始) 4 >>> p.page(0) #錯誤的頁,拋出異常 Traceback (most recent call last): ... EmptyPage: That page number is less than 1 >>> p.page(3) #錯誤的頁,拋出異常 Traceback (most recent call last): ... EmptyPage: That page contains no results
12.2實現一個分頁效果
index.html
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap.css' %}">
</head>
<body>
<div class="container">
<h4>分頁器</h4>
<ul>
{% for book in book_list %}
<li>{{ book.title }} {{ book.price }}</li>
{% endfor %}
</ul>
<ul class="pagination" id="pager">
{% if book_list.has_previous %}
<li class="previous"><a href="/blog/?page={{ book_list.previous_page_number }}">上一頁</a></li>
{% else %}
<li class="previous disabled"><a href="#">上一頁</a></li>
{% endif %}
{% for num in paginator.page_range %}
{% if num == currentPage %}
<li class="item active"><a href="/blog/?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="item"><a href="/blog/?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if book_list.has_next %}
<li class="next"><a href="/blog/?page={{ book_list.next_page_number }}">下一頁</a></li>
{% else %}
<li class="next disabled"><a href="#">下一頁</a></li>
{% endif %}
</ul>
</div>
</body>
</html>
views.py
from django.shortcuts import render,HttpResponse # Create your views here. from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from app01.models import * def index(request): ''' 批量導入數據: Booklist=[] for i in range(100): Booklist.append(Book(title="book"+str(i),price=30+i*i)) Book.objects.bulk_create(Booklist) ''' book_list=Book.objects.all() paginator = Paginator(book_list, 10) page = request.GET.get('page',1) currentPage=int(page) try: print(page) book_list = paginator.page(page) except PageNotAnInteger: book_list = paginator.page(1) except EmptyPage: book_list = paginator.page(paginator.num_pages) return render(request,"index.html",locals())
十三、緩存
由於Django是動態網站,所有每次請求均會去數據進行相應的操作,當程序訪問量大時,耗時必然會更加明顯,最簡單解決方式是使用:緩存,緩存將一個某個views的返回值保存至內存或者memcache中,5分鍾內再有人來訪問時,則不再去執行view中的操作,而是直接從內存或者Redis中之前緩存的內容拿到,並返回。
Django中提供了6種緩存方式:
開發調試 內存 文件 數據庫 Memcache緩存(python-memcached模塊) Memcache緩存(pylibmc模塊)
1、配置
a、開發調試
# 此為開始調試用,實際內部不做任何操作 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎 'TIMEOUT': 300, # 緩存超時時間(默認300,None表示永不過期,0表示立即過期) 'OPTIONS':{ 'MAX_ENTRIES': 300, # 最大緩存個數(默認300) 'CULL_FREQUENCY': 3, # 緩存到達最大個數之后,剔除緩存個數的比例,即:1/CULL_FREQUENCY(默認3) }, 'KEY_PREFIX': '', # 緩存key的前綴(默認空) 'VERSION': 1, # 緩存key的版本(默認1) 'KEY_FUNCTION' 函數名 # 生成key的函數(默認函數會生成為:【前綴:版本:key】) } } # 自定義key def default_key_func(key, key_prefix, version): """ Default function to generate keys. Constructs the key used by all other methods. By default it prepends the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ return '%s:%s:%s' % (key_prefix, version, key) def get_key_func(key_func): """ Function to decide which key function to use. Defaults to ``default_key_func``. """ if key_func is not None: if callable(key_func): return key_func else: return import_string(key_func) return default_key_func
b、內存
# 此緩存將內容保存至內存的變量中 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } } # 注:其他配置同開發調試版本
c、文件
# 此緩存將內容保存至文件 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', } } # 注:其他配置同開發調試版本
d、數據庫
# 此緩存將內容保存至數據庫 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', # 數據庫表 } } # 注:執行創建表命令 python manage.py createcachetable
e、Memcache緩存(python-memcached模塊)
# 此緩存使用python-memcached模塊連接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
f、Memcache緩存(pylibmc模塊)
# 此緩存使用pylibmc模塊連接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
2、應用
a. 全站使用
使用中間件,經過一系列的認證等操作,如果內容在緩存中存在,則使用FetchFromCacheMiddleware獲取內容並返回給用戶,當返回給用戶之前,判斷緩存中是否已經存在,如果不存在則UpdateCacheMiddleware會將緩存保存至緩存,從而實現全站緩存 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # 其他中間件... 'django.middleware.cache.FetchFromCacheMiddleware', ] CACHE_MIDDLEWARE_ALIAS = "" CACHE_MIDDLEWARE_SECONDS = "" CACHE_MIDDLEWARE_KEY_PREFIX = ""
b. 單獨視圖緩存
方式一: from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ... 方式二: from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), ]
c、局部視圖使用
a. 引入TemplateTag {% load cache %} b. 使用緩存 {% cache 5000 緩存key %} 緩存內容 {% endcache %}
十四、信號
Django中提供了“信號調度”,用於在框架執行操作時解耦。通俗來講,就是一些動作發生的時候,信號允許特定的發送者去提醒一些接受者。
1、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 # 創建數據庫連接時,自動觸發
對於Django內置的信號,僅需注冊指定信號,當程序執行相應操作時,自動觸發注冊函數:
from django.core.signals import request_finished from django.core.signals import request_started from django.core.signals import got_request_exception from django.db.models.signals import class_prepared 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 pre_migrate, post_migrate from django.test.signals import setting_changed from django.test.signals import template_rendered from django.db.backends.signals import connection_created def callback(sender, **kwargs): print("xxoo_callback") print(sender,kwargs) xxoo.connect(callback) # xxoo指上述導入的內容
from django.core.signals import request_finished from django.dispatch import receiver @receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!")
2、自定義信號
a. 定義信號
import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
b. 注冊信號
def callback(sender, **kwargs): print("callback") print(sender,kwargs) pizza_done.connect(callback)
c. 觸發信號
from 路徑 import pizza_done pizza_done.send(sender='seven',toppings=123, size=456)
由於內置信號的觸發者已經集成到Django中,所以其會自動調用,而對於自定義信號則需要開發者在任意位置觸發。
十五、序列化
序列化
在django中,在將數據庫中檢索的數據返回給客戶端用戶,數據類型為QuerySet格式,而QuerySet格式不能被json格式化處理傳到前端,因此在處理此類數據時有兩種方法:
方法一:使用django自帶的系列化模塊 serializers模塊
1、serializers
from django.core import serializers ret = models.BookType.objects.all() data = serializers.serialize("json", ret)
注:1、serializers序列化后的數據格式為列表,列表為每一個查詢記錄

2、serializers模塊不能跨表查詢,涉及一對多查詢時,只能查詢到關聯字段
方法二:查詢時轉換為QuerySet字典或列表格式
2、json.dumps
import json #ret = models.BookType.objects.all().values('caption') ret = models.BookType.objects.all().values_list('caption') ret=list(ret) #將QuerySet進行轉化 result = json.dumps(ret)
特殊的:
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return o.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) ds = json.dumps(d, cls=JsonCustomEncoder)
十六、Ajax
AJAX的優缺點
優點:
- AJAX使用Javascript技術向服務器發送異步請求;
- AJAX無須刷新整個頁面;
- 因為服務器響應內容不再是整個頁面,而是頁面中的局部,所以AJAX性能高;
缺點:
- AJAX並不適合所有場景,很多時候還是要使用同步交互;
- AJAX雖然提高了用戶體驗,但無形中向服務器發送的請求次數增多了,導致服務器壓力增大;
- 因為AJAX是在瀏覽器中使用Javascript技術完成的,所以還需要處理瀏覽器兼容性問題;
js實現局部刷新
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .error{ color:red } </style> </head> <body> <form class="Form"> <p>姓名 <input class="v1" type="text" name="username" mark="用戶名"></p> <p>密碼 <input class="v1" type="text" name="email" mark="郵箱"></p> <p><input type="submit" value="submit"></p> </form> <script src="jquery-3.1.1.js"></script> <script> $(".Form :submit").click(function(){ flag=true; $("Form .v1").each(function(){ var value=$(this).val(); if (value.trim().length==0){ var mark=$(this).attr("mark"); var $span=$("<span>"); $span.html(mark+"不能為空!"); $span.prop("class","error"); $(this).after($span); setTimeout(function(){ $span.remove(); },800); flag=false; return flag; } }); return flag }); </script> </body> </html>
jquery實現的ajax
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="{% static 'JS/jquery-3.1.1.js' %}"></script>
</head>
<body>
<button class="send_Ajax">send_Ajax</button>
<script>
//$.ajax的兩種使用方式:
//$.ajax(settings);
//$.ajax(url,[settings]);
$(".send_Ajax").click(function(){
$.ajax({
url:"/handle_Ajax/",
type:"POST",
data:{username:"Yuan",password:123},
success:function(data){
alert(data)
},
//=================== error============
error: function (jqXHR, textStatus, err) {
// jqXHR: jQuery增強的xhr
// textStatus: 請求完成狀態
// err: 底層通過throw拋出的異常對象,值與錯誤類型有關
console.log(arguments);
},
//=================== complete============
complete: function (jqXHR, textStatus) {
// jqXHR: jQuery增強的xhr
// textStatus: 請求完成狀態 success | error
console.log('statusCode: %d, statusText: %s', jqXHR.status, jqXHR.statusText);
console.log('textStatus: %s', textStatus);
},
//=================== statusCode============
statusCode: {
'403': function (jqXHR, textStatus, err) {
console.log(arguments); //注意:后端模擬errror方式:HttpResponse.status_code=500
},
'400': function () {
}
}
})
})
</script>
</body>
</html>
import json def index(request): return render(request,"index.html") def handle_Ajax(request): username=request.POST.get("username") password=request.POST.get("password") print(username,password) return HttpResponse(json.dumps("Error Data!")) views
$.ajax參數
######################------------data---------################ data: 當前ajax請求要攜帶的數據,是一個json的object對象,ajax方法就會默認地把它編碼成某種格式 (urlencoded:?a=1&b=2)發送給服務端;此外,ajax默認以get方式發送請求。 function testData() { $.ajax("/test",{ //此時的data是一個json形式的對象 data:{ a:1, b:2 } }); //?a=1&b=2 ######################------------processData---------################ processData:聲明當前的data數據是否進行轉碼或預處理,默認為true,即預處理;if為false, 那么對data:{a:1,b:2}會調用json對象的toString()方法,即{a:1,b:2}.toString() ,最后得到一個[object,Object]形式的結果。 ######################------------contentType---------################ contentType:默認值: "application/x-www-form-urlencoded"。發送信息至服務器時內容編碼類型。 用來指明當前請求的數據編碼格式;urlencoded:?a=1&b=2;如果想以其他方式提交數據, 比如contentType:"application/json",即向服務器發送一個json字符串: $.ajax("/ajax_get",{ data:JSON.stringify({ a:22, b:33 }), contentType:"application/json", type:"POST", }); //{a: 22, b: 33} 注意:contentType:"application/json"一旦設定,data必須是json字符串,不能是json對象 ######################------------traditional---------################ traditional:一般是我們的data數據有數組時會用到 :data:{a:22,b:33,c:["x","y"]}, traditional為false會對數據進行深層次迭代;
響應參數
/* dataType: 預期服務器返回的數據類型,服務器端返回的數據會根據這個值解析后,傳遞給回調函數。 默認不需要顯性指定這個屬性,ajax會根據服務器返回的content Type來進行轉換; 比如我們的服務器響應的content Type為json格式,這時ajax方法就會對響應的內容 進行一個json格式的轉換,if轉換成功,我們在success的回調函數里就會得到一個json格式 的對象;轉換失敗就會觸發error這個回調函數。如果我們明確地指定目標類型,就可以使用 data Type。 dataType的可用值:html|xml|json|text|script 見下dataType實例 */
示例:
from django.shortcuts import render,HttpResponse from django.views.decorators.csrf import csrf_exempt # Create your views here. import json def login(request): return render(request,'Ajax.html') def ajax_get(request): l=['alex','little alex'] dic={"name":"alex","pwd":123} #return HttpResponse(l) #元素直接轉成字符串alexlittle alex #return HttpResponse(dic) #字典的鍵直接轉成字符串namepwd return HttpResponse(json.dumps(l)) return HttpResponse(json.dumps(dic))# 傳到前端的是json字符串,要想使用,需要JSON.parse(data) //--------------------------------------------------- function testData() { $.ajax('ajax_get', { success: function (data) { console.log(data); console.log(typeof(data)); //console.log(data.name); //JSON.parse(data); //console.log(data.name); }, //dataType:"json", } )} 注解:Response Headers的content Type為text/html,所以返回的是String;但如果我們想要一個json對象 設定dataType:"json"即可,相當於告訴ajax方法把服務器返回的數據轉成json對象發送到前端.結果為object 當然, return HttpResponse(json.dumps(a),content_type="application/json") 這樣就不需要設定dataType:"json"了。 content_type="application/json"和content_type="json"是一樣的!
csrf跨站請求偽造
方法一
$.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, });
方法二:
var formData=new FormData(); var username= $("#username").val(); var email= $("#emailsignup").val(); var csrf=$("[name='csrfmiddlewaretoken']").val(); formData.append("username",username); formData.append("email",email); formData.append("csrfmiddlewaretoken",csrf); $.ajax({ url:"/handle_Ajax/", type:"POST", data: formData,, success:function(data){ alert(data) }, })
跨域AJAX
由於瀏覽器存在同源策略機制,同源策略阻止從一個源加載的文檔或腳本獲取或設置另一個源加載的文檔的屬性。
特別的:由於同源策略是瀏覽器的限制,所以請求的發送和響應是可以進行,只不過瀏覽器不接受罷了。
瀏覽器同源策略並不是對所有的請求均制約:
- 制約: XmlHttpRequest
- 不叼: img、iframe、script等具有src屬性的標簽
跨域,跨域名訪問,如:http://www.c1.com 域名向 http://www.c2.com域名發送請求。
1、JSONP實現跨域請求
JSONP(JSONP - JSON with Padding是JSON的一種“使用模式”),利用script標簽的src屬性(瀏覽器允許script標簽跨域)
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="button" onclick="Jsonp1();" value='提交'/> </p> <p> <input type="button" onclick="Jsonp2();" value='提交'/> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function Jsonp1(){ var tag = document.createElement('script'); tag.src = "http://c2.com:8000/test/"; document.head.appendChild(tag); document.head.removeChild(tag); } function Jsonp2(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'GET', dataType: 'JSONP', success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
2、CORS
隨着技術的發展,現在的瀏覽器可以支持主動設置從而允許跨域請求,即:跨域資源共享(CORS,Cross-Origin Resource Sharing),其本質是設置響應頭,使得瀏覽器允許跨域請求。
* 簡單請求 OR 非簡單請求 條件: 1、請求方式:HEAD、GET、POST 2、請求頭信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 對應的值是以下三個中的任意一個 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同時滿足以上兩個條件時,則是簡單請求,否則為復雜請求 * 簡單請求和非簡單請求的區別? 簡單請求:一次請求 非簡單請求:兩次請求,在發送數據之前會先發一次請求用於做“預檢”,只有“預檢”通過后才再發送一次請求用於數據傳輸。
* 關於“預檢” - 請求方式:OPTIONS - “預檢”其實做檢查,檢查如果通過則允許傳輸數據,檢查不通過則不再發送真正想要發送的消息 - 如何“預檢” => 如果復雜請求是PUT等請求,則服務端需要設置允許某請求,否則“預檢”不通過 Access-Control-Request-Method => 如果復雜請求設置了請求頭,則服務端需要設置允許某請求頭,否則“預檢”不通過 Access-Control-Request-Headers
基於cors實現AJAX請求:
a、支持跨域,簡單請求
服務器設置響應頭:Access-Control-Allow-Origin = '域名' 或 '*'
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.open('GET', "http://c2.com:8000/test/", true); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'GET', dataType: 'text', success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def get(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.write('{"status": true, "data": "seven"}')
b、支持跨域,復雜請求
由於復雜請求時,首先會發送“預檢”請求,如果“預檢”成功,則發送真實數據。
-
- “預檢”請求時,允許請求方式則需服務器設置響應頭:Access-Control-Request-Method
- “預檢”請求時,允許請求頭則需服務器設置響應頭:Access-Control-Request-Headers
- “預檢”緩存時間,服務器設置響應頭:Access-Control-Max-Age
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
c、跨域獲取響應頭
默認獲取到的所有響應頭只有基本信息,如果想要獲取自定義的響應頭,則需要再服務器端設置Access-Control-Expose-Headers。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); // 獲取響應頭 console.log(xhr.getAllResponseHeaders()); } }; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); // 獲取響應頭 console.log(xmlHttpRequest.getAllResponseHeaders()); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
d、跨域傳輸cookie
在跨域請求中,默認情況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書無論在預檢請求中或是在實際請求都是不會被發送。
如果想要發送:
-
- 瀏覽器端:XMLHttpRequest的withCredentials為true
- 服務器端:Access-Control-Allow-Credentials為true
- 注意:服務器端響應的 Access-Control-Allow-Origin 不能是通配符 *
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.withCredentials = true; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, xhrFields:{withCredentials: true}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Credentials', "true") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.set_cookie('kkkkk', 'vvvvv'); self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
十七、數據庫性能相關
select_related 與 prefetch_related 區別
user_list = models.UserInfo.objects.all()
for row in user_list:
# 只去取當前表數據
select_related,主動連表查詢【FK】
user_list = models.UserInfo.objects.all().select_related('FK字段')
for row in user_list:
# 只去取當前表數據和FK表關聯字段
user_list = models.UserInfo.objects.values(...)
for row in user_list:
# 只去取當前表數據和FK表關聯字段
==》 連表降低性能
prefetch_related 被動連表
user_list = models.UserInfo.objects.all().prefetch_related('FK字段')
# [obj,obj,obj]
# 查詢用戶表models.UserInfo.objects.all() 1000
# 把用戶表中所有的ut_id拿到, 用戶類型ID [1,2,3]
# 把用戶表中所有的ut_id拿到, 用戶類型ID [21,21,31]
# select * from UsetType where id in [1,2,3]
# select * from xx where id in [21,21,31]
user_list = models.UserInfo.objects.all().prefetch_related('ut','xx')
for row in user_list:
print(row.name, row.pwd, row.ut.caption)
補充:
# [obj,obj,obj]
# user_list = models.UserInfo.objects.all().only('name') # 只取某個字段 select name from userinfo
# user_list = models.UserInfo.objects.all().defer('name') # 排除當前字段
# for row in user_list:
# print(row.pwd)
select_related,主動連表查詢【FK】,當連表不多時用select_related查詢
prefetch_related 被動連表,當連表較多時,用prefetch_related查詢
未添加:
自定義分頁、驗證碼、頭像預覽、富文本編輯框
