12

How can I sort a column in Django admin by some simple custom method?
(All the answers I got through was by using annotate but I don't know how to use it my case).

Assume the model

class Shots(models.Model):    
    hits = models.PositiveIntegerField()
    all = models.PositiveIntegerField()

In admin site I would like to sort by hits to all ratio:

class ShotAdmin(admin.ModelAdmin):
    list_display = ['ratio']

    def ratio(self, obj):
        return obj.hits / obj.all * 100

I know the ratio is not a field in DB thus simple ratio.admin_order_field = 'ratio' won't work and I need to somehow append this as a field but I have no idea how.

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
micsza
  • 789
  • 1
  • 9
  • 16

1 Answers1

14

By following:

We can compose a solution to your problem:

from django.db.models import F

class ShotsAdmin(admin.ModelAdmin):
    list_display = ('get_ratio',)

    def get_queryset(self, request):
        qs = super(ShotsAdmin, self).get_queryset(request)
        qs = qs.annotate(ratio=F('hits') * 100 / F('all'))
        return qs

    def get_ratio(self, obj):
        return obj.ratio

    get_ratio.admin_order_field = 'ratio'

Explanation:

  • The get_queryset method will annotate a new field named ratio to your queryset. That field's value is the application of your ratio function on the hits and all fields.
  • The get_ratio function returns the aforementioned field from a queryset instance.
  • Finally: get_ratio.admin_order_field = 'ratio' sets the ratio field as the ordering field for your queryset on the admin panel.
culix
  • 10,188
  • 6
  • 36
  • 52
John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • Many thanks, it all works great if applied with the following modifications: 1) call `super` on proper class i.e.`ShotsAdmin`, 2) `ratio=(F('hits') * 100 / F('all'))` as it truncates to the nearest integer – micsza Aug 28 '17 at 15:20
  • Good to know @micsza I will edit my answer accordingly! – John Moutafis Aug 28 '17 at 16:30