在Django查询集中过滤不存在的GenericForeignKey对象

7

我有一个带有通用外键的简单模型:

class Generic(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

我希望过滤掉这个表格中所有具有非空content_object的条目,即过滤掉所有Generic实例中不存在内容对象的条目:
Generic.objects.filter(~Q(content_object=None))

这段代码无法运行,会抛出异常:
django.core.exceptions.FieldError: 字段'content_object'没有生成自动反向关系,因此无法用于反向查询。如果它是GenericForeignKey,请考虑添加GenericRelation。
在引用的内容类型模型中添加GenericRelation没有任何影响。
如何实现这一点?非常感谢您的帮助。
编辑:我意识到我可以级联删除,但在我的情况下这不是一个选项(我希望保留数据)。
1个回答

6
如果你想要过滤掉一些记录,通常最好使用 exclude() 方法:
Generic.objects.exclude(object_id__isnull=True)

注意,您的模型现在不允许空的content_object字段。要更改此行为,请对object_idcontent_type字段都使用null=True参数。参考链接

更新

好的,既然问题已经从筛选出空记录转变为在没有RDBMS本身的帮助下确定损坏的RDBMS引用,我建议使用一个(相当慢且占用内存较多的)解决方法:
broken_items = []
for ct in ContentType.objects.all():        
    broken_items.extend(
        Generic.objects
        .filter(content_type=ct)
        .exclude(object_id__in=ct.model_class().objects.all())
        .values_list('pk', flat=True))

这个脚本可以作为一次性解决方案,但不适合作为稳健的解决方案。如果你绝对想要保留数据,我所能想到的唯一快速的方法是在你的Generic模型中设置一个is_deleted布尔标志,并在(post|pre)_delete信号中将其设置。

另一个人评论了相同的解决方案,然后删除了它,因为它不起作用,除非你的无效,因为这将检查object_id字段是否为空,而不是由该ID引用的相关对象。无论如何,如果您修复它,它仍会产生与原始问题中发布的相同异常。在这里过滤或排除没有任何区别。FWIW,检查= Noneisnull = True相同。 - Ben
那么Ben,"non-null"的content_object是什么意思呢?它只是你的数据库中两个实际字段的一个(足够薄的)包装器。如果你想要排除没有引用任何其他模型的Generics,我给出的查询就是有效的。或者请在答案中进一步明确你对"null object"的定义。 - Alex Morozov
嗨,Alex,我已经澄清了问题。非空意味着实际对象,您可以使用content_type_id定义的表中的id检索该对象。例如,如果您对已删除的对象具有通用关系。 - Ben
好的,现在我明白了。请查看我的更新想法。 - Alex Morozov
1
在第二个版本中,排除应该是在 object_id 而不是 pk: .exclude(object_id__in=ct.model_class().objects.all())? (因为 pk 将会参考 Generic 对象本身的id,而不是你想要检查的相关模型的id。) - medmunds
显示剩余3条评论

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