【问题标题】:Select latest entry of ForeignKey and return as nested object with Django ORM选择 ForeignKey 的最新条目并使用 Django ORM 作为嵌套对象返回
【发布时间】:2020-06-15 09:46:12
【问题描述】:

我正在尝试通过 Django ORM 返回一个导致该对象的查询:

[
  ...,
  {
    "id": 1,
    "product_id": 1,
    "created_at": "2020-03-03T03:34:59.275941Z",
    "updated_at": "2020-03-03T03:34:59.291653Z",
    "latest_comment": {
      "comment": "Leaving a comment",
      "author": {
        "id": 1,
        "name": "John Lennon",
        "profile": {
          "avatar": null
        }
      },
      "updated_at": "2020-03-03T03:34:59.329229Z"
    }
  }
]

我有以下型号:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    avatar = models.ImageField(upload_to="avatar", blank=True)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = AutoDateTimeField(default=timezone.now)

class Product(models.Model):
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = AutoDateTimeField(default=timezone.now)

class Note(models.Model):
    product = models.ForeignKey(Product, related_name="notes", on_delete=models.CASCADE)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = AutoDateTimeField(default=timezone.now)

class Comment(models.Model):
    note = models.ForeignKey(Note, related_name="comments", on_delete=models.CASCADE)
    comment = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = AutoDateTimeField(default=timezone.now)

    class Meta:
        get_latest_by = "updated_at"

我已经能够用下面的代码实现上面的结构,但是它引入了N + 1问题。

product = Product.objects.get(id=1)

notes = []
for note in product.notes.all():
    try:
        note.latest_comment = note.comments.latest()
    except ObjectDoesNotExist:
        note.latest_comment = None
    notes.append(note)

我一直在玩prefetch_related 以及Max 函数的注释,但没有取得多大成功。我还能够编写一个原始查询,可以使用一些 Postgres 和 json_build_object 函数来重现这一点。我试图让 ORM 工作的原因是我想使用CursorPagination,而使用.raw() 或RawSql() 似乎与CursorPagination 不兼容。

【问题讨论】:

    标签: django django-rest-framework django-orm


    【解决方案1】:

    好吧,您不能完全添加嵌套对象,而是使用 Subquery 和这样的查询集从 Comment 对象添加一些值:

    from django.db.models import OuterRef, Subquery
    newest = Comment.objects.filter(note=OuterRef('pk')).order_by('-created_at')
    notes = product.notes.annotate(
        comment_author=Subquery(
            newest.values('author_id')[:1]
            )
        ).annotate(
            comment_comment=Subquery(
                newest.values('comment')[:1]
            )
        )
    # and so on
    

    或者,使用prefetch_related 来减少数据库命中,如下所示:

    for note in product.notes.prefetch_related('comments').all():
        note.latest_comment = note.comments.order_by('-created_at').last()
        notes.append(note)
    

    【讨论】:

      猜你喜欢
      • 2017-03-03
      • 1970-01-01
      • 2010-10-15
      • 1970-01-01
      • 2015-01-12
      • 1970-01-01
      • 1970-01-01
      • 2013-04-16
      • 2011-10-25
      相关资源
      最近更新 更多