2010-11-23 80 views
5

我正在使用帶有'through'類的ManyToManyField,並且在獲取事物列表時會導致很多查詢。我想知道是否有更有效的方法。如何讓Django ManyToMany'通過'查詢更高效?

例如這裏描述的書籍和他們的幾位作者的一些簡單的類,它經過一個角色類(定義如「編輯器」,「插畫」等角色):

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(),那麼我可以得到一個字符串是這樣的:

李四(編者),弗雷德·布洛斯,比利·鮑伯·(插圖)

它工作正常,但它有一個查詢來獲得該書的角色,然後是每個人的另一個查詢。如果我顯示的是書籍列表,則會添加很多查詢。

有沒有辦法更有效地做到這一點,在每一本書的單個查詢,加入?或者是唯一使用像batch-select之類的方法?

(獎勵積分...我authors_names的編碼()看起來有點笨重 - 就是有辦法使它更優雅的Python式的?)

+2

「的Python式的」通常保留給比較,巨蟒:你要找的字是「Python化」。 – 2010-11-23 12:38:11

+1

@daniel:+1正確使用'pythonic',儘管'python-esque'的使用可能意味着作者正在尋求使代碼更有趣...... – 2010-11-23 12:51:10

+3

感謝您的更正。不過,我將從現在起努力使我的代碼不僅更正確,而且更有趣。 – 2010-11-23 13:41:34

回答

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塌陷類似於保羅的建議一個襯墊

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

我會做authors = models.ManyToManyField(Role)和存儲全名的角色。別名,因爲同一個人可以在不同的假名下簽名。

關於笨重,這樣的:

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