Django
一、Django介紹
1.1 簡介
Django是python語言中的一個web框架,Python語言中主流的web框架有Django、Tornado、Flask 等多種。Django相較與其它WEB框架,其優勢為:大而全,框架本身整合了ORM、模型繫結、模板引擎、快取、Session等功能,是一個全能型框架,擁有自己的Admin資料管理後臺,第三方工具齊全,效能折中。缺點:功能太多,資料分表複雜,高效能擴充套件複雜。
1.2 安裝
-
pip安裝:在cmd命令視窗中,輸入
pip install django
-
下載安裝包安裝
1.3 文件
官方文件的連結在:https://docs.djangoproject.com ,
點選頁面右下角的 language 按鈕,可以選擇 zh-hans ,顯示中文,只有部分頁面有中文翻譯
點選頁面右下角的Documentation version,可以選擇版本
二、Django相關知識學習
2.1 相關術語和規範
-
B/S和C/S
Django是用於開發B/S架構的軟體的,軟體主要分為B/S架構和C/S架構:
-
B/S:全稱Browser/Server(瀏覽器/伺服器)
-
C/S:全稱Client/Server(客戶端/伺服器)
-
-
MVC
MVC全名是Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。
-
M: 管理應用程式的狀態(通常儲存到資料庫中),並約束改變狀態的行為(或者叫做“業務規則”)。
-
V: 負責把資料格式化後呈現給使用者。
-
C: 接受外部使用者的操作,根據操作訪問模型獲取資料,並呼叫“檢視”顯示這些資料。控制器是將“模型”和“檢視”隔離,併成為二者之間的聯絡紐帶。
-
-
MTV
Django也是一個MVC框架。但是在Django中,控制器接受使用者輸入的部分由框架自行處理,所以 Django 裡更關注的是模型(Model)、模板(Template)和檢視(Views),稱為 MTV模式:
-
M: 代表模型(Model),即資料存取層。 該層處理與資料相關的所有事務: 如何存取、如何驗證有效性、包含哪些行為以及資料之間的關係等。
-
T: 代表模板(Template),即表現層。 該層處理與表現相關的決定: 如何在頁面或其他型別文件中進行顯示。
-
V: 代表檢視(View),即業務邏輯層。 該層包含存取模型及調取恰當模板的相關邏輯。 你可以把它看作模型與模板之間的橋樑。
-
-
ORM
ORM 就是透過例項物件的語法,完成關係型資料庫的操作的技術,是"物件-關係對映"(Object/Relational Mapping) 的縮寫。
-
ORM把資料庫對映成物件
資料庫的表(table) ---------> 類 (class)
記錄(record,行資料) ---------> 物件(object)
欄位(field) ---------> 物件的屬性(attitude)
-
舉例
-
sql語句
SELECT id, first_name, last_name, phone, birth_date, sex
FROM persons
WHERE id = 10 -
程式直接執行sql,運算元據庫的寫法,如下:
res = db.execSql(sql);
name = res[0]["FIRST_NAME"]; -
改成ORM寫法如下
p = Person.get(10);
name = p.first_name;
-
一比較就可以發現,ORM使用物件,封裝了資料庫操作,因此可以不碰sql語言,開發者只使用物件導向程式設計,與資料物件互動,不用關係底層資料庫。
- ORM的優點
- 資料模型定義在一個地方便於維護,也利於重用程式碼
- ORM有現成的工具,很多功能都可以自動完成
- 它迫使你使用mvc架構
- 基於ORM的業務程式碼比較簡單,程式碼量少,語義性好
- ORM的缺點
- ORM庫不是輕量級工具,需要花很多精力學習和設定
- 對應複雜的查詢,ORM要麼無法表達,要麼效能不如原生的sql
- ORM抽象掉了資料庫層,開發者無法瞭解底層的資料庫操作,也無法定製一些特定的sql
-
三、Django專案構建
3.1 cmd命令列構建專案
-
在cmd視窗中,切換到指定的專案資料夾,執行:
django-admin startproject mysite
其中:mysite是專案名
這時會在指定的專案資料夾中,生成一個mysite資料夾,目錄結構如下:
mysite/ manage.py mysite/ __init__.py settings.py urls.py wsgi.py
這些目錄和檔案的說明如下:
- 最外層的mysite/ 根目錄是專案的名稱
- manage.py 是管理 Django 專案的命令列工具,啟動和結束等
- 裡面一層的 mysite/ 目錄包含你的專案主應用,它是一個 Python 包
- mysite/_init_.py:一個空檔案,告訴 Python 這個目錄是一個 Python 包
- mysite/settings.py:專案的配置檔案,有關於資料庫、編碼、時區等
- mysite/urls.py:專案的 url路由配置,即url路由與其函式的對應配置
- mysite/wsgi.py:用於你的專案的與WSGI相容的Web伺服器入口,用於專案部署
- 最外層的mysite/ 根目錄是專案的名稱
-
cmd 視窗中,進入最外層的mysite
-
在這個目錄下,我們可以輸入如下命令,建立一個 新的子應用
python manage.py startapp myapp01
-
在該cmd目錄下執行如下命令,可以啟動專案
python manage.py runserver
該命令後續可以增加引數,如:
python manage.py runserver 8081 # 指定埠
python manage.py runserver 127.0.0.1:8082 # 指定host和埠cmd視窗中會出現如下資訊:
F:\django_study\first_pro>python manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. July 03, 2019 - 16:11:57 Django version 2.2.1, using settings 'first_pro.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. [03/Jul/2019 16:13:06] "GET / HTTP/1.1" 200 16348 [03/Jul/2019 16:13:06] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423 [03/Jul/2019 16:13:06] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 85876 [03/Jul/2019 16:13:06] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 85692 [03/Jul/2019 16:13:06] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 86184 Not Found: /favicon.ico [03/Jul/2019 16:13:06] "GET /favicon.ico HTTP/1.1" 404 1975
-
在瀏覽器中輸入,啟動服務時的ip:埠對網站訪問
3.2 第一個Django請求
- 請求流程
-
urls.py配置
from first_app import views urlpatterns = [ path('admin/', admin.site.urls), path('test/',views.first_test), ]
-
view.py配置
from django.shortcuts import render,HttpResponse # Create your views here. def first_test(request): print('第一個Django專案views') return HttpResponse('Django專案第一次請求成功')
-
啟動服務
python manage.py runserver
-
瀏覽器傳送請求
127.0.0.1:8000/test
-
其中在views.py檔案中,每一個請求方法需要有一個request引數,透過該引數可以獲取請求相應資訊。可以使用dir(request)檢視詳細。
['COOKIES', 'FILES', 'GET', 'META', 'POST', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_current_scheme_host', '_encoding', '_get_full_path', '_get_post', '_get_raw_host', '_get_scheme', '_initialize_handlers', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_read_started', '_set_content_type_params', '_set_post', '_stream', '_upload_handlers', 'accepted_types', 'accepts', 'body', 'build_absolute_uri', 'close', 'content_params', 'content_type', 'csrf_processing_done', 'encoding', 'environ', 'get_full_path', 'get_full_path_info', 'get_host', 'get_port', 'get_raw_uri', 'get_signed_cookie', 'headers', 'is_ajax', 'is_secure', 'method', 'parse_file_upload', 'path', 'path_info', 'read', 'readline', 'readlines', 'resolver_match', 'scheme', 'session', 'upload_handlers', 'user']
3.3 開發第一個登入表單
-
urls.py配置
path('login_form/',views.login_form),
-
views.py新增業務方法
def login_form(request): html = ''' <html> <body> <form method="post"> 使用者名稱:<input name = "username" type="text"></input></br> 密碼:<input name = "password" type = "password"></input></br> <input type="submit" value="登入"></input> </form> </body> </html> ''' return HttpResponse(html)
-
瀏覽器傳送請求
四、Django配置
4.1 整體介紹
django專案建立後,在主應用中,會有一個settings.py檔案,這個就是該專案的配置檔案
- settings檔案包含Django安裝的所有配置
- settings檔案是一個包含模組級變數的python模組,所以該模組本身必須符合python規則,並且可以使用python的語法
- settings中的所有配置項的key必須全部大寫
- settings中每一個配置項都有預設值,預設配置內容在django/conf/global_settings.py中可以檢視到,專案中不需要匯入該模組,django框架會自動獲取
- settings中可以新增自定義的配置項
4.2 settings檔案
-
manage.py 啟動
預設在manage.py 中配置
if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstdjango.settings")
-
手動指定配置檔案位置
cmd命令啟動如下
python manage.py runserver 0.0.0.0:8000 --settings=firstdjango.settings
-
服務部署啟動
在wsgi.py配置
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "firstdjango.settings")
4.3 常用配置項
import os
"""
當前檔案所在資料夾的上一級目錄的絕對路徑
切記2個 os.path.dirname
"""
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
"""
用於加密session,一個隨機的字串
這樣生成:
from django.core.management import utils
utils.get_random_secret_key()
"""
SECRET_KEY = '=*f&bx760nyar7@8lb8!w$9h(3ea6p3apl$iua!td1q%-u5r4='
# 除錯模式,可以看到錯誤的所有相信資訊,部署時一定要修改為False
DEBUG = True
"""
允許訪問的域名設定
開發環境不用理會
執行環境,配置 DEBUG = False後,
如果允許所有域名訪問,則設定 ALLOW_HOSTS = ['*']
如果指定某些域名可以訪問,則設定 ALLOW_HOSTS = ['*.baidu.com']
"""
ALLOWED_HOSTS = []
"""
應用的配置,
如:'polls.apps.PollsConfig'
如果沒有 PollsConfig ,那麼可以配置為 'polls'
"""
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles', # 只有 DEBUG = Ture 才有效
'polls' # 子應用必須配置,否則不起作用
]
"""
中間層配置
自己編寫的 中間層 需要配置在最後
譬如:
mymidlle.md.TestMiddleware
"""
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',
]
# 配置基礎的urls
ROOT_URLCONF = 'firstdjangopy.urls'
# 配置模板
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 伺服器部署的WSGI配置
WSGI_APPLICATION = 'firstdjango.wsgi.application'
"""
資料庫配置
mysql在python3的使用,需要在 __init__.py 中加入以下程式碼:
import pymysql
pymysql.install_as_MySQLdb()
"""
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_test1',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
"""
使用者密碼驗證
"""
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# 語言選擇 , zh-Hans 為中文
LANGUAGE_CODE = 'en-us'
# 時區 Asia/Shanghai 是中國時區
TIME_ZONE = 'UTC'
# 國際化
USE_I18N = True
# 本地化
USE_L10N = True
# 使用時區,配套TIME_ZONE使用,必須設定為 False
USE_TZ = False
"""
靜態檔案的路徑,預設是 static
如果在各自專案的static目錄以外,還有目錄存放靜態檔案,需要新增如下屬性:
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "common_static1"),
'/var/www/static/',
)
"""
STATIC_URL = '/static/'
4.4 程式中獲取settings中的配置項
如果在專案程式碼中需要獲取settings中的配置項,這樣獲取:
# 切記不要匯入具體的settings模組的路徑,會形成高耦合
# 這樣的方式是不可取的:from mysite import settings
from django.conf import settings
d = settings.DEBUG
五、URL排程器
5.1 工作原理
django透過urlconf來對映檢視函式,只區分路徑,不區分http方法
- Django確定要使用的根URLconf模組,一般是在settings中的ROOT_URLCONF設定的值,但是如果傳入 HttpRequest 物件具有一個urlconf 屬性(由中介軟體設定),則其值將用於代替 ROOT_URLCONF設定。
- Django載入該URLconf模組並查詢變數 urlpatterns,它是一個列表django.urls.path() 和 / 或django.urls.re_path()例項。
- Django按順序遍歷每個URL模式,並停在與請求的URL匹配的第一個URL模式,需要特別注意編寫的順序
- 一旦某個URL模式匹配,Django就會匯入並呼叫給定的檢視,該檢視是一個簡單的Python函式(或基於類的檢視方法)。該檢視透過以下引數傳遞:
- 一個HttpRequest例項。
- 如果匹配的URL模式沒有返回任何命名組,則來自正規表示式的匹配作為位置引數提供。
- 關鍵字引數由路徑表示式匹配的任何命名部分組成,並由可選的kwargs引數傳給 django.urls.path()或django.urls.re_path()。
- 如果沒有URL模式匹配,或者在此過程中的任何點發生異常,Django將呼叫適當的錯誤處理檢視
5.2 簡單示例
-
在子應用下建立urls.py內容如下
urlpatterns = [ ]
-
在專案路由檔案中新增子應用urls.py配置
from django.contrib import admin from django.urls import path from djangoStudy.first_project.book import views from django.urls.conf import include # 可以引入其他應用配置項 urlpatterns = [ path('admin/', admin.site.urls), path('book/',views.book_detail_query_string), path('book/<int:book_id>',views.book_detail_path) ]
-
配置子應用urls.py
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), path('articles/<str:string>/', views.str_archive), path('articles/<path:path>/', views.path_archive), ]
筆記:
-
從URL中捕獲值,請使用尖括號
-
捕獲的值可以選擇包含轉換器型別。例如,用於 <int:name>捕獲,前面的int指整數引數,name是引數的名稱
-
沒有必要新增一個前導斜槓,因為每個URL都有,例如,使用articles而不是/articles。
-
示例請求說明:
- /articles/2005/03/ 匹配列表中的第三個條目。Django會呼叫這個函式,views.month_archive(request, year=2005, month=3)
- /articles/2003/ 會匹配列表中的第一個模式,而不是第二個模式,因為模式是按順序測試的,而第一個模式是第一個要傳遞的測試。看看利用匹配順序插入像這樣的特殊情況。在這裡,Django會呼叫這個函式 views.special_case_2003(request)
- /articles/2003 不匹配任何這些模式,因為每種模式都要求URL以斜線結尾,不過在瀏覽器訪問時,會自動新增 / 。
- **/articles/2003/03/building-a-django-site/ **將匹配最終模式。Django會呼叫這個函式 。views.article_detail(request, year=2003, month=3, slug="building-a-django-site")
-
5.3 路徑轉換器
- str:匹配任何非空字串,不包括路徑分隔符'/'。如果轉換器不包含在表示式中,這是預設值。
- int:匹配零或任何正整數。返回一個int。
- slug:匹配由ASCII字母或數字組成的字串,以及橫線和下劃線字元。例如, building-your-1st-django_site可以匹配,django_@site是不可以匹配的。
- uuid:匹配格式化的UUID。為防止多個URL對映到同一頁面,必須包含破折號,並且字母必須是小寫。例如,075194d3-6885-417e-a8a8-6c931e272f00。返回一個 UUID例項。
- path:匹配任何非空字串,包括路徑分隔符 '/',可以匹配完整的URL路徑,而不僅僅是URL路徑的一部分str,使用時要謹慎,因為可能造成後續的所有url匹配都失效。
path('articles/<uuid:uuid>/',views.article_uuid),
#獲取uuid
import uuid
print(uuid.uuid1())
5.4 自定義路徑轉換器
轉換器是一個包含以下內容的類:
- 一個regex類屬性,作為一個re匹配字串。
- to_python(self, value)方法,它處理匹配的字串轉換成要傳遞到檢視函式的型別。
- to_url(self, value)方法,用於處理將Python型別轉換為URL中使用的字串。
定義方法如下:
-
新建一個converters.py檔案,在檔案中定義一個FourDigitYearConverter類:
class FourDigitYearConverter(object): regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value
-
使用
register_converter()
方法在URLconf中註冊自定義轉換器類:from django.urls import register_converter, path from . import converters, views register_converter(converters.FourDigitYearConverter, 'yyyy') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<yyyy:year>/', views.year_archive) ]
5.5 使用正規表示式
使用正規表示式匹配路徑,請使用 re_path()而不是path()
在Python正規表示式中,命名正規表示式組的語法是(?P<name>pattern),其中name是組的名稱,並且 pattern是一些要匹配的模式
示例:
from django.urls import path, re_path
from . import views
# url() 是 re_path 的別名,不推薦使用
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
]
注意事項:
- 匹配的URL會受到一些限制。例如,年份10000將不再匹配,因為年份整數限制為四位數字
- 無論正規表示式匹配什麼型別,每個捕獲的引數都以字串的形式傳送到檢視
- 除了命名的組語法,例如(?P<year>[0-9]{4}),也可以使用較短的未命名組,例如([0-9]{4}),但是不建議這樣使用,會引起未知的匹配
巢狀引數:
from django.urls import re_path
urlpatterns = [
# 不推薦, 匹配 blog/page-3/
re_path(r'^blog/(page-(\d+)/)?$', blog_articles),
# 推薦 ,匹配:comments/page-2/ 路徑到 comments(request, page_numer)
re_path(r'^comments/(?:page-(?P<page_number>\d+)/)?$', comments),
]
5.6 使用預設值
URLConf中
from django.urls import path
from . import views
urlpatterns = [
# http://127.0.0.1:8000/polls/blog/ 等同於 http://127.0.0.1:8000/polls/blog/1/
path('blog/', views.page),
# http://127.0.0.1:8000/polls/blog/1/
# http://127.0.0.1:8000/polls/blog/10/
# http://127.0.0.1:8000/polls/blog/99/
path('blog/<int:num>/', views.page),
]
views中:
def page(request, num=1):
# 編寫對應的業務邏輯
5.7 錯誤處理
- handler400- 狀態碼400
- handler403- 狀態碼403
- handler404- 狀態碼404
- handler500- 狀態碼500
-
在settings中修改配置
DEBUG = False ALLOWED_HOSTS = ['*', ]
-
在主應用的urls中配置
# polls是子應用 handler404 = "polls.views.page_not_found"
-
在polls應用的views中新增函式page_not_found:
def page_not_found(request, exception): return HttpResponse('自定義的404錯誤頁面')
-
瀏覽器測試訪問,找不到匹配的路由
5.8 引用其他URL排程器
-
多個patterns
from django.urls import include, path extra_patterns = [ path('reports/', credit_views.report), path('reports/<int:id>/', credit_views.report), path('charge/', credit_views.charge), ] urlpatterns = [ path('', main_views.homepage), path('help/', include('apps.help.urls')), path('credit/', include(extra_patterns)), ]
-
使用include消除重複字首
from django.urls import path from . import views urlpatterns = [ path('<page_slug>-<page_id>/history/', views.history), path('<page_slug>-<page_id>/edit/', views.edit), path('<page_slug>-<page_id>/discuss/', views.discuss), path('<page_slug>-<page_id>/permissions/', views.permissions), ] # 修改為: from django.urls import include, path from . import views urlpatterns = [ path('<page_slug>-<page_id>/', include([ path('history/', views.history), path('edit/', views.edit), path('discuss/', views.discuss), path('permissions/', views.permissions), ])), ]
-
傳遞捕獲的引數
在urls中配置
urlpatterns = [ path('admin/', admin.site.urls), # 這裡捕獲username引數,型別為字串 path('<username>/polls/', include('polls.urls')) ]
對應的polls應用下的urls中配置:
urlpatterns = [ path('arg_test/', views.arg_test), ]
對應的polls應用下的views中編寫函式
def arg_test(request, username): # 編寫對應的業務邏輯 return HttpResponse(f'username {username}')
5.9 額外的引數
from django.urls import path
from . import views
urlpatterns = [
# 會傳遞給 views.year_archive(request, year=2005, foo='bar')
path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
]
5.10 URL反向解析
url排程器除了從使用者發起請求,到匹配對應的view,還能在python程式中呼叫進行匹配,透過 path或re_path 中 的name屬性進行解析
- 在模板中,使用url模板標籤
- 在Python程式碼中(主要是views),使用 reverse() 函式
- 在模型例項中,使用 get_absolute_url() 方法
示例:
uls中配置:
from django.urls import path
from . import views
urlpatterns = [
#...
path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
#...
]
-
在模板中測試
-
views.py跳轉頁面
def do_html(request): return render(request,'redirect_test.html') def year_archive(request,year): return HttpResponse(f'重定向成功{year}')
-
模板中程式碼
# 模板中: <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
-
-
python程式碼
from django.urls import reverse from django.http import HttpResponseRedirect def redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
-
在模型中:
""" 在模型中實現方法: def get_absolute_url(self): from django.urls import reverse return reverse('news-year-archive', args=[str(self.id)]) 然後在 模板 中如下使用: """ <a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
5.11 名稱空間
主要用於配合第 10 點 url反向解析 使用,多個不同的urls檔案中可能配置同名的 name,那麼為了進行區分,給不同的urls進行不同的命名,切記同一個專案下名稱空間不能重複!
透過在 url排程器的模組中,定義 app_name = 'polls' 來命名
from django.urls import path
from . import views
# 定義,一般名稱空間和子應用名相同,便於記憶
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
...
]
# 呼叫,一旦有了名稱空間,呼叫時就必須使用 polls: 字首
reverse('polls:index', current_app=self.request.resolver_match.namespace)
名稱空間可以進行巢狀:
# 在 urls 中配置如下:
from django.urls import path
from . import views
# 定義名稱空間,一般名稱空間名和子應用名相同,便於記憶
app_name = 'polls'
extra_patterns = (
[
path('app_name/', views.app_name, name='app_name'),
],
# 此處就是巢狀的名稱空間
'extra'
)
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('extra/', include(extra_patterns)),
...
]
# 在模板中使用:
<a href="{% url 'polls:extra:app_name' %}">點選連結</a>
六、Django模型
模型,就是python中的類對應資料庫中的表
ORM就是透過例項物件的語法,完成關係型資料庫的操作技術,是物件-關係對映
的縮寫
6.1 簡單示例
模型:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField()
對應mysql資料庫中的表
CREATE TABLE `polls_question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`question_text` varchar(200) NOT NULL,
`pub_date` datetime(6) NOT NULL,
PRIMARY KEY (`id`)
)
筆記:
- 模型類必須繼承models.Model
- 每個屬性對應資料庫中的一個欄位
- 表名自動使用 mysite_類名 的小寫(如:polls_question),可以覆蓋
- 如果模型類中沒有指定 primary_key ,那麼會自動建立一個 id 欄位,自增,主鍵
6.2 應用模型
當編寫了模型之後,需要將模型應用到資料庫中:
-
建立專案 model_study 及 子應用 model_app
#建立專案 django-admin startproject model_study #進入專案目錄建立子應用 python manage.py startapp model_app
-
配置應用
-
將模型對應的應用程式新增到專案的settings中:
INSTALLED_APPS = [ 'model_app' ]
-
在settings中配置正確的資料庫連線:
# sqlite3 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # mysql DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'model_study', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', } }
-
安裝對應資料庫的驅動
ps:
如果是mysql,Django2.2 請安裝 mysqlclient 庫
如果是sqlite3,是不需要額外安裝,python自帶驅動庫
注: 需要在mysql資料庫中建立資料庫 model_study
-
-
預備遷移
在專案根目錄的cmd中執行
python manage.py makemigrations model_app
注:
model_app是子應用的名稱,如果不指定,那麼就會對所有
INSTALLED_APPS
中的應用都進行預備遷移 指定該命令後,對應的子應用下的migrations 中會生成一個對應的遷移檔案
-
正式遷移
在根目錄cmd中執行:
python manage.py migrate
ps: 沒有新增子應用名,那麼就會把django專案中所有的應用都遷移到資料庫中
-
模型修改後重新應用
不管是新增模型,還是修改已有模型後,只需要重複執行第3步和第四步,即可自動實現資料庫中的表結構,表關係等資訊的修改
6.3 逆向從資料庫表生成模型類
-
settings中設定好 DATABASES 配置
-
在對一個資料庫中建立好表、約束和表關係等
-
在根目錄的cmd中執行:
python manage.py inspectdb > model_app/models.py
ps: model_app是子應用名
-
第三步執行後會在models中生成對應的模型類
譬如:
class DjangoSession(models.Model): session_key = models.CharField(primary_key=True, max_length=40) session_data = models.TextField() expire_date = models.DateTimeField() class Meta: managed = False # 這個屬性是通知django,不需要進行從模型到資料庫的遷移管理 db_table = 'django_session' # 對應的資料庫中的表名
6.4 欄位Field
模型類的屬性對應資料庫中表的欄位,都是對應的Field類的例項
6.4.1 欄位命名限制
-
字母,數字,下劃線,首字母不能是數字
-
欄位名稱不能是Python保留字
-
由於Django查詢查詢語法的工作方式,欄位名稱不能在一行中包含多個下劃線,譬如“abc__123”就是不允許的,一個下劃線是可以的,如:'first_name'
6.4.2 AutoField、ID、PRIMARY_KEY
預設會自動建立一個自增,主鍵的id列
如果指定了 primary_key 為其它列,那麼不會自動建立id列
可以在模型中指定:
id = models.AutoField(primary_key=True)
6.4.3 常見Field欄位
所有的Field型別,見 https://docs.djangoproject.com/en/2.2/ref/models/fields/#model-field-types
常用Field
- AutoField
- BooleanField
- CharField
- DateField
- DateTimeField
- FloatField
- SmallIntegerField
- IntegerField
- TextField
示例:UUIDField這樣使用:
import uuid
from django.db import models
class MyUUIDModel(models.Model):
# uuid.uuid4 千萬別寫成 uuid.uuid4() ,不要寫 ()
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
6.4.4 Field常見引數
- max_length:欄位最大長度,用於字串等,字串型別CharField必須設定該值
- null:如果True,Django將在資料庫中儲存NULL空值。預設是False
- blank:如果True,該欄位被允許為空白("")。預設是False。請注意,這不同於null。null純粹是與資料庫相關的,而blank與驗證相關。如果一個欄位有blank=True,表單驗證將允許輸入一個空值。如果一個欄位有blank=False,該欄位將是必需的。
- choices:示例:YEAR_IN_SCHOOL_CHOICES = (('FR', 'Freshman'),('SO', 'Sophomore'),('JR', 'Junior'),('SR', 'Senior'),('GR', 'Graduate')) ,中文示例:SEX_CHOICES=((1, '男'),(2, '女')),元組中的第一個元素是將儲存在資料庫中的值,第二個元素是將在頁面中顯示的值,最常見用於下拉選擇框select
- default:欄位的預設值
- help_text:用於顯示額外的“幫助”文字
- primary_key:如果True,這個欄位是模型的主鍵,預設是False
- unique:如果True,該欄位在整個表格中必須是唯一的
- verbose_name:詳細欄位名,不指定則是屬性名的小寫,並且用 空格 替換 '_'
6.4.5 模型之間的關係
-
主外關係中,關聯操作最常用的是: models.CASCADE 級聯刪除 和 models.SET_NULL 設定為null
-
一對多關係中,ForeignKey 寫在一對多關係中,多的那個模型中
6.4.5.1 一對多
使用django.db.models.ForeignKey,例如,如果一個Car模型有一個Manufacturer, 也就是說,一個 Manufacturer可以對應多個汽車,但Car只有一個汽車生產商Manufacturer,那麼使用以下定義:
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=20)
class Car(models.Model):
# 外來鍵名是 對應類名的小寫
# on_delete 是必須屬性
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
6.4.5.2 一對一
使用django.db.models.OneToOneField,例如:地址Place和餐館Restaurant是一對一的關係,而餐館Restaurant和服務員Waiter是一對多的關係
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
# BooleanField 在資料庫使用 tinyint 型別
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
6.4.5.3 多對多
-
自關聯
from django.db import models class Student(models.Model): name = models.CharField(max_length=20) friends = models.ManyToManyField("self")
筆記:
-
會生成一箇中間表,如下
CREATE TABLE `test_app_student_friends` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `from_student_id` INT ( 11 ) NOT NULL, `to_student_id` INT ( 11 ) NOT NULL, PRIMARY KEY ( `id` ), UNIQUE KEY `test_app_student_friends_from_student_id_to_stude_7ef9880e_uniq` ( `from_student_id`, `to_student_id` ), KEY `test_app_student_fri_to_student_id_154a4deb_fk_test_app_` ( `to_student_id` ), CONSTRAINT `test_app_student_fri_from_student_id_c400b5d4_fk_test_app_` FOREIGN KEY ( `from_student_id` ) REFERENCES `test_app_student` ( `id` ), CONSTRAINT `test_app_student_fri_to_student_id_154a4deb_fk_test_app_` FOREIGN KEY ( `to_student_id` ) REFERENCES `test_app_student` ( `id` ) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci
-
-
簡易多對多
class SchoolClass(models.Model): name = models.CharField(max_length=20) class Teacher(models.Model): name = models.CharField(max_length=10) school_class = models.ManyToManyField(SchoolClass)
筆記:
-
會自動生成一箇中間表,DDL語句如下
CREATE TABLE `test_app_teacher_school_class` ( `id` int(11) NOT NULL AUTO_INCREMENT, `teacher_id` int(11) NOT NULL, `schoolclass_id` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `test_app_teacher_school__teacher_id_schoolclass_i_f52c7361_uniq` (`teacher_id`,`schoolclass_id`), KEY `test_app_teacher_sch_schoolclass_id_7ac34d1e_fk_test_app_` (`schoolclass_id`), CONSTRAINT `test_app_teacher_sch_schoolclass_id_7ac34d1e_fk_test_app_` FOREIGN KEY (`schoolclass_id`) REFERENCES `test_app_schoolclass` (`id`), CONSTRAINT `test_app_teacher_sch_teacher_id_8c52afbd_fk_test_app_` FOREIGN KEY (`teacher_id`) REFERENCES `test_app_teacher` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
其中:
test_app_teacher_school_class是表名: test_app是應用名, teacher是第一個模型名,school_class是第二個模型名
-
-
自定義中間表
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', # 必須是 類名的字串 ,用 '' 包裹 through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) level = models.IntegerField(default=1)
筆記:
- 透過 through='Membership' 指定Membership作為中間表
- 透過 through_fields=('group', 'person') 指定中間模型的屬性
- 一般需要自定義中間表時,都是有額外的欄位,譬如 level = models.IntegerField(default=1)
6.5 方法
除了執行程式時,可以測試模型,還可以在根目錄的cmd執行:
python manage.py shell
開啟django指令碼控制檯,測試執行模型的方法,會比啟動專案更方便
模型物件中有一個objects屬性,該屬性是管理器Manager型別的物件,幾乎所有的方法都是透過該物件執行的,具體見下面的程式碼:
6.5.1 新增 save 或 create
#執行之前匯入模組
from moremore_app.models import * #其中moremore_app是子應用名
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
或
p1 = Person(first_name="Bruce", last_name="Springsteen")
p1.save()
#一對多關係新增 方式一
#先新增一方
models.Account.objects.create(user_name='lili',email='234453@qq.com',password='123',signature='li')
#再建立多方物件
article = models.Article(title='物件關係對映',content='物件關係對映內容',pub_date='2021-7-6')
#設定外來鍵的值
article.account_id=2 #其中account_id是資料庫的欄位 2是account表的主鍵值
#儲存物件
article.save()
#一對多關係新增多方 方式二
account = models.Account.objects.get(pk=3) #查詢account表主鍵為3的Account實體
models.Article.objects.create(title='暑假安排',content='好好學習',account=account,pub_date='2021-7-9')
#多對多關係新增
#先新增實體
models.Tag.objects.create(name='電影')
models.Tag.objects.create(name='科技')
models.Tag.objects.create(name='教育')
#再建立關聯
article.tags.set([1,2]) #執行後中間表中有資料
article.tags.set([3,])
#set是直接賦值 如果想在原有基礎上新增則使用add
article.tags.add(1,2,3)
6.6 查詢
- 大部分檢索是懶惰執行的,只在真實獲取值的時候,才會去連線資料庫獲取資料
- 查詢集透過過濾器進行查詢,允許鏈式呼叫
- pk是主鍵的別名(primary key),如果真實主鍵是id,那麼 pk 和 id 使用是一樣的效果
-
過濾器
# get 獲取一個物件 # 查詢主鍵等於 1 的 , 如果主鍵是ID,也可以使用 id=1 # 如果條件找不到對應的記錄,會丟擲 DoesNotExist 錯誤 # 如果條件找到多個記錄,會丟擲 MultipleObjectsReturned 錯誤 person = Person.objects.get(pk=1) # all 獲取所有物件 # 查詢所有,得到的QuerySets物件 all_persons = Person.objects.all() #過濾使用filter #查詢user_name='lili'的Account Account.objects.filter(user_name='lili')
-
欄位查詢
-
欄位檢索,是在欄位名後加 '__' 雙下劃線,再加關鍵字,類似 SQL 語句中的 where 後面的部分, 如: 欄位名__關鍵字
-
在查詢中指定的欄位必須是模型欄位的名稱,但有一個例外,如果是ForeignKey欄位,則是屬性名+ ‘_id’: Entry.objects.filter(blog_id=4) , 定義的 ForeignKey是 blog
-
完整的欄位檢索文件:
https://docs.djangoproject.com/en/2.2/ref/models/querysets/#field-lookups
常見的欄位檢索:
exact :判斷是否等於value,一般不使用,而直接使用 '='
contains:是否包含,大小寫敏感,如果需要不敏感的話,使用icontains
startswith:以value開頭,大小寫敏感
endwith:以value結尾,大小寫敏感
in:是否包含在範圍內
isnull:是否為null, 如:filter(name__isnull=Flase)
gt:大於,如:filter(sage__gt=30) , 年齡大於30
gte:大於等於
lt:小於
lte:小於等於
-
6.6.1 確定的搜尋 ,SQL: where id = 14
Blog.objects.get(id__exact=14)
# 等同於
Blog.objects.get(id=14)
# 不區分大小寫的確定搜尋,匹配 beatles blog 、Beatles blog等
Blog.objects.get(name__iexact="beatles blog")
# 包含,contains ,SQL:WHERE headline LIKE '%Lennon%'
Entry.objects.get(headline__contains='Lennon')
# 不區分大小寫的包含
Entry.objects.get(headline__icontains='Lennon')
# 以什麼開頭, SQL: WHERE headline LIKE 'Lennon%'
# 還有 不區分大小寫的 istartwith
Entry.objects.get(headline__startswith='Lennon')
# 同樣有 endswith ,SQL : WHERE headline LIKE '%Lennon'
# 還有 不區分大小寫的 iendswith
Entry.objects.get(headline__endswith='Lennon')
# in
Entry.objects.get(headline__in=['Lennon', 'Terry'])
#isnull
Account.objects.filter(signature__isnull=True)
6.6.2 gt gte
Account.objects.filter(id__gt=1)
Account.objects.filter(id__gte=1)
6.6.3 gt gte
Account.objects.filter(id__gt=1)
Account.objects.filter(id__gte=1)
6.6.4 日期時間的過濾
year/month/day/week_day/hour/minute/second:時間查詢,如: filter(pub_date__year=2015) 年份是2015的, filter(pub_date__day=15) 天數是15的
#時間可以直接使用gt gte lt lte
Account.objects.filter(register_date__gt='2021-7-1')
#__range查詢某個時間段
Account.objects.filter(register_date__range=('2021-7-1','2021-7-7'))
#查詢某年某月某日 __date
Account.objects.filter(register_date__date='2021-7-6')
<QuerySet [<Account: Account object (2)>, <Account: Account object (3)>]>
#查詢某年 __year
# exclude 例外
# 查詢 日期年份 不是2006的
persons = Person.objects.exclude(pub_date__year=2006)
# filter 獲取物件列表
# 查詢 日期年份 是 2006 的
persons = Person.objects.filter(pub_date__year=2006)
# filter 獲取物件列表,支援切片,但是不支援負數切片
# limit 5 :前5個
persons = Person.objects.filter(pub_date__year=2006)[:5]
# limit 5,5 : 第6個到10個
persons = Person.objects.filter(pub_date__year=2006)[5:10]
# Entry.objects.all()[-1] 不支援
# 返回前10個記錄的, 0 ,2 , 4, 6, 8, 10 ,並且會立刻執行,而不是懶惰執行
Entry.objects.all()[:10:2]
#查詢某月 __month
Account.objects.filter(register_date__month=7)
#查詢某天 __day
Account.objects.filter(register_date__year=2021,register_date__day=6)
Account.objects.filter(register_date__year=2021).filter(register_date__day=6)
#查詢星期幾__week_day 注意 from 1(sunday)to 7(saturday)
Account.objects.filter(register_date__week_day=3)
6.6.5 排序
# order_by() 對結果集排序
person_li = Person.objects.filter(pub_date__year=2006).order_by('pub_date')
# 支援多個欄位, 類似 SQL: order by pub_date, headline
person_li = Person.objects.filter(pub_date__year=2006).order_by('pub_date', 'headline')
#降序排序
person_li = Person.objects.filter(pub_date__year=2006).order_by('-pub_date')
6.6.6 其它方法
# count() 對結果集統計
count = Person.objects.filter(pub_date__year=2006).count()
#reverse() 對結果集進行反轉 注意使用reverse必須先排序
#比如要取資料的最後10條,可以先反轉再切片
Account.objects.all().order_by('id').reverse()[:1]
# first() 返回結果集的第一個物件
person = Person.objects.filter(pub_date__year=2006).first()
person = Person.objects.first()
# last() 返回結果集的最後一個物件
person = Person.objects.filter(pub_date__year=2006).last()
# values() 返回一個 字典物件 列表
person_dict_li = Person.objects.filter(pub_date__year=2006).values()
#values()傳入欄位
Account.objects.all().values('user_name')
<QuerySet [{'user_name': 'lili'}, {'user_name': 'tom'}, {'user_name': '李四'}]>
Account.objects.all().values('user_name','register_date')
<QuerySet [{'user_name': '李四', 'register_date': datetime.datetime(2021, 7, 1, 3, 18, 18, tzinfo=<UTC>)}, {'user_name': 'tom', 'register_date': datetime.datetime(2021, 7, 6, 1, 52, 54, 5896, tzinfo=<UTC>)}, {'user_name': 'lili', 'register_date': datetime.datetime(2021, 7, 6, 2, 10, 42, 927481, tzinfo=<UTC>)}]>
大部分檢索是懶惰的,只在真實獲取值的時候,才會真正去連線資料庫獲取資料:
# 懶惰執行
q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q) # 此處才會真的去連線資料庫獲取記錄
# 返回前10個記錄的, 0 ,2 , 4, 6, 8, 10 ,並且會馬上執行,而不是懶惰執行
q = Entry.objects.all()[:10:2] # 已經獲取到資料了
print(q)
6.6.7 多物件關聯查詢
透過Blog模型中,關聯的另一個模型物件entry的屬性進行過濾:
PS: entry__headline__contains ,即使是訪問模型物件entry的屬性 headline,也必須使用 '__'
# 檢索所有Blog具有至少一個物件Entry ,其headline包含'Lennon'
Blog.objects.filter(entry__headline__contains='Lennon')
# Blog中 有一個物件 entry 的 headline 中包含“Lennon”並且 是 2008年釋出的
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
# 透過 , 分割多個條件, 相當於資料庫中的 'and'
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
# 取上面相反的值
Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='Lennon', pub_date__year=2008, ))
一對一關係中,透過一個模型獲取另一個模型:
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
# BooleanField 在資料庫使用 tinyint 型別
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
# 透過 Place 查詢 Restaurant
place = Place.objects.first()
restaurant = place.restaurant
# 透過 定義了 OneToOneField 的模型 Restaurant 查詢 Place
restaurant = Restaurant.objects.first()
place = restaurant.place
一對多關係中,透過一個模型獲取另一個模型:
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=20)
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
# 從 一的模型 查詢 多的模型
# 透過 '多的模型小寫名_set' 查詢
manufacturer = Manufacturer.objects.first()
cars = manufacturer.car_set.all()
# 從 多的模型 查詢 一的模型
car = Car.objects.first()
manufacturer = car.manufacturer
多對多關係中,透過一個模型獲取另一個模型:
class SchoolClass(models.Model):
name = models.CharField(max_length=20)
class Teacher(models.Model):
name = models.CharField(max_length=10)
school_class = models.ManyToManyField(SchoolClass)
# 從 沒有寫 ManyToManyField 的模型查詢另一 寫了 ManyToManyField 的模型
# 需要在 查詢的模型名的小寫後 加 _set
schoolClass = SchoolClass.objects.first()
teachers = schoolClass.teacher_set.all()
# 從 寫了 ManyToManyField 的模型查詢另一個模型
teacher = Teacher.objects.first()
schoolClasses = teacher.school_class.all()
6.6.8 聚合函式
使用aggregate()函式返回聚合函式的值
常用的聚合函式有:Avg、Count、Max、Min、Sum
# Max 找出最大的
from django.db.models import Max
Person.objects.aggregate(Max('age'))
# 結果是一個字典 {'age__max': 30}
# 可以使用 max=Max('age') 指定 別名為 max,而不使用 age__max
Person.objects.aggregate(max=Max('age'))
# 多個聚合函式一起使用
Person.objects.aggregate(Max('age'), Min('age'), Avg('age'))
6.6.9 分組查詢
使用annotate()函式實現分組查詢,得配合其他函式:
- annotate:用於分組,配合 Avg,Count等聚合函式,如:annotate(max=Max('age'))
- filter: 用於過濾,在 annotate之前使用表示 where 條件,在annotate之後使用表示having條件
- values:在annotate之前使用表示分組欄位,在annotate之後使用表示取值
# 基本應用
# 以 group_id 分組,找出level的最大值,最小值,和平均值
Membership.objects.values('group_id').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))
# 以 group_id 分組 並且 group_id 大於 2 ,找出level的最大值,最小值,和平均值
Membership.objects.values('group_id').annotate(max=Max('level'), min=Min('level'), avg=Avg('level')).filter(group_id__gt=2)
# 和下面這句等效
# 推薦使用下面這種方式
Membership.objects.filter(group_id__gt=2).values('group_id').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))
6.6.10 修改
#單條記錄修改 save
p = Person.objects.get(pk=1)
p.first_name = 'James'
p.save()
obj = MyModel.objects.create(val=1)
# 需要使用F來保證不會出現併發衝突
from django.db.models import F
MyModel.objects.filter(pk=obj.pk).update(age=F('age') + 1)
# 更新多個值 update
Entry.objects.filter(pub_date__year='2007').update(headline='Everything is the same')
6.6.11 刪除
delete方法
person = Person.objects.get(pk=1)
person.delete()
6.6.12 重新整理物件
透過 refresh_from_db 從資料庫中重新獲取物件的內容
person = Person.objects.get(pk=1)
person.refresh_from_db()
6.6.13 Q物件
filter() 等方法中的關鍵字引數查詢都是並且('AND')的, 如果你需要執行更復雜的查詢(例如or語句),那麼可以使用Q 物件。
Q 物件 (django.db.models.Q) 物件用於封裝一組關鍵字引數,可以使用 & 和 | 運算子組合起來,當一個運算子在兩個Q 物件上使用時,它產生一個新的Q 物件。
from django.db.models import Q
# 等同於 select * from poll where question like 'Who%' or question like 'What%'
poll = Poll.objects.filter(Q(question__startswith='Who') | Q(question__startswith='What'))
# 等同於 select * from poll WHERE question like 'Who%' and (pub_date = '2005-05-02' or pub_date = '2005-05-06')
poll = Poll.objects.filter(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
# Q物件可以使用 ~ 運算子取反, 相當於SQL中 not
poll = Poll.objects.filter(
Q(question__startswith='Who'),
~Q(pub_date__year=2005)
)
# Q物件可以和一般的關鍵字引數混用, 但是Q物件必須在一般關鍵字引數的前面
Poll.objects.filter(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who'
)
6.6.14 F物件
簡單記憶:模型的屬性名出現在運算子的右邊,就使用F物件進行包裹
-
可以用模型的A屬性與B屬性進行比較
# 找出女生數量大於男生數量的年級 # 對應sql:select * from grade where girlnum > boynum grades = Grade.objects.filter(girlnum__gt=F('boynum'))
-
支援算術運算
# 找出女生數量大於 男生數量+10 的年級 # 對應的sql: select * from grade where girlnum > boynum + 10 Grade.objects.filter(girlnum__gt=F('boynum') + 10) # 所有書籍的價格 +1 # 對應的 sql: update book set price = price + 1 Book.objects.update(price=F("price")+1)
6.6.15 使用sql語句
-
透過模型使用sql:
透過raw函式執行原始SQL語句進行查詢,主鍵欄位必須包含在查詢的欄位中,不然會引發錯誤 :
# 定義個 person 模型 class Person(models.Model): first_name = models.CharField() last_name = models.CharField() birth_date = models.DateField() # 執行 原始 SQL # 表名前面必須加 應用名myapp,即在資料庫中的真實表名,否則會丟擲異常 for p in Person.objects.raw('SELECT * FROM myapp_person'): print(p) # 欄位先後順序沒關係 Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person') # 等同於 Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person') # 可以從其他表格中查詢出匹配 person 模型的記錄集 # 總之最終的資料集的結構必須和 Person一樣 Person.objects.raw('SELECT first AS first_name, last AS last_name,bd AS birth_date,pk AS id,FROM some_other_table') # 返回的結果集一樣可以執行切片 first_person = Person.objects.raw('SELECT * FROM myapp_person')[0] # 但是上述語句會返回所有結果,基於節省傳輸的需要,在資料庫縮小結果集範圍更正確 first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0] # 傳遞引數 lname = 'Doe' Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])
-
避開模型使用sql
不應用模型,直接使用sql語句進行增刪改查
from django.db import connection def my_custom_sql(obj): with connection.cursor() as cursor: cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [obj.baz]) cursor.execute("SELECT foo FROM bar WHERE baz = %s", [obj.baz]) row = cursor.fetchone() return row
七、檢視
7.1 簡單應用
-
FBV (Function base views)
就是在檢視裡使用函式處理請求
# urlconf 中 urlpatterns = [ path('fbv/', views.current_datetime), ] # views 中 from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
- 檢視函式 current_datetime,每個檢視函式都將一個HttpRequest 物件作為其第一個引數,該引數通常被命名request
- 檢視函式的名稱無關緊要,它不必以某種方式命名,以便Django能夠識別它,但是函式命名一定要能夠清晰的描述它的功能
- 檢視函式返回一個HttpResponse響應的物件,每個檢視函式負責返回一個HttpResponse物件(有例外,但我們在稍後討論)
-
CBV (class base views)
就是在檢視裡使用類處理請求
# urlconf 中 urlpatterns = [ # 一定要使用 as_view() ,記住 小括號 path('cbv/', views.MyView.as_view()), ] # views中 from django.http import HttpResponse from django.views import View class MyView(View): def get(self, request): return HttpResponse('get OK') def post(self, request): return HttpResponse('post OK')
- CBV提供了一個as_view()靜態方法(也就是類方法),呼叫這個方法,會建立一個類的例項,然後透過例項呼叫dispatch()方法,dispatch()方法會根據request的method的不同呼叫相應的方法來處理request(如get(),post()等)
- 提高了程式碼的複用性,可以使用面嚮物件的技術,比如Mixin(多繼承)
- 可以用不同的函式針對不同的HTTP方法處理,而不是透過很多 if 判斷,可以提高程式碼可讀性
7.2 返回錯誤響應
# 使用 HttpResponseNotFound
def my_view(request):
# ...
if foo:
return HttpResponseNotFound('<h1>Page not found</h1>')
else:
return HttpResponse('<h1>Page was found</h1>')
# 還可以直接返回狀態碼
from django.http import HttpResponse
def my_view(request):
# ...
# Return a "created" (201) response code.
return HttpResponse(status=201)
# 特殊的 404 錯誤
from django.http import Http404
from django.shortcuts import render
from polls.models import Poll
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404("Poll does not exist")
return render(request, 'polls/detail.html', {'poll': p})
7.3 檢視裝飾器
@require_http_methods,要求檢視只接收指定的http方法
@require_GET():僅僅允許GET方法
@require_POST():僅僅允許POST方法.
@require_safe():僅僅允許GET和HEAD方法
from django.views.decorators.http import require_http_methods
# 允許 GET和POST方法, 預設就是所有方法都支援
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
pass
@login_required : 必須登入才能訪問裝飾的檢視函式
使用者未登入,則重定向到settings.LOGIN_URL,除非指定了login_url引數,例如:@login_required(login_url='/polls/login/')
@login_required
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
pass
7.4 請求與響應
-
HttpRequest
每一個使用者請求在到達檢視函式的同時,django會自動建立一個HttpRequest物件並把這個物件當做第一個引數傳給要呼叫的views方法。HttpRequest物件裡封裝了本次請求所涉及的使用者瀏覽器端資料、伺服器端資料等,在views裡可以透過request物件來呼叫相應的屬性。
所有檢視函式的第一個引數都是HttpRequest例項
屬性(除非另有說明,否則所有屬性均應視為只讀):
-
HttpRequest.scheme:
表示請求使用的協議(http或https)
-
HttpRequest.body:
原始HTTP請求主體,型別是位元組串。處理資料一些非html表單的資料型別很有用,譬如:二進位制影像,XML等;
-
取form表單資料,請使用 HttpRequest.POST
-
取url中的引數,用HttpRequest.GET
-
-
HttpRequest.path:
表示請求頁面的完整路徑的字串,不包括scheme和域名。
例: "/music/bands/the_beatles/"
-
HttpRequest.path_info:
在某些Web伺服器配置下,主機名後的URL部分被分成指令碼字首部分和路徑資訊部分。path_info無論使用什麼Web伺服器,該屬性始終包含路徑的路徑資訊部分。使用此代替path可以使程式碼更容易在測試和部署伺服器之間移動。
例如,如果WSGIScriptAlias你的應用程式設定為 "/minfo",則path可能是"/minfo/music/bands/the_beatles/" , path_info 會是 "/music/bands/the_beatles/"。
-
HttpRequest.method:
表示請求中使用的HTTP方法的字串,是大寫的。例如:
if request.method == 'GET': do_something() elif request.method == 'POST': do_something_else()
-
HttpRequest.encoding:
表示當前編碼的字串,用於解碼錶單提交資料(或者None,表示使用該DEFAULT_CHARSET設定)。
可以設定此屬性來更改訪問表單資料時使用的編碼,修改後,後續的屬性訪問(例如讀取GET或POST)將使用新encoding值。
-
HttpRequest.content_type:
表示請求的MIME型別的字串,從CONTENT_TYPE解析 。
-
HttpRequest.content_params:
包含在CONTENT_TYPE 標題中的鍵/值引數字典。
-
HttpRequest.GET:
包含所有給定的HTTP GET引數的類似字典的物件。請參閱QueryDict下面的文件。
-
HttpRequest.POST:
包含所有給定HTTP POST引數的類似字典的物件,前提是請求包含表單資料。請參閱QueryDict文件。POST不包含檔案資訊,檔案資訊請見FILES。
-
HttpRequest.COOKIES:
包含所有Cookie的字典,鍵和值是字串。
-
HttpRequest.FILES:
包含所有上傳檔案的類似字典的物件
-
HttpRequest.META:
包含所有可用HTTP meta的字典
中介軟體設定的屬性:
Django的contrib應用程式中包含的一些中介軟體在請求中設定了屬性。如果在請求中看不到該屬性,請確保使用了相應的中介軟體類MIDDLEWARE。
-
HttpRequest.session:
來自SessionMiddleware:代表當前會話的可讀寫字典物件。
-
HttpRequest.site:
來自CurrentSiteMiddleware: 代表當前網站的例項Site或 RequestSite返回get_current_site()
-
HttpRequest.user:
來自AuthenticationMiddleware:AUTH_USER_MODEL代表當前登入使用者的例項
-
-
QueryDict
在一個 HttpRequest物件中,GET和 POST屬性是django.http.QueryDict例項,該物件定義為相同的鍵的多個值的字典狀類,繼承自MultiValueDict, 而MultiValueDict則繼承自dict
qd = request.GET # QueryDict('a=1&a=2&c=3') #<QueryDict: {'a': ['1', '2'], 'c': ['3']}> # 獲取屬性c print(qd['c']) # 有多個值的key,會獲取最後一個值 print(qd['a']) # 列印 2 # 獲取key a 對應的所有value:['1', '2'] print(qd.getlist('a'))
QueryDict.items()
等同於 Dict的items,但是同一個key,多個值時,最後一個值起作用
qd = QueryDict('a=1&a=2&a=3&b=4') print(list(qd.items())) # 列印 [('a', '3'), ('b', '4')]
-
HttpResponse
返回給瀏覽器端的響應物件
屬性:
-
HttpResponse.content:
表示響應的字串
-
HttpResponse.charset:
表示響應將被編碼的字符集,如果在HttpResponse例項化時沒有給出,則會從中提取 content_type,如果沒有設定content_type,則使用settings.DEFAULT_CHARSET
-
HttpResponse.status_code:
該響應的 HTTP 狀態碼
-
HttpResponse.reason_phrase:
響應的HTTP原因描述語,使用HTTP標準的預設原因描述語
除非明確設定,否則reason_phrase由 status_code 決定。 -
HttpResponse.streaming:
總是False,中介軟體透過此屬性可以區分流式響應與常規響應
-
HttpResponse.closed:
如果response已經結束,則返回True,否則返回False
-
-
JsonResponse
包含json格式內容的響應
from django.http import JsonResponse response = JsonResponse({'foo': 'bar'}) print(response.content) # 列印 b'{"foo": "bar"}'
-
FileResponse
檔案內容的響應
from django.http import FileResponse path = os.getcwd() + '/' + 'myfile.png' # os.getcwd() 得到的是django專案根目錄 response = FileResponse(open(path, 'rb'))
7.5 快捷方式
-
render 方法:
必需的引數:
request:用於生成此響應的請求物件。
template_name:要使用的模板的全名或模板名稱的列表。
可選引數:
context:要新增到模板上下文的值的字典。預設情況下,這是一個空字典。如果字典中的值是可呼叫的,檢視將在渲染模板之前呼叫它。
content_type:用於生成文件的MIME型別。預設為DEFAULT_CONTENT_TYPE設定的值。
status:響應的狀態碼。預設為200。
using:用於載入模板的模板引擎名。
from django.shortcuts import render def my_view(request): # View code here... return render(request, 'myapp/index.html', { 'foo': 'bar', }, content_type='application/xhtml+xml') # 等同於 from django.http import HttpResponse from django.template import loader def my_view(request): # View code here... t = loader.get_template('myapp/index.html') c = {'foo': 'bar'} return HttpResponse(t.render(c, request), content_type='application/xhtml+xml')
-
redirect 方法
-
透過傳遞一些物件, 該物件的 get_absolute_url()方法將被呼叫以找出重定向URL:
from django.shortcuts import redirect def my_view(request): ... object = MyModel.objects.get(...) return redirect(object)
-
透過傳遞一個URLConf排程器中配置path或re_path的名稱,以及可選的一些位置或關鍵字引數,該URL將使用reverse()方法反向解析 :
def my_view(request): ... return redirect('polls:index', foo='bar')
-
透過傳遞硬編碼的URL重定向:
# 使用絕對路徑 def my_view(request): ... return redirect('/some/url/') # 可以使用 ./ 和 ../ 的相對路徑 def my_view(request): return redirect('../../blog/')
-
預設情況下,redirect()返回一個臨時重定向(302)。所有上述形式都接受permanent引數; 如果設定為True永久重定向(301)將被返回:
def my_view(request): ... object = MyModel.objects.get(...) return redirect(object, permanent=True)
-
-
get_object_or_404,get_list_or_404
必需的引數:
klass:一Model類或一個Manager或QuerySet從哪個例項來獲取物件
**kwargs:查詢引數,應該採用get()或filter()的引數。
from django.shortcuts import get_object_or_404 def my_view(request): poll = get_object_or_404(MyModel, pk=1) #等同於 def my_view(request): try: poll = Poll.objects.get(pk=1) except Poll.DoesNotExist: raise Http404()
7.6 內建通用檢視
包含在 from django.views import generic 中
具體檢視型別有:
__all__ = [
'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView',
'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView',
'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView',
'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',
]
比較常用的:RedirectView、DetailView、ListView
用法:
DetailView:
# urls中:
path('<int:pk>/', views.MyDetailView.as_view(), name='detail'),
# views:
class MyDetailView(generic.DetailView):
"""
使用通用的詳情檢視
"""
# 對應的模型物件
model = Question
# 使用的模板名
template_name = 'detail.html'
# template(detail.html):
<h1>{{ object.question_text }}</h1>
八、模板
作為一個Web框架,Django需要一種方便的方式來動態生成HTML。最常用的方法依賴於模板。模板包含所需HTML輸出的靜態部分以及描述如何插入動態內容的特殊語法。
對模板引擎的一般支援和Django模板語言的實現都存在於 django.template 名稱空間中
8.1 django預設模版
1、配置
在settings中配置:
# 配置模板
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
筆記:
- BACKEND:是實現Django模板後端API的模板引擎類的路徑。內建是django.template.backends.django.DjangoTemplates和 django.template.backends.jinja2.Jinja2(使用這個需要額外安裝jinja2庫)
- DIRS :按搜尋順序定義引擎應該查詢模板原始檔的目錄列表
- APP_DIRS:告訴引擎是否應該在已安裝的應用程式中查詢模板,每個後端為其模板應儲存在的應用程式內的子目錄定義一個常規名稱。
- OPTIONS:包含後端特定的設定
2、用法
假如settings中配置如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/example.com',
'/home/html/default',
],
},
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [
'/home/html/jinja2',
],
},
]
-
get_template
該函式使用給定名稱載入模板並返回一個 Template物件,找到第一個匹配的模板即返回
template = get_template('story_detail.html')
Django將要查詢的檔案,依次為:
- /home/html/example.com/story_detail.html('django'引擎)
- /home/html/default/story_detail.html('django'引擎)
- /home/html/jinja2/story_detail.html('jinja2'引擎)
示例:
# 最常用的render from django.shortcuts import render def my_view(request): # View code here... return render(request, 'myapp/index.html', { 'foo': 'bar', }, content_type='application/xhtml+xml') # 相當於基礎的get_template: from django.http import HttpResponse from django.template import loader def my_view(request): # View code here... template = loader.get_template('myapp/index.html') context = {'foo': 'bar'} # 注意,這個 render 和 快捷鍵 render 不是一個物件 return HttpResponse(template.render(context, request), content_type='application/xhtml+xml')
-
select_template
select_template() 用法類似 get_template() ,除了它需要一個模板名稱的列表。它按順序嘗試每個名稱並返回存在的第一個模板
template = select_template(['story_253_detail.html','story_detail.html'])
Django將要查詢的檔案,依次為:
- /home/html/example.com/story_253_detail.html('django'引擎)
- /home/html/default/story_253_detail.html('django'引擎)
- /home/html/jinja2/story_253_detail.html('jinja2'引擎)
- /home/html/example.com/story_detail.html('django'引擎)
- /home/html/default/story_detail.html('django'引擎)
- /home/html/jinja2/story_detail.html('jinja2'引擎)
3、模板語言
Django模板只是一個文字文件或使用Django模板語言標記的Python字串。一些結構被模板引擎識別和解釋,主要的是變數( {{ 變數 }} )和標籤( {% 標籤 %} )。
1、變數
一個變數從上下文中輸出一個值,這是一個類似字典的物件將鍵對映到值。
變數被這樣包圍 :{{變數}} 譬如:
# 模板中這樣寫:
My first name is {{ first_name }}. My last name is {{ last_name }}.
# 傳入到 模板中的變數為:
{'first_name': 'John', 'last_name': 'Doe'}
# 在瀏覽器中顯示如下:
My first name is John. My last name is Doe.
字典查詢,屬性查詢和列表索引查詢 都用點符號實現:
# 獲取字典的 value
{{ my_dict.key }}
# 獲取物件的 屬性
{{ my_object.attribute }}
# 獲取列表的 元素
{{ my_list.0 }}
2、標籤
標籤在渲染過程中提供任意的邏輯。
例如,標籤可以輸出內容,用作控制結構,
例如“if”語句或“for”迴圈,從資料庫中獲取內容,甚至可以訪問其他模板標籤。
所有標籤見:
https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#ref-templates-builtins-tags
-
基本用法
標籤被包圍 {% 程式碼 %} 譬如:
# 無引數標籤 csrf_token ,這個標籤是用於html進行 form 表單提交時,包含一個 隨機變化的字串,在html頁面中,其實就是一個 input type='hidden' # 作用是用於防止跨域攻擊 {% csrf_token %} # 傳參的標籤 cycle , 引數 'odd' 'even' ,空格間隔 # 標籤本身也支援 關鍵字引數 {% 標籤 key1='value' key1='even' %} {% cycle 'odd' 'even' %}
-
常見標籤
由於在模板中,沒有辦法透過程式碼縮排判斷程式碼塊,所以控制標籤都需要有結束的標籤
-
if
判斷標籤 if endif :
# athlete_list 不為空 {% if athlete_list %} # 輸出 athlete_list 的長度 | 是過濾器 Number of athletes: {{ athlete_list|length }} {% elif athlete_in_locker_room_list %} Athletes should be out of the locker room soon! {% else %} No athletes. {% endif %}
-
firstof
輸出不是False的第一個引數,所有引數都為False,則什麼都不輸出
{% firstof var1 var2 var3 %} 等同於 {% if var1 %} {{ var1 }} {% elif var2 %} {{ var2 }} {% elif var3 %} {{ var3 }} {% endif %} 使用預設值: {% firstof var1 var2 var3 "預設值" %}
-
for
迴圈遍歷,for endfor結束 ,可以遍歷列表,字典等
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul> 反向迴圈: {% for athlete in athlete_list reversed%} 字典: data.items 這種呼叫方法,是不加 () 的 {% for key, value in data.items %} {{ key }}: {{ value }} {% endfor %} 設定預設值, for empty ,類似python的 for else: <ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% empty %} <li>Sorry, no athletes in this list.</li> {% endfor %} </ul> 等同於,但是比下面的寫法更加簡便: <ul> {% if athlete_list %} {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} {% else %} <li>Sorry, no athletes in this list.</li> {% endif %} </ul>
for迴圈在迴圈中設定了許多變數:
變數 描述 forloop.counter 迴圈的當前迭代(1索引) forloop.counter0 迴圈的當前迭代(0索引) forloop.revcounter 迴圈結束時的迭代次數(1索引) forloop.revcounter0 迴圈結束時的迭代次數(0索引) forloop.first 如果這是透過迴圈的第一次,則為真 forloop.last 如果這是透過迴圈的最後一次,則為真 forloop.parentloop 對於巢狀迴圈,這是圍繞當前迴圈的迴圈 -
布林運算子
and、or和not
{% if athlete_list and coach_list %} Both athletes and coaches are available. {% endif %} {% if not athlete_list %} There are no athletes. {% endif %} {% if athlete_list or coach_list %} There are some athletes or some coaches. {% endif %} {% if not athlete_list or coach_list %} There are no athletes or there are some coaches. {% endif %} {% if athlete_list and not coach_list %} There are some athletes and absolutely no coaches. {% endif %}
允許在同一個標籤中使用兩個and和or子句, and優先順序高於or例如:
{% if athlete_list and coach_list or cheerleader_list %} 將被解釋為: if (athlete_list and coach_list) or cheerleader_list
但是在if標籤中使用實際的括號是無效的語法。如果你需要它們來表示優先順序,你應該使用巢狀if標籤
-
邏輯運算子
==, !=, <, >, <=, >=, in, not in, is, 和 is not
{% if somevar == "x" %} This appears if variable somevar equals the string "x" {% endif %} {% if "bc" in "abcdef" %} This appears since "bc" is a substring of "abcdef" {% endif %} {% if somevar is not True %} This appears if somevar is not True, or if somevar is not found in the context. {% endif %}
優先順序,從低到高:
- or
- and
- not
- in
- ==,!=,<,>,<=,>=
-
url
給定檢視和可選引數匹配的絕對路徑引用(沒有域名的URL)
{% url 'some-url-name' v1 v2 %} 第一個引數是url模式名稱,後面跟著的是引數,以空格分隔 可以使用關鍵字: {% url 'some-url-name' arg1=v1 arg2=v2 %} 如果您想檢索名稱空間的URL,請指定完全限定的名稱: {% url 'myapp:view-name' %}
-
widthratio:乘法和除法
{% widthratio this_value max_value max_width %} 結果是: this_value/max_value*max_width 如果要計算 value * 10 : widthratio value 1 10 如果要計算 value / 8 : widthratio 8 1 還可以這樣使用: {% widthratio this_value max_value max_width as width %} The width is: {{ width }}
-
with
簡單的名稱快取複雜變數,當訪問多次耗時的方法(例如運算元據庫的方法):
{% with total=business.employees.count%} {{ total }} employee {% endwith %} 上述的:{% with total=business.employees.count%} 等同於:{% with business.employees.count as total %}
-
過濾器
透過 | 來使用
示例:{{ value|add:"2" }}
value: 是模板中獲取的物件
add: 是過濾器
“2”: 是過濾器add的引數,只支援一個引數
length:messages|length這裡判斷 messages 不為空,並且長度大於等於100
{% if messages|length >= 100 %} You have lots of messages today! {% endif %}
add:相加(減法:|add:-2 ) , 可以應用於 列表等
{{ value|add:"2" }} 如果value是4,那麼輸出6 {{ first|add:second }} 如果first=[1,2,3] ,second=[4,5,6],那麼輸出:[1,2,3,4,5,6]
divisibleby:能否整除,返回 True和False
{{ value|divisibleby:"2" }}
addslashes:單引號前加 \
{{ value|addslashes }} value="I'm using Django",輸出:"I\'m using Django".
capfirst:首字母大寫,只對字串的第一個單詞的首字母大寫
{{ value|capfirst }} 如果value是"django is good",輸出將是"Django is good"。
center:將值置於給定寬度的欄位中
"{{ value|center:"15" }}" 如果value是"Django",輸出將是。" Django ",前面5個,後面4個空格
cut:刪除給定字元
{{ value|cut:" " }} 如果value是"String with spaces",輸出將是:"Stringwithspaces"
date:日期字串
格式字元 描述 示例 輸出 b 月,文字,3個字母,小寫。 'jan' d 每月的一天,2位數字前導零。 '01' 至 '31' D 星期幾,文字,3個字母。 'Fri' e 時區名稱。可能是任何格式,或者可能會返回一個空字串,具體取決於日期時間。 '','GMT','-500','US/Eastern',等。 E 月,通常用於長日期表示的區域設定特定備選表示。 'listopada'(對於波蘭語區而言'Listopad') f 時間在12小時和分鐘之內,如果它們為零,則分鐘時間不再。專有擴充套件。 '1', '1:30' F 月,文字,長。 'January' g 小時,12小時制,無前導零。 '1' 至 '12' G 小時,24小時制,無前導零。 '0' 至 '23' h 小時,12小時制。 '01' 至 '12' H 小時,24小時制。 '00' 至 '23' i 分鐘。 '00' 至 '59' I 夏令時,無論是否有效。 '1' or '0' J 沒有前導零的月份的一天。 '1' 至 '31' n 月沒有前導零。 '1' 至 '12' N 月份 'Jan.', 'Feb.', 'March', 'May' s 秒 00-59 t 給定月份的天數。 28 至 31 T 這臺機器的時區。 'EST', 'MDT' u 微秒。 000000 至 999999 U Unix時代以來的秒數(1970年1月1日00:00:00 UTC)。 w ^ 沒有前導零的數字。 '0'(星期日)至'6'(星期六) W ^ ISO-8601週數,週數從週一開始。 1, 53 y 年,2位數字。 '99' Y 年,4位數字。 '1999' z 一年中的一天。 0 至 365 Z 以秒為單位的時區偏移量。UTC以西時區的偏移總是負值,而UTC以東的偏移總是正值。 -43200 至 43200 {{ value|date:"D d M Y" }} {{ value|date:"Y-m-d H:i:s" }} value:datetime.datetime.now()
-
include
包含另外一個模板
模板名稱可以是變數,也可以是單引號或雙引號的硬編碼(帶引號)的字串
{% include "foo/bar.html" %} {% include template_name %} 該變數也可以是任何具有render()接受上下文的方法的物件
include傳引數:
下面這個例子在頁面中顯示:"Hello, John!"
-
context:
context = {'greeting': 'hello', 'person': 'John'
-
name_snippet.html模板:
{{ greeting }}, {{ person|default:"friend" }}!
-
原模板透過with傳遞引數
{% include "name_snippet.html" with person="John" greeting="Hello" %}
如果只想使用include傳遞的引數(甚至沒有引數,不使用父模板的上下文變數)渲染模板,使用only:
{% include "name_snippet.html" with greeting="Hi" only %}
-
4、靜態檔案
主要使用 css、javascript和圖片檔案
-
settings中配置
# staticfiles 只在 DEBUG=True 的情況下有效,生產環境中使用 伺服器(如nginx)的靜態檔案管理 INSTALLED_APPS = [ 'django.contrib.staticfiles', ] # "/static/" or "http://static.example.com/" 這種url訪問的請求都會交給 staticfiles 處理 STATIC_URL = '/static/' # 指定靜態檔案儲存的目錄,預設是各個子應用下的 static 資料夾 # 以下是 linux系統, windows下使用: "C:/Users/user/mysite/extra_static_content" STATICFILES_DIRS = [ "/home/special.polls.com/polls/static", "/home/polls.com/polls/static", "/opt/webfiles/common", ]
-
在子應用下建立目錄 static, 存放 靜態檔案
-
模板檔案中
{% load static %} # 寫在模板中第一行 # 圖片 <img src="{% static "images/hi.jpg" %}" alt="Hi!"> # css <link rel="stylesheet" href="{% static 'css/mystyle.css' %}" type="text/css" media="screen"> # javascript <script type='text/javascript' src='{% static 'js/myjs.js' %}'></script> # 設定變數 {% static "images/hi.jpg" as myphoto %} <img src="{{ myphoto }}">
-
get_static_prefix
在模板中指定 settings 中配置的 STATIC_URL
{% load static %} <img src="{% get_static_prefix %}images/hi.jpg" alt="Hi!"> # 等同於 <img src="{% static "images/hi.jpg" %}" alt="Hi!">
5、模板繼承
Django模板引擎中最強大,也是最複雜的部分是模板繼承。模板繼承允許構建一個基本“骨架”模板,其中包含您網站的所有常見元素,並定義子模板可以覆蓋的 blocks
如下,定義一個base.html, 如下:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
上面這個模板中,定義了3個 block :
- title
- sidebar
- content
block的基本使用方法:{% block title %} {% endblock %}
子頁面detail.html繼承上面這個base.html,內容如下:
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
使用{% extends "base.html" %} 繼承 base.html,本頁面有2個block,對應base.html的2個block,會在base.html中,使用本detail.html的對應內容進行替換,而block sidebar 並沒有對應的block,因此繼續使用base.html的 sidebar 的內容
繼承的技巧:
- 如果在模板中使用,{% extends %}必須是該模板中的第一個模板標籤。否則模板繼承將不起作用。
- 基本模板中的越多{% block %}標籤越好。子模板不必定義所有父塊,因此可以在多個塊中填寫合理的預設值
- 如果發現自己在多個模板中複製了內容,則可能意味著您應該將該內容移至父模板的 {% block %} 中
- 如果需要從父模板中獲取塊的內容,則使用 {{ block.super }}
{% block sidebar %}
{{ block.super }}
新的內容
{% endblock %}
- 在使用模板標籤語法之外建立的變數不能在塊{% block% }內使用。例如,該模板不會呈現任何內容
{% trans "Title" as title %}
{% block content %}{{ title }}{% endblock %}
- 為了增加可讀性,您可以選擇為您的結束標籤 命名。例如:{% endblock content %}
{% block content %}
{% endblock content %}
九、admin後臺
Django框架提供了一個自動化後臺管理功能,對網站資料的後臺維護,僅僅需要進行非常簡單的配置和編寫極少的程式碼即可實現。
9.1 配置
settings.py中:
INSTALLED_APPS = [
'django.contrib.admin',
]
如果需要實現後臺管理的中文顯示,則修改以下配置:
LANGUAGE_CODE = 'zh-Hans'
USE_I18N = True
9.2 URL 路由
urls.py中:
urlpatterns = [
path('admin/', admin.site.urls),
]
9.3 建立管理員賬戶
建立管理員賬戶之前,確保專案的資料庫已經正確連線,並且已經將admin應用的模型進行了遷移
另外,如果settings中配置了 AUTH_PASSWORD_VALIDATORS,那麼會對使用者名稱和密碼進行對應的檢測,譬如:不能太簡單,不能是純數字等
-
專案根目錄下 cmd 命令視窗執行以下命令:
python manage.py createsuperuser
-
輸入使用者名稱並按回車
Username: admin
-
提示 電子郵件地址
Email address: admin@example.com
-
最後一步是輸入密碼,將被要求輸入兩次密碼,第二次作為第一次確認:
Password: ********** Password (again): ********* Superuser created successfully.
9.4 基本模型管理
-
建立模型
在model.py中
class Student(models.Model): name = models.CharField(max_length=20) age = models.IntegerField()
-
新增模型管理
在admin.py中
from django.contrib import admin from .models import Student admin.site.register(Student)
-
在瀏覽器中訪問
訪問path為 /admin, 譬如:http://127.0.0.1:8000/admin
-
進一步完善顯示
class Student(models.Model): # 增加 verbose_name name = models.CharField(max_length=20, verbose_name='姓名') # 增加 help_text age = models.IntegerField(help_text='大於18', verbose_name='年齡') # 增加 Meta 類 class Meta: # verbose_name_plural : 複數形式 verbose_name_plural = verbose_name = '學生' # 模型類的 字串化 def __str__(self): return f'{self.name}({self.pk})'
在子應用下的apps.py中:
from django.apps import AppConfig class PersonConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'person'
-
特殊下拉屬性管理
class Student(models.Model): SEX_CHOICES = ((1,'男')), (2, '女') # 增加 verbose_name name = models.CharField(max_length=20, verbose_name='姓名') # 增加 help_text age = models.IntegerField(help_text='大於18', verbose_name='年齡') # 修改已有模型,增加新欄位的話,都需要設定預設值或者設定 null=True sex = models.IntegerField(choices=SEX_CHOICES, default=1, verbose_name='性別') # 增加 Meta 類 class Meta: # verbose_name_plural : 複數形式 verbose_name_plural = verbose_name = '學生' # 模型類的 字串化 def __str__(self): return f'{self.name}({self.pk})'
9.5 關係模型管理
-
建立模型
class Place(models.Model): name = models.CharField(max_length=50, verbose_name='地名') address = models.CharField(max_length=80, verbose_name='門牌') def __str__(self): return f'{self.name}-{self.address}' class Meta: verbose_name_plural = verbose_name = '地址' class Restaurant(models.Model): place = models.OneToOneField( Place, on_delete=models.CASCADE, primary_key=True, verbose_name='地址' ) name = models.CharField(max_length=50, verbose_name='名字') # BooleanField 在資料庫使用 tinyint 型別 serves_hot_dogs = models.BooleanField(default=False, verbose_name='經營熱狗') serves_pizza = models.BooleanField(default=False, verbose_name='經營披薩') def __str__(self): return self.name class Meta: verbose_name_plural = verbose_name = '餐館' class Waiter(models.Model): restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, verbose_name='餐館') name = models.CharField(max_length=50, verbose_name='姓名') def __str__(self): return self.name class Meta: verbose_name_plural = verbose_name = '服務員' class SchoolClass(models.Model): name = models.CharField(max_length=20, verbose_name='班級名') class Meta: verbose_name_plural = verbose_name = '班級' def __str__(self): return self.name class Teacher(models.Model): name = models.CharField(max_length=10, verbose_name='姓名') school_class = models.ManyToManyField(SchoolClass, verbose_name='班級') class Meta: verbose_name_plural = verbose_name = '老師' def __str__(self): return self.name
-
新增模型管理
admin.site.register(Place) admin.site.register(Restaurant) admin.site.register(Waiter) admin.site.register(Teacher) admin.site.register(SchoolClass)
9.6 自定義模型管理類
之前使用的 admin.site.register(Student) 是用的 Django 預設的管理類,也可以自定義:
class StudentAdmin(admin.ModelAdmin):
pass
admin.site.register(Student, StudentAdmin)
自定義管理類的屬性:
class StudentAdmin(admin.ModelAdmin):
# 顯示的屬性列表, 值是 屬性名
list_display = ['name', 'age']
# 排序的 屬性 列表 , 預設是升序,如果需要降序:['-age']
ordering = ['age']
admin.site.register(Student, StudentAdmin)
使用裝飾器註冊
# 該裝飾器使用的是 admin.register , 不是 admin.site.register
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
list_display = ['name', 'age']
ordering = ['age']
自定義模型管理類的屬性:
@admin.register(Student)
class StudentAdmin(admin.ModelAdmin):
# 屬性為空時,在網頁上顯示的內容,預設是: -
empty_value_display = '-empty-'
# 管理的欄位, 和 exclude衝突
fields = ('name',)
# 不管理的欄位,和 fields衝突
exclude = ('age',)
多對多關係,預設是 select多選框,一般使用 filter_horizontal 或者 filter_vertical
@admin.register(Teacher)
class TeacherAdmin(admin.ModelAdmin):
ordering = ('name', )
filter_horizontal = ('school_class', )
9.7 增加額外批次操作
在admin.py中:
-
動作函式是單獨的模組函式:
# 定義動作函式 def age_add_one(modeladmin, request, queryset): queryset.update(age=F('age')+1) # 給動作函式新增描述 age_add_one.short_description = "年齡增加一歲" class StudentAdmin(admin.ModelAdmin): # 在當前自定義管理類中,新增新的動作:age_add_one actions = [age_add_one] admin.site.register(Student, StudentAdmin)
-
動作函式寫在自定義管理類中作為一個方法:
class StudentAdmin(admin.ModelAdmin): # 這裡必須是函式名的字串 actions = ['age_add_one'] # 類方法也同樣是3個引數! def age_add_one(modeladmin, request, queryset): queryset.update(age=F('age') + 1) age_add_one.short_description = "年齡增加一歲" admin.site.register(Student, StudentAdmin)
9.8 覆蓋admin預設模板
如果我們要在admin管理頁面中,增加自己的功能,那麼我們需要覆蓋admin的預設模板,透過以下步驟實現:
1、admin允許覆蓋的頁面
- app_index.html
- change_form.html
- change_list.html
- delete_confirmation.html
- object_history.html
- popup_response.html
2、admin管理模板目錄
django庫下的 contrib/admin/templates/admin 目錄,可以檢視django自帶的所有模板
3、自定義模板
需要修改admin的哪個內建模板,則繼承哪個模板,並且在其基礎上進行修改,我們以app_index.html為例:
原始模板是:
{% extends "admin/index.html" %}
{% load i18n %}
{% block bodyclass %}{{ block.super }} app-{{ app_label }}{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
›
{% for app in app_list %}
{{ app.name }}
{% endfor %}
</div>
{% endblock %}
{% endif %}
{% block sidebar %}{% endblock %}
4、建立對應模板
在settings中配置的templates目錄下,建立admin目錄,並且在admin目錄下,建立mysite目錄(mysite是你的應用名,假設mysite是children,那麼目錄結構如下圖)
我們不需要修改原始模板所有的block,只需要在我們要新增功能的地方,進行繼承,並且修改就行了,如下:
{% extends "admin/app_index.html" %}
{% block sidebar %}
<a href="{% url 'children:customize' %}">自定義功能頁面</a>
{% endblock %}
5、修改 urls
path('customize/', views.customize, name='customize'),
6、修改 views
def customize(request):
"""
編寫你自己的自定義管理功能
:param request:
:return:
"""
return redirect('/admin/children/')
十、session&cookie
10.1 背景介紹
HTTP協議有一個特性就是無狀態的,是指協議對於互動性場景沒有記憶能力。
隨著動態互動的web應用的出現,HTTP的無狀態特性嚴重阻礙了動態互動應用程式的發展,例如一些購物網站在進行購物時候都會進行了頁面跳轉/重新整理,按照HTTP的無狀態協議豈不是登入一次又一次。於是,兩種用於保持http連線狀態的技術就應運而生了,這個就是Session和Cookie。
10.2 cookie簡介
Cookie,有時也用Cookies,是指web程式為了辨別使用者身份、進行 session 跟蹤而儲存在使用者本地終端上的資料(通常經過加密),一般是以鍵值對的形式存在,Cookie具有不可跨域名性
Cookie是http協議中定義在 header 中的欄位
-
cookie解決無狀態問題原理
Cookie 技術透過在請求和響應報文中寫入Cookie 資訊來控制客戶端的狀態。
Cookie 會根據從伺服器端傳送的響應報文內的一個叫做Set-Cookie的首部欄位資訊,通知客戶端儲存Cookie。當下次客戶端再往該伺服器傳送請求時,客戶端會自動在請求報文中加入Cookie 值後傳送出去。
伺服器端發現客戶端傳送過來的Cookie 後,會去檢查究竟是從哪一個客戶端發來的連線請求,然後對比伺服器上的記錄,最後得到之前的狀態資訊。 -
cookie的常見屬性:
-
name:cookie的名稱
-
value:cookie的值
-
domain:該cookie的所屬域名,具有繼承性,只允許本域名及子域名訪問
譬如:test.com 這個是頂級域名, 二級域名 aaa.test.com 就是 test.com 的子域名,三級域名 bbb.aaa.test.com 是 aaa.test.com 的子域名
如果設定一個 cookie: user=terry ,domain = aaa.test.com
那麼:
aaa.test.com 這個域名下的url都可以訪問 該cookie
bbb.aaa.test.com 下的url也可以訪問 該cookie
但是 test.com 下的url不可以訪問該cookie,兄弟域名ccc.test.com 也不可以訪問該cookie
-
path:該cookie的所屬的路徑,具有繼承性,只允許本路徑及子路徑域名訪問,設定為根路徑 path='/' ,則所有路徑都可以訪問
-
expires/Max-Age:expires設定為一個失效時間值,HTTP1.1 中,expires 被棄用並且被Max-Age替代,設定為cookie多久時間之後失效,是整型,表示秒數
response.set_cookie('num',123,max_age=24*3600) response.set_cookie('num',123,expires=(datetime.now()+timedelta(hours=1,seconds=30)))
-
size:cookie的內容大小
-
http:httponly屬性,預設為False,若此屬性為true,則只有在http請求頭中會帶有此cookie的資訊,而不能透過JavaScript(document.cookie)來訪問此cookie
-
secure:預設為False,設定是否只能透過https來傳遞此cookie
-
10.3 django中應用cookie
-
在django設定cookie
# 只設定key和value兩個引數,預設關閉瀏覽器失效 response.set_cookie('key', "value") # 10秒後失效 response.set_cookie('key', "value", max_age=10)
-
django中讀取cookie
request.COOKIES['key'] # 或 request.COOKIES.get('key')
-
簽名的cookie
簽名指的就是加密
# 設定簽名cookie, salt 的內容可以是任意的字串, 不要洩露 response.set_signed_cookie('key2', 'value2', salt='test') # 獲取簽名cookie, 第二個引數 None是預設值 request.get_signed_cookie('key2', None, salt='test')
-
cookie實現三天免登入
-
urls.py
path('login_html/',views.login_html,name='login_html'), path('dologin/',views.dologin,name='dologin'),
-
登入頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <form action="{%url 'dologin' %}" method="post"> {% csrf_token %} <p>使用者名稱:<input type="text" name="username" value="{{ username }}"/></p> <p>密 碼: <input type="password" name="password" value="{{pwd}}"/> </p> <p> <input type="checkbox" name="rember" value="rember" {% if username %}checked{%endif%}/>記住密碼 <input type="submit" value="登入"/> </p> </form> </body> </html>
-
處理檢視函式
def login_html(request): #從cookie中獲取使用者名稱和密碼 username = request.COOKIES.get('username') password = request.get_signed_cookie('pwd',None,salt='pwdsalt') if username and password: return render(request,'cookie_app/login.html',{'username':username,'pwd':password}) else: return render(request,'cookie_app/login.html') def dologin(request): #從登入表單中獲取使用者名稱和密碼及是否勾選了記住密碼 data = request.POST username = data.get('username') password = data.get('password') rember = data.get('rember') response = HttpResponse() if 'root' == username and '123' == password: response.content='登入成功' if rember == 'rember': #勾選了記住使用者名稱和密碼 #將使用者名稱和密碼儲存到cookie中 response.set_cookie('username',username,max_age=3*24*3600) response.set_signed_cookie('pwd',password,salt='pwdsalt',max_age=3*24*3600) else: #刪除cookie中的之前儲存使用者名稱和密碼 response.delete_cookie('username') response.delete_cookie('pwd') return response else: response.delete_cookie('username') response.delete_cookie('pwd') return redirect('login_html')
-
10.4 Session簡介
Session,在計算機中,尤其是在網路應用中,稱為“會話控制”。Session 物件儲存特定使用者會話所需的屬性及配置資訊。這樣,當使用者在應用程式的 Web 頁之間跳轉時,儲存在 Session 物件中的變數將不會丟失,而是在整個使用者會話中一直存在下去。當使用者請求來自應用程式的 Web 頁時,如果該使用者還沒有會話,則 Web 伺服器將自動建立一個 Session 物件。當會話過期或被放棄後,伺服器將終止該會話 。
會話狀態僅在支援 cookie 的瀏覽器中保留!及session是依賴於cookie的。
django框架中的session管理允許儲存和檢索任意資料,它在伺服器端儲存資料並抽象cookie的傳送和接收。Cookie包含session的ID,而不是資料本身(除非使用基於cookie的session管理型別)
10.5 django中應用session
-
啟用session
要應用session,必須開啟session中間層,在settings中:
MIDDLEWARE = [ # 啟用 Session 中間層 'django.contrib.sessions.middleware.SessionMiddleware', ]
-
五中型別的session
Django中預設支援Session,其內部提供了5種型別供開發者使用:
- 資料庫(預設)
- 快取
- 檔案
- 快取+資料庫
- 加密cookie
5種方式的啟動配置各異,但是啟動完成後,在程式中使用的方式都相同
-
資料庫方式
預設的方式,最簡單
# 資料庫方式(預設): SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 資料庫型別的session引擎需要開啟此應用,啟用 sessions 應用 INSTALLED_APPS = [ 'django.contrib.sessions', ]
-
快取
速度最快,但是由於資料是儲存在記憶體中,所以不是永續性的,伺服器重啟或者記憶體滿了就會丟失資料
PS:有其他的方式實現永續性的快取方式,官網查閱:Memcached快取後端
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
-
快取+資料庫
速度次於單純快取方式,但是實現了永續性,每次寫入快取記憶體也將寫入資料庫,並且如果資料尚未存在於快取中,則使用資料庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
-
檔案
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 設定檔案位置, 預設是 tempfile.gettempdir(), # linux下是:/tmp # windows下是: C:\Users\51508\AppData\Local\Temp SESSION_FILE_PATH = 'd:\session_dir'
-
加密cookie
基於cookie的session,所有資料都儲存在cookie中,一般情況下不建議使用這種方式
- cookie有長度限制,4096個位元組
- cookie不會因為服務端的登出而無效,那麼可能造成攻擊者使用已經登出的cookie模仿使用者繼續訪問網站
- SECRET_KEY這個配置項絕對不能洩露,否則會讓攻擊者可以遠端執行任意程式碼
- cookie過大,會影響使用者訪問速度
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
-
session的引數配置
# Session的cookie儲存在瀏覽器上時的key,即:sessionid=隨機字串(預設) SESSION_COOKIE_NAME = "sessionid" # Session的cookie儲存的路徑(預設) SESSION_COOKIE_PATH = "/" # Session的cookie儲存的域名(預設) SESSION_COOKIE_DOMAIN = None # 是否Https傳輸cookie(預設) SESSION_COOKIE_SECURE = False # 是否Session的cookie只支援http傳輸(預設) SESSION_COOKIE_HTTPONLY = True # Session的cookie失效日期(2周)(預設) SESSION_COOKIE_AGE = 1209600 # 是否關閉瀏覽器使得Session過期(預設) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否每次請求都儲存Session,預設修改之後才儲存(預設) SESSION_SAVE_EVERY_REQUEST = False
-
程式中的應用
# 在django程式中透過 request.session 來獲取session物件,並且這個session就是一個字典物件,並且 key 只能是 字串 request.session['has_commented'] = True # 獲取某個session變數 has_commented = request.session.get('has_commented') # 刪除某個key del request.session['has_commented']
-
session物件常見方法
# 獲取session物件 session = request.session # 除常見的字典方法外 # 從會話中刪除當前會話資料並刪除會話cookie flush() # 設定測試cookie以確定使用者的瀏覽器是否支援cookie set_test_cookie() # 返回True或者False,取決於使用者的瀏覽器是否接受測試cookie test_cookie_worked() # 刪除測試cookie delete_test_cookie() # 設定會話的到期時間 # 如果value是整數,則session將在多少秒不活動後到期 # 如果value是一個datetime或timedelta,該session將在相應的日期/時間到期 # 如果value是0,使用者的會話cookie將在使用者的Web瀏覽器關閉時到期 # 如果value是None,則會話將恢復為使用全域性會話到期策略 set_expiry(value) # 返回此會話到期之前的秒數 # kwargs 為 `modification` 和 `expiry`,一般不指定 # modification:最後一次訪問日期,預設當前時間, now # expiry: 到期剩餘秒數,預設全域性配置時間 get_expiry_age(**kwargs) # 返回此會話將過期的日期 # 引數同 get_expiry_age get_expiry_date(**kwargs) # 返回True或者False,取決於使用者的Web瀏覽器關閉時使用者的會話cookie是否會過期 get_expire_at_browser_close() # 從會話儲存中刪除過期的會話,這是個類方法。 clear_expired() # 在保留當前會話資料的同時建立新的會話金鑰 cycle_key()
-
示例程式碼
我們以實現登入來應用session功能(需要使用使用預設的auth應用):
urls:
urlpatterns = [ path('', views.index, name='index'), path('login/', views.login, name='login'), path('logout/', views.logout, name='logout'), ]
views:
@login_required(login_url='test_app:login') def index(request): return render(request, 'index.html') def logout(request): auth.logout(request) return redirect('test_app:login') def login(request): if request.method == 'GET': error_message = request.session.get('error_message') request.session['error_message'] = None return render(request, 'login.html', {'error_message': error_message}) else: username = request.POST['username'] password = request.POST['password'] # 從django的authenticate中獲取對應的使用者名稱和密碼的 ORM 物件 user = auth.authenticate(username=username, password=password) if user: # 將登陸的使用者寫入到session中, 在其它檢視函式中用 request.user 獲取使用者物件 auth.login(request, user) return redirect('test_app:index') else: request.session['error_message'] = '使用者名稱或密碼錯誤!' return redirect('test_app:login')
templates:
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> 歡迎 {{ request.user.username }} 光臨 <br> <a href="{% url 'test_app:logout' %}">退出登入</a> </body> </html>
login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登入頁面</title> </head> <body> <form action="{% url 'test_app:login' %}" method="POST"> {% csrf_token %} 使用者名稱:<input type="text" name="username"> <br> 密碼:<input type="password" name="password"> <br> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <br> <input type="submit" value="登入" > </form> </body> </html>
十一、django框架分頁器
11.1 概述
分頁,就是當我們在頁面中顯示一些資訊列表,內容過多,一個頁面顯示不完,需要分成多個頁面進行顯示時,使用的技術就是分頁技術。
在django專案中,一般是使用3種分頁的技術:
- 自定義分頁功能,所有的分頁功能都是自己實現
- django的外掛 django-pagination 實現
- django自帶的分頁器 paginator
11.2 分頁器相關物件
分頁器的物件在 django/core/paginator.py 模組中,主要包括Paginator類和Page類:
-
Paginator類
-
初始化方法_init_(self, object_list, per_page, orphans=0,allow_empty_first_page=True):
- object_list:可以是QuerySet、帶有count()或_len_()方法的列表、元組或其它可切片的物件,如果是QuerySet,應該進行排序,使用order_by()子句或者有預設的ordering
- per_page:每個頁面上顯示的專案數目,不包括orphans部分
- orphans:預設為0,如果最後一頁顯示的數目小於等於該數值,那麼則將最後一頁的內容(假設數為N)新增到倒數第二頁中去,這樣的話倒數第二頁就成了最後一頁,顯示的數目為:per_page+N
- allow_empty_first_page:預設為True,允許第一頁顯示空白內容,如果設定為False,那麼當object_list為空時,丟擲EmptyPage錯誤
-
方法
-
get_page(self, number)
- numer:指定頁碼數,正確值應該是大於等於1的整數
返回指定number的Page物件,同時還處理超出範圍和無效頁碼,如果number不是數字,則返回第一頁,如果number為負數或大於最大頁數,則返回最後一頁。
-
page(self, number)
- numer:指定頁碼數,正確值應該是大於等於1的整數
返回指定number的Page物件,不處理異常,如果number無效,則丟擲 InvalidPage 錯誤
-
-
屬性
- count:專案總條數,呼叫該屬性時,優先呼叫object_list的count()方法,沒有count()方法才嘗試len(object_list)方法
- num_pages:總頁碼數
- page_range:從1開始的頁碼迭代器,程式碼:range(1, self.num_pages + 1)
-
-
Page類
一般情況下,不會手動例項化該類,而是透過Paginator的page或者get_page方法獲取
-
初始化方法_init_(self, object_list, number, paginator):
- object_list:當頁顯示的object_list物件,object_list可以是QuerySet、帶有count()或_len_()方法的列表、元組或其它可切片的物件
- number:頁碼數
- paginator:Paginator類的例項
-
方法
主要的方法都是用來做邏輯判斷的,以此來決定頁面中顯示的諸如:上一頁、下一頁,首頁,末頁等
-
has_next():如果有下一頁則返回True
-
has_previous():如果有上一頁,則返回True
-
has_other_pages():如果有上一頁或者下一頁,則返回True
-
next_page_number():返回下一頁編號,如果下一頁不存在則引發 InvalidPage 錯誤
-
previous_page_number():返回上一頁編號,如果上一頁不存在則引發 InvalidPage 錯誤
-
start_index() :返回頁面上第一個物件的從1開始的索引,相對於分頁器列表中的所有物件。例如,當為每頁包含2個物件的5個物件的列表進行分頁時,第二個頁面Page物件的start_index返回3
-
end_index() :返回頁面上最後一個物件的從1開始的索引,相對於分頁器列表中的所有物件。例如,當為每頁包含2個物件的5個物件的列表進行分頁時,第二個頁面Page物件的end_index返回4
-
-
屬性
其實就是初始化方法中的3個引數
- object_list:對應的object_list
- number:該物件的所處的頁碼數,從1開始
- paginator:關聯的Paginator例項
-
11.3 專案中應用
-
新建django專案:paginator_study,子應用:paginator_app
-
在mysql資料庫新建 paginator_study 庫
-
settings中配置資料庫:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'paginator_study', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '3306', } }
-
建立模型
class Student(models.Model): name = models.CharField(max_length=20) age = models.IntegerField() sex = models.IntegerField(choices=((1, '男'), (2, '女')), default=1) card_no = models.CharField(max_length=18)
-
遷移資料庫
python manage.py makemigrations python manage.py migrate paginator_app
-
生成測試資料
model中增加批次插入測試資料的方法:
@classmethod def insert_test_data(cls, num=100): def random_str(raw_ite, length): return ''.join(random.choices(raw_ite, k=length)) obj_list = [] for _ in range(num): obj_list.append(Student( name=random_str(string.ascii_lowercase, 8), age=random.randint(18, 50), sex=random.choice([1, 2]), card_no=random_str(string.digits, 18) )) Student.objects.bulk_create(obj_list)
然後在 python manage.py shell 中呼叫該方法
python manage.py shell from paginator_app.models import * Student.insert_test_data()
-
urls增加路由配置
urlpatterns = [ path('students/', views.students, name='students') ]
-
views中實現函式:
每頁顯示的頁碼數,一般儲存在settings中
def students(request): page = request.GET.get('page') all_students = Student.objects.all() paginator = Paginator(all_students, settings.PER_PAGE_NUMBER) students_page = paginator.get_page(page) return render(request, 'paginator_app/students.html', {'students_page':students_page})
-
增加students.html模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>學生列表</title> </head> <body> <table> <tr> <th>序號</th> <th>姓名</th> <th>年齡</th> <th>性別</th> <th>身份證</th> </tr> {% for student in students_page.object_list %} <tr> <td>{{ students_page.start_index|add:forloop.counter0 }}</td> <td>{{ student.name }}</td> <td>{{ student.age }}</td> <td>{{ student.get_sex_display }}</td> <td>{{ student.card_no }}</td> </tr> {% empty %} <tr> <td colspan="5">當前沒有學生</td> </tr> {% endfor %} </table> </body> </html>
-
增加table的樣式
使用css樣式
-
students.html頁面載入static
{% load static %}
-
新建目錄結構如下: paginator_app>>static>>paginator_app>>css>>paginator.css
-
paginator.css內容如下:
table { border-collapse: collapse; text-align: center; } table td, table th { border: 1px solid #cad9ea; color: #666; height: 35px; } table thead th { background-color: #CCE8EB; width: 100px; } table tr:nth-child(odd) { background: #fff; } table tr:nth-child(even) { background: #F5FAFA; }
-
頁面中匯入css檔案
<link rel="stylesheet" href="{% static 'paginator_app/css/paginator.css' %}">
-
-
-
增加分欄選項
-
目標中增加:
{% if students_page.object_list %} <nav> <ul> <li> <a href="?page=1"> <span>首頁</span> </a> </li> {% if students_page.has_previous %} <li> <a href="?page={{ students_page.previous_page_number }}"> <span>上一頁</span> </a> </li> {% endif %} {% for pg in page_range %} {% if pg == students_page.number %} <li><strong>{{ pg }}</strong></li> {% else %} <li><a class="pagination__number" href="?page={{ pg }}">{{ pg }}</a></li> {% endif %} {% endfor %} {% if students_page.has_next %} <li> <a href="?page={{ students_page.next_page_number }}"> <span>下一頁</span> </a> </li> {% endif %} <li> <a href="?page={{ students_page.paginator.num_pages }}"> <span>末頁</span> </a> </li> </ul> </nav> {% endif %}
-
orphans引數的使用
在setting.py中新增
PAGE_ORPHANS = 5
在檢視函式中,建立Paginator時,新增orphans引數
paginator = Paginator(students_li, settings.PER_PAGE_NUMBER, orphans=settings.PAGE_ORPHANS)
可以看到最後一頁如果記錄條數小於5,則會自動加到倒數第二頁中
-
檢視函式中增加:
為了在模板使用諸如: 1 2 3 4 5 6 7 8 這樣的分頁欄,需要判斷相對於當前頁的前後頁碼數,這種功能的實現,可以在自定義過濾器中實現,也可以在 views中實現後傳入一個 變數
# 新增函式 def get_page_range_by_page_and_max_page(page, max_page, num=10): min = page-int(num/2) min = min if min > 1 else 1 max = min + num - 1 max = max if max < max_page else max_page return range(min, max + 1) # 返回值增加 return render(request, 'paginator_app/students.html', { 'students_page': students_page, 'page_range': get_page_range_by_page_and_max_page(students_page.number, paginator.num_pages) })
-
-
分頁欄增加樣式
ul,li{ padding:0; margin:0;list-style:none} .nav{border:1px solid #000; width:510px; overflow:hidden} .nav li{ line-height:22px; float:left; padding:0 5px;} .nav li a:hover{ color:#F00} nav li{line-height:22px; float:left; padding:0 6px;} nav li a{ color:#009900}
-
可以將分頁欄提取到一個模板檔案中,成為公共的檔案
新建paginator_common.html
<nav> <ul> <li> <a href="?page=1"> <span>首頁</span> </a> </li> {% if students_page.has_previous %} <li> <a href="?page={{ students_page.previous_page_number }}"> <span>上一頁</span> </a> </li> {% endif %} {% for pg in page_range %} {% if pg == students_page.number %} <li><strong>{{ pg }}</strong></li> {% else %} <li><a class="pagination__number" href="?page={{ pg }}">{{ pg }}</a></li> {% endif %} {% endfor %} {% if students_page.has_next %} <li> <a href="?page={{ students_page.next_page_number }}"> <span>下一頁</span> </a> </li> {% endif %} <li> <a href="?page={{ students_page.paginator.num_pages }}"> <span>末頁</span> </a> </li> </ul> </nav>
在student.html 中修改
{% if students_page.object_list %} {% include 'paginator_app/paginator_common.html' %} {% endif %}
注意事項:在 paginator_common.html 中使用的 students_page和page_range需要統一命名,否則會報錯
執行結果
十二、django專案中使用驗證碼
12.1 概述
驗證碼(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自動區分計算機和人類的圖靈測試)的縮寫,是一種區分使用者是計算機還是人的公共全自動程式。可以防止:惡意破解密碼、刷票、論壇灌水,有效防止某個駭客對某一個特定註冊使用者用特定程式暴力破解方式進行不斷的登陸嘗試等。
12.2 類別
當今驗證碼各種不同的類別很多,常見的如下:
-
普通型:隨機多個(一般是4個)字母、數字和中文的圖片,可能加一些干擾項
-
問答型:圖片中顯示一個問題,譬如3+3=?
-
拖動行為型:拖動一個小圖片到一個拼圖中
-
點選行為型:按照順序點選圖片中的特定位置
12.3 實現思路
大部分的驗證碼驗證的思路都是這樣的:
- 客戶端傳送獲取驗證碼的請求
- 服務端接收到驗證碼請求後,生成對應的驗證碼和正確答案
- 服務端將驗證碼的正確答案儲存到會話物件當中
- 服務端將驗證碼返回到客戶端
- 客戶端看到驗證碼後:
- 如果看不清等原因,可以重新獲取,那麼就重新回到第1步
- 正確顯示後,輸入答案,提交答案到服務端
- 服務端接收到驗證碼答案後,和儲存在會話物件中的正確答案比對,正確就透過驗證,失敗則返回錯誤提示
12.4 django 專案中實現驗證碼
本文件中以普通的4個字母的驗證碼作為演示
-
實現登入功能(未使用驗證碼)
借用之前 session學習 課程中的部分的登入模組程式碼
-
新建django專案:captcha_study,子應用:captcha_app
-
在mysql資料庫新建 captcha_study 庫
-
settings中配置資料庫:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'captcha_study', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '3306', } }
-
遷移資料庫
由於只使用了django自帶的應用的資料庫模型,所以直接 migrate 就可以
python manage.py migrate
-
建立 superuser
python manager.py createsuperuser
-
修改主應用的urls.py:
path('captcha/', include('captcha_app.urls')),
-
新增子應用的urls.py
from django.urls import path from . import views app_name = 'captcha_app' urlpatterns = [ path('', views.index, name='index'), path('login/', views.login, name='login'), path('logout/', views.logout, name='logout'), ]
-
views中修改:
from django.contrib import auth from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect # Create your views here. @login_required(login_url='captcha_app:login') def index(request): return render(request, 'captcha_app/index.html') def logout(request): # 登出 auth.logout(request) return redirect('captcha_app:login') def login(request): """ 本應用的登入請求 登入請求一般有2個不同的http的method get: 顯示的就是登入頁面 post: 在登入頁面輸入使用者名稱和密碼之後,點選登入提交 :param request: :return: """ # get請求,對一個 登入的頁面 if request.method == 'GET': # 透過 session獲取 error_message error_message = request.session.get('error_message') request.session['error_message'] = None return render(request, 'captcha_app/login.html', {'error_message':error_message}) else: username = request.POST.get('username') password = request.POST.get('password') # 驗證使用者名稱和密碼 user = auth.authenticate(username=username, password=password) # 使用者名稱和密碼正確 if user: # 使用auth應用的話,登入成功必須呼叫 login 方法 # 在其他 函式中 使用 request.user 獲取 使用者物件例項 auth.login(request, user) return redirect('captcha_app:index') else: # 在不同的 檢視函式中傳遞引數,使用 session error_message = '使用者名稱或者密碼錯誤!!' request.session['error_message'] = error_message return redirect('captcha_app:login')
-
新增template
在子應用中建立 templates 資料夾,再建立一個子資料夾:captcha_app
新增index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> 歡迎光臨, 使用者:{{ request.user.username }}, email:{{ request.user.email }} <a href="{% url 'captcha_app:logout' %}">退出登入</a> </body> </html>
新建login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登入頁面</title> </head> <body> <form method="post" action="{% url 'captcha_app:login' %}"> {% csrf_token %} <table> <tr> <td>使用者名稱:</td> <td><input type="text" value="" name="username" id="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" value="" name="password" id="password"></td> </tr> {% if error_message %} <tr> <td colspan="2"><strong>{{ error_message }}</strong></td> </tr> {% endif %} <tr> <td colspan="2"> <input type="submit" value="登入"> </td> </tr> </table> </form> </body> </html>
-
生成圖片
需要安裝pillow庫
pip install Pillow
還需要下載一個字型檔案,譬如:ubuntu.ttf
新建captcha.py,程式碼如下:
import os from PIL import Image, ImageDraw, ImageFont, ImageFilter import random import string def random_str(length=4): """ 隨機字串 預設長度 4 :param length: 預設長度 4 :return: """ return ''.join(random.sample(string.ascii_letters, length)) def random_color(s=1, e=255): """ 隨機 RGB 顏色 :param s: 起始值, 0-255 :param e: 結束時, 0-255 :return: (r, g, b) """ return random.randint(s, e), random.randint(s, e), random.randint(s, e) def veri_code(length=4, width=160, height=40, size=28): """ 生成驗證碼圖片 :param length: 驗證碼字串長度 :param width: 圖片寬度 :param height: 圖片高度 :param size: 字型大小 :return: (驗證碼圖片, 驗證碼字串) """ # 建立Image物件 image = Image.new('RGB', (width, height), (255, 255, 255)) # 建立Font物件 file = os.path.dirname(os.path.abspath(__file__)) font = ImageFont.truetype(f'{file}/ubuntu.ttf', size) # 建立Draw物件 draw = ImageDraw.Draw(image) # 隨機顏色填充每個畫素 for x in range(0, width, 2): for y in range(height): draw.point((x, y), fill=random_color(64, 255)) # 驗證碼 code = random_str(length) # 隨機顏色驗證碼寫到圖片上 for t in range(length): draw.text((40 * t + 5, 5), code[t], font=font, fill=random_color(32, 127)) # 模糊濾鏡 image = image.filter(ImageFilter.BLUR) return image, code if __name__ == '__main__': img, code = veri_code() with open('test.png', 'wb') as f: img.save(f)
login.html增加驗證碼
-
增加驗證碼圖片標籤和輸入驗證碼內容的標籤
<tr> <td>驗證碼:</td> <td> <input type="text" placeholder="請輸入驗證碼" name="check_code"> <img src="{% url 'captcha_app:captcha_img' %}"> </td> </tr>
-
urls增加
path('captcha_img/', views.captcha_img, name='captcha_img'),
-
views中增加
def captcha_img(request): stream = BytesIO() img, code = veri_code() img.save(stream, 'PNG') request.session['check_code'] = code return HttpResponse(stream.getvalue())
-
在圖片標籤上增加點選重新整理的功能
login.html:
<img src="{% url 'captcha_app:captcha_img' %}" onclick="changeCheckCode(this);"> <script> function changeCheckCode(ths){ <!--改變URL,重新整理圖片。--> ths.src = "{% url 'captcha_app:captcha_img' %}?r=" + Math.random(); } </script>
在views中增加驗證碼效驗
-
修改 login 函式
def login(request): """ 本應用的登入請求 登入請求一般有2個不同的http的method get: 顯示的就是登入頁面 post: 在登入頁面輸入使用者名稱和密碼之後,點選登入提交 :param request: :return: """ # get請求,對一個 登入的頁面 if request.method == 'GET': # 透過 session獲取 error_message error_message = request.session.get('error_message') request.session['error_message'] = None return render(request, 'captcha_app/login.html', {'error_message':error_message}) else: check_code = request.POST.get('check_code') # 驗證碼正確 if check_code and check_code.lower() == request.session.get('check_code').lower(): username = request.POST.get('username') password = request.POST.get('password') # 驗證使用者名稱和密碼 user = auth.authenticate(username=username, password=password) # 使用者名稱和密碼正確 if user: # 使用auth應用的話,登入成功必須呼叫 login 方法 # 在其他 函式中 使用 request.user 獲取 使用者物件例項 auth.login(request, user) return redirect('captcha_app:index') else: # 在不同的 檢視函式中傳遞引數,使用 session error_message = '使用者名稱或者密碼錯誤!!' request.session['error_message'] = error_message return redirect('captcha_app:login') # 驗證碼錯誤 else: error_message = '驗證碼錯誤!' request.session['error_message'] = error_message return redirect('captcha_app:login')
-
十三、中介軟體
13.1 概述
AOP,面向切面程式設計,是對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。可以實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術。
面向切面程式設計,就是將交叉業務邏輯封裝成切面,利用AOP的功能將切面織入到主業務邏輯中。所謂交叉業務邏輯是指,通用的,與主業務邏輯無關的程式碼,如安全檢查,事物,日誌等。若不使用AOP,則會出現程式碼糾纏,即交叉業務邏輯與主業務邏輯混合在一起。這樣,會使業務邏輯變得混雜不清。
舉個例子:銀行系統取款會有一個流程查詢也會有一個流程。
Django的中介軟體,就是應用AOP技術來實現的,它是django請求/響應處理的鉤子框架,是一個輕巧的低階“外掛”系統,在不修改django專案原有程式碼的基礎上,可以全域性改變django的輸入或輸出,每個中介軟體元件負責執行某些特定功能。
PS:因為中介軟體改變的是全域性,所以需要謹慎實用,濫用的話,會影響到伺服器的效能
13.2 django預設中介軟體
django專案預設有以下自帶的中介軟體,如下:
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',
]
一般情況下這些中介軟體都會啟用(最少CommonMiddleware會啟用)
13.3 自定義中介軟體說明
如果需要增加自定義的中介軟體(該中介軟體類必須繼承MiddlewareMixin(django.utils.deprecation)),一般是新增在系統的中介軟體之下,如:
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',
# 自定義中介軟體
'my_app.middleware.MyMiddleware',
]
中介軟體中主要有以下方法(一箇中介軟體類最少需要實現下列方法中的一個):
-
process_request:處理請求物件,請求到達django框架時,第一時間呼叫
多箇中介軟體之間順序呼叫
引數:request
返回:
- response:呼叫當前中介軟體的process_response處理
- None:呼叫下一個中介軟體的process_request處理
-
process_response:處理響應物件,檢視函式返回response後,呼叫
多箇中介軟體之間倒序呼叫
引數:request, response
返回:
- response:呼叫上一個中介軟體的process_response處理
-
process_view:檢視預處理,在檢視函式處理之前呼叫,即請求在urlconf當中匹配到對應的檢視函式之後,先不呼叫檢視函式,而是先呼叫此方法
多箇中介軟體之間順序呼叫
引數:request,view_func,view_args,view_kwargs
view_func:url路由匹配到的檢視函式, 不是字串,是函式物件
view_args:檢視函式的可變引數
view_kwargs:檢視函式的可變關鍵字引數
返回:
- response:呼叫最後一箇中介軟體的process_response開始處理
- None:呼叫下一個中介軟體的process_view處理
-
process_exception:在檢視函式處理過程丟擲異常時呼叫,中介軟體的方法(除了process_template_response)中丟擲異常不會觸發
多箇中介軟體之間倒序呼叫
引數:request,exception
exception:是處理過程中丟擲的異常物件
返回:
- response:之後的process_exception都不會觸發,而是直接呼叫最後一箇中介軟體的process_response處理
- None:呼叫上一個中介軟體的process_exception處理
-
process_template_response:預設不執行,在檢視函式完成操作後呼叫,除非檢視函式返回的response中有render方法
多箇中介軟體之間倒序呼叫
引數:request,response
response:不是HttpReponse,而是具有render方法的物件,譬如:SimpleTemplateResponse物件,在(django.template.response中)
返回:
- response:具有render方法的物件,繼續呼叫上一個中介軟體的process_template_response處理,最後一個process_template_response處理完成後,會自動呼叫 response物件中的render方法,得到一個HttpResponse物件,進行返回,再呼叫process_response操作
中介軟體方法的執行時有順序的,process_request與process_view是按照順序去執行的,而process_response、process_exception和process_template_response是反序的 :
總結:使用者請求 >> process_request >> urlconf路由匹配,找到對應的檢視函式 >> process_view >> 檢視函式 >> process_template_response(如果檢視函式返回的response,有render方法,否則這一步不會執行) >> process_response >> 返回response到使用者
其中,在 檢視函式 和 process_template_response 處理過程中,如果出現 exception ,那麼就會倒序執行 中介軟體的process_exception
13.4 常見自定義中介軟體功能
總之,你如果有對全域性request或response的操作需求,那麼就可以使用中介軟體,譬如:
- IP過濾:對一些特定IP地址返回特定響應
- URL過濾:如果使用者訪問的是login檢視,則透過;如果訪問其他檢視,需要檢測是不是有session已經有了就透過,沒有就返回login頁面。這樣就不用在多個檢視函式上寫裝飾器login_required
- 內容壓縮:response內容實現gzip的壓縮,返回壓縮後的內容給前端
- CDN:內容分發網路,實現快取,如果快取中有資料直接返回,沒有找到快取再去請求檢視
- URL過濾:某個子應用升級暫停使用,某個特定的path路徑下的請求,返回一個特定頁面
13.5 例項專案
-
新建django專案:middleware_study,子應用:middleware_app
-
urls增加路由配置
urlpatterns = [ path('', views.index, name='index') ]
-
views中實現函式:
def index(request): return render(request, 'middleware_app/index.html')
-
增加index.html模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中介軟體學習的首頁</title> </head> <body> 歡迎學習中介軟體! </body> </html>
-
新建middleware.py
from django.http import HttpResponse from django.template.response import SimpleTemplateResponse from django.utils.deprecation import MiddlewareMixin class FirstMiddleware(MiddlewareMixin): def process_request(self, request): print('FirstMiddleware process_request') def process_response(self, request, response): print('FirstMiddleware process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('FirstMiddleware process_view') def process_exception(self, request, exception): print('FirstMiddleware process_exception') def process_template_response(self, request, response): print('FirstMiddleware process_template_response') return response class SecondMiddleware(MiddlewareMixin): def process_request(self, request): print('SecondMiddleware process_request') # 觸發當前中介軟體的 process_response # return HttpResponse('SecondMiddleware 測試process_request直接返回response') def process_response(self, request, response): print('SecondMiddleware process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('SecondMiddleware process_view') # 直接返回HttpResponse, 觸發最後一箇中介軟體的 process_response # return HttpResponse('SecondMiddleware process_view 返回HttpResponse') # 在 這裡直接返回具有render方法的 response, # 依然會觸發最後一箇中介軟體的process_template_response方法 # return SimpleTemplateResponse('middleware_app/test_template_response.html') def process_exception(self, request, exception): print('SecondMiddleware process_exception') # 捕獲異常,直接返回HttpResponse, 觸發最後一箇中介軟體的process_response return HttpResponse('SecondMiddleware process_exception捕獲異常,返回response') def process_template_response(self, request, response): print('SecondMiddleware process_template_response') # 直接返回HttpResponse,會導致後續丟擲異常,因為HttpResponse沒有render方法了 # return HttpResponse('SecondMiddleware process_template_response 返回HttpResponse') return response class ThridMiddleware(MiddlewareMixin): def process_request(self, request): print('ThridMiddleware process_request') def process_response(self, request, response): print('ThridMiddleware process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('ThridMiddleware process_view') def process_exception(self, request, exception): print('ThridMiddleware process_exception') def process_template_response(self, request, response): print('ThridMiddleware process_template_response') return response
-
settings中匯入
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', 'middleware_app.middleware.FirstMiddleware', 'middleware_app.middleware.SecondMiddleware', 'middleware_app.middleware.ThridMiddleware', ]
-
urls中增加
urlpatterns = [ path('', views.index, name='index'), ]
-
views中增加
from django.http import HttpResponse from django.shortcuts import render # Create your views here. from django.template.response import SimpleTemplateResponse class MyTemplateTest: def render(self): return HttpResponse('MyTemplateTest render 內容') def index(request): print('views index function') # raise Exception('手動丟擲異常') return MyTemplateTest() # return SimpleTemplateResponse('middleware_app/index.html') # 這個render 返回的是一個普通的HttpResponse,該物件沒有render方法 # return render(request, 'middleware_app/index.html')
13.6 示例-URL過濾
-
setting.py檔案中的配置
MIDDLEWARE = [ 'middleware_app.middlewares.UpgradeMiddleware', ]
-
增加upgrade.html模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>升級維護中</title> </head> <body> 當前系統正在升級,預計2022年1月1日 0點0分0秒升級完成,到時歡迎使用! </body> </html>
-
middleware.py
class UpgradeMiddleware(MiddlewareMixin): def process_request(self, request): if request.path.startswith('/middleware/'): return render(request, 'middleware_app/upgrade.html')
-
瀏覽器中訪問
http://127.0.0.1:8000/middleware
十四、Django的生命週期
14.1 Django生命週期的含義
Django請求的生命週期是指:當使用者在瀏覽器上輸入url到使用者看到網頁的這個時間段內,Django後臺所發生的事情。
14.2 Django生命週期流程
首先,使用者在瀏覽器中輸入url,傳送一個GET方法的request請求。Django中封裝了socket的WSGi伺服器,監聽埠接受這個request 請求。
再進行初步封裝,然後將請求傳送到中介軟體中,這個request請求依次經過中介軟體,對請求進行校驗或處理,再傳輸到路由系統中進行路由分發,匹配相對應的檢視函式(FBV),
再將request請求傳輸到views中的這個檢視函式中,進行業務邏輯的處理,呼叫modles中表物件,透過orm獲取資料庫(DB)的資料。
透過templates中相應的模板進行渲染,然後將這個封裝了模板response響應傳輸到中介軟體中,依次進行處理,最後透過WSGi再進行封裝處理,響應給瀏覽器展示給使用者。
14.3 生命週期分析
14.3.1 客戶端傳送請求
- 在瀏覽器輸入url,譬如www.baidu.com,瀏覽器會自動補全協議(http),變為http://www.baidu.com,現在部分網站都實現了HSTS機制,伺服器自動從http協議重定向到https協議
- 在網頁中點選超連結或javascript指令碼進行url跳轉,僅設定 href=‘絕對路徑’,瀏覽器會自動使用當前url的協議、host和port,譬如在 https://tieba.baidu.com/index.html網頁中,點選一個超連結 /f?kw=chinajoy , 會自動訪問 https://tieba.baidu.com/f?kw=chinajoy
14.2 路由轉發
- IP查詢:因特網內每個公有IP都是唯一的,域名相當於IP的別名,因為我們無法去記住一大堆無意義的IP地址,但如果用一堆有意義的字母組成,大家就能快速訪問對應網站
- DNS解析:透過域名去查詢IP,先從本地快取查詢,其中本地的hosts檔案也繫結了對應IP,若在本機中無法查到,那麼就會去請求本地區域的域名伺服器(通常是對應的網路運營商如電信),這個透過網路設定中的LDNS去查詢,如果還是沒有找到的話,那麼就去根域名伺服器查詢,這裡有所有因特網上可訪問的域名和IP對應資訊(根域名伺服器全球共13臺)
- 路由轉發:透過網路卡、路由器、交換機等裝置,實現兩個IP地址之間的通訊。用到的主要就是路由轉發技術,根據路由表去轉發報文,還有子網掩碼、IP廣播等等知識點
14.3 建立連線
透過TCP協議的三次握手建立連線
14.4 WSGIHandler 處理
當django接受到一個請求時,會初始化一個WSGIHandler,可以在專案下的wsgi.py檔案進行跟蹤檢視:
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
......
它接受2個引數:
- environ:是含有伺服器端的環境變數
- start_response:可呼叫物件,返回一個可迭代物件。
這個handler控制了從請求到響應的整個過程,首先的就是載入django的settings配置,然後就是呼叫django的中介軟體開始操作
14.5 middleware的process_request
中介軟體的process_request方法列表對request物件進行處理
django專案預設有一些自帶的中介軟體,如下:
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',
]
一般情況下這些中介軟體都會啟用,如果需要增加自定義的中介軟體(該中介軟體類必須繼承MiddlewareMixin),一般是新增在系統的中介軟體之下,如:
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',
# 自定義中間層
'my_app.middleware.MyMiddleware',
]
中介軟體中主要有以下方法(一箇中介軟體類最少需要實現下列方法中的一個):
-
process_request:處理請求物件
引數:request
返回:
- response:呼叫process_response列表處理
- None:呼叫下一個中介軟體的process_request處理
-
process_response:處理響應物件
引數:request, response
返回:
- response:呼叫上一個中介軟體的process_response處理
-
process_view:檢視預處理,在檢視函式處理之前呼叫
引數:request,view_func,view_args,view_kwargs
view_func:url路由匹配到的檢視函式
view_args:檢視函式的可變引數
view_kwargs:檢視函式的可變關鍵字引數
返回:
- response:呼叫process_response處理
- None:呼叫下一個中介軟體的process_view處理
-
process_exception:在檢視函式或中介軟體處理過程丟擲異常時呼叫
引數:request,exception
exception:是處理過程中丟擲的異常物件
返回:
- response:之後的process_exception都不會觸發,而是呼叫process_response處理
- None:呼叫上一個中介軟體的process_exception處理
-
process_template_response:在檢視函式完成操作後呼叫,預設不執行,除非檢視函式返回的response中有render方法
引數:request,response
response:不是HttpReponse,而是SimpleTemplateResponse物件,具有render方法
返回:
-
response:SimpleTemplateResponse物件,呼叫上一個中介軟體的process_template_response處理,最後一個process_template_response處理完成後,會自動呼叫 response物件中的render方法,得到一個HttpResponse物件,進行返回,再呼叫process_response操作
中介軟體方法的執行時有順序的,process_request與process_view是按照順序去執行的,而process_response、process_exception和process_template_response是反序的 :
-
14.6 URLConf路由配置
透過urls.py檔案中的 urlpatterns 配置找到對應的 檢視函式或者檢視類的方法,如果沒有找到匹配的方法,那麼就會觸發異常,由中介軟體的process_exception 進行處理
14.7 middleware的process_view
呼叫中介軟體的 process_view 方法進行預處理
14.8 views處理request
呼叫對應的檢視函式或檢視類的方法處理request,譬如獲取GET和POST引數,並且呼叫特定的模型物件執行資料庫操作,如果沒有資料庫操作,那麼就直接跳到我們後續的14步了
14.9 models處理
檢視方法中,一般情況下都需要呼叫模型類進行資料操作,一般是透過模型的manager管理類進行操作的,如:MyModel.objects.get(pk=1)
如果沒有資料操作,那麼這一步和下一步就忽略
14.10 資料庫操作
如果django透過模型類執行對資料庫的增刪改查,那麼此時整個流程就會在對應的資料庫中執行
14.11 views處理資料
檢視方法獲取到資料後:
- 將資料封裝到一個context字典當中,然後呼叫指定的template.html,透過模板中的變數、標籤和過濾器等,再結合傳入的資料context,會觸發中介軟體的process_template_response方法,最終渲染成HttpResponse
- 不呼叫模板,直接返回資料,譬如 JsonResponse、FileResponse等
- 執行redirect,生成一個重定向的HttpResponse,觸發中介軟體的process_response後,返回到客戶端,結束該web請求的生命週期
14.12 middleware的process_response
呼叫中介軟體的 process_response 方法進行處理,最後一箇中介軟體的process_response執行完成後,返回到WSGIHandler類中
至此,django程式設計的處理部分完畢
14.14 WSGIHandler處理
WSGIHandler類獲取到response後
-
先處理response的響應行和響應頭,然後呼叫 start_response 返回http協議的 響應行和響應頭 到uWSGI,這個 start_response 只能呼叫一次
-
第一步處理完成後,如果是檔案需要對response進行,否則就直接將response作為http協議的body部分返回給uWSGI
14.15 客戶端接收響應
客戶端接收到伺服器的響應後,做對應的操作,譬如:顯示在瀏覽器中,或是javascript的處理等
至此,整個web請求的生命週期結束。
十五、Django日誌
15.1 概述
django框架的日誌透過python內建的logging模組實現的,日記可以記錄自定義的一些資訊描述,也可以記錄系統執行中的一些物件資料,還可以記錄包括堆疊跟蹤、錯誤程式碼之類的詳細資訊
logging主要由4部分組成:Loggers、Handlers、Filters和Formatters
15.2 settings中完整的配置
LOGGING = {
# 固定值
'version':1,
# 格式器,詳細見第6點
'formatters': {},
# 過濾器,詳細見第5點
'filters':{},
# 處理器,詳細見第4點
'handlers':{},
# 記錄器,詳細見第3點
'loggers':{},
# 根記錄器,配置等同普通記錄器,但是沒有propagate配置項
'root':{},
# 預設為False。True:是將配置解釋為現有配置的增量。False:配置會覆蓋已有預設配置
'incremental':True,
# 預設為True。禁用任何現有的非root記錄器。如果設定了incremental=True,則此配置無效
'disable_existing_loggers': False
}
15.3 Loggers
這個類是logging系統的入口
python定義了日誌的5個級別,分別對應python程式中日誌資訊的不同嚴重性(嚴重程度從上到下越來越嚴重,也就是級別越高):
- DEBUG:用於除錯的最低階的系統資訊
- INFO:一般性的系統資訊
- WARNING:一些警告性的資訊,發生了一些小問題,這些問題不影響系統的正常執行,但是也不建議出現
- ERROR:系統出現錯誤了,該錯誤會影響系統的正常執行,記錄錯誤相關的資訊
- CRITICAL:非常嚴重的問題,譬如可能引起系統崩潰的問題等
在使用logger記錄日誌時,每條日誌訊息還有日誌級別,當logger記錄該日誌訊息時,會將訊息的級別和logger配置的日誌級別進行比較,只有訊息的級別達到或超過logger配置的日誌級別,才會將該日誌訊息傳遞給handler進一步處理,否則該日誌訊息會被忽略
PS:一般開發環境時,會啟用DEBUG級別,而在生產環境中,啟用WARNING或ERROR級別
15.3.1 settings中配置
透過在settings中配置LOGGING配置項實現日誌配置,共4個配置項(都是可選的,不過一般會指定handler):
- level:指定記錄日誌的級別,沒有配置則處理所有級別的日誌
- propagate:設定該記錄器的日誌是否傳播到父記錄器,不設定則是True
- filters:指定過濾器列表
- handlers:指定處理器列表
示例如下:
LOGGING = {
'version': 1, # 固定值,現在只有這一個版本
'disable_existing_loggers': False, # 設定已存在的logger不失效
'loggers': {
'': {
'handlers': ['console'],
},
'django': {
'handlers': ['console'],
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
'myproject.custom': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
'filters': ['special']
}
}
}
說明:
配置了4個 logger, 分別對應2個不同的handler(console輸出日誌到控制檯,mail_admins輸出日誌到郵件)
- '':預設的記錄器,不指定特定名稱,那麼就是使用這個記錄器,沒有配置level,那麼就是處理所有級別的日誌,傳遞所有級別的日誌到console控制器
- django:傳遞所有級別的日誌到console控制器
- django.request:django記錄器的子記錄器,處理ERROR級別及以上的日誌,propagate設定為 False,表明不傳播日誌給 "django",該logger傳遞日誌到mail_admins控制器
- myproject.custom:處理INFO級別及以上的日誌,應用了一個 special 的過濾器來過濾日誌,傳遞日誌到2個控制器(['console', 'mail_admins'])處理
django框架有個預設的配置:DEFAULT_LOGGING,一旦配置了自己的LOGGING後,那麼所有的預設的LOGGER全部都失效,失效不等於沒有記錄器了,而是說記錄器不起作用了,即不會記錄日誌,也不會將日誌傳播給父記錄器。因此你應該非常小心使用,因為你會感覺你丟了日誌一樣,可以手動設定同名的logger實現覆蓋,如:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/path/to/django/debug.log',
},
},
'loggers': {
# 覆蓋了 django 記錄器,所有django的記錄日誌最後全部寫入到檔案中
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
disable_existing_loggers預設是True,除非設定disable_existing_loggers為False,那麼預設配置的記錄器才會起作用
LOGGING = {
'disable_existing_loggers': False,
}
配置還可以使用系統變數,如下示例中讀取 DJANGO_LOG_LEVEL 環境變數:
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
},
},
}
15.3.2 logger的簡單使用
在諸如views.py中:
import logging
# 生成一個以當前檔名為名字的logger例項
logger = logging.getLogger(__name__)
# __name__ 也可以換成在settings中配置的 logger 名稱,生成指定的logger
logger_custom = logging.getLogger('myproject.custom')
def index(request):
logger.debug("進入index檢視函式")
logger_custom.debug("進入index檢視函式")
-
getLogger(_name_):記錄器名使用模組名,這是基於每個模組過濾和處理日誌記錄呼叫,對應預設記錄器,即名稱為 '' 的
-
getLogger('myproject.custom'):透過 "." 符號分隔的方式,定義記錄器名稱的層次結構,'myproject' 是跟記錄器, 'myproject.custom'是子記錄器,甚至 'myproject.custom.child' 是孫輩的記錄器。使用層次結構的作用是因為子記錄器是可以傳播日誌記錄給父記錄器的,最終傳播到根記錄器。預設就是傳播的,如果不想傳播,那麼需要在logger的配置中使用'propagate': False
15.3.3 常用方法
Logger.debug(msg)、Logger.info(msg)、Logger.warning(msg)、Logger.error(msg)、Logger.critical(msg) ,分別對應5個不同的日誌級別
Logger.log(level, msg) 記錄日誌,手動指定level,level的選擇是CRITICAL = 50,ERROR = 40,WARNING = 30,INFO = 20,DEBUG = 10,需要填寫數字,譬如 50或logging.DEBUG
Logger.exception():等同於Logger.error(msg, exc_info=True),輸出異常的堆疊資訊
15.3.4 不常用方法
Logger.setLevel(lel):指定最低的日誌級別,低於lel的級別將被忽略。debug是最低的內建級別,critical為最高
Logger.addFilter(filt)、Logger.removeFilter(filt):新增或刪除指定的filter Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或刪除指定的handler
14.3.5 Django內建logger
內建的logger在django專案執行中會自動記錄日誌,與我們手動建立的logger的執行沒有關係,除非我們也建立相同的logger
django框架呼叫的地方在:django.core.servers.basehttp中(如WSGIRequestHandler)
-
django:django框架中所有訊息的記錄器,一般使用它的子記錄器,而不是它釋出訊息,因為預設情況下子記錄器的日誌會傳播到根記錄器django,除非設定 'propagate': False
-
django.request:記錄與請求處理相關的訊息。5XX響應作為ERROR訊息; 4XX響應作為WARNING訊息引發。記錄到django.security記錄器的請求不會記錄到django.request中
傳送給此記錄器的訊息具有以下額外上下文:
- status_code:與請求關聯的HTTP響應程式碼
- request:生成日誌訊息的請求物件。
-
django.server:記錄與runserver命令呼叫的伺服器接收的請求的處理相關的訊息。5XX響應記錄為ERROR 訊息,4XX響應記錄為WARNING訊息,其他所有響應記錄為INFO。
傳送給此記錄器的訊息具有以下額外上下文:
- status_code:與請求關聯的HTTP響應程式碼。
- request:生成日誌訊息的請求物件。
-
django.template:記錄與模板呈現相關的訊息
-
django.db.backends:記錄程式碼和資料庫互動相關的訊息,例如,請求執行的每個SQL語句都會記錄為DEBUG級別的日誌。這個記錄器只有在settings.DEBUG設定為True時才啟用,並且不記錄事務管理(如:BEGIN, COMMIT, 和 ROLLBACK)
傳送給此記錄器的訊息具有以下額外上下文:
- duration:執行SQL語句所花費的時間。
- sql:已執行的SQL語句。
- params:SQL呼叫中使用的引數
-
django.security.*:記錄任何SuspiciousOperation和其他安全相關錯誤(django.security.csrf )的訊息,SuspiciousOperation子型別有:
DisallowedHost
DisallowedModelAdminLookup
DisallowedModelAdminToField
DisallowedRedirect
InvalidSessionKey
RequestDataTooBig
SuspiciousFileOperation
SuspiciousMultipartForm
SuspiciousSession
TooManyFieldsSent使用如:django.security.DisallowedHost
-
django.db.backends.schema:記錄資料庫遷移過程中的日誌,但是不記錄執行的查詢SQL語句等,傳送給此記錄器的訊息具有以下額外上下文:
- sql:已執行的SQL語句。
- params:SQL呼叫中使用的引數
15.4 Handlers
這個類是確定logger中訊息發生的引擎程式,描述特定的日誌記錄行為,譬如控制檯列印、寫入日誌檔案、透過網路進行傳送等
與logger一樣,handler也具有日誌級別,如果日誌記錄的日誌級別未達到或超過handler的級別,則handler將忽略該訊息。
一個logger可以有多個handler,每個handler可以有不同的日誌級別和記錄方法
15.4.1 settings中配置
4個引數(如下),加上對應class類的初始化引數
- class(必需):處理程式類的名稱
- level(可選的):處理程式的級別
- formatter(可選的):處理程式的格式化程式
- filters(可選的):處理程式的過濾器的列表
LOGGING = {
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': ['special']
}
},
}
說明:
配置了2個handler,使用了2個filter:require_debug_true和special, 使用了1個formatter:simple
- console:使用logging.StreamHandler,記錄INFO級別及以上的日誌到sys.stderr,使用simple格式化輸出內容
- mail_admins:使用django.utils.log.AdminEmailHandler,將ERROR及以上的日誌透過special過濾器過濾後,傳送郵件到管理員郵箱(需要配置settings中的一些其他的資訊,譬如ADMINS等)
15.4.2 不常用方法
Handler.setLevel(level):指定被處理的資訊級別,低於level級別的資訊將被忽略
Handler.setFormatter(fmt):給這個handler選擇一個格式fmt
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或刪除一個filter物件
15.4.3 內建處理器
1. python3的logging中的handler:
-
StreamHandler:輸出到stream,未指定則使用sys.stderr輸出到控制檯
-
FileHandler:繼承自StreamHandler,輸出到檔案,預設情況下,檔案無限增長
初始化引數:filename,mode ='a',encoding = None,delay = False
delay如果為True,那麼會延遲到第一次呼叫emit寫入資料時才開啟檔案
配置:
'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/path/to/django/app.log', #引數配置在這裡,多個引數按順序繼續配置即可, 如果要新增encoding,那麼在下面新增 encoding: 'utf-8' 即可 }, }
-
NullHandler:沒有任何輸出,避免出現錯誤:No handlers could be found for logger XXX
-
WatchedFileHandler:自動重開log檔案,配合別的會自動切分的log檔案使用
-
RotatingFileHandler:自動按大小切分的log檔案
初始化引數:filename,mode ='a',maxBytes = 0,backupCount = 0,encoding = None,delay = False
maxBytes:最大位元組數,超過時建立新的日誌檔案,如果backupCount或maxBytes有一個為0,那麼就一直使用一個檔案
backupCount:最大檔案個數,新檔案的副檔名是指定的檔案後加序號".1"等,譬如:backupCount=5,基礎檔名為:app.log,那麼達到指定maxBytes之後,會關閉檔案app.log,將app.log重新命名為app.log.1,如果app.log.1存在,那麼就順推,先將 app.log.1重新命名為app.log.2,再將現在的app.log命名為app.log.1,最大建立到app.log.5(舊的app.log.5會被刪除),然後重新建立app.log檔案進行日誌寫入,也就是永遠只會對app.log檔案進行寫入。
-
TimedRotatingFileHandler:按時間自動切分的log檔案,檔案字尾 %Y-%m-%d_%H-%M-%S
初始化引數:filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None
when:時間間隔型別,不區分大小寫
'S':秒 'M':分鐘 'H':小時 'D':天 'W0'-'W6':星期幾(0 = 星期一) 'midnight':如果atTime未指定,則在 0點0分0秒 翻轉,否則在atTime時間翻轉
interval:間隔的數值
backupCount: 檔案個數
encoding:編碼
delay:True是寫入檔案時才開啟檔案,預設False,例項化時即開啟檔案
utc:False則使用當地時間,True則使用UTC時間
atTime:必須是datetime.time例項,指定檔案第一次切分的時間,when設定為S,M,H,D時,該設定會被忽略
-
SocketHandler:透過TCP套接字傳送日誌記錄訊息
初始化引數:host, port
-
DatagramHandler:透過UDP套接字傳送日誌記錄訊息
-
SysLogHandler :傳送記錄訊息到遠端或本地Unix系統日誌
-
NTEventLogHandler:傳送日誌訊息到本地的Windows NT,Windows 2000或Windows XP事件日誌
-
SMTPHandler:透過email傳送日誌記錄訊息
初始化引數:mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=5.0
mailhost:發件人郵箱伺服器地址(預設25埠)或地址和指定埠的元組,如:('smtp.163.com', 25)
fromaddr:發件人郵箱
toaddrs:收件人郵箱列表
subject:郵件標題
credentials:如果郵箱伺服器需要登入,則傳遞(username, password)元組
secure:使用TLS加密協議
-
MemoryHandler :在記憶體中的日誌記錄緩衝,定期將其傳送到目標處理程式,只要緩衝區已滿,或者發生某個嚴重程度或更高的事件,就會發生髮送
-
HTTPHandler:傳送記錄訊息到Web伺服器,使用GET或POST
-
QueueHandler:傳送記錄訊息到佇列中,適合多程序(multiprocessing)場景
-
QueueListener:從佇列中接收訊息,適合多程序(multiprocessing)場景,用於和QueueHandler搭配
2. django內建的handler
-
AdminEmailHandler:會將收到的每一條日誌訊息傳送一個郵件到ADMINS指定的郵箱地址
必須settings中設定 DEBUG=False 才起作用
示例配置:
DEBUG=False EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.qq.com' # smtp地址 EMAIL_PORT = 25 EMAIL_HOST_USER = 'xxxx@qq.com' # smtp 伺服器的使用者名稱 EMAIL_HOST_PASSWORD = 'xxxxxxxx' # smtp伺服器的密碼 SERVER_EMAIL = 'xxxx@qq.com' ADMINS = [('Terry', 'xxxx@qq.com')]
ADMINS預設是[],示例配置:[('John', 'john@example.com'), ('Mary', 'mary@example.com')]
如果日誌記錄包含request屬性,則請求的完整詳細資訊將包含在電子郵件中。
如果客戶端的IP地址在INTERNAL_IPS設定中,則電子郵件主題將包含短語“內部IP” ; 如果沒有,它將包括“外部IP”。
如果日誌記錄包含堆疊跟蹤資訊,則該堆疊跟蹤將包含在電子郵件中。
初始化引數:include_html=False, email_backend=None
include_html:該值設定為True,並且settings中設定DEBUG=True時,則會在郵件中包括含有除錯網頁的全部內容的HTML附件,該附件包含完整的回溯,包含堆疊每個級別的區域性變數的名稱和值,以及Django設定的值,會比較敏感,謹慎使用
email_backend:指定郵件後臺,沒設定使用django預設的
簡單配置如下:
'handlers': { 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler', } },
覆蓋django預設的郵件後臺,配置:
'handlers': { 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler', 'email_backend': 'django.core.mail.backends.filebased.EmailBackend', 'include_html': True # 資訊中包含html頁面 } },
15.4.4 自定義處理器
任意自定義的class,繼承自logging.Handler,並且實現emit和flush方法(如下logging內建的StreamHandler類):
from logging import Handler
class StreamHandler(Handler):
terminator = '\n'
def __init__(self, stream=None):
Handler.__init__(self)
if stream is None:
stream = sys.stderr
self.stream = stream
def flush(self):
self.acquire()
try:
if self.stream and hasattr(self.stream, "flush"):
self.stream.flush()
finally:
self.release()
def emit(self, record):
try:
msg = self.format(record)
stream = self.stream
stream.write(msg)
stream.write(self.terminator)
self.flush()
except Exception:
self.handleError(record)
15.4.5 Filters
過濾器filter用於提供對日誌記錄從logger傳遞到handler的附加控制
預設情況下,logger和handler將處理滿足日誌級別要求的任何日誌訊息,但是,透過安裝filter,可以在日誌記錄過程中新增其他條件。例如,可以安裝僅允許ERROR級別 來自特定源的訊息的filter。
filter還可用於在發出之前修改日誌記錄。例如,如果滿足一組特定條件,可以編寫一個過濾器,將ERROR日誌記錄降級為WARNING記錄。
filter可以安裝在logger或handler上; 可以在鏈中使用多個filter來執行多個過濾操作。
1.settings中配置
LOGGING = {
'filters': {
'special': {
'()': 'project.logging.SpecialFilter',
'foo': 'bar',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
}
說明:
配置了2個過濾器
-
special:使用自定義的SpecialFilter類,傳入初始化引數 foo='bar'
-
require_debug_true:使用類:RequireDebugTrue
2.內建過濾器
-
python內建的過濾器:
日誌過濾器的父類:Filter,一般不直接使用
-
django內建的過濾器:
-
CallbackFilter:
初始化引數:callback
callback:為每個記錄呼叫callback,如果callback返回True,則允許日誌透過,如果callback返回False,則不允許該日誌透過
示例:
# callback函式: def skip_unreadable_post(record): if record.exc_info: exc_type, exc_value = record.exc_info[:2] if isinstance(exc_value, UnreadablePostError): return False return True # filter配置: 'filters': { 'skip_unreadable_posts': { '()': 'django.utils.log.CallbackFilter', 'callback': skip_unreadable_post, } },
-
RequireDebugFalse:僅在settings.DEBUG為False時傳遞記錄
'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse', } }
-
RequireDebugTrue:僅在settings.DEBUG為True時傳遞記錄
-
3.自定義過濾器
1. 在子應用下建立一個py檔案,自定義class,繼承自logging.Filter,並且實現filter方法(如下django內建的RequireDebugFalse類)
class RequireDebugFalse(logging.Filter):
def filter(self, record):
return not settings.DEBUG
class ExistWordFilter(logging.Filter):
def __init__(self, name='', exist_word=None):
super().__init__(name)
self.exist_word = exist_word
def filter(self, record):
if not self.exist_word or self.exist_word in record.msg:
return True
else:
return False
-
setting中配置過濾
'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse', }, 'exist_word_filter': { '()': 'logger_app.log.ExistWorFilter', 'exist_word':'測試' }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true','exist_word_filter'], 'class': 'logging.StreamHandler', 'formatter': 'simple' },
-
檢視函式
from django.http.response import HttpResponse from django.shortcuts import render # 這是views檢視模組 import logging # 這個 logger 只會在控制檯輸出 warning及以上的日誌 logger = logging.getLogger('logger_study.file') # 這個logger 會輸出 info及以上的日誌 # logger_server = logging.getLogger('django.server') def logger_test(request): logger.debug('logger debug 測試') logger.info('logger info 測試') logger.warning('logger warning') logger.error('logger error') return HttpResponse('日誌測試響應成功')
-
執行結果
控制檯輸出
[DEBUG][2021-08-02 13:45:03,896] logger debug 測試 [INFO][2021-08-02 13:45:03,896] logger info 測試
只有資訊中包含“測試”的日誌資訊才在控制檯輸出。
15.4.6 Formatter
日誌記錄最終需要呈現為文字,formatter程式描述該文字的確切格式。formatter通常由包含LogRecord屬性的Python格式化字串組成 ; 但是,也可以編寫自定義formatter來實現特定的格式化行為。
1.settings中配置:
3個引數(具體看後面的Formatter類):
- ():指定格式器的類,不指定的話,預設使用logging.Formatter
- format:格式化字串
- style:樣式選擇
- datefmt:日期格式化字串,使用的是python中時間日期格式化符號
LOGGING = {
'formatters': {
'verbose': {
'()': 'logging.Formatter',
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
}
}
說明:
配置了2個格式器:
- simple:只輸出簡單的:日誌級別名稱 日誌訊息
- verbose:輸出:日誌級別名稱 生成日誌訊息的時間 模組 程序 執行緒 日誌訊息
2.內建格式器
-
python內建的格式器
-
Formatter:預設格式器
初始化引數:fmt=None, datefmt=None, style='%'
- fmt:格式化字串,指定輸出格式,如:'{levelname}{process:d}{message}'
%(name)s:記錄器logger的名稱 %(levelno)s:日誌級別對應的數字 %(levelname)s:日誌級別名稱 %(pathname)s:日誌記錄呼叫的原始檔的完整路徑 %(filename)s:日誌記錄呼叫的原始檔名 %(module)s:模組名 %(lineno)d:日誌呼叫的行數 %(funcName)s:函式名 %(created)f:日誌建立時間,time.time() %(asctime)s:日誌建立時間,文字型別 %(msecs)d:日誌建立時間的毫秒部分 %(relativeCreated)d:日誌建立時間 - 載入日誌模組的時間 的 毫秒數 %(thread)d:執行緒ID %(threadName)s:執行緒名 %(process)d:程序ID %(processName)s:程序名 %(message)s:日誌訊息
-
datefmt:日期格式化字串,為None則使用ISO8601格式化,如:'2010-01-01 08:03:26,870'
-
style:'%','{' 或 '$',3選一:
- '%':預設是這個,使用python的 % 格式化 , 如: %(levelname)s
- '{':使用 str.format格式化(django框架使用這個), 如:
- '$':使用類 string.Template 格式化,如:$levelname
-
BufferingFormatter:一種適於格式化批次記錄的格式器,對每條記錄都使用指定的格式器進行格式化
初始化引數:linefmt=None
linefmt:指定格式器,設定為None使用上述的預設Formatter
-
-
django內建的格式器
- ServerFormatter:根據status_code不同,將日誌訊息格化式成不同的顏色的訊息
15.4.7 logging.LogRecord物件
每次logger記錄日誌時,都會自動建立該LogRecord例項
- 初始化引數:name,level,pathname,lineno,msg,args,exc_info,func = None,sinfo = None
說明:
- name - 用於記錄此LogRecord表示的事件的記錄器的名稱
- level - 日誌記錄事件的數字級別(DEBUG是10,INFO是20,以此類推),並將轉換為LogRecord的兩個屬性: 對於數值levelno和對應的級別名稱levelname
- pathname - 進行日誌記錄呼叫的原始檔的完整路徑名
- lineno - 進行日誌記錄呼叫的原始檔中的行號
- msg - 事件描述訊息,可能是帶有可變資料佔位符的格式字串
- args - 要合併到msg引數中以獲取事件描述的可變資料
- exc_info - 包含當前異常資訊的異常元組,或者None(沒有可用的異常資訊)
- func - 呼叫日誌記錄呼叫的函式或方法的名稱
- sinfo - 表示當前執行緒中堆疊基礎的堆疊資訊的文字字串,直到日誌記錄呼叫
-
方法
- getMessage:返回使用者提供的引數與訊息合併後的訊息
-
屬性
首先是第6點Formatters中內建格式器Formatters類的 fmt 屬性中,描述的類似:%(name)s 中的 name 就是 該物件的屬性
其他屬性如下:
- args:元組或字典(只有一個引數是),用於合併日誌訊息
- exc_info:異常元組(類似:sys.exc_info),沒有異常則是None
- msg:日誌記錄呼叫中傳遞的格式字串,可以合併args生成日誌訊息
- stack_info:在當前執行緒中從堆疊底部堆疊幀資訊(如果有的話),直到幷包括記錄呼叫的堆疊幀
15.4.8 專案中應用日誌
預設配置
不進行額外的配置,直接在專案中使用logger,則會使用django.utils.log的預設配置DEFAULT_LOGGING,預設的情況如下:
DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'formatters': {
'django.server': {
0 '()': 'django.utils.log.ServerFormatter',
'format': '[{server_time}] {message}',
'style': '{',
}
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
},
'django.server': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'django.server',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
},
'django.server': {
'handlers': ['django.server'],
'level': 'INFO',
'propagate': False,
},
}
}
-
如果設定DEBUG=True,那麼會將除django.server以外的INFO及以上的日誌輸出到控制檯
-
如果設定DEBUG=False,那麼會將除django.server以外的ERROR和CRITICAL級別的日誌傳遞到AdminEmailHandler
-
與DEBUG設定無關,所有的django.server記錄器的日誌,INFO及以上級別的都會輸出到控制檯
之所以有上述行為,是因為除了django.server記錄器外其他內建記錄器都會傳播訊息到根記錄器django,而根記錄器django使用了console和mail_admins處理器
# 這是views檢視模組
import logging
# 這個 logger 只會在控制檯輸出 warning及以上的日誌
logger = logging.getLogger(__name__)
# 這個logger 會輸出 info及以上的日誌
logger_server = logging.getLogger('django.server')
def index(request):
logger.debug('logger debug 測試')
logger.info('logger info 測試-------------------')
logger.warning('logger warning 測試')
logger.error('logger error 測試')
logger_server.debug('logger_server debug 測試')
logger_server.info('logger_server info 測試-------------------')
logger_server.warning('logger_server warning 測試')
logger_server.error('logger_server error 測試')
return HttpResponse('歡迎學習django的日誌功能!!')
2、一般應用
-
在根目錄下建立 log 目錄
-
在settings中配置:
BASE_LOG_DIR = os.path.join(BASE_DIR, "log") LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { # 一般應用檔案 'standard': { 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d:%(funcName)s] %(message)s' }, # 應用於控制檯 'simple':{ 'format':'[%(levelname)s][%(asctime)s] %(message)s' } }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "log_study.log"), 'maxBytes': 1024 * 1024 * 50, # 日誌大小 50M 'backupCount': 3, 'formatter': 'standard', 'encoding': 'utf-8', }, }, 'loggers': { '': { # 部署到生產環境之後可以把'console'移除 'handlers': ['console', 'file'], 'level': 'DEBUG', }, }, }
注意:RotatingFileHandler在生成新的檔案時,會丟擲 PermissionError 異常(其它分割檔案的也同樣會出現),是因為django自帶的簡易web伺服器,啟動時預設啟動2個程序,以便於實現 reload 的功能,如果要避免此錯誤,那麼必須修改manage.py的啟動引數為:
runserver --noreload
但是增加了 --noreload 這個引數後,web伺服器不會自動檢測程式碼的修改和重啟服務
-
檢視函式中使用logger:
import logging # __name__ 模組logger,對應settings中的 名稱為:'' 的 logger logger = logging.getLogger(__name__) def index(request): logger.debug('debug 測試') logger.info('info 測試') logger.warning('warning 測試') logger.error('error 測試') return HttpResponse('歡迎學習django的日誌功能!!')
- 結果:
console控制檯輸出:
[DEBUG][2021-07-29 17:23:42,803] logger debug 測試 [INFO][2021-07-29 17:23:42,804] logger info 測試------------------- [WARNING][2021-07-29 17:23:42,804] logger warning 測試 [ERROR][2021-07-29 17:23:42,805] logger error 測試
log目錄生成 log_study.log內容為:
[INFO][2021-07-29 17:23:42,804][views.py:14:logger_test] logger info 測試------------------- [WARNING][2021-07-29 17:23:42,804][views.py:15:logger_test] logger warning 測試 [ERROR][2021-07-29 17:23:42,805][views.py:16:logger_test] logger error 測試
3、自動生成新日誌檔名
-
修改日誌檔案的大小
'file': { # 這項值是修改的最多的 'level': 'INFO', # 'filters': ['require_debug_false'], 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "logging_study.log"), 'maxBytes': 1024, # 日誌大小 'backupCount': 3, 'formatter': 'standard', 'encoding': 'utf-8', },
-
檢視函式中使用logger
import logging # __name__ 模組logger,對應settings中的 名稱為:'' 的 logger logger = logging.getLogger(__name__) def index(request): logger.debug('logger debug 測試') logger.info('logger info 測試-------------------') logger.warning('logger warning 測試') for i in range(10): logger.error(f'logger error 測試{i}') return HttpResponse('歡迎學習django的日誌功能!!')
-
生成的日誌檔案
3.propagate的使用
-
修改配置檔案
'loggers': { 'logger_study': { # 部署到生產環境之後可以把'console'移除 'handlers': ['console'], 'level': 'DEBUG', }, 'logger_study.file': { # 部署到生產環境之後可以把'console'移除 'handlers': ['file'], 'level': 'DEBUG', 'propagate':False, }, },
-
檢視函式
import logging # __name__ 模組logger,對應settings中的 名稱為:'' 的 logger logger = logging.getLogger('logger_study.file') def index(request): logger.debug('logger debug 測試') logger.info('logger info 測試-------------------') logger.warning('logger warning 測試') logger.error('logger error 測試')
-
執行結果
預設propagate的值是True,會向上傳播
控制檯執行結果:
[DEBUG][2021-07-29 17:46:23,796] logger debug 測試 [INFO][2021-07-29 17:46:23,796] logger info 測試------------------- [WARNING][2021-07-29 17:46:23,797] logger warning 測試 [ERROR][2021-07-29 17:46:23,798] logger error 測試
日誌檔案
[INFO][2021-07-29 17:46:23,796][views.py:14:logger_test] logger info 測試------------------- [WARNING][2021-07-29 17:46:23,797][views.py:15:logger_test] logger warning 測試 [ERROR][2021-07-29 17:46:23,798][views.py:16:logger_test] logger error 測試
配置propagate的值是False,會發現只有檔案中有日誌資訊,控制檯沒有日誌資訊
-
RotatingFileHandler的使用
-
修改配置檔案
在handlers中新增TimedRotatingFileHandler處理器
'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "log_study.log"), 'maxBytes': 1024, # 日誌大小 50M 'backupCount': 3, 'formatter': 'standard', 'encoding': 'utf-8', }, 'timed_file': { # 這項值是修改的最多的 'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "logging_study_timed.log"), 'when': 's', 'interval': 10, 'backupCount': 5, 'formatter': 'standard', 'encoding': 'utf-8', }, }, 'loggers': { 'logger_study': { # 部署到生產環境之後可以把'console'移除 'handlers': ['console'], 'level': 'DEBUG', }, 'logger_study.file': { # 部署到生產環境之後可以把'console'移除 'handlers': ['file','timed_file'], 'level': 'DEBUG', 'propagate':False, }, },
-
檢視函式
import logging # __name__ 模組logger,對應settings中的 名稱為:'' 的 logger logger = logging.getLogger('logger_study.file') def index(request): logger.debug('logger debug 測試') logger.info('logger info 測試-------------------') logger.warning('logger warning 測試') logger.error('logger error 測試')
-
執行結果
-
-
SMTPHandler的使用
-
setting.py中的配置
'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true', 'exist_word_filter'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 這項值是修改的最多的 'level': 'INFO', # 'filters': ['require_debug_false'], 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "logging_study.log"), 'maxBytes': 1024, # 日誌大小 50M 'backupCount': 3, 'formatter': 'standard', 'encoding': 'utf-8', }, 'smtp':{ 'level': 'ERROR', 'class': 'logging.handlers.SMTPHandler', 'formatter': 'standard', 'mailhost': ('smtp.163.com', 25), 'fromaddr': 'zqy871589@163.com', 'toaddrs': ['zqy871589@163.com'], 'subject': 'logging_study系統日誌訊息', 'credentials': ('zqy871589@163.com', 'GBVSLRZPXFGATDIP') }, 'timed_file': { # 這項值是修改的最多的 'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "logging_study_timed.log"), 'when': 's', 'interval': 10, 'backupCount': 5, 'formatter': 'standard', 'encoding': 'utf-8', }, }, 'loggers': { 'logging_study': { # 部署到生產環境之後可以把'console'移除 'handlers': ['console'], 'level': 'DEBUG', }, 'logging_study.file': { # 部署到生產環境之後可以把'console'移除 # 'handlers': ['file', 'timed_file', 'smtp'], 'handlers': ['smtp'], 'level': 'DEBUG', }, },
-
執行結果
-
十六、訊號signals
1、概述
訊號可以在框架中的其他位置發生操作時通知分離的應用程式,簡而言之,就是訊號允許特定的sender通知一組receiver某些操作已經發生,這在多處程式碼和同一事件有關聯的情況下很有用。
2、內建訊號
內建訊號的完整文件:https://docs.djangoproject.com/zh-hans/2.2/ref/signals/
模型層中定義的內建訊號,在 django.db.models.signals 模組中:
-
pre_init:例項化模型時,此訊號在模型的_init_()方法的開頭髮送
此訊號傳送的引數:
- instance:模型類
- args:模型初始化的位置引數
- kwargs:模型初始化的關鍵字引數
-
post_init:和pre_init一樣,但是這個_init_()方法在方法完成時傳送
此訊號傳送的引數:
- instance:剛剛初始化完成的模型例項
-
pre_save:在模型 save()方法開始時傳送
-
post_save:在模型 save()方法完成時傳送
-
pre_delete:在模型 delete()方法開始時傳送
-
post_delete:在模型 delete()方法結束時傳送
-
m2m_changed:多對多關係中,模型更改時傳送
對web請求,也有內建訊號,在 django.core.signals 模組中
- request_started:Django開始處理HTTP請求時傳送
- request_finished:當Django完成向客戶端傳送HTTP響應時傳送
3、定義訊號
所有的訊號都是django.dispatch.Signal例項
初始化引數:providing_args=None, use_caching=False
- providing_args:訊號將為sender提供的引數名稱列表
- use_caching:預設為False,是否使用快取,設定為True,會為每個sender對應的接收器進行快取(儲存到sender_receivers_cache),呼叫 .connect() 或 .disconnect() 後會清除快取。內建訊號基本都設定為True
示例:
from django import dispatch
pizza_done = dispatch.Signal(providing_args=["toppings", "size"])
4、接收receiver
接收器可以是任何python函式或方法,引數最少必須是:sender, **kwargs,示例如下:
def my_callback(sender, **kwargs):
print("第一個接收器函式!")
def my_callback1(sender, arg1=None, **kwargs):
print("第二個接收器函式!")
引數說明:
- sender:訊號的傳送物件,其實可以是任意物件,取決於傳送時傳遞的引數
- **kwargs:訊號物件定義時的providing_args指定的引數
5、訊號註冊(連線到接收器)
有兩種方式可以將訊號連線上接收器
-
connect方法
使用 Signal.connect() 監聽訊號,傳送訊號時呼叫接收器函式,Signal是訊號物件
Signal.connect(receiver,sender = None,weak = True,dispatch_uid = None)
引數說明:
-
receiver - 將連線到此訊號的回撥函式
-
sender - 指定訊號的特定傳送者,為None就是任何傳送者都接收
-
weak - Django預設將訊號處理程式儲存為弱引用。因此,如果您的接收器是本地函式,它可能是垃圾收集。為了防止這種情況,請在呼叫訊號connect()方法時設定weak=False
-
dispatch_uid - 在可能傳送重複訊號的情況下訊號接收器的唯一識別符號,就是一個不重複的字串
示例如下:
from django.core.signals import request_finished request_finished.connect(my_callback)
-
-
receiver裝飾器
receiver(signal, **kwargs)
引數說明:
- signal:訊號物件或訊號物件列表
- **kwargs:這個裝飾器也是使用的connect方法,這個關鍵字引數就是傳遞給connect方法的關鍵字引數
receiver原始碼:
def receiver(signal, **kwargs): """ A decorator for connecting receivers to signals. Used by passing in the signal (or list of signals) and keyword arguments to connect:: @receiver(post_save, sender=MyModel) def signal_receiver(sender, **kwargs): ... @receiver([post_save, post_delete], sender=MyModel) def signals_receiver(sender, **kwargs): ... """ def _decorator(func): if isinstance(signal, (list, tuple)): for s in signal: s.connect(func, **kwargs) else: signal.connect(func, **kwargs) return func return _decorator
使用示例如下:
from django.core.signals import request_finished from django.dispatch import receiver @receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!")
-
只接收特定傳送者傳送的訊號
# 只接收特定模型MyModel傳送的訊號 @receiver(pre_save, sender=MyModel) def my_handler(sender, **kwargs): ...
-
防止重複訊號
在某些情況下,將接收器連線到訊號的程式碼可能會多次執行,這可能導致接收器功能被多次註冊,因此對於單個訊號事件被多次呼叫(例如,在儲存模型時使用訊號傳送電子郵件時),使用唯一識別符號作為dispatch_uid引數以標識接收方函式,此識別符號通常使用字串,使用後有確保接收器僅對每個唯一dispatch_uid值繫結一次訊號:
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
注意事項:
上述的訊號定義和註冊程式碼,理論上可以在任意程式碼位置,但是模型相關的訊號建議避免在應用程式的根模組及其models模組中註冊,以儘量減少 import 程式碼的副作用
通常情況下:
-
訊號定義:在對應子應用的signals模組中,如果使用connect註冊的話,receive也定義在 signals 模組中
-
訊號註冊:在對應子應用的 apps 中的 AppConfig 類中的 ready 方法中實現,示例如下:
apps.py:
class TestAppConfig(AppConfig): name = 'test_app' def ready(self): # 透過 get_model 來獲取特定模型物件,引數 MyModel 就是 模型類的名稱字串 pre_save.connect(receiver, sender=self.get_model('MyModel'))
6、訊號斷開連線
訊號要斷開連線,使用以下方法:
Signal.disconnect(receiver = None,sender = None,dispatch_uid = None)
引數說明見 Signal.connect
receiver參數列示已註冊的接收器斷開連線,當使用了dispatch_uid時,這個引數可以為None
返回值:如果有接收器被斷開連線則返回True,沒有則返回False
7、傳送訊號
兩種傳送訊號的方法:
-
Signal.send(sender, **kwargs):所有內建訊號都是使用此方法傳送資訊。這個方法不能捕獲由接收器丟擲的異常; 它只是允許錯誤向上傳播,因此,在出現錯誤時,不是所有接收器都可以被通知訊號。
-
Signal.send_robust(sender, **kwargs):捕獲從Exception類派生的所有錯誤,並確保所有接收器都收到訊號通知。如果發生錯誤,則會在引發錯誤的接收器的元組對中返回錯誤例項。正常的返回值是:[(receiver, response), ... ],response是receiver的返回值,發生錯誤時是[(receiver, err), ... ],錯誤的tracebacks儲存在 err.__traceback__屬性上
8、專案中應用訊號
1、應用內建訊號
pre_save和post_save
- 在子應用中新建signals.py
def pre_save_receive(sender, **kwargs):
print(f'模型儲存之前:{sender},kwargs:{kwargs}')
- 子應用的apps.py:
@receiver(post_save,dispatch_uid="test_app_post_save_receive")
def post_save_receive(sender, **kwargs):
print(f'模型儲存之後:{sender},kwargs:{kwargs}')
from .signals import pre_save_receive
class TestAppConfig(AppConfig):
name = 'test_app'
verbose_name = '測試應用'
def ready(self):
pre_save.connect(pre_save_receive)
- 當在admin後臺登入之後,控制檯輸出:
模型儲存之前:<class 'django.contrib.sessions.models.Session'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D414358>, 'instance': <Session: nnq90hk1vizs2ov9pulruvwvq36lhcba>, 'raw': False, 'using': 'default', 'update_fields': None}
模型儲存之後:<class 'django.contrib.sessions.models.Session'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D4142E8>, 'instance': <Session: nnq90hk1vizs2ov9pulruvwvq36lhcba>, 'created': True, 'update_fields': None, 'raw': False, 'using': 'default'}
模型儲存之前:<class 'django.contrib.auth.models.User'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D414358>, 'instance': <User: admin>, 'raw': False, 'using': 'default', 'update_fields': frozenset({'last_login'})}
模型儲存之後:<class 'django.contrib.auth.models.User'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D4142E8>, 'instance': <User: admin>, 'created': False, 'update_fields': frozenset({'last_login'}), 'raw': False, 'using': 'default'}
模型儲存之前:<class 'django.contrib.sessions.models.Session'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D414358>, 'instance': <Session: nnq90hk1vizs2ov9pulruvwvq36lhcba>, 'raw': False, 'using': 'default', 'update_fields': None}
模型儲存之後:<class 'django.contrib.sessions.models.Session'>,kwargs:{'signal': <django.db.models.signals.ModelSignal object at 0x0000017C6D4142E8>, 'instance': <Session: nnq90hk1vizs2ov9pulruvwvq36lhcba>, 'created': False, 'update_fields': None, 'raw': False, 'using': 'default'}
2、自定義訊號
-
在子應用中新建signals.py
from django.dispatch import receiver from django import dispatch # 定義訊號 index_signal = dispatch.Signal(providing_args=["request"]) # 定義接收器,使用裝飾器註冊訊號 @receiver(index_signal, dispatch_uid="test_app_pre_index_request") def pre_index_request(sender, request=None, **kwargs): print(f'訪問首頁之前:{sender}, {kwargs}') print('訪問IP:', request.META['REMOTE_ADDR'])
-
views中:
def index(request): # 傳送訊號 index_signal.send(sender='index_function', request=request) return render(request, 'index.html')
-
訪問首頁,控制檯輸出:
訪問首頁之前:index_function, {'signal': <django.dispatch.dispatcher.Signal object at 0x0000013C2B09F160>} 訪問IP: 127.0.0.1
十七、快取redis
概述
動態網站的基本權衡是,它們是動態的。每次使用者請求頁面時,Web伺服器都會進行各種計算 - 從資料庫查詢到模板呈現再到業務邏輯 - 以建立站點訪問者看到的頁面。從處理開銷的角度來看,這比標準的檔案讀取檔案系統伺服器要耗時多了。對於大多數Web應用程式來說,這種開銷並不是什麼大問題。因為大多數Web應用程式只是中小型網站,沒有擁有一流的流量。但對於中到高流量的站點,儘可能減少開銷是至關重要的,這就是快取的用武之地。快取某些內容是為了儲存昂貴計算的結果,這樣就不必在下次執行計算。
Django框架帶有一個強大的快取系統,可以儲存動態頁面,因此不必為每個請求計算它們。Django提供不同級別的快取粒度:可以快取特定檢視的輸出,也可以只快取頁面中難以生成的部分或者可以快取整個站點。
Redis,是一個記憶體資料庫(現在已經支援記憶體資料持久化到硬碟當中,重新啟動時,會自動從硬碟進行載入),由於其效能極高,因此經常作為中介軟體、快取使用。
本文件介紹就是Django框架使用Redis資料庫來應用快取框架
2、Redis
redis預設不支援windows,由於一般開發環境在windows,因為需要使用第三方團隊維護的windows版本,下載地址:
https://github.com/tporadowski/redis/releases
直接減壓壓縮包,安裝好之後,啟動redis
啟動redis:
redis-server
連線redis資料庫
redis-cli
核心配置,在redis.windows.conf下
繫結IP:如果需要遠端訪問,可以將此註釋,或繫結一個真是IP
bind 127.0.0.1
埠:預設為6379
port 6379
日誌檔案
logfile "Logs/redis_log.txt"
資料庫個數
databases 16
基本命令
檢測 redis 服務是否啟動
PING
設定鍵值對:
set uname baizhan
取出鍵值對:
get uname
刪除鍵值對:
del uname
檢視所有鍵值對:
keys *
刪除所有的鍵值對
flushall
執行結果:
3、應用redis快取
django中應用redis,目前一般使用第三方庫 django-redis
安裝:pip install django-redis
1.settings配置
CACHES = {
# default 是快取名,可以配置多個快取
"default": {
# 應用 django-redis 庫的 RedisCache 快取類
"BACKEND": "django_redis.cache.RedisCache",
# 配置正確的 ip和port
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
# redis客戶端類
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# redis連線池的關鍵字引數
"CONNECTION_POOL_KWARGS": {
"max_connections": 100
}
# 如果 redis 設定了密碼,那麼這裡需要設定對應的密碼,如果redis沒有設定密碼,那麼這裡也不設定
# "PASSWORD": "123456",
}
}
}
更多配置項:
-
LOCATION:設定連線url,譬如:"redis://127.0.0.1:6379/0",如果要設定redis主從連線,設定列表:["redis://127.0.0.1:6379/1", "redis://127.0.0.1:6378/1"],第一個連線是 master 伺服器
-
TIMEOUT:快取的超時時間,單位秒,預設是 300秒,如果為None,表示快取永不超時,如果為0,表示快取立刻超時,相當於不使用快取
-
LOCATION:支援使用 本地url符號作為連線,
支援三種 URL scheme :
- redis://: 普通的 TCP 套接字連線 - redis://[:password]@localhost:6379/0
- rediss://: SSL 包裹的 TCP 套接字連線 - rediss://[:password]@localhost:6379/0
- unix://: Unix 域套接字連線 - unix://[:password]@/path/to/socket.sock?db=0
但是密碼放在url中,不是很安全,所以建議使用示例中的方式
-
OPTIONS:
-
SOCKET_CONNECT_TIMEOUT:建立連線超時時間,單位秒
-
SOCKET_TIMEOUT:連線建立後,讀寫超時時間,單位秒
-
COMPRESSOR:預設不使用壓縮,指定壓縮的類,譬如"django_redis.compressors.zlib.ZlibCompressor"
-
IGNORE_EXCEPTIONS:預設為False,當Redis僅用於快取時,連線異常或關閉後,忽略異常,不觸發異常,可以設定為True,也可以全域性設定 DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS=True
-
PICKLE_VERSION:序列化使用的是pickle,預設情況下使用最新的pickle版本,這裡可以設定指定版本號(設定為 -1 也是指最新版本)
-
CONNECTION_POOL_CLASS:設定自定義的連線池類
-
PARSER_CLASS:redis.connection.HiredisParser,可以這樣設定,使用C寫的redis客戶端,效能更好
-
CLIENT_CLASS:設定一些特殊客戶端類,譬如:
分片客戶端:
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": [ "redis://127.0.0.1:6379/1", "redis://127.0.0.1:6379/2", ], "OPTIONS": { "CLIENT_CLASS": "django_redis.client.ShardClient", } } }
叢集客戶端:
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": [ "redis://127.0.0.1:6379/1", ], "OPTIONS": { "CLIENT_CLASS": "django_redis.client.HerdClient", } } }
-
SERIALIZER:設定序列化,如 "django_redis.serializers.json.JSONSerializer"
-
全域性配置,即設定在settings最外層的配置項:
# 給所有快取配置相同的忽略行為
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
# 設定指定的 logger 輸出日誌, 需要設定logger
DJANGO_REDIS_LOGGER = 'some.specified.logger'
2.手動操作redis
透過配置獲取django_redis的get_redis_connection,進行操作,如下:
from django_redis import get_redis_connection
conn = get_redis_connection("default") # redis.client.StrictRedis
# 支援所有redis的介面
conn.hset('hash_test','k1','v1')
# 也可以手動將資料清除
get_redis_connection("default").flushall()
# 得知連線池的連線數
get_redis_connection("default").connection_pool
執行結果
3.全站快取
主要使用兩個中介軟體實現:
-
FetchFromCacheMiddleware :從快取中讀取資料
- 快取狀態為200的GET和HEAD請求的響應(除非響應頭中設定不進行快取)
- 對具有不同查詢引數的相同URL的請求的響應被認為是各自不同的頁面,並且被分別單獨快取。
- 該中介軟體會使用與對應的GET請求相同的響應頭來回答HEAD請求,即可以為HEAD請求返回快取的GET響應。
-
UpdateCacheMiddleware :將資料更新到快取中
-
該中介軟體會自動在每個響應中設定幾個headers:
- 設定Expires為當前日期/時間 加上 定義的CACHE_MIDDLEWARE_SECONDS值,GMT時間
- 設定響應的Cache-Control的max-age,值是定義的CACHE_MIDDLEWARE_SECONDS值。
-
如果檢視設定了自己的快取時間(即設定了Cache-Control 的max age),那麼頁面將被快取直到到期時間,而不是CACHE_MIDDLEWARE_SECONDS。
使用裝飾器 django.views.decorators.cache可以設定檢視的到期時間(使用cache_control()裝飾器,程式碼:@cache_control(max_age=3600))或禁用檢視的快取(使用never_cache()裝飾器,程式碼:@never_cache)
-
如果USE_I18N設定為True,則生成的快取key將包含當前語言的名稱,這樣可以輕鬆快取多語言網站,而無需自己建立快取金鑰。
-
如果 USE_L10N設定為True 並且 USE_TZ被設定為True,快取key也會包括當前語言
-
在settings的中介軟體中設定:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
# 其他中介軟體...
'django.middleware.cache.FetchFromCacheMiddleware',
]
PS:UpdateCacheMiddleware必須是第一個中介軟體,FetchFromCacheMiddleware必須是最後一箇中介軟體
然後,將以下必需設定新增到Django的settings檔案中:
- CACHE_MIDDLEWARE_ALIAS - 用於儲存的快取別名。
- CACHE_MIDDLEWARE_SECONDS - 每個頁面應快取的秒數。
- CACHE_MIDDLEWARE_KEY_PREFIX - 用於生成快取key的字首,如果使用相同的Django安裝在多個站點之間共享快取,請將其設定為站點名稱或此Django例項特有的其他字串,以防止發生金鑰衝突。如果你不在乎,請使用空字串。
在檢視中:
def index(request):
# 透過設定時間戳,進行多次訪問,可以看到時間戳的變化,就可以得知是否是快取頁面了
return HttpResponse('歡迎光臨,當前時間戳:' + str(time.time()))
4.檢視函式快取
-
透過裝飾器cache_page
from django.views.decorators.cache import cache_page @cache_page(60 * 15) def index(request): ...
說明:
-
cache_page除了預設的timeout引數外,還有兩個可選的關鍵字引數
- cache,示例程式碼:@cache_page(60 * 15, cache="special_cache"), 該cache指向settings中配置的快取的名稱,預設是"default"
- key_prefix:快取key的字首,與CACHE_MIDDLEWARE_KEY_PREFIX功能相同
-
如果多個url指向同一個檢視函式,會為每個url建立一個單獨的快取,例如:
urlpatterns = [ path('foo/<int:code>/', views.index), ]
/foo/1/ 和/foo/23/ 請求會分別進行快取
-
-
透過urls中配置cache_page
在URLconf中指定檢視快取,而不是在檢視函式上硬編碼裝飾器,可以進一步解耦快取和檢視函式之間的關係,使用起來更靈活
from django.views.decorators.cache import cache_page urlpatterns = [ path('index/', cache_page(60 * 15)(views.index)), ]
5.模板檔案快取
使用cache模板標記快取模板片段
-
引入TemplateTag
{% load cache %}
-
使用快取
{% cache 5000 cache_key %} 快取內容 {% endcache %}
說明:
cache最少兩個引數:
-
5000: 快取超時時間,單位秒,如果為None,那麼就是永久快取
-
cache_key:快取的key,不能使用變數,只是一個字串(不要引號),相當於CACHE_MIDDLEWARE_KEY_PREFIX
可以透過將一個或多個附加引數(可以是帶或不帶過濾器的變數,變數個數可以是多個,如:{% cache 500 sidebar var1 var2 var3 ... %})傳遞給 cache 來唯一標識快取片段來執行此操作,示例如下:
{% load cache %} {% cache 500 sidebar request.user.username %} 指定登入使用者的側邊欄 {% endcache %}
如果USE_I18N設定為True,每站點中介軟體快取將根據語言進行區分,對於cache模板標記,可以使用模板中可用的特定於 轉換的變數 來實現相同的結果,示例如下:
{% load i18n %} {% load cache %} {% get_current_language as LANGUAGE_CODE %} {% cache 600 welcome LANGUAGE_CODE %} {% trans "Welcome to example.com" %} {% endcache %}
快取超時可以是模板變數,使用變數可以避免多次使用同一個值,示例(假設my_timeout設定為 600):
{% cache my_timeout sidebar %} ... {% endcache %}
預設情況下,cache將嘗試使用名為“template_fragments”的快取。如果不存在此快取,則使用預設的default快取。可以透過using關鍵字引數指定使用的快取,該關鍵字引數必須是標記的最後一個引數,示例:
{% cache 300 cache_key ... using="localcache" %}
PS:指定不存在的 快取名 會報錯
-
6.低階快取
有時不想快取整個頁面資料,而只是想快取某些費時查詢並且基本不會改變的資料,可以透過一個簡單的低階快取API實現,該API可以快取任何可以安全pickle的Python物件:字串,字典,模型物件列表等
-
django.core.cache.caches
from django.core.cache import caches cache1 = caches['myalias'] cache2 = caches['myalias'] # 判斷為True if cache1 is cache2: ...
說明:
-
可以透過CACHES類似字典一樣的方式訪問settings中配置的快取,在同一個執行緒中重複請求相同的別名將返回相同的物件
-
如果指定的 myalias 不存在,將引發 InvalidCacheBackendError
-
為了執行緒安全性,為會每個執行緒返回快取的不同例項
-
作為快捷方式, 預設快取(default)可以使用 django.core.cache.cache :
# 使用 default 快取 from django.core.cache import cache # 上面的cache等同於下面的寫法 from django.core.cache import caches cache = caches['default']
-
-
django.core.cache.cache
from django.core.cache import cache
# 使用 redis 的一般用法
cache.set('manul_set', 'ok')
manul_set = cache.get('manul_set')
# 可以手動設定 timeout,如果不指定timeout,預設是 300秒
cache.set("key", "value", timeout=None)
# 可以獲取key的超時設定(ttl:time to live)
# 返回值的3種情況:
# 0: key 不存在 (或已過期)
# None: key 存在但沒有設定過期
# ttl: 任何有超時設定的 key 的超時值
cache.set("foo", "value", timeout=25)
cache.ttl("foo") # 得到 25
cache.ttl("not-existent") # 得到 0
# 讓一個值永久存在
cache.persist("foo")
cache.ttl("foo") # 得到 None
# 指定一個新的過期時間
cache.set("foo", "bar", timeout=22)
cache.ttl("foo") # 得到 22
cache.expire("foo", timeout=5)
cache.ttl("foo") # 得到 5
# 支援 redis 分散式鎖, 使用 上下文管理器 分配鎖
with cache.lock("somekey"):
do_some_thing()
# 使用全域性萬用字元的方式來檢索或者刪除鍵
cache.keys("foo_*") # 返回所有匹配的值, 如 ["foo_1", "foo_2"]
# 使用 iter_keys 取代keys 得到 一個迭代器
cache.iter_keys("foo_*") # 得到一個迭代器
next(cache.iter_keys("foo_*")) # 得到 foo_1
# 刪除 鍵
cache.delete_pattern("foo_*") # 支援萬用字元
7.低階快取的應用
-
檢視函式
def get_result(): # 做一些費時,但是不經常變更的資料查詢,運算等 time.sleep(5) return 'lower level cache is ok!!!' def lower_level_cache(request): # 需要獲取一個統計資料 result = get_cache_or_exc_func('lower_level_cache', get_result) return JsonResponse({'result': result})
-
快取函式
在公共包中建立py檔案,定義快取函式
from django.core.cache import cache def get_cache_or_exc_func(key, func, *args, **kwargs): """ 根據傳入的key和func,先獲取快取的內容,沒有則使用func計算,並儲存 :param key: 快取的key :param func: 計算函式 :param args: 可變引數 :param kwargs: 可變關鍵字引數 :return: 快取的內容或func計算的結果 """ # 加上cache鎖 with cache.lock(key+'_lock'): # 獲取快取中的變數 result = cache.get(key) # 判斷快取中的變數是否存在 if result: # 直接返回快取的結果 return result # 快取中不存在 else: # 計算資料,得到結果 result = func(*args, **kwargs) # 將結果儲存到快取中 cache.set(key, result) # 返回計算的結果 return result
8.session快取
# 配置session的引擎為cache
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 此處別名依賴快取的設定
SESSION_CACHE_ALIAS = 'default'
-
路由配置
app_name = 'redis_app' path('login/',views.login,name='login'), path('index/',views.index,name='index')
-
檢視函式
def index(request): uname = request.session.get('uname') if uname == 'root': return render(request,'redis_app/index.html',{'uname':uname}) else: return redirect('redis_app:login') def login(request): if request.method == 'GET': return render(request,'redis_app/login.html') elif request.method == 'POST': #獲取使用者名稱和密碼 uname = request.POST.get('uname') password= request.POST.get('password') print('uname:',uname,'password:',password) if uname == 'root' and password == 'root':#登入成功 #將使用者名稱存放到session中 request.session['uname'] = uname #跳轉到主頁 return redirect('redis_app:index') else: return redirect('redis_app:login')
-
頁面
login.html頁面
<form action="{%url 'redis_app:login'%}" method="POST"> {% csrf_token %} <p>使用者名稱:<input type = "text" name ="uname" ></p> <p>密碼:<input type = "password" name ="password" ></p> <p><input type="submit" value="登入"></p> </form>
index.html頁面
<p>歡迎{{uname}}登入</p>
-
執行結果
十八、Celery
1、概述
1.Celery介紹
Celery是由Python開發、簡單、靈活、可靠的分散式任務佇列,是一個處理非同步任務的框架,其本質是生產者消費者模型,生產者傳送任務到訊息佇列,消費者負責處理任務。Celery側重於實時操作,但對排程支援也很好,其每天可以處理數以百萬計的任務。特點:
- 簡單:熟悉celery的工作流程後,配置使用簡單
- 高可用:當任務執行失敗或執行過程中發生連線中斷,celery會自動嘗試重新執行任務
- 快速:一個單程序的celery每分鐘可處理上百萬個任務
- 靈活:幾乎celery的各個元件都可以被擴充套件及自定製
Celery由三部分構成:
-
訊息中介軟體(Broker):官方提供了很多備選方案,支援RabbitMQ、Redis、Amazon SQS、MongoDB、Memcached 等,官方推薦RabbitMQ
-
任務執行單元(Worker):任務執行單元,負責從訊息佇列中取出任務執行,它可以啟動一個或者多個,也可以啟動在不同的機器節點,這就是其實現分散式的核心
-
結果儲存(Backend):官方提供了諸多的儲存方式支援:RabbitMQ、 Redis、Memcached,SQLAlchemy, Django ORM、Apache Cassandra、Elasticsearch等
架構如下:
工作原理:
- 任務模組Task包含非同步任務和定時任務。其中,非同步任務通常在業務邏輯中被觸發併發往訊息佇列,而定時任務由Celery Beat程序週期性地將任務發往訊息佇列;
- 任務執行單元Worker實時監視訊息佇列獲取佇列中的任務執行;
- Woker執行完任務後將結果儲存在Backend中;
2.django應用Celery
django框架請求/響應的過程是同步的,框架本身無法實現非同步響應。
但是我們在專案過程中會經常會遇到一些耗時的任務, 比如:傳送郵件、傳送簡訊、大資料統計等等,這些操作耗時長,同步執行對使用者體驗非常不友好,那麼在這種情況下就需要實現非同步執行。
非同步執行前端一般使用ajax,後端使用Celery。
2、專案應用
django專案應用celery,主要有兩種任務方式,一是非同步任務(釋出者任務),一般是web請求,二是定時任務
本文件使用redis資料庫作為訊息中介軟體和結果儲存資料庫
環境如下:
- celery4.4.7
- redis3.5.3
PS:本文僅適用celery庫進行學習,另外有一些第三方庫可以提供更方便的操作,譬如:django-celery,django-celery-beat等
1.非同步任務redis
1.安裝庫
pip install celery
2.celery.py
在主專案目錄下,新建 celery.py 檔案:
import os
import django
from celery import Celery
from django.conf import settings
# 設定系統環境變數,安裝django,必須設定,否則在啟動celery時會報錯
# celery_study 是當前專案名
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_study.settings')
django.setup()
#例項化一個celery類
celery_app = Celery('celery_study')
#指定配置檔案的位置
celery_app.config_from_object('django.conf:settings')
#自動從settings的配置INSTALLED_APPS中的應用目錄下載入 tasks.py
celery_app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
PS:是和settings.py檔案同目錄,一定不能建立在專案根目錄,不然會引起 celery 這個模組名的命名衝突
同時,在主專案的init.py中,新增如下程式碼:
from .celery import celery_app
__all__ = ['celery_app']
3.settings.py
在配置檔案中配置對應的redis配置:
# Broker配置,使用Redis作為訊息中介軟體
BROKER_URL = 'redis://127.0.0.1:6379/0'
# BACKEND配置,這裡使用redis
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
# 結果序列化方案
CELERY_RESULT_SERIALIZER = 'json'
# 任務結果過期時間,秒
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
# 時區配置
CELERY_TIMEZONE='Asia/Shanghai'
# 指定匯入的任務模組,可以指定多個
#CELERY_IMPORTS = (
# 'other_dir.tasks',
#)
PS:所有配置的官方文件:http://docs.celeryproject.org/en/latest/userguide/configuration.html
4.tasks.py
在子應用下建立各自對應的任務檔案tasks.py(必須是tasks.py這個名字,不允許修改)
from celery import shared_task
@shared_task
def add(x, y):
return x + y
@shared_task
def mul(x, y):
return x * y
@shared_task
def xsum(numbers):
return sum(numbers)
5.呼叫任務
在 views.py 中,透過 delay 方法呼叫任務,並且返回任務對應的 task_id,這個id用於後續查詢任務狀態
from . import tasks
def mul_func(request):
ar = tasks.mul.delay(10, 3)
return HttpResponse('返回mul任務,task_id:'+ ar.id)
6.啟動celery
在cmd視窗中,切換到專案根目錄下,執行:
celery worker -A celery_study -l info
說明:
- -A celery_study:指定專案
- worker: 表明這是一個任務執行單元
- -l info:指定日誌輸出級別
更多celery命令的引數,可以輸入:
celery --help
或
celery worker --help
異常處理:
- win10平臺,使用celery4.x時,會出現以下錯誤:
ValueError: not enough values to unpack (expected 3, got 0)
解決方法:
- 先安裝一個擴充套件 eventlet
pip install eventlet
- 然後啟動worker的時候加一個引數 -P eventlet,如下:
celery worker -A celery_study -l debug -P eventlet
- 使用redis時,有可能會出現如下類似的異常
AttributeError: 'str' object has no attribute 'items'
這是由於版本差異,需要解除安裝已經安裝的python環境中的 redis 庫,重新指定安裝特定版本(celery4.x以下適用 redis2.10.6, celery4.3以上使用redis3.2.0以上):
pip install redis==2.10.6
7.獲取任務結果
在 views.py 中,透過 AsyncResult.get() 獲取結果
def get_result_by_taskid(request):
task_id = request.GET.get('task_id')
ar = result.AsyncResult(task_id)
if ar.ready():
return JsonResponse({'status': ar.state, 'result': ar.get()})
else:
return JsonResponse({'status': ar.state, 'result': ''})
AsyncResult類的常用的屬性和方法:
-
state: 返回任務狀態,等同status;
-
task_id: 返回任務id;
-
result: 返回任務結果,同get()方法;
-
ready(): 判斷任務是否執行以及有結果,有結果為True,否則False;
-
info(): 獲取任務資訊,預設為結果;
-
wait(t): 等待t秒後獲取結果,若任務執行完畢,則不等待直接獲取結果,若任務在執行中,則wait期間一直阻塞,直到超時報錯;
-
successful(): 判斷任務是否成功,成功為True,否則為False;
2.定時任務
在第一步的非同步任務的基礎上,進行部分修改即可
1.settings.py
from celery.schedules import crontab
CELERYBEAT_SCHEDULE = {
'add_every_30_seconds': {
# 任務路徑
'task': 'celery_app.tasks.add',
# 每30秒執行一次
'schedule': 30,
'args': (14, 5)
},
'xsum_week1_20_20_00': {
# 任務路徑
'task': 'celery_app.tasks.xsum',
# 每週一20點20分執行
'schedule': crontab(hour=20, minute=20, day_of_week=1),
'args': ([1,2,3,4],),
},
}
說明(更多內容見文件:http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#crontab-schedules):
- task:任務函式
- schedule:執行頻率,可以是整型(秒數),也可以是timedelta物件,也可以是crontab物件,也可以是自定義類(繼承celery.schedules.schedule)
- args:位置引數,列表或元組
- kwargs:關鍵字引數,字典
- options:可選引數,字典,任何 apply_async() 支援的引數
- relative:預設是False,取相對於beat的開始時間;設定為True,則取設定的timedelta時間
2.啟動celery
分別啟動worker和beat
celery worker -A celery_study -l debug -P eventlet
celery beat -A celery_study -l debug
3.任務繫結
Celery可透過task繫結到例項獲取到task的上下文,這樣我們可以在task執行時候獲取到task的狀態,記錄相關日誌等
程式碼如下:
@shared_task(bind=True)
def mul(self, x, y):
logger.info('-mul'*10)
logger.info(self.name)
logger.info(dir(self))
return x * y
說明:
-
在裝飾器中加入引數 bind=True
-
在task函式中的第一個引數設定為self
self物件是celery.app.task.Task的例項,可以用於實現重試等多種功能
@shared_task(bind=True) def mul(self, x, y): try: logger.info('-mul' * 10) logger.info(f'{self.name}, id:{self.request.id}') raise Exception except Exception as e: # 出錯每4秒嘗試一次,總共嘗試4次 self.retry(exc=e, countdown=4, max_retries=4) return x * y
4.任務鉤子
Celery在執行任務時,提供了鉤子方法用於在任務執行完成時候進行對應的操作,在Task原始碼中提供了很多狀態鉤子函式如:on_success(成功後執行)、on_failure(失敗時候執行)、on_retry(任務重試時候執行)、after_return(任務返回時候執行)
- 透過繼承Task類,重寫對應方法即可,示例:
class MyHookTask(Task):
def on_success(self, retval, task_id, args, kwargs):
logger.info(f'task id:{task_id} , arg:{args} , successful !')
def on_failure(self, exc, task_id, args, kwargs, einfo):
logger.info(f'task id:{task_id} , arg:{args} , failed ! erros: {exc}')
def on_retry(self, exc, task_id, args, kwargs, einfo):
logger.info(f'task id:{task_id} , arg:{args} , retry ! erros: {exc}')
- 在對應的task函式的裝飾器中,透過 base=MyHookTask 指定
@shared_task(base=MyHookTask, bind=True)
def mul(self, x, y):
......
5.任務編排
在很多情況下,一個任務需要由多個子任務或者一個任務需要很多步驟才能完成,Celery也能實現這樣的任務,完成這型別的任務透過以下模組完成:
- group: 並行排程任務
- chain: 鏈式任務排程
- chord: 類似group,但分header和body2個部分,header可以是一個group任務,執行完成後呼叫body的任務
- map: 對映排程,透過輸入多個入參來多次排程同一個任務
- starmap: 類似map,入參類似*args
- chunks: 將任務按照一定數量進行分組
文件:https://docs.celeryproject.org/en/latest/getting-started/next-steps.html#canvas-designing-work-flows
1.group
urls.py:
path('primitive/', views.test_primitive),
views.py:
def test_primitive(request):
# 建立10個並列的任務
lazy_group = group(tasks.add.s(i, i) for i in range(10))
promise = lazy_group()
result = promise.get()
return JsonResponse({'function': 'test_primitive', 'result': result})
說明:
透過task函式的 s 方法傳入引數,啟動任務
上面這種方法需要進行等待,如果依然想實現非同步的方式,那麼就必須在tasks.py中新建一個task方法,呼叫group,示例如下:
tasks.py:
@shared_task
def group_task(num):
return group(add.s(i, i) for i in range(num))().get()
urls.py:
path('first_group/', views.first_group),
views.py:
def first_group(request):
ar = tasks.group_task.delay(10)
return HttpResponse('返回first_group任務,task_id:' + ar.task_id)
2.chain
預設上一個任務的結果作為下一個任務的第一個引數
def test_primitive(request):
# 等同呼叫 mul(add(add(2, 2), 5), 8)
promise = chain(tasks.add.s(2, 2), tasks.add.s(5), tasks.mul.s(8))()
# 72
result = promise.get()
return JsonResponse({'function': 'test_primitive', 'result': result})
3.chord
任務分割,分為header和body兩部分,hearder任務執行完在執行body,其中hearder返回結果作為引數傳遞給body
def test_primitive(request):
# header: [3, 12]
# body: xsum([3, 12])
promise = chord(header=[tasks.add.s(1,2),tasks.mul.s(3,4)],body=tasks.xsum.s())()
result = promise.get()
return JsonResponse({'function': 'test_primitive', 'result': result})
6、celery管理和監控
celery透過flower元件實現管理和監控功能 ,flower元件不僅僅提供監控功能,還提供HTTP API可實現對woker和task的管理
官網:https://pypi.org/project/flower/
文件:https://flower.readthedocs.io/en/latest
-
安裝flower
pip install flower
-
啟動flower
flower -A celery_study --port=5555
說明:
- -A:專案名
- --port: 埠號
-
訪問
在瀏覽器輸入:http://127.0.0.1:5555
十九、DebugToolBar
1、概述
Django框架的除錯工具欄使用django-debug-toolbar庫,是一組可配置的皮膚,顯示有關當前請求/響應的各種除錯資訊,點選時,顯示有關皮膚內容的更多詳細資訊。
官方文件:https://django-debug-toolbar.readthedocs.io/en/latest/
2、應用
1.安裝
pip install django-debug-toolbar
2.settings.py配置
先決條件:必須確認django.contrib.staticfiles 正確安裝並且啟用
INSTALLED_APPS = [
# ...
'django.contrib.staticfiles',
# ...
'debug_toolbar',
]
STATIC_URL = '/static/'
3.urls.py路由
在主應用下的根urls.py中的最下面新增如下程式碼:
from django.conf import settings
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
] + urlpatterns
說明:
- 這裡使用 '_debug_' 作為路徑訪問,可以設定任意的路徑名,只要能輕易區分一般應用
- 如果放在子應用的urls.py下的話,會丟擲NoReverseMatch 'djdt' is not a registered namespace異常
4.啟用中介軟體
除錯工具欄主要在中介軟體中實現:
MIDDLEWARE = [
# ...
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ...
]
PS:這個中介軟體儘可能配置到最前面,但是,必須要要放在處理編碼和響應內容的中介軟體後面,比如我們要是使用了GZipMiddleware,就要把DebugToolbarMiddleware放在GZipMiddleware後面
5.設定內部IP
除錯工具欄只會允許特定的ip訪問,在settings的INTERNAL_IPS中配置
INTERNAL_IPS = [
'127.0.0.1',
]
6.訪問
訪問應用的任意頁面,在頁面的右上角會有一個 DJDT的懸浮窗
PS:如果訪問的頁面是如下編寫的程式碼,則不會出現懸浮窗,因為內容中沒有 body 標籤
def my_view(request):
# ......
return render(request,'toolbar_app/index.html')
7.皮膚功能
- History:訪問歷史資訊
- Versions :代表是哪個django版本
- Timer : 用來計時的,判斷載入當前頁面總共花的時間
- Settings : 讀取django中的配置資訊
- Headers : 當前請求頭和響應頭資訊
- Request: 當前請求的相關資訊(檢視函式,Cookie資訊,Session資訊等)
- SQL:檢視當前介面執行的SQL語句
- StaticFiles:當前介面載入的靜態檔案
- Templates:當前介面用的模板
- Cache:快取資訊
- Signals:訊號
- Logging:當前介面日誌資訊
- Redirects:當前介面的重定向資訊
- Profiling:檢視檢視函式的資訊
8.皮膚配置
django-debug-toolbar預設使用全皮膚,見第7點,
預設的全域性配置在 debug_toolbar.settings.CONFIG_DEFAULTS
預設的皮膚配置在 debug_toolbar.settings.PANELS_DEFAULTS
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.history.HistoryPanel',
'debug_toolbar.panels.versions.VersionsPanel',
'debug_toolbar.panels.timer.TimerPanel',
'debug_toolbar.panels.settings.SettingsPanel',
'debug_toolbar.panels.headers.HeadersPanel',
'debug_toolbar.panels.request.RequestPanel',
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
'debug_toolbar.panels.templates.TemplatesPanel',
'debug_toolbar.panels.cache.CachePanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
'debug_toolbar.panels.profiling.ProfilingPanel',
]
如果不使用預設的全功能皮膚,那麼在settings中配置 DEBUG_TOOLBAR_PANELS 即可,示例如下:
DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.timer.TimerPanel",
"debug_toolbar.panels.headers.HeadersPanel",
"debug_toolbar.panels.request.RequestPanel",
"debug_toolbar.panels.templates.TemplatesPanel",
]
說明:
- 當前只啟用了4個皮膚:時間、頭資訊、請求資訊和模板資訊
- 可以新增自定義皮膚
- 可以刪除預設內建皮膚
- 可以改變皮膚的順序
9.工具欄配置
在settings中配置 DEBUG_TOOLBAR_CONFIG 覆蓋預設配置,分為2部分,一部分適用於工具欄本身,另一部分適用於某些特定皮膚
DEBUG_TOOLBAR_CONFIG = {
# Toolbar options
"DISABLE_PANELS": {"debug_toolbar.panels.redirects.RedirectsPanel"},
"INSERT_BEFORE": "</body>",
"RENDER_PANELS": None,
"RESULTS_CACHE_SIZE": 10,
"ROOT_TAG_EXTRA_ATTRS": "",
"SHOW_COLLAPSED": False,
"SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar",
# Panel options
"EXTRA_SIGNALS": [],
"ENABLE_STACKTRACES": True,
"HIDE_IN_STACKTRACES": (
"socketserver" if six.PY3 else "SocketServer",
"threading",
"wsgiref",
"debug_toolbar",
"django.db",
"django.core.handlers",
"django.core.servers",
"django.utils.decorators",
"django.utils.deprecation",
"django.utils.functional",
),
"PROFILER_MAX_DEPTH": 10,
"SHOW_TEMPLATE_CONTEXT": True,
"SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"),
"SQL_WARNING_THRESHOLD": 500, # milliseconds
}
工具欄選項
-
DISABLE_PANELS
預設:
{'debug_toolbar.panels.redirects.RedirectsPanel'}
此設定是要禁用(但仍顯示)的皮膚的完整Python路徑的集合。
-
INSERT_BEFORE
預設:
'</body>'
工具欄在HTML中搜尋此字串並在之前插入。
-
RENDER_PANELS
預設:
None
如果設定為
False
,除錯工具欄將把皮膚的內容保留在伺服器上的記憶體中並按需載入它們。如果設定為True
,則會在每個頁面內呈現皮膚。這可能會降低頁面呈現速度,但在多程序伺服器上需要這樣做,例如,如果在生產中部署工具欄(不建議這樣做)。預設值
None
告訴工具欄自動執行正確的操作,具體取決於WSGI容器是否執行多個程序。此設定允許您在需要時強制執行不同的操作。 -
RESULTS_CACHE_SIZE
預設:
10
工具欄在記憶體中保持的結果快取數量。
-
ROOT_TAG_EXTRA_ATTRS
預設:
''
此設定將注入根模板div中,以避免與客戶端框架發生衝突。例如,將除錯工具欄與Angular.js一起使用時,將其設定為
'ng-non-bindable'
或'class="ng-non-bindable"'
。 -
SHOW_COLLAPSED
預設:
False
如果更改為
True
,則預設情況下將摺疊工具欄。 -
SHOW_TOOLBAR_CALLBACK
預設:
'debug_toolbar.middleware.show_toolbar'
這是用於確定工具欄是否應顯示的函式路徑,預設檢測DEBUG設定為True,並且訪問IP必須在INTERNAL_IPS中,程式碼如下:
def show_toolbar(request): """ Default function to determine whether to show the toolbar on a given page. """ if request.META.get("REMOTE_ADDR", None) not in settings.INTERNAL_IPS: return False return bool(settings.DEBUG)
可以設定自定義的檢測函式路徑
皮膚選項
-
EXTRA_SIGNALS
預設:
[]
皮膚:訊號
可能在專案中的自定義訊號列表,定義為訊號的Python路徑。
-
ENABLE_STACKTRACES
預設:
True
皮膚:快取,SQL
如果設定為
True
,則將顯示SQL查詢和快取呼叫的堆疊跟蹤。啟用堆疊跟蹤會增加執行查詢時使用的CPU時間。 -
HIDE_IN_STACKTRACES
預設值:('socketserver', 'threading', 'wsgiref', 'debug_toolbar', 'django')`
皮膚:快取,SQL
用於消除與伺服器相關的堆疊跟蹤,這可能導致巨大的DOM結構和工具欄渲染延遲。
-
PROFILER_MAX_DEPTH
預設:
10
皮膚:剖析
此設定會影響分析器分析中的函式呼叫深度。
-
SHOW_TEMPLATE_CONTEXT
預設:
True
皮膚:模板
如果設定為
True
則模板的上下文將包含在模板除錯皮膚中。如果專案中擁有大型模板上下文,或者具有不希望被評估的惰性資料結構的模板上下文,則關閉此選項非常有用。 -
SKIP_TEMPLATE_PREFIXES
預設:
('django/forms/widgets/', 'admin/widgets/')
皮膚:模板
收集渲染的模板和上下文時,將跳過以這些字串開頭的模板。預設情況下會跳過基於模板的表單小部件,因為皮膚的HTML可以輕鬆地增長到數百兆位元組,包含許多表單欄位和許多選項。
-
SQL_WARNING_THRESHOLD
預設:
500
皮膚:SQL
SQL皮膚突出顯示執行時間超過這段時間(以毫秒為單位)的查詢