Displaying a DecimalField with currency in the django admin

11.7k Views Asked by At

I have multiple models with multiple DecimalField representing money, like 100.34 EUR.

My goal was to display the currency in the admin list view, like shown in this image.

https://i.stack.imgur.com/fyaaC.png

However I couldn't find a way to do it for every money field.

I tried creating a custom MoneyField, inheriting from DecimalField and changind the unicode representation, but it didn't work.

I also tried with https://github.com/jakewins/django-money but I had no luck.

I investigated the source code of django admin and I finally found the problem:

In django.admin.contrib.admin.util.py in the display_for_field function it checks if the value is an instance of DecimalField. If that's the case it displays it like a Decimal number.

elif isinstance(field, models.DecimalField):
    return formats.number_format(value, field.decimal_places) 

It makes sense, but it prevents me from displaying the EUR symbol/text in the admin.

How can I solve this problem?

I know that I could simply use a method for each field that display the formatted value as a string, but I wanted to know if there was a DRYer way.

4

There are 4 best solutions below

2
On

Or create a class like this and inherit from it

class ImprovedAdmin(admin.ModelAdmin):
    """Handles column formatting in list display"""
    def __init__(self, *args, **kwargs):
        def generate_formatter(name, str_format):
            formatter = lambda o: str_format%(getattr(o, name) or 0)
            formatter.short_description = name
            formatter.admin_order_field = name
            return formatter

        all_fields = []
        for f in self.list_display:
            if isinstance(f, basestring):
                all_fields.append(f)
            else:
                new_field_name = f[0]+'_formatted'
                setattr(self, new_field_name, generate_formatter(f[0], f[1]))
                all_fields.append(new_field_name)
        self.list_display = all_fields

        super(ImprovedAdmin, self).__init__(*args, **kwargs)


class MyModelAdmin(ImprovedAdmin):
    list_display = ('id', ('amount', '%.2f EUR'), ('interest', '%.2f %%'))
4
On

For display in the admin list, you can make a custom function in your ModelAdmin class, which will be called to format each value:

class MyModelAdmin(admin.ModelAdmin):
    list_display = ('formatted_amount', ...other fields...,)

    def formatted_amount(self, obj):
        # obj is the Model instance

        # If your locale is properly set, try also:
        # locale.currency(obj.amount, grouping=True)
        return '%.2f EUR' % obj.amount
0
On

There are 2 ways to display like "34.00 EUR" so that you can always display a number with the 2nd decimal place even if the decimal place is zero.

<The 1st way>

Define price with models.DecimalField and decimal_places=2 as shown below:

# "models.py"

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(
        max_digits=5,
        decimal_places=2, # Here
        validators=[
            MaxValueValidator(99999),
            MinValueValidator(0)
        ],
        default=0
    )

Then, define price_with_EUR() then assign it to list_display as shown below:

# "admin.py"

from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = [
        'name', 
        'price_with_EUR' # Here
    ]

    def price_with_EUR(self, obj): # Here
        return f'{self.price} EUR'

<The 2nd way>

Define price_with_EUR() with @property decorator as shown below in "models.py".

"models.py":

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        validators=[
            MaxValueValidator(99999),
            MinValueValidator(0)
        ],
        default=0
    )

    @property # Here
    def price_with_EUR(self):
        return f'{self.price} EUR'

Then, assign price_with_EUR() to list_display as shown below:

# "admin.py"

from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = [
        'name', 
        'price_with_EUR' # Here
0
On

Before 0.12 version of django-money money representation was partially supported for Django admin.

Since 0.12 version (released on 22.10.2017) all MoneyField representations (including Django's admin) could be configured with special formatter.

import moneyed
from moneyed.localization import _FORMATTER
from decimal import ROUND_HALF_EVEN


BOB = moneyed.add_currency(
    code='BOB',
    numeric='068',
    name='Peso boliviano',
    countries=('BOLIVIA', )
)

# Currency Formatter will output 2.000,00 Bs.
_FORMATTER.add_sign_definition(
    'default',
    BOB,
    prefix=u'Bs. '
)

_FORMATTER.add_formatting_definition(
    'es_BO',
    group_size=3, group_separator=".", decimal_point=",",
    positive_sign="",  trailing_positive_sign="",
    negative_sign="-", trailing_negative_sign="",
    rounding_method=ROUND_HALF_EVEN
)