Django GenericRelation 不保存相关对象的 id - 这是一个错误还是我做错了?

发布于 2024-10-10 15:43:18 字数 1522 浏览 7 评论 0原文

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

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

这不符合逻辑。我假设在执行 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 的文档并不十分详细。

I have a model with a generic relation (call it A), when creating an instance of this object I pass an instance of another model (call it B) as the initializer of the content_object field (via kwargs of the constructor).

If I don't save B before creating A then when saving A the content_object_id is saved to the db as NULL. If I save B before passing it to the constructor of A then everything's allright.

It's not logical. I assumed that the ID of the related object (B) is fetched when doing A.save() and it should throw some kind of an exception if B isn't saved yet but it just fails silently. I don't like the current solution (saving B beforhand) because we don't know yet if I will be always willing to keep the object, not just scrap it, and there are performance considerations - what if I will add some another data and save it once more shortly after.

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()

Suppose we do:

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

Of course I can do it this way, but it doesn't change anything:

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

My question is - is this a bug in django which I should report or maybe I'm doing something wrong. Docs on GenericRelations aren't exactly verbose.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

筱武穆 2024-10-17 15:43:18

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

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

不确定这是否有帮助,但创建方法,如在此 问题中发布的,可能会让您了解还可以使用通用关系做什么。

I agree that it is strange that you can save A without saving B beforehand.

But I can not think of a case where you will set a relation to an object that you don't want to keep. Makes no sense having a relation to an object that does not exist ;-)
Thus saving B beforehand is fine for me.

Not sure whether this helps, but the create method, as posted in this question, might give you an idea what you can also do with generic relations.

羁〃客ぐ 2024-10-17 15:43:18

B 的实例在保存之前的 pk 为 None,并且您的 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)

An instance of B has a pk of None prior to save, and your extnodedata_object_id field allows null values, so the save of the A instance is valid.

It sounds like what might work for you is to override A's save to properly handle new instances of B; e.g. (untested):

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)
jJeQQOZ5 2024-10-17 15:43:18

谢谢您的回答。我决定花更多时间调查 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 中的一个错误,所以我会报告它并看看结果如何。

Thank you for your answers. I decided to take some more time to investigate the django sources and came up with a solution myself. I subclassed the GenericForeignKey. The code should be self-explanatory.

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)

I think that this should be considered a bug in django, so I will report it and see how it turns out.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文