【问题标题】:How to traverse a GenericForeignKey in Django?如何在 Django 中遍历 GenericForeignKey?
【发布时间】:2016-07-09 22:42:23
【问题描述】:

我正在使用 Django v1.9.4 和 PostgreSQL 9.2.14。使用以下型号:

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType

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

class Bar(models.Model):
    foos = GenericRelation(Foo, related_query_name='bars')
    class Meta:
        abstract = True

class BarX(Bar):
    name = models.CharField(max_length=10, default='bar x')

class BarY(Bar):
    name = models.CharField(max_length=10, default='bar y')

创建一些实例来演示我的问题:

>>> bar_x = BarX.objects.create()
>>> bar_y = BarY.objects.create()
>>> foo1 = Foo.objects.create(bar=bar_x)
>>> foo2 = Foo.objects.create(bar=bar_y)
>>> foo1.bar.name
u'bar x'
>>> foo2.bar.name
u'bar y'

我无法在 django 中遍历 GFK,尝试过滤会引发异常,并显示一条建议添加 GenericRelation 的消息。但是使用通用关系,通过相关的查询名称bars,不能可靠地工作。 例如:

>>> [foo.bar.name for foo in Foo.objects.all()]
[u'bar x', u'bar y']  # in a pure python loop, it's working
>>> Foo.objects.filter(bar__name='bar x')
FieldError: Field 'bar' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
>>> Foo.objects.values_list('bars__name', flat=1)
[None, u'bar y']   # but why None is returned in here?
>>> Foo.objects.filter(bars__name='bar x')
[]  # why no result here?
>>> Foo.objects.filter(bars__name='bar y')
[<Foo: Foo object>]  # but this one works?

我做错了什么?


给未来读者的警告: GenericRelation 上的模板 related_query_name 在 Django 1.9 上无法正常工作。

fix#25354 合并后,在 Django 1.10 中添加了 related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings

如果您使用的是 Django 1.10,则可以继续将 GenericRelation 放在抽象基类上,并将其模板化为 related_query_name='%(app_label)s_%(class)s',以确保子类之间的唯一性。

【问题讨论】:

    标签: python django postgresql generic-foreign-key generic-relations


    【解决方案1】:

    一般来说,不可能以您尝试的方式在这个方向上遍历GenericForeignKeyGenericForeignKey 可以指向应用程序中的任何模型,不仅是 Bar 及其子类。出于这个原因,Foo.objects.filter(bar__somefield='some value') 无法知道您目前想到的目标模型,因此无法判断目标模型具有哪些字段。事实上,在执行此类查询时,无法选择要连接的数据库表——它可以是任何表,具体取决于Foo.content_type 的值。

    如果您确实想在连接中使用通用关系,则必须在该关系的另一端定义 GenericRelation。这样你就可以让 Django 知道它应该在另一边寻找哪个模型。

    例如,您可以像这样创建 BarXBarY 模型:

    class BarX(Bar):
        name = models.CharField(max_length=10, default='bar x')
        foos = GenericRelation(Foo, related_query_name='bar_x')
    
    class BarY(Bar):
        name = models.CharField(max_length=10, default='bar y')
        foos = GenericRelation(Foo, related_query_name='bar_y')
    

    如果您这样做,那么您可以执行如下查询:

    Foo.objects.filter(bar_x__name='bar x')
    Foo.objects.filter(bar_y__name='bar y')
    

    但是,您必须选择一个目标模型。这是一个你无法以任何方式真正克服的限制。每个数据库连接都需要提前知道它对哪些表进行操作。

    如果您绝对需要同时允许 BarXBarY 作为目标,您应该能够使用 Q 表达式在查询过滤器中明确列出它们:

    Foo.objects.filter(Q(bar_x__name='bar x') | Q(bar_y__name='bar y'))
    

    【讨论】:

    • OK 所以这似乎是由于 sql join 的限制。但是对于我的问题中的示例,为什么 django 完全允许查询?它不应该引发异常而不是返回不正确的结果吗?
    • 是的,这看起来像一个错误,我认为它不应该允许您进行此类查询。您能否将这些查询生成的 SQL 添加到您的问题中?
    • 我对您的解决方案进行了一些尝试,发现了另一个有趣的事情,可能没有必要将GenericRelation 字段放在每个具有唯一related_query_name 的子类上。实际上,您可以将其保留在基类中并像 related_query_name='%(app_label)s_%(class)s' 一样对其进行模板化,以确保唯一性。
    • 是的,我认为可能是这种情况,但我不确定它是否也适用于GenericRelations,所以我选择不提它......很高兴看到它确实有效。
    猜你喜欢
    • 2020-11-26
    • 2015-02-02
    • 2021-08-21
    • 1970-01-01
    • 2011-04-26
    • 2012-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多