summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Smeaton <josh.smeaton@gmail.com>2014-12-01 17:11:23 +1100
committerTim Graham <timograham@gmail.com>2014-12-12 10:17:48 -0500
commit267a1dcd9b7877d97917ba6222fd247da060f9b0 (patch)
tree70399202a3ad907f0be364a7c2be865b6923fc01
parente2868308bfa4ec165fb54b6128ee18a7b4d50643 (diff)
Fixed #23941 -- Removed implicit decimal formatting from expressions.
-rw-r--r--django/db/backends/__init__.py2
-rw-r--r--django/db/backends/oracle/base.py4
-rw-r--r--django/db/backends/sqlite3/base.py4
-rw-r--r--django/db/backends/utils.py15
-rw-r--r--django/db/models/expressions.py2
-rw-r--r--django/db/models/fields/__init__.py2
-rw-r--r--docs/ref/models/expressions.txt11
-rw-r--r--tests/aggregation/tests.py19
-rw-r--r--tests/backends/tests.py18
9 files changed, 57 insertions, 20 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 64444297f3..ffe42e925d 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -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):
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 8f025ef41e..6443d9514d 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -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
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index e85bfaf031..db2ac07ce9 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -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):
diff --git a/django/db/backends/utils.py b/django/db/backends/utils.py
index a95255b862..7aa0cfdad5 100644
--- a/django/db/backends/utils.py
+++ b/django/db/backends/utils.py
@@ -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)
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 996a215da3..63c2951075 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -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):
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 3b42c998b7..de754c83e5 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -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)
diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt
index 5dadf35873..24e4f42834 100644
--- a/docs/ref/models/expressions.txt
+++ b/docs/ref/models/expressions.txt
@@ -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
=====================
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index e4b821b43d..88a5f11053 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -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'):
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index db79cd0325..1c01b4a1eb 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -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):