我正在使用带有'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?)
答案 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() ])