Django:按多对多字段订购模型

Posted

技术标签:

【中文标题】Django:按多对多字段订购模型【英文标题】:Django: Order a model by a many-to-many field 【发布时间】:2010-10-30 09:43:01 【问题描述】:

我正在编写一个带有 People 模型的 Django 应用程序,但我遇到了障碍。我正在使用多对多关系将角色对象分配给人们 - 其中角色具有名称和权重。我希望按照最重角色的权重来排列我的人员名单。如果我执行 People.objects.order_by('-roles__weight'),那么当人们分配有多个角色时,我会得到重复。

我最初的想法是添加一个名为 heaviest-role-weight 的非规范化字段 - 并按此排序。然后可以在每次向用户添加或删除新角色时更新此信息。然而,事实证明,每次在 Django 中更新 ManyToManyField 时,都无法执行自定义操作(无论如何,yet)。

所以,我认为我可以完全过火并编写一个自定义字段、描述符和管理器来处理这个问题 - 但是当为 ManyToManyField 动态创建 ManyRelatedManager 时,这似乎非常困难。

我一直在尝试想出一些可以为我做这件事的聪明的 SQL - 我确信可以使用子查询(或几个),但我担心它不兼容所有Django 支持的数据库后端。

以前有没有人这样做过 - 或者对如何实现有任何想法?

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

这是一种无需注释的方法:

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

【讨论】:

【参考方案3】:

在 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

注意:您的 SQL 方言可能不允许按 p.* 分组。如果是这样,只需列出表 p 中您打算在 select 子句中使用的所有列。

注意:如果您只是按 p.ID 分组,您将无法在 select 子句中调用 p 中的其他列。

我不知道这如何与 Django 交互。

【讨论】:

以上是关于Django:按多对多字段订购模型的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy按多对多关系排序

如何在 Eloquent ORM 中按多对多关系的数据透视表的字段进行排序

如何以 django 形式过滤多对多字段

Django自引用多对多模型

如何在 Sequelize 中按多对多关系排序?

Django 多对多字段