2009-06-01 4 views
12

Ich schreibe eine Django-Anwendung, die ein Modell für Menschen hat, und ich habe einen Haken gefunden. Ich lege Rollenobjekte Personen zu, die eine Viele-zu-Viele-Beziehung verwenden - wobei Rollen einen Namen und ein Gewicht haben. Ich möchte meine Liste der Leute nach dem Gewicht ihrer schwersten Rolle ordnen. Wenn ich People.objects.order_by ('- roles__weight') mache, erhalte ich Dubletten, wenn Leuten mehrere Rollen zugewiesen sind.Django: Bestellen Sie ein Modell von einem Viele-zu-Viele-Feld

Meine ursprüngliche Idee war, ein denormalisiertes Feld namens Schwerstgewicht hinzuzufügen - und danach sortieren. Dies könnte dann jedes Mal aktualisiert werden, wenn eine neue Rolle von einem Benutzer hinzugefügt oder entfernt wird. Es stellt sich jedoch heraus, dass es keine Möglichkeit gibt, eine benutzerdefinierte Aktion jedes Mal durchzuführen, wenn ein ManyToManyField in Django (yet, auf jeden Fall) aktualisiert wird.

Also dachte ich, ich könnte dann völlig über Bord gehen und ein benutzerdefiniertes Feld, einen Deskriptor und einen Manager schreiben, um damit umzugehen - aber das scheint extrem schwierig, wenn der ManyRelatedManager dynamisch für ein ManyToManyField erstellt wird.

Ich habe versucht, mit einigen cleveren SQL zu kommen, die das für mich tun könnte - ich bin sicher, dass es mit einer Unterabfrage (oder ein paar) möglich ist, aber ich würde besorgt sein, dass es nicht kompatibel ist Die Datenbank-Backends unterstützt Django.

Hat jemand das schon einmal gemacht - oder haben Sie irgendwelche Ideen, wie es erreicht werden könnte?

Antwort

11

Django 1.1 (derzeit Beta) fügt aggregation Unterstützung hinzu. Ihre Abfrage kann mit etwas wie:

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

Dies sortiert Menschen nach ihren schwersten Rollen, ohne doppelte zurückgibt.

Die erzeugte Abfrage ist:

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

Genau das, was ich war Auf der Suche nach. Ich verwende Django 1.1 nicht, aber ich kann dieses SQL in meinem benutzerdefinierten Manager verwenden. Vielen Dank :) –

1

So etwas wie dies 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 

. Hinweis: Gruppe von p * kann von Ihrem SQL-Dialekt nicht anerkannt werden. Wenn dies der Fall ist, listen Sie einfach alle Spalten in der Tabelle p auf, die Sie in der SELECT-Klausel verwenden möchten.

Hinweis: Wenn Sie nur nach p.ID gruppieren, können Sie die anderen Spalten in p in Ihrer SELECT-Klausel nicht aufrufen.

Ich weiß nicht, wie das mit Django interagiert.

6

Hier ist eine Möglichkeit, es ohne eine Anmerkung zu tun:

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

Dieses Sie etwas sagen können wie:

>>> person = Person.objects.get(id=1) 
>>> roles = person.ordered_roles() 
Verwandte Themen