Django數(shù)據(jù)表關(guān)聯(lián)關(guān)系映射(一對一通危、一對多、多對多)

這篇是能比較清楚的講解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)的表查詢悼枢、更新埠忘、刪除操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馒索,一起剝皮案震驚了整個濱河市莹妒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绰上,老刑警劉巖旨怠,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蜈块,居然都是意外死亡鉴腻,警方通過查閱死者的電腦和手機(jī)迷扇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爽哎,“玉大人蜓席,你說我怎么就攤上這事】涡浚” “怎么了厨内?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長渺贤。 經(jīng)常有香客問我雏胃,道長,這世上最難降的妖魔是什么志鞍? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任瞭亮,我火速辦了婚禮,結(jié)果婚禮上固棚,老公的妹妹穿的比我還像新娘统翩。我一直安慰自己,他們只是感情好玻孟,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布唆缴。 她就那樣靜靜地躺著,像睡著了一般黍翎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艳丛,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天匣掸,我揣著相機(jī)與錄音,去河邊找鬼氮双。 笑死碰酝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戴差。 我是一名探鬼主播送爸,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼暖释!你這毒婦竟也來了袭厂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤球匕,失蹤者是張志新(化名)和其女友劉穎纹磺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亮曹,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡橄杨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年秘症,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片式矫。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡摹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出采转,到底是詐尸還是另有隱情聪廉,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布氏义,位于F島的核電站锄列,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惯悠。R本人自食惡果不足惜邻邮,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望克婶。 院中可真熱鬧筒严,春花似錦、人聲如沸情萤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筋岛。三九已至娶视,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間睁宰,已是汗流浹背肪获。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留柒傻,地道東北人孝赫。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像红符,于是被迫代替她去往敵國和親青柄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容