python - Django 查询按多对多关系最新版本的字段排序

标签 python django django-queryset django-orm

假设我有以下 Django 模型:

class Toolbox(models.Model):
    class Meta:
      constraints = [
          models.UniqueConstraint(
              fields=["name", "version"],
              name="%(app_label)s_%(class)s_unique_name_version",
          )
      ]

    name = models.CharField(max_length=255)
    version = models.PositiveIntegerField()
    tools = models.ManyToManyField("Tool", related_name="toolboxes")

    def __str__(self) -> str:
        return f"{self.name}"

class Tool(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self) -> str:
        return f"{self.name}"

我想编写一个查询来获取所有工具并返回按其最新工具箱名称排序的工具。我知道我可以使用以下代码实现此目的:

tools = Tool.objects.all()
for tool in tools:
    tool.latest_toolbox = tool.toolboxes.order_by("-version").first()

tools = sorted(tools, key=lambda x: x.latest_toolbox.name)

这是一个用 pytest-django 编写的单元测试来证明这是有效的:

from pytest_django.asserts import assertQuerysetEqual

def test_sort_tools_by_latest_toolbox_name():
    tool1 = Tool.objects.create(name="Tool 1")
    tool2 = Tool.objects.create(name="Tool 2")
    toolbox1_v1 = Toolbox.objects.create(name="A", version=1)
    toolbox1_v1.tools.add(tool1)
    toolbox1_v2 = Toolbox.objects.create(name="Z", version=2)
    toolbox1_v2.tools.add(tool1)
    toolbox2_v1 = Toolbox.objects.create(name="B", version=1)
    toolbox2_v1.tools.add(tool2)

    tools = Tool.objects.all()
    for tool in tools:
        tool.latest_toolbox = tool.toolboxes.order_by("-version").first()
    
    tools = sorted(tools, key=lambda x: x.latest_toolbox.name)
    assertQuerysetEqual(tools, [tool2, tool1])

但是,Tool 表有数千条记录,这需要几分钟才能执行。我可以编写更快的查询吗?

我已经尝试了以下方法,但它返回了重复项并且没有正确排序工具:

Tool.objects.order_by("toolboxes__name")
# <QuerySet [<Tool: Tool 1>, <Tool: Tool 2>, <Tool: Tool 1>]>

最佳答案

使用 Subquery 尝试这种方法和 OuterRef :

from django.db.models import OuterRef, Subquery

toolbox_subquery = Toolbox.objects.filter(tools=OuterRef('pk')).order_by('-version')
tools_qs = Tool.objects.order_by(Subquery(toolbox_subquery.values('name')[:1]))

如果您需要最新工具箱的名称而不是为了订购,您可以将其放在带注释的字段中:

tools_qs = Tool.objects.annotate(latest_toolbox_name=Subquery(toolbox_subquery.values('name')[:1])).order_by('latest_toolbox_name')

然后,每个工具都会有一个带注释的字段 latest_toolbox_name,其中包含最新版本的关联工具箱的名称。

关于python - Django 查询按多对多关系最新版本的字段排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69114026/

相关文章:

python - 如何设置从第三列绘制值的条件? python / Pandas

python - 将路径/文件列表复制到目录

django - "Model object has no attribute ' 保存 '"

python - Django 查询集优化 - 防止选择带注释的字段

Python list.insert() 多索引/列表列表

javascript - 使用 webkitbrowser 获取通过注入(inject)的 javascript 修改的输入文本的值时出错

django - 如何在没有 HTML 标记的情况下提取 Django 表单错误消息

python - Django: 'User' 对象不支持索引

django ModelMultipleChoiceField 查询集/过滤器已关联的对象

django - 从按最新排序的查询集中获取不同的 Django 对象