mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #23941 -- Removed implicit decimal formatting from expressions.
This commit is contained in:
		| @@ -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): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user