How can I sort by the id of a ManyToManyField in Django?

早过忘川 提交于 2019-11-29 09:54:15

I just found a way to do this without having to create a class for the relationship. It relies on the extra feature that lets you add additional columns to output. In your example it would look like:

theuser.following.filter(user__is_active=True)\
    .extra(select={'creation_seq': 'appname_user_user_following.id'})\
    .order_by("creation_seq")

Notice that appname_user_user_following is the name of the relationship table Django creates under the covers. It's deterministic and something you can get and set via meta-mechanisms, but it's pretty much safe to hardcode.

Here's an example of the SQL that's being created under the covers with fake table and columns names:

SELECT (appname_user_user_following.id) AS `creation_seq`, `appname_user`.`id`
FROM `appname_user` INNER JOIN `appname_user_user_following` ON
(`appname_user`.`id` = `appname_user_user_following`.`user_id`) WHERE
`appname_user_user_following`.`user_followed_id` = 1  ORDER BY `creation_seq` ASC';

In fact (at least in Django 1.10), you don't need to use the extra feature but instead can just order by the field directly. Just use the automatically created through table name followed by ".id" as an argument to order_by. E.g.

pizza.toppings.all().order_by('appname_pizza_toppings.id')

article.tags.all().order_by('appname_article_tags.id')

For this particular question:

theuser.following.filter(user__is_active=True)\ .order_by("appname_user_user_following.id")

Many other solutions suggest creating a custom through table and adding a field but if you just want to sort by the id of the automatically generated through table then this is not necessary.

Tested with Django 1.11.10.

You don't have to hardcode the relationship table name (How to read the database table name of a Model instance?).

So an update on @Ry4an Brase's answer can look like

recently_followed = '-{}.id'.format(theuser.following.through._meta.db_table)  
theuser.following.filter(user__is_active=True).order_by(recently_followed)

I am not sure whether you can achieve this with a regular ManytoManyField. You could try defining the intermediate model explicitly.

nb: Untested code!

class Person(models.Model)
    name = models.CharField(max_length=30)

class FollowerRelationship(models.Model)
    follower = models.ForeignKey(Person, related_name = following_set)
    following = models.ForeignKey(Person, related_name = follower_set)

You can then create following relationships in the shell as follows.

# Create Person objects
>>> a = Person(name="Alice")
>>> a.save()
>>> b = Person(name="Bob")
>>> b.save()
>>> c = Person(name="Chris")
>>> c.save()

# Let Alice follow Chris and Bob 
>>> FollowerRelationship.objects.create(follower=a, following=c)
>>> FollowerRelationship.objects.create(follower=a, following=b)

You can create a queryset of FollowerRelationship objects where Alice is the follower, ordered by the id of the join table, with the line:

>>> qs = FollowerRelationship.objects.filter(follower=a).order_by('id')
>>> [fr.following for fr in qs]

Note that you have to loop through the FollowerRelationship objects, to get the 'followed' Person in the relationship.

You may also want to look at Extra fields on many-to-many relationships in the Django docs, which describes how to specify the intermediate model in a many-to-many relationship.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!