【问题标题】:Prefetch related after many-to-one relation多对一关系后的预取相关
【发布时间】:2017-10-19 14:43:43
【问题描述】:

我有以下(简化的)模型结构:

class Article(Model):
    @property
    def article_number(self) -> str:
        return self.attributes.get(masterdata_type__code='article_number').value

class Attribute(Model):
    article = ForeignKey(Article, null=True, blank=True, related_name='attributes')
    masterdata_type = ForeignKey(Type, related_name='attributes')
    value = CharField(max_length=256, blank=True, db_index=True)

class Type(Model):
    code = models.CharField(max_length=32)

在管理面板中,我想显示 100 篇文章的列表并显示其 article_number。现在这会导致 100 次数据库调用(我可以使用 Silk 监控看到),所以我想预取这些对象。我在管理面板中尝试了以下解决方案,但都不起作用。在这里减少数据库调用次数的正确方法是什么?

我正在使用带有 PostgreSQL 后端的 Django 1.8.14。

不起作用的解决方案:

def get_queryset(self, request):
    queryset = super(ArticleSetAdmin, self).get_queryset(request)
    return queryset.prefetch_related('attributes__masterdata_type')

还有 100 次数据库调用。

def get_queryset(self, request):
    queryset = super(ArticleSetAdmin, self).get_queryset(request)
    return queryset.prefetch_related(
        Prefetch('attributes', queryset=Attribute.objects.select_related('masterdata_type')))

还有 100 次数据库调用。

def get_queryset(self, request):
    queryset = super(ArticleSetAdmin, self).get_queryset(request)
    return queryset.prefetch_related(
        Prefetch('attributes', queryset=Attribute.objects.prefetch_related('masterdata_type')))

还有 100 次数据库调用。

def get_queryset(self, request):
    queryset = super(ArticleSetAdmin, self).get_queryset(request)
    return queryset.select_related('attributes__masterdata_type')

错误,因为属性与文章是多对一关系。

【问题讨论】:

  • 请贴出全文模型
  • 您需要显示更多代码,因为它不是导致问题的 get_queryset。您是在视图或模板中查找相关对象吗?当您获取相关对象时,您是否在过滤某些内容 - 这些可能会触发额外的数据库调用。
  • 最好的解决方案是安装 django-debug-toolbar,看看额外的查询是什么以及触发它们的代码行。

标签: django django-admin django-queryset


【解决方案1】:

您需要过滤Prefetch对象中的查询集,否则article_number中的get()会导致新的查询。

def get_queryset(self, request):
    queryset = super(ArticleSetAdmin, self).get_queryset(request)
    return queryset.prefetch_related(
        Prefetch(
            'attributes',
            queryset=Attribute.objects.filter(masterdata_type__code='article_number'),
            to_attr=article_number_attributes,
        )
    )

我使用了to_attr 参数,这样我们就不会用过滤后的查询集覆盖attributes。然后,您需要更新您的 article_number 以使用新属性。

@property
def article_number(self):
    return self.article_number_attributes[0].value

【讨论】:

  • 我明白了,我会试试的!如果我想在管理面板中显示两个属性(比如 article_number 和 article_category),我应该如何更改此示例?我可以将过滤器与masterdata_type__code__in=['article_number', 'article_category'] 一起使用,但是如何在模型Article 上的正确getter 方法中引用正确的属性?我可以将多个属性存储为字典吗?
  • 一种选择是使用具有两个不同查询集的两个 Prefetch 对象,例如queryset.prefetch_related(Prefetch(...), Prefetch(...))。如果您在 Prefetch 的查询集中使用 masterdata_type__code__in=['article_number', 'article_category'],那么您必须遍历您的 getter 中的列表并使用正确的项目(您可能必须将 select_related 添加到 Prefetch 的查询集中以防止多次查询)。 Prefetch api 不允许您自定义数据的格式。如果你想要一本字典,你必须创建它。
猜你喜欢
  • 2017-07-18
  • 1970-01-01
  • 2021-11-28
  • 2012-06-14
  • 2019-07-17
  • 2020-07-27
  • 1970-01-01
  • 1970-01-01
  • 2012-12-21
相关资源
最近更新 更多