2009-06-01 101 views
12

我正在編寫一個具有人員模型的Django應用程序,並且我碰到了一個障礙。我將角色對象分配給使用多對多關係的人 - 其中角色具有名稱和權重。我希望以他們最重的角色的重量來排列我的人名單。如果我做People.objects.order_by(' - roles__weight'),那麼當人們有多個角色分配給他們時,我會得到重複。Django:通過多對多訂購模型

我最初的想法是添加一個稱爲最重的角色重量的非規範化字段 - 並按此排序。這可以在每次向用戶添加或刪除新角色時更新。但是,事實證明,每當在Django中更新ManyToManyField時,都無法執行自定義操作(無論如何,這都是yet)。

所以,我認爲我可以完全放棄並寫一個自定義字段,描述符和管理器來處理這個問題 - 但是當爲ManyToManyField動態創建ManyRelatedManager時,這似乎極其困難。

我一直在想出一些聰明的SQL,可以爲我做這個 - 我敢肯定它可能與子查詢(或幾個),但我會擔心它不兼容將所有數據庫後端Django支持。

有沒有人做過此事 - 或者有任何想法如何實現?

回答

11

Django 1.1(目前測試版)增加了aggregation支持。您的查詢可以像做:

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

此排序由他們的最重的角色的人,不返回重複。

生成的查詢是:

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

正是我一直在尋找。我沒有運行Django 1.1,但我可以在我的自定義管理器中使用該SQL。謝謝 :) – 2009-06-01 14:40:45

1

事情是這樣的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 

注:按P組*可以通過您的SQL方言被禁止。如果是這樣,只需列出您希望在select子句中使用的表p中的所有列。

注意:如果您只是通過p.ID進行分組,您將無法在select子句中調用p中的其他列。

我不知道這是如何與Django交互。

6

這裏有一個辦法做到這一點沒有註釋:

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

這使您可以這樣說:

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