У меня есть сайт, на котором пользователи могут видеть список фильмов и создавать отзывы для них.
Пользователь должен иметь возможность видеть список всех фильмов. Кроме того, ЕСЛИ они просмотрели фильм, они должны иметь возможность увидеть оценку, которую они дали. Если нет, фильм просто отображается без оценки.
Они не заботятся о оценках, предоставляемых другими пользователями.
Рассмотрим следующее models.py
from django.contrib.auth.models import User
from django.db import models
class Topic(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Record(models.Model):
user = models.ForeignKey(User)
topic = models.ForeignKey(Topic)
value = models.TextField()
class Meta:
unique_together = ("user", "topic")
То, что я действительно хочу, это
select * from bar_topic
left join (select topic_id as tid, value from bar_record where user_id = 1)
on tid = bar_topic.id
Рассмотрим следующий test.py
для контекста:
from django.test import TestCase
from bar.models import *
from django.db.models import Q
class TestSuite(TestCase):
def setUp(self):
t1 = Topic.objects.create(name="A")
t2 = Topic.objects.create(name="B")
t3 = Topic.objects.create(name="C")
# 2 for Johnny
johnny = User.objects.create(username="Johnny")
johnny.record_set.create(topic=t1, value=1)
johnny.record_set.create(topic=t3, value=3)
# 3 for Mary
mary = User.objects.create(username="Mary")
mary.record_set.create(topic=t1, value=4)
mary.record_set.create(topic=t2, value=5)
mary.record_set.create(topic=t3, value=6)
def test_raw(self):
print('\nraw\n---')
with self.assertNumQueries(1):
topics = Topic.objects.raw('''
select * from bar_topic
left join (select topic_id as tid, value from bar_record where user_id = 1)
on tid = bar_topic.id
''')
for topic in topics:
print(topic, topic.value)
def test_orm(self):
print('\norm\n---')
with self.assertNumQueries(1):
topics = Topic.objects.filter(Q(record__user_id=1)).values_list('name', 'record__value')
for topic in topics:
print(*topic)
BOTH тесты должны печатать тот же самый результат, однако только исходная версия выводит правильную таблицу результатов:
raw --- A 1 B None C 3
вместо этого орма возвращает этот
orm --- A 1 C 3
Любая попытка объединить остальные темы, те, которые не имеют отзывов от пользователя "johnny", приводят к следующему:
orm
---
A 1
A 4
B 5
C 3
C 6
Как я могу выполнить простое поведение необработанного запроса с помощью ORM Django?
edit: Этот вид работ, но кажется очень плохим:
topics = Topic.objects.filter(record__user_id=1).values_list('name', 'record__value') noned = Topic.objects.exclude(record__user_id=1).values_list('name') for topic in chain(topics, noned): ...
edit: Это работает немного лучше, но все равно плохо:
topics = Topic.objects.filter(record__user_id=1).annotate(value=F('record__value')) topics |= Topic.objects.exclude(pk__in=topics)
orm --- A 1 B 5 C 3