Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django annotation output_field=DecimalField ignores max_digits and decimal_places

Inside an annotation I do some calculations, and I want the output to be a decimal, with max 8 digits and max 2 decimal. I don't know why but Django ignores decimal_places and max_digits. Here is my code:

Order.objects.all().annotate(
        amount=Coalesce(
            Sum(
                Case(
                    When(
                        Q(payments__status='complete'),
                        then=F('payments__amount') - (
                            F('payments__amount') * F('payments__vat')/100
                        ) 
                    ), output_field=DecimalField(decimal_places=2, max_digits=8)

                )
            ), 0)
    ).values('amount')

output = 12.5999999999999996447286321199499070644378662109375 I'm using Django 1.9.5

like image 570
jalanga Avatar asked Feb 15 '17 11:02

jalanga


People also ask

What is decimalfield in Django?

DecimalField – Django Models Last Updated : 12 Feb, 2020 DecimalField is a field which stores a fixed-precision decimal number, represented in Python by a Decimal instance. It validates the input using DecimalValidator.

How to get the maximum number of digits in a decimal field?

Show activity on this post. DecimalField accepts arguments for max_digits which is the total number of digits in a decimal number and for decimal_places which is the number of decimal places. If you define max_digits to be 3 and decimal_places to be 2, the largest number you can save is 9.99.

What is the default value of a field in Django?

Default is False. If True, the field is allowed to be blank. Default is False. The name of the database column to use for this field. If this isn’t given, Django will use the field’s name. The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.

How does it validate the input using decimalvalidator?

It validates the input using DecimalValidator. The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. The number of decimal places to store with the number. For example, to store numbers up to 999 with a resolution of 2 decimal places, you’d use:


1 Answers

I had had the same issue in my job, to solve this I've created the following custom aggregate:

class SumDecimal(Func):
    function = 'SUM'
    name = 'sum'
    contains_aggregate = True
    output_field = DecimalField()
    template = '%(function)s(%(expressions)s)'

    def __init__(self, *args, **kwargs):
        self.decimal_places = kwargs.pop('decimal_places', None)
        super(SumDecimal, self).__init__(*args, **kwargs)

    def as_sql(self, compiler, connection, function=None, template=None):
        sql = super(SumDecimal, self).as_sql(
            compiler=compiler,
            connection=connection,
            function=function,
            template=template)

        if self.decimal_places:
            sql, params = sql
            sql = 'CAST(%s AS DECIMAL(16, %d))' % (sql, self.decimal_places)
            return sql, params

        return sql

To use this is pretty simple, just use like:

mymodel.objects.all().annotate(sum_value=SumDecimal('value', decimal_places=2))
like image 65
Rafael Souza da Silva Avatar answered Sep 26 '22 17:09

Rafael Souza da Silva