使用反向字段时自动创建一对一关系

Posted

技术标签:

【中文标题】使用反向字段时自动创建一对一关系【英文标题】:Automatically create One-To-One relation when using reverse field 【发布时间】:2017-08-24 11:30:40 【问题描述】:

当创建模型的两个实例并使用 OneToOneField 连接它们时,连接会在对象创建时自动创建并保存:

from django.db import models

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    next = models.OneToOneField('self', on_delete=models.SET_NULL, related_name='prev', null=True, blank=True)

>>> m2 = MyModel.objects.create(name="2")
>>> m1 = MyModel.objects.create(name="1", next=m2)
>>> m2.prev
<MyModel: 1>
>>> m2.refresh_from_db()
>>> m2.prev
<MyModel: 2>

但是,当创建相同的连接但使用反向字段时,创建也会自动完成,但不是保存。

>>> m1 = MyModel.objects.create(name="1")
>>> m2 = MyModel.objects.create(name="2", prev=m1)
>>> m1.next
<MyModel: 2>
>>> m1.refresh_from_db()
>>> m1.next

请注意,最后一条语句不会打印任何内容,因为它返回 None


如何让它在使用反向字段创建时始终保存关系,而不必每次都手动使用.save()

【问题讨论】:

为什么调用refresh_from_db时会改变prev的值?这是预期的结果吗? prev 在调用refresh_from_db 时不在数据库中,因此被替换为数据库中的内容,即None 【参考方案1】:

实现这一点的简单方法可能是使用您认为可行的解决方案 pre_save/post_save 信号。但不确定这个答案有多可行,尝试制作一些模组,看看是否可行!

from django.db.models.signals import post_save
from django.dispatch import receiver

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    next = models.OneToOneField('self', on_delete=models.SET_NULL, related_name='prev', null=True, blank=True)

@receiver(post_save, sender=MyModel)
def mymodel_post_save(sender, instance, **kwargs):
     if hasattr(instance, 'prev'): # if prev exists
          # now check if prev is added next
          if not instance.prev.next: # if next is not present 
                 instance.prev.next = instance
                 MyModel.objects.filter(
                     pk=instance.prev.pk
                 ).update(next=instance)

【讨论】:

if not instance.prev.next 将始终评估为 false,因为它不检查数据库中的实际内容,仅检查内存中的内容。删除检查意味着将始终调用更新。需要检查仅在内存中更改的内容。【参考方案2】:

这是一个糟糕的主意。

你会让所有查看你的代码的人感到困惑。

我什至对MyModel.objects.create 有效而不是抛出无效的关键字参数感到惊讶。我可能会为此打开一个勾号。

当你打电话时:

m2 = MyModel.objects.create(name="2", prev=m1)

为了按预期工作,.create 方法需要在 m1 实例上调用 save,因为 m1 是保存关系的实例(通过 next

谁会想到呢?您正在隐藏功能。来自PEP 20的一些引用

显式优于隐式。

应该有一种——最好只有一种——明显的方法。

我的建议是将关系更改为:

class MyModel(models.Model):
    name = models.CharField(primary_key=True, max_length=255)
    prev = models.OneToOneField('self',
                                null=True,
                                blank=True,
                                related_name='next',
                                on_delete=models.SET_NULL)

因为,它更有可能在创建时知道prev 是什么。

【讨论】:

以上是关于使用反向字段时自动创建一对一关系的主要内容,如果未能解决你的问题,请参考以下文章

如何自动创建一个实体,它与我正在创建的实体具有一对一的非可选关系?

Core Data 关系的自定义设置器以强制反向互惠关系

如何使用 Hibernate 和 postgresql 为一对多关系实现每个表的自动递增 id 字段

mybatis可以像hibernate一样自动创建表吗

django之多表查询

Django