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

8
创建两个模型实例并使用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值?这是预期的结果吗? - Mojimi
prev isn't in the database when refresh_from_db is called and is therefore replaced with what is in the database, i.e. None - Oskar Persson
2个回答

7

可能实现这个的简单方法是使用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。删除此检查意味着更新将始终被调用。需要检查仅在内存中更改了什么的内容。 - Oskar Persson

3

这是一个糟糕的想法。

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

我甚至很惊讶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是什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接