Skip to main content
 首页 » 编程设计

python之Django:select_related 与多表继承

2024年12月31日23tintown

我有以下模型结构:

class A(models.Model): 
    prop_a = models.CharField(max_length=255) 
 
class B(A): 
    prop_b = models.CharField(max_length=255) 
 
class C(A): 
    prop_c = models.CharField(max_length=255) 
 
class D(models.Model): 
    fk = models.ForeignKey('A') 

本质上,我有一个模型 (D),它有一个外键指向一个“抽象”模型 (A),该模型由 B 和 C。

现在,当我运行 D.objects.all().select_related() 时,只会查询 A 的属性。我假设这是因为在查询时,Django 不知道 fk 是哪个子类的实例(在我当前的结构中我也不知道)。

有没有办法在不改变模型结构的情况下查询子类的属性?

我还尝试了 prefetch_related 并尝试使用来自 django-model-utils 的 InheritanceManager , 都无济于事。

编辑:为了澄清,我正在寻找一种方法来迭代 D 的对象,访问 fk 的具体值(是 B 或 C 对象),同时只访问数据库一次。

运行 D.objects.all().select_related() 生成查询

'SELECT "mapping_d"."id", "mapping_d"."fk_id", "mapping_a"."id", "mapping_a"."prop_a"FROM "mapping_d"INNER JOIN "mapping_a"ON ( "mapping_d"."fk_id"= "mapping_a"."id")'

假设其中一个是 D 类型的对象 d ,其属性 fk 是 C 类型.

现在,当我访问值 d.fk.c 时,Django 会运行​​一个额外的查询来获取 C 的属性:

'SELECT "mapping_a"."id", "mapping_a"."prop_a", "mapping_c"."a_ptr_id", "mapping_c"."prop_c"FROM "mapping_c"INNER JOIN "mapping_a"ON ( "mapping_c"."a_ptr_id"= "mapping_a"."id") WHERE "mapping_c"."a_ptr_id"= 3'

我想避免对 D 的每个对象进行这种额外的查询。以建议的方式使用 InheritanceManager 似乎无法做到这一点。

请您参考如下方法:

我使用 Inheritance Manager通常并按预期运行。

1) 记得在A上设置继承管理器:

class A(models.Model): 
    # ... 
    objects = InheritanceManager() 

2) 进行正确的查询:

some_d_object = D.objects.get( pk = 1 ) 
related = some_d_object.fk.all().select_related() 

请注意,您的查询 D.objects.all().select_related() 将返回 D 对象。没有人从 D 继承,那么,没有子类,只有 D 对象。

3) 享受继承。

编辑 我编辑这个问题是因为 OP 评论。

With this pattern, Django performs an additional DB query when I access the related object. Since I have many D objects, this gets quite expensive. Do you know if there is any way to fetch them all at once? –

好吧,让我们尝试写一个答案,因为我真的不明白 OP 正在寻找的女巫对象。问题很好,因为他几乎写了a Minimal, Complete, and Verifiable example但我不知道预期值,让我们尝试一些样本。

1) 如果你想要所有对象,就像:

all_objects = A.objects.all().select_subclasses() 

2) 如果你想过滤,那就过滤吧。我写了一个例子:

filtered_objects = A.objects.filter( d__pk = 1 ).select_subclasses() 

3) 在单个查询中获取所有子类以及 D 类:

不幸的是你不能应用prefetch_related(也许你可以,但我不知道该怎么做)。然后您需要使用 itertools 处理数据。

all_objects_and_D = list( 
                     A 
                     .objects 
                     .select_related('D') 
                     .select_subclasses() 
                     .order_by( 'D__pk' ) 
                     ) 
#at this point use itertools to massage your data 
keyfunc = lambda x: x.D 
data = [  { k, list(g) }  
          for k, g in  
          itertools.groupby(all_objects_and_D, key=keyfunc ) 
       ]