這篇是能比較清楚的講解Django Model中外鍵規(guī)則的:http://c.biancheng.net/view/7645.html
我們知道涉及到數(shù)據(jù)表之間的對應(yīng)關(guān)系就會想到一對一蜈垮、一對多耗跛、多對多,在學(xué)習(xí) MySQL 數(shù)據(jù)庫時表關(guān)系設(shè)計是需要重點掌握的知識攒发。Django 中定義了三種關(guān)系類型的字段用來描述數(shù)據(jù)庫表的關(guān)聯(lián)關(guān)系:一對多(Foreignkey)调塌、一對一(OneToOneFiled)、以及多對多(ManyToManyFiled)惠猿,在本節(jié)我們對它們做簡單的介紹羔砾。
1. 一對多關(guān)系類型
這種類型在數(shù)據(jù)庫中體現(xiàn)是外鍵關(guān)聯(lián)關(guān)系,它在和其他的 Model 建立關(guān)聯(lián)同時也和自己建立關(guān)聯(lián),用來描述一對多的關(guān)系姜凄,例如一個作者可以寫很多不同的書政溃,但是這些書又只能對應(yīng)這一個作者,再比如一本圖書只能屬于一個出版社檀葛,一個出版社可以出版很多不同種類的圖書玩祟,這就是一對多的關(guān)系腹缩。Django 會自動將字段的名稱添加“_id”作為列名屿聋,F(xiàn)orgienKey 的定義如下:
class django.db,model.ForeignKey(to,on_delete,**options)
1) 必填參數(shù)
它有兩個必填參數(shù)。to藏鹊,指定所關(guān)聯(lián)的 Model润讥,它的中取值可以是直接引用其他的 Model,也可以是 Model 所對應(yīng)的字符串名稱盘寡;on_delete楚殿,當(dāng)刪除關(guān)聯(lián)表的數(shù)據(jù)時,Django 將根據(jù)這個參數(shù)設(shè)定的值確定應(yīng)該執(zhí)行什么樣的 SQL 約束竿痰。
on_delete 可以理解為 MySQL 外鍵的級聯(lián)動作脆粥,當(dāng)主表執(zhí)行刪除操作時對子表的影響,即子表要執(zhí)行的操作影涉,Django 提供的可選值如下所示:
- CASCADE变隔,級聯(lián)刪除,它是大部分 ForeignKey 的定義時選擇的約束蟹倾。它的表現(xiàn)是刪除了“主”匣缘,則“子”也會被自動刪除。
- PROTECT鲜棠,刪除被引用對象時肌厨,將會拋出 ProtectedError 異常。當(dāng)主表被一個或多個子表關(guān)聯(lián)時豁陆,主表被刪除則會拋出異常柑爸。
- SET_NULL,設(shè)置刪除對象所關(guān)聯(lián)的外鍵字段為 null盒音,但前提是設(shè)置了選項 null 為True表鳍,否則會拋出異常。
- SET_DEFAULT:將外鍵字段設(shè)置為默認(rèn)值里逆,但前提是設(shè)置了 default 選項进胯,且指向的對象是存在的。
- SET(value):刪除被引用對象時原押,設(shè)置外鍵字段為 value胁镐。value 如果是一個可調(diào)用對象,那么就會被設(shè)置為調(diào)用后的結(jié)果。
- DO_NOTHING:不做任何處理盯漂。但是颇玷,由于數(shù)據(jù)表之間存在引用關(guān)系,刪除關(guān)聯(lián)數(shù)據(jù)就缆,會造成數(shù)據(jù)庫拋出異常帖渠。
2) 可選參數(shù)
除了必填參數(shù)以外,F(xiàn)oreignKey 還有一些常用的可選參數(shù)需要關(guān)注竭宰。如下所示:
- to_field:關(guān)聯(lián)對象的字段名稱空郊。默認(rèn)情況下,Django 使用關(guān)聯(lián)對象的主鍵(大部分情況下是 id)切揭,如果需要修改成其他字段狞甚,可以設(shè)置這個參數(shù)。但是廓旬,需要注意哼审,能夠關(guān)聯(lián)的字段必須有 unique=True 的約束。
- db_constraint:默認(rèn)值是 True孕豹,它會在數(shù)據(jù)庫中創(chuàng)建外鍵約束涩盾,維護(hù)數(shù)據(jù)完整性。通常情況下励背,這符合大部分場景的需求春霍。如果數(shù)據(jù)庫中存在一些歷史遺留的無效數(shù)據(jù),則可以將其設(shè)置為 False椅野,這時就需要自己去維護(hù)關(guān)聯(lián)關(guān)系的正確性了终畅。
- related_name:這個字段設(shè)置的值用于反向查詢,默認(rèn)不需要設(shè)置竟闪,Django 會設(shè)置其為“小寫模型名 _set”离福。
- related_query_name:這個名稱用于反向過濾。如果設(shè)置了 related_name炼蛤,那么將用它作為默認(rèn)值妖爷,否則 Django 會把模型的名稱作為默認(rèn)值。
3) 語法格式
1. #一個A類實例對象關(guān)聯(lián)多個B類實例對象
2. class A(model.Model):
3. ....
4. class B(model.Model):
5. 屬性 = models.ForeignKey(多對一中"一"的模型類, ...)
4) 實例應(yīng)用
修改原來在《Django ORM進(jìn)階之項目實戰(zhàn)》定義的代碼理朋,將出版社與圖書之間修改為一對多的關(guān)系絮识,添加如下代碼:
1. from django.db import models
2. #新建出版社表
3. class PubName(models.Model):
4. pubname=models.CharField('名稱',max_length=255,unique=True)
5. #更改書籍信息表
6. class Book(models.Model):
7. title=models.CharField(max_length=30,unique=True, verbose_name='書名')
8. price=models.DecimalField(max_digits=7,decimal_places=2,verbose_name='定價')
9. #添加默認(rèn)價格
10. def default_price(self):
11. return '¥30'
12. #零售價格
13. retail_price=models.DecimalField(max_digits=7,decimal_places=2,verbose_name='零售價',default=default_price)
14. pub=models.ForeignKey(to=PubName,on_delete=models.CASCADE ,null=True) #創(chuàng)建Foreign外鍵關(guān)聯(lián)pub,以pub_id關(guān)聯(lián)
15. def __str__(self)
16. return "title:%s pub:%s price:%s" % (self.title, self.pub, self.price)
此處需要注意每次更改完 models 都需要進(jìn)行數(shù)據(jù)庫遷移操作,依次執(zhí)行以下命令即可:
python manager.py makemigrations
python manager.py migrate
插入數(shù)據(jù)創(chuàng)建一對多對象嗽上,如下所示:
1. #創(chuàng)建PubName實例化對象pub1并插入書籍信息
2. pub1=PubName.objects.create(pubname="清華出版社")
3. Book.objects.create(title="Python",price="59.00",retail_price="59.00",pub=pub1)
4. Book.objects.create(title="Redis",price="25.00",retail_price="25.00",pub=pub1)
5. Book.objects.create(title="Java",price="45.00",retail_price="45.00",pub=pub1)
6. #創(chuàng)建PubName實例化對象pub2并插入書籍信息
7. pub2=PubName.objects.create(pubname="c語言中文網(wǎng)出版")
8. Book.objects.create(title="Django",price="65.00",retail_price="65.00",pub=pub2)
9. Book.objects.create(title="Flask",price="45.00",retail_price="45.00",pub=pub2)
10. Book.objects.create(title="Tornado",price="35.00",retail_price="35.00",pub=pub2)
訪問 MySQL 數(shù)據(jù)庫分別查詢 index_book次舌、index_pubname 數(shù)據(jù)表(如下所示),index_pubname 數(shù)據(jù)表的 id 字段作為唯一值關(guān)聯(lián)多個書籍信息兽愤,F(xiàn)oreignKey 外鍵關(guān)聯(lián)鍵自動在 index_book 表中生成 pub_Id 字段并作為關(guān)聯(lián)字段彼念。此時 index_pubname 作為主表而 index_book 是子表挪圾,主表的 id 是子表的外鍵,兩者之間存在外鍵約束 CASCADE逐沙。
mysql> select * from index_book;
+----+---------+--------+-------+--------------+
| id | title | pub_id | price | retail_price |
+----+---------+--------+-------+--------------+
| 1 | Python | 1 | 59.00 | 59.00 |
| 2 | Redis | 1 | 25.00 | 25.00 |
| 3 | Java | 1 | 45.00 | 45.00 |
| 4 | Django | 2 | 65.00 | 65.00 |
| 5 | Flask | 2 | 45.00 | 45.00 |
| 6 | Tornado | 2 | 35.00 | 35.00 |
+----+---------+--------+-------+--------------+
6 rows in set (0.01 sec)
mysql> select * from index_pubname;
+----+-----------------+
| id | pubname |
+----+-----------------+
| 1 | c語言中文網(wǎng)出版 |
| 2 | 清華出版社 |
+----+-----------------+
6 rows in set (0.00 sec)
2. 一對一關(guān)系類型
OneToOneFiled 繼承自 ForeignKey哲思,在概念上,它類似 unique=Ture 的 ForeignKey吩案,它與 ForeignKey 最顯著的區(qū)別在于反向查詢上棚赔,F(xiàn)oreignKey 反向查詢返回的是一個對象實例列表,而 OneToOneFiled 反向查詢返回的是一個對象實例徘郭。
一對關(guān)系類型的使用和場景相對其他兩種關(guān)聯(lián)關(guān)系要少靠益,經(jīng)常用于對已有 Model 的擴(kuò)展,例如我們可以對 UserInfo 表進(jìn)行擴(kuò)展崎岂,添加類似用戶昵稱捆毫、個性簽名等字段。此時就可以新建一個 Model冲甘,并定義一個字段與 UserInfo 表一對一關(guān)聯(lián)。這樣就實現(xiàn)了用戶信息拓展表與 UserInfo 表一對一關(guān)聯(lián)途样,下面會用通過實例進(jìn)行說明江醇。
1) 語法格式
1. class A(model.Model):
2. ...
3. class B(model.Model):
4. 屬性 = models.OneToOneField(A)
2) 實例應(yīng)用
新建 index\models.py 下添加以下代碼:
1. #新建一對一關(guān)用戶信息表拓展表,添加完成后執(zhí)行數(shù)據(jù)庫遷移同步操作
2. class ExtendUserinfo(models.Model):
3. user=models.OneToOneField(to=UserInfo,on_delete=models.CASCADE)
4. signature=models.CharField(max_length=255,verbose_name='用戶簽名',help_text='自建簽名')
5. nickname=models.CharField(max_length=255,verbose_name='昵稱',help_text='自建昵稱')
使用 Django shell 創(chuàng)建數(shù)據(jù),如下所示:
1. from index.models import UserInfo,ExtendUserinfo
2. username=UserInfo.objects.create(username="xiaoming",password="******")
3. username=UserInfo.objects.create(username="xiaohong",password="*******",gender="F")
4. #創(chuàng)建一對一表關(guān)聯(lián)
5. ExtendUserinfo.objects.create(user=username,signature="good good study,day day up",nickname="XH")
3) MySQL數(shù)據(jù)表顯示
最后通過訪問 MySQL 數(shù)據(jù)庫何暇,我們可以得到如下所示數(shù)據(jù)表陶夜,使用 user_id 進(jìn)行表之間的關(guān)聯(lián):
mysql> select * from index_userinfo;
+----+----------+----------+--------+
| id | username | password | gender |
+----+----------+----------+--------+
| 1 | xiaoming | ****** | M |
| 2 | xiaohong | ******* | F |
+----+----------+----------+--------+
2 rows in set (0.00 sec)
mysql> select * from index_extenduserinfo;
+----+----------------------------+----------+---------+
| id | signature | nickname | user_id |
+----+----------------------------+----------+---------+
| 1 | good good study,day day up | XH | 2 |
+----+----------------------------+----------+---------+
1 row in set (0.00 sec)
3. 多對多關(guān)系類型
多對多關(guān)系也是比較常見的,比如一個作者可以寫很多本書裆站,一本書也可以由很多作者一起完成条辟,那么這時候 Author 和 Book 之間就是多對多的關(guān)系。 Django 通過中間表的方式來實現(xiàn) Model 之間的多對多的關(guān)系宏胯,這和 MySQL 中實現(xiàn)方式是一致的羽嫡。這個中間表我們可以自己提供,也可以使用 Django 默認(rèn)生成的中間表肩袍。
1) ManyToManyFiled定義
class django.db.models.ManyToManyFiled(to,**options)
它只有一個必填的參數(shù)即 to杭棵,與其他兩個關(guān)聯(lián)詞在一樣,用來指定與當(dāng)前的 Model 關(guān)聯(lián)的 Model氛赐。
2)可選參數(shù)
當(dāng)然 ManyToManyFiled 還有一些重要的可選參數(shù)魂爪,下面我們對它們依次進(jìn)行介紹:
- relate_name 與 ForeignKey 中的相同都用于反向查詢。
- db_table 用于指定中間表的名稱艰管,如果沒有提供滓侍,Django 會使用多對多字段的名稱和包含這張表的 Model 的名稱組合起來構(gòu)成中間表的名稱,當(dāng)然也會包含 index 前綴牲芋。
- through 用于指定中間表撩笆,這個參數(shù)不需要設(shè)置尔破,Django會自動生成隱式的 through Model。由于 Django可以生成自身默認(rèn)的中間表浇衬,該參數(shù)可以讓用戶自己去控制表之間的關(guān)聯(lián)關(guān)系或者增加一些額外的信息懒构。
3) 語法格式
1. class Author(models.Model):
2. ...
3. class Book(models.Model):
4. ...
5. authors = models.ManyToManyField(Author)
4) 多對多中間表
創(chuàng)建 Author 與 Book 之間多對多關(guān)聯(lián)關(guān)系,在 Author Model 中添加如下代碼:
1. books=models.ManyToManyField(to="Book") #創(chuàng)建多對多映射關(guān)系
然后再執(zhí)行數(shù)據(jù)庫遷移命令耘擂,我們可以執(zhí)行以下命令可以查看 Django 執(zhí)行 sql 語句:
python manage.py sqlmigrate index 0007_author_books
sql 語句如下所示:
1. CREATE TABLE `index_author_books` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `author_id` integer NOT NULL, `book_id` integer NOT NULL);
2. ALTER TABLE `index_author_books` ADD CONSTRAINT `index_author_books_author_id_2bfd143c_fk_index_author_id` FOREIGN KEY (`author_id`) REFERENCES `index_author` (`id`);
3. ALTER TABLE `index_author_books` ADD CONSTRAINT `index_author_books_book_id_1c280bc9_fk_index_book_id` FOREIGN KEY (`book_id`) REFERENCES `index_book` (`id`);
4. ALTER TABLE `index_author_books` ADD CONSTRAINT `index_author_books_author_id_book_id_b0dd3503_uniq` UNIQUE (`author_id`, `book_id`);
由 sql 語句可以看出胆剧,Django 默認(rèn)隱式的創(chuàng)建了 index_author_books 表(Django 的命名規(guī)范),即維護(hù)關(guān)聯(lián)關(guān)系的中間表醉冤。這個表有三個字段分別是主鍵 id秩霍,與index_author 表關(guān)聯(lián)的 author_id,以及 index_book 表關(guān)聯(lián)的 book_id蚁阳。同時為這兩個關(guān)聯(lián) id 創(chuàng)建了外鍵約束(FORGIEN KEY),最后還為 index_author_books 表創(chuàng)建了唯一性約束 author_id 和 book_id铃绒。
上面介紹了 Django 自身默認(rèn)創(chuàng)建中間表的過程,當(dāng)然我們也可以自己建立中間表螺捐,然后通過 Author 表中 books 字段的 through 參數(shù)指向這張中間表颠悬。如果大家對于 MySQL 自建中間表或其它知識不熟悉,推薦學(xué)習(xí)本網(wǎng)站的《MySQL教程》來查漏補(bǔ)缺定血。
5) 實例應(yīng)用
插入作者信息數(shù)據(jù)赔癌,如下所示:
1. author1=Author.objects.create(name="Luncy",email="123456@qq.com")
2. author2=Author.objects.create(name="Tom",email="456789@163.com")
因為書籍信息之前已經(jīng)準(zhǔn)備完畢,所以下面我們開始創(chuàng)建多對多映射關(guān)系澜沟,我們在 Django shell 進(jìn)行如下操作:
1. author1.books.add(Book.objects.get(id="1"))
2. author1.books.add(Book.objects.get(id="2"))
3. author1.books.add(Book.objects.get(id="3"))
4. author2.books.add(Book.objects.get(id="1"))
5. author2.books.add(Book.objects.get(id="4"))
6. author2.books.add(Book.objects.get(id="5"))
7. author2.books.add(Book.objects.get(id="3"))
8. author2.books.add(Book.objects.get(id="6"))
9. author1.books.add(Book.objects.get(id="6"))
多對多關(guān)系在中間表插入數(shù)據(jù)需要使用 add() 方法灾票,books 是對應(yīng)的多對多字段。通過以上代碼就完成多對多關(guān)系的創(chuàng)建茫虽,最后在 MySQL 中查看多對多相關(guān)聯(lián)的三張數(shù)據(jù)表刊苍,如下所示:
#書籍信息表(index_book)
+----+---------+--------+-------+--------------+
| id | title | pub_id | price | retail_price |
+----+---------+--------+-------+--------------+
| 1 | Python | 8 | 59.00 | 59.00 |
| 2 | Redis | 8 | 25.00 | 25.00 |
| 3 | Java | 8 | 45.00 | 45.00 |
| 4 | Django | 9 | 65.00 | 65.00 |
| 5 | Flask | 9 | 45.00 | 45.00 |
| 6 | Tornado | 9 | 35.00 | 35.00 |
+----+---------+--------+-------+--------------+
#作家信息表(index_author)
+----+-------+----------------+
| id | name | email |
+----+-------+----------------+
| 1 | Luncy | 123456@qq.com |
| 2 | Tom | 456789@163.com |
+----+-------+----------------+
#中間表(index_author_books)
+----+-----------+---------+
| id | author_id | book_id |
+----+-----------+---------+
| 4 | 1 | 1 |
| 7 | 1 | 3 |
| 5 | 1 | 4 |
| 6 | 1 | 5 |
| 8 | 1 | 6 |
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 2 | 3 |
| 9 | 2 | 6 |
+----+-----------+---------+
本節(jié)用了較長的篇幅給大家講解了 Django 中數(shù)據(jù)表的關(guān)聯(lián)關(guān)系,它和 MySQL 的思想是一致的濒析,只是 Django 提供了自己的一套方法正什,所以我們也要學(xué)會使用它,在后續(xù)章節(jié)我們將基于此節(jié)的內(nèi)容介紹 Django QuerySet API 即與數(shù)據(jù)庫接口相關(guān)的表查詢悼枢、更新埠忘、刪除操作。