主頁 > 後端開發 > Django框架

Django框架

2020-09-20 02:16:48 後端開發

Django框架

Django是一個功能強大的web框架,

博客中缺少知識流程解釋圖,后期我會一一補充回來,因為博客園插圖片比較麻煩,請諒解,

前言:框架模式簡介

1、MVC和MTV框架模式:分層級進行管理

說到框架模式我們有必要簡單的說下設計模式,了解下設計模式這個概念,因為有人對設計模式和框架模式的概念經常混淆

設計模式:

是一套被反復使用,多數人知道并經過分類的代碼設計經驗總結,是為了解決一些通用性問題的
目的:重用代碼并保證代碼的可靠性
設計模式分類:單例,抽象工廠 等等 23種模式
一句話總結:解決某一些特殊問題的一種思想和思路

框架模式:

代碼重用,框架模式是解決如何設計程式框架的代碼,在框架模式中會包含多種設計模式,與設計模式是一種包含關系,
舉例來說:比如要蓋樓,那怎么蓋樓屬于框架模式,樓里面的電梯怎么設計,樓梯怎么設計,屬于設計模式,所以框架模式在
蓋樓中屬于如何把樓蓋起來,那么他里面會包含多種設計模式,具體的細節碰到不同的東西,會采用不同的設計模式來解決,
因此在一種框架模式中會包含多種設計模式,

目前流行的框架模式:

  • MVC(適用于多種編程語言,單在python中不常用):

    Web服務器開發領域里著名的MVC模式,所謂MVC就是把Web應用分為模型(M),控制器(C)和視圖(V)三層

    M:Models 模型層,在程式中用于處理資料邏輯的部分,(主要是處理資料),主要負責在資料庫中對資料的存取操作,其實就是對資料庫的增刪改查的操作
    V:Views 視圖層 ,在應用程式中處理顯示部分的內容(html,jsp),也就是業務邏輯相關操作
    C: Controllers 控制層 ,處理用戶互動的部分,主要作用于M和V之間做協調,通常是負責從模型層中抽取資料,再進行業務處理,最后將資料傳給視圖層,并將視圖傳給客戶端,(控制器,也就是通過路徑找到對應視圖函式)
    
    詳解:用戶首先打開瀏覽器,輸入網址,然后瀏覽器向服務器發送請求,到了服務器之后,由控制層接收這個請求,接收完請求就知道用戶想要做什么,了解了用戶的意圖,如果需要用到一些資料,比如想查看某某商品的資訊,那么控制器就需要找到商品的資訊,所以控制器就找模型層了,模型層會根據資料庫創建模型(注意模型層不是資料庫)一般情況資料庫有多少張表,那么模型層就有多少個類,每個表中有多少個欄位,模型層中的類就有多少個變(屬性),在模型層里還會提供增刪改查的操作,那么這個執行結構再反饋給控制器,到此,控制層和模型層的互動完成了,接下來,控制層就會把接收到的資料發送給視圖,視圖會把資料顯示在網頁里,反饋給瀏覽器,這樣用戶就看到了
    

?

  • MTV(django)

    Django的MTV模式本質上和MVC是一樣的,也是為了各組件間保持松耦合關系,只是定義上有些許不同,

    M:模型層,功能同上,資料庫相關操作
    T:templates:模板層,用于處理用戶顯示部分的內容,和MVC中的V是一樣的,通過html展示(模板渲染等)
    V:views 視圖層,在MTV中視圖層是處理用戶互動的部分,從模型層中獲取資料,再將資料交給模板層,再先是給用戶和MVC中的控制層用法一樣,也就是業務邏輯相關操作
    
    加上一個url控制器,也就是通過路徑找到對應視圖函式
    
    詳解:用戶打開瀏覽器,瀏覽器發送請求,視圖層接收用戶請求,接受完請求呼叫模型層,模型層根據資料庫創建模型,進行增刪改查等操作,模型層處理完資料回傳給視圖層,視圖層接收完資料呼叫模板層,模板層里存放HTML等頁面,模板層會把HTML模板頁面回傳給視圖層,視圖層填充資料到模板上,然后再回傳給瀏覽器
    
2、WSGI

WSGI(Web Server Gateway Interface)就是一種規范,它定義了web應用程式與web服務器程式之間的介面格式,實作web應用程式與web服務器程式間的解耦,

開發的專案分為兩個部分的程式
1 服務器程式  socket相關功能的模塊,wsgiref、uwsgi等等,負責接收網路請求并決議網路請求相關資料,分裝加工成一些可用的資料格式,格式大家都是統一的,方便應用程式的開發

2 應用程式  就是使用接收到的網路請求相關資料,進行邏輯代碼的開發,比如資料庫相關操作,資料結構的調整、檔案操作等等,,,

一、基本使用

1、下載的三種方式:
  • 直接在pycharm中的setting中進行下載
  • 在cmd中通過命令下載:pip install django==版本
  • 在pycharm的Terminal控制臺中進行下載(下載時要注意路徑問題)
2、創建專案

(1)通過cmd或pycharm控制臺的命令創建專案

? 先切換到要創建專案的目錄下,然后執行創建專案命令:

django-admin startproject mysite  # mysite是專案名稱

? 創建專案后會生成如下的目錄,當前目錄下會生成mysite的工程,里面有個主目錄和我們創建的專案目錄同名,在專案目錄下有個manage.py檔案,在主目錄下有settings.py\urls.py\wsgi.py,每個檔案的功能介紹如下:

manage.py ----- Django專案里面的工具,通過它可以呼叫django shell和資料庫,啟動關閉專案與專案互動等,不管你將框架分了幾個檔案,必然有一個啟動檔案,其實他們本身就是一個檔案,
settings.py ---- 包含了專案的默認設定,包括資料庫資訊,除錯標志以及其他一些作業的變數,
urls.py ----- 負責把URL模式映射到應用程式,
wsgi.py ---- runserver命令就使用wsgiref模塊做簡單的web server,后面會看到renserver命令,所有與socket相關的內容都在這個檔案里面了,目前不需要關注它,

? 一個django專案中可以有多個應用,每個應用完成專案的部分功能,這些功能相對于其他功能來說是相對獨立的,但又同時存在于同一個專案中,每個應用的邏輯資料庫等也都是相對獨立的,每個應用都有屬于自己的模塊單位;開發的時候,都是通過應用來寫邏輯

(2)通過pycharm創建django專案

  • 點擊File --》New Project 選擇第二項 Django

  • 在Location中選擇選專案創建的地址和專案名

  • Project Interpreter:

    Project Interpreter中的Newenvironment using是創建專案執行的虛擬環境

    Project Interpreter中的Existing interpreter是使用本地的環境,也可以使用已創建好的虛擬環境

  • More Settings

    Template language:模板引擎;默認是Django的Template模板引擎

    如若下載jinja2模板引擎可進行切換,或者其他模板引擎

    注:django中的模板引擎未單獨封裝成模塊;jinja2是模仿的的django的Template

    Templates folder:存放html檔案的檔案夾名

    Application name:是創建的應用的應用名

  • create創建完成后執行即可,通過控制臺顯示的鏈接即可訪問

3、運行專案

啟動專案命令:

python manage.py runserver 127.0.0.1:8080
ip和port可以不用寫,不寫時默認是 127.0.0.1:8000

運行成功后,會看到一個專案鏈接,在瀏覽器中輸入此鏈接即可訪問到創建的專案

4、創建應用Application

(1)創建專案時直接通過Application name創建應用

(2)pycharm中手動創建應用

  • cmd或pycharm控制器Terminal中創建應用
要在專案目錄下執行命令進行創建應用
python manage.py startapp 應用名
經常用到的三個檔案
models.py  資料庫相關內容
views.py  視圖,業務邏輯代碼相關內容
tests.py 用來寫一些測驗代碼,用來測驗我們自己寫的視圖的,目前用不到
  • 手動創建檔案夾(也可復制其它應用的檔案,要注意修改配置)

    • 創建應用檔案夾(滑鼠右鍵使用python package創建)
    • 在settings.py中找到INSTALLED_APPS在其下面添加應用的配置資訊
    • 應用名.apps.應用名Config(后面的應用名的首字母大寫)
  • 最后一點可能需要配置一下template的路徑

    • 在settings.py中找到TEMPLATES在其下面的'DIRS':[]中添加os.path.join(BASE_DIR, 'templates')

    • 結果顯示:
      'DIRS': [os.path.join(BASE_DIR, 'templates')],
      
  • 注意

    • 手動創建應用的時候要把應用指定給當前專案
    • 在settings檔案的INSTALLED_APPS的下面
      • 方式一:應用名.apps.應用名Config(后面的應用名的首字母大寫)
      • 方式二:應用名 ( 這是簡寫方式)
    • 如若不指定應用,Django中的某些方法將無法使用
5、django專案的匯入

請看博客:django專案匯入

6、windows中安裝不同版本的python解釋器

在python3.7及以上版本中,使用django框架的時候,django的版本要在2.0或以上,否則會出現問題;python3.6使用django的1.0版本,

當想即使用python3.7和python3.6針對django的不同版本進行創建專案時,python解釋器的安裝要注意,

解釋器安裝請參考博客:windows中安裝不同版本的python解釋器

二、url路由系統

? 在django中,url中的路徑寫法是正則,正則里面有無名分組正則,有有名分組正則,那么對應django中的功能,我們稱之為無名分組路由和有名分組路由

? 在django的1.0版本中路由組態檔urls.py中使用的是url(),里面可以直接使用正則匹配路徑的方式

? 而在django的2.0版本中路由組態檔urls.py中使用的是path(),里面不能直接使用正則匹配路徑,如需使用正則路徑進行匹配就要使用re_path(),使用前要先匯入

1、無命名分組路由

看寫法,urls.py檔案中內容如下

urlpatterns = [
		...
    url(r'^books/(\d+)/(\d+)/', views.book),
    #正則里面()分組正則,會將分組中的正則匹配到的內容作為回傳值回傳
]

簡單分析,偽代碼
'''
當用戶請求的路徑是它: /books/2019/8/

url(r'^books/(\d+)/(\d+)/', views.book), 里面做的事情如下

re.match(^books/(\d+)/,/books/2019/)
2019 和 8 作為位置引數交給了要執行的book視圖函式
視圖函式book的寫法
def book(request,xx,oo):
    xx = 2019
    oo = 8
    pass
'''

視圖views.py檔案中函式的寫法

#位置傳參,url中正則^books/(\d+)/(\d+)/,那么第一個()分組匹配到的資料,作為book函式的第二個引數,第二個()分組匹配到的資料,作為book的第三個引數
def book(request, year, month):
    print('year', year, 'month', month) #year 2020

    # return HttpResponse('%s所有書籍' % year)
    return HttpResponse('%s年%s月所有書籍' % (year, month))

使用url路由系統時需要注意幾個點

1. urlpatterns中的元素按照書寫順序從上往下逐一匹配正則運算式,一旦匹配成功則不再繼續,
2. 若要從URL中捕獲一個值,只需要在它周圍放置一對圓括號(正則分組匹配),
3. 不需要添加一個前導的反斜杠(也就是寫在正則最前面的那個/),因為每個URL 都有,例如,應該是^articles 而不是 ^/articles,
4. 每個正則運算式前面的'r' 是可選的但是建議加上,
5. ^articles$  以什么結尾,以什么開頭,嚴格限制路徑
2、有名分組路由

其實就玩得正則的有名分組,看示例

Urls.py檔案中的寫法

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # /books/2020/6/
    url(r'^books/(?P<year>\d+)/(?P<month>\d+)/', views.book),
    # {'year':2020,'month':6},url類將有名分組正則匹配出來的資料,交給了book視圖函式作為關鍵字引數來使用]

View.py檔案中函式的寫法如下

# ^books/(?P<year>\d+)/(?P<month>\d+)/
#獲取到url中的有名分組正則匹配到的資料,那么函式形參名稱必須和有名分組正則的那個名稱相同才可以,也就是按照上面的url來看的話,函式的形參必須是year和month這兩個名稱,并且關鍵字傳參不需要考慮函式形參的位置
def book(request, month, year):
    # print('year', year, 'month', month) #year 2020
    print(request.path) #/books/2020/6/
    # return HttpResponse('%s所有書籍' % year)
    return HttpResponse('%s年%s月所有書籍' % (year, month))
3、路徑中的尾部斜杠問題

當用戶通過瀏覽器訪問django框架完整的專案中的某個路徑時,如果用戶在輸入網址路徑的最后,沒有加上/斜杠,比如http://127.0.0.1:8000/test,那么django會發將用戶輸入的網址路徑加上一個后置的/,也就會將路徑變成這樣http://127.0.0.1:8000/test/,然后給瀏覽器發送一個重定向的回應操作,狀態碼為301,那么瀏覽器拿到這個重定向操作之后,就會自動發起一個這樣的路徑請求http://127.0.0.1:8000/test/,所以當我們打開瀏覽器控制臺的network功能查看請求程序時,會看到兩個請求,一個沒有后置的斜杠的,一個是有后置斜杠的,第二個請求才會走到我們的urs.py檔案中的路徑配合和分發對應視圖的地方,

我們可以通過一個配置項,告訴django,不要自動加路徑后面的斜杠了,但是需要注意的就是你自己寫的url中的正則,也別加后面的斜杠,不然正則匹配不到,

配置項直接寫在settings組態檔中,任意位置

APPEND_SLASH = False  #False表示告訴Django,不加路徑后面的斜杠,默認值是True
5、視圖函式中指定默認值

views.py檔案:

# 在路由沒有匹配任何引數的時候,num使用自己的默認值
# 當路由中有分組匹配資料的動作,比如url(r'^test/(\d+)/', views.test),用戶輸入網址:http://127.0.0.1:8000/test/22/,那么22就被匹配到了,會作為引數傳給我們的test函式,那么num的值就變成了22
def test(request, num=10):
    print('number>>>',num)
    return HttpResponse('test')

urls.py檔案

# url(r'^test/', views.test),
	url(r'^test/(\d+)/', views.test),
6、url反向決議

? 由于將來專案中的不同功能對應的url路徑可能會發生變化,所以我們在每個url路徑上加上一個別名,將來通過別名反向決議來使用這個別名對應的路徑,那么不管路徑將來發生什么變化,只要別名不變,那么邏輯中使用這個路徑的地方,都可以通過別名獲取到

(1)url別名用法

urlpatterns = [
    ...
    url(r'^add_book/', views.add_book, name='add_book'),  #name屬性對應的值,就是這個路徑的別名

]

(2)views視圖中使用url反向決議的方式

# 首先在視圖中引入反向決議的方法
from django.urls import reverse  #url別名反向決議,通過name別名對應的資料,決議出我們的url路徑

1 針對沒有引數的別名 url(r'^add_book/', views.add_book, name='add_book'),
	反向決議:reverse('book_list')
2 針對含有無名分組的url:url(r'^book_list/v2/(\d+)/(\d+)/', views.book_list, name='book_list'),
	反向決議:reverse('book_list',args=(11,22))
3 針對含有有名分組的url:url(r'^book_list/v2/(?P<year>\d+)/(?P<month>\d+)/', views.book_list, name='book_list'),
	反向決議:reverse('book_list',kwargs={'year':2022,'month':11})

(3)html檔案中使用url別名反向決議

  • 無引數:{% url 'add_book' %}
  • 有引數:{% url 'add_book' 2020 12 %}
針對無引數的路徑:url(r'^add_book/', views.add_book, name='add_book'),
	反向決議:<a href=https://www.cnblogs.com/wylshkjj/p/"{% url'add_book'%}" class="btn btn-primary">添加書籍</a>
這對有引數的路徑:url(r'^add_book/(\d+)/(\d+)/', views.add_book, name='add_book'),
							 url(r'^add_book/(?P<year>\d+)/(?P<month>\d+)/', views.add_book, name='add_book'),
	反向決議: <a href=https://www.cnblogs.com/wylshkjj/p/"{% url'add_book' 2020 12 %}" class="btn btn-primary">添加書籍</a>
7、路由分發和命名空間 namespace

1 創建app

python manage.py startapp app02

2 配置app,在settings.py檔案中

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'app02'
]

路由分發步驟

1 在專案主目錄下的urls.py(以后我們稱之為總路由)檔案中,寫上如下內容

from django.conf.urls import url, include
from django.contrib import admin

# 路由分發功能
urlpatterns = [
    url(r'^admin/', admin.site.urls),
  	
    # /app01/index/ -- 去掉/app01/ -- index/拿到app01應用下面的urls.py檔案中進行路徑匹配
    url(r'^app01/', include('app01.urls')),
    # 當訪問路徑是以/app01/開頭的,那么django主目錄下面的urls.py檔案會自動幫我們去找app01下面的urls.py檔案進行路徑匹配
    url(r'^app02/', include('app02.urls')), #include引數格式 '應用名稱.urls'
]

2 在每個應用檔案夾下面創建一個叫做urls.py的檔案,里面寫上如下內容

from django.conf.urls import url, include
from app01 import views
# 路由分發功能
urlpatterns = [
    #' index/'
  	# 寫上自己應用中的每個路徑對應的函式
    url(r'^index/', views.index),

]

出現一個問題,當兩個應用中的某個url的別名相同了,那么使用url反向決議的時候,發現都是決議出來的都是最后一個應用的對應路徑,這叫做別名沖突,

看示例:

app01下的urls.py

from django.conf.urls import url, include
from app01 import views
# 路由分發功能
urlpatterns = [
    url(r'^index/', views.index),
    url(r'^home/', views.home, name='home'),

]

App02下的urls.py

from django.conf.urls import url, include
from app02 import views
# 路由分發功能
urlpatterns = [
    url(r'^index/', views.index),
    url(r'^home/', views.home, name='home'),

]

App01的views.py檔案中進行別名發現決議,代碼:

def index(request):
    print('app01>>>',reverse('home'))
    #app01>>> /app02/home/
    return HttpResponse('app01-index')

App02的views.py檔案中進行別名發現決議,代碼:

def index(request):
    print('app02>>>',reverse('home'))
    #app02>>> /app02/home/
    return HttpResponse('app02-index')

發現別名沖突,導致反向決議出錯了,

解決方法,命名空間

示例:總路由寫法

from django.conf.urls import url, include
from django.contrib import admin

# 路由分發功能
urlpatterns = [
    url(r'^admin/', admin.site.urls),
  	# 路由分發時,給每個應用的路由進行了空間劃分,使用namespace
    url(r'^app01/', include('app01.urls', namespace='app01')),
    url(r'^app02/', include('app02.urls', namespace='app02')),
]

views.py檔案寫法

def index(request):
  # 在進行反向決議時,需要用到命名空間:別名,來進行決議
    print('app02>>>',reverse('app02:home'))
    # app02>>> /app02/home/
    return HttpResponse('app02-index')

三、視圖

1、request的物件

常用的屬性和方法

print(request)  # wsgirequest物件
print(request.path)  # 請求路徑 #/index/
print(request.method)  # 請求方法
print(request.POST)  # post請求提交的資料 <QueryDict: {'username': ['root']}>
print(request.GET)  # 獲取url中的查詢引數 <QueryDict: {'a': ['1'], 'b': ['2']}>  #不是針對get請求的
print(request.body)  #獲取http請求訊息格式的請求資料部分的內容  b''
print(request.META)  #請求頭資訊
print(request.get_full_path())  # 獲取完整路徑(包含查詢引數的) /index/?a=1&b=3

print(request.FILES)  # 上傳的檔案物件資料
print(request.FILES.get('file'))  # 上傳的檔案名

#<QueryDict: {'username': ['root'], 'sex': ['female'], 'hobby': ['2', '3']}>
print(request.POST.get('username'))  # 前端中傳輸的username的值
print(request.POST.get('sex'))  # 前端中單選傳輸的sex值
# 多選提交來的資料通過getlist來獲取
print(request.POST.getlist('hobby'))  # ['2', '3']
2、response的回應

(1)常用方法

from django.shortcuts import render, HttpResponse, redirect

return HttpResponse('你好') #回復字串
return render(request,'home.html') #回復html頁面

#重定向方法,引數是個路徑
return redirect('/home/')  #封裝了302狀態碼,以及瀏覽器要重定向的路徑

(2)添加回應頭鍵值對

ret = render(request,'home.html') 
ret['a'] = 'b' #添加回應頭鍵值對
return ret

(3)添加回應狀態碼

ret = render(request,'home.html', status=202) #render修改狀態碼還可以這樣改
#ret['a'] = 'b' #添加回應頭鍵值對
ret.status_code = 201 #添加回應狀態碼
return ret #回復html頁面
3、CBV和FBV

兩種視圖邏輯的寫法方法

FBV:全稱function based view,就是基于函式來寫視圖邏輯

CBV:全稱class based view,就是基于類來寫視圖

基于類的視圖CBV寫法,如下,views.py檔案

from django.views import View

#登錄需求
class LoginView(View):
    # get請求  獲取login頁面
    def get(self,request):
        return render(request,'login.html')
        
    # post請求,獲取post請求提交的資料,并校驗等等
    def post(self,request):
        print(request.POST)
        
        #<QueryDict: {'uname': ['chao'], 'pwd': ['123']}>
        return render(request,'login.html')

url路徑的寫法:urls.py檔案

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^home/', views.home),

    # 類視圖的url寫法
    url(r'^login/', views.LoginView.as_view()),

]
4、cbv原始碼重點(反射)
from django.views import View
View里面的dispatch方法中的反射邏輯,實作了不同的請求方法,找到我們視圖類中的對應方法執行
5、FBV和CBV加裝飾器

FBV和普通函式加裝飾器方式一樣

示例:

#裝飾器函式
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner

#使用裝飾器
@outer
def books(request):
    print('FBV執行了')
    return HttpResponse('book.html--ok')

CBV加裝飾器

#裝飾器函式
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner


#使用裝飾器
#1 引入django提供的裝飾器方法method_decorator來配合裝飾器函式的使用
from django.utils.decorators import method_decorator

@method_decorator(outer,name='get') #CBV加裝飾器方式3
class BookView(View):
    #給類方法統一加裝飾器,借助dispatch方法(父類的dispatch方法,就是通過反射來完成不同的請求方法找到并執行我們自己定義的視圖類的對應方法)
    # 重寫dispatch方法,dispatch方法是在其他請求方法對應的類方法執行之前執行的
    # @method_decorator(outer) #加裝飾器方式2
    def dispatch(self, request, *args, **kwargs):
        # print('xxxxxx')
        ret = super().dispatch(request, *args, **kwargs)
        # print('oooooo')
        return ret

    #CBV加裝飾器的方式1,給單獨的方法加裝飾器
    # @method_decorator(outer)
    def get(self, request, xx):
        print('CBV的get方法')
        return render(request, 'book.html')

    # @method_decorator(outer)
    def post(self,request, xx):
        print('CBV的post方法')
        return HttpResponse('ok')
  

四、Template模板

1、Template的基本使用

(1)通過{{ 變數 }}:獲取單個變數值

(2)通過{% 邏輯 %}:獲取邏輯渲染結果

2、變數的使用

Number資料型別,容器資料型別和物件都可以直接進行渲染

(1)回傳前端頁面的資料格式

  • 在return回傳的時候可以直接寫入引數,區別在于html中渲染的時候直接通過元素直接獲取(使用原引數沒有作用),容器資料型別才有效,number型別無效

    return render(request, "index.html", info)

  • 在return回傳的時候也可以使用字典的方式,使用字典時,就是直接通過字典的鍵來進行相應的操作

    return render(request, "index.html", {'info': info})

(2)句點號的使用:

? 在字典資料型別中需要使用句點號和索引搭配才能獲取到相應的值

? 同理物件的方法和屬性的呼叫也是通過句點號,而且要注意呼叫方法不能加()

# views.py
from django.shortcuts import render
import datetime
# Create your views here.
class obj():
    pass
def index(request):
    pdd = '1234'
    info = {
        'name': '旋風奧利奧',
        'age': '18',
        'hobby': "girl",
        'dict': {'drink': '飲品', 'milk': '牛奶'},
        'list': ['面包', '包子'],
        'object': obj(),
        'size': 123456,
        'time': datetime.datetime.now()
    }
    return render(request, "index.html", info)
    # return render(request, "index.html", {'info': info})

# html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>{{ name }}</h3>
<p>{{ dict }}</p>
<p>{{ list }}</p>
<p>{{ pdd }}</p>
<hr>
{% for foo in list %}
<p>{{ foo }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
<p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{{ object.obk }}
</body>
</html>

# url.py
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index)
]
3、Template過濾器
  • 通過過濾器對資料進行過濾處理顯示
  • 使用方式:{{ 變數|過濾器:引數 }}
  • 過濾器支持鏈式操作,通過多個管道符和過濾器相配合實作層級過濾
  • 過濾器可以接收引數
  • 注意事項:管道符左右兩遍不能留空格,否則不能識別;還有引數和:間也不能留空格;
3.1、內置過濾器

(1)default:默認值

? 當變數獲取的到值的時候顯示獲取的值,獲取不到值或者獲取的是一個布爾型別的False時就使用默認的default值:{{ value|default:"沒有值"}}

(2)length:長度

? 回傳的是字串或串列的長度:{{ value|length }}

(3)filesizeformat:檔案大小的顯示處理

? 將值格式化為一個 “人類可讀的” 檔案尺寸 (例如 13 KB, 4.1 MB, 102 bytes, 等等),

(4)slice:切片:{{ value|slice:"2:-1" }}

(5)date:時間顯示處理

? 對獲取的時間進行過濾處理:{{ value|date:"Y-m-d H:i:s"}}

(6)safe:宣告此變數值(或代碼段)不轉義

{'a_tag':'<a href=https://www.cnblogs.com/wylshkjj/p/"">百度',}
渲染
<p>
    {{ a_tag|safe }} #生成標簽效果
</p>

(7)truncatechars:指定字串長度,超出部分以 ... 顯示
{{ value|truncatechars:9}}:指定九個字符長度,也包括 ... 這三個字符

(8)truncatewords:以單詞的形式指定字串長度,超出部分以 ... 顯示

? {{ value|truncatewords:3}}:指三個單詞長度,不包括 ... 部分

(9)cut:過濾字串

? {{ value|cut:' ' }}:過濾掉value變數中和引數中的空格相同的字符

(10)join:字串拼接

? {{ hobby|join:'+' }}:把串列資料通過加號拼接成一個字串

3.2、自定義過濾器
  • 在應用檔案夾中創建一個名為templatetags的檔案夾(檔案夾的名字必須是templatetags)
  • 在templatetags檔案夾中創建任意 .py 檔案,如:mytag.py
  • 在mytag.py檔案中寫上如下內容
from django import template
register = template.Library()  # 制作注冊器,名字必須叫register
#過濾最多兩個引數
@register.filter  # 注冊過濾器,需要兩個引數的
def add(v1, v2):  # v1表示管道符前面的,v2表示冒號后面的引數
    print(v1,v2)  # 100 50
    return v1 + v2

@register.filter  # 注冊過濾器,需要一個引數的
def xxx(v1):   # v1表示管道符前面的
    print(v1) 
    return 'xxx'
  • 在html檔案中匯入
{% load mytag %}  <!-- 首先通過load來加載一下mytag檔案,不一定放在檔案的開頭,但是一定要放在使用過濾器的前面先進行參考 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base頁面</h1>
  <!-- {% load mytag %} 放這里也是可以的 -->  
<div>
    {{ num|add:50}}  <!-- 使用過濾器,和django內置過濾器用法一樣,這里生成的是add函式的回傳值 -->
</div>
<div>
    {{ num|xxx }}
</div>
<!-- {% load mytag %} 放這里不行 -->  

</body>
</html>
4、Template標簽

? 使用方式:{% 標簽 %} {%end標簽%}

4.1、內置標簽

(1)for 回圈標簽

? {% for xx in hobby %}{% endfor %}

forloop的使用

? 注:回圈序號可以通過{{forloop}}顯示,必須在回圈內部用

  • forloop.counter 當前回圈的索引值(從1開始),forloop是回圈器,通過點來使用功能

  • forloop.counter0 當前回圈的索引值(從0開始)

  • forloop.revcounter 當前回圈的倒序索引值(從1開始)

  • forloop.revcounter0 當前回圈的倒序索引值(從0開始)

  • forloop.first 當前回圈是不是第一次回圈(布林值)

  • forloop.last 當前回圈是不是最后一次回圈(布林值)

  • forloop.parentloop 本層回圈的外層回圈的物件,再通過上面的幾個屬性來顯示外層回圈的計數等

for回圈的反向回圈:

? 可以利用{% for obj in list reversed %}反向完成回圈,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in dict %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for i in dict.values %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
    <p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.first }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.last }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    {% for foo in list %}
        <p>{{ forloop.parentloop.counter }} : {{ forloop.revcounter0 }} : {{ foo }}</p>
    {% endfor %}
{% endfor %}
<hr>

# 反向回圈串列
{% for foo in list reversed %}
    <p>{{ foo }}</p>
{% endfor %}

</body>
</html>

(2)if 判斷標簽

? {% if 判斷條件 %}{% endif %}

  • if陳述句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判斷,注意條件兩邊都有空格,
  • 但是不支持連續判斷操作:{% if a > b > c %}{% endif %}
  • {% if %}會對一個變數求值,如果它的值是“True”(存在、不為空、且不是boolean型別的false值),對應的內容塊會輸出,
{% if num > 100 or num < 0 %}
	<p>無效</p>  <!--不滿足條件,不會生成這個標簽-->
{% elif num > 80 and num < 100 %}
	<p>優秀</p>
{% else %}  <!--也是在if標簽結構里面的-->
	<p>炊訓吧</p>
{% endif %}

?

(3)with 起別名標簽

? 使用一個簡單地名字快取一個復雜的變數,多用于給一個復雜的變數起別名,當你需要使用一個“昂貴的”方法(比如訪問資料庫)很多次的時候是非常有用的;注意:等號左右不要加空格,

方式一:
{% with total=business.employees.count %}
    {{ total }} <!--只能在with陳述句體內用-->
{% endwith %}

方式二:
{% with business.employees.count as total %}
    {{ total }}
{% endwith %}

(4)for empty聯合使用的情況

? 當回圈的hobby沒有資料或為空的時候,就顯示empty下面的內容

<ul>
    {% for xx in hobby %}
    <li>{{ xx }}</li>
    {% empty %}
        <h2>抱歉,沒有查詢到相關資料</h2>
    {% endfor %}
</ul>

(5)Django的模板語言中屬性的優先級大于方法

? 處理的字典資料中不要出現以方法名為鍵的鍵值對,因為默認會獲取該鍵值對資料,而不是走方法去處理資料,導致得不到想要的資料結果,

def xx(request):
    d = {"a": 1, "b": 2, "c": 3, "items": "100"}
    return render(request, "xx.html", {"data": d})

? 如上,我們在使用render方法渲染一個頁面的時候,傳的字典d有一個key是items并且還有默認的 d.items() 方法,此時在模板語言中:{{ data.items }}

? 默認會取d的items key的值,

4.2、自定義標簽
  • 在應用檔案夾中創建一個名為templatetags的檔案夾(檔案夾的名字必須是templatetags)
  • 在templatetags檔案夾中創建任意 .py 檔案,如:mytag.py
  • 在mytag.py檔案中寫上如下內容
from django import template
register = template.Library()  # 制作注冊器,名字必須叫register
@register.simple_tag
def atag(v1,v2):  # 沒有引數個數限制
    print(v1,v2)
    return v1 + v2
  • 在html檔案中匯入
 {% load mytag %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base頁面</h1>
{% load mytag %}

<div>
    {% atag a b %}  <!-- 注意,是{%%} 來包裹使用,先是標簽名稱然后空格寫引數,引數之間也是空格分隔的 -->
</div>

</body>

</html>
5、Templete模板繼承

將一些頁面公共的部分,可以抽離出來單獨做成一個html頁面,使用這些公用部分的其他html檔案,只需要繼承一下它就可以了,具體使用流程如下:

(1) 創建公用模板,比如內容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body{
            padding: 0;
            margin: 0;
        }
        {% block css %}
        .nav{
            height: 60px;
            background-color: green;
        }
        {% endblock %}
        .nav a{
            color:white;
            text-decoration: none;
        }
        .left-menu{
            width: 30%;
            background-color: rgba(0,0,0,0.5);
            float:left;
        }
        .menu .menu-title{
            text-align: center;
        }
        .main{
            float: right;
            width: 65%;
            height: 300px;
            border: 1px solid red;
        }
    </style>
</head>
<body>

<div class="nav">
    <a href=https://www.cnblogs.com/wylshkjj/p/"/xx1/">首頁
    個人中心
    詳情頁
{% block content %} 公共頁面 {% endblock %}
{% block js %} {% endblock %}

(2)將來如果說繼承公用模板的html檔案中需要修改公用模板中的一些內容,那么需要在公用模板中預留一些鉤子,鉤子的寫法如下

{% block content %}  #block 后面的塊名稱隨便起
公共頁面
{% endblock %}
#也可以這樣寫 {% endblock content %}  #endblock指定名稱

(3)繼承公用模板需要在html檔案中寫如下內容:

{% extends 'xx.html' %}  <!-- 需要先繼承一下公用模板,寫法就是extends '公用模板檔案名稱',注意,必須寫在第一行 -->

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首頁</h1>
{% endblock %}


(4) 在使用公用模板的其他html檔案中,如果需要更改公用模板里面的內容,只需要在html檔案中寫上相同的鉤子,鉤子里面寫上自定義的內容,寫法如下

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首頁</h1>
{% endblock %}

(5)注意事項:

{% block content %}
...
{% endblock content %}  

 在大型模版中,這個方法幫你清楚的看到哪一個 {% block %} 標簽被關閉了,

6、要注意Template模板渲染實在瀏覽器解釋之前執行的,模板渲染后才輪到瀏覽器器來執行解釋

? 與safe處理效果相同的mark_safe方法,mark_safr方法需要匯入模塊

mark_safe與|safe的優缺點:

7、拓展:XSS攻擊(上面過濾|safe的拓展)

xss攻擊:,全稱跨站腳本攻擊

? Django的模板中在進行模板渲染的時候會對HTML標簽和JS等語法標簽進行自動轉義,原因顯而易見,這樣是為了安全,django擔心這是用戶添加的資料,比如如果有人給你評論的時候寫了一段js代碼,這個評論一提交,js代碼就執行啦,這樣你是不是可以搞一些壞事兒了,寫個彈窗的死回圈,瀏覽器就不能用了,瀏覽器會一直彈出彈窗,這叫做xss攻擊,所以瀏覽器中進行了一些轉義處理,但是有的時候我們需要這些HTML元素不被轉義,比如我們做一個內容管理系統,后臺添加的文章中是經過修飾的,這些修飾可能是通過一個類似于FCKeditor編輯加注了HTML修飾符的文本,如果自動轉義的話顯示的就是保護HTML標簽的源檔案,為了在Django中關閉HTML的自動轉義有兩種方式,如果是一個單獨的變數我們可以通過過濾器“|safe”的方式告訴Django這段代碼是安全的不必轉義,

8、django的UTC格林威治時間的處理

? 在settings.py檔案中,具體情況集體分析,主要修改settings組態檔的引數,

9、Template模板注意事項

? 在引入的javastript檔案中使用模板渲染是不會生效的

? Django對于html的Template渲染時是以字串的形式先讀取所有的html檔案中的內容,然后執行模板渲染,此實javascript的匯入部位代碼還是字串,檔案還沒有被引入,當模板渲染結束后回傳給瀏覽器的時候,瀏覽器決議時異步執行javascript檔案,而此時無法再進行模板渲染了,因為模板渲染是由后臺執行的,瀏覽器并不會,也沒有此功能,

? 若想在javascript中使用模板渲染,那就不能使用檔案引入的方式,只能在html檔案中寫javascript,并在里面使用模板渲染規則

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True
10、組件與動態組件的應用

(1)組件

組件就是一個html檔案,其中封裝了一些特定功能,比如就是一個導航欄,或者就是一個左側選單,相當我們將一些特定功能封裝成了一個組件,將來任何其他html檔案中如果使用這部分功能,可以直接引入使用,

在django模板渲染系統中使用組件的步驟

第一步:創建組件的html檔案,名字隨便取,比如叫做zujian.html,比如內容如下,做一個頂部導航欄組件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>zujian</title>
    <style>
        .nav{
            background-color: blue;
            height: 100px;
        }
        .nav a{
            color:white;

        }
    </style>
</head>
<body>

<div class="nav">
    <a href=https://www.cnblogs.com/wylshkjj/p/"">百度
    京東
    個人中心

第二步:使用組件,需要借助下面這個標簽語法

{% include '組件檔案名稱.html' %}

示例:比如我們需要某個html檔案中使用,比如show.html檔案中使用,show.html檔案內容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
<!-- <style> -->
<!-- 可以修改樣式,但是注意權重問題 -->
<!--    .nav{  
            background-color: yellow!important;#}
        } 
-->
<!-- </style> -->
</head>
<body>

<h1>這是show頁面</h1> 
<!-- include 'zujian.html' -->  <!-- 在哪里引入組件,組件就生成在頁面的對應位置上 -->
</body>

</html>

(2)動態組件的應用

注意:在settings中的INSTALLED_APPS配置當前app,不然django無法找到自定義的標簽

  1. 在app中創建templatetags檔案夾(檔案夾名只能是templatetags)

  2. 在templatetags檔案夾中創建任意 .py 檔案,如:mytag.py

  3. 在mytag.py檔案中寫上如下內容

from django import template
register = template.Library() #制作注冊器,名字必須叫register
@register.inclusion_tag('zujian2.html')
def xiaolin(v1):
    #v1 = [11,22,33]
    return {'data': v1}
  1. 使用inclusion_tag,比如在show2.html檔案中使用:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% load mytag %}
{% xiaolin d %}

<h1>這是show2頁面</h1>
</body>

</html>
  1. 需要后臺給show2.html傳遞資料,比如views.py檔案寫法如下
def show2(request):
    d = ['國產', '歐美', '日韓']
    return render(request,'show2.html',{'d': d})

五、靜態檔案配置

Django的常用的三種靜態檔案的配置方式

(1)settings配置引入靜態檔案

<!-- html代碼 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href=https://www.cnblogs.com/wylshkjj/p/"/static/css/index.css">

<body>
hello world!
.c{ height: 100px; width: 100px; border: 1px solid black; background-color: aqua; color: aliceblue; }
# settings中的配置

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'statics'),
]

(2)template渲染模式static引入靜態檔案

<!-- html代碼 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href=https://www.cnblogs.com/wylshkjj/p/"{% static "css/index.css" %}">

<body>
hello world!
.c{ height: 100px; width: 100px; border: 1px solid black; background-color: aqua; color: aliceblue; }

(3)template渲染模式get_static_prefix引入靜態檔案

<!-- html代碼 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href=https://www.cnblogs.com/wylshkjj/p/"{% get_static_prefix %}css/index.css ">

<body>
hello world!
.c{ height: 100px; width: 100px; border: 1px solid black; background-color: aqua; color: aliceblue; }

六、資料庫的連接

這里只講解兩種資料庫的連接模式:

(1)sqlite3

? Django默認自帶的資料庫,直接執行初始化創建資料庫即可

(2)mysql

? mysql資料庫的連接需要配通過包來實作,在Java中叫連接池,不過python把這個封裝成了包,很多包都可以實作,對于django1.0版本通常使用pymysql模塊來實作連接,但是對于django2.0版本以上可能會出錯,這時也可以使用mysqlclient模塊來連接資料庫,當然還有一些其它連接資料庫的模塊,

? 這里主要介紹pymysql的連接方法

(3)初始化和執行資料庫

? 初始化命令:python manage.py makemigrations

? 執行操作命令:python manage.py migrate

(4)通過pycharm的圖形界面操作來操作資料庫

? 在pycharm的右邊框位置有個database在里面可以創建sqlite和MySQL的連接實作對資料庫的操作

(5)還有一些關于資料庫操作中的問題,后面會總結,關注本人博客即可,

七、ORM(物件關系映射)

ORM全稱:object relational mapping ---- 物件 關系 映射

主要完成的就是資料庫操作,

1、model類的屬性引數

比如:models.CharField(null=True,blank=True)
(1)null
	如果null=True,Django將用NULL來在資料庫中存盤值,默認情況null=False
(2)blank
	如果blank=True,該欄位允許不填,默認blank=False
	需要注意,null與default不同;null是資料庫范疇,blank是資料驗證范疇
(3)dfault
	設定欄位默認值,可以是一個值或者可呼叫物件,
	如果可呼叫,每有新物件被創建它都會被呼叫,如果欄位沒有設定可以為空,那么當進行添加新欄位操作時要加上default默認值,否則會有操作提示,讓你添加默認值,
(4)primary_key
	主鍵,當primary_key=True就是為當前欄位添加主鍵,
	如果表中沒有設定primary_key=True,Django會自動添加一個IntegerField欄位作為主鍵
	當表中設定了primary_key=True,就覆寫了默認添加主鍵的功能
(5)unique
	唯一屬性(唯一索引),當欄位設定unique=True就表示該欄位在整張表中必須是唯一的
(6)choices
	由二元元組組成的一個可迭代物件(例:串列,元組),用來給欄位提供選擇選項,
	如果設定了choices,默認的表單是一個先擇框而不是一個文本框,選擇框中的選項就choice中的選項值
(7)db_index
	db_index=True時代表此欄位設定資料庫索引
(8)auto_now_add
	auto_now_add=True,在創建資料記錄的時候會把當前時間添加到資料庫相應欄位中
(9)auto_now
	auto_now=True,每次更新資料記錄時會自動更新該欄位,標識該資料最后一次的修改時間
	注:只能在save方式時觸發自動更新時間的動作
(10)DatetimeField、DateField、TimeField都可以使用auto_now_add和auto_now這兩個屬性
1.1、models中的FIeld類對應型別和對應的mysql的資料型別對比

(1)可以在pycharm中的Project專案區中的External Libraries中的Python解釋器下的site-packages檔案夾中的:site-packages\django\db\backends\mysql中base.py檔案查看(前提是你使用的IDE是pycharm)

(2)也可以在pip安裝的django包中的路徑找到對應關系的檔案:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(3)在創建的虛擬環境中的pip安裝的django包中的路徑找到對應關系的檔案:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(4)下面是對應關系

'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)',
1.2、欄位的choices屬性

(1)使用

class Book(models.Model):
		...
  	# sex = models.CharField(max_length=12)
    sex_choices = ((1, '男性'),(0, '女性'))  #enum列舉 單選
    sex = models.IntegerField(choices=sex_choices, default=1)

(2)獲取含有choices屬性的字典資料方法

ret = models.Book.objects.get(id=5)
print(ret.sex)  #  1 獲取到的是資料庫中存盤的欄位資料
print(ret.get_sex_display())  # 男性 -- 能夠幫我們獲取到該欄位資料對應的choices指定的元祖中的這個資料對應的文本內容
    # sex_choices = ((1, '男性'), (0, '女性'))  # enum列舉 單選
    # sex = models.IntegerField(choices=sex_choices, default=1)
    # 比如拿sex這個欄位來說,資料庫中存的資料是1,表示男性,如果我想獲取到男性這個字串資料,我直接通過模型類物件.get_sex_display()這個方法就能獲取到,這個方法的語法是get_模型類屬性名稱_display()
1.3、auto_now_add和auto_now

(1)使用

class ShowTime(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    brithday = models.DateTimeField(auto_now_add=True)  # 添加記錄時,自動添加創建時間
    bday = models.DateTimeField(auto_now=True)  # 添加記錄時,也能自動添加創建時間,并且更新記錄時,自動更新為當前時間

(2)操作

    # models.DateTimeField(auto_now=True)中的auto_now=True對update方法沒有效果
    # models.ShowTime.objects.all().update(
    #     name='chao2',
    # )

    # auto_now=True只有在使用save方法來進行更新動作時,才生效,才會自動更新修改時間
    ret = models.ShowTime.objects.get(id=1)
    ret.name = 'zhen'
    ret.save()

2、ORM單表增刪改查

2.1、增
2.2、刪
# 呼叫者可以是model物件,也可以是QuerySet型別資料
    # 呼叫者可以是model物件,可以是querset型別資料
    # obj = models.Book.objects.get(id=3).delete()
    # print(obj) # (1, {'app01.Book': 1})

    # 回傳結果其實還是受影響的行數

    # obj = models.Book.objects.filter(publish='出版社').delete()
    # print(obj)  # (2, {'app01.Book': 2})
    
    # 錯誤示例演示
    # obj = models.Book.objects.delete()
    # 報錯'Manager' object has no attribute 'delete'
    # 控制器沒有delete方法,原因就是怕你一下子全部洗掉了所有資料
    # 如果想洗掉所有資料,
2.3、改
2.4、查
2.5、動態操作
2.6、關鍵字傳參的兩種方式:
方式1
filter(id=5, publish='出版社')
create(id=5, publish='出版社')
...

方式2
filter(**{'id':5, 'publish':'出版社'})
...

3、ORM多表操作增刪改

3.1、表的基本結構
from django.db import models

# Create your models here.

class Author(models.Model):
    """作者表"""
    # id = models.AutoField(primary_key=True)
    # 其實模型類中的id主鍵欄位不需要我們手動指定,django的orm默認會給每張表都添加一個id欄位并且為主鍵,如果我們自己指定了主鍵,以我們自己指定的為準,就不會自動幫你添加主鍵欄位了
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # ad = models.ForeignKey(to='AuthorDetail',to_field='id',on_delete=models.CASCADE)
    # ad = models.ForeignKey('AuthorDetail', on_delete=models.CASCADE)  # 只是單純的外鍵關系,需要手動指定唯一約束才可以,在orm中提供了一對一關系的類,叫做OneToOneField
    ad = models.OneToOneField('AuthorDetail') #foreign + unique
    # books = models.ManyToManyField('Book')
    
    # models.ForeignKey(AuthorDetail)這里面的外鍵關聯的表,可以不寫引號,但是如果不寫引號,那么這個外鍵關聯的表必須在寫這個外鍵欄位的表上面,一般我們都寫上引號,這樣就不用考慮哪個表寫在上面,哪個表寫在下面了
    # ad = models.ForeignKey(AuthorDetail, on_delete=models.CASCADE)
    # to=可以用寫,to_field='id'也可以不用寫,自動找到是to=那張表的id欄位
    # django 1.11 版本的foreignkey 默認有on_delete=models.CASCADE這個屬性,所以如果要做這種級聯洗掉模式,可以不用寫這個屬性,但是django2.0之后,必須要自己手動加上這個屬性

    # 關于級聯,級聯有很多,這里用到的就是下面這個,還有些其他的下面會講解
    # models.CASCADE 級聯洗掉  # 沒辦法設定級聯更新,要做級聯更新,自己通過原生sql去加上,就是修改表

class AuthorDetail(models.Model):
    """作者詳細資訊表"""
    birthday = models.DateField()
    telephone = models.CharField(max_length=24)
    address = models.CharField(max_length=64)
    #通過手機號查找某人  手機號151開頭

class Publish(models.Model):
    name = models.CharField(max_length=64)
    city = models.CharField(max_length=64)

class Book(models.Model):
    title = models.CharField(max_length=64)
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=10,decimal_places=2)
    pub = models.ForeignKey('Publish')
    # ForeignKey這個類,生成資料庫欄位的時候,會自動將該屬性名稱_id作為我們的資料庫欄位名稱
    authors = models.ManyToManyField('Author')
    # authors在執行資料庫同步指令之后,不會生成欄位,而是會幫我們生成一個第三張表,這個表就是書籍表和作者表的多對多關系表

# class BookToAuthor(models.Model):
額外拓展:**{}打散和**request.POST打散
print(request.POST.dict())  # dict()方法能將QueryDict型別資料轉換為普通字典型別資料

# 傳資料時,可以用**{}打散的方式來傳輸入,但是如果碰到models中有decimal型別的欄位資料,那么update更新時,會對提交的資料進行decimal型別資料轉換,
# 發現有Decimal資料要存盤,會將提交的資料轉換為Decimal型別來存盤,所有個資料型別強轉的程序,導致如果我們直接寫**request.POST,會報錯,所以引入了request.POST.dict()這個方法,其實如果說不涉及到強制型別轉換失敗的請求,引數直接寫**request.POST就可以
obj_list.update(
  **request.POST.dict()
)

<!-- 模型類中的pk屬性能夠自動幫我們找到模型類中的主鍵欄位 -->

<form action="/edit_book/{{ id }}/" method="post">
<form action="{% url 'edit_book' id %}" method="post">
<form action="{% url 'edit_book' obj.id %}" method="post">
<form action="{% url 'edit_book' obj.pk %}" method="post">
3.2、一對一
  1. 創建一對一關系欄位的一些屬性

    to
        設定要關聯的表,
    
    to_field
        設定要關聯的欄位,
        
    on_delete
        同ForeignKey欄位,這個是級聯屬性,django2.0之后需要手動添加,之前默認就有的內置屬性
    
  2. 一對一關系表的增刪改查操作

    • # 一對一
          # 如果使用的是模型類的關系屬性名稱來添加資料,那么屬性名稱對應的值必須是關聯表中的某條記錄的模型類物件
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='eric',
          #     age=26,
          #     ad=au_obj  # 其實存在資料庫中的還是au_obj的id值
          # )
      
          # 如果使用資料庫表欄位的形式來創建關系記錄資料,那么需要使用資料庫中表欄位名稱來指定資料(用的居多)
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='pdd',
          #     age=16,
          #     ad_id=2  # 直接使用關聯表中的某條記錄的id值的方式
          # )
      
    • #洗掉
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一對一和一對多洗掉一樣
      
    • # 修改
          # 在一對一和一對多關系時,和單表操作是一樣的
          # 一對一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型類物件)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一對多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查詢功能內容比較多,后面分出一塊詳細補充

3.3、一對多(多對一)
  1. 創建一對多關系欄位的一些引數

    to
        設定要關聯的表
    
    to_field
        設定要關聯的表的欄位
    
    related_name
        反向操作時,使用的欄位名,用于代替原反向查詢時的'表名_set',
        
    related_query_name
        反向查詢操作時,使用的連接前綴,用于替換表名,
    
    on_delete
        當洗掉關聯表中的資料時,當前表與其關聯的行的行為,
    
  2. 一對多(多對一)表的增刪改查

    • # 一對多
          # book表和publish表是多對一的關系
          # 添加記錄和上面的一對一一樣
          # 寫法1
          # publish_obj = models.Publish.objects.get(id=1)
          # models.Book.objects.create(
          #     title='紅樓夢',
          #     pub_date='2008-09-09',
          #     price=88.88,
          #     pub=publish_obj  # 某個出版社的模型類物件
          # )
          # 寫法2
          # models.Book.objects.create(
          #     title='金鱗',
          #     pub_date='2008-09-09',
          #     price=82.82,
          #     pub_id=1  # 某個出版社記錄的id值
          # )
      
    • #洗掉
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一對一和一對多洗掉一樣
      
    • # 修改
          # 在一對一和一對多關系時,和單表操作是一樣的
          # 一對一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型類物件)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一對多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查詢功能內容比較多,后面分出一塊詳細補充

3.4、多對多
  1. 創建多對多關系欄位的一些引數

    ### 多對多的引數:
    to
    	設定要關聯的表
    
    related_name
    	同ForeignKey欄位,
    
    related_query_name
    	同ForeignKey欄位,
    	
    through
    	在使用ManyToManyField欄位時,Django將自動生成一張表來管理多對多的關聯關系,
    
    ### 但我們也可以手動創建第三張表來管理多對多關系,此時就需要通過        
    through來指定第三張表的表名,
    
    through_fields
    	設定關聯的欄位,
    
    db_table
    	默認創建第三張表時,資料庫中表的名稱,       
    	示例:authors = models.ManyToManyField('Author',db_table='xx')
    
  2. 創建第三張表時的三種方式

    • 方式1

      手動創建第三張表(沒辦法使manytomanyfield提供的操作第三張表資料時的方法)
      # 想操作第三張,就需要自己寫sql或者直接對第三張表來添加資料,
      # 比如models.Author2Book.objects.create(author_id=1,book_id=1,xx='oo')
        class Book(models.Model):
            title = models.CharField(max_length=32, verbose_name="書名")
      
        class Author(models.Model):
            name = models.CharField(max_length=32, verbose_name="作者姓名")
      
        # 自己創建第三張表,分別通過外鍵關聯書和作者
        class Author2Book(models.Model):
            author = models.ForeignKey(to="Author")
            book = models.ForeignKey(to="Book")
            xx = models.CharField(max_length=32)  # 拓展欄位
            class Meta:
                unique_together = ("author", "book")
      
    • 方式2

      # 中介模型,orm提供的有些方法可以用,有些用不了,比如add添加資料的方法
      # 手動創建第三張表,并通過ManyToManyField來指定一下這個關系表
      class Book(models.Model):
          title = models.CharField(max_length=32, verbose_name="書名")
      
      # 自己創建第三張表,并通過ManyToManyField指定關聯
      class Author(models.Model):
          name = models.CharField(max_length=32, verbose_name="作者姓名")
          books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
          # through_fields接受一個2元組('field1','field2'):
          # 其中field1是定義ManyToManyField的模型外鍵的名(author),field2是關聯目標模型(book)的外鍵名,
      
      # 比如author_obj的id為1
      author_obj.books.add(1,2)
      '''
      Author2Book
      id author_id  book_id  xx
      1   1          1       怎么添加
      2   1          2       怎么添加  add方法搞不定
      '''
      class Author2Book(models.Model):
          author = models.ForeignKey(to="Author")
          book = models.ForeignKey(to="Book")
          # 可以擴展其他的欄位了
          xx = models.CharField(max_length=32)  # 拓展欄位
          class Meta:
              unique_together = ("author", "book")
      
    • 方式3

      通過ManyToManyField自動生成第三張表
      class Book(models.Model):
          title = models.CharField(max_length=32, verbose_name="書名")
      
      # 通過ORM自帶的ManyToManyField自動創建第三張表
      class Author(models.Model):
          name = models.CharField(max_length=32, verbose_name="作者姓名")
          books = models.ManyToManyField(to="Book", related_name="authors")  #自動生成的第三張表我們是沒有辦法添加其他欄位的
      
  3. 多對多表的增刪改查

    • # 多對多 
          # 作者表和書籍表是多對多關系
          # title1  --  name1和name2
          # book_obj = models.Book.objects.create(
          #     title='title1',
          #     pub_date='2020-07-10',
          #     price=2,
          #     pub_id=1
          # )
      
          # author_obj1 = models.Author.objects.create(
          #     name='name1',
          #     age=16,
          #     ad_id=3  # 直接使用關聯表中的某條記錄的id值的方式
          # )
          # author_obj2 = models.Author.objects.create(
          #     name='name2',
          #     age=16,
          #     ad_id=4  # 直接使用關聯表中的某條記錄的id值的方式
          # )
      
          # 方式1
          # book_obj.authors.add(author_obj1,author_obj2)  # 寫對應作者的模型類物件
          # 方式2
          # book_obj.authors.add(1, 4)  # 直接寫作者記錄的id值
          # 方式3
          book_obj = models.Book.objects.get(id=1)
          book_obj.authors.add(*[2, 3])  # 直接寫作者記錄的id值
          '''
          app01_book_authors
          id  book_id  author_id
          1   3        3
          2   3        4
          '''
      
    • 改和刪

      # 多對多更新和洗掉
          # 針對關系記錄的操作
          # book_obj = models.Book.objects.get(id=1)
      
          # author_obj = models.Author.objects.get(id=2)
          # 洗掉remove  在多對多關系表中洗掉了書籍id為1的,作者id為2的記錄洗掉
          # book_obj.authors.remove(2)  # 洗掉單條
          # book_obj.authors.remove(2, 3)  # 洗掉多條
          # book_obj.authors.remove(author_obj)  # 按照模型類物件為引數進行洗掉
      
          # 清空clear
          # book_obj.authors.clear()  # 將當前書籍對應的所有作者在多對多關系表中的關系記錄,全部洗掉
      
          # 更新(修改)
          # book_obj = models.Book.objects.get(id=4)
          # book_obj.authors.set('3')  # 引數是可迭代型別資料
          # book_obj.authors.set([3, ])  # 更新多個記錄
      
          # book_obj = models.Book.objects.get(id=1)
          # book_obj.authors.set([4, ])
          # set動作有兩步:1 先執行clear   2 再執行add添加
      
    • 查詢功能內容比較多,后面分出一塊詳細補充

3.5、創建模型類時的一些元資訊配置
元資訊
    ORM對應的類里面包含另一個Meta類,而Meta類封裝了一些資料庫的資訊,主要欄位如下:
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
    class Meta:
        unique_together = ("author", "book")

db_table
    ORM在資料庫中的表名默認是 app_類名,可以通過db_table可以重寫表名,db_table = 'book_model'

index_together
    聯合索引,

unique_together
    聯合唯一索引,

ordering
    指定默認按什么欄位排序,
    ordering = ['pub_date',]
    只有設定了該屬性,我們查詢到的結果才可以被reverse(),否則是能對排序了的結果進行反轉(order_by()方法排序過的資料)
3.6、db_column指定列名稱
title = models.CharField(max_length=64,db_column='xx')
author = models.ForeignKey(to="Author",db_column='ss')  
3.7、on_delete級聯模式引數
on_delete
當洗掉關聯表中的資料時,當前表與其關聯的行的行為,

models.CASCADE
洗掉關聯資料,與之關聯也洗掉

models.DO_NOTHING
洗掉關聯資料,引發錯誤IntegrityError

models.PROTECT
洗掉關聯資料,引發錯誤ProtectedError

models.SET_NULL
洗掉關聯資料,與之關聯的值設定為null(前提FK欄位需要設定為可空)
# pub = models.ForeignKey('Publish',on_delete=models.SET_NULL, null=True)

models.SET_DEFAULT
洗掉關聯資料,與之關聯的值設定為默認值(前提FK欄位需要設定默認值)

models.SET

洗掉關聯資料,
a. 與之關聯的值設定為指定值,設定:models.SET(值)
b. 與之關聯的值設定為可執行物件的回傳值,設定:models.SET(可執行物件)
3.8、ForeignKey的db_contraint引數

4、ORM多表操作的查詢

4.0、配置日志查看sql陳述句
1 query
2 在settings組態檔中寫上如下內容,就能夠自動列印出我們執行orm陳述句對應的sql陳述句
	LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
	} 

3 通過django配置的連接mysql的管道來查看(pymysql)
from app01 import models

def add_book(request):
    '''
    添加表記錄
    :param request: http請求資訊
    :return:
    '''
    book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社')
    book_obj.save()
    from django.db import connection  #通過這種方式也能查看執行的sql陳述句
    print(connection.queries)
    return HttpResponse('ok')
4.1、子查詢:基于物件的跨表查詢
# 查詢
# 一對一查詢
    # 正向查詢使用關聯屬性名稱
    # 查詢一下**這個作者的手機號
    # author_obj = models.Author.objects.get(name='**')
    # # author_obj.ad  # 找到了author_obj關聯的作者詳細資訊表里面對應記錄
    # print(author_obj.ad.telephone) # 120

    # 反向查詢
    # 反向查詢用關聯它的模型類的名稱小寫
    # 查詢一下地址在上海的那個作者是誰
    # author_detail_obj = models.AuthorDetail.objects.filter(address='上海').first()
    # print(author_detail_obj.author.name)

# 一對多

    # 正向查詢
    # 使用關聯屬性查詢
    # 查詢一下紅樓懵是哪個出版社出版的
    # book_obj = models.Book.objects.get(title='紅樓懵')
    # print(book_obj.pub.name)

    # 反向查詢
    # 模型類名小寫_set
    # 查詢一下**出版社出版了哪些書
    # pub_obj = models.Publish.objects.get(name='**出版社')
    # pub_obj.book_set  # 可能為多條記錄,所以模型類名小寫_set
    # 類似于objects控制器
    # print(pub_obj.book_set.all().values('title'))
    # 查詢結果不會自動去重

# 多對多
    # 正向查詢
    # 使用屬性來查
    # 查詢一下**第二部這本書是誰寫的
    # book_obj = models.Book.objects.get(title='**第二部')
    # print(book_obj.authors.all().values('name'))

    # 反向查詢
    # 使用模型類名小寫_set
    # 查詢一下**寫了哪些書
    # author_obj = models.Author.objects.get(name='**')
    # print(author_obj.book_set.all().values('title'))
    
4.2、連表查詢:基于雙下劃線的跨表查詢
# 基于雙下劃線的跨表查詢
    # 連表操作
    # select * from t2 inner join t1 on t1.t2_id=t2.id
    # select * from t1 inner join t2 on t1.t2_id=t2.id
# 一對一
    # 查詢一下***這個作者的家庭住址
    # 正向操作,使用屬性
    # ret = models.Author.objects.filter(name='***').values('ad__address')
    # <QuerySet [{'ad__address': '美國'}]>
    # 方向操作,表名小寫
    # ret = models.AuthorDetail.objects.filter(author__name='***').values('address')
    # <QuerySet [{'address': '美國'}]>

# 一對多
    # 查詢一下少年是哪個出版社出版的
    # 正向操作  使用關聯屬性
    # ret = models.Book.objects.filter(title='少年').values('pub__name')
    #<QuerySet [{'pub__name': '橘子成人出版社'}]>

    # 反向操作
    # ret = models.Publish.objects.filter(book__title='少年').values('name')
    #<QuerySet [{'name': '橘子成人出版社'}]>


# 多對多
    # 查詢一下第二部這本書是誰寫的
    # 正向操作  使用關聯屬性
    # ret = models.Book.objects.filter(title='第二部').values('authors__name')
    # <QuerySet [{'authors__name': '***'}, {'authors__name': 'pdd'}]>
    # ret = models.Author.objects.filter(book__title='第二部').values('name')
    # <QuerySet [{'name': '***'}, {'name': 'pdd'}]>
    # print(ret)
4.3、聚合查詢
# 聚合查詢aggregate
    # 統計一下所有書籍的平均價格 max min avg count sum
    # ret = models.Book.objects.aggregate(Avg('price'))
    # print(ret) # 結果是普通字典型別:{'price__avg': 43.925}
    # ret = models.Book.objects.all().aggregate(a=Avg('price'), b=Max('price'))
    # print(ret) # {'price__avg': 43.925, 'price__max': Decimal('88.88')} -- {'a': 43.925, 'b': Decimal('88.88')}
    # aggregate方法可以看為是orm陳述句的結束陳述句,結果為普通字典型別,不能在繼續呼叫queryset或者模型類物件提供的方法了
4.4、分組查詢
# 分組查詢

    # 查詢一下每個出版社出版書的平均價格
  
    # ret = models.Book.objects.values('pub_id').annotate(a=Avg('price')) # 只能獲取到values指定的欄位和統計結果資料
    
    # ret = models.Book.objects.values('pub_id','id').annotate(a=Avg('price')) # 多條件分組pub_id和id值相同的算為1組
    
    # ret = models.Book.objects.values('pub__name').annotate(a=Avg('price')) # 以出版社名稱分組
    
    # print(ret)
    
    # ret = models.Publish.objects.annotate(a=Avg('book__price'))  # 回傳結果是Publish的模型類物件,這個模型類物件里面包含Publish的所有屬性資料,還有annotate的統計結果資料
    
    ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name', 'a')
    # print(ret)
    
		# 原生sql,偽代碼
    # select publish.name,Avg(book.price) from publish inner join book on publish.id=book.pub_id group by publish.id;

    # select avg(price) from book group by pub_id;
4.5、F查詢
# F查詢
    from django.db.models import F
    # 查詢一下點贊數大于評論數的書籍
    # models.Book.objects.filter(dianzan__gt=comment)
    # obj_list = models.Book.objects.all().values()
    # a = []
    # for i in obj_list:
    #     if i.dianzan > i.comment:
    #         a.append(i)
    # print(a)
    # F查詢可以用來做本表不同欄位之間的一個比較
    # ret = models.Book.objects.filter(dianzan__gt=F('comment'))
    # print(ret)
    # F可以用來對本表資料進行一些統一操作(四則運算都支持)
    # 將所有的書籍上調10塊錢
    models.Book.objects.all().update(price=F('price')+10)
    # models.Book.objects.all().update(price=100)
4.6、Q查詢
from django.db.models import Q
#查詢書名中包含少年兩個字的并且評論數大于20的書籍
# ret = models.Book.objects.filter(title__contains='少年',comment__gt=20)  #filter中逗號分隔的條件,默認是and的關系
# <QuerySet [<Book: 少年2>]>

# 想進行或的關系查詢需要借助到我們Q查詢Q
# 查詢書名中包含少年兩個字的或者評論數大于20的書籍Q
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20))

# 查詢書名中包含少年兩個字的或者評論數大于20的,并且點贊數大于等于80的
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20), dianzan__gte=80)
# 注意,如果結合逗號來進行and的關系查詢,那么必須將沒有Q包裹的查詢條件放在Q包裹的查詢條件后面

#下面這種方式也可以,Q查詢可以多層嵌套使用
# ret = models.Book.objects.filter(Q(Q(title__contains='少年') | Q(comment__gt=20)) & ~Q(dianzan__gte=80))  #~ 條件取反 ,&--and關系 ,|--or關系
# print(ret)

5、ORM執行原生sql陳述句

6、ORM鎖和事務

models.Book.objects.select_for_update().all() 
'''
    select * from app01_book for update;
'''
方式1
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        
        "ATOMIC_REQUESTS": True,  # 全域開啟事務,系結的是http請求回應整個程序
        
    },
  
}

方式2  給視圖函式直接加裝飾器,表示整個視圖邏輯中的sql都捆綁為了一個事務操作
from django.db import transaction

# sql一旦出錯,會自動回滾
@transaction.atomic
def viewfunc(request):
  	sid = transaction.savepoint()  #創建保存點
    # This code executes inside a transaction.
    do_stuff()  #事務和變數等處理邏輯是沒關系的,d['xx'] = 'oo'
    ....
    
    do_other_stuff()
    transaction.savepoint_rollback(sid)  #回滾保存點
		#transaction.savepoint_commit(sid) #提交保存點
    
方式3  給邏輯加視圖
from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():  # 加事務
        # This code executes inside a transaction.
        do_more_stuff()
        #models.Book.objects.select_for_update().all()

    do_other_stuff()
    

7、Python腳本中呼叫Django環境

xx.py檔案,隨便建一個檔案,單獨運行這個檔案時,想獲取到django的內容環境內容,需要下面的寫法

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm2.settings")
    import django
    django.setup()

    from app01 import models
    print(models.Book.objects.all())

八、Ajax的使用

1、ajax介紹

1.1、ajax(Asynchronous Javascript And XML)翻譯成中文就是“異步Javascript和XML”,
即使用Javascript語言與服務器進行異步互動,傳輸的資料為XML(但傳輸的資料不僅是XML)
1.2、ajax還有一個最大的特點就是,當服務器回應時,不用重繪整個瀏覽器頁面,而是可以區域重繪
這一特點給用戶的感受是在不知不覺中完成請求和回應程序,
(1)與服務器異步互動
(2)瀏覽器頁面區域重繪
1.3、同步互動與異步互動
(1)同步互動:客戶端發出一個請求后,需要等待服務器回應結束后,才能發出第二個請求;
(2)異步互動:客戶端發出一個請求后,無需等待服務器回應結束,就可以發出第二個請求,
注:資料的發送是暗著發送的(ajax更新資料只改一小塊)
優點:
ajax使用Javascript技術向服務器發送異步請求;
ajax無需重繪整個頁面;
因為服務器回應內容不再是整個頁面,而是頁面中的區域,所以ajax性能高;
缺點:
ajax并不適和所有場景,很多時候還是要使用同步互動;
ajax雖然提高了用戶體驗,但無形中向服務器發送的請求次數增多了,導致服務器壓力增大;
因為ajax是在瀏覽器中使用Javascript技術完成的,所以還需要處理瀏覽器兼容問題;
操作:
創建核心物件;
使用核心物件打開與服務器的連接;
發送請求;
注冊監聽,監聽服務器回應
XMLHTTPRequest
open(請求方式,URL,是否異步)
send(請求體)
onreadystate,指定監聽函式,他會在xmlHTTP物件的狀態發生變化時被呼叫
readyState,當前xmlHTTP物件的狀態,其中4狀態表示服務器回應結果
status,服務器回應的狀態碼,只有服務器回應結束時才有這個東東,200表示回應成功
requestText:獲取服務器的回應體

2、ajax的使用方式

2.1、基于javascript的ajax的實作
GET:ajax具體實作(JS方式):
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">ajax提交</button>
    </body>
    <script>
        function func1(){
            // step1
            var xmlhttp=createXMLHttpRequest();
            // step4:推薦放到這個位置
            xmlhttp.onreadystatechange=function() {  //監聽
                // alert(xmlhttp.status);  //回傳狀態碼
                if(xmlhttp.readyState==4 && xmlhttp.status==200){
                    {#alert(xmlhttp.readyState);//查看狀態程序#}
                    var data=https://www.cnblogs.com/wylshkjj/p/xmlhttp.responseText;
                    alert(data);
                };
            }
            // step2
            xmlhttp.open("GET","/ajax/",true);  //打開服務器連接
            // step3
            xmlhttp.send(null);  //發送請求
        };
        // 處理瀏覽器兼容問題
        function createXMLHttpRequest(){
            var xmlHttp;
            // 適用于大多數瀏覽器,以及IE7和更高版本
            try{
                xmlHttp = new XMLHttpRequest();
            } catch (e){
                // 適用于IE6
                try{
                    xmlHttp = new ActiveXObject("Msxm12.XMLHTTP");
                } catch (e){
                    // 適用于IE5.5,以及更早版本
                    try{
                       xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                    } catch (e){}
                }
            }
            return xmlHttp;
        }

    </script>
    


POST:請求注意事項
    POST請求必須設定ContentType請求頭的值為:application/x-www-form-urlencoded,
    表單的enctype默認值就是為application/x-www-form-urlencoded,因為默認值就是這個,
    當設定了
的enctype="application/x-www-form-urlencoded"時,等同于設定了Con tent-Type請求頭,但在ajax發送請求時,就沒有默認值了,這需要自行設定請求頭: xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded") 實體: <meta charset="UTF-8"> Title <body>

用戶名:

密碼:

<script> function func1(self){ var username=self.value; var xmlhttp=createXMLHttpRequest(); xmlhttp.open("POST","/register/",true); xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); xmlhttp.send("username="+username); xmlhttp.onreadystatechange=function() { if(xmlhttp.readyState==4 && xmlhttp.status==200){ var s=xmlhttp.responseText; if (s=="1"){ document.getElementById("error").innerHTML="用戶名已經注冊過了"; } }; } } // 處理瀏覽器兼容問題 function createXMLHttpRequest(){ var xmlHttp; // 適用于大多數瀏覽器,以及IE7和更高版本 try{ xmlHttp = new XMLHttpRequest(); } catch (e){ // 適用于IE6 try{ xmlHttp = new ActiveXObject("Msxm12.XMLHTTP"); } catch (e){ // 適用于IE5.5,以及更早版本 try{ xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e){} } } return xmlHttp; } </script>
2.2、基于JQuery的ajax的實作:(最底層方法:$.ajax())
  1. 最底層方法:$.ajax()

    (1)$.ajax({
            url:"/url地址/",  
            type:"POST"  // 請求方法
        })
    (2)$.ajax("url地址",{})
    
    /* 
    url 引數:
    	可以是相對路徑,可以是絕對路徑,瀏覽器不會跳到這個路徑的,這是ajax請發送請求,往哪里發使用的
    	寫路徑時,如果后臺使用的是django框架,那么url路徑的后面的斜杠要加上,如果想不加上斜杠,那么需要在django的settings組態檔中加上 APPEND_SLASH = False,并且后臺的urls.py檔案中的路徑要和ajax請求路徑對應好,該有斜杠寫斜杠,不需要斜杠的,去掉斜杠 
    */
    
  2. 在js代碼中可以使用url別名反向決議來寫路徑

    $.ajax({  //
      url:'{% url "login_ajax" %}',
      type:'post',
      ...
    }),
    /*
    	但是,要注意一點,當這段js代碼是寫到某個js檔案中,然后hmtl檔案通過script標簽的src屬性來引入時,你的{% url "login_ajax" %}語法就不能被模板渲染了,也就是失效了
    */
    
  3. 高級方法:

    • $.get("url地址",{}):通過GET發送請求
    • $.post("url地址",{}):通過POST發送請求
    • $.getJson("url地址",{}):相當于$.get( 引數 datetype:Json)
    • $.getScript("url地址",{}):實時加載,現用現取,不用不加載
  4. 請求資料型別:data,processDate,contentType,traditional,dataType,sucess

    • data:當前ajax請求要攜帶的資料,是一個json的object物件,ajax方法就會默認地把它編碼成某種格式(urlencoded?a=1&b=2)發送給服務端;此外,ajax默認以get方式發送求,

    • processDate:

      ? processDate:宣告當前的data資料是否進行轉碼或預處理,默認為true,即預處理;if為false,那么對data:{a:1,b:2}會呼叫json物件的toString()方法,即{a:1,b:2}.toString(),最后得到一個[object,Object]形式的結果,該屬性的意義在于,當 data是一個dom結構或者xml資料時,我們希望資料不進行處理,直接發過去,就可以將其設定為true,

    • contentType:

      ? 默認值:"application/x-www-form-urlencoded",發送資訊至服務器時內容編碼型別,用來指明當前請求的資料編碼格式:urlencoded:?a:1&b:2;如果想以其他方式提交資料,比如contentType:"application/json",即向服務器發送一個json字串,

    • dataType:
      當dataType已經宣告為“json”時,就是告訴服務器“要回就給給我回json格式的,不然老子不要!”,而且接受到資料后將自動轉換成JavaScript物件,dataType的可用值:html xml json text script

    • success:
      success引數:success:function(){} 當成功執行時執行的函式

    • complete:
      complete引數:complete:function(){} 當執行不成功的時候執行的函式
      complete會列印所有錯誤資訊

    • beforeSend(XHR):
      型別:Function發送請求前可修改XMLHttpRequest 物件的函,如添加自定義HTTP頭,XMLHttpRequerst
      物件是唯一的引數,這是一個Ajax事件,如果回傳false可以取消本次ajax請求,

    • error:
      error:function(data){
      alert(data)
      }
      內部錯誤,指服務器的內部錯誤

    <!-- 實體: -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">AJAX提交</button>
    <script src="/static/jquery-3.1.1.min.js"></script>
    <script>
        function func1() {
            Text()
        }
        function Text(){
        //回呼函式
            //$.post();
            //$.get("/jquery/",{name:"eric"});
            //$.post("/jquery/",{name:"eric"});
            $.post("/jquery_get/",{name:"eric"},function(data,stateText,obj){
                //console.log(arguments);//列印引數物件中的所有引數
                console.log(data);
                console.log(stateText);
                console.log(obj);
                alert(data);
            });
        }
    </script>
    </body>
    </html> 
    
    <!-- $.ajax() 實體: -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <button onclick="func1()">AJAX提交</button>
    <script src="/static/jquery-3.1.1.min.js"></script>
    <script>
        function func1() {
            Text()
        }
        function Text(){
            // $.post();
            // $.get("/jquery/",{name:"eric"});
            // $.post("/jquery/",{name:"eric"});
            $.post("/jquery_get/",{name:"eric"},function(data,stateText,obj){
                // console.log(arguments);  // 列印引數物件中的所有引數
                console.log(data);
                console.log(stateText);
                console.log(obj);
                alert(data);
            });
            $.ajax({
                url:"/jquery_get/",
                type:"POST",
                // data:{a:1,b:2},
                data:{a:1,b:[3,4]},  // 迭代回圈,深層回圈
                traditional:true  // 禁止程式執行默認的迭代回圈,深層回圈
                // processData:false,
                // contextType:text,
            });
        }
    </script>
    </body>
    </html>
    
2.3、jQuery的data屬性寫法有很多

常用的三種:

要注意data和Content-Type還有datatype三者的區別
2.4、JSON和JSONP

后面補充

3、ajax的實際應用

3.1、通過httprespnse回應字典型別資料
3.2、通過httprespnse回應非字典型別資料
# 方式一的基礎上前端不變
import json
from django.http import JsonResponse
def data(request):
    hobby_list = {'name': 'xx', 'hobby': ['女', 'gay']}
    return JsonResponse(hobby_list, safe=False)
4、ajax檔案上傳

后面與其他檔案上傳方法一起解釋

九、cookie和session

1、cookie

cookie的特點:

  上面的資料只是HTTP的Cookie規范,但在瀏覽器大戰的今天,一些瀏覽器為了打敗對手,為了展現自己的能力起見,可能對Cookie規范“擴展”了一些,例如每個Cookie的大小為8KB,最多可保存500個Cookie等!但也不會出現把你硬碟占滿的可能!
注意,不同瀏覽器之間是不共享Cookie的,也就是說在你使用IE訪問服務器時,服務器會把Cookie發給IE,然后由IE保存起來,當你在使用FireFox訪問服務器時,不可能把IE保存的Cookie發送給服務器,

(1)cookie是保存在瀏覽器端的鍵值對,可以用做登錄

1.保存在用戶瀏覽器
2.可以主動清除
3.可以被“偽造”
4.跨域名cookie不共享
5.瀏覽器設定不接收cookie

(2)服務端設定Cookie

v=datetime.datetime.utcnow() + datetime.timedelta(seconds=10)
# 設定cookie超時時間,系統默認一個時間,也可以用此方式添加一個時間,但以系統默認時間為基準,
obj.set_cookie('k1','v1',max_age=10,expires=v,path="",domain="",secure=False,httponly=False)
# max_age時間周期,給某個url生效設定cookie,如果path="/"表示全域生效,
# domain引數:用域名設定訪問權限
secure引數(證書訪問)執行https的cookie設定等操作的時候用ture
httponly引數:(安全)禁止了js使用時獲取cookies,僅僅Http網路傳輸使用
獲取cookie資料:.request_COOKIES.get() 

(3)cookie:

客戶端可修改
服務端也可修改
保存在客戶端

(4)cookie 應用:

登錄認證:
      普通的cookie:
           敏感資訊,不適于放在cookie中,放在資料庫中,頻繁操作資料庫
      簽名的cookie(可能會被解密):
           加密的cookie使用(簽名):
              .set_signed_cookie()
           解密資料:request.get_signed_cookie()
           cookie時做認證的時候,將不敏感的資訊放在cookie中,頻繁操作資料庫

(5)cookie操作代碼示例:

# 獲取cookie
	request.COOKIES.get('is_login')
  
# 設定cookie
	ret = redirect('/home/') # 302  /home/
  # ret = HttpResponse('ok')
  # ret = render(request,'home.html')
  # 設定cookie
  ret.set_cookie('is_login', True)
  ret.set_cookie('name', 'root')  # 設定同名的cookie,就是修改cookie
  return ret

# 設定簽名cookie
	ret.set_signed_cookie('name', 'root',salt='sksk')  
# 獲取簽名cookie
	request.get_signed_cookie('name',salt='sksk')  # 以防cookie在傳輸程序中被人修改了
  
# 洗掉cookie
def logout(request):
    ret = redirect('login')
    ret.delete_cookie('is_login')
    return ret
  

(6)cookie引數代碼示例:

key, 鍵

value='', 值

max_age=None, 超時時間  值一個數字,單位是秒

expires=None, 超時時間(IE requires expires, so set it if hasn't been already.) 值是日期時間型別資料

path='/', Cookie生效的路徑,/ 表示根路徑,特殊的:根路徑的cookie可以被任何url的頁面訪問
當:path='/index',
127.0.0.1:8001/home  cookie 不生效,獲取不到
127.0.0.1:8001/index/xxx/xx/ggg  生效,凡是訪問/index路徑下面的子路路徑,都能獲取到這個cookie

domain=None, Cookie生效的域名

secure=False, https傳輸

httponly=False 只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到也可以被覆寫)

(7)cookie值中設定中文的方法,但是實際上開發并沒有這樣做的

方式1涉及到了編碼和解碼的知識,請看博客:編碼與解碼

# 方式1
def login(request):

    ret = HttpResponse('ok')
    
    ret.set_cookie('k1','你好'.encode('utf-8').decode('iso-8859-1'))
    
    # 取值:request.COOKIES['k1'].encode('utf-8').decode('iso-8859-1').encode('iso-8859-1').decode('utf-8')

    return ret

# 方式2 json

def login(request):

    ret = HttpResponse('ok')
    
    import json

    ret.set_cookie('k1',json.dumps('你好'))
    # 取值 json.loads(request.COOKIES['k1'])
    return ret

(8)基于cookie的登錄認證的裝飾器

這里涉及python的基礎知識,請看博客:裝飾器和函式的閉包

示例

def check_login(f):
    def inner(request, *args, **kwargs):
        is_login = request.COOKIES.get('is_login')
        if is_login == 'True':
            ret = f(request)
            return ret
        else:
            return redirect('login')
    return inner


@check_login
def cart(request):
    # is_login = request.COOKIES.get('is_login')
    # if is_login == 'True':
    return render(request, 'cart.html')

2、session

session都是request操作的,session是保存在服務器上的鍵值對,依賴于cookie

針對以上特點cookie有資料大小限制,還明文存盤在客戶端,出現了session技術

session技術就是為了提升上面兩個特點,

基于cookie技術(目前先這樣里面,其實不通過cookie也能完成session技術)

session特點:

? 1.基于cookie時,也是以鍵值對的形式來存的資料,但是資料是密文的,只是一把鑰匙(隨機字串)

? 2.真正的資料是保存在服務端的,將來通過cookie帶過來的這個隨機字串,來找到服務端保存的對應用戶的資料,資料既然是保存在服務端的,那么資料大小沒有限制

注意:一個瀏覽器對應一個服務端,只有一個session,

(1)設定session

request.session['key'] = value
request.session.setdefault(key,value)  # 存在就不設定

(2)獲取session

request.session['key']
request.session.get(key,'')

(3)洗掉session

request.session.pop(key)  #洗掉某一個鍵值對
del request.session['key']
request.session.delete()  #洗掉所有的session鍵值對
request.session.flush()   #洗掉所有的session鍵值對.洗掉了cookie

(4)設定超時時間

request.session.set_expiry()

(5)清除當前過期的session

request.session.clear_expired()
用戶session的隨機字串(key):request.session.session_key
將所有session失效日期小于當前日期的資料洗掉:request.session.clear_expired()
檢查用戶session的隨機字串 在資料庫中是否存在:request.session.exists("session_key")
洗掉當前用戶的所有session資料:request.session.delete()
洗掉當前用戶的資料并洗掉Session的Cookie:request.session.flush()
確保用戶前面的資料不可以再次被用戶的瀏覽器訪問,呼叫函式:django.contrib.auth.logout()
注:設定會話Session和Cookie的超時時間
request.session.set_expiry(value)
(1).如果value是個整數,session會在些秒數后失效,
(2).如果value是個datatime或timedelta,session就會在這個時間后失效,
(3).如果value是0,用戶關閉瀏覽器session就會失效,
(4).如果value是None,session會依賴全域session失效策略,

(6)流程:瀏覽器首次訪問服務器的時候沒有cookie,然后訪問服務器,服務器會生成一個session_ID字典,

字典的值為瀏覽器的資料和cookie(一個隨機字串),然后回傳session_ID給瀏覽器,下次瀏覽器再來的時
候攜帶session_ID訪問服務器,可以唯一找到對應的服務器存盤的session,并且打開這個字典,并從中取到值做對應校驗,檢驗是否正確能否訪問
注:一個session對應一個cookie,并且一個瀏覽器對應一個session資料
session存在資料庫中,默認存在django_session表中,也可以存在一個檔案當中,或者快取當中,這個取決于自設定.

(7)代碼示例

# 設定值
	request.session['is_login'] = True  # 等于別的值,就是修改動作
	request.session['username'] = 'root'
'''
    request.session['is_login'] = True
    做了三步事情
    1. 生成一個隨機字串,并將這個字串添加到了cookie里面,鍵值對名稱這樣的: sessionid:ljlijsoidjoasdiog
    2. 將is_login和username這兩個鍵值對資料{'is_login':True,'username':'root'}首先進行了json序列化,然后進行了加密,然后將加密后的資料和sessionid對應的隨機值保存到了django-sesison表中,
    3. 給session保存的資料,加了有效期,默認是兩周
'''
# 取值
	request.session['k1']
	is_login = request.session.get('is_login')
'''
    request.session['is_login']
    1.去cookie里面取出sessionid這個鍵對應的值(隨機字串)
    2.通過這個隨機字串去django-session表里面獲取對應的資料,session_data那一列的資料
    3.對資料進行解密和反序列化,得到{'is_login':True,'username':'root'},然后通過鍵取出對應的值
'''
  
# 洗掉值
	request.session.flush()
  # del request.session['k1']
  
'''
    1 洗掉cookie中的sessionid值
    2 洗掉django-sesion表中保存的資料記錄
'''

# 所有 鍵、值、鍵值對
request.session.keys()
request.session.values()
request.session.items()
# 獲取sessionid的值
session_key = request.session.session_key

# 設定會話Session和Cookie的超時時間
request.session.set_expiry(value)
    * 如果value是個整數,session會在些秒數后失效,
    * 如果value是個datatime或timedelta,session就會在這個時間后失效,
    * 如果value是0,用戶關閉瀏覽器session就會失效,
    * 如果value是None,session會依賴全域session失效策略,

(8)session全域配置

1. 資料庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默認)

2. 快取Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'  # 使用的快取別名(默認記憶體快取,也可以是memcache),此處別名依賴快取的設定

3. 檔案Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None   # 快取檔案路徑,如果為None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() 

4. 快取+資料庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_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過期(默認)

(9)基于session的登錄認證的裝飾器

示例

def check_login(f):
    def inner(request, *args, **kwargs):
        is_login = request.session.get('is_login')
        if is_login:
            ret = f(request, *args, **kwargs)
            return ret
        else:
            return redirect('login')
    return inner
#使用
@check_login
def cart(request):
    return render(request, 'cart.html')

3、jQuery操作cookie

下載:http://plugins.jquery.com/cookie/

引入

<script type="text/javascript" src=https://www.cnblogs.com/wylshkjj/p/"js/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>

cookie操作

設定值:
	$.cookie('the_cookie', 'the_value');
	# 設定7天有效期
	$.cookie('the_cookie', 'the_value', { expires: 7 });

讀取cookie
	$.cookie('the_cookie');  # 通過鍵來取值
洗掉cookie
	$.cookie('the_cookie', null);   // 通過傳遞null作為cookie的值即可

缺點就是當前端進制了jquery的調度或者cookie的檔案調度就會失效

十、Django中間件

1、中間件

(1)組態檔

? 專案主目錄中的組態檔是django留給開發者使用的用戶級別組態檔

? 實際上django有一個自己的默認全域組態檔,

那么他們的關系如下

# django默認配置
from django.conf import global_settings

# 用戶級別配置
from django_middleware import settings


from django.conf import settings # 中間人
# 比如需要引入配置中的某個配置項settings.APPEND_SLASH,那么這樣的引入的查找順序是這樣,先去用戶級別settings.py檔案中去找這個配合,如果沒有找到,那么會去global_settings中去找默認配置

(2)Django請求的生命周期

? 中間件用來對請求和回應做一些統一加工和處理的,比如對所有請求中的post請求做一個csrftoken認證,就用到了我們的'django.middleware.csrf.CsrfViewMiddleware',后面視圖中能夠使用request.session做一個session相關操作,這個request.session的功能就是在這個中間件中加工好的'django.contrib.sessions.middleware.SessionMiddleware'

(3)中間件(重要)

在Django的setting中有個MIDDLEWARE串列,里面的東西可以理解為過濾管道,里面有個安全過濾管道: from django.middleware.csrf import CsrfViewMiddleware里面有五個常用的方法:

事實上做常用到的只有:process_request和process_response這兩個方法;

process_request和process_views這兩個方法動作是在views.py之前執行操作的;

process_template_response這個方法動作實在views.py中通過rander和一下改變下觸發的;

process_exception和process_response這兩個方法的動作是在views.py執行完畢后再執行的,

中間件執行順序流程圖:
流程圖

(4)自定義中間件

中間件應用場景:登錄認證、權限認證、頻率訪問限制,請求資料統計等

自定義中間件的步驟

  1. 在應用檔案夾下面創建一個檔案夾,名字隨便,比如叫做mymiddleware
  2. 在mymiddleware檔案夾下面創建一個py檔案,名稱隨意,比如叫做middlewares.py
  3. 在middlewares.py檔案中寫上如下內容
from django.utils.deprecation import MiddlewareMixin
class LoginAuth(MiddlewareMixin):
    def process_request(self,request):
        print('xxxxxx')
  1. 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',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
    # 配置中間件類,告訴django,我寫的這個自定義中間件,你幫我使用上,一般都放到最后,不然上面幾個中間件的相關功能就沒辦法使用上了
    'app01.mymiddleware.middlewares.LoginAuth',
  # 如果中間件功能不想用了,就注釋它就行了
  # 'app01.mymiddleware.middlewares.LoginAuth'
]

這樣幾部搞定之后,所有的請求都會觸發我們的中間件類中的process_request方法,

(5)登錄認證中間件示例

中間件代碼如下

# 登錄認證中間件
class LoginAuth(MiddlewareMixin):
    # /login/登錄請求,應該正常放行,讓他能獲得login.html頁面
    # 白名單、黑名單
    white_list = ['/login/', ]
    def process_request(self,request):
        print('請求來啦!!!')
        # 獲取當前請求路徑:request.path /home/
        # 如果當前請求的路徑在白名單里面,我們不進行登錄認證
        if not request.path in self.white_list:

            is_login = request.session.get('is_login')  #True
            if not is_login:
                return redirect('/login/')

視圖代碼如下

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        print('xxxxx')
        uname = request.POST.get('username')
        if uname == 'root':
            request.session['is_login'] = True

            return redirect('/home/')  #告訴瀏覽器向/home/發送get請求獲取頁面
        else:
            return redirect('/login/')


def home(request):
    print('home')
    return render(request, 'home.html')


def index(request):
    print('index')
    return render(request, 'index.html')
(6)五個方法的詳細執行流程
  1. process_request(self,request)

    # 示例代碼
    class Md1(MiddlewareMixin):
        def process_request(self,request):
            print('MD1--process_request')
            # 當process_request回傳的是None,那么才會繼續執行后面的中間件的process_request,如果你是最后一個中間件,并且你回傳的是None,那么邏輯會繼續執行到我們的url路由控制器
            # 但是當process_request里面return的是一個HttpResponse物件,那么后面的中間件的process_request將不再執行,也不會走到url路由控制器
            return HttpResponse('中間件md1的邏輯,沒有通過!!!!')
    
    class Md2(MiddlewareMixin):
        def process_request(self, request):
            print('MD2--process_request')
    
  2. process_view(self, request, view_func, view_args, view_kwargs)

    # 示例代碼
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    # 登錄認證中間件
    class LoginAuth(MiddlewareMixin):
        # /login/登錄請求,應該正常放行,讓他能獲得login.html頁面
        # 白名單、黑名單
        white_list = ['/login/', ]
        def process_request(self,request):
            print('請求來啦!!!')
            # 獲取當前請求路徑:request.path /home/
            # 如果當前請求的路徑在白名單里面,我們不進行登錄認證
            if not request.path in self.white_list:
                is_login = request.session.get('is_login')  #True
                if not is_login:
                    return redirect('/login/')
    
            # True,None
            # if is_login:
            #     return None
            # else:
            #     return redirect('/login/')
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中間件md1的邏輯,沒有通過!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--回應')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--回應')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            '''
            :param request:
            :param view_func:   此次請求要執行的視圖函式物件
            :param view_args:  要函式的引數
            :param view_kwargs: 要函式的引數
            :return:
            '''
            print('md2---view')
            # print(view_func, view_args, view_kwargs)
            print(view_func.__name__) # home
    
    
    
  3. process_template_response(self,request,response)

    # 代碼示例
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中間件md1的邏輯,沒有通過!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--回應')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
        def process_exception(self, request, exception):
            print('md1--exception')
    
        def process_template_response(self, request, response):
            print("MD1 中的process_template_response")
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--回應')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md2---view')
    
        def process_exception(self, request, exception):
    
            print('md2--exception')
    
        def process_template_response(self, request, response):
            print("MD2 中的process_template_response")
            return response
    
    # 視圖代碼
    def home(request):
        print('home')
        def render():
            print('你好render')
            return HttpResponse('你好response')
    
        ret = HttpResponse('ojbk')
        # 必須給HttpResponse物件封裝一個render屬性等于一個render函式,才能出發中間件里面的process_template_response方法
        ret.render = render
        return ret
    
  4. process_exception(self, request, exception)

    作用:當視圖函式出現錯誤或者例外時,自動觸發執行的方法,能夠捕獲視圖出現的錯誤,進行一些錯誤的統一處理

    # 示例代碼
    from django.shortcuts import render, redirect, HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    # 登錄認證中間件
    class LoginAuth(MiddlewareMixin):
        # /login/登錄請求,應該正常放行,讓他能獲得login.html頁面
        # 白名單、黑名單
        white_list = ['/login/', ]
        def process_request(self,request):
            print('請求來啦!!!')
            # 獲取當前請求路徑:request.path /home/
            # 如果當前請求的路徑在白名單里面,我們不進行登錄認證
            if not request.path in self.white_list:
    
                is_login = request.session.get('is_login')  #True
                if not is_login:
                    return redirect('/login/')
    
            # True,None
            # if is_login:
            #     return None
            # else:
            #     return redirect('/login/')
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # return HttpResponse('中間件md1的邏輯,沒有通過!!!!')
    
        def process_response(self, request, response):
    
            print('Md1--回應')
            return response
        def process_view(self, request, view_func, view_args, view_kwargs):
            print('md1---view')
    
        def process_exception(self, request, exception):
            print('md1--exception')
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--回應')
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            '''
            :param request:
            :param view_func:   此次請求要執行的視圖函式物件
            :param view_args:  要函式的引數
            :param view_kwargs: 要函式的引數
            :return:
            '''
            print('md2---view')
            # print(view_func, view_args, view_kwargs)
            # print(view_func.__name__) # home
    
        def process_exception(self, request, exception):
            
            from django.core.exceptions import PermissionDenied
            # print('>>>>>>', exception, type(exception))
            # if isinstance(exception,PermissionDenied):
            #     return HttpResponse('權限不夠!!!!!')
            #>>>>>> 權限不夠 <class 'django.core.exceptions.PermissionDenied'>
            # >>>>>> 出錯啦!!!
            print('md2--exception')
    
    
    
    # 視圖函式示例
    from django.core.exceptions import PermissionDenied
    
    def home(request):
        print('home')
        # raise Exception('出錯啦!!!')
        raise PermissionDenied('權限不夠')
        return render(request, 'home.html')
    
  5. process_response(self, request, response)

    注意:當中間件1的process_request方法return的是一個HttpResponse物件,那么會執行中間件1的process_response方法,不會去執行中間件2 的方法了

    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print('MD1--process_request')
            # 當process_request回傳的是None,那么才會繼續執行后面的中間件的process_request,如果你是最后一個中間件,并且你回傳的是None,那么邏輯會繼續執行到我們的url路由控制器
            # 但是當process_request里面return的是一個HttpResponse物件,那么后面的中間件的process_request將不再執行,也不會走到url路由控制器
            return HttpResponse('中間件md1的邏輯,沒有通過!!!!')
    
        def process_response(self, request, response):
            '''
    
            :param request:   當前請求物件
            :param response:   視圖函式的回應物件
            :return:
            '''
            print('Md1--回應')
            # print(response) #<HttpResponse status_code=200, "text/html; charset=utf-8">
            # 對回應內容做統一處理
            # response['xx'] = 'oo'  #統一加回應頭
            # print(response.content) #b'ok'
    
            # 注意,如果你寫了process_response方法,那么這個方法必須return response
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self, request):
            print('MD2--process_request')
    
        def process_response(self, request, response):
            print('Md2--回應')
            # if response.content != b'ok':
                # 如果process_response方法里面return了一個HttpResponse物件,這個物件會覆寫視圖回傳來的HttpResponse物件
                # return HttpResponse('回應不ok!!!')
            return response
    

2、csrf跨站請求偽造

默認情況下不使用任何方法或裝飾器,在Django專案組態檔setting.py檔案的MIDDLEWARE的csrf管道會攔截post請求,

十一、Form驗證

1、Model&Form&ModelForm區分
2、Forms組件

提供了三個功能

3、forms組件的使用流程
  1. 創建一個自定義的form類,代碼如下

    from django import forms
    class RegisterForm(forms.Form):
        phone = forms.CharField()
        username = forms.CharField()
        password = forms.CharField()
        # 可以將label顯示為中文
        phone = forms.CharField(label='手機號')
        username = forms.CharField(label='用戶名')
        password = forms.CharField(label='密碼')
    
  2. 視圖函式

    def register(request):
    
        if request.method == 'GET':
            form = RegisterForm()  #將上面的form類進行實體化
            return render(request, 'register.html', {'form': form})
        else:
            print(request.POST)
            return HttpResponse('ok')
    
  3. html(自動生成標簽的功能)

    <!-- 自動生成對應標簽 -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>注冊頁面</h1>
    <form action="/register/" method="post">
        {% csrf_token %}
        <div>
            <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
            {{ form.phone }}
        </div>
        <div>
            <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
            {{ form.username }}
        </div>
        <div>
            <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
            {{ form.password }}
        </div>
        <input type="submit">
    </form>
    </body>
    </html>
    
4、保留原資料和校驗功能

forms組件提供的html自動生成標簽的動能,上面的使用流程的第三步就體現了這里不再贅述

forms組件示例代碼

class RegisterForm(forms.Form):
    # 每個欄位,默認都有一個required=True的引數,表示該欄位資料不能為空
    # phone = forms.CharField(label='手機號', required=True)
    phone = forms.CharField(
        label='手機號',
        required=True,
        # 錯誤提示資訊的自定制
        error_messages={
            'required': '小敏敏提示您,不能為空!',
        }
    )
    username = forms.CharField(label='用戶名')
    password = forms.CharField(label='密碼')

views.py內容如下

def register(request):

    if request.method == 'GET':
        form = RegisterForm()

        return render(request, 'register.html', {'form': form})
    else:
        print(request.POST)
        form = RegisterForm(data=https://www.cnblogs.com/wylshkjj/p/request.POST)
        # 把資料交給了RegisterForm,那么在通過form來渲染標簽時,就將原來的資料以默認值的形式(value='值')生成在了對應標簽上
        if form.is_valid():  # 執行校驗,如果所有資料都校驗通過了,回傳True,但凡有一個資料,沒有通過校驗,回傳False
            print('通過校驗的資料', form.cleaned_data)
            return HttpResponse('ok')

        print('錯誤資訊>>>',form.errors)

        return render(request, 'register.html', {'form': form})

register.html內容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
      /* 給錯誤資訊定制一些css樣式 */
        .error_msg{
            font-size: 12px;
            color:red;
        }
    </style>
</head>
<body>
<h1>注冊頁面</h1>
<!-- form標簽的這個novalidate屬性,就是告訴瀏覽器,別多管閑事兒,也就去掉了瀏覽器的那個必須填的提示資訊 -->
<form action="/register/" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
        {{ form.phone }}  
        <span class="error_msg">{{ form.phone.errors.0 }}</span> <!-- 展示一個欄位的錯誤資訊 -->
    </div>
    <div>
        <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
        {{ form.username }}
        <span class="error_msg">{{ form.username.errors.0 }}</span>
    </div>
    <div>
        <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
        {{ form.password }}
        <span class="error_msg">{{ form.password.errors.0 }}</span>
    </div>
    <input type="submit">
</form>
</body>
</html>
5、Form常用欄位屬性與插件
(1)initial

初始值

username = forms.CharField(label='用戶名',initial='張三')
# 生成input標簽有個默認值
(2)widget

插件的意思,能夠定制我們的標簽顯示效果

示例1 密文輸入框
password = forms.CharField(
        label='密碼',
        # type = 'password'
        # widget=forms.widgets.PasswordInput,
        # 簡寫,不需要寫widgets.
        widget=forms.PasswordInput,
        # CharField默認的插件是TextInput
        # widget=forms.widgets.TextInput,
    )

示例2 給標簽加上一些其他屬性
password = forms.CharField(
        label='密碼',
        #attrs={'標簽屬性':'屬性值'}
        #widget=forms.TextInput(attrs={'class':'c1 c2',})
  			#widget=forms.PasswordInput(attrs={'class':'c1',})
  			widget=forms.TextInput(attrs={'class':'c1 c2','placeholder':'請輸入密碼'}),
    )

生成單選下拉框

  #sex = forms.fields.ChoiceField() fields可以不用寫  
  sex = forms.ChoiceField(
        choices=((1,'女'), (2,'男')),
      	#(1,'女') 生成一個option標簽,value值為1,文本內容為女
        label='性別',
        # initial=2,  #初始值

    )

單選radio框

    sex = forms.ChoiceField(
        choices=((1,'女'), (2,'男')),
        label='性別',
        # initial=2,
        
        # widget=forms.Select, #ChoiceField的默認插件
        
        # input,type='radio'的單選框
        # widget=forms.RadioSelect,
        widget=forms.RadioSelect(attrs={'class': 'c1'}),
    )

多選下拉框

hobby = forms.MultipleChoiceField(
        choices=((1,'喝酒'),(2,'抽煙'),(3,'勵志')),
        label='愛好',
    )

多選checkbox框

    hobby = forms.MultipleChoiceField(
        choices=((1, '喝酒'), (2, '抽煙'), (3, '勵志')),
        label='愛好',
        # widget=forms.CheckboxInput, #做單選功能的復(多)選框形式
        widget=forms.CheckboxSelectMultiple,
    )

單選功能的復選框形式

    hobby = forms.MultipleChoiceField(
        #choices=((1, '喝酒'), (2, '抽煙'), (3, '勵志')),
        label='愛好',
        widget=forms.CheckboxInput, #做單選功能的復(多)選框形式
        
    )
(3)date型別
bday = forms.CharField(
        label='生日',
        widget=forms.TextInput(attrs={'type': 'date'}),
    )
(4)單選或者多選框使用資料庫中的資料

方式1

    
  models中的模型類
  class Publish(models.Model):

    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name
  
  form類中的欄位寫法
  # 生成選擇框,并使用資料庫中的資料
    publish = forms.ModelChoiceField(
        queryset=models.Publish.objects.all(),

    )
  會自動生成單選下拉框,并且option標簽value屬性對應的值,是queryset引數對應的queryset集合里面的models模型類物件的id值,option標簽的文本內容是每個models模型類物件

方式2

    publish = forms.ChoiceField(
        choices=models.Publish.objects.all().values_list('id','name')  #queryset[(1,xasdf),]
    )
6、Form所有內置欄位
看示例
Field
    required=True,               是否允許為空
    widget=None,                 HTML插件
    label=None,                  用于生成Label標簽或顯示內容
    initial=None,                初始值
    help_text='',                幫助資訊(在標簽旁邊顯示)
    # {{ form.欄位名稱.help_text }}
    error_messages=None,         錯誤資訊 {'required': '不能為空', 'invalid': '格式錯誤'}
    validators=[],               自定義驗證規則
    
    disabled=False,              是否可以編輯
    
 
 
CharField(Field)
    max_length=None,             最大長度
    min_length=None,             最小長度
    strip=True                   是否移除用戶輸入空白
 
IntegerField(Field)
    max_value=https://www.cnblogs.com/wylshkjj/p/None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             總長度
    decimal_places=None,         小數位長度
 
BaseTemporalField(Field)
    input_formats=None          時間格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            時間間隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正則運算式
    max_length=None,            最大長度
    min_length=None,            最小長度
    error_message=None,         忽略,錯誤資訊使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允許空檔案
 
ImageField(FileField)      
    ...
    注:需要PIL模塊,pip3 install Pillow
    以上兩個字典使用時,需要注意兩點:
        - form表單中 enctype="multipart/form-data"
        - view函式中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                選項,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默認select插件
    label=None,                Label內容
    initial=None,              初始值
    help_text='',              幫助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查詢資料庫中的資料
    empty_label="---------",   # 默認空顯示內容
    to_field_name=None,        # HTML中value的值對應的欄位
    limit_choices_to=None      # ModelForm中對queryset二次篩選
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   對選中的值進行一次轉換
    empty_value=https://www.cnblogs.com/wylshkjj/p/''            空值的默認值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   對選中的每一個值進行一次轉換
    empty_value=https://www.cnblogs.com/wylshkjj/p/''            空值的默認值
 
ComboField(Field)
    fields=()                  使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象類,子類中可以實作聚合多個字典去匹配一個值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式串列:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式串列:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     檔案選項,目錄下檔案顯示在頁面中
    path,                      檔案夾路徑
    match=None,                正則匹配
    recursive=False,           遞回下面的檔案夾
    allow_files=True,          允許檔案
    allow_folders=False,       允許檔案夾
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          決議ipv4地址,如果是::ffff:192.0.2.1時候,可決議為192.0.2.1, PS:protocol必須為both才能啟用
 
SlugField(CharField)           數字,字母,下劃線,減號(連字符)
    ...
 
UUIDField(CharField)           uuid型別
復制代碼
7、資料校驗功能
格式不對的錯誤資訊定制
'invalid': '郵箱格式不對',
(1)簡單校驗示例

form類

class DataForm(forms.Form):
    # 不能為空,長度不能超過10位,不能短于4位
    username = forms.CharField(
        label='用戶名',
        max_length=10,
        min_length=4,
        error_messages={
            'required':'不能為空',
            'max_length':'太長啦,受不了',
            'min_length':'太短啦,不舒服',

        }
    )

    # 可以為空
    phone = forms.CharField(
        label='手機號',
        required=False,
    )
    # 要滿足郵箱格式,不能為空
    email = forms.EmailField(  #input,type='email'和input,type='text'是一樣的,都是普通文本輸入框

        label='郵箱',
        error_messages={
            'invalid': '郵箱格式不對',
        }
    )
    # 提交的資料不能超出選項
    sex = forms.ChoiceField(
        label='性別',
        choices=((1,'女'),(2,'男')),
        widget=forms.RadioSelect,
    )

    #form只校驗類中寫的屬性對應的那些資料
    # {'xx':'oo'}
    # csrfmiddlewaretoken = forms.CharField()

view.py

def data(request):

    if request.method == 'GET':
        form = DataForm()
        return render(request,'data.html',{'form': form})

    else:
        form = DataForm(data=https://www.cnblogs.com/wylshkjj/p/request.POST)
        if form.is_valid():
            # 校驗成功之后的資料form.cleaned_data,里面不包含沒有校驗的資料,也就是不會校驗form類中沒有指定的屬性對應的資料
            print(form.cleaned_data)
            # {'username': 'chao', 'phone': '', 'email': '[email protected]', 'sex': '1'}
            # todo 保存資料
            return HttpResponse('ok')
        else:
            #print(form.errors)
            return render(request,'data.html',{'form': form})

Data.html檔案

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

<form action="/data/" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
        {{ form.username }}
        {{ form.username.errors.0 }}
    </div>
    <div>
        <label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
        {{ form.phone }}
        {{ form.phone.errors.0 }}
    </div>
    <div>
        <label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
        {{ form.email }}
        {{ form.email.errors.0 }}
    </div>
    <div>
        <label for="{{ form.sex.id_for_label }}">{{ form.sex.label }}</label>
        {{ form.sex }}
        {{ form.sex.errors.0 }}
    </div>

    <input type="submit">

</form>


</body>

</html>
(2)RegexValidator驗證器

示例代碼

from django.core.validators import RegexValidator

class DataForm(forms.Form):
    # 不能為空,長度不能超過10位,不能短于4位
    username = forms.CharField(
				....
        # RegexValidator('正則','不符合規則時的錯誤資訊')
        validators=[RegexValidator(r'^a', '用戶名必須以a開頭'),RegexValidator(r'b$', '用戶名必須以b結尾')],
(3)校驗函式

示例

import re
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
# ValidationError是django提供的一個資料檢驗失敗的一個錯誤型別

# 先定義一個函式,函式中我們可以做資料的校驗,如果資料校驗失敗,我們可以raise ValidationError('錯誤資訊')
def mobile_match(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手機號格式有誤!')

使用

phone = forms.CharField(
        label='手機號',
        # required=False,
        validators=[mobile_match, ],
    )
        
(4)區域鉤子

代碼示例

class DataForm(forms.Form):
    # 不能為空,長度不能超過10位,不能短于4位
    username = forms.CharField(
        label='用戶名',
        max_length=10,
        min_length=4,
    )

    # 可以為空
    phone = forms.CharField(
        validators=[mobile_match, ],
    )
		...

    # 區域鉤子,能夠對username這個欄位的資料進行一個更高級的校驗
    # 語法 clean_欄位名稱
    def clean_username(self):
        uname = self.cleaned_data.get('username')
        if '666' in uname:
            raise ValidationError('不能光喊6,沒有用!')

        # 如果校驗通過,需要return這個值
        return uname
    
    def clean_phone(self):
        pass
(5)全域鉤子

示例代碼

  class DataForm(forms.Form):
  
  	# 密碼
    password = forms.CharField()
    # 確認密碼
    confirm_password = forms.CharField()
    
  # 多個欄位進行比較時,一般都是用全域鉤子
    def clean(self):
        p1 = self.cleaned_data.get('password')
        p2 = self.cleaned_data.get('confirm_password')
        if p1 != p2:
            # 直接raise錯誤,這個錯誤資訊保存到了全域錯誤資訊里面
            # 也就是self.errors里面
            # raise ValidationError('兩次密碼不一致,你恐怕是個sz吧')
            self.add_error('confirm_password', '抱歉,兩次密碼不一致,請核對!')
						self.add_error('password', '抱歉,兩次密碼不一致,請核對!')

        # 如果校驗通過,必須return self.cleaned_data
        return self.cleaned_data

原始碼部分

    def _clean_fields(self):
        for name, field in self.fields.items():
            # print(name, field)
            # name屬性名稱,field是CharField()類的實體化物件
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = https://www.cnblogs.com/wylshkjj/p/self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    # 將CharField中的max_length,required..
                    value = field.clean(value)
                # 將屬性對應的CharField里面的簡單校驗規則進行了校驗
                self.cleaned_data[name] = value
                #self.cleaned_data['username'] = 'abc'
                if hasattr(self, 'clean_%s' % name): #clean_username
                    value = https://www.cnblogs.com/wylshkjj/p/getattr(self,'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)

簡單總結

首先對所有的我們自己定義的類中的屬性進行了回圈,self.fields能夠獲取到所有的屬性
回圈程序中先對屬性,比如username = form.Charfield(max_length=12),里面的引數校驗規則進行了校驗,這個校驗完成之后,給self.clean_data這個字典進行了賦值self.clean_data['username'] = 'asdf'
然后對該欄位的區域鉤子進行了校驗,在區域鉤子中我們可以拿到self.clean_data['username'] 這個資料進行處理,別忘了區域鉤子校驗成功之后,要return 這個欄位的值,然后才進入下一次回圈,處理下一個屬性

當上面的所有回圈執行完之后,才執行我們的全域夠子,全域鉤子中校驗成功之后別忘了return self.clean_data

靜態檔案配置和templates模板配置

可以在應用檔案夾下創建一個static名稱的檔案夾,存放自己應用的靜態檔案
可以在應用檔案夾下創建一個templates名稱的檔案夾,存放自己應用的html檔案

django尋找html檔案靜態檔案的查找順序是
先找總配置中的templates或者statics檔案夾,如果找不到對應檔案,就去每個應用下的static或者templates去找對應檔案,順序是按照settings.py組態檔中的INDSTALL_APP中注冊app的順序進行查找,找到就回傳,不在繼續往下找了,所以要注意,最好在static或者templates下面創建一個以應用名為名稱的檔案夾,將檔案放到這里面,以防檔案名沖突導致的問題,

十二、ModelForm驗證

modelform的使用
(1)自定義modelform的類
class BookModelForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = '__all__'
        labels = {
            'title': '書籍名稱',
            'pub': '出版社',
            'price': '價格',
            'pub_date': '出版日期',
            'authors': '作者',
        }
        error_messages = {
            'title': {'required': '不能為空', 'max_length': '不能太長,受不了'},
            'price': {'required': '不能為空', },
        }

    # 想在自己定義的forms類在初始化的時候統一加一些樣式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name, field in self.fields.items():
            # print(name, field)
            field.widget.attrs.update({'class': 'form-control'})

(2)在視圖中使用modelform
class Edit(View):

    def get(self, request, pk):
        old_book_obj = models.Book.objects.get(pk=pk)
        # 如果不傳instance=old_book_obj, 那么就是一個添加頁面效果,也就是生成標簽沒有默認值
        # form = BookModelForm()
        form = BookModelForm(instance=old_book_obj)

        return render(request, 'edit.html',{'form': form,})

    def post(self,request,pk):
        old_book_obj = models.Book.objects.filter(pk=pk).first()
        # 如果沒有傳instance=old_book_obj,那么form.save()做的是添加記錄的動作,
        # 如果傳了instance=old_book_obj,那么form.save()就是一個修改動作
        form = BookModelForm(data=https://www.cnblogs.com/wylshkjj/p/request.POST, instance=old_book_obj) #
        if form.is_valid():
            # authors_objs = form.cleaned_data.pop('authors')
            #
            # book_obj = models.Book.objects.create(
            #     **form.cleaned_data
            # )
            # book_obj.authors.add(*authors_objs)
            form.save()
            return redirect('book_list')
        else:
            return render(request, 'edit.html',{'form': form,})

(3)html中使用modelform
<form action="" method="post" novalidate>

                {% for field in form %}
                <div class="form-group {% if field.errors.0 %}has-error{% endif %}">
                    <label class="control-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
                    {{ field }}
                    <span class="text-danger">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}


                <button class="btn btn-success pull-right">提交</button>
            </form>

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/85017.html

標籤:Python

上一篇:fnmatch用法

下一篇:盤點GitHub上優質python開源專案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more