0

我有一个具有通用关系的模型(称为 A),在创建此对象的实例时,我将另一个模型的实例(称为 B)作为 content_object 字段的初始化程序(通过构造函数的 kwargs)传递。

如果我在创建 A 之前不保存 B,那么在保存 A 时 content_object_id 将作为 NULL 保存到数据库中。如果我在将 B传递给 A 的构造函数之前保存它,那么一切都很好。

这不合逻辑。我假设在执行 A.save() 时会获取相关对象 (B) 的 ID,如果 B 尚未保存但它只是默默地失败,它应该抛出某种异常。我不喜欢当前的解决方案(预先保存 B),因为我们还不知道我是否总是愿意保留该对象,而不仅仅是废弃它,并且还有性能考虑 - 如果我要添加一些其他数据怎么办并在不久后再次保存。

class BaseNodeData(models.Model):
    ...
    extnodedata_content_type = models.ForeignKey(ContentType, null=True)
    extnodedata_object_id = models.PositiveIntegerField(null=True)
    extnodedata = generic.GenericForeignKey(ct_field='extnodedata_content_type', fk_field='extnodedata_object_id')

class MarkupNodeData(models.Model):
    raw_content = models.TextField()

假设我们这样做:

markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
markup.save()
base.save()
# both records are inserted to the DB but base is stored with extnodedata_object_id=NULL

markup = MarkupNodeData(raw_content='...')
base = BaseNodeData(..., extnodedata=markup)
base.save()
markup.save()
# no exception is thrown and everything is the same as above

markup = MarkupNodeData(raw_content='...')
markup.save()
base = BaseNodeData(..., extnodedata=markup)
base.save()
# this works as expected

当然我可以这样做,但它不会改变任何东西:

base = BaseNodeData(...)
base.extnodedata = markup

我的问题是 - 这是我应该报告的 django 中的错误还是我做错了什么。GenericRelations 上的文档并不完全冗长。

4

3 回答 3

0

我同意你可以在不事先保存 B 的情况下保存 A 是很奇怪的。

但我想不出你会设置与不想保留的对象的关系的情况。与不存在的对象建立关系是没有意义的;-) 因此事先保存 B 对我来说很好。

不确定这是否有帮助,但是这个问题中发布的 create 方法可能会让您了解您还可以对泛型关系做什么。

于 2011-01-02T23:48:56.393 回答
0

B 的实例在保存之前具有 None 的 pk,并且您的 extnodedata_object_id 字段允许空值,因此 A 实例的保存是有效的。

听起来可能对您有用的是覆盖 A 的保存以正确处理 B 的新实例;例如(未经测试):

def save(self, *args, **kwargs):
    b = self.extnodedata
    if b and b.pk is None:
        b.save()
        self.extnodedata = b
    return super(BaseNodeData, self).save(*args, **kwargs)
于 2011-01-03T16:39:24.197 回答
0

谢谢您的回答。我决定花更多时间调查 django 源并自己想出了一个解决方案。我对 GenericForeignKey 进行了子类化。代码应该是不言自明的。

from django.contrib.contenttypes import generic
from django.db.models import signals

class ImprovedGenericForeignKey(generic.GenericForeignKey):
    """
    Corrects the behaviour of GenericForeignKey so even if you firstly
    assign an object to this field and save it after its PK gets saved.

    If you assign a not yet saved object to this field an exception is 
    thrown upon saving the model.
    """

    class IncompleteData(Exception):
        message = 'Object assigned to field "%s" doesn\'t have a PK (save it first)!'

        def __init__(self, field_name):
            self.field_name = field_name

        def __str__(self):
            return self.message % self.field_name

    def contribute_to_class(self, cls, name):
        signals.pre_save.connect(self.instance_pre_save, sender=cls, weak=False)
        super(ImprovedGenericForeignKey, self).contribute_to_class(cls, name)

    def instance_pre_save(self, sender, instance, **kwargs):
        """
        Ensures that if GenericForeignKey has an object assigned
        that the fk_field stores the object's PK.
        """

        """ If we already have pk set don't do anything... """
        if getattr(instance, self.fk_field) is not None: return

        value = getattr(instance, self.name)

        """
        If no objects is assigned then we leave it as it is. If null constraints
        are present they should take care of this, if not, well, it's not my fault;)
        """
        if value is not None:
            fk = value._get_pk_val()

            if fk is None:
                raise self.IncompleteData(self.name)

            setattr(instance, self.fk_field, fk)

我认为这应该被认为是 django 中的一个错误,所以我会报告它并看看结果如何。

于 2011-01-03T20:01:27.657 回答