2009-06-01 16 views
12

Sto scrivendo un'applicazione Django che ha un modello per le persone, e ho trovato un problema. Sto assegnando oggetti Role a persone che usano una relazione Many-To-Many - dove i ruoli hanno un nome e un peso. Desidero ordinare la mia lista di persone dal peso del loro ruolo più pesante. Se faccio People.objects.order_by ('- roles__weight'), ricevo duplicati quando le persone hanno più ruoli assegnati a loro.Django: Ordina un modello da un campo molti-a-molti

La mia idea iniziale era quella di aggiungere un campo denormalizzato chiamato il peso del ruolo più pesante - e ordinare per quello. Questo potrebbe quindi essere aggiornato ogni volta che un nuovo ruolo è stato aggiunto o rimosso da un utente. Tuttavia, risulta che non è possibile eseguire un'azione personalizzata ogni volta che un oggetto ManyToManyField viene aggiornato in Django (yet, in ogni caso).

Quindi, ho pensato che avrei potuto andare completamente fuori bordo e scrivere un campo personalizzato, descrittore e gestore per gestirlo - ma ciò sembra estremamente difficile quando ManyRelatedManager viene creato dinamicamente per un ManyToManyField.

Ho cercato di creare un SQL intelligente che potesse farlo per me - sono sicuro che è possibile con una sottoquery (o pochi), ma sarei preoccupato che non sia compatibile per tutti il database supporta i supporti di Django.

Qualcuno l'ha già fatto prima o ha qualche idea su come potrebbe essere raggiunto?

risposta

11

Django 1.1 (attualmente in versione beta) aggiunge il supporto aggregation. La tua domanda può essere fatta con qualcosa del tipo:

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

Questo tipo di persone con i loro ruoli più pesanti, senza restituire duplicati.

La query generato è:

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

proprio quello che stavo cercando. Non sto eseguendo Django 1.1, ma posso usare quello SQL nel mio gestore personalizzato. Grazie :) –

1

Qualcosa di simile in 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: gruppo da p * può essere annullato dal dialetto SQL. Se è così, basta elencare tutte le colonne nella tabella p che si intende utilizzare nella clausola select.

Nota: se si raggruppa solo per p.ID, non sarà possibile chiamare le altre colonne in p nella clausola select.

Non so come questo interagisca con Django.

6

Ecco un modo per farlo senza un'annotazione:

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

Ciò consente di dire qualcosa come:

>>> person = Person.objects.get(id=1) 
>>> roles = person.ordered_roles() 
Problemi correlati