Аннотируйте модели кверисетов полями из "сквозной" модели

Кажется, что это должно быть просто, но я не смог найти ответ, несмотря на множество гуглений.

У меня есть две модели, объединенные отношением "многие ко многим" через третью модель, которая имеет некоторые дополнительные поля.

class User(AbstractUser):
    pass

class Project(models.Model):
    name = models.CharField(max_length=100, unique=True, blank=False)
    description = models.CharField(max_length=255, blank=True)
    users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='projects', through='UserProject')

    # additional project fields

class UserProject(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    favorite = models.BooleanField(default=False)
    last_used = models.DateTimeField(default=timezone.now)

Я хочу получить доступ к проектам данного пользователя вместе с полями favorite и last_used для этого пользователя для каждого проекта.

Я не могу понять, как это сделать, используя аннотации или любой другой метод при вызове user.projects. Если я вызываю сквозное отношение напрямую (т.е. user.userproject_set), я могу добавить поля из проекта одним sql-запросом, вызвав, например, user.userproject_set.select_related('project').annotate(name='project.name'), но тогда модели в результирующем наборе запросов будут UserProjects, а не Projects, что не то, что я хочу.

Написать sql-запрос для выполнения того, что я хочу, было бы тривиально (в основном это был бы тот же запрос, который Django создает для присоединения полей Project к кверисету UserProject). Есть ли способ заставить Django добавить поля из сквозной таблицы к моделям в кверисете, доступ к которым осуществляется через одну сторону отношения "многие-ко-многим"?

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

user.projects.all()

После перебора множества различных вариантов, включая FilterRelation, я нашел способ получить желаемое, используя метод QuerySet extra для добавления нужных мне полей из промежуточной таблицы:

user.projects.extra(select={
    'favorite': 'app_userproject.favorite',
    'last_used': 'app_userproject.last_used'
})

Не требуется никаких дополнительных SQL-запросов/заходов в базу данных, никаких новых объединений и никаких изменений в предложении where, потому что соответствующие объединения и условия уже являются частью запроса, который Django строит для получения дочерних моделей из отношения "многие-ко-многим". Именно поэтому кажется, что в Django должен быть какой-то способ сделать это, не прибегая к extra - который Django грозится обесценить.

Вернуться на верх