From 7d77e9786a118dd95a268872dd9d36664066b96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Stenstro=CC=88m?= Date: Sat, 18 May 2013 13:58:45 +0200 Subject: [PATCH] Fixed #20246 -- Added non-breaking spaces between values an units --- AUTHORS | 1 + .../contrib/humanize/templatetags/humanize.py | 18 +++-- django/contrib/humanize/tests.py | 26 +++---- django/template/defaultfilters.py | 28 ++++---- django/utils/html.py | 7 ++ django/utils/timesince.py | 7 +- tests/defaultfilters/tests.py | 68 +++++++++--------- tests/template_tests/filters.py | 59 +++++++-------- tests/utils_tests/test_timesince.py | 71 ++++++++++--------- 9 files changed, 155 insertions(+), 130 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6ecda3ff57..2e02f7c07e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -536,6 +536,7 @@ answer newbie questions, and generally made Django that much better: starrynight Vasiliy Stavenko Thomas Steinacher + Emil Stenström Johan C. Stöver Nowell Strite Thomas Stromberg diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 21f4c452fa..eaee734f75 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -194,17 +194,20 @@ def naturaltime(value): return _('now') elif delta.seconds < 60: return ungettext( - 'a second ago', '%(count)s seconds ago', delta.seconds + # Translators: \\u00a0 is non-breaking space + 'a second ago', '%(count)s\u00a0seconds ago', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - 'a minute ago', '%(count)s minutes ago', count + # Translators: \\u00a0 is non-breaking space + 'a minute ago', '%(count)s\u00a0minutes ago', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - 'an hour ago', '%(count)s hours ago', count + # Translators: \\u00a0 is non-breaking space + 'an hour ago', '%(count)s\u00a0hours ago', count ) % {'count': count} else: delta = value - now @@ -216,15 +219,18 @@ def naturaltime(value): return _('now') elif delta.seconds < 60: return ungettext( - 'a second from now', '%(count)s seconds from now', delta.seconds + # Translators: \\u00a0 is non-breaking space + 'a second from now', '%(count)s\u00a0seconds from now', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - 'a minute from now', '%(count)s minutes from now', count + # Translators: \\u00a0 is non-breaking space + 'a minute from now', '%(count)s\u00a0minutes from now', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - 'an hour from now', '%(count)s hours from now', count + # Translators: \\u00a0 is non-breaking space + 'an hour from now', '%(count)s\u00a0hours from now', count ) % {'count': count} diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py index 1e1c8424e6..5c39c79ac3 100644 --- a/django/contrib/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -195,22 +195,22 @@ class HumanizeTests(TestCase): result_list = [ 'now', 'a second ago', - '30 seconds ago', + '30\xa0seconds ago', 'a minute ago', - '2 minutes ago', + '2\xa0minutes ago', 'an hour ago', - '23 hours ago', - '1 day ago', - '1 year, 4 months ago', + '23\xa0hours ago', + '1\xa0day ago', + '1\xa0year, 4\xa0months ago', 'a second from now', - '30 seconds from now', + '30\xa0seconds from now', 'a minute from now', - '2 minutes from now', + '2\xa0minutes from now', 'an hour from now', - '23 hours from now', - '1 day from now', - '2 days, 6 hours from now', - '1 year, 4 months from now', + '23\xa0hours from now', + '1\xa0day from now', + '2\xa0days, 6\xa0hours from now', + '1\xa0year, 4\xa0months from now', 'now', 'now', ] @@ -218,8 +218,8 @@ class HumanizeTests(TestCase): # date in naive arithmetic is only 2 days and 5 hours after in # aware arithmetic. result_list_with_tz_support = result_list[:] - assert result_list_with_tz_support[-4] == '2 days, 6 hours from now' - result_list_with_tz_support[-4] == '2 days, 5 hours from now' + assert result_list_with_tz_support[-4] == '2\xa0days, 6\xa0hours from now' + result_list_with_tz_support[-4] == '2\xa0days, 5\xa0hours from now' orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime try: diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 88526e5a20..4201cfeb67 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -14,7 +14,7 @@ from django.utils import formats from django.utils.dateformat import format, time_format from django.utils.encoding import force_text, iri_to_uri from django.utils.html import (conditional_escape, escapejs, fix_ampersands, - escape, urlize as urlize_impl, linebreaks, strip_tags) + escape, urlize as urlize_impl, linebreaks, strip_tags, avoid_wrapping) from django.utils.http import urlquote from django.utils.text import Truncator, wrap, phone2numeric from django.utils.safestring import mark_safe, SafeData, mark_for_escaping @@ -810,7 +810,8 @@ def filesizeformat(bytes): try: bytes = float(bytes) except (TypeError,ValueError,UnicodeDecodeError): - return ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0} + value = ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0} + return avoid_wrapping(value) filesize_number_format = lambda value: formats.number_format(round(value, 1), 1) @@ -821,16 +822,19 @@ def filesizeformat(bytes): PB = 1<<50 if bytes < KB: - return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} - if bytes < MB: - return ugettext("%s KB") % filesize_number_format(bytes / KB) - if bytes < GB: - return ugettext("%s MB") % filesize_number_format(bytes / MB) - if bytes < TB: - return ugettext("%s GB") % filesize_number_format(bytes / GB) - if bytes < PB: - return ugettext("%s TB") % filesize_number_format(bytes / TB) - return ugettext("%s PB") % filesize_number_format(bytes / PB) + value = ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} + elif bytes < MB: + value = ugettext("%s KB") % filesize_number_format(bytes / KB) + elif bytes < GB: + value = ugettext("%s MB") % filesize_number_format(bytes / MB) + elif bytes < TB: + value = ugettext("%s GB") % filesize_number_format(bytes / GB) + elif bytes < PB: + value = ugettext("%s TB") % filesize_number_format(bytes / TB) + else: + value = ugettext("%s PB") % filesize_number_format(bytes / PB) + + return avoid_wrapping(value) @register.filter(is_safe=False) def pluralize(value, arg='s'): diff --git a/django/utils/html.py b/django/utils/html.py index 8b28d97d13..edddc48e62 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -281,3 +281,10 @@ def clean_html(text): text = trailing_empty_content_re.sub('', text) return text clean_html = allow_lazy(clean_html, six.text_type) + +def avoid_wrapping(value): + """ + Avoid text wrapping in the middle of a phrase by adding non-breaking + spaces where there previously were normal spaces. + """ + return value.replace(" ", "\xa0") diff --git a/django/utils/timesince.py b/django/utils/timesince.py index d70ab2ffe1..46c387f262 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import datetime +from django.utils.html import avoid_wrapping from django.utils.timezone import is_aware, utc from django.utils.translation import ugettext, ungettext_lazy @@ -40,18 +41,18 @@ def timesince(d, now=None, reversed=False): since = delta.days * 24 * 60 * 60 + delta.seconds if since <= 0: # d is in the future compared to now, stop processing. - return ugettext('0 minutes') + return avoid_wrapping(ugettext('0 minutes')) for i, (seconds, name) in enumerate(chunks): count = since // seconds if count != 0: break - result = name % count + result = avoid_wrapping(name % count) if i + 1 < len(chunks): # Now get the second item seconds2, name2 = chunks[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: - result += ugettext(', ') + name2 % count2 + result += ugettext(', ') + avoid_wrapping(name2 % count2) return result def timeuntil(d, now=None): diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py index 21734faf95..be16719c8e 100644 --- a/tests/defaultfilters/tests.py +++ b/tests/defaultfilters/tests.py @@ -527,24 +527,26 @@ class DefaultFiltersTests(TestCase): def test_timesince(self): # real testing is done in timesince.py, where we can provide our own 'now' + # NOTE: \xa0 avoids wrapping between value and unit self.assertEqual( timesince_filter(datetime.datetime.now() - datetime.timedelta(1)), - '1 day') + '1\xa0day') self.assertEqual( timesince_filter(datetime.datetime(2005, 12, 29), datetime.datetime(2005, 12, 30)), - '1 day') + '1\xa0day') def test_timeuntil(self): + # NOTE: \xa0 avoids wrapping between value and unit self.assertEqual( timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)), - '1 day') + '1\xa0day') self.assertEqual( timeuntil_filter(datetime.datetime(2005, 12, 30), datetime.datetime(2005, 12, 29)), - '1 day') + '1\xa0day') def test_default(self): self.assertEqual(default("val", "default"), 'val') @@ -574,43 +576,45 @@ class DefaultFiltersTests(TestCase): 'get out of town') def test_filesizeformat(self): - self.assertEqual(filesizeformat(1023), '1023 bytes') - self.assertEqual(filesizeformat(1024), '1.0 KB') - self.assertEqual(filesizeformat(10*1024), '10.0 KB') - self.assertEqual(filesizeformat(1024*1024-1), '1024.0 KB') - self.assertEqual(filesizeformat(1024*1024), '1.0 MB') - self.assertEqual(filesizeformat(1024*1024*50), '50.0 MB') - self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0 MB') - self.assertEqual(filesizeformat(1024*1024*1024), '1.0 GB') - self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0 TB') - self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0 PB') + # NOTE: \xa0 avoids wrapping between value and unit + self.assertEqual(filesizeformat(1023), '1023\xa0bytes') + self.assertEqual(filesizeformat(1024), '1.0\xa0KB') + self.assertEqual(filesizeformat(10*1024), '10.0\xa0KB') + self.assertEqual(filesizeformat(1024*1024-1), '1024.0\xa0KB') + self.assertEqual(filesizeformat(1024*1024), '1.0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*50), '50.0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*1024), '1.0\xa0GB') + self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0\xa0TB') + self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0\xa0PB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000), - '2000.0 PB') - self.assertEqual(filesizeformat(complex(1,-1)), '0 bytes') - self.assertEqual(filesizeformat(""), '0 bytes') + '2000.0\xa0PB') + self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0bytes') + self.assertEqual(filesizeformat(""), '0\xa0bytes') self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"), - '0 bytes') + '0\xa0bytes') def test_localized_filesizeformat(self): + # NOTE: \xa0 avoids wrapping between value and unit with self.settings(USE_L10N=True): with translation.override('de', deactivate=True): - self.assertEqual(filesizeformat(1023), '1023 Bytes') - self.assertEqual(filesizeformat(1024), '1,0 KB') - self.assertEqual(filesizeformat(10*1024), '10,0 KB') - self.assertEqual(filesizeformat(1024*1024-1), '1024,0 KB') - self.assertEqual(filesizeformat(1024*1024), '1,0 MB') - self.assertEqual(filesizeformat(1024*1024*50), '50,0 MB') - self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0 MB') - self.assertEqual(filesizeformat(1024*1024*1024), '1,0 GB') - self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0 TB') + self.assertEqual(filesizeformat(1023), '1023\xa0Bytes') + self.assertEqual(filesizeformat(1024), '1,0\xa0KB') + self.assertEqual(filesizeformat(10*1024), '10,0\xa0KB') + self.assertEqual(filesizeformat(1024*1024-1), '1024,0\xa0KB') + self.assertEqual(filesizeformat(1024*1024), '1,0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*50), '50,0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0\xa0MB') + self.assertEqual(filesizeformat(1024*1024*1024), '1,0\xa0GB') + self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0\xa0TB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), - '1,0 PB') + '1,0\xa0PB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000), - '2000,0 PB') - self.assertEqual(filesizeformat(complex(1,-1)), '0 Bytes') - self.assertEqual(filesizeformat(""), '0 Bytes') + '2000,0\xa0PB') + self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0Bytes') + self.assertEqual(filesizeformat(""), '0\xa0Bytes') self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"), - '0 Bytes') + '0\xa0Bytes') def test_pluralize(self): self.assertEqual(pluralize(1), '') diff --git a/tests/template_tests/filters.py b/tests/template_tests/filters.py index 7ba1681fd5..68ef15d827 100644 --- a/tests/template_tests/filters.py +++ b/tests/template_tests/filters.py @@ -35,59 +35,60 @@ def get_filter_tests(): now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone today = date.today() + # NOTE: \xa0 avoids wrapping between value and unit return { # Default compare with datetime.now() - 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), - 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'), - 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'), + 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1\xa0minute'), + 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1\xa0day'), + 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1\xa0hour, 25\xa0minutes'), # Compare to a given parameter - 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1 day'), - 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1 minute'), + 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1\xa0day'), + 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1\xa0minute'), # Check that timezone is respected - 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8 hours'), + 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8\xa0hours'), # Regression for #7443 - 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1 week'), - 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1 week'), - 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'), - 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'), + 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1\xa0week'), + 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1\xa0week'), + 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0\xa0minutes'), + 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0\xa0minutes'), # Ensures that differing timezones are calculated correctly - 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'), - 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'), - 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'), - 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'), + 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0\xa0minutes'), + 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0\xa0minutes'), + 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0\xa0minutes'), + 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0\xa0minutes'), 'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''), 'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''), # Regression for #9065 (two date objects). - 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0 minutes'), - 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1 day'), + 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0\xa0minutes'), + 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1\xa0day'), # Default compare with datetime.now() - 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), - 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), - 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), + 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2\xa0minutes'), + 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1\xa0day'), + 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8\xa0hours, 10\xa0minutes'), # Compare to a given parameter - 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'), - 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'), + 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1\xa0day'), + 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1\xa0minute'), # Regression for #7443 - 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0 minutes'), - 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0 minutes'), - 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'), - 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'), + 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0\xa0minutes'), + 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0\xa0minutes'), + 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1\xa0week'), + 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1\xa0week'), # Ensures that differing timezones are calculated correctly - 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'), - 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'), + 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0\xa0minutes'), + 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0\xa0minutes'), # Regression for #9065 (two date objects). - 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0 minutes'), - 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1 day'), + 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0\xa0minutes'), + 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1\xa0day'), 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "'", "b": mark_safe("'")}, r"\' \'"), 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "'", "b": mark_safe("'")}, r"<a>\' \'"), diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py index 5e641a42c4..cdb95e6877 100644 --- a/tests/utils_tests/test_timesince.py +++ b/tests/utils_tests/test_timesince.py @@ -21,32 +21,33 @@ class TimesinceTests(unittest.TestCase): def test_equal_datetimes(self): """ equal datetimes. """ - self.assertEqual(timesince(self.t, self.t), '0 minutes') + # NOTE: \xa0 avoids wrapping between value and unit + self.assertEqual(timesince(self.t, self.t), '0\xa0minutes') def test_ignore_microseconds_and_seconds(self): """ Microseconds and seconds are ignored. """ self.assertEqual(timesince(self.t, self.t+self.onemicrosecond), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t+self.onesecond), - '0 minutes') + '0\xa0minutes') def test_other_units(self): """ Test other units. """ self.assertEqual(timesince(self.t, self.t+self.oneminute), - '1 minute') - self.assertEqual(timesince(self.t, self.t+self.onehour), '1 hour') - self.assertEqual(timesince(self.t, self.t+self.oneday), '1 day') - self.assertEqual(timesince(self.t, self.t+self.oneweek), '1 week') + '1\xa0minute') + self.assertEqual(timesince(self.t, self.t+self.onehour), '1\xa0hour') + self.assertEqual(timesince(self.t, self.t+self.oneday), '1\xa0day') + self.assertEqual(timesince(self.t, self.t+self.oneweek), '1\xa0week') self.assertEqual(timesince(self.t, self.t+self.onemonth), - '1 month') - self.assertEqual(timesince(self.t, self.t+self.oneyear), '1 year') + '1\xa0month') + self.assertEqual(timesince(self.t, self.t+self.oneyear), '1\xa0year') def test_multiple_units(self): """ Test multiple units. """ self.assertEqual(timesince(self.t, - self.t+2*self.oneday+6*self.onehour), '2 days, 6 hours') + self.t+2*self.oneday+6*self.onehour), '2\xa0days, 6\xa0hours') self.assertEqual(timesince(self.t, - self.t+2*self.oneweek+2*self.oneday), '2 weeks, 2 days') + self.t+2*self.oneweek+2*self.oneday), '2\xa0weeks, 2\xa0days') def test_display_first_unit(self): """ @@ -55,10 +56,10 @@ class TimesinceTests(unittest.TestCase): """ self.assertEqual(timesince(self.t, self.t+2*self.oneweek+3*self.onehour+4*self.oneminute), - '2 weeks') + '2\xa0weeks') self.assertEqual(timesince(self.t, - self.t+4*self.oneday+5*self.oneminute), '4 days') + self.t+4*self.oneday+5*self.oneminute), '4\xa0days') def test_display_second_before_first(self): """ @@ -66,30 +67,30 @@ class TimesinceTests(unittest.TestCase): get 0 minutes. """ self.assertEqual(timesince(self.t, self.t-self.onemicrosecond), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.onesecond), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.oneminute), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.onehour), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.oneday), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.oneweek), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.onemonth), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-self.oneyear), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, - self.t-2*self.oneday-6*self.onehour), '0 minutes') + self.t-2*self.oneday-6*self.onehour), '0\xa0minutes') self.assertEqual(timesince(self.t, - self.t-2*self.oneweek-2*self.oneday), '0 minutes') + self.t-2*self.oneweek-2*self.oneday), '0\xa0minutes') self.assertEqual(timesince(self.t, self.t-2*self.oneweek-3*self.onehour-4*self.oneminute), - '0 minutes') + '0\xa0minutes') self.assertEqual(timesince(self.t, - self.t-4*self.oneday-5*self.oneminute), '0 minutes') + self.t-4*self.oneday-5*self.oneminute), '0\xa0minutes') def test_different_timezones(self): """ When using two different timezones. """ @@ -97,28 +98,28 @@ class TimesinceTests(unittest.TestCase): now_tz = datetime.datetime.now(LocalTimezone(now)) now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15)) - self.assertEqual(timesince(now), '0 minutes') - self.assertEqual(timesince(now_tz), '0 minutes') - self.assertEqual(timeuntil(now_tz, now_tz_i), '0 minutes') + self.assertEqual(timesince(now), '0\xa0minutes') + self.assertEqual(timesince(now_tz), '0\xa0minutes') + self.assertEqual(timeuntil(now_tz, now_tz_i), '0\xa0minutes') def test_date_objects(self): """ Both timesince and timeuntil should work on date objects (#17937). """ today = datetime.date.today() - self.assertEqual(timesince(today + self.oneday), '0 minutes') - self.assertEqual(timeuntil(today - self.oneday), '0 minutes') + self.assertEqual(timesince(today + self.oneday), '0\xa0minutes') + self.assertEqual(timeuntil(today - self.oneday), '0\xa0minutes') def test_both_date_objects(self): """ Timesince should work with both date objects (#9672) """ today = datetime.date.today() - self.assertEqual(timeuntil(today + self.oneday, today), '1 day') - self.assertEqual(timeuntil(today - self.oneday, today), '0 minutes') - self.assertEqual(timeuntil(today + self.oneweek, today), '1 week') + self.assertEqual(timeuntil(today + self.oneday, today), '1\xa0day') + self.assertEqual(timeuntil(today - self.oneday, today), '0\xa0minutes') + self.assertEqual(timeuntil(today + self.oneweek, today), '1\xa0week') def test_naive_datetime_with_tzinfo_attribute(self): class naive(datetime.tzinfo): def utcoffset(self, dt): return None future = datetime.datetime(2080, 1, 1, tzinfo=naive()) - self.assertEqual(timesince(future), '0 minutes') + self.assertEqual(timesince(future), '0\xa0minutes') past = datetime.datetime(1980, 1, 1, tzinfo=naive()) - self.assertEqual(timeuntil(past), '0 minutes') + self.assertEqual(timeuntil(past), '0\xa0minutes')