Commit d17bc728 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #17135 -- Made it possible to use decorators (like stringfilter) on...

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
parent bebbc9e4
Loading
Loading
Loading
Loading
+4 −8
Original line number Diff line number Diff line
@@ -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):
+3 −7
Original line number Diff line number Diff line
@@ -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
+21 −8
Original line number Diff line number Diff line
@@ -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):
+67 −119
Original line number Diff line number Diff line
@@ -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
+2 −4
Original line number Diff line number Diff line
@@ -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):
Loading