2010-11-23 3 views
5

'Through'클래스를 사용하여 ManyToManyField를 사용하고 있습니다. 결과 목록을 가져올 때 많은 쿼리가 발생합니다. 더 효율적인 방법이 있는지 궁금합니다. 예를 들어Django ManyToMany 'through'쿼리를보다 효율적으로 만들려면 어떻게해야합니까?

여기에 역할 클래스를 통해가는 책과 여러 저자를 설명하는 몇 가지 간단한 클래스입니다 ("편집기", "일러스트 레이터"등과 같은 역할을 정의하기 위해) :

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는() 나는이 같은 문자열 뭔가 얻을 수 있습니다 :

홍길동 (편집자), 프레드 Bloggs에, 빌리 밥 (일러스트 레이터)

잘 작동하지만 책에 대한 역할을 얻는 쿼리 하나를 실행 한 다음 모든 사람에 대해 다른 쿼리를 수행합니다. 도서 목록이 표시되면 많은 검색어가 추가됩니다.

더 간단하게 책 하나당 하나의 조인으로이 작업을 수행 할 수 있습니까? 또는 batch-select과 같은 것을 사용하는 유일한 방법입니까?

은 (보너스 포인트 ... authors_names의 내 코딩은() 약간 투박한 외모 - 좀 더 우아하게 파이썬 - 억양 만들 수있는 방법이)

+2

'파이썬 - 억양'보통 몬티 파이썬의 비교를 위해 예약되어 있습니다 : 당신이 찾고있는 단어는 '파이썬'입니다. –

+1

@daniel : 'python-esque'의 사용법은 저자가 코드를 좀 더 재미있게 만들려고 노력하고 있음에도 불구하고 'pythonic'을 올바르게 사용하기 위해 +1 ... –

+3

고마워. 그러나 지금부터는 내 코드를 정확하고 재미있게 만들려고 노력할 것입니다. –

답변

8

이것은 내가 장고에 자주 건너 패턴입니다. author_name과 같은 속성을 만드는 것은 정말 쉽습니다. 한 책을 표시 할 때 훌륭하게 작동하지만 페이지의 많은 책에 대해 속성을 사용할 때 쿼리 수가 폭발합니다.

첫째, 당신은 그러나,이 모든 책의 역할을 찾고 문제를 해결하지 않는 모든 사람

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) 

에 대한 조회를 방지하기 위해 select_related를 사용할 수 있습니다.

도서 목록을 표시하는 경우 한 검색어에서 귀하의 도서에 대한 모든 역할을 찾아보고 캐시 할 수 있습니다.

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

아아, select_related() 포인터 Alasdair, 고맙습니다. 고맙습니다. 내 코드에서 대답의 두 번째 부분을 가장 효과적으로 사용하는 방법을 생각해야 할 것입니다. 어쩌면 맞춤 관리자에 있습니까? –

+0

전 일괄 적으로 선택하지는 않았지만 유망 해 보입니다. 나는 내 자신의 커스텀 매니저를 쓰기 전에 그것을 조사 할 것이다. – Alasdair

+0

역할 .__ unicode__는 렌더링에 이상적인 후보입니다. –

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

나는 이명의가 정직 할까 걱정하지 않는다.이 프로젝트의 목적, 저자의 이름, 가명이 아닌지는 사람이다. –

+0

코드 제안에 감사드립니다. 그러나 그것은 정확히 똑같은 일을하지 않습니다. - 역할이 없다면, 저는 저자의 이름 뒤에 빈 괄호를 쓰지 않을 것입니다. –

관련 문제