The Problem:
The built-in Django filter, truncate_words, truncates a string after a certain number of words. This is great, but many times I find I have very tight space restrictions in certain areas of a page, and a string that is too long would push its way into another element and subsequently into my head in the form of a headache.
The built-in truncate_words filter is no help here — it does nothing to limit the width of a string.
I.e., “One two” and “ooooooooooooooooooooooooooonnnnnnnnnnnnnnneeeeeeeeeeeeee twoooooooooooooo” are both only 2 words, yet they have extremely different widths
The Solution:
We need a filter that truncates not only by words, but by characters too. It’s an extremely simple filter, and often times I wonder why it’s not included in Django.
Let’s start from the very beginning. Every filter must live in your app’s templatetags directory. So create a file in that directory named “truncate_filters.py” or something. If you need any more information than that, take a look at the Django documentation on how to create a custom filter.
Here is what the filter looks like:
from django import template
register = template.Library()
@register.filter("truncate_chars")
def truncate_chars(value, max_length):
if len(value) <= max_length:
return value
truncd_val = value[:max_length]
if value[max_length] != " ":
rightmost_space = truncd_val.rfind(" ")
if rightmost_space != -1:
truncd_val = truncd_val[:rightmost_space]
return truncd_val + "..."
*update* code was changed per chris and paul’s suggestions below, I haven’t tested but assume they work
Here’s how it works visually on this string: “This is a sample string”
This is what happens when the filter is supplied with the argument 20:
- Cut down the string to 20 chars if it is greater than 20 chars in length. The string now becomes: “This is a sample str”
- Find the right-most space, indicating the start of the last word in the string, and truncate again: The string is now: “This is a sample”
- Add “…” and return. “This is a sample…”
You can invoke the filter from within your template like so:
{% load truncate_filters %}
<ol>
{% for some_string in a_list_of_strings %}
<li>{{some_string|truncate_chars:50}}</li>
{% endfor %}
</ol>
Hope someone out there finds this helpful
4:40 pm, August 10, 2008SmileyChris /
So kinda like http://code.djangoproject.com/ticket/5025 ?
Interesting idea about cutting it back to the last word, but if you’re that worried about truncating characters, I’d suggest it just inserts the ellipsis after truncating to length-3.
3:25 pm, February 2, 2009Chris /
Minor error at a boundary condition. You can fix it by changing line 8 to:
if not len(value) == max_length+1 and value[max_length+1] != ” “:
3:36 pm, May 1, 2009Graham King /
Exactly what I needed. Thank you.
1:25 pm, May 19, 2009Daniel Karlsson /
my django expert informed me that strings are lists and therefore we can use {{ some_list|slice:”:20″ }} or more specific {{ some_string | slice:”:20″}}
5:53 pm, October 6, 2009Chris Robinson /
Worked beautifully thanks! No idea why truncating by characters isn’t part of the default set of django filters
2:08 pm, February 9, 2010Neum /
Excellent! That was exactly what I was looking for. I agree, this should be in Django proper. Until then, you saved me 10 min.
Thanks!
10:53 am, February 25, 2010Paul /
Thanks that’s useful but I found two bugs with this:
- it throws an error if there’s no space in the string before max_length
- the get the character right after truncation, you should use value[max_length], not value[max_length + 1] which also solves the issue chris reported.
Heres the fixed version:
from django import template
register = template.Library()
@register.filter(“truncate_chars”)
def truncate_chars(value, max_length):
if len(value) <= max_length:
return value
truncd_val = value[:max_length]
if value[max_length] != ” “:
rightmost_space = truncd_val.rfind(” “)
if rightmost_space != -1:
truncd_val = truncd_val[:rightmost_space]
return truncd_val + “…”
10:56 am, February 25, 2010Paul /
the indentation was lost in the paste. here it is:
http://gist.github.com/314819