如何在Django中遍历GenericForeignKey?

发布于 2021-01-29 15:23:03

我将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?

我究竟做错了什么?


对未来读者的警告说明: 在Django 1.9related_query_nameGenericRelation, 模板化无法正常工作。

增加了在Django
1.10是related_query_name现在支持使用和“%(类)S”串应用标签和类插值“%(app_label)S”,后修正#25354被合并。

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

关注者
0
被浏览
48
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    通常,无法GenericForeignKey按照您尝试的方式在此方向上遍历a
    。AGenericForeignKey可以指向您的应用程序中的任何模型,不仅指向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'))
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看