2010-11-23 6 views
5

Estoy usando ManyToManyField con una clase 'through' y esto da como resultado muchas consultas cuando obtengo una lista de cosas. Me pregunto si hay una manera más eficiente.¿Cómo hago que las consultas Django ManyToMany 'through' sean más eficientes?

Por ejemplo aquí hay algunas clases simplificados para la descripción Libros y sus diversos autores, que pasa por una clase de papel (para definir los roles como "Editor", "ilustrador", etc):

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) 

Si llamo Book.authors_names() entonces pueden obtener una cadena como esta:

John Doe (Editor), Fred Bloggs, Billy Bob (Ilustrador)

Funciona bien, pero realiza una consulta para obtener los roles del libro y luego otra consulta para cada persona. Si estoy mostrando una lista de libros, esto se suma a muchas consultas.

¿Hay alguna manera de hacer esto de manera más eficiente, en una sola consulta por libro, con una combinación? ¿O es la única forma de usar algo como batch-select?

(Para los puntos de bonificación ... mi codificación de authors_names() parece un poco torpe - hay una manera de hacerlo más elegante al estilo de Python?)

+2

'Python-esque' es generalmente reservado para las comparaciones con Monty Python: la palabra que está buscando es 'Pythonic'. –

+1

@daniel: +1 para el uso correcto de 'pythonic', aunque el uso de 'python-esque' puede implicar que el autor está tratando de hacer el código un poco más gracioso ... –

+3

Gracias por la corrección. Sin embargo, a partir de ahora me esforzaré por hacer que mi código no solo sea más correcto sino también más divertido. –

Respuesta

8

Este es un patrón que me encuentro a menudo en Django. Es realmente fácil crear propiedades como author_name, y funcionan muy bien cuando se muestra un libro, pero el número de consultas explota cuando se desea usar la propiedad para muchos libros en una página.

En primer lugar, puede utilizar select_related para evitar que las operaciones de búsqueda para cada persona

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) 

Sin embargo, esto no resuelve el problema de buscar los papeles por cada libro.

Si está visualizando una lista de libros, puede buscar todos los roles de sus libros en una consulta y luego almacenarlos en la memoria caché.

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

A continuación, puede acceder a las funciones de un libro a través del diccionario roles_by_dict.

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

Usted tendrá que reconsiderar su propiedad author_name utilizar el almacenamiento en caché de esta manera.


Voy a disparar para los puntos de bonificación también.

Agregue un método a la función para representar el nombre completo y el nombre del rol.

class Role(models.Model): 
    ... 
    @property 
    def name_and_role(self): 
     out = self.person.full_name 
     if self.name: 
      out += ' (%s)' % role.name 
     return out 

Los author_names colapsa a un chiste similar a la sugerencia de Paulo

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

Ah, muchas gracias por el puntero select_related() Alasdair, eso es una mejora. Tendré que pensar cuál es la mejor manera de usar la segunda parte de tu respuesta en mi código. Tal vez en un administrador personalizado? –

+0

No me había encontrado antes con la selección por lotes, pero parece prometedor. Podría investigar eso antes de tratar de escribir mi propio administrador personalizado. – Alasdair

+0

Rol .__ unicode__ es un candidato ideal para renderizar –

1

me haría authors = models.ManyToManyField(Role) y almacenar nombre completo en papel. alias, porque la misma persona puede firmar libros bajo pseudónimos distintos.

Sobre el torpe, esto:

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) 

podría ser:

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

No me preocupan los seudónimos para ser honesto: para los propósitos de este proyecto, el nombre de un autor, seudónimo o no, es la persona. –

+0

Y gracias por la sugerencia de código. Pero eso no hace exactamente lo mismo: si no hay role.name, no quiero paréntesis vacíos después del nombre del autor. –

Cuestiones relacionadas