Merge pull request #1565 from garrypolley/#19988-ordinal-html-i18n-fix
fixes #19988, allow html in ordinal for humanize
This commit is contained in:
commit
907f19eba7
|
@ -8,11 +8,13 @@ from django.conf import settings
|
||||||
from django.template import defaultfilters
|
from django.template import defaultfilters
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.formats import number_format
|
from django.utils.formats import number_format
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import pgettext, ungettext, ugettext as _
|
from django.utils.translation import pgettext, ungettext, ugettext as _
|
||||||
from django.utils.timezone import is_aware, utc
|
from django.utils.timezone import is_aware, utc
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def ordinal(value):
|
def ordinal(value):
|
||||||
"""
|
"""
|
||||||
|
@ -24,9 +26,11 @@ def ordinal(value):
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return value
|
return value
|
||||||
suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th'))
|
suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th'))
|
||||||
if value % 100 in (11, 12, 13): # special case
|
if value % 100 in (11, 12, 13): # special case
|
||||||
return "%d%s" % (value, suffixes[0])
|
return mark_safe("%d%s" % (value, suffixes[0]))
|
||||||
return "%d%s" % (value, suffixes[value % 10])
|
# Mark value safe so i18n does not break with <sup> or <sub> see #19988
|
||||||
|
return mark_safe("%d%s" % (value, suffixes[value % 10]))
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def intcomma(value, use_l10n=True):
|
def intcomma(value, use_l10n=True):
|
||||||
|
@ -97,6 +101,7 @@ intword_converters = (
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=False)
|
@register.filter(is_safe=False)
|
||||||
def intword(value):
|
def intword(value):
|
||||||
"""
|
"""
|
||||||
|
@ -130,6 +135,7 @@ def intword(value):
|
||||||
return _check_for_i18n(new_value, *converters(new_value))
|
return _check_for_i18n(new_value, *converters(new_value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def apnumber(value):
|
def apnumber(value):
|
||||||
"""
|
"""
|
||||||
|
@ -144,6 +150,7 @@ def apnumber(value):
|
||||||
return value
|
return value
|
||||||
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
|
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
|
||||||
|
|
||||||
|
|
||||||
# Perform the comparison in the default time zone when USE_TZ = True
|
# Perform the comparison in the default time zone when USE_TZ = True
|
||||||
# (unless a specific time zone has been applied with the |timezone filter).
|
# (unless a specific time zone has been applied with the |timezone filter).
|
||||||
@register.filter(expects_localtime=True)
|
@register.filter(expects_localtime=True)
|
||||||
|
@ -172,6 +179,7 @@ def naturalday(value, arg=None):
|
||||||
return _('yesterday')
|
return _('yesterday')
|
||||||
return defaultfilters.date(value, arg)
|
return defaultfilters.date(value, arg)
|
||||||
|
|
||||||
|
|
||||||
# This filter doesn't require expects_localtime=True because it deals properly
|
# This filter doesn't require expects_localtime=True because it deals properly
|
||||||
# with both naive and aware datetimes. Therefore avoid the cost of conversion.
|
# with both naive and aware datetimes. Therefore avoid the cost of conversion.
|
||||||
@register.filter
|
@register.filter
|
||||||
|
@ -180,7 +188,7 @@ def naturaltime(value):
|
||||||
For date and time values shows how many seconds, minutes or hours ago
|
For date and time values shows how many seconds, minutes or hours ago
|
||||||
compared to current timestamp returns representing string.
|
compared to current timestamp returns representing string.
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, date): # datetime is a subclass of date
|
if not isinstance(value, date): # datetime is a subclass of date
|
||||||
return value
|
return value
|
||||||
|
|
||||||
now = datetime.now(utc if is_aware(value) else None)
|
now = datetime.now(utc if is_aware(value) else None)
|
||||||
|
|
|
@ -28,6 +28,7 @@ from i18n import TransRealMixin
|
||||||
|
|
||||||
now = datetime.datetime(2012, 3, 9, 22, 30)
|
now = datetime.datetime(2012, 3, 9, 22, 30)
|
||||||
|
|
||||||
|
|
||||||
class MockDateTime(datetime.datetime):
|
class MockDateTime(datetime.datetime):
|
||||||
@classmethod
|
@classmethod
|
||||||
def now(self, tz=None):
|
def now(self, tz=None):
|
||||||
|
@ -40,11 +41,11 @@ class MockDateTime(datetime.datetime):
|
||||||
|
|
||||||
class HumanizeTests(TransRealMixin, TestCase):
|
class HumanizeTests(TransRealMixin, TestCase):
|
||||||
|
|
||||||
def humanize_tester(self, test_list, result_list, method):
|
def humanize_tester(self, test_list, result_list, method, normalize_result_func=escape):
|
||||||
for test_content, result in zip(test_list, result_list):
|
for test_content, result in zip(test_list, result_list):
|
||||||
t = Template('{%% load humanize %%}{{ test_content|%s }}' % method)
|
t = Template('{%% load humanize %%}{{ test_content|%s }}' % method)
|
||||||
rendered = t.render(Context(locals())).strip()
|
rendered = t.render(Context(locals())).strip()
|
||||||
self.assertEqual(rendered, escape(result),
|
self.assertEqual(rendered, normalize_result_func(result),
|
||||||
msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result))
|
msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result))
|
||||||
|
|
||||||
def test_ordinal(self):
|
def test_ordinal(self):
|
||||||
|
@ -58,6 +59,19 @@ class HumanizeTests(TransRealMixin, TestCase):
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
self.humanize_tester(test_list, result_list, 'ordinal')
|
self.humanize_tester(test_list, result_list, 'ordinal')
|
||||||
|
|
||||||
|
def test_i18n_html_ordinal(self):
|
||||||
|
"""Allow html in output on i18n strings"""
|
||||||
|
test_list = ('1', '2', '3', '4', '11', '12',
|
||||||
|
'13', '101', '102', '103', '111',
|
||||||
|
'something else', None)
|
||||||
|
result_list = ('1<sup>er</sup>', '2<sup>e</sup>', '3<sup>e</sup>', '4<sup>e</sup>',
|
||||||
|
'11<sup>e</sup>', '12<sup>e</sup>', '13<sup>e</sup>', '101<sup>er</sup>',
|
||||||
|
'102<sup>e</sup>', '103<sup>e</sup>', '111<sup>e</sup>', 'something else',
|
||||||
|
'None')
|
||||||
|
|
||||||
|
with translation.override('fr-fr'):
|
||||||
|
self.humanize_tester(test_list, result_list, 'ordinal', lambda x: x)
|
||||||
|
|
||||||
def test_intcomma(self):
|
def test_intcomma(self):
|
||||||
test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25,
|
test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25,
|
||||||
'100', '1000', '10123', '10311', '1000000', '1234567.1234567', Decimal('1234567.1234567'),
|
'100', '1000', '10123', '10311', '1000000', '1234567.1234567', Decimal('1234567.1234567'),
|
||||||
|
|
Loading…
Reference in New Issue