外鍵和表關系
外鍵:
在MySQL
中,表有兩種引擎,一種是InnoDB
,另外一種是myisam
。如果使用的是InnoDB
引擎,是支持外鍵約束的。外鍵的存在使得ORM
框架在處理表關系的時候異常的強大。因此這里我們首先來介紹下外鍵在Django
中的使用。
類定義為class ForeignKey(to,on_delete,**options)
。第一個參數是引用的是哪個模型,第二個參數是在使用外鍵引用的模型數據被刪除了,這個字段該如何處理,比如有CASCADE
、SET_NULL
等。
這里以一個實際案例來說明。比如有一個User
和一個Article
兩個模型。一個User
可以發表多篇文章,一個Article
只能有一個Author
,並且通過外鍵進行引用。那么相關的示例代碼如下:
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE)
以上使用ForeignKey
來定義模型之間的關系。即在article
的實例中可以通過author
屬性來操作對應的User
模型。這樣使用起來非常的方便。示例代碼如下:
article = Article(title='abc',content='123') author = User(username='張三',password='111111') article.author = author article.save() # 修改article.author上的值 article.author.username = '李四' article.save()
為什么使用了ForeignKey
后,就能通過author
訪問到對應的user
對象呢。因此在底層,Django
為Article
表添加了一個屬性名_id
的字段(比如author的字段名稱是author_id),這個字段是一個外鍵,記錄着對應的作者的主鍵。以后通過article.author
訪問的時候,實際上是先通過author_id
找到對應的數據,然后再提取User
表中的這條數據,形成一個模型。
如果想要引用另外一個app
的模型,那么應該在傳遞to
參數的時候,使用app.model_name
進行指定。以上例為例,如果User
和Article
不是在同一個app
中,那么在引用的時候的示例代碼如下
# User模型在user這個app中 class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) # Article模型在article這個app中 class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("user.User",on_delete=models.CASCADE)
如果模型的外鍵引用的是本身自己這個模型,那么to
參數可以為'self'
,或者是這個模型的名字。在論壇開發中,一般評論都可以進行二級評論,即可以針對另外一個評論進行評論,那么在定義模型的時候就需要使用外鍵來引用自身。示例代碼如下:
class Comment(models.Model): content = models.TextField() origin_comment = models.ForeignKey('self',on_delete=models.CASCADE,null=True) # 或者 # origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)
外鍵刪除操作:
如果一個模型使用了外鍵。那么在對方那個模型被刪掉后,該進行什么樣的操作。可以通過on_delete
來指定。可以指定的類型如下:
CASCADE
:級聯操作。如果外鍵對應的那條數據被刪除了,那么這條數據也會被刪除。PROTECT
:受保護。即只要這條數據引用了外鍵的那條數據,那么就不能刪除外鍵的那條數據。SET_NULL
:設置為空。如果外鍵的那條數據被刪除了,那么在本條數據上就將這個字段設置為空。如果設置這個選項,前提是要指定這個字段可以為空。SET_DEFAULT
:設置默認值。如果外鍵的那條數據被刪除了,那么本條數據上就將這個字段設置為默認值。如果設置這個選項,前提是要指定這個字段一個默認值。SET()
:如果外鍵的那條數據被刪除了。那么將會獲取SET
函數中的值來作為這個外鍵的值。SET
函數可以接收一個可以調用的對象(比如函數或者方法),如果是可以調用的對象,那么會將這個對象調用后的結果作為值返回回去。DO_NOTHING
:不采取任何行為。一切全看數據庫級別的約束。
以上這些選項只是Django級別的,數據級別依舊是RESTRICT!
表關系:
表之間的關系都是通過外鍵來進行關聯的。而表之間的關系,無非就是三種關系:一對一、一對多(多對一)、多對多等。以下將討論一下三種關系的應用場景及其實現方式。
一對多:
- 應用場景:比如文章和作者之間的關系。一個文章只能由一個作者編寫,但是一個作者可以寫多篇文章。文章和作者之間的關系就是典型的多對一的關系。
-
實現方式:一對多或者多對一,都是通過
ForeignKey
來實現的。還是以文章和作者的案例進行講解。
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE)
那么以后在給Article
對象指定author
,就可以使用以下代碼來完成:
article = Article(title='abc',content='123') author = User(username='zhiliao',password='111111') # 要先保存到數據庫中 author.save() article.author = author article.save()
並且以后如果想要獲取某個用戶下所有的文章,可以通過article_set
來實現。示例代碼如下:
user = User.objects.first() # 獲取第一個用戶寫的所有文章 articles = user.article_set.all() for article in articles: print(article)
一對一:
-
應用場景:比如一個用戶表和一個用戶信息表。在實際網站中,可能需要保存用戶的許多信息,但是有些信息是不經常用的。 如果把所有信息都存放到一張表中可能會影響查詢效率,因此可以把用戶的一些不常用的信息存放到另外一張表中我們叫做
UserExtension
。但是用戶表User
和用戶信息表UserExtension
就是典型的一對一了。 -
實現方式:
Django
為一對一提供了一個專門的Field
叫做OneToOneField
來實現一對一操作。示例代碼如下:
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class UserExtension(models.Model): birthday = models.DateTimeField(null=True) school = models.CharField(blank=True,max_length=50) user = models.OneToOneField("User", on_delete=models.CASCADE)
在UserExtension
模型上增加了一個一對一的關系映射。其實底層是在UserExtension
這個表上增加了一個user_id
,來和user
表進行關聯,並且這個外鍵數據在表中必須是唯一的,來保證一對一。
多對多:
-
應用場景:比如文章和標簽的關系。一篇文章可以有多個標簽,一個標簽可以被多個文章所引用。因此標簽和文章的關系是典型的多對多的關系。
-
實現方式:
Django
為這種多對多的實現提供了專門的Field
。叫做ManyToManyField
。還是拿文章和標簽為例進行講解。示例代碼如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() tags = models.ManyToManyField("Tag",related_name="articles") class Tag(models.Model): name = models.CharField(max_length=50)
在數據庫層面,實際上Django
是為這種多對多的關系建立了一個中間表。這個中間表分別定義了兩個外鍵,引用到article
和tag
兩張表的主鍵。
related_name和related_query_name:
related_name:
還是以User
和Article
為例來進行說明。如果一個article
想要訪問對應的作者,那么可以通過author
來進行訪問。但是如果有一個user
對象,想要通過這個user
對象獲取所有的文章,該如何做呢?這時候可以通過user.article_set
來訪問,這個名字的規律是模型名字小寫_set
。示例代碼如下:
user = User.objects.get(name='張三') user.article_set.all()
如果不想使用模型名字小寫_set
的方式,想要使用其他的名字,那么可以在定義模型的時候指定related_name
。示例代碼如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles')
以后在方向引用的時候。使用articles
可以訪問到這個作者的文章模型。示例代碼如下:
user = User.objects.get(name='張三') user.articles.all()
如果不想使用反向引用,那么可以指定related_name='+'
。示例代碼如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+')
后將不能通過user.article_set
來訪問文章模型了。
related_query_name:
在查找數據的時候,可以使用filter
進行過濾。使用filter
過濾的時候,不僅僅可以指定本模型上的某個屬性要滿足什么條件,還可以指定相關聯的模型滿足什么屬性。比如現在想要獲取寫過標題為abc
的所有用戶,那么可以這樣寫:
users = User.objects.filter(article__title='abc')
如果你設置了related_name
為articles
,因為反轉的過濾器的名字將使用related_name
的名字,那么上例代碼將改成如下:
users = User.objects.filter(articles__title='abc')
可以通過related_query_name
將查詢的反轉名字修改成其他的名字。比如article
。示例代碼如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 傳遞related_name參數,以后在方向引用的時候使用articles進行訪問 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article')
那么在做反向過濾查找的時候就可以使用以下代碼:
users = User.objects.filter(article__title='abc')
指定外鍵字段:
class Game(models.Model): GAME_AREAS = ( ) GAME_VALIDS = ( (0, '無效'), (1, '有效'), ) appid = models.IntegerField(primary_key=True, unique=True, verbose_name="游戲標識") name = models.CharField(max_length=32, verbose_name="游戲名稱") area = models.CharField(max_length=32, choices=GAME_AREAS, verbose_name="區域") is_valid = models.SmallIntegerField(choices=GAME_VALIDS, verbose_name="是否有效") def __str__(self): return self.name class Meta: db_table = "common_game" verbose_name = verbose_name_plural = "游戲表" class GameService(models.Model): appid = models.ForeignKey(Game, db_column='appid', null=True, on_delete=models.SET_NULL, verbose_name='游戲') # 指定db_column字段