Выберите последнюю запись ForeignKey и верните как вложенный объект с Django ORM - PullRequest
0 голосов
/ 03 марта 2020

Я пытаюсь вернуть запрос через 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.

1 Ответ

0 голосов
/ 03 марта 2020

Ну, вы не можете точно добавить вложенный объект, просто добавьте некоторые значения из Comment объекта, используя Subquery с таким набором запросов:

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...