Dova Kin
Dova Kin

Reputation: 65

Ordering by computed field in admin panel

I'm learning Django and I got stuck on this problem

I'm basically trying to order by a computed field in an admin panel view, I've read some "tutorials" like this: https://books.agiliq.com/projects/django-admin-cookbook/en/latest/sorting_calculated_fields.html on google but I can't seem to figure out how it all works (between annotations etc)

Here's my classes:

class StockAdmin(admin.ModelAdmin):
list_display = ("ticker_symbol", "security_name", "current_pretax_yield", "current_aftertax_yield", "displayed_price")
search_fields = ("ticker_symbol", "security_name")

def current_pretax_yield(self, obj):
    try:
        curyield = float(obj.coupon_amount/obj.last_price)
        return str(round((curyield*100),3)) + "%"
    except:
        return "n/a"


def current_aftertax_yield(self, obj):
    try:
        withholding_tax = 15
        at_yield = ((obj.coupon_amount/obj.last_price*100)*(1-(withholding_tax/100))*0.74)
        return str(round(at_yield, 2)) + "%"
    except:
        return "n/a"

def get_queryset(self, request):
    queryset = super().get_queryset(request)
    queryset = queryset.annotate(
        _current_aftertax_yield=self.current_aftertax_yield(),
        _current_pretax_yield=self.current_pretax_yield(),
    )

current_aftertax_yield.admin_order_field = '_current_aftertax_yield'
current_pretax_yield.admin_order_field = '_current_pretax_yield'

Basically, I want to get "coupon amount" and "last price" fields from the database, perform the calculations you see in the functions, then display those calculated files in the admin panel and be able to "order by" them

The code as I have now errors out with a TypeError: current_aftertax_yield() missing 1 required positional argument: 'obj'

I've tried to follow this: https://books.agiliq.com/projects/django-admin-cookbook/en/latest/sorting_calculated_fields.html but I can't quite figure it out on my own..

Any ideas? Is there an easier way of doing this? I used a lot of computed values in PHP and it was trivial to implement!

Upvotes: 0

Views: 167

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

You can not use methods to annotate a Queryset. You should specify an expression constructed an expression for the database. You can not use a method for that. You can make use of combinations of F-expressions together with certain aggregates, etc.

Here both the current_pretax_yield and current_aftertax_yield scale with coupon_amount/last_price, so we can make an annotation, and then sort by that annotation:

from django.db.models import F

class StockAdmin(admin.ModelAdmin):
    list_display = ("ticker_symbol", "security_name", "current_pretax_yield", "current_aftertax_yield", "displayed_price")
    search_fields = ("ticker_symbol", "security_name")

    def current_pretax_yield(self, obj):
        try:
            curyield = float(obj.coupon_amount/obj.last_price)
            return str(round((curyield*100),3)) + "%"
        except:
            return "n/a"
    
    def current_aftertax_yield(self, obj):
        try:
            withholding_tax = 15
            at_yield = ((obj.coupon_amount/obj.last_price*100)*(1-(withholding_tax/100))*0.74)
            return str(round(at_yield, 2)) + "%"
        except:
            return "n/a"

    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            _yield=F('coupon_amount')/F('last_price')
        )

    current_aftertax_yield.admin_order_field = '_yield'
    current_pretax_yield.admin_order_field = '_yield'

Upvotes: 2

Related Questions