白日依山尽,黄河入海流。欲穷千里目,更上一层楼。 -- 唐·王之涣

20221006django关系模型多对多多对一和一对一

一对一

举例:用户模型和用户扩展信息是属于一对一关系。一对一关系在Django模型中是通过OneToOneField类型来表示的。

类型用法:OneToOneField(to, on_delete, parent_link=False, **options)

  • to 要进行关联的模型名称
  • on_delete 删除关联表中数据时要执行的操作配置项

其实一对一关系算是一种特殊的多对一关系,只不过反向查询的时候返回的是一个单一对象

多对一

举例: 一个分类下有多个博客文章,是属于多对一关系。在Django模型中是通过 ForeignKey类型来表示的。

类型用法:ForeignKey(to, on_delete, **options)

  • to 和 on_delete的含义和上面一对一关系一样

  • options 包含有:

    • related_name
    • related_query_name
    • swappable
    • db_constraint
    • limit_choices_to

常用的是前两个,后面三个不常用,这里不做介绍,前两个的具体含义和用法详见后面扩展部分

多对多

举例:一个文章会有多个标签,每个标签下也会有多个文章。在Django模型中是通过 ManyToManyField类型来表示的。

类型用法: ManyToManyField(to, **options)

  • 只有 to 参数,没有 on_delete

  • options 包含有:

    • related_name
    • related_query_name
    • symmetrical
    • through
    • through_fields
    • db_table
    • limit_choices_to

这里需要注意 through这个参数。默认情况下,ManyToManyField 在Django中会自动创建一个中间表,用常见的 博客应用来说明,中间表名称默认是 blog_post_tags,该表有三个字段

  • id 该表本身的递增字段
  • post_id 关联到 Post模型的 id字段
  • tag_id 关联到 Tag模型的 id 字段

如果不想使用默认的创建规则,则可以通过 through参数指向一个自定义的中间模型。当然自定义中间模型的时候,如果中间表的字段没有使用默认规则,然后可以通过 through_fields来指定,它是一个二元组

扩展

1、on_delete属性的取值说明

  • models.CASCADE 级联删除,也就是1的那头删除,对应的多的这头也删除,比如级联删除时:删除博客的分类,同时也会删除该分类下的所有博客文章
  • models.PROJECT 保护,在删除的时候会触发一个ProjectedError来组织删除
  • models.RESTRICT django3.1新增。正常情况下会触发一个RestrictedError 来阻止删除。但是如果该对象同时引用了其他对象,且是CASCADE 则可以被删除

官网案例:

模型如下,注意 Song 关联了 Album 且删除属性是RESTRICT, 同时还关联了 Artist,删除属性是CASCADE

1
2
3
4
5
6
7
8
9
class Artist(models.Model):
name = models.CharField(max_length=10)

class Album(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)

class Song(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
album = models.ForeignKey(Album, on_delete=models.RESTRICT)

则根据上面的定义描述,创建如下记录

1
2
3
4
5
6
>>> artist_one = Artist.objects.create(name='artist one')
>>> artist_two = Artist.objects.create(name='artist two')
>>> album_one = Album.objects.create(artist=artist_one)
>>> album_two = Album.objects.create(artist=artist_two)
>>> song_one = Song.objects.create(artist=artist_one, album=album_one)
>>> song_two = Song.objects.create(artist=artist_one, album=album_two)

1、album_one/album_two 删除都会报 RestrictedError,因为 song_one/song_two 都关联了Album,且是RESTRICT

2、artist_two 删除会报 RestrictedError,因为 song_two 关联了album_two, album_two关联了 artise_two, song_two是 RESTRICT限制,所以artist_two 删除会报 RestrictedError

3、artist_one 删除是正常的。但是请注意: 这个时候是只是删除了artist_one, 没有删除 album_one 和 song_one ,没有级联删除

  • models.SET_NULL 删除时设置关联的字段为Null,但是需要该字段同时 null=True,一般也要配置blank=True
  • models.SET_DEFAULT 删除时设置关联的字段的默认值,但是需要该字段具备默认值才行
  • models.SET() 删除时设置关联的字段为给定的值,或者一个可调用对象的结果 。
  • models.DO_NOTHING 顾名思义,什么也不做

2、关于 related_namerelated_query_name

举个例子来解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Category(models.Model):
name = models.CharField(max_length=10)

def __str__(self):
return self.name

class Post(models.Model):
title = models.CharField(max_length=120)
category = models.ForeignKey(Category,
on_delete=models.SET_NULL, null=True, blank=True,
# related_name="cs",
# related_query_name="p"
)

def __str__(self):
return self.title

1、related_name/related_query_name 是出现自多对一或者多对多的关系中

2、正常情况下,在 ForeignKey 或者 ManyToManyField 一侧,实际用法如下

  • 查询某个分类下都有哪些文章的时候是
1
2
3
4
5
c1 = Category.objects.get(pk=1)
# 这是使用的 model_set 的方式
c1.post_set.all()
# 配置了 related_name 的话,上面的案例中related_name='cs'
# c1.cs.all()
  • 查询文章标题是什么或者包含什么的对应的分类的时候
1
2
3
4
# 默认是使用的 model 小写名字
c2 = Category.objects.filter(post__name='python测试博客')
# 配置了 related_query_name 的话,related_query_name='p'
# c2 = Category.objects.filter(p__name='python测试博客')

3、正常情况下不指定,Django给了合理的默认值。一般

  • related_name 是相关对象的属性,比如 cs 是 c1 对象的属性
  • related_query_name 是用于Django查询集合

官网参考地址 https://docs.djangoproject.com/en/3.2/ref/models/fields/#module-django.db.models.fields.related

作者

Colin

发布于

2022-10-06

许可协议