Django的ManyToManyField及其属性through

ManyToManyField

Django中的ManyToManyField用于表示一个多对多关系,当在模型中使用了该字段时,Django会自动生成一个表来管理多对多关系。

例如有下面两个模型:

class User(models.Model):
	username = CharField(max_length=200)
	password = Charfield(max_length=200)

class Image(models.Model):
	image = models.ImageField(upload_to='images/%Y/%m/%d')
	# 多对多关系
	users_like = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='images_liked', blank=True)

为了演示方便,这两个模型精简了很多字段。settings.AUTH_USER_MODEL代表着使用settings.py中设定的User模型,这里将其看成普通的User模型即可。Image模型中设置了一个字段users_like使用到了多对多关系。

User和Image模型在数据库中对应user和image表,image表中只会有id和image这个字段,并不会出现users_like字段。Django会生成另外一个表“users_like”来管理多对多关系。

users_like表:

idimage_iduser_id
711
814

Image模型就对应着image_id这一个字段,User模型就对应user_id。当执行Image.users_like.all()时,Django会根据Image中的id来查询"users_like"表中images_id字段与其相等的数据,比如当前的图片id为1,那么就查询image_id中为1的数据,就可以查出id为1的图片有用户1和用户4喜欢。

through属性

through用于指定多对多关系的中间表,如果不使用through指定中间表,Django会自动生成一个。所以当对中间表有一些其他需求时(例如添加一个记录时间的字段),那么就可以使用through来指定一个自己设计的中间表。上面提到的例子就是Django自动生成了一个中间表。

这里创建一个中间模型,用于记录用户的关注记录:

class Contact(models.Model):
    # 创建对应关系的用户
    user_from = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='rel_from_set', on_delete=models.CASCADE)
    # 被关注的用户
    user_to = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='rel_to_set', on_delete=models.CASCADE)
    # 关注时间
    created = models.DateTimeField(auto_now_add=True, db_index=True)

class User(models.Model):
	username = CharField(max_length=200)
	password = Charfield(max_length=200)
	following = models.ManyToManyField(self,  through=Contact, related_name='followers', symmetrical=False)

Contact模型中,user_from记录创建 关注关系 的用户,user_to记录被关注的用户。例如a关注了b,那么user_from中就会记录a的id,user_to就会记录b的id。由于想记录关注关系创建的时间,而自动创建的中间表不能满足需求,那么可以自己定义一个中间模型。

在上面的User模型中,ManyToManyField指向的模型是其本身,因为要记录的就是某某关注了某某,所以中间表需要记录的都是user对user之间的关系。

现在数据库中新增了一个Contact表

iduser_from_iduser_to_idcreated
114省略
211省略
313省略

和之前的不同,上面设计的多对多关系都是对应的同一个模型,Django会根据顺序来决定对应的是user_from还是user_to字段,正向查询时就是对应的user_from字段,反向查询时就是对应的user_to字段。当中间模型两个字段的指向的都是同一个模型,并且多对多关系指向的都是自己,这两个外键分表代表多对多关系(不同)的两端。

先假设创建了一个User的示例为user,当使用user.following.all()正向查询时,Django会执行下面的查询语句:

SELECT `auth_user`.`id` FROM `auth_user` INNER JOIN `account_contact`
ON (`auth_user`.`id` = `account_contact`.`user_to_id`) 
WHERE `account_contact`.`user_from_id` = 1

实际上还查了很多auth_user表中的东西,但这里用不到就省略了。也就是说当我查询当前用户关注了谁的时候,会使用"auth_user"表中的id来和"account_contact"进行连接(具体连接方式查看sql的内连接),筛选出两表的交集部分,然后再添加``account_contact.user_from_id=1的条件,就可以将id为1的用户所创建的活动筛选出来了。当调用user.followers.all()时也是如此,反向关系匹配的就是user_to这一个字段,会就根据当前用户id去匹配user_to字段。

through_fields属性

上面提到两种情况Django都能知道使用哪一个外键,当中间表有多个模型的外键时,Django会无法分清使用哪一个外键,这时就需要使用through_fields来执行使用哪一个外键。through_fields 接受一个二元元组 (‘field1’, ‘field2’),分别用来指定多对多关系的两端。

class Person(models.Model):
	"""
		Person模型
	"""
    name = models.CharField(max_length=50)

class Group(models.Model):
		"""
			Group模型
		"""
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through='Membership',
    )

class Membership(models.Model):
		"""
			中间模型
		"""
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

这个Membership模型,当中有三个外键,其中有两个(person、inviter)都指向了Person模型。而在Group中指定了Group和Person的Person的多对多关系,但Django应该使用peron还是inviter中的哪个外键呢?出现这种情况必须使用through_fields来指定使用哪个外键。

可以将Group模型中的members字段加上through_fields

members = models.ManyToManyField(
        Person,
        through='Membership',
        through_fields=('group', 'person'),
    )

指定后Django才能清楚知道使用哪一个外键。上面同一个模型对应同一个模型的,并且该对应该模型的只有两个外键,那么可以不使用through_field来指定,如果是出现3个指向User模型的外键时,这时候是需要进行指定的。

参考内容来源:

Django官网 在多对多(many-to-many)关系中添加添加额外的属性字段

Django官网 ManyToManyField

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值