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

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

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

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

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

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

【问题讨论】:

    标签: sql django database-design django-models


    【解决方案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 交互。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-11-05
          • 2016-10-10
          • 2014-12-08
          • 2017-07-05
          • 2021-11-27
          • 2018-01-05
          • 2013-05-17
          相关资源
          最近更新 更多