I am writing a Django application that has a model for People, and I have hit a snag. I am assigning Role objects to people using a Many-To-Many relationship - where Roles have a name and a weight. I wish to order my list of people by their heaviest role's weight. If I do People.objects.order_by('-roles__weight'), then I get duplicates when people have multiple roles assigned to them.
我正在编写一个Django应用程序,它有一个People模型,我遇到了麻烦。我正在为使用多对多关系的人分配角色对象 - 其中角色具有名称和权重。我希望按照他们最重要的角色来命令我的人员名单。如果我做People.objects.order_by(' - roles__weight'),那么当人们分配了多个角色时,我会得到重复。
My initial idea was to add a denormalized field called heaviest-role-weight - and sort by that. This could then be updated every time a new role was added or removed from a user. However, it turns out that there is no way to perform a custom action every time a ManyToManyField is updated in Django (yet, anyway).
我最初的想法是添加一个称为最重角色权重的非规范化字段 - 并按此排序。然后,每次从用户添加或删除新角色时,都可以更新此更新。但是,事实证明,每次在Django中更新ManyToManyField时都无法执行自定义操作(无论如何)。
So, I thought I could then go completely overboard and write a custom field, descriptor and manager to handle this - but that seems extremely difficult when the ManyRelatedManager is created dynamically for a ManyToManyField.
所以,我认为我可以完全超越并编写一个自定义字段,描述符和管理器来处理这个问题 - 但是当为ManyToManyField动态创建ManyRelatedManager时,这似乎非常困难。
I have been trying to come up with some clever SQL that could do this for me - I'm sure it's possible with a subquery (or a few), but I'd be worried about it not being compatible will all the database backends Django supports.
我一直试图想出一些聪明的SQL,可以为我做这个 - 我敢肯定它可能有一个子查询(或一些),但我担心它不兼容所有的数据库后端Django支持。
Has anyone done this before - or have any ideas how it could be achieved?
有没有人以前做过这个 - 或者有什么想法可以实现它?
3 个解决方案
#1
12
Django 1.1 (currently beta) adds aggregation support. Your query can be done with something like:
Django 1.1(目前为测试版)增加了聚合支持。您的查询可以通过以下方式完成:
from django.db.models import Max
People.objects.annotate(max_weight=Max('roles__weight')).order_by('-max_weight')
This sorts people by their heaviest roles, without returning duplicates.
这会按照最重要的角色对人进行排序,而不会返回重复项。
The generated query is:
生成的查询是:
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
#2
6
Here's a way to do it without an annotation:
这是一种没有注释的方法:
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')
This lets you say something like:
这可以让你说:
>>> person = Person.objects.get(id=1)
>>> roles = person.ordered_roles()
#3
1
Something like this in SQL:
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
Note: group by p.* may be disallowed by your dialect of SQL. If so, just list all the columns in table p that you intend to use in the select clause.
注意:您的SQL方言可能不允许使用p。*分组。如果是这样,只需列出要在select子句中使用的表p中的所有列。
Note: if you just group by p.ID, you won't be able to call for the other columns in p in your select clause.
注意:如果您只是按p.ID分组,则无法在select子句中调用p中的其他列。
I don't know how this interacts with Django.
我不知道这是如何与Django交互的。
#1
12
Django 1.1 (currently beta) adds aggregation support. Your query can be done with something like:
Django 1.1(目前为测试版)增加了聚合支持。您的查询可以通过以下方式完成:
from django.db.models import Max
People.objects.annotate(max_weight=Max('roles__weight')).order_by('-max_weight')
This sorts people by their heaviest roles, without returning duplicates.
这会按照最重要的角色对人进行排序,而不会返回重复项。
The generated query is:
生成的查询是:
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
#2
6
Here's a way to do it without an annotation:
这是一种没有注释的方法:
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')
This lets you say something like:
这可以让你说:
>>> person = Person.objects.get(id=1)
>>> roles = person.ordered_roles()
#3
1
Something like this in SQL:
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
Note: group by p.* may be disallowed by your dialect of SQL. If so, just list all the columns in table p that you intend to use in the select clause.
注意:您的SQL方言可能不允许使用p。*分组。如果是这样,只需列出要在select子句中使用的表p中的所有列。
Note: if you just group by p.ID, you won't be able to call for the other columns in p in your select clause.
注意:如果您只是按p.ID分组,则无法在select子句中调用p中的其他列。
I don't know how this interacts with Django.
我不知道这是如何与Django交互的。