Fixed #17135 -- Made it possible to use decorators (like stringfilter) on template filter functions in combination with auto-escaping. Refs #16726.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17056 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Aymeric Augustin 2011-10-30 07:32:21 +00:00
parent bebbc9e4a5
commit d17bc72880
8 changed files with 154 additions and 178 deletions

View File

@ -11,7 +11,7 @@ from django.utils.tzinfo import LocalTimezone
register = template.Library()
@register.filter
@register.filter(is_safe=True)
def ordinal(value):
"""
Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd',
@ -25,9 +25,8 @@ def ordinal(value):
if value % 100 in (11, 12, 13): # special case
return u"%d%s" % (value, suffixes[0])
return u"%d%s" % (value, suffixes[value % 10])
ordinal.is_safe = True
@register.filter
@register.filter(is_safe=True)
def intcomma(value, use_l10n=True):
"""
Converts an integer to a string containing commas every three digits.
@ -47,7 +46,6 @@ def intcomma(value, use_l10n=True):
return new
else:
return intcomma(new, use_l10n)
intcomma.is_safe = True
# A tuple of standard large number to their converters
intword_converters = (
@ -97,7 +95,7 @@ intword_converters = (
)),
)
@register.filter
@register.filter(is_safe=False)
def intword(value):
"""
Converts a large integer to a friendly text representation. Works best
@ -129,9 +127,8 @@ def intword(value):
new_value = value / float(large_number)
return _check_for_i18n(new_value, *converters(new_value))
return value
intword.is_safe = False
@register.filter
@register.filter(is_safe=True)
def apnumber(value):
"""
For numbers 1-9, returns the number spelled out. Otherwise, returns the
@ -144,7 +141,6 @@ def apnumber(value):
if not 0 < value < 10:
return value
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
apnumber.is_safe = True
@register.filter
def naturalday(value, arg=None):

View File

@ -18,7 +18,7 @@ from django.utils.safestring import mark_safe
register = template.Library()
@register.filter
@register.filter(is_safe=True)
def textile(value):
try:
import textile
@ -28,9 +28,8 @@ def textile(value):
return force_unicode(value)
else:
return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8')))
textile.is_safe = True
@register.filter
@register.filter(is_safe=True)
def markdown(value, arg=''):
"""
Runs Markdown over a given value, optionally using various
@ -73,9 +72,8 @@ def markdown(value, arg=''):
return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode))
else:
return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
markdown.is_safe = True
@register.filter
@register.filter(is_safe=True)
def restructuredtext(value):
try:
from docutils.core import publish_parts
@ -87,5 +85,3 @@ def restructuredtext(value):
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings)
return mark_safe(force_unicode(parts["fragment"]))
restructuredtext.is_safe = True

View File

@ -1057,30 +1057,43 @@ class Library(object):
self.tags[getattr(func, "_decorated_function", func).__name__] = func
return func
def filter(self, name=None, filter_func=None):
def filter(self, name=None, filter_func=None, **flags):
if name is None and filter_func is None:
# @register.filter()
return self.filter_function
elif filter_func is None:
def dec(func):
return self.filter_function(func, **flags)
return dec
elif name is not None and filter_func is None:
if callable(name):
# @register.filter
return self.filter_function(name)
return self.filter_function(name, **flags)
else:
# @register.filter('somename') or @register.filter(name='somename')
def dec(func):
return self.filter(name, func)
return self.filter(name, func, **flags)
return dec
elif name is not None and filter_func is not None:
# register.filter('somename', somefunc)
self.filters[name] = filter_func
for attr in ('is_safe', 'needs_autoescape'):
if attr in flags:
value = flags[attr]
# set the flag on the filter for FilterExpression.resolve
setattr(filter_func, attr, value)
# set the flag on the innermost decorated function
# for decorators that need it e.g. stringfilter
if hasattr(filter_func, "_decorated_function"):
setattr(filter_func._decorated_function, attr, value)
return filter_func
else:
raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.filter: (%r, %r)", (name, filter_func))
def filter_function(self, func):
self.filters[getattr(func, "_decorated_function", func).__name__] = func
return func
def filter_function(self, func, **flags):
name = getattr(func, "_decorated_function", func).__name__
return self.filter(name, func, **flags)
def simple_tag(self, func=None, takes_context=None, name=None):
def dec(func):

View File

@ -37,23 +37,33 @@ def stringfilter(func):
if args:
args = list(args)
args[0] = force_unicode(args[0])
if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
if (isinstance(args[0], SafeData) and
getattr(_dec._decorated_function, 'is_safe', False)):
return mark_safe(func(*args, **kwargs))
return func(*args, **kwargs)
# Include a reference to the real function (used to check original
# arguments by the template parser).
# arguments by the template parser, and to bear the 'is_safe' attribute
# when multiple decorators are applied).
_dec._decorated_function = getattr(func, '_decorated_function', func)
for attr in ('is_safe', 'needs_autoescape'):
if hasattr(func, attr):
import warnings
warnings.warn("Setting the %s attribute of a template filter "
"function is deprecated; use @register.filter(%s=%s) "
"instead" % (attr, attr, getattr(func, attr)),
PendingDeprecationWarning)
setattr(_dec, attr, getattr(func, attr))
return wraps(func)(_dec)
###################
# STRINGS #
###################
@register.filter
@register.filter(is_safe=True)
@stringfilter
def addslashes(value):
"""
@ -62,14 +72,12 @@ def addslashes(value):
filter instead.
"""
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
addslashes.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def capfirst(value):
"""Capitalizes the first character of the value."""
return value and value[0].upper() + value[1:]
capfirst.is_safe = True
@register.filter("escapejs")
@stringfilter
@ -77,12 +85,11 @@ def escapejs_filter(value):
"""Hex encodes characters for use in JavaScript strings."""
return escapejs(value)
@register.filter("fix_ampersands")
@register.filter("fix_ampersands", is_safe=True)
@stringfilter
def fix_ampersands_filter(value):
"""Replaces ampersands with ``&amp;`` entities."""
return fix_ampersands(value)
fix_ampersands_filter.is_safe = True
# Values for testing floatformat input against infinity and NaN representations,
# which differ across platforms and Python versions. Some (i.e. old Windows
@ -96,7 +103,7 @@ neg_inf = -1e200 * 1e200
nan = (1e200 * 1e200) // (1e200 * 1e200)
special_floats = [str(pos_inf), str(neg_inf), str(nan)]
@register.filter
@register.filter(is_safe=True)
def floatformat(text, arg=-1):
"""
Displays a float to a specified number of decimal places.
@ -172,16 +179,14 @@ def floatformat(text, arg=-1):
return mark_safe(formats.number_format(number, abs(p)))
except InvalidOperation:
return input_val
floatformat.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def iriencode(value):
"""Escapes an IRI value for use in a URL."""
return force_unicode(iri_to_uri(value))
iriencode.is_safe = True
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def linenumbers(value, autoescape=None):
"""Displays text with line numbers."""
@ -196,17 +201,14 @@ def linenumbers(value, autoescape=None):
for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
return mark_safe(u'\n'.join(lines))
linenumbers.is_safe = True
linenumbers.needs_autoescape = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def lower(value):
"""Converts a string into all lowercase."""
return value.lower()
lower.is_safe = True
@register.filter
@register.filter(is_safe=False)
@stringfilter
def make_list(value):
"""
@ -216,9 +218,8 @@ def make_list(value):
For a string, it's a list of characters.
"""
return list(value)
make_list.is_safe = False
@register.filter
@register.filter(is_safe=True)
@stringfilter
def slugify(value):
"""
@ -228,9 +229,8 @@ def slugify(value):
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return mark_safe(re.sub('[-\s]+', '-', value))
slugify.is_safe = True
@register.filter
@register.filter(is_safe=True)
def stringformat(value, arg):
"""
Formats the variable according to the arg, a string formatting specifier.
@ -245,17 +245,15 @@ def stringformat(value, arg):
return (u"%" + unicode(arg)) % value
except (ValueError, TypeError):
return u""
stringformat.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def title(value):
"""Converts a string into titlecase."""
t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
title.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def truncatechars(value, arg):
"""
@ -268,9 +266,8 @@ def truncatechars(value, arg):
except ValueError: # Invalid literal for int().
return value # Fail silently.
return Truncator(value).chars(length)
truncatechars.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def truncatewords(value, arg):
"""
@ -285,9 +282,8 @@ def truncatewords(value, arg):
except ValueError: # Invalid literal for int().
return value # Fail silently.
return Truncator(value).words(length, truncate=' ...')
truncatewords.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def truncatewords_html(value, arg):
"""
@ -302,16 +298,14 @@ def truncatewords_html(value, arg):
except ValueError: # invalid literal for int()
return value # Fail silently.
return Truncator(value).words(length, html=True, truncate=' ...')
truncatewords_html.is_safe = True
@register.filter
@register.filter(is_safe=False)
@stringfilter
def upper(value):
"""Converts a string into all uppercase."""
return value.upper()
upper.is_safe = False
@register.filter
@register.filter(is_safe=False)
@stringfilter
def urlencode(value, safe=None):
"""
@ -326,17 +320,14 @@ def urlencode(value, safe=None):
if safe is not None:
kwargs['safe'] = safe
return urlquote(value, **kwargs)
urlencode.is_safe = False
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def urlize(value, autoescape=None):
"""Converts URLs in plain text into clickable links."""
return mark_safe(urlize_impl(value, nofollow=True, autoescape=autoescape))
urlize.is_safe = True
urlize.needs_autoescape = True
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def urlizetrunc(value, limit, autoescape=None):
"""
@ -347,17 +338,14 @@ def urlizetrunc(value, limit, autoescape=None):
"""
return mark_safe(urlize_impl(value, trim_url_limit=int(limit), nofollow=True,
autoescape=autoescape))
urlizetrunc.is_safe = True
urlizetrunc.needs_autoescape = True
@register.filter
@register.filter(is_safe=False)
@stringfilter
def wordcount(value):
"""Returns the number of words."""
return len(value.split())
wordcount.is_safe = False
@register.filter
@register.filter(is_safe=True)
@stringfilter
def wordwrap(value, arg):
"""
@ -366,9 +354,8 @@ def wordwrap(value, arg):
Argument: number of characters to wrap the text at.
"""
return wrap(value, int(arg))
wordwrap.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def ljust(value, arg):
"""
@ -377,9 +364,8 @@ def ljust(value, arg):
Argument: field size.
"""
return value.ljust(int(arg))
ljust.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def rjust(value, arg):
"""
@ -388,14 +374,12 @@ def rjust(value, arg):
Argument: field size.
"""
return value.rjust(int(arg))
rjust.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def center(value, arg):
"""Centers the value in a field of a given width."""
return value.center(int(arg))
center.is_safe = True
@register.filter
@stringfilter
@ -413,16 +397,15 @@ def cut(value, arg):
# HTML STRINGS #
###################
@register.filter("escape")
@register.filter("escape", is_safe=True)
@stringfilter
def escape_filter(value):
"""
Marks the value as a string that should not be auto-escaped.
"""
return mark_for_escaping(value)
escape_filter.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def force_escape(value):
"""
@ -431,9 +414,8 @@ def force_escape(value):
possible escaping).
"""
return mark_safe(escape(value))
force_escape.is_safe = True
@register.filter("linebreaks")
@register.filter("linebreaks", is_safe=True, needs_autoescape=True)
@stringfilter
def linebreaks_filter(value, autoescape=None):
"""
@ -443,10 +425,8 @@ def linebreaks_filter(value, autoescape=None):
"""
autoescape = autoescape and not isinstance(value, SafeData)
return mark_safe(linebreaks(value, autoescape))
linebreaks_filter.is_safe = True
linebreaks_filter.needs_autoescape = True
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def linebreaksbr(value, autoescape=None):
"""
@ -458,19 +438,16 @@ def linebreaksbr(value, autoescape=None):
if autoescape:
value = escape(value)
return mark_safe(value.replace('\n', '<br />'))
linebreaksbr.is_safe = True
linebreaksbr.needs_autoescape = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def safe(value):
"""
Marks the value as a string that should not be auto-escaped.
"""
return mark_safe(value)
safe.is_safe = True
@register.filter
@register.filter(is_safe=True)
def safeseq(value):
"""
A "safe" filter for sequences. Marks each element in the sequence,
@ -478,9 +455,8 @@ def safeseq(value):
with the results.
"""
return [mark_safe(force_unicode(obj)) for obj in value]
safeseq.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def removetags(value, tags):
"""Removes a space separated list of [X]HTML tags from the output."""
@ -491,47 +467,42 @@ def removetags(value, tags):
value = starttag_re.sub(u'', value)
value = endtag_re.sub(u'', value)
return value
removetags.is_safe = True
@register.filter
@register.filter(is_safe=True)
@stringfilter
def striptags(value):
"""Strips all [X]HTML tags."""
return strip_tags(value)
striptags.is_safe = True
###################
# LISTS #
###################
@register.filter
@register.filter(is_safe=False)
def dictsort(value, arg):
"""
Takes a list of dicts, returns that list sorted by the property given in
the argument.
"""
return sorted(value, key=Variable(arg).resolve)
dictsort.is_safe = False
@register.filter
@register.filter(is_safe=False)
def dictsortreversed(value, arg):
"""
Takes a list of dicts, returns that list sorted in reverse order by the
property given in the argument.
"""
return sorted(value, key=Variable(arg).resolve, reverse=True)
dictsortreversed.is_safe = False
@register.filter
@register.filter(is_safe=False)
def first(value):
"""Returns the first item in a list."""
try:
return value[0]
except IndexError:
return u''
first.is_safe = False
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
def join(value, arg, autoescape=None):
"""
Joins a list with a string, like Python's ``str.join(list)``.
@ -544,43 +515,37 @@ def join(value, arg, autoescape=None):
except AttributeError: # fail silently but nicely
return value
return mark_safe(data)
join.is_safe = True
join.needs_autoescape = True
@register.filter
@register.filter(is_safe=True)
def last(value):
"Returns the last item in a list"
try:
return value[-1]
except IndexError:
return u''
last.is_safe = True
@register.filter
@register.filter(is_safe=True)
def length(value):
"""Returns the length of the value - useful for lists."""
try:
return len(value)
except (ValueError, TypeError):
return ''
length.is_safe = True
@register.filter
@register.filter(is_safe=False)
def length_is(value, arg):
"""Returns a boolean of whether the value's length is the argument."""
try:
return len(value) == int(arg)
except (ValueError, TypeError):
return ''
length_is.is_safe = False
@register.filter
@register.filter(is_safe=True)
def random(value):
"""Returns a random item from the list."""
return random_module.choice(value)
random.is_safe = True
@register.filter("slice")
@register.filter("slice", is_safe=True)
def slice_filter(value, arg):
"""
Returns a slice of the list.
@ -600,9 +565,8 @@ def slice_filter(value, arg):
except (ValueError, TypeError):
return value # Fail silently.
slice_filter.is_safe = True
@register.filter
@register.filter(is_safe=True, needs_autoescape=True)
def unordered_list(value, autoescape=None):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
@ -688,14 +652,12 @@ def unordered_list(value, autoescape=None):
return '\n'.join(output)
value, converted = convert_old_style_list(value)
return mark_safe(_helper(value))
unordered_list.is_safe = True
unordered_list.needs_autoescape = True
###################
# INTEGERS #
###################
@register.filter
@register.filter(is_safe=False)
def add(value, arg):
"""Adds the arg to the value."""
try:
@ -705,9 +667,8 @@ def add(value, arg):
return value + arg
except Exception:
return ''
add.is_safe = False
@register.filter
@register.filter(is_safe=False)
def get_digit(value, arg):
"""
Given a whole number, returns the requested digit of it, where 1 is the
@ -726,13 +687,12 @@ def get_digit(value, arg):
return int(str(value)[-arg])
except IndexError:
return 0
get_digit.is_safe = False
###################
# DATES #
###################
@register.filter
@register.filter(is_safe=False)
def date(value, arg=None):
"""Formats a date according to the given format."""
if not value:
@ -746,9 +706,8 @@ def date(value, arg=None):
return format(value, arg)
except AttributeError:
return ''
date.is_safe = False
@register.filter
@register.filter(is_safe=False)
def time(value, arg=None):
"""Formats a time according to the given format."""
if value in (None, u''):
@ -762,9 +721,8 @@ def time(value, arg=None):
return time_format(value, arg)
except AttributeError:
return ''
time.is_safe = False
@register.filter("timesince")
@register.filter("timesince", is_safe=False)
def timesince_filter(value, arg=None):
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
if not value:
@ -775,9 +733,8 @@ def timesince_filter(value, arg=None):
return timesince(value)
except (ValueError, TypeError):
return u''
timesince_filter.is_safe = False
@register.filter("timeuntil")
@register.filter("timeuntil", is_safe=False)
def timeuntil_filter(value, arg=None):
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
if not value:
@ -786,33 +743,29 @@ def timeuntil_filter(value, arg=None):
return timeuntil(value, arg)
except (ValueError, TypeError):
return u''
timeuntil_filter.is_safe = False
###################
# LOGIC #
###################
@register.filter
@register.filter(is_safe=False)
def default(value, arg):
"""If value is unavailable, use given default."""
return value or arg
default.is_safe = False
@register.filter
@register.filter(is_safe=False)
def default_if_none(value, arg):
"""If value is None, use given default."""
if value is None:
return arg
return value
default_if_none.is_safe = False
@register.filter
@register.filter(is_safe=False)
def divisibleby(value, arg):
"""Returns True if the value is devisible by the argument."""
return int(value) % int(arg) == 0
divisibleby.is_safe = False
@register.filter
@register.filter(is_safe=False)
def yesno(value, arg=None):
"""
Given a string mapping values for true, false and (optionally) None,
@ -843,13 +796,12 @@ def yesno(value, arg=None):
if value:
return yes
return no
yesno.is_safe = False
###################
# MISC #
###################
@register.filter
@register.filter(is_safe=True)
def filesizeformat(bytes):
"""
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
@ -873,9 +825,8 @@ def filesizeformat(bytes):
if bytes < 1024 * 1024 * 1024 * 1024 * 1024:
return ugettext("%s TB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024))
return ugettext("%s PB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024 * 1024))
filesizeformat.is_safe = True
@register.filter
@register.filter(is_safe=False)
def pluralize(value, arg=u's'):
"""
Returns a plural suffix if the value is not 1. By default, 's' is used as
@ -918,19 +869,16 @@ def pluralize(value, arg=u's'):
except TypeError: # len() of unsized object.
pass
return singular_suffix
pluralize.is_safe = False
@register.filter("phone2numeric")
@register.filter("phone2numeric", is_safe=True)
def phone2numeric_filter(value):
"""Takes a phone number and converts it in to its numerical equivalent."""
return phone2numeric(value)
phone2numeric_filter.is_safe = True
@register.filter
@register.filter(is_safe=True)
def pprint(value):
"""A wrapper around pprint.pprint -- for debugging, really."""
try:
return pformat(value)
except Exception, e:
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
pprint.is_safe = True

View File

@ -5,23 +5,21 @@ from django.utils.encoding import force_unicode
register = Library()
@register.filter
@register.filter(is_safe=False)
def localize(value):
"""
Forces a value to be rendered as a localized value,
regardless of the value of ``settings.USE_L10N``.
"""
return force_unicode(formats.localize(value, use_l10n=True))
localize.is_safe = False
@register.filter
@register.filter(is_safe=False)
def unlocalize(value):
"""
Forces a value to be rendered as a non-localized value,
regardless of the value of ``settings.USE_L10N``.
"""
return force_unicode(value)
unlocalize.is_safe = False
class LocalizeNode(Node):
def __init__(self, nodelist, use_l10n):

View File

@ -143,6 +143,10 @@ You can use ``register.filter()`` as a decorator instead:
If you leave off the ``name`` argument, as in the second example above, Django
will use the function's name as the filter name.
Finally, ``register.filter()`` also accepts two keyword arguments, ``is_safe``
and ``needs_autoescape``, described in :ref:`filters and auto-escaping
<filters-auto-escaping>` below.
Template filters that expect strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -166,6 +170,8 @@ This way, you'll be able to pass, say, an integer to this filter, and it
won't cause an ``AttributeError`` (because integers don't have ``lower()``
methods).
.. _filters-auto-escaping:
Filters and auto-escaping
~~~~~~~~~~~~~~~~~~~~~~~~~
@ -206,17 +212,16 @@ Template filter code falls into one of two situations:
1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,
``'``, ``"`` or ``&``) into the result that were not already present. In
this case, you can let Django take care of all the auto-escaping
handling for you. All you need to do is put the ``is_safe`` attribute on
your filter function and set it to ``True``, like so:
handling for you. All you need to do is set the ``is_safe`` flag to ``True``
when you register your filter function, like so:
.. code-block:: python
@register.filter
@register.filter(is_safe=True)
def myfilter(value):
return value
myfilter.is_safe = True
This attribute tells Django that if a "safe" string is passed into your
This flag tells Django that if a "safe" string is passed into your
filter, the result will still be "safe" and if a non-safe string is
passed in, Django will automatically escape it, if necessary.
@ -236,17 +241,16 @@ Template filter code falls into one of two situations:
.. code-block:: python
@register.filter
@register.filter(is_safe=True)
def add_xx(value):
return '%sxx' % value
add_xx.is_safe = True
When this filter is used in a template where auto-escaping is enabled,
Django will escape the output whenever the input is not already marked
as "safe".
By default, ``is_safe`` defaults to ``False``, and you can omit it from
any filters where it isn't required.
By default, ``is_safe`` is ``False``, and you can omit it from any filters
where it isn't required.
Be careful when deciding if your filter really does leave safe strings
as safe. If you're *removing* characters, you might inadvertently leave
@ -279,12 +283,12 @@ Template filter code falls into one of two situations:
can operate in templates where auto-escaping is either on or off in
order to make things easier for your template authors.
In order for your filter to know the current auto-escaping state, set
the ``needs_autoescape`` attribute to ``True`` on your function. (If you
don't specify this attribute, it defaults to ``False``). This attribute
tells Django that your filter function wants to be passed an extra
keyword argument, called ``autoescape``, that is ``True`` if
auto-escaping is in effect and ``False`` otherwise.
In order for your filter to know the current auto-escaping state, set the
``needs_autoescape`` flag to ``True`` when you register your filter function.
(If you don't specify this flag, it defaults to ``False``). This flag tells
Django that your filter function wants to be passed an extra keyword
argument, called ``autoescape``, that is ``True`` if auto-escaping is in
effect and ``False`` otherwise.
For example, let's write a filter that emphasizes the first character of
a string:
@ -294,6 +298,7 @@ Template filter code falls into one of two situations:
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
@register.filter(needs_autoescape=True)
def initial_letter_filter(text, autoescape=None):
first, other = text[0], text[1:]
if autoescape:
@ -302,27 +307,45 @@ Template filter code falls into one of two situations:
esc = lambda x: x
result = '<strong>%s</strong>%s' % (esc(first), esc(other))
return mark_safe(result)
initial_letter_filter.needs_autoescape = True
The ``needs_autoescape`` attribute on the filter function and the
``autoescape`` keyword argument mean that our function will know whether
automatic escaping is in effect when the filter is called. We use
``autoescape`` to decide whether the input data needs to be passed
through ``django.utils.html.conditional_escape`` or not. (In the latter
case, we just use the identity function as the "escape" function.) The
``conditional_escape()`` function is like ``escape()`` except it only
escapes input that is **not** a ``SafeData`` instance. If a ``SafeData``
instance is passed to ``conditional_escape()``, the data is returned
unchanged.
The ``needs_autoescape`` flag and the ``autoescape`` keyword argument mean
that our function will know whether automatic escaping is in effect when the
filter is called. We use ``autoescape`` to decide whether the input data
needs to be passed through ``django.utils.html.conditional_escape`` or not.
(In the latter case, we just use the identity function as the "escape"
function.) The ``conditional_escape()`` function is like ``escape()`` except
it only escapes input that is **not** a ``SafeData`` instance. If a
``SafeData`` instance is passed to ``conditional_escape()``, the data is
returned unchanged.
Finally, in the above example, we remember to mark the result as safe
so that our HTML is inserted directly into the template without further
escaping.
There's no need to worry about the ``is_safe`` attribute in this case
There's no need to worry about the ``is_safe`` flag in this case
(although including it wouldn't hurt anything). Whenever you manually
handle the auto-escaping issues and return a safe string, the
``is_safe`` attribute won't change anything either way.
``is_safe`` flag won't change anything either way.
.. versionchanged:: 1.4
``is_safe`` and ``needs_autoescape`` used to be attributes of the filter
function; this syntax is deprecated.
.. code-block:: python
@register.filter
def myfilter(value):
return value
myfilter.is_safe = True
.. code-block:: python
@register.filter
def initial_letter_filter(text, autoescape=None):
# ...
return mark_safe(result)
initial_letter_filter.needs_autoescape = True
Writing custom template tags
----------------------------

View File

@ -251,6 +251,9 @@ these changes.
:mod:`django.core.management`. This also means that the old (pre-1.4)
style of :file:`manage.py` file will no longer work.
* Setting the ``is_safe`` and ``needs_autoescape`` flags as attributes of
template filter functions will no longer be supported.
2.0
---

View File

@ -6,11 +6,10 @@ from django.template.loader import get_template
register = template.Library()
@register.filter
@stringfilter
def trim(value, num):
return value[:num]
trim = stringfilter(trim)
register.filter(trim)
@register.simple_tag
def no_params():
@ -303,4 +302,4 @@ assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_
def assignment_tag_without_context_parameter(arg):
"""Expected assignment_tag_without_context_parameter __doc__"""
return "Expected result"
assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"
assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"