" % self.filter_expression
def encode_output(self, output):
# Check type so that we don't run str() on a Unicode object
@@ -703,30 +754,153 @@ class VariableNode(Node):
return output
def render(self, context):
- output = resolve_variable_with_filters(self.var_string, context)
+ output = self.filter_expression.resolve(context)
return self.encode_output(output)
class DebugVariableNode(VariableNode):
def render(self, context):
try:
- output = resolve_variable_with_filters(self.var_string, context)
+ output = self.filter_expression.resolve(context)
except TemplateSyntaxError, e:
if not hasattr(e, 'source'):
e.source = self.source
raise
return self.encode_output(output)
-def register_tag(token_command, callback_function):
- registered_tags[token_command] = callback_function
+def generic_tag_compiler(params, defaults, name, node_class, parser, token):
+ "Returns a template.Node subclass."
+ bits = token.contents.split()[1:]
+ bmax = len(params)
+ def_len = defaults and len(defaults) or 0
+ bmin = bmax - def_len
+ if(len(bits) < bmin or len(bits) > bmax):
+ if bmin == bmax:
+ message = "%s takes %s arguments" % (name, bmin)
+ else:
+ message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
+ raise TemplateSyntaxError, message
+ return node_class(bits)
-def unregister_tag(token_command):
- del registered_tags[token_command]
+class Library(object):
+ def __init__(self):
+ self.filters = {}
+ self.tags = {}
-def register_filter(filter_name, callback_function, has_arg):
- registered_filters[filter_name] = (callback_function, has_arg)
+ def tag(self, name = None, compile_function = None):
+ if name == None and compile_function == None:
+ # @register.tag()
+ return self.tag_function
+ elif name != None and compile_function == None:
+ if(callable(name)):
+ # @register.tag
+ return self.tag_function(name)
+ else:
+ # @register.tag('somename') or @register.tag(name='somename')
+ def dec(func):
+ return self.tag(name, func)
+ return dec
+ elif name != None and compile_function != None:
+ # register.tag('somename', somefunc)
+ self.tags[name] = compile_function
+ return compile_function
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
-def unregister_filter(filter_name):
- del registered_filters[filter_name]
+ def tag_function(self,func):
+ self.tags[func.__name__] = func
+ return func
-import defaulttags
-import defaultfilters
+ def filter(self, name = None, filter_func = None):
+ if name == None and filter_func == None:
+ # @register.filter()
+ return self.filter_function
+ elif filter_func == None:
+ if(callable(name)):
+ # @register.filter
+ return self.filter_function(name)
+ else:
+ # @register.filter('somename') or @register.filter(name='somename')
+ def dec(func):
+ return self.filter(name, func)
+ return dec
+ elif name != None and filter_func != None:
+ # register.filter('somename', somefunc)
+ self.filters[name] = filter_func
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg)
+
+ def filter_function(self, func):
+ self.filters[func.__name__] = func
+ return func
+
+ def simple_tag(self,func):
+ (params, xx, xxx, defaults) = getargspec(func)
+
+ class SimpleNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ return func(*resolved_vars)
+
+ compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(func.__name__, compile_func)
+ return func
+
+ def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
+ def dec(func):
+ (params, xx, xxx, defaults) = getargspec(func)
+ if takes_context:
+ if params[0] == 'context':
+ params = params[1:]
+ else:
+ raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
+
+ class InclusionNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ if takes_context:
+ args = [context] + resolved_vars
+ else:
+ args = resolved_vars
+
+ dict = func(*args)
+
+ if not getattr(self, 'nodelist', False):
+ from django.core.template_loader import get_template
+ t = get_template(file_name)
+ self.nodelist = t.nodelist
+ return self.nodelist.render(context_class(dict))
+
+ compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(func.__name__, compile_func)
+ return func
+ return dec
+
+def get_library(module_name):
+ lib = libraries.get(module_name, None)
+ if not lib:
+ try:
+ mod = __import__(module_name, '', '', [''])
+ except ImportError, e:
+ raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
+ for k, v in mod.__dict__.items():
+ if isinstance(v, Library):
+ lib = v
+ libraries[module_name] = lib
+ break
+ if not lib:
+ raise InvalidTemplateLibrary, "Template library %s does not have a Library member" % module_name
+ return lib
+
+def add_to_builtins(module_name):
+ builtins.append(get_library(module_name))
+
+add_to_builtins('django.core.template.defaulttags')
+add_to_builtins('django.core.template.defaultfilters')
diff --git a/django/core/template/decorators.py b/django/core/template/decorators.py
deleted file mode 100644
index 2a61a600ec..0000000000
--- a/django/core/template/decorators.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from django.core.template import Context, Node, TemplateSyntaxError, register_tag, resolve_variable
-from django.core.template_loader import get_template
-from django.utils.functional import curry
-from inspect import getargspec
-
-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
- "Returns a template.Node subclass."
- bits = token.contents.split()[1:]
- bmax = len(params)
- def_len = defaults and len(defaults) or 0
- bmin = bmax - def_len
- if(len(bits) < bmin or len(bits) > bmax):
- if bmin == bmax:
- message = "%s takes %s arguments" % (name, bmin)
- else:
- message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
- raise TemplateSyntaxError, message
- return node_class(bits)
-
-def simple_tag(func):
- (params, xx, xxx, defaults) = getargspec(func)
-
- class SimpleNode(Node):
- def __init__(self, vars_to_resolve):
- self.vars_to_resolve = vars_to_resolve
-
- def render(self, context):
- resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
- return func(*resolved_vars)
-
- compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
- compile_func.__doc__ = func.__doc__
- register_tag(func.__name__, compile_func)
- return func
-
-def inclusion_tag(file_name, context_class=Context, takes_context=False):
- def dec(func):
- (params, xx, xxx, defaults) = getargspec(func)
- if takes_context:
- if params[0] == 'context':
- params = params[1:]
- else:
- raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
-
- class InclusionNode(Node):
- def __init__(self, vars_to_resolve):
- self.vars_to_resolve = vars_to_resolve
-
- def render(self, context):
- resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
- if takes_context:
- args = [context] + resolved_vars
- else:
- args = resolved_vars
-
- dict = func(*args)
-
- if not getattr(self, 'nodelist', False):
- t = get_template(file_name)
- self.nodelist = t.nodelist
- return self.nodelist.render(context_class(dict))
-
- compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
- compile_func.__doc__ = func.__doc__
- register_tag(func.__name__, compile_func)
- return func
- return dec
diff --git a/django/core/template/defaultfilters.py b/django/core/template/defaultfilters.py
index 3a25add5e1..5d87b62e57 100644
--- a/django/core/template/defaultfilters.py
+++ b/django/core/template/defaultfilters.py
@@ -1,28 +1,32 @@
"Default variable filters"
-from django.core.template import register_filter, resolve_variable
+from django.core.template import resolve_variable, Library
+from django.conf.settings import DATE_FORMAT, TIME_FORMAT
import re
import random as random_module
+register = Library()
+
###################
# STRINGS #
###################
-def addslashes(value, _):
+
+def addslashes(value):
"Adds slashes - useful for passing strings to JavaScript, for example."
return value.replace('"', '\\"').replace("'", "\\'")
-def capfirst(value, _):
+def capfirst(value):
"Capitalizes the first character of the value"
value = str(value)
return value and value[0].upper() + value[1:]
-def fix_ampersands(value, _):
+def fix_ampersands(value):
"Replaces ampersands with ``&`` entities"
from django.utils.html import fix_ampersands
return fix_ampersands(value)
-def floatformat(text, _):
+def floatformat(text):
"""
Displays a floating point number as 34.2 (with one decimal place) -- but
only if there's a point to be displayed
@@ -37,7 +41,7 @@ def floatformat(text, _):
else:
return '%d' % int(f)
-def linenumbers(value, _):
+def linenumbers(value):
"Displays text with line numbers"
from django.utils.html import escape
lines = value.split('\n')
@@ -47,18 +51,18 @@ def linenumbers(value, _):
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
return '\n'.join(lines)
-def lower(value, _):
+def lower(value):
"Converts a string into all lowercase"
return value.lower()
-def make_list(value, _):
+def make_list(value):
"""
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.
"""
return list(str(value))
-def slugify(value, _):
+def slugify(value):
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
value = re.sub('[^\w\s-]', '', value).strip().lower()
return re.sub('\s+', '-', value)
@@ -77,7 +81,7 @@ def stringformat(value, arg):
except (ValueError, TypeError):
return ""
-def title(value, _):
+def title(value):
"Converts a string into titlecase"
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
@@ -96,16 +100,16 @@ def truncatewords(value, arg):
value = str(value)
return truncate_words(value, length)
-def upper(value, _):
+def upper(value):
"Converts a string into all uppercase"
return value.upper()
-def urlencode(value, _):
+def urlencode(value):
"Escapes a value for use in a URL"
import urllib
return urllib.quote(value)
-def urlize(value, _):
+def urlize(value):
"Converts URLs in plain text into clickable links"
from django.utils.html import urlize
return urlize(value, nofollow=True)
@@ -119,7 +123,7 @@ def urlizetrunc(value, limit):
from django.utils.html import urlize
return urlize(value, trim_url_limit=int(limit), nofollow=True)
-def wordcount(value, _):
+def wordcount(value):
"Returns the number of words"
return len(value.split())
@@ -160,17 +164,17 @@ def cut(value, arg):
# HTML STRINGS #
###################
-def escape(value, _):
+def escape(value):
"Escapes a string's HTML"
from django.utils.html import escape
return escape(value)
-def linebreaks(value, _):
+def linebreaks(value):
"Converts newlines into and
s"
from django.utils.html import linebreaks
return linebreaks(value)
-def linebreaksbr(value, _):
+def linebreaksbr(value):
"Converts newlines into
s"
return value.replace('\n', '
')
@@ -184,7 +188,7 @@ def removetags(value, tags):
value = endtag_re.sub('', value)
return value
-def striptags(value, _):
+def striptags(value):
"Strips all [X]HTML tags"
from django.utils.html import strip_tags
if not isinstance(value, basestring):
@@ -214,7 +218,7 @@ def dictsortreversed(value, arg):
decorated.reverse()
return [item[1] for item in decorated]
-def first(value, _):
+def first(value):
"Returns the first item in a list"
try:
return value[0]
@@ -228,7 +232,7 @@ def join(value, arg):
except AttributeError: # fail silently but nicely
return value
-def length(value, _):
+def length(value):
"Returns the length of the value - useful for lists"
return len(value)
@@ -236,7 +240,7 @@ def length_is(value, arg):
"Returns a boolean of whether the value's length is the argument"
return len(value) == int(arg)
-def random(value, _):
+def random(value):
"Returns a random item from the list"
return random_module.choice(value)
@@ -253,7 +257,7 @@ def slice_(value, arg):
except (ValueError, TypeError):
return value # Fail silently.
-def unordered_list(value, _):
+def unordered_list(value):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing
tags.
@@ -314,17 +318,17 @@ def get_digit(value, arg):
# DATES #
###################
-def date(value, arg):
+def date(value, arg=DATE_FORMAT):
"Formats a date according to the given format"
from django.utils.dateformat import format
return format(value, arg)
-def time(value, arg):
+def time(value, arg=TIME_FORMAT):
"Formats a time according to the given format"
from django.utils.dateformat import time_format
return time_format(value, arg)
-def timesince(value, _):
+def timesince(value):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
from django.utils.timesince import timesince
return timesince(value)
@@ -347,7 +351,7 @@ def divisibleby(value, arg):
"Returns true if the value is devisible by the argument"
return int(value) % int(arg) == 0
-def yesno(value, arg):
+def yesno(value, arg=_("yes,no,maybe")):
"""
Given a string mapping values for true, false and (optionally) None,
returns one of those strings accoding to the value:
@@ -379,7 +383,7 @@ def yesno(value, arg):
# MISC #
###################
-def filesizeformat(bytes, _):
+def filesizeformat(bytes):
"""
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
@@ -393,7 +397,7 @@ def filesizeformat(bytes, _):
return "%.1f MB" % (bytes / (1024 * 1024))
return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
-def pluralize(value, _):
+def pluralize(value):
"Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
try:
if int(value) != 1:
@@ -408,62 +412,62 @@ def pluralize(value, _):
pass
return ''
-def phone2numeric(value, _):
+def phone2numeric(value):
"Takes a phone number and converts it in to its numerical equivalent"
from django.utils.text import phone2numeric
return phone2numeric(value)
-def pprint(value, _):
+def pprint(value):
"A wrapper around pprint.pprint -- for debugging, really"
from pprint import pformat
return pformat(value)
-# Syntax: register_filter(name of filter, callback, has_argument)
-register_filter('add', add, True)
-register_filter('addslashes', addslashes, False)
-register_filter('capfirst', capfirst, False)
-register_filter('center', center, True)
-register_filter('cut', cut, True)
-register_filter('date', date, True)
-register_filter('default', default, True)
-register_filter('default_if_none', default_if_none, True)
-register_filter('dictsort', dictsort, True)
-register_filter('dictsortreversed', dictsortreversed, True)
-register_filter('divisibleby', divisibleby, True)
-register_filter('escape', escape, False)
-register_filter('filesizeformat', filesizeformat, False)
-register_filter('first', first, False)
-register_filter('fix_ampersands', fix_ampersands, False)
-register_filter('floatformat', floatformat, False)
-register_filter('get_digit', get_digit, True)
-register_filter('join', join, True)
-register_filter('length', length, False)
-register_filter('length_is', length_is, True)
-register_filter('linebreaks', linebreaks, False)
-register_filter('linebreaksbr', linebreaksbr, False)
-register_filter('linenumbers', linenumbers, False)
-register_filter('ljust', ljust, True)
-register_filter('lower', lower, False)
-register_filter('make_list', make_list, False)
-register_filter('phone2numeric', phone2numeric, False)
-register_filter('pluralize', pluralize, False)
-register_filter('pprint', pprint, False)
-register_filter('removetags', removetags, True)
-register_filter('random', random, False)
-register_filter('rjust', rjust, True)
-register_filter('slice', slice_, True)
-register_filter('slugify', slugify, False)
-register_filter('stringformat', stringformat, True)
-register_filter('striptags', striptags, False)
-register_filter('time', time, True)
-register_filter('timesince', timesince, False)
-register_filter('title', title, False)
-register_filter('truncatewords', truncatewords, True)
-register_filter('unordered_list', unordered_list, False)
-register_filter('upper', upper, False)
-register_filter('urlencode', urlencode, False)
-register_filter('urlize', urlize, False)
-register_filter('urlizetrunc', urlizetrunc, True)
-register_filter('wordcount', wordcount, False)
-register_filter('wordwrap', wordwrap, True)
-register_filter('yesno', yesno, True)
+# Syntax: register.filter(name of filter, callback)
+register.filter(add)
+register.filter(addslashes)
+register.filter(capfirst)
+register.filter(center)
+register.filter(cut)
+register.filter(date)
+register.filter(default)
+register.filter(default_if_none)
+register.filter(dictsort)
+register.filter(dictsortreversed)
+register.filter(divisibleby)
+register.filter(escape)
+register.filter(filesizeformat)
+register.filter(first)
+register.filter(fix_ampersands)
+register.filter(floatformat)
+register.filter(get_digit)
+register.filter(join)
+register.filter(length)
+register.filter(length_is)
+register.filter(linebreaks)
+register.filter(linebreaksbr)
+register.filter(linenumbers)
+register.filter(ljust)
+register.filter(lower)
+register.filter(make_list)
+register.filter(phone2numeric)
+register.filter(pluralize)
+register.filter(pprint)
+register.filter(removetags)
+register.filter(random)
+register.filter(rjust)
+register.filter(slice_)
+register.filter(slugify)
+register.filter(stringformat)
+register.filter(striptags)
+register.filter(time)
+register.filter(timesince)
+register.filter(title)
+register.filter(truncatewords)
+register.filter(unordered_list)
+register.filter(upper)
+register.filter(urlencode)
+register.filter(urlize)
+register.filter(urlizetrunc)
+register.filter(wordcount)
+register.filter(wordwrap)
+register.filter(yesno)
\ No newline at end of file
diff --git a/django/core/template/defaulttags.py b/django/core/template/defaulttags.py
index 08ae3d9852..e47c8d1d02 100644
--- a/django/core/template/defaulttags.py
+++ b/django/core/template/defaulttags.py
@@ -1,9 +1,12 @@
"Default tags used by the template system, available to all templates."
-from django.core.template import Node, NodeList, Template, Context, resolve_variable, resolve_variable_with_filters, get_filters_from_token, registered_filters
-from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, register_tag
+from django.core.template import Node, NodeList, Template, Context, resolve_variable
+from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END
+from django.core.template import get_library, Library, InvalidTemplateLibrary
import sys
+register = Library()
+
class CommentNode(Node):
def render(self, context):
return ''
@@ -27,15 +30,13 @@ class DebugNode(Node):
return ''.join(output)
class FilterNode(Node):
- def __init__(self, filters, nodelist):
- self.filters, self.nodelist = filters, nodelist
+ def __init__(self, filter_expr, nodelist):
+ self.filter_expr, self.nodelist = filter_expr, nodelist
def render(self, context):
output = self.nodelist.render(context)
# apply filters
- for f in self.filters:
- output = registered_filters[f[0]][0](output, f[1])
- return output
+ return self.filter_expr.resolve(Context({'var': output}))
class FirstOfNode(Node):
def __init__(self, vars):
@@ -81,7 +82,7 @@ class ForNode(Node):
parentloop = {}
context.push()
try:
- values = resolve_variable_with_filters(self.sequence, context)
+ values = self.sequence.resolve(context)
except VariableDoesNotExist:
values = []
if values is None:
@@ -147,8 +148,8 @@ class IfEqualNode(Node):
return self.nodelist_false.render(context)
class IfNode(Node):
- def __init__(self, boolvars, nodelist_true, nodelist_false):
- self.boolvars = boolvars
+ def __init__(self, bool_exprs, nodelist_true, nodelist_false):
+ self.bool_exprs = bool_exprs
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
def __repr__(self):
@@ -169,9 +170,9 @@ class IfNode(Node):
return nodes
def render(self, context):
- for ifnot, boolvar in self.boolvars:
+ for ifnot, bool_expr in self.bool_exprs:
try:
- value = resolve_variable_with_filters(boolvar, context)
+ value = bool_expr.resolve(context)
except VariableDoesNotExist:
value = None
if (value and not ifnot) or (ifnot and not value):
@@ -179,19 +180,18 @@ class IfNode(Node):
return self.nodelist_false.render(context)
class RegroupNode(Node):
- def __init__(self, target_var, expression, var_name):
- self.target_var, self.expression = target_var, expression
+ def __init__(self, target, expression, var_name):
+ self.target, self.expression = target, expression
self.var_name = var_name
def render(self, context):
- obj_list = resolve_variable_with_filters(self.target_var, context)
+ obj_list = self.target.resolve(context)
if obj_list == '': # target_var wasn't found in context; fail silently
context[self.var_name] = []
return ''
output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
for obj in obj_list:
- grouper = resolve_variable_with_filters('var.%s' % self.expression, \
- Context({'var': obj}))
+ grouper = self.expression.resolve(Context({'var': obj}))
# TODO: Is this a sensible way to determine equality?
if output and repr(output[-1]['grouper']) == repr(grouper):
output[-1]['list'].append(obj)
@@ -236,21 +236,7 @@ class SsiNode(Node):
return output
class LoadNode(Node):
- def __init__(self, taglib):
- self.taglib = taglib
-
- def load_taglib(taglib):
- mod = __import__("django.templatetags.%s" % taglib.split('.')[-1], '', '', [''])
- reload(mod)
- return mod
- load_taglib = staticmethod(load_taglib)
-
def render(self, context):
- "Import the relevant module"
- try:
- self.__class__.load_taglib(self.taglib)
- except ImportError:
- pass # Fail silently for invalid loads.
return ''
class NowNode(Node):
@@ -276,15 +262,15 @@ class TemplateTagNode(Node):
return self.mapping.get(self.tagtype, '')
class WidthRatioNode(Node):
- def __init__(self, val_var, max_var, max_width):
- self.val_var = val_var
- self.max_var = max_var
+ def __init__(self, val_expr, max_expr, max_width):
+ self.val_expr = val_expr
+ self.max_expr = max_expr
self.max_width = max_width
def render(self, context):
try:
- value = resolve_variable_with_filters(self.val_var, context)
- maxvalue = resolve_variable_with_filters(self.max_var, context)
+ value = self.val_expr.resolve(context)
+ maxvalue = self.max_expr.resolve(context)
except VariableDoesNotExist:
return ''
try:
@@ -295,15 +281,18 @@ class WidthRatioNode(Node):
return ''
return str(int(round(ratio)))
-def do_comment(parser, token):
+#@register.tag
+def comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
"""
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
+comment = register.tag(comment)
-def do_cycle(parser, token):
+#@register.tag
+def cycle(parser, token):
"""
Cycle among the given strings each time this tag is encountered
@@ -369,11 +358,9 @@ def do_cycle(parser, token):
else:
raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
+cycle = register.tag(cycle)
-def do_debug(parser, token):
- "Print a whole load of debugging information, including the context and imported modules"
- return DebugNode()
-
+#@register.tag(name="filter")
def do_filter(parser, token):
"""
Filter the contents of the blog through variable filters.
@@ -388,12 +375,14 @@ def do_filter(parser, token):
{% endfilter %}
"""
_, rest = token.contents.split(None, 1)
- _, filters = get_filters_from_token('var|%s' % rest)
+ filter_expr = parser.compile_filter("var|%s" % (rest))
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
- return FilterNode(filters, nodelist)
+ return FilterNode(filter_expr, nodelist)
+filter = register.tag("filter", do_filter)
-def do_firstof(parser, token):
+#@register.tag
+def firstof(parser, token):
"""
Outputs the first variable passed that is not False.
@@ -419,8 +408,9 @@ def do_firstof(parser, token):
if len(bits) < 1:
raise TemplateSyntaxError, "'firstof' statement requires at least one argument"
return FirstOfNode(bits)
+firstof = register.tag(firstof)
-
+#@register.tag(name="for")
def do_for(parser, token):
"""
Loop over each item in an array.
@@ -462,11 +452,12 @@ def do_for(parser, token):
if bits[2] != 'in':
raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
loopvar = bits[1]
- sequence = bits[3]
+ sequence = parser.compile_filter(bits[3])
reversed = (len(bits) == 5)
nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token()
return ForNode(loopvar, sequence, reversed, nodelist_loop)
+do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate):
"""
@@ -497,6 +488,17 @@ def do_ifequal(parser, token, negate):
nodelist_false = NodeList()
return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+#@register.tag
+def ifequal(parser, token):
+ return do_ifequal(parser, token, False)
+ifequal = register.tag(ifequal)
+
+#@register.tag
+def ifnotequal(parser, token):
+ return do_ifequal(parser, token, True)
+ifnotequal = register.tag(ifnotequal)
+
+#@register.tag(name="if")
def do_if(parser, token):
"""
The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
@@ -554,9 +556,9 @@ def do_if(parser, token):
not_, boolvar = boolpair.split()
if not_ != 'not':
raise TemplateSyntaxError, "Expected 'not' in if statement"
- boolvars.append((True, boolvar))
+ boolvars.append((True, parser.compile_filter(boolvar)))
else:
- boolvars.append((False, boolpair))
+ boolvars.append((False, parser.compile_filter(boolpair)))
nodelist_true = parser.parse(('else', 'endif'))
token = parser.next_token()
if token.contents == 'else':
@@ -565,8 +567,10 @@ def do_if(parser, token):
else:
nodelist_false = NodeList()
return IfNode(boolvars, nodelist_true, nodelist_false)
+do_if = register.tag("if", do_if)
-def do_ifchanged(parser, token):
+#@register.tag
+def ifchanged(parser, token):
"""
Check if a value has changed from the last iteration of a loop.
@@ -587,8 +591,10 @@ def do_ifchanged(parser, token):
nodelist = parser.parse(('endifchanged',))
parser.delete_first_token()
return IfChangedNode(nodelist)
+ifchanged = register.tag(ifchanged)
-def do_ssi(parser, token):
+#@register.tag
+def ssi(parser, token):
"""
Output the contents of a given file into the page.
@@ -613,8 +619,10 @@ def do_ssi(parser, token):
else:
raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
return SsiNode(bits[1], parsed)
+ssi = register.tag(ssi)
-def do_load(parser, token):
+#@register.tag
+def load(parser, token):
"""
Load a custom template tag set.
@@ -623,17 +631,18 @@ def do_load(parser, token):
{% load news.photos %}
"""
bits = token.contents.split()
- if len(bits) != 2:
- raise TemplateSyntaxError, "'load' statement takes one argument"
- taglib = bits[1]
- # check at compile time that the module can be imported
- try:
- LoadNode.load_taglib(taglib)
- except ImportError, e:
- raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
- return LoadNode(taglib)
+ for taglib in bits[1:]:
+ # add the library to the parser
+ try:
+ lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
+ parser.add_library(lib)
+ except InvalidTemplateLibrary, e:
+ raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
+ return LoadNode()
+load = register.tag(load)
-def do_now(parser, token):
+#@register.tag
+def now(parser, token):
"""
Display the date, formatted according to the given string.
@@ -649,8 +658,10 @@ def do_now(parser, token):
raise TemplateSyntaxError, "'now' statement takes one argument"
format_string = bits[1]
return NowNode(format_string)
+now = register.tag(now)
-def do_regroup(parser, token):
+#@register.tag
+def regroup(parser, token):
"""
Regroup a list of alike objects by a common attribute.
@@ -699,17 +710,21 @@ def do_regroup(parser, token):
firstbits = token.contents.split(None, 3)
if len(firstbits) != 4:
raise TemplateSyntaxError, "'regroup' tag takes five arguments"
- target_var = firstbits[1]
+ target = parser.compile_filter(firstbits[1])
if firstbits[2] != 'by':
raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
- expression = lastbits_reversed[2][::-1]
- var_name = lastbits_reversed[0][::-1]
- return RegroupNode(target_var, expression, var_name)
-def do_templatetag(parser, token):
+ expression = parser.compile_filters('var.%s' % lastbits_reversed[2][::-1])
+
+ var_name = lastbits_reversed[0][::-1]
+ return RegroupNode(target, expression, var_name)
+regroup = register.tag(regroup)
+
+#@register.tag
+def templatetag(parser, token):
"""
Output one of the bits used to compose template tags.
@@ -735,8 +750,10 @@ def do_templatetag(parser, token):
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
return TemplateTagNode(tag)
+templatetag = register.tag(templatetag)
-def do_widthratio(parser, token):
+@register.tag
+def widthratio(parser, token):
"""
For creating bar charts and such, this tag calculates the ratio of a given
value to a maximum value, and then applies that ratio to a constant.
@@ -752,26 +769,11 @@ def do_widthratio(parser, token):
bits = token.contents.split()
if len(bits) != 4:
raise TemplateSyntaxError("widthratio takes three arguments")
- tag, this_value_var, max_value_var, max_width = bits
+ tag, this_value_expr, max_value_expr, max_width = bits
try:
max_width = int(max_width)
except ValueError:
raise TemplateSyntaxError("widthratio final argument must be an integer")
- return WidthRatioNode(this_value_var, max_value_var, max_width)
-
-register_tag('comment', do_comment)
-register_tag('cycle', do_cycle)
-register_tag('debug', do_debug)
-register_tag('filter', do_filter)
-register_tag('firstof', do_firstof)
-register_tag('for', do_for)
-register_tag('ifequal', lambda parser, token: do_ifequal(parser, token, False))
-register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True))
-register_tag('if', do_if)
-register_tag('ifchanged', do_ifchanged)
-register_tag('regroup', do_regroup)
-register_tag('ssi', do_ssi)
-register_tag('load', do_load)
-register_tag('now', do_now)
-register_tag('templatetag', do_templatetag)
-register_tag('widthratio', do_widthratio)
+ return WidthRatioNode(parser.compile_filter(this_value_expr),
+ parser.compile_filter(max_value_expr), max_width)
+widthratio = register.tag(widthratio)
diff --git a/django/core/template/loader.py b/django/core/template/loader.py
index 6d747d560d..e1409c2bd0 100644
--- a/django/core/template/loader.py
+++ b/django/core/template/loader.py
@@ -21,7 +21,7 @@
# installed, because pkg_resources is necessary to read eggs.
from django.core.exceptions import ImproperlyConfigured
-from django.core.template import Origin, StringOrigin, Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, resolve_variable, register_tag
+from django.core.template import Origin, StringOrigin, Template, TemplateDoesNotExist, add_to_builtins
from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG
template_source_loaders = []
@@ -68,9 +68,6 @@ def find_template_source(name, dirs=None):
def load_template_source(name, dirs=None):
find_template_source(name, dirs)[0]
-class ExtendsError(Exception):
- pass
-
def get_template(template_name):
"""
Returns a compiled Template object for the given template name,
@@ -113,166 +110,4 @@ def select_template(template_name_list):
# If we get here, none of the templates could be loaded
raise TemplateDoesNotExist, ', '.join(template_name_list)
-class BlockNode(Node):
- def __init__(self, name, nodelist, parent=None):
- self.name, self.nodelist, self.parent = name, nodelist, parent
-
- def __repr__(self):
- return "" % (self.name, self.nodelist)
-
- def render(self, context):
- context.push()
- # Save context in case of block.super().
- self.context = context
- context['block'] = self
- result = self.nodelist.render(context)
- context.pop()
- return result
-
- def super(self):
- if self.parent:
- return self.parent.render(self.context)
- return ''
-
- def add_parent(self, nodelist):
- if self.parent:
- self.parent.add_parent(nodelist)
- else:
- self.parent = BlockNode(self.name, nodelist)
-
-class ExtendsNode(Node):
- def __init__(self, nodelist, parent_name, parent_name_var, template_dirs=None):
- self.nodelist = nodelist
- self.parent_name, self.parent_name_var = parent_name, parent_name_var
- self.template_dirs = template_dirs
-
- def get_parent(self, context):
- if self.parent_name_var:
- self.parent_name = resolve_variable_with_filters(self.parent_name_var, context)
- parent = self.parent_name
- if not parent:
- error_msg = "Invalid template name in 'extends' tag: %r." % parent
- if self.parent_name_var:
- error_msg += " Got this from the %r variable." % self.parent_name_var
- raise TemplateSyntaxError, error_msg
- try:
- return get_template_from_string(*find_template_source(parent, self.template_dirs))
- except TemplateDoesNotExist:
- raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
-
- def render(self, context):
- compiled_parent = self.get_parent(context)
- parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
- parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
- for block_node in self.nodelist.get_nodes_by_type(BlockNode):
- # Check for a BlockNode with this node's name, and replace it if found.
- try:
- parent_block = parent_blocks[block_node.name]
- except KeyError:
- # This BlockNode wasn't found in the parent template, but the
- # parent block might be defined in the parent's *parent*, so we
- # add this BlockNode to the parent's ExtendsNode nodelist, so
- # it'll be checked when the parent node's render() is called.
- if parent_is_child:
- compiled_parent.nodelist[0].nodelist.append(block_node)
- else:
- # Keep any existing parents and add a new one. Used by BlockNode.
- parent_block.parent = block_node.parent
- parent_block.add_parent(parent_block.nodelist)
- parent_block.nodelist = block_node.nodelist
- return compiled_parent.render(context)
-
-class ConstantIncludeNode(Node):
- def __init__(self, template_path):
- try:
- t = get_template(template_path)
- self.template = t
- except Exception, e:
- if TEMPLATE_DEBUG:
- raise
- self.template = None
-
- def render(self, context):
- if self.template:
- return self.template.render(context)
- else:
- return ''
-
-class IncludeNode(Node):
- def __init__(self, template_name):
- self.template_name = template_name
-
- def render(self, context):
- try:
- template_name = resolve_variable(self.template_name, context)
- t = get_template(template_name)
- return t.render(context)
- except TemplateSyntaxError, e:
- if TEMPLATE_DEBUG:
- raise
- return ''
- except:
- return '' # Fail silently for invalid included templates.
-
-def do_block(parser, token):
- """
- Define a block that can be overridden by child templates.
- """
- bits = token.contents.split()
- if len(bits) != 2:
- raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
- block_name = bits[1]
- # Keep track of the names of BlockNodes found in this template, so we can
- # check for duplication.
- try:
- if block_name in parser.__loaded_blocks:
- raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
- parser.__loaded_blocks.append(block_name)
- except AttributeError: # parser._loaded_blocks isn't a list yet
- parser.__loaded_blocks = [block_name]
- nodelist = parser.parse(('endblock',))
- parser.delete_first_token()
- return BlockNode(block_name, nodelist)
-
-def do_extends(parser, token):
- """
- Signal that this template extends a parent template.
-
- This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
- uses the literal value "base" as the name of the parent template to extend,
- or ``{% extends variable %}`` uses the value of ``variable`` as the name
- of the parent template to extend.
- """
- bits = token.contents.split()
- if len(bits) != 2:
- raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
- parent_name, parent_name_var = None, None
- if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
- parent_name = bits[1][1:-1]
- else:
- parent_name_var = bits[1]
- nodelist = parser.parse()
- if nodelist.get_nodes_by_type(ExtendsNode):
- raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
- return ExtendsNode(nodelist, parent_name, parent_name_var)
-
-def do_include(parser, token):
- """
- Loads a template and renders it with the current context.
-
- Example::
-
- {% include "foo/some_include" %}
- """
-
- bits = token.contents.split()
- if len(bits) != 2:
- raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
- path = bits[1]
- if path[0] in ('"', "'") and path[-1] == path[0]:
- return ConstantIncludeNode(path[1:-1])
- return IncludeNode(bits[1])
-
-register_tag('block', do_block)
-register_tag('extends', do_extends)
-register_tag('include', do_include)
+add_to_builtins('django.core.template.loader_tags')
diff --git a/django/core/template/loader_tags.py b/django/core/template/loader_tags.py
new file mode 100644
index 0000000000..20d794c473
--- /dev/null
+++ b/django/core/template/loader_tags.py
@@ -0,0 +1,172 @@
+from django.core.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
+from django.core.template import Library, Context, Node
+from django.core.template.loader import get_template, get_template_from_string, find_template_source
+from django.conf.settings import TEMPLATE_DEBUG
+register = Library()
+
+class ExtendsError(Exception):
+ pass
+
+class BlockNode(Node):
+ def __init__(self, name, nodelist, parent=None):
+ self.name, self.nodelist, self.parent = name, nodelist, parent
+
+ def __repr__(self):
+ return "" % (self.name, self.nodelist)
+
+ def render(self, context):
+ context.push()
+ # Save context in case of block.super().
+ self.context = context
+ context['block'] = self
+ result = self.nodelist.render(context)
+ context.pop()
+ return result
+
+ def super(self):
+ if self.parent:
+ return self.parent.render(self.context)
+ return ''
+
+ def add_parent(self, nodelist):
+ if self.parent:
+ self.parent.add_parent(nodelist)
+ else:
+ self.parent = BlockNode(self.name, nodelist)
+
+class ExtendsNode(Node):
+ def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+ self.nodelist = nodelist
+ self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
+ self.template_dirs = template_dirs
+
+ def get_parent(self, context):
+ if self.parent_name_expr:
+ self.parent_name = self.parent_name_expr.resolve(context)
+ parent = self.parent_name
+ if not parent:
+ error_msg = "Invalid template name in 'extends' tag: %r." % parent
+ if self.parent_name_expr:
+ error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
+ raise TemplateSyntaxError, error_msg
+ try:
+ return get_template_from_string(*find_template_source(parent, self.template_dirs))
+ except TemplateDoesNotExist:
+ raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
+
+ def render(self, context):
+ compiled_parent = self.get_parent(context)
+ parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
+ parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
+ for block_node in self.nodelist.get_nodes_by_type(BlockNode):
+ # Check for a BlockNode with this node's name, and replace it if found.
+ try:
+ parent_block = parent_blocks[block_node.name]
+ except KeyError:
+ # This BlockNode wasn't found in the parent template, but the
+ # parent block might be defined in the parent's *parent*, so we
+ # add this BlockNode to the parent's ExtendsNode nodelist, so
+ # it'll be checked when the parent node's render() is called.
+ if parent_is_child:
+ compiled_parent.nodelist[0].nodelist.append(block_node)
+ else:
+ # Keep any existing parents and add a new one. Used by BlockNode.
+ parent_block.parent = block_node.parent
+ parent_block.add_parent(parent_block.nodelist)
+ parent_block.nodelist = block_node.nodelist
+ return compiled_parent.render(context)
+
+class ConstantIncludeNode(Node):
+ def __init__(self, template_path):
+ try:
+ t = get_template(template_path)
+ self.template = t
+ except:
+ if TEMPLATE_DEBUG:
+ pass
+# raise
+ self.template = None
+
+ def render(self, context):
+ if self.template:
+ return self.template.render(context)
+ else:
+ return ''
+
+class IncludeNode(Node):
+ def __init__(self, template_name):
+ self.template_name = template_name
+
+ def render(self, context):
+ try:
+ template_name = resolve_variable(self.template_name, context)
+ t = get_template(template_name)
+ return t.render(context)
+ except TemplateSyntaxError, e:
+ if TEMPLATE_DEBUG:
+ raise
+ return ''
+ except:
+ return '' # Fail silently for invalid included templates.
+
+def do_block(parser, token):
+ """
+ Define a block that can be overridden by child templates.
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
+ block_name = bits[1]
+ # Keep track of the names of BlockNodes found in this template, so we can
+ # check for duplication.
+ try:
+ if block_name in parser.__loaded_blocks:
+ raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
+ parser.__loaded_blocks.append(block_name)
+ except AttributeError: # parser._loaded_blocks isn't a list yet
+ parser.__loaded_blocks = [block_name]
+ nodelist = parser.parse(('endblock',))
+ parser.delete_first_token()
+ return BlockNode(block_name, nodelist)
+
+def do_extends(parser, token):
+ """
+ Signal that this template extends a parent template.
+
+ This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
+ uses the literal value "base" as the name of the parent template to extend,
+ or ``{% extends variable %}`` uses the value of ``variable`` as the name
+ of the parent template to extend.
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
+ parent_name, parent_name_expr = None, None
+ if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
+ parent_name = bits[1][1:-1]
+ else:
+ parent_name_expr = parser.compile_filter(bits[1])
+ nodelist = parser.parse()
+ if nodelist.get_nodes_by_type(ExtendsNode):
+ raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
+ return ExtendsNode(nodelist, parent_name, parent_name_expr)
+
+def do_include(parser, token):
+ """
+ Loads a template and renders it with the current context.
+
+ Example::
+
+ {% include "foo/some_include" %}
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
+ path = bits[1]
+ if path[0] in ('"', "'") and path[-1] == path[0]:
+ return ConstantIncludeNode(path[1:-1])
+ return IncludeNode(bits[1])
+
+register.tag('block', do_block)
+register.tag('extends', do_extends)
+register.tag('include', do_include)
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index e6ef3b062e..7c2019cac0 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -1,9 +1,11 @@
-from django.core.template import Node, NodeList, Template, Context, resolve_variable, resolve_variable_with_filters, registered_filters
-from django.core.template import TemplateSyntaxError, register_tag, TokenParser
+from django.core.template import Node, NodeList, Template, Context, resolve_variable
+from django.core.template import TemplateSyntaxError, TokenParser, Library
from django.core.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
from django.utils import translation
import re, sys
+register = Library()
+
class GetAvailableLanguagesNode(Node):
def __init__(self, variable):
self.variable = variable
@@ -53,10 +55,10 @@ class BlockTranslateNode(Node):
def render(self, context):
context.push()
for var,val in self.extra_context.items():
- context[var] = resolve_variable_with_filters(val, context)
+ context[var] = val.resolve(context)
singular = self.render_token_list(self.singular)
if self.plural and self.countervar and self.counter:
- count = resolve_variable_with_filters(self.counter, context)
+ count = self.counter.resolve(context)
context[self.countervar] = count
plural = self.render_token_list(self.plural)
result = translation.ngettext(singular, plural, count) % context
@@ -179,9 +181,9 @@ def do_block_translate(parser, token):
value = self.value()
if self.tag() != 'as':
raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'"
- extra_context[self.tag()] = value
+ extra_context[self.tag()] = parser.compile_filter(value)
elif tag == 'count':
- counter = self.value()
+ counter = parser.compile_filter(self.value())
if self.tag() != 'as':
raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'"
countervar = self.tag()
@@ -213,7 +215,7 @@ def do_block_translate(parser, token):
return BlockTranslateNode(extra_context, singular, plural, countervar, counter)
-register_tag('get_available_languages', do_get_available_languages)
-register_tag('get_current_language', do_get_current_language)
-register_tag('trans', do_translate)
-register_tag('blocktrans', do_block_translate)
+register.tag('get_available_languages', do_get_available_languages)
+register.tag('get_current_language', do_get_current_language)
+register.tag('trans', do_translate)
+register.tag('blocktrans', do_block_translate)
diff --git a/docs/templates.txt b/docs/templates.txt
index 53df12e6a0..8c673e1d1c 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -278,6 +278,11 @@ In the above, the ``load`` tag loads the ``comments`` tag library, which then
makes the ``comment_form`` tag available for use. Consult the documentation
area in your admin to find the list of custom libraries in your installation.
+**New in Django development version:** The ``{% load %}`` tag can take multiple
+library names, separated by spaces. Example::
+
+ {% load comments i18n %}
+
Built-in tag and filter reference
=================================
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 48c17c63ae..790b580bd6 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -444,6 +444,15 @@ the given Python module name, not the name of the app.
Once you've created that Python module, you'll just have to write a bit of
Python code, depending on whether you're writing filters or tags.
+To be a valid tag library, the module contain a module-level variable that is a
+``template.Library`` instance, in which all the tags and filters are
+registered. So, near the top of your module, put the following::
+
+ from django.core import template
+ register = template.Library()
+
+Convention is to call this instance ``register``.
+
.. admonition:: Behind the scenes
For a ton of examples, read the source code for Django's default filters
@@ -453,10 +462,16 @@ Python code, depending on whether you're writing filters or tags.
Writing custom template filters
-------------------------------
-Custom filters are just Python functions that take two arguments:
+**This section applies to the Django development version.**
- * The value of the variable (input) -- not necessarily a string
- * The value of the argument -- always a string
+Custom filters are just Python functions that take one or two arguments:
+
+ * The value of the variable (input) -- not necessarily a string.
+ * The value of the argument -- this can have a default value, or be left
+ out altogether.
+
+For example, in the filter ``{{ var|foo:"bar" }}``, the filter ``foo`` would be
+passed the variable ``var`` and the argument ``"bar"``.
Filter functions should always return something. They shouldn't raise
exceptions. They should fail silently. In case of error, they should return
@@ -468,36 +483,48 @@ Here's an example filter definition::
"Removes all values of arg from the given string"
return value.replace(arg, '')
-Most filters don't take arguments. For filters that don't take arguments, the
-convention is to use a single underscore as the second argument to the filter
-definition. Example::
+And here's an example of how that filter would be used::
- def lower(value, _):
+ {{ somevariable|cut:"0" }}
+
+Most filters don't take arguments. In this case, just leave the argument out of
+your function. Example::
+
+ def lower(value): # Only one argument.
"Converts a string into all lowercase"
return value.lower()
-When you've written your filter definition, you need to register it, to make it
-available to Django's template language::
+When you've written your filter definition, you need to register it with
+your ``Library`` instance, to make it available to Django's template language::
- from django.core import template
- template.register_filter('cut', cut, True)
- template.register_filter('lower', lower, False)
+ register.filter('cut', cut)
+ register.filter('lower', lower)
-``register_filter`` takes three arguments:
+The ``Library.filter()`` method takes two arguments:
1. The name of the filter -- a string.
2. The compilation function -- a Python function (not the name of the
function as a string).
- 3. A boolean, designating whether the filter requires an argument. This
- tells Django's template parser whether to throw ``TemplateSyntaxError``
- when filter arguments are given (or missing).
-The convention is to put all ``register_filter`` calls at the bottom of your
-template-library module.
+If you're using Python 2.4 or above, you can use ``register.filter()`` as a
+decorator instead::
+
+ @register.filter(name='cut')
+ def cut(value, arg):
+ return value.replace(arg, '')
+
+ @register.filter
+ def lower(value):
+ return value.lower()
+
+If you leave off the ``name`` argument, as in the second example above, Django
+will use the function's name as the filter name.
Writing custom template tags
----------------------------
+**This section applies to the Django development version.**
+
Tags are more complex than filters, because tags can do anything.
A quick overview
@@ -525,8 +552,6 @@ For each template tag the template parser encounters, it calls a Python
function with the tag contents and the parser object itself. This function is
responsible for returning a ``Node`` instance based on the contents of the tag.
-By convention, the name of each compilation function should start with ``do_``.
-
For example, let's write a template tag, ``{% current_time %}``, that displays
the current date/time, formatted according to a parameter given in the tag, in
`strftime syntax`_. It's a good idea to decide the tag syntax before anything
@@ -612,17 +637,32 @@ without having to be parsed multiple times.
Registering the tag
~~~~~~~~~~~~~~~~~~~
-Finally, use a ``register_tag`` call, as in ``register_filter`` above. Example::
+Finally, register the tag with your module's ``Library`` instance, as explained
+in "Writing custom template filters" above. Example::
- from django.core import template
- template.register_tag('current_time', do_current_time)
+ register.tag('current_time', do_current_time)
-``register_tag`` takes two arguments:
+The ``tag()`` method takes two arguments:
- 1. The name of the template tag -- a string.
+ 1. The name of the template tag -- a string. If this is left out, the
+ name of the compilation function will be used.
2. The compilation function -- a Python function (not the name of the
function as a string).
+As with filter registration, it is also possible to use this as a decorator, in
+Python 2.4 and above:
+
+ @register.tag(name="current_time")
+ def do_current_time(parser, token):
+ # ...
+
+ @register.tag
+ def shout(parser, token):
+ # ...
+
+If you leave off the ``name`` argument, as in the second example above, Django
+will use the function's name as the tag name.
+
Setting a variable in the context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
index d440e25dd5..539994ba84 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/othertests/defaultfilters.py
@@ -1,15 +1,15 @@
"""
->>> floatformat(7.7, None)
+>>> floatformat(7.7)
'7.7'
->>> floatformat(7.0, None)
+>>> floatformat(7.0)
'7'
->>> floatformat(0.7, None)
+>>> floatformat(0.7)
'0.7'
->>> floatformat(0.07, None)
+>>> floatformat(0.07)
'0.1'
->>> floatformat(0.007, None)
+>>> floatformat(0.007)
'0.0'
->>> floatformat(0.0, None)
+>>> floatformat(0.0)
'0'
"""
diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py
index b1ffb99c8a..d2d2203b17 100644
--- a/tests/othertests/markup.py
+++ b/tests/othertests/markup.py
@@ -1,19 +1,20 @@
# Quick tests for the markup templatetags (django.contrib.markup)
-from django.core.template import Template, Context
-import django.contrib.markup.templatetags.markup # this registers the filters
+from django.core.template import Template, Context, add_to_builtins
+
+add_to_builtins('django.contrib.markup.templatetags.markup')
# find out if markup modules are installed and tailor the test appropriately
try:
import textile
except ImportError:
textile = None
-
+
try:
import markdown
except ImportError:
markdown = None
-
+
try:
import docutils
except ImportError:
@@ -36,7 +37,7 @@ if textile:
Paragraph 2 with “quotes” and code
"""
else:
assert rendered == textile_content
-
+
### test markdown
markdown_content = """Paragraph 1
@@ -64,4 +65,4 @@ if docutils:
assert rendered =="""Paragraph 1
Paragraph 2 with a link
"""
else:
- assert rendered == rest_content
\ No newline at end of file
+ assert rendered == rest_content
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
index c1dbdde64f..86a67bea67 100644
--- a/tests/othertests/templates.py
+++ b/tests/othertests/templates.py
@@ -1,8 +1,12 @@
-import traceback
+from django.conf import settings
+
+# Turn TEMPLATE_DEBUG off, because tests assume that.
+settings.TEMPLATE_DEBUG = False
from django.core import template
from django.core.template import loader
from django.utils.translation import activate, deactivate, install
+import traceback
# Helper objects for template tests
class SomeClass:
@@ -99,8 +103,14 @@ TEMPLATE_TESTS = {
# Chained filters, with an argument to the first one
'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "Yes"}, "yes"),
- #Escaped string as argument
- 'basic-syntax30': (r"""{{ var|default_if_none:" endquote\" hah" }}""", {"var": None}, ' endquote" hah'),
+ # Escaped string as argument
+ 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
+
+ # Variable as argument
+ 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
+
+ # Default argument testing
+ 'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
### IF TAG ################################################################
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
@@ -289,7 +299,7 @@ TEMPLATE_TESTS = {
def test_template_loader(template_name, template_dirs=None):
"A custom template loader that loads the unit-test templates."
try:
- return ( TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name )
+ return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
except KeyError:
raise template.TemplateDoesNotExist, template_name
diff --git a/tests/testapp/templatetags/testtags.py b/tests/testapp/templatetags/testtags.py
index 7b755043fa..e9177d8c24 100644
--- a/tests/testapp/templatetags/testtags.py
+++ b/tests/testapp/templatetags/testtags.py
@@ -2,6 +2,8 @@
from django.core import template
+register = template.Library()
+
class EchoNode(template.Node):
def __init__(self, contents):
self.contents = contents
@@ -11,5 +13,5 @@ class EchoNode(template.Node):
def do_echo(parser, token):
return EchoNode(token.contents.split()[1:])
-
-template.register_tag("echo", do_echo)
\ No newline at end of file
+
+register.tag("echo", do_echo)
\ No newline at end of file