Я наткнулся на эту проблему, которую не смог решить, не прибегая к необработанному SQL, но я не хотел переписывать весь запрос.
Ниже приведено описание того, как можно дополнить набор запросов с помощью внешнегоraw sql, без необходимости заботиться о реальном запросе, который генерирует набор запросов.
Вот типичный сценарий: у вас есть сайт, похожий на reddit, с моделью LinkPost и режимом UserPostVote, например:
class LinkPost(models.Model):
some fields....
class UserPostVote(models.Model):
user = models.ForeignKey(User,related_name="post_votes")
post = models.ForeignKey(LinkPost,related_name="user_votes")
value = models.IntegerField(null=False, default=0)
где таблица userpostvote собирает голоса пользователей по сообщениям.Теперь вы пытаетесь отобразить главную страницу для пользователя с приложением нумерации страниц, но вы хотите, чтобы стрелки были красными для сообщений, за которые проголосовал пользователь.
Сначала вы получаете сообщения для страницы:
post_list = LinkPost.objects.all()
paginator = Paginator(post_list,25)
posts_page = paginator.page(request.GET.get('page'))
, так что теперь у вас есть QuerySet posts_page, сгенерированный пагинатором django, который выбирает записи для отображения.Как нам теперь добавить аннотацию голоса пользователя к каждому сообщению перед его отображением в шаблоне?
Вот где это сложно, и мне не удалось найти чистое решение ORM.select_related не позволит вам получать только голоса, соответствующие вошедшему в систему пользователю, а циклический просмотр постов будет выполнять групповые запросы вместо одного и делать все это в прямом смысле: мы не можем использовать набор запросов из приложения разбивки на страницы.
Итак, вот как я это делаю:
q1 = posts_page.object_list.query # The query object of the queryset
q1_alias = q1.get_initial_alias() # This forces the query object to generate it's sql
(q1str, q1param) = q1.sql_with_params() #This gets the sql for the query along with
#parameters, which are none in this example
теперь у нас есть запрос для набора запросов, и просто оберните его, добавив псевдоним и оставив внешнее соединение с ним:
q2_augment = "SELECT B.value as uservote, A.*
from ("+q1str+") A LEFT OUTER JOIN reddit_userpostvote B
ON A.id = B.post_id AND B.user_id = %s"
q2param = (request.user.id,)
posts_augmented = LinkPost.objects.raw(q2_augment,q1param+q2param)
вуаля!Теперь мы можем получить доступ к post.uservote для сообщения в расширенном наборе запросов.И мы просто попали в базу данных одним запросом.