Django 一、Django介紹 1.1 簡介 Django是python語言中的一個web框架,Python語言中主流的web框架有Django、Tornado、Flask 等多種。Django相較與其它WEB框架,其優勢為:大而全,框架本身集成了ORM、模型綁定、模板引擎、緩存、Session ...
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='[email protected]',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)
就是在視圖裡使用類處理請求
# url