Django менеджер аннотирует первый элемент m2m как fk - PullRequest
1 голос
/ 16 июня 2020

Допустим, у меня есть эти модели.

class AdventureWaypoint(models.Model):
    adventure = models.ForeignKey("Adventure", on_delete=models.CASCADE)
    waypoint = models.ForeignKey("Waypoint", on_delete=models.CASCADE)
    created_timestamp = models.DateTimeField(auto_now_add=True)

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

class Adventure(models.Model):
    waypoints = models.ManyToManyField("Waypoint", through='AdventureWaypoint', related_name='waypoints')
    objects = AdventureManager()

Как я могу написать менеджер, который аннотирует first_waypoint для каждого набора запросов?

Я пробовал следующее, но, к сожалению, Subquery работает не так, как ожидалось. Не удалось также использовать F.

class AdventureManager(models.Manager):
    def get_queryset(self):
        qs = super(AdventureManager).get_queryset()
        first_waypoint = AdventureWaypoint.objects.filter(adventure_id=OuterRef("id")).order_by('created_timestamp').first().waypoint
        return qs.annotate(first_waypoint=Subquery(first_waypoint, output_field=models.ForeignKey("Waypoint", on_delete=models.CASCADE, null=True, blank=True)))

Должен ли я прибегать к необработанному SQL или как это можно сделать с помощью Django ORM?

Примечание: я не хочу использовать @property, поскольку я хочу использовать это аннотированное поле в наборах запросов, например, для фильтрации.

1 Ответ

1 голос
/ 16 июня 2020

Вы можете попробовать это (согласно документации ):

first_waypoint = AdventureWaypoint.objects.filter(adventure_id=OuterRef("id")).order_by('created_timestamp')
return qs.annotate(first_waypoint=Subquery(first_waypoint.values('waypoint')[:1]))

Обновить

Вы не можете добавить реальный экземпляр, подобный этому. Вы можете получить только такое имя:

return qs.annotate(first_waypoint=Subquery(first_waypoint.values('waypoint__name')[:1]))

Или вы можете использовать prefetch_related для извлечения данных, чтобы уменьшить количество обращений к базе данных и получить доступ к значению через для l oop:

for adventure in Adventure.objects.prefetch_related('waypoints'):
   print(adventure.waypoints.first())
...