2009-06-01 5 views
12

Estoy escribiendo una aplicación de Django que tiene un modelo para Personas, y me he encontrado con un problema. Estoy asignando objetos Role a personas que usan una relación Muchos a Muchos, donde los roles tienen un nombre y un peso. Deseo ordenar mi lista de personas por el peso de su papel más pesado. Si hago People.objects.order_by ('- roles__weight'), entonces obtengo duplicados cuando las personas tienen múltiples roles asignados a ellos.Django: ordena un modelo por un campo de varios a varios

Mi idea inicial era agregar un campo desnormalizado llamado peso más grande-rol - y ordenar por eso. Esto podría actualizarse cada vez que se agregue o elimine un nuevo rol de un usuario. Sin embargo, resulta que no hay forma de realizar una acción personalizada cada vez que se actualiza ManyToManyField en Django (yet, de todos modos).

Entonces, pensé que podría ir completamente por la borda y escribir un campo, descriptor y administrador personalizados para manejar esto, pero eso parece extremadamente difícil cuando ManyRelatedManager se crea dinámicamente para ManyToManyField.

He estado tratando de llegar a algún SQL inteligente que pueda hacer esto por mí - estoy seguro de que es posible con una subconsulta (o algunas), pero me preocuparía que no sea compatible todo los back-ends de base de datos que admite Django.

¿Alguien ha hecho esto antes o tiene alguna idea de cómo se podría lograr?

Respuesta

11

Django 1.1 (actualmente en beta) agrega aggregation de soporte. Su consulta se puede hacer con algo como:

from django.db.models import Max 
People.objects.annotate(max_weight=Max('roles__weight')).order_by('-max_weight') 

Esto clasifica a las personas por sus funciones más pesadas, sin devolver duplicados.

La consulta generada es:

SELECT people.id, people.name, MAX(role.weight) AS max_weight 
FROM people LEFT OUTER JOIN people_roles ON (people.id = people_roles.people_id) 
      LEFT OUTER JOIN role ON (people_roles.role_id = role.id) 
GROUP BY people.id, people.name 
ORDER BY max_weight DESC 
+0

justo lo que estaba buscando. No estoy ejecutando Django 1.1, pero puedo usar ese SQL en mi administrador personalizado. Gracias :) –

1

Algo como esto en SQL:

select p.*, max (r.Weight) as HeaviestWeight 
from persons p 
inner join RolePersons rp on p.id = rp.PersonID 
innerjoin Roles r on rp.RoleID = r.id 
group by p.* 
order by HeaviestWeight desc 

. Nota: grupo por p * puede ser rechazado por su dialecto de SQL. Si es así, simplemente enumere todas las columnas en la tabla p que tiene la intención de usar en la cláusula de selección.

Nota: si solo agrupa por p.ID, no podrá llamar a las otras columnas en p en su cláusula de selección.

No sé cómo esto interactúa con Django.

6

Aquí está una manera de hacerlo sin una anotación:

class Role(models.Model): 
    pass 

class PersonRole(models.Model): 
    weight = models.IntegerField() 
    person = models.ForeignKey('Person') 
    role = models.ForeignKey(Role) 

    class Meta: 
     # if you have an inline configured in the admin, this will 
     # make the roles order properly 
     ordering = ['weight'] 

class Person(models.Model): 
    roles = models.ManyToManyField('Role', through='PersonRole') 

    def ordered_roles(self): 
     "Return a properly ordered set of roles" 
     return self.roles.all().order_by('personrole__weight') 

Esto le permite decir algo como:

>>> person = Person.objects.get(id=1) 
>>> roles = person.ordered_roles() 
Cuestiones relacionadas