Fixed #343: filters that take strings now handle non-strings correctly. Thanks to Boffbowsh for the original patch, and to SmileyChris for the updated patch and tests.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4558 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
1266766da4
commit
36512d5d73
|
@ -580,6 +580,8 @@ class FilterExpression(object):
|
||||||
def args_check(name, func, provided):
|
def args_check(name, func, provided):
|
||||||
provided = list(provided)
|
provided = list(provided)
|
||||||
plen = len(provided)
|
plen = len(provided)
|
||||||
|
# Check to see if a decorator is providing the real function.
|
||||||
|
func = getattr(func, '_decorated_function', func)
|
||||||
args, varargs, varkw, defaults = getargspec(func)
|
args, varargs, varkw, defaults = getargspec(func)
|
||||||
# First argument is filter input.
|
# First argument is filter input.
|
||||||
args.pop(0)
|
args.pop(0)
|
||||||
|
|
|
@ -8,6 +8,42 @@ import random as random_module
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
#######################
|
||||||
|
# STRING DECORATOR #
|
||||||
|
#######################
|
||||||
|
|
||||||
|
def smart_string(obj):
|
||||||
|
# FUTURE: Unicode strings should probably be normalized to a specific
|
||||||
|
# encoding and non-unicode strings should be converted to unicode too.
|
||||||
|
# if isinstance(obj, unicode):
|
||||||
|
# obj = obj.encode(settings.DEFAULT_CHARSET)
|
||||||
|
# else:
|
||||||
|
# obj = unicode(obj, settings.DEFAULT_CHARSET)
|
||||||
|
# FUTURE: Replace dumb string logic below with cool unicode logic above.
|
||||||
|
if not isinstance(obj, basestring):
|
||||||
|
obj = str(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def stringfilter(func):
|
||||||
|
"""
|
||||||
|
Decorator for filters which should only receive strings. The object passed
|
||||||
|
as the first positional argument will be converted to a string.
|
||||||
|
"""
|
||||||
|
def _dec(*args, **kwargs):
|
||||||
|
if args:
|
||||||
|
args = list(args)
|
||||||
|
args[0] = smart_string(args[0])
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
# Make sure the internal name is the original function name because this
|
||||||
|
# is the internal name of the filter if passed directly to Library().filter
|
||||||
|
_dec.__name__ = func.__name__
|
||||||
|
|
||||||
|
# Include a reference to the real function (used to check original
|
||||||
|
# arguments by the template parser).
|
||||||
|
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
||||||
|
return _dec
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# STRINGS #
|
# STRINGS #
|
||||||
###################
|
###################
|
||||||
|
@ -16,16 +52,18 @@ register = Library()
|
||||||
def addslashes(value):
|
def addslashes(value):
|
||||||
"Adds slashes - useful for passing strings to JavaScript, for example."
|
"Adds slashes - useful for passing strings to JavaScript, for example."
|
||||||
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
||||||
|
addslashes = stringfilter(addslashes)
|
||||||
|
|
||||||
def capfirst(value):
|
def capfirst(value):
|
||||||
"Capitalizes the first character of the value"
|
"Capitalizes the first character of the value"
|
||||||
value = str(value)
|
|
||||||
return value and value[0].upper() + value[1:]
|
return value and value[0].upper() + value[1:]
|
||||||
|
capfirst = stringfilter(capfirst)
|
||||||
|
|
||||||
def fix_ampersands(value):
|
def fix_ampersands(value):
|
||||||
"Replaces ampersands with ``&`` entities"
|
"Replaces ampersands with ``&`` entities"
|
||||||
from django.utils.html import fix_ampersands
|
from django.utils.html import fix_ampersands
|
||||||
return fix_ampersands(value)
|
return fix_ampersands(value)
|
||||||
|
fix_ampersands = stringfilter(fix_ampersands)
|
||||||
|
|
||||||
def floatformat(text, arg=-1):
|
def floatformat(text, arg=-1):
|
||||||
"""
|
"""
|
||||||
|
@ -52,7 +90,7 @@ def floatformat(text, arg=-1):
|
||||||
try:
|
try:
|
||||||
d = int(arg)
|
d = int(arg)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return str(f)
|
return smart_string(f)
|
||||||
m = f - int(f)
|
m = f - int(f)
|
||||||
if not m and d < 0:
|
if not m and d < 0:
|
||||||
return '%d' % int(f)
|
return '%d' % int(f)
|
||||||
|
@ -69,22 +107,26 @@ def linenumbers(value):
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
|
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
linenumbers = stringfilter(linenumbers)
|
||||||
|
|
||||||
def lower(value):
|
def lower(value):
|
||||||
"Converts a string into all lowercase"
|
"Converts a string into all lowercase"
|
||||||
return value.lower()
|
return value.lower()
|
||||||
|
lower = stringfilter(lower)
|
||||||
|
|
||||||
def make_list(value):
|
def make_list(value):
|
||||||
"""
|
"""
|
||||||
Returns the value turned into a list. For an integer, it's a list of
|
Returns the value turned into a list. For an integer, it's a list of
|
||||||
digits. For a string, it's a list of characters.
|
digits. For a string, it's a list of characters.
|
||||||
"""
|
"""
|
||||||
return list(str(value))
|
return list(value)
|
||||||
|
make_list = stringfilter(make_list)
|
||||||
|
|
||||||
def slugify(value):
|
def slugify(value):
|
||||||
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
|
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
|
||||||
value = re.sub('[^\w\s-]', '', value).strip().lower()
|
value = re.sub('[^\w\s-]', '', value).strip().lower()
|
||||||
return re.sub('[-\s]+', '-', value)
|
return re.sub('[-\s]+', '-', value)
|
||||||
|
slugify = stringfilter(slugify)
|
||||||
|
|
||||||
def stringformat(value, arg):
|
def stringformat(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -96,13 +138,14 @@ def stringformat(value, arg):
|
||||||
of Python string formatting
|
of Python string formatting
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return ("%" + arg) % value
|
return ("%" + str(arg)) % value
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def title(value):
|
def title(value):
|
||||||
"Converts a string into titlecase"
|
"Converts a string into titlecase"
|
||||||
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
||||||
|
title = stringfilter(title)
|
||||||
|
|
||||||
def truncatewords(value, arg):
|
def truncatewords(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -118,6 +161,7 @@ def truncatewords(value, arg):
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
return truncate_words(value, length)
|
return truncate_words(value, length)
|
||||||
|
truncatewords = stringfilter(truncatewords)
|
||||||
|
|
||||||
def truncatewords_html(value, arg):
|
def truncatewords_html(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -133,10 +177,12 @@ def truncatewords_html(value, arg):
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
return truncate_html_words(value, length)
|
return truncate_html_words(value, length)
|
||||||
|
truncatewords_html = stringfilter(truncatewords_html)
|
||||||
|
|
||||||
def upper(value):
|
def upper(value):
|
||||||
"Converts a string into all uppercase"
|
"Converts a string into all uppercase"
|
||||||
return value.upper()
|
return value.upper()
|
||||||
|
upper = stringfilter(upper)
|
||||||
|
|
||||||
def urlencode(value):
|
def urlencode(value):
|
||||||
"Escapes a value for use in a URL"
|
"Escapes a value for use in a URL"
|
||||||
|
@ -144,11 +190,13 @@ def urlencode(value):
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
return urllib.quote(value)
|
return urllib.quote(value)
|
||||||
|
urlencode = stringfilter(urlencode)
|
||||||
|
|
||||||
def urlize(value):
|
def urlize(value):
|
||||||
"Converts URLs in plain text into clickable links"
|
"Converts URLs in plain text into clickable links"
|
||||||
from django.utils.html import urlize
|
from django.utils.html import urlize
|
||||||
return urlize(value, nofollow=True)
|
return urlize(value, nofollow=True)
|
||||||
|
urlize = stringfilter(urlize)
|
||||||
|
|
||||||
def urlizetrunc(value, limit):
|
def urlizetrunc(value, limit):
|
||||||
"""
|
"""
|
||||||
|
@ -159,10 +207,12 @@ def urlizetrunc(value, limit):
|
||||||
"""
|
"""
|
||||||
from django.utils.html import urlize
|
from django.utils.html import urlize
|
||||||
return urlize(value, trim_url_limit=int(limit), nofollow=True)
|
return urlize(value, trim_url_limit=int(limit), nofollow=True)
|
||||||
|
urlizetrunc = stringfilter(urlizetrunc)
|
||||||
|
|
||||||
def wordcount(value):
|
def wordcount(value):
|
||||||
"Returns the number of words"
|
"Returns the number of words"
|
||||||
return len(value.split())
|
return len(value.split())
|
||||||
|
wordcount = stringfilter(wordcount)
|
||||||
|
|
||||||
def wordwrap(value, arg):
|
def wordwrap(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -171,7 +221,8 @@ def wordwrap(value, arg):
|
||||||
Argument: number of characters to wrap the text at.
|
Argument: number of characters to wrap the text at.
|
||||||
"""
|
"""
|
||||||
from django.utils.text import wrap
|
from django.utils.text import wrap
|
||||||
return wrap(str(value), int(arg))
|
return wrap(value, int(arg))
|
||||||
|
wordwrap = stringfilter(wordwrap)
|
||||||
|
|
||||||
def ljust(value, arg):
|
def ljust(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -179,7 +230,8 @@ def ljust(value, arg):
|
||||||
|
|
||||||
Argument: field size
|
Argument: field size
|
||||||
"""
|
"""
|
||||||
return str(value).ljust(int(arg))
|
return value.ljust(int(arg))
|
||||||
|
ljust = stringfilter(ljust)
|
||||||
|
|
||||||
def rjust(value, arg):
|
def rjust(value, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -187,15 +239,18 @@ def rjust(value, arg):
|
||||||
|
|
||||||
Argument: field size
|
Argument: field size
|
||||||
"""
|
"""
|
||||||
return str(value).rjust(int(arg))
|
return value.rjust(int(arg))
|
||||||
|
rjust = stringfilter(rjust)
|
||||||
|
|
||||||
def center(value, arg):
|
def center(value, arg):
|
||||||
"Centers the value in a field of a given width"
|
"Centers the value in a field of a given width"
|
||||||
return str(value).center(int(arg))
|
return value.center(int(arg))
|
||||||
|
center = stringfilter(center)
|
||||||
|
|
||||||
def cut(value, arg):
|
def cut(value, arg):
|
||||||
"Removes all values of arg from the given string"
|
"Removes all values of arg from the given string"
|
||||||
return value.replace(arg, '')
|
return value.replace(arg, '')
|
||||||
|
cut = stringfilter(cut)
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# HTML STRINGS #
|
# HTML STRINGS #
|
||||||
|
@ -205,15 +260,18 @@ def escape(value):
|
||||||
"Escapes a string's HTML"
|
"Escapes a string's HTML"
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
return escape(value)
|
return escape(value)
|
||||||
|
escape = stringfilter(escape)
|
||||||
|
|
||||||
def linebreaks(value):
|
def linebreaks(value):
|
||||||
"Converts newlines into <p> and <br />s"
|
"Converts newlines into <p> and <br />s"
|
||||||
from django.utils.html import linebreaks
|
from django.utils.html import linebreaks
|
||||||
return linebreaks(value)
|
return linebreaks(value)
|
||||||
|
linebreaks = stringfilter(linebreaks)
|
||||||
|
|
||||||
def linebreaksbr(value):
|
def linebreaksbr(value):
|
||||||
"Converts newlines into <br />s"
|
"Converts newlines into <br />s"
|
||||||
return value.replace('\n', '<br />')
|
return value.replace('\n', '<br />')
|
||||||
|
linebreaksbr = stringfilter(linebreaksbr)
|
||||||
|
|
||||||
def removetags(value, tags):
|
def removetags(value, tags):
|
||||||
"Removes a space separated list of [X]HTML tags from the output"
|
"Removes a space separated list of [X]HTML tags from the output"
|
||||||
|
@ -224,13 +282,13 @@ def removetags(value, tags):
|
||||||
value = starttag_re.sub('', value)
|
value = starttag_re.sub('', value)
|
||||||
value = endtag_re.sub('', value)
|
value = endtag_re.sub('', value)
|
||||||
return value
|
return value
|
||||||
|
removetags = stringfilter(removetags)
|
||||||
|
|
||||||
def striptags(value):
|
def striptags(value):
|
||||||
"Strips all [X]HTML tags"
|
"Strips all [X]HTML tags"
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
if not isinstance(value, basestring):
|
|
||||||
value = str(value)
|
|
||||||
return strip_tags(value)
|
return strip_tags(value)
|
||||||
|
striptags = stringfilter(striptags)
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# LISTS #
|
# LISTS #
|
||||||
|
@ -265,7 +323,7 @@ def first(value):
|
||||||
def join(value, arg):
|
def join(value, arg):
|
||||||
"Joins a list with a string, like Python's ``str.join(list)``"
|
"Joins a list with a string, like Python's ``str.join(list)``"
|
||||||
try:
|
try:
|
||||||
return arg.join(map(str, value))
|
return arg.join(map(smart_string, value))
|
||||||
except AttributeError: # fail silently but nicely
|
except AttributeError: # fail silently but nicely
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
@ -654,6 +654,16 @@ decorator instead::
|
||||||
If you leave off the ``name`` argument, as in the second example above, Django
|
If you leave off the ``name`` argument, as in the second example above, Django
|
||||||
will use the function's name as the filter name.
|
will use the function's name as the filter name.
|
||||||
|
|
||||||
|
Template filters which expect strings
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
If you are writing a template filter which only expects a string as the first
|
||||||
|
argument, you should use the included decorator ``to_str`` which will convert
|
||||||
|
an object to it's string value before being passed to your function::
|
||||||
|
|
||||||
|
def lower(value):
|
||||||
|
return value.lower()
|
||||||
|
lower = template.to_str(lower)
|
||||||
|
|
||||||
Writing custom template tags
|
Writing custom template tags
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
|
@ -388,7 +388,53 @@ False
|
||||||
>>> phone2numeric('0800 flowers')
|
>>> phone2numeric('0800 flowers')
|
||||||
'0800 3569377'
|
'0800 3569377'
|
||||||
|
|
||||||
|
# Filters shouldn't break if passed non-strings
|
||||||
|
>>> addslashes(123)
|
||||||
|
'123'
|
||||||
|
>>> linenumbers(123)
|
||||||
|
'1. 123'
|
||||||
|
>>> lower(123)
|
||||||
|
'123'
|
||||||
|
>>> make_list(123)
|
||||||
|
['1', '2', '3']
|
||||||
|
>>> slugify(123)
|
||||||
|
'123'
|
||||||
|
>>> title(123)
|
||||||
|
'123'
|
||||||
|
>>> truncatewords(123, 2)
|
||||||
|
'123'
|
||||||
|
>>> upper(123)
|
||||||
|
'123'
|
||||||
|
>>> urlencode(123)
|
||||||
|
'123'
|
||||||
|
>>> urlize(123)
|
||||||
|
'123'
|
||||||
|
>>> urlizetrunc(123, 1)
|
||||||
|
'123'
|
||||||
|
>>> wordcount(123)
|
||||||
|
1
|
||||||
|
>>> wordwrap(123, 2)
|
||||||
|
'123'
|
||||||
|
>>> ljust('123', 4)
|
||||||
|
'123 '
|
||||||
|
>>> rjust('123', 4)
|
||||||
|
' 123'
|
||||||
|
>>> center('123', 5)
|
||||||
|
' 123 '
|
||||||
|
>>> center('123', 6)
|
||||||
|
' 123 '
|
||||||
|
>>> cut(123, '2')
|
||||||
|
'13'
|
||||||
|
>>> escape(123)
|
||||||
|
'123'
|
||||||
|
>>> linebreaks(123)
|
||||||
|
'<p>123</p>'
|
||||||
|
>>> linebreaksbr(123)
|
||||||
|
'123'
|
||||||
|
>>> removetags(123, 'a')
|
||||||
|
'123'
|
||||||
|
>>> striptags(123)
|
||||||
|
'123'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue