Forráskód Böngészése

Fixed #20246 -- Added non-breaking spaces between values an units

Emil Stenström 12 éve
szülő
commit
7d77e9786a

+ 1 - 0
AUTHORS

@@ -536,6 +536,7 @@ answer newbie questions, and generally made Django that much better:
     starrynight <cmorgh@gmail.com>
     Vasiliy Stavenko <stavenko@gmail.com>
     Thomas Steinacher <http://www.eggdrop.ch/>
+    Emil Stenström <em@kth.se>
     Johan C. Stöver <johan@nilling.nl>
     Nowell Strite <http://nowell.strite.org/>
     Thomas Stromberg <tstromberg@google.com>

+ 12 - 6
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}

+ 13 - 13
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:

+ 16 - 12
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'):

+ 7 - 0
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")

+ 4 - 3
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):

+ 36 - 32
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), '')

+ 30 - 29
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": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"),
         'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"&lt;a&gt;\&#39; <a>\'"),

+ 36 - 35
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')