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

Phi*_*ord 5 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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

有没有办法在每本书的单个查询中使用连接更有效地执行此操作?或者是使用批量选择之类的东西的唯一方法?

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

Ala*_*air 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)
Run Code Online (Sandbox Code Playgroud)

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

如果要显示书籍列表,则可以在一个查询中查找书籍的所有角色,然后缓存它们.

>>> 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)    
Run Code Online (Sandbox Code Playgroud)

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

>>> for book in books:
...    book_roles = roles_by_book[book]
Run Code Online (Sandbox Code Playgroud)

您将不得不重新考虑您的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
Run Code Online (Sandbox Code Playgroud)

author_names崩溃的一个衬垫类似保罗的建议

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