mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Implemented auto-escaping of variable output in templates. Fully controllable by template authors and it's possible to write filters and templates that simulataneously work in both auto-escaped and non-auto-escaped environments if you need to. Fixed #2359
See documentation in templates.txt and templates_python.txt for how everything works. Backwards incompatible if you're inserting raw HTML output via template variables. Based on an original design from Simon Willison and with debugging help from Michael Radziej. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6671 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -57,6 +57,8 @@ from django.utils.functional import curry, Promise
|
||||
from django.utils.text import smart_split
|
||||
from django.utils.encoding import smart_unicode, force_unicode
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
|
||||
from django.utils.html import escape
|
||||
|
||||
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||
|
||||
@@ -595,7 +597,16 @@ class FilterExpression(object):
|
||||
arg_vals.append(arg)
|
||||
else:
|
||||
arg_vals.append(arg.resolve(context))
|
||||
obj = func(obj, *arg_vals)
|
||||
if getattr(func, 'needs_autoescape', False):
|
||||
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
|
||||
else:
|
||||
new_obj = func(obj, *arg_vals)
|
||||
if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
|
||||
obj = mark_safe(new_obj)
|
||||
elif isinstance(obj, EscapeData):
|
||||
obj = mark_for_escaping(new_obj)
|
||||
else:
|
||||
obj = new_obj
|
||||
return obj
|
||||
|
||||
def args_check(name, func, provided):
|
||||
@@ -637,7 +648,7 @@ def resolve_variable(path, context):
|
||||
"""
|
||||
Returns the resolved variable, which may contain attribute syntax, within
|
||||
the given context.
|
||||
|
||||
|
||||
Deprecated; use the Variable class instead.
|
||||
"""
|
||||
return Variable(path).resolve(context)
|
||||
@@ -647,7 +658,7 @@ class Variable(object):
|
||||
A template variable, resolvable against a given context. The variable may be
|
||||
a hard-coded string (if it begins and ends with single or double quote
|
||||
marks)::
|
||||
|
||||
|
||||
>>> c = {'article': {'section':'News'}}
|
||||
>>> Variable('article.section').resolve(c)
|
||||
u'News'
|
||||
@@ -662,25 +673,25 @@ class Variable(object):
|
||||
|
||||
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, var):
|
||||
self.var = var
|
||||
self.literal = None
|
||||
self.lookups = None
|
||||
|
||||
|
||||
try:
|
||||
# First try to treat this variable as a number.
|
||||
#
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# catching. Since this should only happen at compile time, that's
|
||||
# probably OK.
|
||||
self.literal = float(var)
|
||||
|
||||
|
||||
# So it's a float... is it an int? If the original value contained a
|
||||
# dot or an "e" then it was a float, not an int.
|
||||
if '.' not in var and 'e' not in var.lower():
|
||||
self.literal = int(self.literal)
|
||||
|
||||
|
||||
# "2." is invalid
|
||||
if var.endswith('.'):
|
||||
raise ValueError
|
||||
@@ -691,12 +702,12 @@ class Variable(object):
|
||||
# we're also dealing with a literal.
|
||||
if var[0] in "\"'" and var[0] == var[-1]:
|
||||
self.literal = var[1:-1]
|
||||
|
||||
|
||||
else:
|
||||
# Otherwise we'll set self.lookups so that resolve() knows we're
|
||||
# dealing with a bonafide variable
|
||||
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
||||
|
||||
|
||||
def resolve(self, context):
|
||||
"""Resolve this variable against a given context."""
|
||||
if self.lookups is not None:
|
||||
@@ -705,18 +716,18 @@ class Variable(object):
|
||||
else:
|
||||
# We're dealing with a literal, so it's already been "resolved"
|
||||
return self.literal
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %r>" % (self.__class__.__name__, self.var)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.var
|
||||
|
||||
def _resolve_lookup(self, context):
|
||||
"""
|
||||
Performs resolution of a real variable (i.e. not a literal) against the
|
||||
given context.
|
||||
|
||||
given context.
|
||||
|
||||
As indicated by the method's name, this method is an implementation
|
||||
detail and shouldn't be called by external code. Use Variable.resolve()
|
||||
instead.
|
||||
@@ -757,14 +768,7 @@ class Variable(object):
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||
else:
|
||||
raise
|
||||
|
||||
if isinstance(current, (basestring, Promise)):
|
||||
try:
|
||||
current = force_unicode(current)
|
||||
except UnicodeDecodeError:
|
||||
# Failing to convert to unicode can happen sometimes (e.g. debug
|
||||
# tracebacks). So we allow it in this particular instance.
|
||||
pass
|
||||
|
||||
return current
|
||||
|
||||
class Node(object):
|
||||
@@ -838,16 +842,31 @@ class VariableNode(Node):
|
||||
return "<Variable Node: %s>" % self.filter_expression
|
||||
|
||||
def render(self, context):
|
||||
return self.filter_expression.resolve(context)
|
||||
try:
|
||||
output = force_unicode(self.filter_expression.resolve(context))
|
||||
except UnicodeDecodeError:
|
||||
# Unicode conversion can fail sometimes for reasons out of our
|
||||
# control (e.g. exception rendering). In that case, we fail quietly.
|
||||
return ''
|
||||
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
|
||||
return force_unicode(escape(output))
|
||||
else:
|
||||
return force_unicode(output)
|
||||
|
||||
class DebugVariableNode(VariableNode):
|
||||
def render(self, context):
|
||||
try:
|
||||
return self.filter_expression.resolve(context)
|
||||
output = force_unicode(self.filter_expression.resolve(context))
|
||||
except TemplateSyntaxError, e:
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = self.source
|
||||
raise
|
||||
except UnicodeDecodeError:
|
||||
return ''
|
||||
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
|
||||
return escape(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||
"Returns a template.Node subclass."
|
||||
@@ -961,7 +980,8 @@ class Library(object):
|
||||
else:
|
||||
t = get_template(file_name)
|
||||
self.nodelist = t.nodelist
|
||||
return self.nodelist.render(context_class(dict))
|
||||
return self.nodelist.render(context_class(dict,
|
||||
autoescape=context.autoescape))
|
||||
|
||||
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
|
||||
compile_func.__doc__ = func.__doc__
|
||||
|
||||
@@ -9,9 +9,11 @@ class ContextPopException(Exception):
|
||||
|
||||
class Context(object):
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict_=None):
|
||||
|
||||
def __init__(self, dict_=None, autoescape=True):
|
||||
dict_ = dict_ or {}
|
||||
self.dicts = [dict_]
|
||||
self.autoescape = autoescape
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.dicts)
|
||||
@@ -97,3 +99,4 @@ class RequestContext(Context):
|
||||
processors = tuple(processors)
|
||||
for processor in get_standard_processors() + processors:
|
||||
self.update(processor(request))
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.template import Variable, Library
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext, ungettext
|
||||
from django.utils.encoding import force_unicode, iri_to_uri
|
||||
from django.utils.safestring import mark_safe, SafeData
|
||||
|
||||
register = Library()
|
||||
|
||||
@@ -29,6 +30,9 @@ def stringfilter(func):
|
||||
# Include a reference to the real function (used to check original
|
||||
# arguments by the template parser).
|
||||
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
||||
for attr in ('is_safe', 'needs_autoescape'):
|
||||
if hasattr(func, attr):
|
||||
setattr(_dec, attr, getattr(func, attr))
|
||||
return _dec
|
||||
|
||||
###################
|
||||
@@ -39,17 +43,20 @@ def stringfilter(func):
|
||||
def addslashes(value):
|
||||
"""Adds slashes - useful for passing strings to JavaScript, for example."""
|
||||
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
||||
addslashes.is_safe = True
|
||||
addslashes = stringfilter(addslashes)
|
||||
|
||||
def capfirst(value):
|
||||
"""Capitalizes the first character of the value."""
|
||||
return value and value[0].upper() + value[1:]
|
||||
capfirst.is_safe=True
|
||||
capfirst = stringfilter(capfirst)
|
||||
|
||||
def fix_ampersands(value):
|
||||
"""Replaces ampersands with ``&`` entities."""
|
||||
from django.utils.html import fix_ampersands
|
||||
return fix_ampersands(value)
|
||||
fix_ampersands.is_safe=True
|
||||
fix_ampersands = stringfilter(fix_ampersands)
|
||||
|
||||
def floatformat(text, arg=-1):
|
||||
@@ -90,31 +97,39 @@ def floatformat(text, arg=-1):
|
||||
return force_unicode(f)
|
||||
m = f - int(f)
|
||||
if not m and d < 0:
|
||||
return u'%d' % int(f)
|
||||
return mark_safe(u'%d' % int(f))
|
||||
else:
|
||||
formatstr = u'%%.%df' % abs(d)
|
||||
return formatstr % f
|
||||
return mark_safe(formatstr % f)
|
||||
floatformat.is_safe = True
|
||||
|
||||
def iriencode(value):
|
||||
"""Escapes an IRI value for use in a URL."""
|
||||
return force_unicode(iri_to_uri(value))
|
||||
iriencode = stringfilter(iriencode)
|
||||
|
||||
def linenumbers(value):
|
||||
def linenumbers(value, autoescape=None):
|
||||
"""Displays text with line numbers."""
|
||||
from django.utils.html import escape
|
||||
lines = value.split(u'\n')
|
||||
# Find the maximum width of the line count, for use with zero padding
|
||||
# string format command.
|
||||
# string format command
|
||||
width = unicode(len(unicode(len(lines))))
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
|
||||
return u'\n'.join(lines)
|
||||
if not autoescape or isinstance(value, SafeData):
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)
|
||||
else:
|
||||
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
|
||||
linenumbers = stringfilter(linenumbers)
|
||||
|
||||
def lower(value):
|
||||
"""Converts a string into all lowercase."""
|
||||
return value.lower()
|
||||
lower.is_safe = True
|
||||
lower = stringfilter(lower)
|
||||
|
||||
def make_list(value):
|
||||
@@ -125,6 +140,7 @@ def make_list(value):
|
||||
For a string, it's a list of characters.
|
||||
"""
|
||||
return list(value)
|
||||
make_list.is_safe = False
|
||||
make_list = stringfilter(make_list)
|
||||
|
||||
def slugify(value):
|
||||
@@ -135,7 +151,8 @@ def slugify(value):
|
||||
import unicodedata
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
||||
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
|
||||
return re.sub('[-\s]+', '-', value)
|
||||
return mark_safe(re.sub('[-\s]+', '-', value))
|
||||
slugify.is_safe = True
|
||||
slugify = stringfilter(slugify)
|
||||
|
||||
def stringformat(value, arg):
|
||||
@@ -152,10 +169,12 @@ def stringformat(value, arg):
|
||||
return (u"%" + unicode(arg)) % value
|
||||
except (ValueError, TypeError):
|
||||
return u""
|
||||
stringformat.is_safe = True
|
||||
|
||||
def title(value):
|
||||
"""Converts a string into titlecase."""
|
||||
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
||||
title.is_safe = True
|
||||
title = stringfilter(title)
|
||||
|
||||
def truncatewords(value, arg):
|
||||
@@ -170,6 +189,7 @@ def truncatewords(value, arg):
|
||||
except ValueError: # Invalid literal for int().
|
||||
return value # Fail silently.
|
||||
return truncate_words(value, length)
|
||||
truncatewords.is_safe = True
|
||||
truncatewords = stringfilter(truncatewords)
|
||||
|
||||
def truncatewords_html(value, arg):
|
||||
@@ -184,23 +204,28 @@ def truncatewords_html(value, arg):
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
return truncate_html_words(value, length)
|
||||
truncatewords_html.is_safe = True
|
||||
truncatewords_html = stringfilter(truncatewords_html)
|
||||
|
||||
def upper(value):
|
||||
"""Converts a string into all uppercase."""
|
||||
return value.upper()
|
||||
upper.is_safe = False
|
||||
upper = stringfilter(upper)
|
||||
|
||||
def urlencode(value):
|
||||
"""Escapes a value for use in a URL."""
|
||||
from django.utils.http import urlquote
|
||||
return urlquote(value)
|
||||
urlencode.is_safe = False
|
||||
urlencode = stringfilter(urlencode)
|
||||
|
||||
def urlize(value):
|
||||
def urlize(value, autoescape=None):
|
||||
"""Converts URLs in plain text into clickable links."""
|
||||
from django.utils.html import urlize
|
||||
return urlize(value, nofollow=True)
|
||||
return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
|
||||
urlize.is_safe=True
|
||||
urlize.needs_autoescape = True
|
||||
urlize = stringfilter(urlize)
|
||||
|
||||
def urlizetrunc(value, limit):
|
||||
@@ -211,12 +236,14 @@ def urlizetrunc(value, limit):
|
||||
Argument: Length to truncate URLs to.
|
||||
"""
|
||||
from django.utils.html import urlize
|
||||
return urlize(value, trim_url_limit=int(limit), nofollow=True)
|
||||
return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
|
||||
urlizetrunc.is_safe = True
|
||||
urlizetrunc = stringfilter(urlizetrunc)
|
||||
|
||||
def wordcount(value):
|
||||
"""Returns the number of words."""
|
||||
return len(value.split())
|
||||
wordcount.is_safe = False
|
||||
wordcount = stringfilter(wordcount)
|
||||
|
||||
def wordwrap(value, arg):
|
||||
@@ -227,6 +254,7 @@ def wordwrap(value, arg):
|
||||
"""
|
||||
from django.utils.text import wrap
|
||||
return wrap(value, int(arg))
|
||||
wordwrap.is_safe = True
|
||||
wordwrap = stringfilter(wordwrap)
|
||||
|
||||
def ljust(value, arg):
|
||||
@@ -236,6 +264,7 @@ def ljust(value, arg):
|
||||
Argument: field size.
|
||||
"""
|
||||
return value.ljust(int(arg))
|
||||
ljust.is_safe = True
|
||||
ljust = stringfilter(ljust)
|
||||
|
||||
def rjust(value, arg):
|
||||
@@ -245,16 +274,24 @@ def rjust(value, arg):
|
||||
Argument: field size.
|
||||
"""
|
||||
return value.rjust(int(arg))
|
||||
rjust.is_safe = True
|
||||
rjust = stringfilter(rjust)
|
||||
|
||||
def center(value, arg):
|
||||
"""Centers the value in a field of a given width."""
|
||||
return value.center(int(arg))
|
||||
center.is_safe = True
|
||||
center = stringfilter(center)
|
||||
|
||||
def cut(value, arg):
|
||||
"""Removes all values of arg from the given string."""
|
||||
return value.replace(arg, u'')
|
||||
"""
|
||||
Removes all values of arg from the given string.
|
||||
"""
|
||||
safe = isinstance(value, SafeData)
|
||||
value = value.replace(arg, u'')
|
||||
if safe and arg != ';':
|
||||
return mark_safe(value)
|
||||
return value
|
||||
cut = stringfilter(cut)
|
||||
|
||||
###################
|
||||
@@ -262,29 +299,60 @@ cut = stringfilter(cut)
|
||||
###################
|
||||
|
||||
def escape(value):
|
||||
"Escapes a string's HTML"
|
||||
from django.utils.html import escape
|
||||
return escape(value)
|
||||
"""
|
||||
Marks the value as a string that should not be auto-escaped.
|
||||
"""
|
||||
from django.utils.safestring import mark_for_escaping
|
||||
return mark_for_escaping(value)
|
||||
escape.is_safe = True
|
||||
escape = stringfilter(escape)
|
||||
|
||||
def linebreaks(value):
|
||||
def force_escape(value):
|
||||
"""
|
||||
Escapes a string's HTML. This returns a new string containing the escaped
|
||||
characters (as opposed to "escape", which marks the content for later
|
||||
possible escaping).
|
||||
"""
|
||||
from django.utils.html import escape
|
||||
return mark_safe(escape(value))
|
||||
escape = stringfilter(escape)
|
||||
force_escape.is_safe = True
|
||||
|
||||
def linebreaks(value, autoescape=None):
|
||||
"""
|
||||
Replaces line breaks in plain text with appropriate HTML; a single
|
||||
newline becomes an HTML line break (``<br />``) and a new line
|
||||
followed by a blank line becomes a paragraph break (``</p>``).
|
||||
"""
|
||||
from django.utils.html import linebreaks
|
||||
return linebreaks(value)
|
||||
autoescape = autoescape and not isinstance(value, SafeData)
|
||||
return mark_safe(linebreaks(value, autoescape))
|
||||
linebreaks.is_safe = True
|
||||
linebreaks.needs_autoescape = True
|
||||
linebreaks = stringfilter(linebreaks)
|
||||
|
||||
def linebreaksbr(value):
|
||||
def linebreaksbr(value, autoescape=None):
|
||||
"""
|
||||
Converts all newlines in a piece of plain text to HTML line breaks
|
||||
(``<br />``).
|
||||
"""
|
||||
return value.replace('\n', '<br />')
|
||||
if autoescape and not isinstance(value, SafeData):
|
||||
from django.utils.html import escape
|
||||
value = escape(value)
|
||||
return mark_safe(value.replace('\n', '<br />'))
|
||||
linebreaksbr.is_safe = True
|
||||
linebreaksbr.needs_autoescape = True
|
||||
linebreaksbr = stringfilter(linebreaksbr)
|
||||
|
||||
def safe(value):
|
||||
"""
|
||||
Marks the value as a string that should not be auto-escaped.
|
||||
"""
|
||||
from django.utils.safestring import mark_safe
|
||||
return mark_safe(value)
|
||||
safe.is_safe = True
|
||||
safe = stringfilter(safe)
|
||||
|
||||
def removetags(value, tags):
|
||||
"""Removes a space separated list of [X]HTML tags from the output."""
|
||||
tags = [re.escape(tag) for tag in tags.split()]
|
||||
@@ -294,12 +362,14 @@ def removetags(value, tags):
|
||||
value = starttag_re.sub(u'', value)
|
||||
value = endtag_re.sub(u'', value)
|
||||
return value
|
||||
removetags.is_safe = True
|
||||
removetags = stringfilter(removetags)
|
||||
|
||||
def striptags(value):
|
||||
"""Strips all [X]HTML tags."""
|
||||
from django.utils.html import strip_tags
|
||||
return strip_tags(value)
|
||||
striptags.is_safe = True
|
||||
striptags = stringfilter(striptags)
|
||||
|
||||
###################
|
||||
@@ -315,6 +385,7 @@ def dictsort(value, arg):
|
||||
decorated = [(var_resolve(item), item) for item in value]
|
||||
decorated.sort()
|
||||
return [item[1] for item in decorated]
|
||||
dictsort.is_safe = False
|
||||
|
||||
def dictsortreversed(value, arg):
|
||||
"""
|
||||
@@ -326,6 +397,7 @@ def dictsortreversed(value, arg):
|
||||
decorated.sort()
|
||||
decorated.reverse()
|
||||
return [item[1] for item in decorated]
|
||||
dictsortreversed.is_safe = False
|
||||
|
||||
def first(value):
|
||||
"""Returns the first item in a list."""
|
||||
@@ -333,25 +405,36 @@ def first(value):
|
||||
return value[0]
|
||||
except IndexError:
|
||||
return u''
|
||||
first.is_safe = True
|
||||
|
||||
def join(value, arg):
|
||||
"""Joins a list with a string, like Python's ``str.join(list)``."""
|
||||
try:
|
||||
return arg.join(map(force_unicode, value))
|
||||
data = arg.join(map(force_unicode, value))
|
||||
except AttributeError: # fail silently but nicely
|
||||
return value
|
||||
safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
|
||||
value, True)
|
||||
if safe_args:
|
||||
return mark_safe(data)
|
||||
else:
|
||||
return data
|
||||
join.is_safe = True
|
||||
|
||||
def length(value):
|
||||
"""Returns the length of the value - useful for lists."""
|
||||
return len(value)
|
||||
length.is_safe = True
|
||||
|
||||
def length_is(value, arg):
|
||||
"""Returns a boolean of whether the value's length is the argument."""
|
||||
return len(value) == int(arg)
|
||||
length_is.is_safe = True
|
||||
|
||||
def random(value):
|
||||
"""Returns a random item from the list."""
|
||||
return random_module.choice(value)
|
||||
random.is_safe = True
|
||||
|
||||
def slice_(value, arg):
|
||||
"""
|
||||
@@ -372,8 +455,9 @@ def slice_(value, arg):
|
||||
|
||||
except (ValueError, TypeError):
|
||||
return value # Fail silently.
|
||||
slice_.is_safe = True
|
||||
|
||||
def unordered_list(value):
|
||||
def unordered_list(value, autoescape=None):
|
||||
"""
|
||||
Recursively takes a self-nested list and returns an HTML unordered list --
|
||||
WITHOUT opening and closing <ul> tags.
|
||||
@@ -394,6 +478,11 @@ def unordered_list(value):
|
||||
</ul>
|
||||
</li>
|
||||
"""
|
||||
if autoescape:
|
||||
from django.utils.html import conditional_escape
|
||||
escaper = conditional_escape
|
||||
else:
|
||||
escaper = lambda x: x
|
||||
def convert_old_style_list(list_):
|
||||
"""
|
||||
Converts old style lists to the new easier to understand format.
|
||||
@@ -443,12 +532,14 @@ def unordered_list(value):
|
||||
sublist = _helper(sublist_item, tabs+1)
|
||||
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
|
||||
indent, indent)
|
||||
output.append('%s<li>%s%s</li>' % (indent, force_unicode(title),
|
||||
sublist))
|
||||
output.append('%s<li>%s%s</li>' % (indent,
|
||||
escaper(force_unicode(title)), sublist))
|
||||
i += 1
|
||||
return '\n'.join(output)
|
||||
value, converted = convert_old_style_list(value)
|
||||
return _helper(value)
|
||||
return mark_safe(_helper(value))
|
||||
unordered_list.is_safe = True
|
||||
unordered_list.needs_autoescape = True
|
||||
|
||||
###################
|
||||
# INTEGERS #
|
||||
@@ -457,6 +548,7 @@ def unordered_list(value):
|
||||
def add(value, arg):
|
||||
"""Adds the arg to the value."""
|
||||
return int(value) + int(arg)
|
||||
add.is_safe = False
|
||||
|
||||
def get_digit(value, arg):
|
||||
"""
|
||||
@@ -476,6 +568,7 @@ def get_digit(value, arg):
|
||||
return int(str(value)[-arg])
|
||||
except IndexError:
|
||||
return 0
|
||||
get_digit.is_safe = False
|
||||
|
||||
###################
|
||||
# DATES #
|
||||
@@ -489,6 +582,7 @@ def date(value, arg=None):
|
||||
if arg is None:
|
||||
arg = settings.DATE_FORMAT
|
||||
return format(value, arg)
|
||||
date.is_safe = False
|
||||
|
||||
def time(value, arg=None):
|
||||
"""Formats a time according to the given format."""
|
||||
@@ -498,6 +592,7 @@ def time(value, arg=None):
|
||||
if arg is None:
|
||||
arg = settings.TIME_FORMAT
|
||||
return time_format(value, arg)
|
||||
time.is_safe = False
|
||||
|
||||
def timesince(value, arg=None):
|
||||
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
|
||||
@@ -507,6 +602,7 @@ def timesince(value, arg=None):
|
||||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(value)
|
||||
timesince.is_safe = False
|
||||
|
||||
def timeuntil(value, arg=None):
|
||||
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
|
||||
@@ -517,6 +613,7 @@ def timeuntil(value, arg=None):
|
||||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(datetime.now(), value)
|
||||
timeuntil.is_safe = False
|
||||
|
||||
###################
|
||||
# LOGIC #
|
||||
@@ -525,16 +622,19 @@ def timeuntil(value, arg=None):
|
||||
def default(value, arg):
|
||||
"""If value is unavailable, use given default."""
|
||||
return value or arg
|
||||
default.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
|
||||
|
||||
def divisibleby(value, arg):
|
||||
"""Returns True if the value is devisible by the argument."""
|
||||
return int(value) % int(arg) == 0
|
||||
divisibleby.is_safe = False
|
||||
|
||||
def yesno(value, arg=None):
|
||||
"""
|
||||
@@ -566,6 +666,7 @@ def yesno(value, arg=None):
|
||||
if value:
|
||||
return yes
|
||||
return no
|
||||
yesno.is_safe = False
|
||||
|
||||
###################
|
||||
# MISC #
|
||||
@@ -588,29 +689,30 @@ def filesizeformat(bytes):
|
||||
if bytes < 1024 * 1024 * 1024:
|
||||
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
|
||||
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
||||
filesizeformat.is_safe = True
|
||||
|
||||
def pluralize(value, arg=u's'):
|
||||
"""
|
||||
Returns a plural suffix if the value is not 1. By default, 's' is used as
|
||||
the suffix:
|
||||
|
||||
* If value is 0, vote{{ value|plurlize }} displays "0 votes".
|
||||
* If value is 1, vote{{ value|plurlize }} displays "1 vote".
|
||||
* If value is 2, vote{{ value|plurlize }} displays "2 votes".
|
||||
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
|
||||
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
|
||||
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
|
||||
|
||||
If an argument is provided, that string is used instead:
|
||||
|
||||
* If value is 0, class{{ value|plurlize:"es" }} displays "0 classes".
|
||||
* If value is 1, class{{ value|plurlize:"es" }} displays "1 class".
|
||||
* If value is 2, class{{ value|plurlize:"es" }} displays "2 classes".
|
||||
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
|
||||
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
|
||||
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
|
||||
|
||||
If the provided argument contains a comma, the text before the comma is
|
||||
used for the singular case and the text after the comma is used for the
|
||||
plural case:
|
||||
|
||||
* If value is 0, cand{{ value|plurlize:"y,ies" }} displays "0 candies".
|
||||
* If value is 1, cand{{ value|plurlize:"y,ies" }} displays "1 candy".
|
||||
* If value is 2, cand{{ value|plurlize:"y,ies" }} displays "2 candies".
|
||||
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
|
||||
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
|
||||
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
|
||||
"""
|
||||
if not u',' in arg:
|
||||
arg = u',' + arg
|
||||
@@ -631,11 +733,13 @@ def pluralize(value, arg=u's'):
|
||||
except TypeError: # len() of unsized object.
|
||||
pass
|
||||
return singular_suffix
|
||||
pluralize.is_safe = False
|
||||
|
||||
def phone2numeric(value):
|
||||
"""Takes a phone number and converts it in to its numerical equivalent."""
|
||||
from django.utils.text import phone2numeric
|
||||
return phone2numeric(value)
|
||||
phone2numeric.is_safe = True
|
||||
|
||||
def pprint(value):
|
||||
"""A wrapper around pprint.pprint -- for debugging, really."""
|
||||
@@ -644,6 +748,7 @@ def pprint(value):
|
||||
return pformat(value)
|
||||
except Exception, e:
|
||||
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
|
||||
pprint.is_safe = True
|
||||
|
||||
# Syntax: register.filter(name of filter, callback)
|
||||
register.filter(add)
|
||||
@@ -662,6 +767,7 @@ register.filter(filesizeformat)
|
||||
register.filter(first)
|
||||
register.filter(fix_ampersands)
|
||||
register.filter(floatformat)
|
||||
register.filter(force_escape)
|
||||
register.filter(get_digit)
|
||||
register.filter(iriencode)
|
||||
register.filter(join)
|
||||
@@ -679,6 +785,7 @@ register.filter(pprint)
|
||||
register.filter(removetags)
|
||||
register.filter(random)
|
||||
register.filter(rjust)
|
||||
register.filter(safe)
|
||||
register.filter('slice', slice_)
|
||||
register.filter(slugify)
|
||||
register.filter(stringformat)
|
||||
|
||||
@@ -14,9 +14,25 @@ from django.template import get_library, Library, InvalidTemplateLibrary
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.itercompat import groupby
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = Library()
|
||||
|
||||
class AutoEscapeControlNode(Node):
|
||||
"""Implements the actions of the autoescape tag."""
|
||||
def __init__(self, setting, nodelist):
|
||||
self.setting, self.nodelist = setting, nodelist
|
||||
|
||||
def render(self, context):
|
||||
old_setting = context.autoescape
|
||||
context.autoescape = self.setting
|
||||
output = self.nodelist.render(context)
|
||||
context.autoescape = old_setting
|
||||
if self.setting:
|
||||
return mark_safe(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
class CommentNode(Node):
|
||||
def render(self, context):
|
||||
return ''
|
||||
@@ -392,6 +408,22 @@ class WithNode(Node):
|
||||
context.pop()
|
||||
return output
|
||||
|
||||
#@register.tag
|
||||
def autoescape(parser, token):
|
||||
"""
|
||||
Force autoescape behaviour for this block.
|
||||
"""
|
||||
args = token.contents.split()
|
||||
if len(args) != 2:
|
||||
raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
|
||||
arg = args[1]
|
||||
if arg not in (u'on', u'off'):
|
||||
raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
|
||||
nodelist = parser.parse(('endautoescape',))
|
||||
parser.delete_first_token()
|
||||
return AutoEscapeControlNode((arg == 'on'), nodelist)
|
||||
autoescape = register.tag(autoescape)
|
||||
|
||||
#@register.tag
|
||||
def comment(parser, token):
|
||||
"""
|
||||
@@ -492,12 +524,15 @@ def do_filter(parser, token):
|
||||
|
||||
Sample usage::
|
||||
|
||||
{% filter escape|lower %}
|
||||
{% filter force_escape|lower %}
|
||||
This text will be HTML-escaped, and will appear in lowercase.
|
||||
{% endfilter %}
|
||||
"""
|
||||
_, rest = token.contents.split(None, 1)
|
||||
filter_expr = parser.compile_filter("var|%s" % (rest))
|
||||
for func, unused in filter_expr.filters:
|
||||
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
|
||||
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
|
||||
nodelist = parser.parse(('endfilter',))
|
||||
parser.delete_first_token()
|
||||
return FilterNode(filter_expr, nodelist)
|
||||
|
||||
Reference in New Issue
Block a user