1

to be brief, i have this queryset:

monthly_revenue = list(Booking.objects.annotate(month=Month('created_at'))
                           .values('month')
                           .annotate(total=Sum('price'))
                           .order_by('month'))

this is what it is returning:

[{'month': 11, 'total': Decimal('4550.00')}]

the result is going to a js script to show a graph, and i need to remove the Decimal() prefix. Any help or hint is appreciated.

Simou
  • 682
  • 9
  • 28

4 Answers4

3

If you just want to remove "Decimal" prefix you could just define a specific output field in your annotation:

monthly_revenue = list(Booking.objects.annotate(month=Month('created_at'))
                           .values('month')
                           .annotate(total=Sum('price', output_field=FloatField()))
                           .order_by('month'))
Chris
  • 2,162
  • 1
  • 6
  • 17
1

Since you're using Django you can use DjangoJSONEncoder as follows:

from django.core.serializers.json import DjangoJSONEncoder

my_dict = [{'month': 11, 'total': Decimal('4550.00')}]
json_result = json.dumps(my_dict, cls=DjangoJSONEncoder)

But, keep in mind that DjangoJSONEncoder turns decimal into strings so the result would be:

[{"month": 11, "total": "4550.00"}]

If you navigate to DjangoJSONEncoder source code you find this:

elif isinstance(o, (decimal.Decimal, uuid.UUID, Promise)):
    return str(o)
Ramy M. Mousa
  • 5,727
  • 3
  • 34
  • 45
  • i already had that encoder extended for models serialization, now i just adjusted it to convert decimal to float, i'll share the solution. – Simou Jan 17 '20 at 11:33
0

you could convert it to a type float, like this

monthly_revenue = list(Booking.objects.annotate(month=Month('created_at'))
                       .values('month')
                       .annotate(total=float(Sum('price')))
                       .order_by('month'))
rossi
  • 314
  • 1
  • 10
  • i tried it earlier, it doesn't work it throws: `Traceback (most recent call last): File "", line 3, in TypeError: float() argument must be a string or a number, not 'Sum'` – Simou Jan 17 '20 at 11:24
  • you could convert it to a float before calling the Sum function – rossi Jan 17 '20 at 11:26
  • cannot use `float` in query `float() argument must be a string or a number, not 'F'` – hn_tired Oct 21 '21 at 14:02
0

I finally found two solutions that worked for me: Thanks to @Ramy's answer, I overidden the DjangoJSONEncoder, to support the conversion of a Decimal to a float:

import decimal
import uuid
from django.utils.functional import Promise
from django.core.serializers.json import DjangoJSONEncoder

class JsonDecimalToFloatEncoder(DjangoJSONEncoder):
def default(self, o):
    if isinstance(o, (decimal.Decimal, uuid.UUID, Promise)):
        return float(o)
    return super().default(o)

and I used it as he mentioned:

monthly_revenue = list(Booking.objects.annotate(month=Month('created_at'))
                               .values('month')
                               .annotate(total=Sum('price'))
                               .order_by('month'))
json_result = json.dumps(monthly_revenue,cls=ExtendedEncoder)
json_result
'[{"month": 11, "total": 4550.0}]'

this solution requires more work on the server side, so i opted for the second solution of @rossi which does more work on the database rather than the server, and that's what the django's documentation suggest.

from django.db.models import FloatField
from django.db.models.functions import Cast

list(Booking.objects.annotate(month=Month('created_at'))
                              .values('month')                                
                              .annotate(total_as_f=Cast('total', 
                               output_field=FloatField()))
                              .order_by('month'))

[{'month': 11, 'total': Decimal('4550.00'), 'total_as_f': 4550.0}]

hope this will help anyone in the futur.

Simou
  • 682
  • 9
  • 28