Django之model外键
外键和表关系
外键:
在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参数的时候,使⽤del_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来指定。可以指定的类型如下:
1. CASCADE:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
2. PROTECT:受保护。即只要这条数据引⽤了外键的那条数据,那么就不能删除外键的那条数据。
3. SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定
这个字段可以为空。
4. SET_DEFAULT:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是
要指定这个字段⼀个默认值。
5. SET():如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收⼀个可以调⽤的对象(⽐
如函数或者⽅法),如果是可以调⽤的对象,那么会将这个对象调⽤后的结果作为值返回回去。
6. DO_NOTHING:不采取任何⾏为。⼀切全看数据库级别的约束。
以上这些选项只是Django级别的,数据级别依旧是RESTRICT!
表关系:
表之间的关系都是通过外键来进⾏关联的。⽽表之间的关系,⽆⾮就是三种关系:⼀对⼀、⼀对多(多对⼀)、多对多等。以下将讨论⼀下三种关系的应⽤场景及其实现⽅式。
django网站⼀对多:
1. 应⽤场景:⽐如⽂章和作者之间的关系。⼀个⽂章只能由⼀个作者编写,但是⼀个作者可以写多篇⽂章。⽂章和作者之间的关系就是
典型的多对⼀的关系。
2. 实现⽅式:⼀对多或者多对⼀,都是通过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)
⼀对⼀:
1. 应⽤场景:⽐如⼀个⽤户表和⼀个⽤户信息表。在实际⽹站中,可能需要保存⽤户的许多信息,但是有些信息是不经常⽤的。如果
把所有信息都存放到⼀张表中可能会影响查询效率,因此可以把⽤户的⼀些不常⽤的信息存放到另外⼀张表中我们叫做UserExtension。
但是⽤户表User和⽤户信息表UserExtension就是典型的⼀对⼀了。
2. 实现⽅式: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表进⾏关联,并且这个
外键数据在表中必须是唯⼀的,来保证⼀对⼀。
多对多:
1. 应⽤场景:⽐如⽂章和标签的关系。⼀篇⽂章可以有多个标签,⼀个标签可以被多个⽂章所引⽤。因此标签和⽂章的关系是典型的多
对多的关系。
2. 实现⽅式: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 = (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 = (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字段