diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 71e5bd61ea..9734124c88 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -1,6 +1,12 @@ """Default variable filters.""" import re + +try: + from decimal import Decimal, InvalidOperation, ROUND_HALF_UP +except ImportError: + from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP + import random as random_module try: from functools import wraps @@ -45,7 +51,6 @@ def stringfilter(func): # STRINGS # ################### - def addslashes(value): """ Adds slashes before quotes. Useful for escaping strings in CSV, for @@ -92,6 +97,18 @@ def fix_ampersands(value): fix_ampersands.is_safe=True fix_ampersands = stringfilter(fix_ampersands) +# Values for testing floatformat input against infinity and NaN representations, +# which differ across platforms and Python versions. Some (i.e. old Windows +# ones) are not recognized by Decimal but we want to return them unchanged vs. +# returning an empty string as we do for completley invalid input. Note these +# need to be built up from values that are not inf/nan, since inf/nan values do +# not reload properly from .pyc files on Windows prior to some level of Python 2.5 +# (see Python Issue757815 and Issue1080440). +pos_inf = 1e200 * 1e200 +neg_inf = -1e200 * 1e200 +nan = (1e200 * 1e200) / (1e200 * 1e200) +special_floats = [str(pos_inf), str(neg_inf), str(nan)] + def floatformat(text, arg=-1): """ Displays a float to a specified number of decimal places. @@ -119,24 +136,42 @@ def floatformat(text, arg=-1): * {{ num1|floatformat:"-3" }} displays "34.232" * {{ num2|floatformat:"-3" }} displays "34" * {{ num3|floatformat:"-3" }} displays "34.260" + + If the input float is infinity or NaN, the (platform-dependent) string + representation of that value will be displayed. """ + try: - f = float(text) - except (ValueError, TypeError): + input_val = force_unicode(text) + d = Decimal(input_val) + except UnicodeEncodeError: return u'' + except InvalidOperation: + if input_val in special_floats: + return input_val + else: + return u'' try: - d = int(arg) + p = int(arg) except ValueError: - return force_unicode(f) + return input_val + try: - m = f - int(f) - except OverflowError: - return force_unicode(f) - if not m and d < 0: - return mark_safe(u'%d' % int(f)) + m = int(d) - d + except (OverflowError, InvalidOperation): + return input_val + + if not m and p < 0: + return mark_safe(u'%d' % (int(d))) + + if p == 0: + exp = Decimal(1) else: - formatstr = u'%%.%df' % abs(d) - return mark_safe(formatstr % f) + exp = Decimal('1.0') / (Decimal(10) ** abs(p)) + try: + return mark_safe(u'%s' % str(d.quantize(exp, ROUND_HALF_UP))) + except InvalidOperation: + return input_val floatformat.is_safe = True def iriencode(value): diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 9e360fc061..a97596f4d7 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -13,15 +13,15 @@ u'0.1' u'0.0' >>> floatformat(0.0) u'0' ->>> floatformat(7.7,3) +>>> floatformat(7.7, 3) u'7.700' ->>> floatformat(6.000000,3) +>>> floatformat(6.000000, 3) u'6.000' >>> floatformat(6.200000, 3) u'6.200' >>> floatformat(6.200000, -3) u'6.200' ->>> floatformat(13.1031,-3) +>>> floatformat(13.1031, -3) u'13.103' >>> floatformat(11.1197, -2) u'11.12' @@ -35,10 +35,23 @@ u'8.280' u'' >>> floatformat(13.1031, u'bar') u'13.1031' +>>> floatformat(18.125, 2) +u'18.13' >>> floatformat(u'foo', u'bar') u'' +>>> floatformat(u'¿Cómo esta usted?') +u'' >>> floatformat(None) u'' +>>> pos_inf = float(1e30000) +>>> floatformat(pos_inf) == unicode(pos_inf) +True +>>> neg_inf = float(-1e30000) +>>> floatformat(neg_inf) == unicode(neg_inf) +True +>>> nan = pos_inf / pos_inf +>>> floatformat(nan) == unicode(nan) +True >>> addslashes(u'"double quotes" and \'single quotes\'') u'\\"double quotes\\" and \\\'single quotes\\\''