Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Order a model by a many-to-many field

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.

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

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.

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.

Has anyone done this before - or have any ideas how it could be achieved?

like image 333
Rob Golding Avatar asked Jun 01 '09 13:06

Rob Golding


1 Answers

Django 1.1 (currently beta) adds aggregation support. Your query can be done with something like:

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
like image 164
Ayman Hourieh Avatar answered Oct 08 '22 23:10

Ayman Hourieh