西安最好的網(wǎng)站建設(shè)公司品牌推廣思路
????????上一節(jié)提到了Django是基于MVC架構(gòu)的Web框架,MVC架構(gòu)追求的是“模型”和“視圖”的解耦合。所謂“模型”說(shuō)得更直白一些就是數(shù)據(jù)(的表示),所以通常也被稱作“數(shù)據(jù)模型”。在實(shí)際的項(xiàng)目中,數(shù)據(jù)模型通常通過(guò)數(shù)據(jù)庫(kù)實(shí)現(xiàn)持久化操作,而關(guān)系型數(shù)據(jù)庫(kù)在過(guò)去和當(dāng)下都是持久化的首選方案,下面我們通過(guò)完成一個(gè)投票項(xiàng)目來(lái)講解和模型相關(guān)的知識(shí)點(diǎn)。投票項(xiàng)目的首頁(yè)會(huì)展示某在線教育平臺(tái)所有的學(xué)科;點(diǎn)擊學(xué)科可以查看到該學(xué)科的老師及其信息;用戶登錄后在查看老師的頁(yè)面為老師投票,可以投贊成票和反對(duì)票;未登錄的用戶可以通過(guò)登錄頁(yè)進(jìn)行登錄;尚未注冊(cè)的用戶可以通過(guò)注冊(cè)頁(yè)輸入個(gè)人信息進(jìn)行注冊(cè)。在這個(gè)項(xiàng)目中,我們使用MySQL數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)數(shù)據(jù)持久化操作。
1 ORM模型
1.1 ORM介紹
對(duì)象關(guān)系映射(Object Relational Mapping,簡(jiǎn)稱ORM)模式是一種為了解決面向?qū)ο笈c關(guān)系數(shù)據(jù)庫(kù)存在的互不匹配的現(xiàn)象的技術(shù)。
????????簡(jiǎn)單的說(shuō),ORM 是通過(guò)使用描述對(duì)象和數(shù)據(jù)庫(kù)之間映射的元數(shù)據(jù),將程序中的對(duì)象自動(dòng)持久化到關(guān)系數(shù)據(jù)庫(kù)中。ORM 在業(yè)務(wù)邏輯層和數(shù)據(jù)庫(kù)層之間充當(dāng)了橋梁的作用。ORM 解決的主要問(wèn)題是對(duì)象和關(guān)系的映射。它通常把一個(gè)類和一個(gè)表一一對(duì)應(yīng),類的每個(gè)實(shí)例對(duì)應(yīng)表中的一條記錄,類的每個(gè)屬性對(duì)應(yīng)表中的每個(gè)字段,具體如下圖所示。ORM 提供了對(duì)數(shù)據(jù)庫(kù)的映射,不用直接編寫 SQL 代碼,只需像操作對(duì)象一樣從數(shù)據(jù)庫(kù)操作數(shù)據(jù)。讓軟件開發(fā)人員專注于業(yè)務(wù)邏輯的處理,提高了開發(fā)效率。

????????ORM 模式也是有一定缺點(diǎn)的,它會(huì)在一定程度上犧牲程序的執(zhí)行效率。此外,還存在許多復(fù)雜場(chǎng)景是 ORM 模式無(wú)法解決的,同樣還是需要手動(dòng)編寫 SQL 語(yǔ)句完成。
1.2 創(chuàng)建 Django 項(xiàng)目
????????首先創(chuàng)建Django項(xiàng)目vote,在項(xiàng)目下創(chuàng)建名為polls的應(yīng)用和保存模板頁(yè)的文件夾tempaltes,項(xiàng)目文件夾的結(jié)構(gòu)如下所示。


????????根據(jù)上面描述的項(xiàng)目需求,這里準(zhǔn)備了四個(gè)靜態(tài)頁(yè)面,分別是展示學(xué)科的頁(yè)面subjects.html
,顯示學(xué)科老師的頁(yè)面teachers.html
,登錄頁(yè)面login.html
,注冊(cè)頁(yè)面register.html
,稍后我們會(huì)將靜態(tài)頁(yè)修改為Django項(xiàng)目所需的模板頁(yè)。
1.3 數(shù)據(jù)庫(kù)中生成模型表
????????在 Django 中,一個(gè)模型(model)會(huì)映射到一個(gè)數(shù)據(jù)庫(kù)表。每個(gè)模型都是一個(gè) Python 類,它是django.db.models.Model
的子類,模型的每個(gè)屬性都代表一個(gè)數(shù)據(jù)庫(kù)字段。
(1) 在 polls 中添加數(shù)據(jù)模型
????????在 polls 的 models.py
中添加如下代碼:
from django.db import models # 引入Django.db.models模塊class Subject(models.Model):"""編寫Subject模型類,數(shù)據(jù)模型應(yīng)該繼承于models.Model或其子類"""no = models.AutoField(primary_key=True, verbose_name='編號(hào)')name = models.CharField(max_length=50, verbose_name='名稱')intro = models.CharField(max_length=1000, verbose_name='介紹')is_hot = models.BooleanField(verbose_name='是否熱門')def __str__(self):return self.nameclass Meta:# 通過(guò)db_table自定義數(shù)據(jù)表名db_table = 'tb_subject'class Teacher(models.Model):"""編寫Teacher模型類,數(shù)據(jù)模型應(yīng)該繼承于models.Model或其子類"""sex_choices = ((0, '女'),(1, '男'),)no = models.AutoField(primary_key=True, verbose_name='編號(hào)')name = models.CharField(max_length=20, verbose_name='姓名')sex = models.BooleanField(default=True, verbose_name='性別', choices=sex_choices)birth = models.DateField(verbose_name='出生日期')intro = models.CharField(max_length=1000, verbose_name='個(gè)人介紹')photo = models.ImageField(max_length=255, verbose_name='照片')gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好評(píng)數(shù)')bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差評(píng)數(shù)')sno = models.ForeignKey(Subject, on_delete=models.CASCADE, db_column='sno')def __str__(self):return self.nameclass Meta:db_table = 'tb_teacher'
????????Subject 和 Teacher 模型中的每一個(gè)屬性都指明了models下面的一個(gè)數(shù)據(jù)類型,代表了數(shù)據(jù)庫(kù)中的一個(gè)字段。上面的類在數(shù)據(jù)庫(kù)中會(huì)創(chuàng)建如下的表,見1.3節(jié)的第二步創(chuàng)建表格的過(guò)程。
(2) 遷移模型
????????使用 Django 給我們提供的兩個(gè)命令來(lái)在數(shù)據(jù)庫(kù)中生成 polls 應(yīng)用下定義的數(shù)據(jù)模型,第一步生成遷移文件,第二步將遷移文件應(yīng)用到數(shù)據(jù)庫(kù):
python manage.py makemigrations
python manage.py migrate
1.3 自動(dòng)生成數(shù)據(jù)模型
1. 配置關(guān)系型數(shù)據(jù)庫(kù)MySQL
(1) 在MySQL中創(chuàng)建數(shù)據(jù)庫(kù),創(chuàng)建用戶,授權(quán)用戶訪問(wèn)該數(shù)據(jù)庫(kù)。
create database vote default charset utf8;
create user 'username'@'%' identified by 'yourpassword';
grant all privileges on vote.* to 'username'@'%';
flush privileges;
(2) 在 MySQL 中創(chuàng)建保存學(xué)科和老師信息的二維表(保存用戶信息的表稍后處理)。
use vote;-- 創(chuàng)建學(xué)科表
create table `tb_subject`
(`no` integer auto_increment comment '學(xué)科編號(hào)',`name` varchar(50) not null comment '學(xué)科名稱',`intro` varchar(1000) not null default '' comment '學(xué)科介紹',`is_hot` boolean not null default 0 comment '是不是熱門學(xué)科',primary key (`no`)
);
-- 創(chuàng)建老師表
create table `tb_teacher`
(`no` integer auto_increment comment '老師編號(hào)',`name` varchar(20) not null comment '老師姓名',`sex` boolean not null default 1 comment '老師性別',`birth` date not null comment '出生日期',`intro` varchar(1000) not null default '' comment '老師介紹',`photo` varchar(255) not null default '' comment '老師照片',`gcount` integer not null default 0 comment '好評(píng)數(shù)',`bcount` integer not null default 0 comment '差評(píng)數(shù)',`sno` integer not null comment '所屬學(xué)科',primary key (`no`),foreign key (`sno`) references `tb_subject` (`no`)
);
(3) 安裝數(shù)據(jù)庫(kù)的驅(qū)動(dòng),Python 3.x 使用 pymysql 作為 MySQL的驅(qū)動(dòng),然后在Django項(xiàng)目文件夾的__init__.py
中添加如下所示的代碼:
import pymysql
pymysql.install_as_MySQLdb() # 為了pymysql發(fā)揮最大數(shù)據(jù)庫(kù)操作性能
???????? 溫馨提示: 如果使用Django 2.2及以上版本,還會(huì)遇到PyMySQL跟Django框架的兼容性問(wèn)題,兼容性問(wèn)題會(huì)導(dǎo)致項(xiàng)目無(wú)法運(yùn)行,需要按照GitHub上PyMySQL倉(cāng)庫(kù)Issues中提供的方法進(jìn)行處理??傮w來(lái)說(shuō),使用pymysql會(huì)比較麻煩,強(qiáng)烈建議大家首選安裝mysqlclient,mysqlclient執(zhí)行效率也比較高。
(4) 修改項(xiàng)目的settings.py文件,首先將我們創(chuàng)建的應(yīng)用polls添加已安裝的項(xiàng)目(INSTALLED_APPS)中,然后配置MySQL作為持久化方案。
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','polls', # 把應(yīng)用文件加入
]ATABASES = {'default': {# 數(shù)據(jù)庫(kù)引擎配置'ENGINE': 'django.db.backends.mysql',# 數(shù)據(jù)庫(kù)的名字'NAME': 'vote',# 數(shù)據(jù)庫(kù)服務(wù)器的IP地址(本機(jī)可以寫為localhost或127.0.0.1)'HOST': 'localhost',# 啟動(dòng)MySQL服務(wù)的端口號(hào)'PORT': 3306,# 數(shù)據(jù)庫(kù)用戶名和口令'USER': 'carpediem','PASSWORD': 'carpediem2021',# 數(shù)據(jù)庫(kù)使用的字符集'CHARSET': 'utf8',# 數(shù)據(jù)庫(kù)時(shí)間日期的時(shí)區(qū)設(shè)定'TIME_ZONE': 'Asia/Chongqing',}
}
????????在配置ENGINE屬性時(shí),常用的可選值包括:
'django.db.backends.sqlite3'
:SQLite嵌入式數(shù)據(jù)庫(kù)。'django.db.backends.postgresql'
:BSD許可證下發(fā)行的開源關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品。'django.db.backends.mysql'
:甲骨文公司經(jīng)濟(jì)高效的數(shù)據(jù)庫(kù)產(chǎn)品。'django.db.backends.oracle'
:甲骨文公司關(guān)系型數(shù)據(jù)庫(kù)旗艦產(chǎn)品。
????????其他的配置可以參考官方文檔中的數(shù)據(jù)庫(kù)配置部分。
(5) Django框架提供了ORM來(lái)解決數(shù)據(jù)持久化問(wèn)題,ORM翻譯成中文叫“對(duì)象關(guān)系映射”。因?yàn)镻ython是面向?qū)ο蟮木幊陶Z(yǔ)言,我們?cè)赑ython程序中使用對(duì)象模型來(lái)保存數(shù)據(jù),而關(guān)系型數(shù)據(jù)庫(kù)使用關(guān)系模型,用二維表來(lái)保存數(shù)據(jù),這兩種模型并不匹配。使用ORM是為了實(shí)現(xiàn)對(duì)象模型到關(guān)系模型的雙向轉(zhuǎn)換,這樣就不用在Python代碼中書寫SQL語(yǔ)句和游標(biāo)操作,因?yàn)檫@些都會(huì)由ORM自動(dòng)完成。利用Django的ORM,我們可以直接將剛才創(chuàng)建的學(xué)科表和老師表變成Django中的模型類。
python manage.py inspectdb > polls/models.py
我們可以對(duì)自動(dòng)生成的模型類稍作調(diào)整,代碼如下所示。
from django.db import modelsclass Subject(models.Model):no = models.AutoField(primary_key=True, verbose_name='編號(hào)')name = models.CharField(max_length=50, verbose_name='名稱')intro = models.CharField(max_length=1000, verbose_name='介紹')is_hot = models.BooleanField(verbose_name='是否熱門')class Meta:managed = Falsedb_table = 'tb_subject'class Teacher(models.Model):no = models.AutoField(primary_key=True, verbose_name='編號(hào)')name = models.CharField(max_length=20, verbose_name='姓名')sex = models.BooleanField(default=True, verbose_name='性別')birth = models.DateField(verbose_name='出生日期')intro = models.CharField(max_length=1000, verbose_name='個(gè)人介紹')photo = models.ImageField(max_length=255, verbose_name='照片')gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好評(píng)數(shù)')bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差評(píng)數(shù)')sno = models.ForeignKey(Subject, models.DO_NOTHING, db_column='sno')class Meta:managed = Falsedb_table = 'tb_teacher'
????????若你的確想要允許 Django 管理這些表格的生命周期,你需要將上面的 managed 選項(xiàng)的值改為 True (或者刪掉它,因?yàn)?True 是默認(rèn)值)
???????? 溫馨提示: 所有模型都是django.db.models.Model
類的子類,模型類跟關(guān)系型數(shù)據(jù)庫(kù)的二維表對(duì)應(yīng),模型對(duì)象跟表中的記錄對(duì)應(yīng),模型對(duì)象的屬性跟表中的字段對(duì)應(yīng)。每個(gè)字段由django.db.models.Field
子類(內(nèi)置在Django core)的實(shí)例表示,它們并將被轉(zhuǎn)換為數(shù)據(jù)庫(kù)的列。
????????該功能僅是一個(gè)快捷方式,不是最佳的創(chuàng)建模型的方法。參考 inspectdb 文檔 獲取更多信息
補(bǔ)充:
1. 通用字段屬性
選項(xiàng) | 說(shuō)明 |
---|---|
null | 數(shù)據(jù)庫(kù)中對(duì)應(yīng)的字段是否允許為NULL ,默認(rèn)為False |
blank | 后臺(tái)模型管理驗(yàn)證數(shù)據(jù)時(shí),是否允許為NULL ,默認(rèn)為False |
choices | 設(shè)定字段的選項(xiàng),各元組中的第一個(gè)值是設(shè)置在模型上的值,第二值是人類可讀的值 |
db_column | 字段對(duì)應(yīng)到數(shù)據(jù)庫(kù)表中的列名,未指定時(shí)直接使用字段的名稱 |
db_index | 設(shè)置為True 時(shí)將在該字段創(chuàng)建索引 |
db_tablespace | 為有索引的字段設(shè)置使用的表空間,默認(rèn)為DEFAULT_INDEX_TABLESPACE |
default | 字段的默認(rèn)值 |
editable | 字段在后臺(tái)模型管理或ModelForm 中是否顯示,默認(rèn)為True |
error_messages | 設(shè)定字段拋出異常時(shí)的默認(rèn)消息的字典,其中的鍵包括null 、blank 、invalid 、invalid_choice 、unique 和unique_for_date |
help_text | 表單小組件旁邊顯示的額外的幫助文本。 |
primary_key | 將字段指定為模型的主鍵,未指定時(shí)會(huì)自動(dòng)添加AutoField 用于主鍵,只讀。 |
unique | 設(shè)置為True 時(shí),表中字段的值必須是唯一的 |
verbose_name | 字段在后臺(tái)模型管理顯示的名稱,未指定時(shí)使用字段的名稱 |
2. ForeignKey
屬性
limit_choices_to
:值是一個(gè)Q對(duì)象或返回一個(gè)Q對(duì)象,用于限制后臺(tái)顯示哪些對(duì)象。related_name
:用于獲取關(guān)聯(lián)對(duì)象的關(guān)聯(lián)管理器對(duì)象(反向查詢),如果不允許反向,該屬性應(yīng)該被設(shè)置為'+'
,或者以'+'
結(jié)尾。to_field
:指定關(guān)聯(lián)的字段,默認(rèn)關(guān)聯(lián)對(duì)象的主鍵字段。db_constraint
:是否為外鍵創(chuàng)建約束,默認(rèn)值為True
。on_delete
:外鍵關(guān)聯(lián)的對(duì)象被刪除時(shí)對(duì)應(yīng)的動(dòng)作,可取的值包括django.db.models
中定義的:CASCADE
:級(jí)聯(lián)刪除。PROTECT
:拋出ProtectedError
異常,阻止刪除引用的對(duì)象。SET_NULL
:把外鍵設(shè)置為null
,當(dāng)null
屬性被設(shè)置為True
時(shí)才能這么做。SET_DEFAULT
:把外鍵設(shè)置為默認(rèn)值,提供了默認(rèn)值才能這么做。
3. ManyToManyField
屬性
symmetrical
:是否建立對(duì)稱的多對(duì)多關(guān)系。through
:指定維持多對(duì)多關(guān)系的中間表的Django模型。throughfields
:定義了中間模型時(shí)可以指定建立多對(duì)多關(guān)系的字段。db_table
:指定維持多對(duì)多關(guān)系的中間表的表名。
4. 模型元數(shù)據(jù)選項(xiàng)
選項(xiàng) | 說(shuō)明 |
---|---|
abstract | 設(shè)置為True時(shí)模型是抽象父類 |
app_label | 如果定義模型的應(yīng)用不在INSTALLED_APPS中可以用該屬性指定 |
db_table | 模型使用的數(shù)據(jù)表名稱 |
db_tablespace | 模型使用的數(shù)據(jù)表空間 |
default_related_name | 關(guān)聯(lián)對(duì)象回指這個(gè)模型時(shí)默認(rèn)使用的名稱,默認(rèn)為<model_name>_set |
get_latest_by | 模型中可排序字段的名稱。 |
managed | 設(shè)置為True時(shí),Django在遷移中創(chuàng)建數(shù)據(jù)表并在執(zhí)行flush管理命令時(shí)把表移除 |
order_with_respect_to | 標(biāo)記對(duì)象為可排序的 |
ordering | 對(duì)象的默認(rèn)排序 |
permissions | 創(chuàng)建對(duì)象時(shí)寫入權(quán)限表的額外權(quán)限 |
default_permissions | 默認(rèn)為('add', 'change', 'delete') |
unique_together | 設(shè)定組合在一起時(shí)必須獨(dú)一無(wú)二的字段名 |
index_together | 設(shè)定一起建立索引的多個(gè)字段名 |
verbose_name | 為對(duì)象設(shè)定人類可讀的名稱 |
verbose_name_plural | 設(shè)定對(duì)象的復(fù)數(shù)名稱 |
2 使用ORM完成模型的CRUD操作
2.1 基本的增刪改查
????????有了Django框架的ORM,我們可以直接使用面向?qū)ο蟮姆绞絹?lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)的CRUD(增刪改查)操作。我們可以在PyCharm的終端中輸入下面的命令進(jìn)入到Django項(xiàng)目的交互式環(huán)境,然后嘗試對(duì)模型的操作。
python manage.py shell
1. 新增
from polls.models import SubjectSubject.objects.create(name='H5前端開發(fā)', intro='前段比較熱的學(xué)科', is_hot=True)subject1 = Subject(name='Python全棧開發(fā)', intro='當(dāng)下最熱門的學(xué)科', is_hot=True)
subject1.save()
subject2 = Subject(name='全棧軟件測(cè)試', intro='學(xué)習(xí)自動(dòng)化測(cè)試的學(xué)科', is_hot=False)
subject2.save()
subject3 = Subject(name='JavaEE分布式開發(fā)', intro='基于Java語(yǔ)言的服務(wù)器應(yīng)用開發(fā)', is_hot=True)
2. 刪除
subject = Subject.objects.get(no=2)
subject.delete()
3. 更新
subject = Subject.objects.get(no=1)
subject.name = 'Python全棧+人工智能'
subject.save()
4. 查詢
(1) 查詢所有對(duì)象
Subject.objects.all()
(2) 過(guò)濾數(shù)據(jù)
# 查詢名稱為“Python全棧+人工智能”的學(xué)科
Subject.objects.filter(name='Python全棧+人工智能')# 查詢名稱包含“全?!钡膶W(xué)科(模糊查詢)
Subject.objects.filter(name__contains='全棧')
Subject.objects.filter(name__startswith='全棧')
Subject.objects.filter(name__endswith='全棧')# 查詢所有熱門學(xué)科
Subject.objects.filter(is_hot=True)# 查詢編號(hào)大于3小于10的學(xué)科
Subject.objects.filter(no__gt=3).filter(no__lt=10)
Subject.objects.filter(no__gt=3, no__lt=10)# 查詢編號(hào)在3到7之間的學(xué)科
Subject.objects.filter(no__gte=3, no__lte=7)
Subject.objects.filter(no__range=(3, 7))
(3) 查詢單個(gè)對(duì)象
# 查詢主鍵為1的學(xué)科
Subject.objects.get(pk=1)
Subject.objects.get(no=1)
Subject.objects.filter(no=1).first()
Subject.objects.filter(no=1).last()
(4) 排序
# 查詢所有學(xué)科按編號(hào)升序排列
Subject.objects.order_by('no')
# 查詢所有部門按部門編號(hào)降序排列
Subject.objects.order_by('-no')
(5) 切片
# 按編號(hào)從小到大查詢前3個(gè)學(xué)科
Subject.objects.order_by('no')[:3]
(6) 計(jì)數(shù)
# 查詢一共有多少個(gè)學(xué)科
Subject.objects.count()
(7) 高級(jí)查詢
# 查詢編號(hào)為1的學(xué)科的老師
Teacher.objects.filter(sno__no=1)
Subject.objects.get(pk=1).teacher_set.all() # 查詢學(xué)科名稱有“全棧”二字的學(xué)科的老師
Teacher.objects.filter(sno__name__contains='全棧')
????????上面的 objects 是一個(gè)特殊的屬性,通過(guò)它來(lái)查詢數(shù)據(jù)庫(kù),它是模型的一個(gè) Manager。在 filter() 方法中還有一些比較神奇的雙下劃線輔助我們進(jìn)一步過(guò)濾結(jié)果,詳細(xì)了解,請(qǐng)閱讀官方文檔————執(zhí)行查詢
溫馨提示:
- 說(shuō)明1:由于老師與學(xué)科之間存在多對(duì)一外鍵關(guān)聯(lián),所以能通過(guò)學(xué)科反向查詢到該學(xué)科的老師(從一對(duì)多關(guān)系中“一”的一方查詢“多”的一方),反向查詢屬性默認(rèn)的名字是類名小寫_set(如上面例子中的teacher_set),當(dāng)然也可以在創(chuàng)建模型時(shí)通過(guò)ForeingKey的related_name屬性指定反向查詢屬性的名字。如果不希望執(zhí)行反向查詢可以將related_name屬性設(shè)置為’+‘或者以’+'開頭的字符串。
- 說(shuō)明2:ORM查詢多個(gè)對(duì)象時(shí)會(huì)返回QuerySet對(duì)象,QuerySet使用了惰性查詢,即在創(chuàng)建QuerySet對(duì)象的過(guò)程中不涉及任何數(shù)據(jù)庫(kù)活動(dòng),等真正用到對(duì)象時(shí)(對(duì)QuerySet求值)才向數(shù)據(jù)庫(kù)發(fā)送SQL語(yǔ)句并獲取對(duì)應(yīng)的結(jié)果,這一點(diǎn)在實(shí)際開發(fā)中需要引起注意!
- 說(shuō)明3:如果希望更新多條數(shù)據(jù),不用先逐一獲取模型對(duì)象再修改對(duì)象屬性,可以直接使用QuerySet對(duì)象的
update()
方法一次性更新多條數(shù)據(jù)。
參考
- Django 使用原生的 SQL 語(yǔ)句操作 MySQL 數(shù)據(jù)庫(kù):https://www.imooc.com/wiki/djangolesson/nativesql.html
- 深入模型:https://gitee.com/zengyujin/Python-100-Days/blob/master/Day41-55/42.深入模型.md
- Django入門指南-第5章:模型設(shè)計(jì):https://www.bookstack.cn/read/django-beginners-guide-zh/Fundamentals-2.md
- Django v4.0 中文文檔 模型:https://www.bookstack.cn/read/Django-4.0-zh/94d959954f3d0daa.md