如何让Django ManyToMany'通过'查询更有效率?

时间:2010-11-23 11:47:38

标签: python django many-to-many

我正在使用带有'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)
    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)

如果我调用Book.authors_names(),那么我可以得到这样的字符串:

  

John Doe(编辑),Fred Bloggs,Billy Bob(插画家)

它工作正常,但是它会执行一个查询来获取本书的角色,然后对每个人执行另一个查询。如果我正在显示一个书籍列表,这会增加很多查询。

有没有办法在每本书的单个查询中使用连接更有效地执行此操作?或者是使用batch-select等内容的唯一方法?

(对于奖励积分......我对authors_names()的编码看起来有点笨拙 - 有没有办法让它更优雅地使用Python-esque?)

2 个答案:

答案 0 :(得分:8)

这是我经常在Django遇到的模式。创建author_name等属性非常简单,当您显示一本书时,它们的效果很好,但当您想要在页面上使用该属性时,查询数量会爆炸。

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

  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)

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

如果您要显示图书清单,则可以在一个查询中查找图书的所有角色,然后缓存它们。

>>> 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)    

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

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

您必须重新考虑author_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

author_names折叠成一个类似于Paulo的建议的单行班次

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

答案 1 :(得分:1)

我会使authors = models.ManyToManyField(Role)并将全名存储在Role.alias中,因为同一个人可以使用不同的假名来签名。

关于笨重,这:

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)

可能是:

def authors_names(self):
   return ', '.join([ '%s (%s)' % (role.person.full_name, role.name) 
                 for role in self.role_set.all() ])