Django: упорядочить модель по полю "многие ко многим"
Я пишу приложение Django, у которого есть модель для People, и я попал в ловушку. Я назначаю объекты Role для людей, использующих отношения "Много-ко-многим", где роли имеют имя и вес. Я хочу заказать свой список людей по самому тяжелому весу. Если я делаю People.objects.order_by ('- role__weight'), то я получаю дубликаты, когда у людей есть несколько назначенных им ролей.
Моя первоначальная идея заключалась в том, чтобы добавить денормализованное поле, называемое тяжелее-ролевым весом, и сортировать по нему. Затем это можно было обновить каждый раз, когда новая роль была добавлена или удалена пользователем. Однако выясняется, что нет никакого способа выполнить пользовательское действие каждый раз, когда ManyToManyField обновляется в Django (пока, в любом случае).
Итак, я думал, что смогу тогда полностью переборщить и написать собственное поле, дескриптор и менеджер, чтобы справиться с этим, но это кажется чрезвычайно сложным, когда ManyRelatedManager создается динамически для ManyToManyField.
Я пытаюсь придумать какой-нибудь умный 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
Примечание: группа по p. * может быть запрещена вашим диалектом SQL. Если это так, просто перечислите все столбцы таблицы p, которые вы собираетесь использовать в предложении select.
Примечание. Если вы просто группируете p.ID, вы не сможете вызвать другие столбцы в p в предложении select.
Я не знаю, как это взаимодействует с Django.