Index: django/utils/text.py =================================================================== --- django/utils/text.py (revision 5505) +++ django/utils/text.py (working copy) @@ -34,12 +34,17 @@ def truncate_words(s, num): "Truncates a string after a certain number of words." length = int(num) - words = s.split() - if len(words) > length: - words = words[:length] - if not words[-1].endswith('...'): - words.append('...') - return ' '.join(words) + words = [] + for count, match in enumerate(re.finditer(r'(?:^\s+)?\S+\s*', s)): + if count >= length: # count is 0-based + if words: + last_word = words[-1].rstrip() + words[-1] = last_word + if not last_word.endswith('...'): + words.append(' ...') + break + words.append(match.group()) + return ''.join(words) def truncate_html_words(s, num): """ Index: tests/regressiontests/defaultfilters/tests.py =================================================================== --- tests/regressiontests/defaultfilters/tests.py (revision 5505) +++ tests/regressiontests/defaultfilters/tests.py (working copy) @@ -89,6 +89,13 @@ >>> truncatewords('A sentence with a few words in it', 'not a number') 'A sentence with a few words in it' +# truncatewords should preserve whitespace, as it may be useful for +# chained filters (such as linebreaks) +>>> truncatewords(' Three \n words\n still\t', 2) +' Three \n words ...' +>>> truncatewords(' Three \n words\n still\t', 3) +' Three \n words\n still\t' + >>> truncatewords_html('
one two - three
four five