Fixed #23941 -- Removed implicit decimal formatting from expressions.
This commit is contained in:
parent
e2868308bf
commit
267a1dcd9b
|
@ -1195,8 +1195,6 @@ class BaseDatabaseOperations(object):
|
|||
Transform a decimal.Decimal value to an object compatible with what is
|
||||
expected by the backend driver for decimal (numeric) columns.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
return utils.format_number(value, max_digits, decimal_places)
|
||||
|
||||
def year_lookup_bounds_for_date_field(self, value):
|
||||
|
|
|
@ -312,9 +312,7 @@ WHEN (new.%(col_name)s IS NULL)
|
|||
return value
|
||||
|
||||
def convert_decimalfield_value(self, value, field):
|
||||
if value is not None:
|
||||
value = backend_utils.typecast_decimal(field.format_number(value))
|
||||
return value
|
||||
return backend_utils.typecast_decimal(field.format_number(value))
|
||||
|
||||
# cx_Oracle always returns datetime.datetime objects for
|
||||
# DATE and TIMESTAMP columns, but Django wants to see a
|
||||
|
|
|
@ -277,9 +277,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
return converters
|
||||
|
||||
def convert_decimalfield_value(self, value, field):
|
||||
if value is not None:
|
||||
value = backend_utils.typecast_decimal(field.format_number(value))
|
||||
return value
|
||||
return backend_utils.typecast_decimal(field.format_number(value))
|
||||
|
||||
def convert_datefield_value(self, value, field):
|
||||
if value is not None and not isinstance(value, datetime.date):
|
||||
|
|
|
@ -191,9 +191,18 @@ def format_number(value, max_digits, decimal_places):
|
|||
Formats a number into a string with the requisite number of digits and
|
||||
decimal places.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, decimal.Decimal):
|
||||
context = decimal.getcontext().copy()
|
||||
context.prec = max_digits
|
||||
return "{0:f}".format(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context))
|
||||
else:
|
||||
if max_digits is not None:
|
||||
context.prec = max_digits
|
||||
if decimal_places is not None:
|
||||
value = value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)
|
||||
else:
|
||||
context.traps[decimal.Rounded] = 1
|
||||
value = context.create_decimal(value)
|
||||
return "{:f}".format(value)
|
||||
if decimal_places is not None:
|
||||
return "%.*f" % (decimal_places, value)
|
||||
return "{:f}".format(value)
|
||||
|
|
|
@ -255,7 +255,7 @@ class ExpressionNode(CombinableMixin):
|
|||
elif internal_type.endswith('IntegerField'):
|
||||
return int(value)
|
||||
elif internal_type == 'DecimalField':
|
||||
return backend_utils.typecast_decimal(field.format_number(value))
|
||||
return backend_utils.typecast_decimal(value)
|
||||
return value
|
||||
|
||||
def get_lookup(self, lookup):
|
||||
|
|
|
@ -1536,7 +1536,7 @@ class DecimalField(Field):
|
|||
)
|
||||
|
||||
def _format(self, value):
|
||||
if isinstance(value, six.string_types) or value is None:
|
||||
if isinstance(value, six.string_types):
|
||||
return value
|
||||
else:
|
||||
return self.format_number(value)
|
||||
|
|
|
@ -257,7 +257,10 @@ placeholder within the ``template``.
|
|||
|
||||
The ``output_field`` argument requires a model field instance, like
|
||||
``IntegerField()`` or ``BooleanField()``, into which Django will load the value
|
||||
after it's retrieved from the database.
|
||||
after it's retrieved from the database. Usually no arguments are needed when
|
||||
instantiating the model field as any arguments relating to data validation
|
||||
(``max_length``, ``max_digits``, etc.) will not be enforced on the expression's
|
||||
output value.
|
||||
|
||||
Note that ``output_field`` is only required when Django is unable to determine
|
||||
what field type the result should be. Complex expressions that mix field types
|
||||
|
@ -318,8 +321,10 @@ values into their corresponding database type.
|
|||
|
||||
The ``output_field`` argument should be a model field instance, like
|
||||
``IntegerField()`` or ``BooleanField()``, into which Django will load the value
|
||||
after it's retrieved from the database.
|
||||
|
||||
after it's retrieved from the database. Usually no arguments are needed when
|
||||
instantiating the model field as any arguments relating to data validation
|
||||
(``max_length``, ``max_digits``, etc.) will not be enforced on the expression's
|
||||
output value.
|
||||
|
||||
Technical Information
|
||||
=====================
|
||||
|
|
|
@ -17,7 +17,7 @@ with warnings.catch_warnings(record=True) as w:
|
|||
from django.test import TestCase
|
||||
from django.test.utils import Approximate
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils import six
|
||||
from django.utils import six, timezone
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
|
||||
from .models import Author, Publisher, Book, Store
|
||||
|
@ -689,6 +689,19 @@ class BaseAggregateTestCase(TestCase):
|
|||
self.assertNotIn('order by', qstr)
|
||||
self.assertEqual(qstr.count(' join '), 0)
|
||||
|
||||
def test_decimal_max_digits_has_no_effect(self):
|
||||
Book.objects.all().delete()
|
||||
a1 = Author.objects.first()
|
||||
p1 = Publisher.objects.first()
|
||||
thedate = timezone.now()
|
||||
for i in range(10):
|
||||
Book.objects.create(
|
||||
isbn="abcde{}".format(i), name="none", pages=10, rating=4.0,
|
||||
price=9999.98, contact=a1, publisher=p1, pubdate=thedate)
|
||||
|
||||
book = Book.objects.aggregate(price_sum=Sum('price'))
|
||||
self.assertEqual(book['price_sum'], Decimal("99999.80"))
|
||||
|
||||
|
||||
class ComplexAggregateTestCase(TestCase):
|
||||
fixtures = ["aggregation.json"]
|
||||
|
@ -755,8 +768,8 @@ class ComplexAggregateTestCase(TestCase):
|
|||
self.assertEqual(b2.sums, 383.69)
|
||||
|
||||
b3 = Book.objects.annotate(sums=Sum(F('rating') + F('pages') + F('price'),
|
||||
output_field=DecimalField(max_digits=6, decimal_places=2))).get(pk=4)
|
||||
self.assertEqual(b3.sums, Decimal("383.69"))
|
||||
output_field=DecimalField())).get(pk=4)
|
||||
self.assertEqual(b3.sums, Approximate(Decimal("383.69"), places=2))
|
||||
|
||||
def test_complex_aggregations_require_kwarg(self):
|
||||
with six.assertRaisesRegex(self, TypeError, 'Complex expressions require an alias'):
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import copy
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from decimal import Decimal, Rounded
|
||||
import re
|
||||
import threading
|
||||
import unittest
|
||||
|
@ -1059,6 +1059,22 @@ class BackendUtilTests(TestCase):
|
|||
'0.1')
|
||||
equal('0.1234567890', 12, 0,
|
||||
'0')
|
||||
equal('0.1234567890', None, 0,
|
||||
'0')
|
||||
equal('1234567890.1234567890', None, 0,
|
||||
'1234567890')
|
||||
equal('1234567890.1234567890', None, 2,
|
||||
'1234567890.12')
|
||||
equal('0.1234', 5, None,
|
||||
'0.1234')
|
||||
equal('123.12', 5, None,
|
||||
'123.12')
|
||||
with self.assertRaises(Rounded):
|
||||
equal('0.1234567890', 5, None,
|
||||
'0.12346')
|
||||
with self.assertRaises(Rounded):
|
||||
equal('1234567890.1234', 5, None,
|
||||
'1234600000')
|
||||
|
||||
|
||||
class DBTestSettingsRenamedTests(IgnoreAllDeprecationWarningsMixin, TestCase):
|
||||
|
|
Loading…
Reference in New Issue