首页 > 解决方案 > 选择 ForeignKey 的最新条目并使用 Django ORM 作为嵌套对象返回

问题描述

我正在尝试通过导致此对象的 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()orRawSql()似乎不兼容CursorPagination.

标签: djangodjango-rest-frameworkdjango-orm

解决方案


好吧,您不能完全添加嵌套对象,而是Comment使用Subquerywith queryset 从对象中添加一些值,如下所示:

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)

推荐阅读