如何使Django ManyToMany'通过'查询更有效率? [英] How do I make Django ManyToMany 'through' queries more efficient?

查看:104
本文介绍了如何使Django ManyToMany'通过'查询更有效率?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个带有'through'类的ManyToManyField,并且在获取事物列表时会产生大量查询。我想知道是否有更有效的方式。



例如,这里是一些简化的类,它们描述了Books及其几个作者,它们通过一个Role类(定义角色如Editor,Illustrator等):

  class Person(models.Model):
first_name = models.CharField(max_length = 100)
last_name = models.CharField(max_length = 100)

@property
def full_name(self):
return''。 join([self.first_name,self.last_name,])

class Role(models.Model):
name = models.CharField(max_length = 50)
person = models .ForeignKey(Person)
book = models.ForeignKey(Book)

class Book(models.Model):
title = models.CharField(max_length = 255)
author = models.ManyToManyField(Person,through ='Role')

@property
def author_names(self):
names = []
for role in self.role_set.all():
person_name = role.person.full_name
如果role.name:
person_name + ='(%s)'%(role.name)
names.append(person_name)
返回','.join(name)

如果我打电话给Book.authors_names(),那么我可以得到一个这样的字符串:


John Doe(编辑),Fred Bloggs,Billy Bob(Illustrator)


它可以正常工作,但是它可以为一个查询获取该书的角色,然后再为每个人查询另一个查询。如果我正在显示图书列表,这将加起来很多查询。



有没有办法更有效地这样做,每本书的单个查询,加入?或者是使用批量选择的唯一方法?



(对于奖励积分...我的编码作者__names()看起来有点笨重 - 有没有办法使它更优雅的Python-esque?)

解决方案

这是Django经常遇到的一种模式。创建属性(如 author_name )非常容易,当您显示一本书时,它们会很好地运行,但是当您想要为许多书籍使用该属性时,查询数量会爆炸在一个页面上。



首先,您可以使用 select_related ,以防止每个人查找

  for role in self.role_set.all()。select_related(depth = 1):
person_name = role.person.full_name
如果role.name:
person_name + ='(%s)'%(role.name,)
names.append(person_name)
return','.join(names)

但是,这并不能解决查找每本书的角色的问题。



如果您正在显示书籍列表,您可以在一个查询中查找所有图书的角色,然后缓存。

 >>> books = Book.objects.filter(** your_kwargs)
>>> roles = Role.objects.filter(book_in = books).select_related(depth = 1)
>>> roles_by_book = defaultdict(list)
>>>角色扮演角色:
... roles_by_book [role.book] .append(books)

然后,您可以通过 roles_by_dict 字典访问图书的角色。

 >>>在书中的书籍:
... book_roles = roles_by_book [book]

你会有重新考虑您的 author_name 属性以使用这样的缓存。








为角色添加一个方法来呈现全名和角色名称。

 类角色(models.Model):
...
@property
def name_and_role(self):
out = self.person.full_name
if self.name:
out + ='(%s)'%role.name
return out
/ pre>

author_names 折叠到类似于保罗的建议的一个班轮

  @property 
def author_names(self):
return','.join([role.name_and_role for role in self.role_set.all ()])


I'm using a ManyToManyField with a 'through' class and this results in a lot of queries when fetching a list of things. I'm wondering if there's a more efficient way.

For example here are some simplified classes describing Books and their several authors, which goes through a Role class (to define roles like "Editor", "Illustrator", etc):

class Person(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    @property
    def full_name(self):
        return ' '.join([self.first_name, self.last_name,])

class Role(models.Model):
    name = models.CharField(max_length=50)
    person = models.ForeignKey(Person)
    book = models.ForeignKey(Book)

class Book(models.Model):
    title = models.CharField(max_length=255)
    authors = models.ManyToManyField(Person, through='Role')

    @property
    def authors_names(self):
        names = []
        for role in self.role_set.all():
            person_name = role.person.full_name
            if role.name:
                person_name += ' (%s)' % (role.name,)
            names.append(person_name)
        return ', '.join(names)

If I call Book.authors_names() then I can get a string something like this:

John Doe (Editor), Fred Bloggs, Billy Bob (Illustrator)

It works fine but it does one query to get the Roles for the book, and then another query for every Person. If I'm displaying a list of Books, this adds up to a lot of queries.

Is there a way to do this more efficiently, in a single query per Book, with a join? Or is the only way to use something like batch-select?

(For bonus points... my coding of authors_names() looks a bit clunky - is there a way to make it more elegantly Python-esque?)

解决方案

This is a pattern I come across often in Django. It's really easy to create properties such as your author_name, and they work great when you display one book, but the number of queries explodes when you want to use the property for many books on a page.

Firstly, you can use select_related to prevent the lookup for every person

  for role in self.role_set.all().select_related(depth=1):
        person_name = role.person.full_name
        if role.name:
            person_name += ' (%s)' % (role.name,)
        names.append(person_name)
    return ', '.join(names)

However, this doesn't solve the problem of looking up the roles for every book.

If you are displaying a list of books, you can look up all the roles for your books in one query, then cache them.

>>> books = Book.objects.filter(**your_kwargs)
>>> roles = Role.objects.filter(book_in=books).select_related(depth=1)
>>> roles_by_book = defaultdict(list)
>>> for role in roles:
...    roles_by_book[role.book].append(books)    

You can then access a book's roles through the roles_by_dict dictionary.

>>> for book in books:
...    book_roles = roles_by_book[book]

You will have to rethink your author_name property to use caching like this.


I'll shoot for the bonus points as well.

Add a method to role to render the full name and role name.

class Role(models.Model):
    ...
    @property
    def name_and_role(self):
        out = self.person.full_name
        if self.name:
            out += ' (%s)' % role.name
        return out

The author_names collapses to a one liner similar to Paulo's suggestion

@property
def authors_names(self):
   return ', '.join([role.name_and_role for role in self.role_set.all() ])

这篇关于如何使Django ManyToMany'通过'查询更有效率?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆