Все значения поля many to many: Django
У меня две модели:
class Author(models.Model);
name = models.CharField(max_length=255)
class Book(models.Model):
title = models.CharField(max_length=255)
authors = models.ManyToManyField(Author, null=True, blank=True)
Теперь я хочу получить информацию обо всех книгах. Итак, я сделал:
book_info = Book.objects.all().values('title', 'authors__name')
И, он дает выход вроде (для 1 книги с 2 авторами):
[{'title': u'book1', 'authors__name': u'author1'},{'title': u'book1', 'authors__name': u'author2'}]
То, что я хотел, было что-то вроде:
[{'title': u'book1', 'authors': [{'name':u'author1'},{'name':u'author2'}]}]
У меня может быть больше полей в модели автора, поэтому хотелось бы также получить эти поля.
Могу ли я сделать это в одном запросе?
Что я могу сделать, чтобы получить что-то вроде желаемого результата?
Ответы
Ответ 1
Django 1.4
Отличный вопрос, используйте prefetch_related:
In [3]: [{'name': b.name, 'authors': [a.name for a in b.authors.all()]} for b in Book.objects.prefetch_related('authors')]
(0.000) SELECT "test_app_book"."id", "test_app_book"."name" FROM "test_app_book"; args=()
(0.000) SELECT ("test_app_book_authors"."book_id") AS "_prefetch_related_val", "test_app_author"."id", "test_app_author"."name" FROM "test_app_author" INNER JOIN "test_app_book_authors" ON ("test_app_author"."id" = "test_app_book_authors"."author_id") WHERE "test_app_book_authors"."book_id" IN (1, 2); args=(1, 2)
Out[3]:
[{'authors': [u'a', u'b'], 'name': u'book'},
{'authors': [u'b'], 'name': u'test'}]
Django 1.3
prefetch_related был введен в Django 1.4. Для Django 1.3 вам нужно django-selectreverse:
In [19]: [{'name': b.name, 'authors': [a.name for a in b.authors_prefetch]} for b in Book.objects.select_reverse({'authors_prefetch': 'authors'})]
(0.000) SELECT "test_app_book"."id", "test_app_book"."name" FROM "test_app_book"; args=()
(0.001) SELECT (test_app_book_authors.book_id) AS "main_id", "test_app_author"."id", "test_app_author"."name" FROM "test_app_author" INNER JOIN "test_app_book_authors" ON ("test_app_author"."id" = "test_app_book_authors"."author_id") WHERE "test_app_book_authors"."book_id" IN (1, 2); args=(1, 2)
Out[19]:
[{'authors': [u'a', u'b'], 'name': u'book'},
{'authors': [u'b'], 'name': u'test'}]
Используя django-selectreverse:
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, null=True, blank=True)
objects = ReverseManager()
Ответ 2
Чтобы избежать выполнения двух запросов, как в описательном ответе от @jpic, вы можете просто вручную объединить свои результаты после этого. Мне это кажется немного взломанным, но оно работает.
def merge_values(values):
grouped_results = itertools.groupby(values, key=lambda value: value['id'])
merged_values = []
for k, g in grouped_results:
groups = list(g)
merged_value = {}
for group in groups:
for key, val in group.iteritems():
if not merged_value.get(key):
merged_value[key] = val
elif val != merged_value[key]:
if isinstance(merged_value[key], list):
if val not in merged_value[key]:
merged_value[key].append(val)
else:
old_val = merged_value[key]
merged_value[key] = [old_val, val]
merged_values.append(merged_value)
return merged_values
book_info = marge_values(Book.objects.all().values('title', 'authors__name'))
Функция merge_values берет из этот смысл