什么是web框架
眾所周知,所有的Web應用基本都是基於B/S結構,其本質上其實就是一個socket服務端(web服務器),一個socket客戶端(用戶瀏覽器)。下面的代碼就是一個簡單的web端,運行后,使用瀏覽器訪問,會打印hello world
#!/usr/bin/env python #_*_coding:utf-8_*_ import socket def handle_request(client): buf = client.recv(1024) client.send(b"HTTP/1.1 200 OK\r\n\r\n") client.send(b"Hello, world") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
上述代碼通過socket實現了web服務端的本質,而對於真實開發中的Python Web程序來說,一般會分為兩部分:服務器程序和應用程序。
-
- 服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各種數據進行整理。
- 應用程序則負責具體的邏輯處理。
為了方便應用程序的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。這樣,服務器程序就需要為不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。這時候,標准化就變得尤為重要。我們可以設立一個標准,只要服務器程序支持這個標准,框架也支持這個標准,那么他們就可以配合使用。一旦標准確定,雙方各自實現。這樣,服務器可以支持更多支持標准的框架,框架也可以使用更多支持標准的服務器。
WSGI
WSGI全稱為:The Python Web Server Gateway Interface,單從名字來看就是一種網關,而網關的作用就是在協議之間進行轉換。
WSGI是為 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口,是一種規范,定義了使用Python編寫的Web app與Web server之間接口格式,實現web app與web server間的解耦。
Python標准庫提供的獨立WSGI服務器稱為wsgiref。
# 利用wsgiref完成上面簡單的web服務程序 from wsgiref.simple_server import make_server def RunServer(environ, start_response): # environ 存放的是用戶訪問時提交的信息,包涵http header等 start_response('200 OK', [('Content-Type', 'text/html')]) # 構建http response頭部信息 return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] # 返回給用戶的信息,注意是 bytes 格式。 if __name__ == '__main__': httpd = make_server('', 8000, RunServer) # 啟動一個服務,監聽8000端口,請求進來交給RunServer函數處理 print("Serving HTTP on port 8000...") httpd.serve_forever() # 啟動服務開始接受請求
MVC與MTV模式
說到web開發,就需要遵循一定的開發模式。著名的MVC/MTV模式,其本質是使各組件之間保持松耦合關系。
MVC框架
MVC 是一種使用 MVC(Model View Controller 模型-視圖-控制器)設計創建 Web 應用程序的模式
- Model(模型)表示應用程序核心(比如數據庫記錄列表)
- View(視圖)顯示數據(數據庫記錄)
- Controller(控制器)處理輸入(寫入數據庫記錄)
MVC 模式同時提供了對 HTML、CSS 和 JavaScript 的完全控制。
- Model(模型)是應用程序中用於處理應用程序數據邏輯的部分。通常模型對象負責在數據庫中存取數據。
- View(視圖)是應用程序中處理數據顯示的部分。通常視圖是依據模型數據創建的前端網頁。
- Controller(控制器)是應用程序中處理用戶交互的部分。通常控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據映射,模式渲染等。
MTV框架
MTV是一種使用MTV(Model Templates Views 模型-模版-視圖)設計創建 Web 應用程序的模式
- Model(模型):負責業務對象與數據庫的對象(ORM)
- Template(模版):負責如何把頁面展示給用戶
- View(視圖):負責業務邏輯,並在適當的時候調用Model和Template
此外,Django還有一個url分發器,它的作用是將一個個URL的頁面請求分發給不同的view處理,view再調用相應的Model和Template
區別
MVC即模型-視圖-控制器模式,就是為那些需要為同樣的數據提供多個視圖的應用程序而設計的。它很好地實現了數據層與表示層的分離,特別適用於開發與用戶圖形界面有關的應用程序。控制器用來處理用戶命令以及程序事件;模型維護數據並提供數據訪問方法;視圖用於數據的顯示。
MTV即模型-模版-視圖模式,其標准名稱是有爭議的。在MVC的解釋中,視圖描述了展現給用戶的數據,是指所看到的數據,而不是如何看見它。在python中視圖是指對某一特定URL的回調函數,因為回調函數描述了所要展現的數據。模版用於將內容與展現分離。在django中,視圖描述了要展現的數據,而視圖一般轉交給模版。模版描述了數據如何展現。控制器則是指django框架本身,通過URL配置,系統將一個請求發送到一個合適的視圖。
Django處理順序
Django是標准的MTV框架。
- wsgi:socket請求處理
- 控制器(django框架本身):控制用戶輸入,url匹配,通過映射列表將一個請求發送到一個合適的視圖;
- views --Views:python程序,向模型和模板發送(或獲取)數據;
- 模型綁定 --Model:數據庫存取數據
- 模板引擎 --Templates:用於將內容與展現分離,描述了數據如何展現(如網頁模板);
- 模式渲染 --Views:將模板和數據整合,形成最終網頁;
- 控制器(django框架本身):返回用戶展示。
Django基礎命令
安裝Django
使用命令行執行如下命令進行安裝
pip3 install Django
注意:會安裝在python安裝目錄的scripts目錄下。針對多環境的情況下,使用哪個解釋器安裝,那么就會安裝在哪個版本下。
安裝完畢后會在scripts下產生一個django-admin.py文件,利用該文件可以在命令行下創建一個Django項目。
利用命令創建一個Django項目
1 創建一個django project
django-admin.py startproject PEOJECTNAME
創建完畢后:會產生PROJECTNAME命名的文件夾,里面包含 PROJECTNAME 目錄 和 manager.py文件。
其中:
-
- manager.py 用來管理我們所有的命令的文件
- PROJECTNAME:存放全局的相關文件
-
- settings.py:項目的配置信息文件
- urls.py:把用戶發過來的url通過urls文件來過濾(控制),交給某個view函數來處理 -->控制器
- wsgi.py:處理socket相關的任務(類nginx/apache的功能) --> 一個 WSGI 兼容的 Web 服務器的入口,以便運行你的項目。 生產上一般不會使用wsgi(第三方模塊),一般會使用uwsgi + nginx 來運行。
2 創建一個app
python manage.py startapp APPNAME
為什么一個項目里要創建一個應用?
舉個例子:微信是一個項目,那么通訊錄,錢包,朋友圈就是一個個相互獨立功能,這里叫做應用。一個項目是所有應用的集合。
創建一個應用之后會產生同名目錄,目錄下的主要文件功能如下:
-
- models.py:MTV框架,存放和數據進行操作的代碼
- admin.py:django的數據庫后台管理工具
- views.py:MTV框架,包含所有的視圖函數,由urls.py 來選擇發送給具體哪個view函數
- test.py:用來測試的
注意:views.py 中的函數:一定是來根據用戶輸入的內容來返回一個具體的頁面,wsgi模塊用來負責HTTP協議的解析,然后放在views視圖函數中,作為一個固定的參數(request)使用。
3 啟動Django項目
python manage.py runserver PORT
啟動一個web應用服務,並監聽8800端口
Django 版的 hello world
1 定義URL選擇器
定義url選擇器是為了能在用戶輸入url的時候把對應的url映射到指定的views函數中去處理,所以urls.py會存放在項目全局的目錄下。
from blog import views # blog為項目名,導入views模塊 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/',views.blog), # 匹配已blog結尾的url,交給views下的index函數處理 url(r'',views.index), # 就可以表示首頁 ] # 注意:這里的url,是正則表達式
2 定義views函數
經過項目的urls控制,那么請求將會分配給具體的應用(函數)來處理,所以需要在應用的目錄中的views.py文件中進行添加。由於我們返回的是靜態頁面,不需要進行數據庫相關的處理,所以在index函數內,可以直接返回頁面給客戶端
這里有兩種方式:直接返回html標簽、返回html文件
返回html標簽:
from django.shortcuts import render,HttpResponse
# request:請求信息對象 HttpResponse:響應信息對象 def index(request): return HttpResponse('<h1>hello world</h1>')
# 注意:用戶的請求經由wsgi處理后,會返回一個對象標識,一般這里使用request來接受這個對象。 # HttpResponse 用戶對客戶的相應
返回html文件:
所有的html文件是由Templates來進行處理的,所以需要現在Templates目錄下創建要返回的html文件

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>hello world</h1> 9 </body> 10 </html>
from django.shortcuts import render,HttpResponse # request:請求信息對象 HttpResponse:響應信息對象 def index(request): return render(request,'index.html') # render,的第一個參數就是request,標識把后面的index文件返回給這個request # 答復的html文件,render會去Templates下尋找,因為在setting.py文件中已經定義了Templates的路徑
3 啟動服務
# 在項目目錄下執行 python manage.py runserver 8800
啟動完畢后,使用瀏覽器訪問。
127.0.0.1:8800/blog # 這里的blog表示應用的名稱

1 # 思路或者說流程如下 2 # 1.定義登陸界面的urls 3 # 2.定義處理函數 4 # 3.定義返回的頁面 5 6 7 # 1.定義項目的urls 8 from django.conf.urls import url,include 9 from django.contrib import admin 10 from blog import views 11 12 urlpatterns = [ 13 url(r'^login_in/', views.login_in), 14 ] 15 16 ======================================== 17 18 # 2.定義處理函數views.login_in 19 def login_in(request): 20 21 if request.method == 'GET': 22 23 if not request.GET : 24 return render(request,'login_in.html') 25 else: 26 username = request.GET.get('username',None) 27 password = request.GET.get('password',None) 28 29 if username == 'dachenzi' and password == '123456': 30 return HttpResponse('login Success!') 31 else: 32 return HttpResponse('Wrong username or password!') 33 if request.method =='POST': 34 if not request.POST: 35 return render(request,'login_in.html') 36 else: 37 username = request.POST.get('username', None) 38 password = request.POST.get('password', None) 39 40 if username == 'dachenzi' and password == '123456': 41 return HttpResponse('login Success!') 42 else: 43 return HttpResponse('Wrong username or password!') 44 # 注意這里第一次訪問會返回login_in頁面,而在login頁面中,我們提交了用戶名密碼等信息,由於我們沒有傳遞csrftoken信息,DJango會對POST請求默認拒絕,所以會顯示403,可以手動的關閉這個動作 45 # 在項目的配置文件中setting.py中取消django.middleware.csrf.CsrfViewMiddleware,即可 46 # request.POST/GET 會保存用戶提交的數據(類似字典的形式,可以使用字典來獲取) 47 # username = request.POST['username'] 48 # password = request.POST['pwd'] 49 # 上面這中方式也可以取,但是如果key不存在,那么會報錯.所以建議使用get方法 50 51 52 ======================================== 53 54 55 # 3.編寫返回的html文件 56 <!DOCTYPE html> 57 <html lang="en"> 58 <head> 59 <meta charset="UTF-8"> 60 <title>Title</title> 61 </head> 62 <body> 63 64 <form action="/login_in/" method="post"> 65 <p>姓名 <input type="text" name="username"></p> 66 <p>密碼 <input type="text" name="password"></p> 67 <p><input type="submit" value="登陸"> </p> 68 </form> 69 70 </body> 71 </html>
基礎配置
為了使我們的項目依賴的文件放在特定的地方提供訪問,那么我們在創建完項目還需要做一些基礎的配置。
配置靜態文件存放路徑
靜態文件:比如css,js文件都是要提供給用戶進行下載的,所以我們一般規定靜態文件,存放在django項目的一級子目錄static中,需要手動創建這個目錄,然后做如下配置。
# 修改settings.py文件 STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), # 添加靜態文件路徑 )
配置模板路徑
用來集中存放用於返回給用戶的模板文件(如果是利用pycharm創建的項目,它會自動幫我們創建template目錄,並自動進行關聯),創建templates目錄(同樣存放在django項目的一級子目錄下)。
# 修改settings.py文件 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] # 添加templates文件的路徑 , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
關閉csrftoken
csrftoken用於防止csrf攻擊,csrf的相關配置將在后面進行說明,所以這里可以先關閉csrftoken。
# 修改settings.py文件 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', # 注釋掉csrf中間件即可(需要重啟django項目) 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
路由系統(urls控制)
url控制其實就是把不同的url對應到不同的views函數中去
格式:
urlpatterns = [ url(正則表達式,views視圖函數,參數,別名), ]
url可以有多個,每個url都是一個獨立的規則。
參數如下:
-
regex: 正則表達式,與之匹配的 URL 會執行對應的第二個參數 view。
-
view: 用於執行與正則表達式匹配的 URL 請求。
-
kwargs: 視圖使用的字典類型的參數。 --> 很少使用
-
name: 用來反向獲取 URL。
URLconf的正則字符串參數
例子:
urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), # 把url加上括號,就表示取出,那么會默認傳給后面的views函數,如果不定義變量接受,就會報錯 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), ]
注意:
-
- 一旦匹配成功則不再繼續
- 不需要添加一個前導的反斜杠,因為每個URL 都有。例如,應該是^articles 而不是 ^/articles。
- 每個正則表達式前面的'r' 是可選的但是建議加上。
url的分組
若要從URL 中捕獲一個值,只需要在它周圍放置一對圓括號。分為無名分組和有名分組:
無名分組
即捕獲url中的一個值時,會當作參數按照位置傳遞給后面的views函數。
只需要把要分組的正則字符串用括號括起來即可。
urlpatterns = [ url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), ] # ([0-9]{4}) 第一個位置參數 # ([0-9]{2}) 第二個位置參數
views函數需要定義位置參數來一一對應
有名分組
即捕獲url中的一個值時,並賦予其名稱,使用關鍵字參數來進行傳遞。
在Python 正則表達式中,命名正則表達式組的語法是(?P<name>pattern),其中name 是組的名稱,pattern 是要匹配的模式。
例子:
urlpatterns = [ url(r'^articles/(?P<year>[0-9]{4})/(?P<mouth>[0-9]{2})/$', views.article_detail), ] # 會傳遞year和mouth 這兩個關鍵字參數。
這意味你的URLconf 會更加明晰且不容易產生參數順序問題的錯誤 —— 你可以在你的視圖函數定義中重新安排參數的順序。
URL匹配規則在什么上查找
URLconf 在請求的URL 上查找,將它當做一個普通的Python 字符串。不包括GET和POST參數以及域名。
-
- GET:把用戶發送的參數放在URL中進行傳遞,在URL中一般用?隔開。
- POST:把用戶發送的參數放在請求頭中傳遞。
例如,http://www.example.com/myapp/ 請求中,URLconf 將查找myapp/。
在http://www.example.com/myapp/?page=3 請求中,URLconf 仍將查找myapp/。
URLconf 不檢查請求的方法。換句話講,所有的請求方法 —— 同一個URL的POST、GET、HEAD等等 —— 都將路由到相同的函數。
指定視圖參數的默認值
有一個方便的小技巧是指定視圖參數的默認值。 下面是一個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 值。
上面的方法是通過給函數指定默認值,當然在url中也是可以指定默認值的
urlpatterns = [ url(r'text$',view.test,{'name':'test'}) # 等於在url傳遞的時候,傳遞name = test,給test函數 ]
include(路由分發)
當項目中的應用變得越來越多的時候,如果所有的應用的URL都通過項目的urls統一進行分配,那么耦合度會很高,另外也不利於管理,所以這里通過include來交給應用的urls來處理。
from django.conf.urls import include, url urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/', include('blog.urls')), ] # 通過include 來指定交給那個應用的urls來處理 # include 包涵在 django.conf.urls中

1 # 項目的urls 2 3 from django.conf.urls import url,include 4 from django.contrib import admin 5 from blog import views 6 7 urlpatterns = [ 8 url(r'^admin/', admin.site.urls), 9 url(r'^blog/',include('blog.urls')) 10 ] 11 12 13 #應用的urls 14 from django.conf.urls import url 15 from django.contrib import admin 16 from blog import views 17 18 urlpatterns = [ 19 url(r'^admin/', admin.site.urls), 20 url(r'^([0-9]{4})/([0-9]{2})/([0-9]{2})/$',views.index) 21 ]
注意:路由分發后,子路徑的起始位置就從分發的URL開始了。
urlpatterns = [ url(r'blog/',include('blog.urls')), ] # blog.urls urlpatterns = [ url(r'admin/$',views.admin), ] # 這里admin的路徑為: blog/admin/
別名(name參數)
當我們在做路徑匹配然后配合from表單等需要提交的數據的元素的時候會遇到以下問題
url文件:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), # 匹配路徑 ]
返回的頁面文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登錄頁面</h1> <form action="/login/" method="post"> <!-- 根據路徑進行提交 --> <p><h2>姓名</h2></p> <p><input type="text" name="username"></p> <p><h2>密碼</h2></p> <p><input type="text" name="password"></p> <p><input type="submit" value="登錄"></p> </form> </body> </html>
-
- 如果我們某一天改了url匹配的路徑,那么,就緒要修改頁面中所有的提交路徑,否則將會提交失敗
而url的name就是解決這個問題的。
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.login,name='LOGIN'), # 添加name關鍵字 ]
返回的html文件如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登錄頁面</h1> <form action="{% url 'LOGIN' %}" method="post"> <!-- 這里使用name關鍵字來動態的傳遞url --> <p><h2>姓名</h2></p> <p><input type="text" name="username"></p> <p><h2>密碼</h2></p> <p><input type="text" name="password"></p> <p><input type="submit" value="登錄"></p> </form> </body> </html>
這樣就算URL后期更改,也會動態指向最新的URL。
-
- 個人覺得說白了就是一個變量的替換,只不過是用的是django特有的語法格式。
- name='LOGIN' 就是把前面匹配的url保存到LOGIN中,然后Django 在返回html文件的時候再進行替換。
補充:
當url存在正則表達式的時候,只使用name參數是不行的。因為正則部分無法進行渲染。目前的解決方法是在url部分進行拼接
# --------------------------- url --------------------------- from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^abc/(\d+)/', views.login,name="LOGIN"), ] # -------------------------- Teamplates --------------------------- <form action="{% url "LOGIN" 4 %}"> # 這里正則匹配了幾個部分,那么就需要傳遞幾個參數 <p>Username:</p> <input type="text" name="username"> <p>Password:</p> <input type="text" name="password"> <input type="submit" value="提交"> </form>
反推URL
什么叫反推url?在views函數中,如果想要獲取當前函數對應的url,該怎么辦呢?還記得前面的name屬性嗎,反推url就是在views中根據name屬性的值,獲得對應的url
from django.urls import reverse url = reverse('name')
當然reverse還有兩個參數(args,kwargs)
args = () kwargs = {} # 參數是配合urls中的正則表達式的 url('^detail/(\d+)' ,name='i1',views.detail) -- > reverse('i1',args=(10,)) # 反推的URL為: detail/10 # kwargs則表示在命名關鍵字的情況下 url('^detail/(?P<nid>\d+)' ,name='i2',views.detail) -- > reverse('i1',kwargs={'nid':10}) # 反推的url為: detail/10
PS:還有一個方法是利用request對象的path_info,因為其中存放的是用戶提交的url。
命名空間
當在url(路由系統)中使用了include時,在views函數中,我們就無法單獨的利用name參數來反推url了,因為在include時,是無法使用name關鍵字的,不過django提供了其他關鍵字提供類似功能:namespace,稱作命名空間。
# urls.py url = [ url(r'crm/',include('crm.urls'),namespace='crm'), url(r'cmdb/',include('cmdb.urls'),namespace='cmdb'), ] # crm 中的 urls.py app_name = crm url = [ url(r'index/',views.index,name='index'), ] # crm 中的views.py def test(request): url = reverse('crm:index') # 這里通過namespace:name 來反向獲取url print(url) return HttpResponse(200)
PS:在html中,通過{% url 'crm:index' %} 也是通過namespace:name來獲取url的。
views視圖函數
一個視圖函數,或者簡短來說叫做視圖,是一個簡單的Python函數,它接受web請求,並且返回web響應。無論視圖本身包含什么邏輯,都要返回響應。
簡單的視圖函數
編寫一個簡單的視圖函數,通過訪問指定的url來獲取當前時間。
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.current_datetime), ]
views函數:
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
1. 訪問timer,會轉到current_datetime函數進行處理
2. 通過date模塊獲取當前時間,然后通過Httpresponse進行返回,每個視圖函數都要返回HttpResponse對象
注意:
- 請求對象 : Httprequest() 保存這請求對象的屬性以及值(函數的參數request其實就是它的實例化對象)
- 響應對象 : Httpresponse() 在views函數中,每次返回的都是一個Httpresponse的對象,render() 其實也是在內部進行渲染后返回的還是一個Httpresponse對象
快捷函數
快捷函數存放的位置是:django.shortcuts。它收集了“跨越” 多層MVC 的輔助函數和類。 換句話講,這些函數/類為了方便,引入了可控的耦合。
HttpResponse函數
返回給用戶的信息,必須是一個HttpResponse對象,接受字符串,或者字節。
return HttpResponse('hello world')
render函數
主要用來做模板的渲染
注意:不管什么函數最后都會返回 Httpresponse對象,只不過render函數對這個對象進行封裝了。
格式如下:
render(request,template_name,context=None)
-
- request:表示經過wsgi處理后返回的一個請求對象
- template_name:表示要返回的模板文件的名稱
- context:表示要經過render渲染的變量及key的對應關系
通過render函數編寫訪問獲得時間例子:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.timer), # 定義url入口 ]
views函數:
def timer(request): import datetime now = datetime.datetime.now() return render(request,'timer.html',{'TIME':now}) # 構建context關鍵字,進行傳遞
返回的html文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>current time {{ TIME }}</h1> <!-- 變量引用 --> </body> </html> <!-- 變量會在 render返回的時候進行渲染 -->
redirect函數
用於重定向跳轉
格式如下:
redirect('/url/')
-
- 跳轉到新的url進行訪問
urls文件
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^timer/$', views.timer), url(r'^login/$', views.login,name='LOGIN'), # 登陸頁面 url(r'^check/$', views.check,name='CHECK'), # 檢查用戶名 url(r'^welcome/$', views.welcome), # 歡迎頁面 ]
views文件
def login(request): return render(request,'login.html') def check(request): username = request.POST.get('username',None) password = request.POST.get('password',None) if username == 'dachenzi' and password == '123': return redirect('/welcome/') #跳轉鏈接到welcome def welcome(request): return render(request,'welcome.html')
PS:服務端應答的是要跳轉的url,告訴client要去訪問返回的這個url,client接受后,會訪問應答的url。
request到底是什么
在views中定義函數的參數request到底是什么?包含了什么?它有哪些屬性,方法?
我們通過打印它所屬的類,類查看相關的類屬性(或者在views函數中,直接打印request)
# views函數 def index(request): print(type(request)) return render(request,'index.html') # 查看打印結果 <class 'django.core.handlers.wsgi.WSGIRequest'>
導入這個模塊來查看屬性信息
class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s/%s' % (script_name.rstrip('/'), path_info.replace('/', '', 1)) self.META = environ self.META['PATH_INFO'] = path_info self.META['SCRIPT_NAME'] = script_name self.method = environ['REQUEST_METHOD'].upper() self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', '')) ... ... ... ...
代碼較多這里就不列出了。注意到各個參數都是通過一個叫environ的東西來獲取的,那么什么是enviro呢?我們來打印一下

1 {'PROCESSOR_REVISION': '3a09', 2 'PROCESSOR_LEVEL': '6', 3 'CONTENT_TYPE': 'text/plain', 4 'PATH_INFO': '/welcome/', 5 'PUBLIC': 'C:\\Users\\Public', 6 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 7 'COMPUTERNAME': 'MANINTHEMIRROR', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel', 8 'ASL.LOG': 'Destination=file', 9 'wsgi.multithread': True, 10 'PYTHONIOENCODING': 'UTF-8', 11 'PROGRAMW6432': 'C:\\Program Files', 12 'SYSTEMROOT': 'C:\\WINDOWS', 13 'GOROOT': 'E:\\Go\\', 14 'USERDOMAIN': 'MANINTHEMIRROR', 15 'LOGONSERVER': '\\\\MANINTHEMIRROR', 16 'SCRIPT_NAME': '', 17 'LOCALAPPDATA': 'C:\\Users\\Are you SuperMan\\AppData\\Local', 18 'DJANGO_SETTINGS_MODULE': 'myblog.settings', 19 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 20 'HOMEDRIVE': 'C:', 21 'APPDATA': 'C:\\Users\\Are you SuperMan\\AppData\\Roaming', 22 'ONEDRIVE': 'C:\\Users\\Are you SuperMan\\OneDrive', 23 'PROCESSOR_ARCHITECTURE': 'AMD64', 24 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, sdch, br', 25 'PATH':'D:\\Python35\\Scripts\\;D:\\Python35\\;C:\\ProgramData\\Oracle\\Java\\javapath;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Common Files\\Acronis\\SnapAPI\\;D:\\Program Files\\TortoiseSVN\\bin;M:\\Program Files\\Git\\cmd;D:\\Python27;D:\\Python27\\Scripts\\;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;H:\\putty;"C:\\Users\\Are you SuperMan\\Desktop\\Icon";D:\\Program Files\\IDM Computer Solutions\\UltraEdit\\;C:\\Users\\Are you SuperMan\\AppData\\Local\\Microsoft\\WindowsApps;', 26 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 27 'USERDOMAIN_ROAMINGPROFILE': 'MANINTHEMIRROR', 28 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>, 29 'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 30 'OS': 'Windows_NT', 31 'REMOTE_HOST': '', 32 'USERNAME': 'Are you SuperMan', 33 'USERPROFILE': 'C:\\Users\\Are you SuperMan', 34 'PYTHONPATH': 'D:\\python Django 學習\\myblog', 35 'wsgi.input': <_io.BufferedReader name=280>, 36 'RUN_MAIN': 'true', 37 'SERVER_SOFTWARE': 'WSGIServer/0.2', 38 'SERVER_PORT': '8000', 39 'REQUEST_METHOD': 'GET', 40 'HTTP_CONNECTION': 'keep-alive', 41 'MOZ_PLUGIN_PATH': 'M:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\', 42 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 43 'PYTHONUNBUFFERED': '1', 44 'TMP': 'C:\\Users\\AREYOU~1\\AppData\\Local\\Temp', 45 'PSMODULEPATH': 'C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', 46 'PROGRAMFILES': 'C:\\Program Files', 47 'REMOTE_ADDR': '127.0.0.1', 48 'HTTP_COOKIE': 'csrftoken=wHbeFOZqTco2hK46d2kK3O4n6zHJNTJ4xkxIMSLudnP0g3GjMqPD9oug6fJeCCw7', 49 'NUMBER_OF_PROCESSORS': '8', 50 'wsgi.version': (1, 0), 51 'SERVER_NAME': 'ManInTheMirror', 52 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 53 'AWE_DIR': 'M:\\Program Files (x86)\\Khrona LLC\\Awesomium SDK\\1.6.6\\', 54 'GOPATH': 'E:\\GO\\bin', 55 'wsgi.multiprocess': False, 56 'wsgi.url_scheme': 'http', 57 'TEMP': 'C:\\Users\\AREYOU~1\\AppData\\Local\\Temp', 58 'HOMEPATH': '\\Users\\Are you SuperMan', 59 'HTTP_HOST': '127.0.0.1:8000', 60 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 61 'CONTENT_LENGTH': '', 62 'SESSIONNAME': 'Console', 63 'wsgi.run_once': False, 64 'WINDIR': 'C:\\WINDOWS', 65 'QUERY_STRING': '', 66 'SERVER_PROTOCOL': 'HTTP/1.1', 67 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.8', 68 'PYCHARM_HOSTED': '1', 69 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 70 'COMSPEC': 'C:\\WINDOWS\\system32\\cmd.exe', 71 'FP_NO_HOST_CHECK': 'NO', 72 'SYSTEMDRIVE': 'C:', 73 'ALLUSERSPROFILE': 'C:\\ProgramData', 74 'GATEWAY_INTERFACE': 'CGI/1.1', 75 'HTTP_CACHE_CONTROL': 'max-age=0', 76 'PROGRAMDATA': 'C:\\ProgramData', 77 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 78 'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 79 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW'}
通過上面的信息我們可以看到environ里面存放的都是用戶發送的信息,比如HTTP報頭。那么如果我想獲取用戶的瀏覽器標識就可以使用
request.environ.get['HTTP_USER_AGENT'] # 也可以針對不同的USER_AGENT來返回不同的頁面
注意:
- 用戶提交的數據,django內部會進行解析,能識別的會放在對應的對象中(比如request.GET,request.POST)。
- 對於PUT,DELETE方法,django沒有提供解析方式,提交的數據會存放在request.body中。
- 關於請求頭的信息可以通過request.environ,也可以通過request.Meta獲取,因為它倆是一個東西。
獲取用戶提交數據
# 獲取數據 request.method # 存放用戶訪問訪問方法 post/get request.POST # 存放用戶POST提交的數據 request.GET # 存放用戶GET提交的數據 request.environ # 獲取未經django解析的,用戶提交的原數據。 request.FILES # 存放用戶上傳的文件 ... ... # 獲取數據的方式 .get('name') # 獲取單個數據 .getlist('name') # 獲取列表數據(比如checkbox等)
form表單上傳文件
針對form表單上傳的文件,首先,form表單應該具有 enctype="multipart/form-data" 屬性,否則文件無法上傳,服務端要想獲取用戶上傳的文件使用 request.POST.get('name') 是無法獲取到信息的(表單不加form-data屬性,這里會獲取到文件名,因為上傳的時候就沒有上傳文件的內容)。
想要獲取用戶上傳的文件,那么需要在request.FILES中獲取。
# request.FILES obj = request.FILES.get('name') # 獲取文件對象 obj.name() # 文件的名稱 obj.chunks() # 文件的內容(生成器) obj.size # 文件的大小
循環寫入服務端
import os ... obj = request.FILES.get('filename') filename = os.path('upload',obj.name) with open(filename,'wb) as f: for chunk in obj.chunks(): f.write(chunk) ...
Template
使用 Django的 模板系統 (Template System)來實現將Python代碼和HTML代碼分開的目的。
python的模板包涵:HTML代碼+邏輯控制代碼
將具體數據嵌入到前端模板的語法,在一個html文件中包含模板語法的文件,可以認為是模板文件
模板的基礎語法
主要分為兩部分:渲染變量使用雙大括號{{ }},渲染標簽則使用雙大括號雙百分號{% %}
變量
在html頁面中使用兩個大括號包起來的字符串叫做變量:
{{ Var_name }}
這里通過python django的shell環境來舉例(在這個環境中可以直接引用 所屬django模塊中的變量等信息)
# 使用 manage.py進入shell界面 python manage.py shell >>> from django.template import Context,Template >>> t = Template('My name is {{ name }}') >>> c = Context({'name':'dachenzi'}) >>> t.render(c) # render會把變量進行渲染 'My name is dachenzi' >>>
深度查詢
如果傳遞的變量是屬組或者字典,那么需要進行深度查詢(通過使用.(點)來完成深度查詢):
{{ Var_Name.username }} #獲取Var_Name中的key為:username的值 --> 變量為字典類型 {{ Var_Name.2 }} # 獲取Var_Name中第2個值 --> 變量為list類型
使用.帶進行深度查詢,查詢list類型的數據
>>> from django.template import context,Template >>> t = Template('hello {{ name.1}}') >>> c = Context({'name':[1,2,3]}) >>> t.render(c) 'hello 2'
使用items循環字典類型
{% for key,value in user_dict.items %} <li>{{ key }}-{{ value }}</li> {% endfor %}
變量過濾器filter
可以理解為python中的內置函數,過濾器是模板的特有語法,通過前端來過濾部分數據。注意filter只能處理一個參數。
格式:
{{ var|method:parameter}}
method主要有:
# 1 add : 給變量加上相應的值 {{ num|add:3}} # 表示給后端傳過來的num的值加上3 # 2 addslashes : 給變量中的引號前加上斜線 {{ book_info.book_name|addslashes }} #如果變量中包涵引號,打印的時候會添加斜線來轉意 # 3 capfirst : 首字母大寫 {{ book_info.book_name|capfirst }} # 4 cut : 從字符串中移除指定的字符 {{ book_info.book_name|cut:'L' }} #刪除變量中的L # 5 date : 格式化日期字符串 {{ book_info.book_time|date:'Y-m-d' }} # 按照date指定的格式,進行顯示 # 6 default : 如果值是False,就替換成設置的默認值,否則就是用本來的值 # 7 default_if_none: 如果值是None,就替換成設置的默認值,否則就使用本來的值 # 8 safe : 是否解析html標簽 {{ book_info.book_name|safe }} #當變量是一個html標簽的時候,為了安全隱患是不會進行解析的,如果想要讓前端解析,那么需要使用safe方法說明其是安全的。 # 9 切片相關 {{ value7|filesizeformat }}<br> 文件的字節數 {{ value7|first }}<br> 第一個字符 {{ value7|length }}<br> 長度 {{ value7|slice:":-1" }}<br> 切片
自定義過濾器之filter
a、在app中創建templatetags模塊(必須的) #名稱必須為templatetags
b、在目錄下創建任意 .py 文件,如:my_tags.py #用來存放自定義的tag
c、編寫自定義過濾器
from django import template # 需要先導入文件 register = template.Library() #register的名字是固定的,不可改變,因為下面會進行調用 @register.filter def multi(num1,num2): return num1 * num2
d、在引用的html文件中進行加載
{% load my_tags %} # 在頁面頂部,當和extend同時使用時,放在extend下面
e、調用
{{ hello|multi:2 }} # hello 表示傳遞的第一個參數,2表示傳遞的是第二個參數,冒號后面必須要緊跟傳遞的參數,多一個空格都不行
PS:filter 的函數只能接受一個額外參數(兩個參數,默認會把要處理的對象當作第一個參數,第二個參數是我們需要傳遞的參數),可以用於if/for等語句的條件,渲染時 使用 {{ 參數1 | xxx:參數2 }} 渲染 。
自定義過濾器之simple_tag
a、在app中創建templatetags模塊(必須的) #名稱必須為templatetags
b、在目錄下創建任意 .py 文件,如:my_tags.py #用來存放自定義的tag
c、編寫自定義過濾器
from django import template # 需要先導入文件 register = template.Library() #register的名字是固定的,不可改變,因為下面會進行調用 @register.simple_tag # 必須要裝飾我們自定義的函數才行 def simple_tag_multi(num1,num2): return num1 * num2
d、在引用的html文件中進行加載
{% load my_tags %} # 在頁面頂部,當和extend同時使用時,放在extend下面
e、調用
{% simple_tag_multi 10 20 %} # 參數以空格隔開,多個空格效果相同
PS:simpletag 可以接受多個參數,但不可以用if/for等語句的條件,使用 {% xxx %} 渲染
邏輯控制語法
循環等邏輯語句需要使用{% %} 來進行渲染
for循環例子:
{% for i in li %} <p>{{ i }}</p> {% endfor %} #使用endfor來表示循環結束
在for循環中存在一個forloop變量,該變量記錄循環相關數據
- forloop.counter 表示當前迭代數(第幾次循環)從1開始
- forloop.counter0 同上,但是從0開始
- forloop.first 判斷此次循環是否是第一次循環,是則返回True
- forloop.revcounter 表示當前迭代數(第幾次循環)- 倒序
- forloop.last 判斷此次循環是否是最后一次循環
- forloop.parentloop 用於在嵌套循環時,獲取父循環的上面的信息
if循環例子:
{% if li.1 > 100 %} <p> 大於 </p> {% elif li.1 < 100 %} <p> 小於 </p> {% else %} <p> 等於 </p> {% endif %} #使用endif來表示條件判斷結束
其他渲染標簽
{% url 'varname' %}
引用路由配置的地址
<form action="{% url "LOGIN"%}" method="POST"> # 路由引用 <input type="text"> <input type="submit" value="提交"> </form>
{% verbatim %}
禁止render解析。
既在有的時候我們僅僅只是想打印出 {{ hello }} 而已,使用verbatim來禁止render進行解析
{% verbatim %} {{ hello }} {% endverbatim %} #使用verbatim 包起來的代碼 將不會進行解析
extends模板繼承
到目前為止,我們的模板范例都只是些零星的 HTML 片段,但在實際應用中,你將用 Django 模板系統來創建整個 HTML 頁面。 這就帶來一個常見的 Web 開發問題: 在整個網站中,如何減少共用頁面區域(比如站點導航)所引起的重復和冗余代碼?Django 解決此類問題的首選方法是使用一種優雅的策略—— 模板繼承 。
本質上來說,模板繼承就是先構造一個基礎框架模板,而后在其子模板中對它所包含站點公用部分和定義塊進行重載。
編寫模板需要使用模板標簽:
{% block name%} {% endblock %}:所有的 {% block %} 標簽告訴模板引擎,子模板可以重載這些部分。 每個{% block %}標簽所要做的是告訴模板引擎,該模板下的這一塊內容將有可能被子模板覆蓋。

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 <style> 8 * { 9 margin:0; 10 padding: 0; 11 } 12 .title { 13 width:100%; 14 height:100px; 15 background-color: darkblue; 16 position: absolute; 17 top:0; 18 } 19 .left { 20 width: 200px; 21 position: absolute; 22 top: 100px; 23 left:0; 24 bottom: 0; 25 background-color: green; 26 } 27 .right { 28 position: absolute; 29 top: 100px; 30 left: 200px; 31 bottom:0; 32 right:0; 33 overflow: auto; 34 {# background-color: wheat;#} 35 } 36 .left_list { 37 text-align: center; 38 padding-top: 20px; 39 } 40 </style> 41 42 43 44 </head> 45 <body> 46 47 <div class="title"></div> 48 <div class="left"> 49 <div class="left_list"> 50 <a href="/booklist/">文章列表</a> 51 </div> 52 53 54 </div> 55 <div class="right"> 56 {% block content %} # 這里定義content,表示這里可以被繼承模板進行替換 57 {% endblock %} # 包起來的表示代碼段 58 </div> 59 60 61 62 </body> 63 </html>
子模板進行繼承,並定制自己要顯示的內容(子模板不需要額外的html代碼,其代碼都來自於模板文件)。
{% extends 'index.html' %} # 繼承模板文件 {% block content %} # 針對模板中content的代碼塊進行定義 <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> <h1>hello world</h1> {% endblock %}
模板使用的訣竅
<1>如果在模板中使用 {% extends %} ,必須保證其為模板中的第一個模板標記。 否則,模板繼承將不起作用。 <2>一般來說,基礎模板中的 {% block %} 標簽越多越好。 記住,子模板不必定義父模板中所有的代碼塊,因此 你可以用合理的缺省值對一些代碼塊進行填充,然后只對子模板所需的代碼塊進行(重)定義。 俗話說,鈎子越 多越好。 <3>如果發覺自己在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的某個 {% block %} 中。 如果你需要訪問父模板中的塊的內容,使用 {{ block.super }}這個標簽吧,這一個魔法變量將會表現出父模 板中的內容。 如果只想在上級代碼塊基礎上添加內容,而不是全部重載,該變量就顯得非常有用了。 <4>不允許在同一個模板中定義多個同名的 {% block %} 。 存在這樣的限制是因為block 標簽的工作方式是雙向的。 也就是說,block 標簽不僅挖了一個要填的坑,也定義了在父模板中這個坑所填充的內容。如果模板中出現了兩個 相同名稱的 {% block %} 標簽,父模板將無從得知要使用哪個塊的內容。
<5>一個html文件只能引入一個模版
include引入
在很多網站中,基本上的都會有一個開頭和一個結尾,在每一個網頁中都會顯示。相對於這種的來說,在Django中,最好的方法就是使用include的標簽,在每一個模板中都加入這個開頭和結尾的標簽。
簡單來說,就是那些多個頁面都需要的公共標簽放在一個統一的html文件,供其他html文件引入。
{% include 'tpl.html' %} : 表示引入tpl.html文件。
# -------------------- tpl.html -------------------- <div> <h1>hello world</h1> </div> # -------------------- xxx.html -------------------- ... <div> {% include 'tpl.html' %} {% include 'tpl.html' %} ... </div> ...
PS:在一個頁面中可以通過include引入不同的公共組件。
數據庫與ORM
對象關系映射(英語:(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),是一種程序技術,用於實現面向對象編程語言里不同類型系統的數據之間的轉換 。從效果上說,它其實是創建了一個可在編程語言里使用的--“虛擬對象數據庫”。
簡單一句話來說:就是把數據庫的表映射為一個個對象,對對象的操作會被映射成SQL語句,在數據庫執行。
數據庫的配置
1 django默認支持sqlite,mysql, oracle,postgresql數據庫。
<1> sqlite
django默認使用sqlite的數據庫,默認自帶sqlite的數據庫驅動 , 引擎名稱:django.db.backends.sqlite3
<2> mysql
引擎名稱:django.db.backends.mysql
2 mysql驅動程序
- MySQLdb(mysql python)
- mysqlclient
- MySQL
- PyMySQL(純python的mysql驅動程序)
3 在django的項目中會默認使用sqlite數據庫,在settings里有如下設置:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }

1 DATABASES = { 2 3 'default': { 4 5 'ENGINE': 'django.db.backends.mysql', 6 7 'NAME': 'books', #你的數據庫名稱 8 9 'USER': 'root', #你的數據庫用戶名 10 11 'PASSWORD': '', #你的數據庫密碼 12 13 'HOST': '', #你的數據庫主機,留空默認為localhost 14 15 'PORT': '3306', #你的數據庫端口 16 17 } 18 19 }
PS:
NAME即數據庫的名字,在mysql連接前該數據庫必須已經創建,而上面的sqlite數據庫下的db.sqlite3則是項目自動創建,USER和PASSWORD分別是數據庫的用戶名和密碼。
設置完后,再啟動我們的Django項目前,我們需要激活我們的mysql。然后,啟動項目,會報錯:no module named MySQLdb,這是因為django默認你導入的驅動是MySQLdb,可是MySQLdb對於py3有很大問題,所以我們需要的驅動是PyMySQL
所以,我們只需要找到項目名文件下的__init__,在里面寫入:
import pymysql pymysql.install_as_MySQLdb()
問題解決!
ORM表模型
我們所說的ORM主要分為兩種:
- DB First 數據庫里先創建數據庫表結構,根據表結構生成類,根據類操作數據庫
- Code First 先寫代碼,執行代碼創建數據庫表結構
主流的orm都是code first。django 的orm也是code first,所以本質上分為兩部分:
- 根據類自動創建數據庫表
- 根據類對數據庫表中的數據進行各種操作
創建表對象
現在有一張表,主要字段如下:
對應的python語句為:
from django.db import models # 導入models,django提供的對象包含很多建表的方法 # Create your models here. class UserInfo(models.Model): # 需要繼承models.Model id = models.AutoField(primary_key=True,null=False,verbose_name='ID') name = models.CharField(max_length=4,null=False,verbose_name='用戶名') password = models.CharField(max_length=64,null=False,verbose_name='密碼') email = models.EmailField(null=False,verbose_name='郵箱') # AutoField : 自增字段,類似於mysql的int字段加auto_increment屬性 # CharField:可變長字段,類似於mysql的varchar類型,需要指定長度 # EmailField:郵件字段,僅僅提供給 django admin進行約束使用,映射到MySQL上,根本上也是字符串類型 # null:是否為空,通用參數,默認為否。 # verbose_name:django admin上對表操作時,顯示的字段名稱 # primary_key:主鍵 # max_length:針對於CharField字段,標示其長度
更多的Django字段類型
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) - 二進制類型
django中提供了很多的字段類型,大部分都是提供給django admin 來做驗證的,實際體現在數據庫中的,大部分都是字符串類型。
常用的字段參數
django中提供了很多參數對字段進行控制
null 是否可以為空 default 默認值 primary_key 主鍵 db_column 列名 db_index 索引(bool) unique 唯一索引 --------------時間日期類---------------- unique_for_date 對日期字段來說,表示只對時間做索引 unique_for_month 對日期字段來說,表示只對月份做索引 unique_for_year 對日期字段來說,表示只對年做索引 auto_now 無論是你添加還是修改對象,時間為你添加或者修改的時間。 auto_now_add 為添加時的時間,更新對象時不會有變動。 ---------------django admin ------------- choices 在django admin中顯示下拉框,避免連表查詢(比如用戶類型,可以在存在內存中) blank 在django admin中是否可以為空 verbose_name 字段在django admin中顯示的名稱 editable 在django admin中是否可以進行編輯,默認是true error_message 當在django admin中輸入的信息不匹配時,字段的提示信息 help_text 在django admin中輸入框旁邊進行提示 validators 在django admin中自定義規則限制
生成表
前面我們已經編寫了對應數據庫表的類,這里我們將進行實例化(創建數據庫對應的表)。
利用django提供的命令進行數據庫的初始化工作.(以及其他對數據庫表進行修改的動作,比如修改表結構,字段屬性等,都需要執行如下步驟)
# 進入項目目錄下執行 python manage.py makemigrations # 大致含義是:把類轉換成對應格式的sql語句。 # 創建表 python manage.py migrate # 在數據庫中執行生成的sql語句
注意:
- 執行makemigrations后生成的文件,存放在應用目錄下的migrations目錄下,一般以0001_initial.py為文件名並且依次遞增。
- 0001_initial.py存放的是 django 根據我們寫的 class 生成的相關代碼,執行migrate后,會根據這些代碼生成對應的數據庫表。
- 如果我們執行migrate后沒有生成我們創建的表格,那么需要確認在django配置文件中,是否加載了我們的應用(因為django會加載setting里面的install_app中查找對應的modules.py)
- 表名默認是:應用名_class類名

1 INSTALLED_APPS = [ 2 'django.contrib.admin', 3 'django.contrib.auth', 4 'django.contrib.contenttypes', 5 'django.contrib.sessions', 6 'django.contrib.messages', 7 'django.contrib.staticfiles', 8 'app01.apps.App01Config', 9 '你的app名稱' 10 ]
PS:當我們對已生成的表添加新字段時,會出現如下情況
ou are trying to add a non-nullable field 'code' to business without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option:
由於我們添加字段會影響之前的數據,所以這時django提醒我們,不能只是單純的添加一個字段,還需要為該字段指定對應的值,當然有幾種解決辦法:
- 設置新增字段default屬性,表示其默認值
- 設置新增字段null屬性,表示其默認可以為空
- 根據上面的提示,選擇1,手動輸入默認值即可(注意默認值需要用引號引起來,不然django認為輸入的數據是個變量)
利用orm完成數據庫的增刪改查
前面我們利用django 提供的orm創建了類對應數據庫的表,接下來就需要利用orm來對數據庫的表數據進行增刪改查等基本操作了。
orm之增加
根據django的MTV架構,業務的處理是在views中完成的,那么對數據庫的查詢邏輯也應該在views中編寫,而我們定義的數據庫對象在model.py中,那么在導入之后,才能進行表操作。
django提供了兩種增加數據的方式:1、create,2、save
#------------------------------------- create ------------------------------------- from app01 import models # 從自己的應用中導入models模塊 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' models.UserInfo.objects.create(name=username,password=password,email=email) # 表對象.objects.create() 用來新增數據 # name,password,email 表示表的字段 # 等號右邊的表示數據 # 傳遞的key也可以是字典利用**dic進行傳遞,在后面利用form的時候會經常使用這種方式。 dict = {'usernane':'daxin','password':'abc.123',email='daxin@qq.com'} models.UserInfo.objects.create(**dict) #------------------------------------- save ------------------------------------- from app01 import models # 從自己的應用中導入models模塊 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' userobj = models.UserInfo(name=username,password=password,email=email) userobj.save() # save的方式是利用對象實例化的傳遞參數后,調用save方法進行保存的 # 利用對象,我們可以方便的進行數據修改后,再進行save # 比如 userobj.name = 'dahuaidan' # userobj.save()
注意:利用create新增數據時,會默認返回新增加的數據對象,我們可以接受該對象來進行其他的操作,比如添加多對多關系等
PS:比較常用的方式是 利用create進行數據的增加
orm之刪除
想要對數據進行刪除,首先需要匹配到數據,然后執行刪除操作,那么就涉及兩部分:查找,刪除
models.UserInfo.objects.filter(id=1).delete() # 利用filter對數據唯一的字段進行過濾,匹配到刪除的行,也可以用all()匹配所有數據,進行刪除 # 利用delete方法進行刪除操作
orm之查找
查找數據時非常靈活,我們可以針對不同的需求進行搭配使用。
# 獲取表里所有的對象 models.UserInfo.objects.all() # 結果為一個QuerySet對象(django提供的),可以理解為一個列表 # 獲取id為1的對象 models.UserInfo.objects.filter(id=1) # 類似於sql中的 where查詢條件,結果也是一個QuerySet # 獲取name字段包含da的記錄 models.UserInfo.objects.filter(name__contains='da') # 這里涉及了萬能的雙下划線,在后續會進行說明 # get獲取一條數據 models.UserInfo.objects.get(id=1) # 注意get不到數據,會直接拋出異常 # 除了id等於1的其他數據 models.UserInfo.objects.exclude(id=1)
注意:
- 查找到的不是想sql語句那樣直接列出一行的各個字段及對應的值,而已把改行表示為一個行對象。匹配到一行,就表示1個表中的行對象。
- 每個對象內才包含了對應的字段和值。
- 通過 行對象.字段名,來獲取對應的數據。
- filter中包含多個條件,那么它們是and的關系。
PS:由於filter、all取到的數據默認是 QuerySet格式,在某些場景下我們只需要驗證是否取到,我們可以直接獲取結果中的第一個數據即可,即在最后添加.first()即可,表示取第一個數據,或者使用.count()來統計匹配到的數據的個數。
module.UserInfo.filter(username='daxin').first()

1 fieldname__contains='' # 表示該fieldname包涵的關鍵字 2 fieldname__lt = '' # 表示filedname小於 3 fieldname__gte = '' # 表示fieldanem 大於等於
PS:由於all取到的是所有數據,返回的是一個對象,很多時候用起來並不是那么方便,在SQL中還有select id,name from table 這種只取固定某些列的語句,當然在djang中也是存在的,利用values(還有values_list,只是內部由字典變成了元組)
models.Userinfo.object.all().values('id','username') # 表示只取指定的id和username列 # 結果依舊返回的是QuerySet,但是數據不同,體現的形式是一個字典 QuerySet({'id':1,'username':'daxin'},{'id':2,'username':'dachenzi'}......) # 使用value_list('id','username'),體現的形式是一個元組 QuerySet((1,'daxin'),(2,'dachenzi')......)
orm之修改
整體思路和刪除是相同的,首先查找到數據,然后對字段進行修改
models.UserInfo.objects.filter(id=1).update(name='dachenzi') # 利用filter過濾到要修改的數據,然后更新它的name字段的值
其他
有的時候我們想要查看我們寫的代碼對應的sql語句到底是什么,那么可以使用query來進行輸出
print(modules.Book.objects.all().query) # SELECT "app01_book"."nid", "app01_book"."title", "app01_book"."price", "app01_book"."pub_id" FROM "app01_book"
表與表之間的關系
我們知道,在數據庫中表與表的關系主要有三種,一對一,一對多,多對多,在 django中提供了專門的類型來做表之間的關聯
一對多
表示當前表的某個字段的值,來自於其他表,比如人員表和部門表,在人員表中利用一對多外鍵關系標明該員工屬於哪個部門。
user_type = models.ForeignKey('表名',to_field='字段') # 默認會自動關聯對方表的ID字段(主鍵),手動指定的話必須是唯一列才行。
注意:
- 默認情況下,外鍵字段在數據庫中存儲的名稱為:字段名_id (上面的例子的話,在數據庫中的字段名:user_type_id)
- 外鍵字段(user_type_id)存放的是所關連表的id信息
- 同時還存在一個字段名(上面的例子就是user_type)對象,這個對象指代的就是所關聯的表中的數據對象
- 我們可以通過user_type,來跨表獲取其關聯的信息的某寫字段的值(通過.字段名,即可訪問)
正向查詢
什么叫正向查詢?還是拿人員表和部門表舉例,外鍵關系存在人員表中,那么我們通過人員表利用表中的外鍵字段就可以查詢到該人員的部門信息,我一般稱之為正向查詢。

1 # -------------------- models.py -------------------- 2 3 from django.db import models 4 5 # Create your models here. 6 7 8 class Business(models.Model): 9 10 caption = models.CharField(max_length=32) 11 code = models.CharField(max_length=16,default='SA') 12 13 14 class Host(models.Model): 15 16 nid = models.AutoField(primary_key=True) 17 hostname = models.CharField(max_length=16) 18 ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) 19 port = models.IntegerField() 20 b = models.ForeignKey(to='Business',to_field='id') # 外鍵關聯Business表 21 22 23 # -------------------- views.py -------------------- 24 25 def host(request): 26 27 v1 = models.Host.objects.all() 28 29 return render(request,'host.html',{'v1':v1}) 30 31 32 33 # -------------------- host.html -------------------- 34 35 <!DOCTYPE html> 36 <html lang="en"> 37 <head> 38 <meta charset="UTF-8"> 39 <title>Title</title> 40 </head> 41 <body> 42 <div> 43 <p>主機信息</p> 44 {% for row in v1 %} 45 <p>{{ row.nid }} - {{ row.hostname }} - {{ row.ip }} - {{ row.port }} - {{ row.b_id }} - {{ row.b.caption }} - {{ row.b.code }}</p> # 通過b對象,進行跨表查詢主機業務線相關信息 46 {% endfor %} 47 </div> 48 </body> 49 </html> 50 51 # 這里通過row.b_id 和通過b對象來獲取 row.b.id 結果是相同的,區別在於使用b對象,會多一次sql查詢
PS:當多個表進行及聯跨表,那么都可以通過表的外鍵字段使用點來進行跨表訪問(或者使用雙下划線)
雙下划線和點跨表的不同之處在於:
- 雙下划線一般用於條件查詢(values,values_list)
- 點則一般用於在結果中利用外鍵對象進行跨表查詢
# -------------- 利用點進行跨表查詢 -------------- # 如果我只想獲取主機地址和所屬業務線使用點跨表的話 v2 = models.Host.objects.all() return render(request,'host.html',{'v2':v2}) # 前端代碼 {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b.caption }}</p> {% endfor %} # 我們只用了兩個字段,卻取出了相關的所有數據。 # 是否可以在查詢階段只取出所需字段即可?使用雙下划線__即可 # -------------- 利用雙下划線跨表查詢 -------------- v3 = models.Host.object.all().values('hostname','b__caption') # 我們可以看到在作為value的條件語句,通過b對象配合雙下划線就進行了一次跨表查詢 # 前端代碼 <div> {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b__caption }}</p> # 只需要向字典一樣即可。 {% endfor %} </div> # 在django內部,它之所以能識別__,是因為,它在處理values條件時,會使用__作為分隔符,分隔后再進行相關處理
反向查詢
很多時候我們會有另一種需求,查詢一個部門下的所有員工,這時部門表中沒有外間字段關聯人員表啊,該怎么查?其實是可以的,我把這個查詢方式稱之為反向查詢。
在建立外鍵關系時,不止會在外鍵所在表中產生外鍵所關聯表的對象,在所關聯表中也會產生一個那個關聯我的表的對象(可能有點繞,沒找到更好的表達方式),這個對象一般有兩種體現方式:
- 關聯我的表(小寫):一般用在values,values_list當作條件做字段過濾
- 關聯我的表名(小寫)_set:一般當作對象,當作字段處理
# -------------------------- models.py ------------------------------------ from django.db import models class UserInfo(models.Model): id = models.AutoField(primary_key=True) username = models.CharField(max_length=32) password = models.CharField(max_length=64) dp = models.ForeignKey('Dept',on_delete=False) # 外鍵關聯 class Dept(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=12) # -------------------------- views.py ------------------------------------ from django.shortcuts import render,HttpResponse,redirect from app01 import models def test(request): v = models.Dept.objects.all() # 獲取部門表中所有數據 for item in v: print(item.id,item.title,item.userinfo_set.all()) print(item.id,item.title,item.userinfo_set.values('username','password')) # 字典 print(item.id,item.title,item.userinfo_set.values_list('username','password')) # 元組 return HttpResponse('ok') # 可以看到部門表中存在 userinfo_set 用於跨表到 UserInfo 表中,並且支持all,values,values_list過濾
上面這種方式是通過對象的方式來跨表進行數據查詢,在正向查詢中,我們可以利用values配合雙下划線直接進行跨表查詢,在反向查詢中當然也是可以的。
def test(request): v = models.Dept.objects.values('id','title','userinfo__username') # 在values,values_list等作為條件時,就不帶_set了,直接使用表名就可以進行跨表 for item in v: print(item) return HttpResponse('ok')
注意:這種方式會列出部門內的所有人員(比如IT部門4個人員,那么會列出4條信息。
多對多
前面說了一對多的情況,這里還有一種情況叫多對對,比如一個主機可以關聯多個業務線,不同的業務線可以關聯多個主機,所以這里,業務線和主機的關系為多對多,在多對多的情況下,有需要一張額外的表來表示對應關系,這里有兩種情況來創建這張關系表。
方式1 - 手動創建
手動創建,故名思議,我們需要手動的創建一張關系表,然后創建兩個ForeignKey字段(一對多),關聯兩張表即可。
# 業務線表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主機表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() # 多對多關系表 class Application(models.Model): h = models.ForeignKey(to='Host',to_field='nid') # 關聯主機id,字段名為h_id,同時存在對象h,存放對應的Host信息 b = models.ForeignKey(to='Business',to_field='id') # 關聯業務線id,字段名為b_id,同時存在對象b,存放對應的Business信息
PS:一種手動創建三張表,可以利用創建的Application關系表來直接操作多對多關系。
方式2 - 自動創建
在Django中,還存在一種方式為自動創建, 通過django提供的ManyToMany關鍵字創建。
# 業務線表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主機表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() business = models.ManyToManyField('Business') # 通過manytomany字段創建多對多關系
注意:
- 雖然我們創建了兩張表,但是由於我們使用了manytomany關鍵字創建多對對關系,django還會為我們創建一張表名為當前表名加當前字段名的表(上面的例子就是:app01_host_business),當作關系表,不會產生business字段。
- 由於我們沒有關系表這個class類對象,所以我們不能直接操作關系表,需要通過Host對象間接通過business字段來操作關系表。
- 雖然利用manytomany關鍵字,能幫我們創建關系表,節省了很多代碼,但是自動生成的表中只會生成兩個字段(兩張表的主鍵id字段),不能定制其他字段,相反手動生成的表可以定制,平時可以根據情況二者混用。
操作關系表
手動創建關系表的情況下,由於含有第三張表對應的class,那么我們可以直接使用這個class對關系表進行操作,但是多對多的情況下沒有關系表的class,所以我們需要通過其他辦法來操作。
# 通過manytomany對象操作關系表 add() # 添加關系 remove() # 刪除關系 clear() # 清除所有關系 all() # 獲取對應的所有關系對象(在查詢時這里可以使用all,filter,get等,就像查找過濾其他數據一樣) # --------------- 例子 --------------- # models.Host.objects.filter(nid=1).first().business.add(1,2) # 添加兩條多對多關系 1(host) --> 1(business),1(host) --> 2(business) models.Host.objects.filter(nid=1).first().business.remove(2) # 刪除一條多對多關系 1 -x-> 2 models.Host.objects.filter(nid=1).first().business.clear() # 清除nid為1,所有的多對多關系