Джанго: Заказать модель по полю многие-ко-многим - PullRequest
12 голосов
/ 01 июня 2009

Я пишу приложение Django, в котором есть модель для людей, и я столкнулся с проблемой. Я назначаю объекты ролей людям, использующим отношение «многие ко многим», где роли имеют имя и вес. Я хочу упорядочить свой список людей по весу их самой тяжелой роли. Если я сделаю People.objects.order_by ('- role__weight'), то получу дубликаты, когда людям назначено несколько ролей.

Моя первоначальная идея состояла в том, чтобы добавить денормализованное поле с именем heavymost-role-weight - и отсортировать по нему Это может быть обновлено каждый раз, когда новая роль добавляется или удаляется от пользователя. Однако оказывается, что нет способа выполнить пользовательское действие каждый раз, когда ManyToManyField обновляется в Django ( пока , в любом случае).

Итак, я подумал, что мог бы тогда пойти полностью за борт и написать настраиваемое поле, дескриптор и менеджер, чтобы справиться с этим - но это кажется чрезвычайно трудным, когда ManyRelatedManager создается динамически для ManyToManyField.

Я пытался придумать какой-нибудь умный SQL, который мог бы сделать это для меня - я уверен, что это возможно с подзапросом (или несколькими), но я буду беспокоиться о том, что он не будет совместим, Бэкэнды базы данных поддерживает Django.

Кто-нибудь делал это раньше - или есть идеи, как этого достичь?

Ответы [ 3 ]

13 голосов
/ 01 июня 2009

Django 1.1 (в настоящее время бета) добавляет поддержку агрегации . Ваш запрос может быть выполнен с чем-то вроде:

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
6 голосов
/ 04 июня 2010

Вот способ сделать это без аннотации:

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()
1 голос
/ 01 июня 2009

Примерно так в 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.

Я не знаю, как это взаимодействует с Джанго.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...