djs22
djs22

Reputation: 1156

Django Admin: Order by value on related Foreign Key

I'm trying to sort a Django Admin list page by a specific value in the objects' related foreign key set.

Specifically, in the below code, I want the ContentAdmin view to show a list of all content objects sorted by the "Twitter Score" (The Score object with name "Twitter").

In the django app I have the following models:

class Content(models.Model):
    body = models.CharField(max_length=564)
    title = models.CharField(max_length=64) 

class Score(models.Model):
    name = models.CharField(max_length=64)
    score = models.IntegerField()
    content = models.ForeignKey('Content')

And in the admin.py I have the following:

class ContentAdmin(admin.ModelAdmin):
    list_display = ('title', 'show_twitter_score',)

    def show_twitter_score(self, obj):
        twitter_score = obj.score_set.get(name='Twitter')
        return 'Twitter: ' + str(twitter_score.score)

GOAL: The admin panel for ContentAdmin displays the content objects ordered by "Twitter" scores

Thanks everyone!

Upvotes: 7

Views: 6739

Answers (3)

djs22
djs22

Reputation: 1156

I solved this by extending the get_queryset method of the ContentAdmin class. After that, it was just a matter of getting the right ORM query

def get_queryset(self, request):
    qs = super(ContentAdmin, self).get_queryset(request)
    return qs.filter(score__name='Twitter').order_by('-score__score')

For Django 1.5 and earlier, the method was queryset.

def queryset(self, request):
    qs = super(ContentAdmin, self).queryset(request)
    return qs.filter(score__name='Twitter').order_by('-score__score')

Upvotes: 5

Tommaso Barbugli
Tommaso Barbugli

Reputation: 12031

Since django admin uses the db to sort you cant sort on the function you are showing in the list.

What you can do is to add the column you want to show to the queryset that django admin is using to list your models, this way you can have sorting.

To add the column you need you have to use the queryset extra method.

This should do the trick :)

Content.objects.all().extra(select={'twitter_score': 'SELECT score from content_score WHERE content_score.id = content_content.id'})

BONUS ROUND:

Content.objects.all().extra(select={'twitter_score': 'SELECT 'Twitter score:' || score from content_score WHERE content_score.id = content_content.id'})

Upvotes: -3

César
César

Reputation: 10119

If I understand correctly, you can try this from ModelAdmin.list_display in Django's documentation:

Usually, elements of list_display that aren't actual database fields can't be used in sorting (because Django does all the sorting at the database level).

However, if an element of list_display represents a certain database field, you can indicate this fact by setting the admin_order_field attribute of the item.

For example:

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_first_name(self):
        return '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
    colored_first_name.allow_tags = True
    colored_first_name.admin_order_field = 'first_name'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'colored_first_name')

The above will tell Django to order by the first_name field when trying to sort by colored_first_name in the admin.

You can try this workaround in your code for the sorting.

Upvotes: 1

Related Questions