=== django/contrib/admin/templatetags/admin_list.py ================================================================== --- django/contrib/admin/templatetags/admin_list.py (revision 2929) +++ django/contrib/admin/templatetags/admin_list.py (local) @@ -65,7 +65,7 @@ 'ALL_VAR': ALL_VAR, '1': 1, } -pagination = register.inclusion_tag('admin/pagination.html')(pagination) +pagination = register.inclusion_tag(pagination, 'admin/pagination.html') def result_headers(cl): lookup_opts = cl.lookup_opts @@ -185,7 +185,7 @@ return {'cl': cl, 'result_headers': list(result_headers(cl)), 'results': list(results(cl))} -result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) +result_list = register.inclusion_tag(result_list, 'admin/change_list_results.html') def date_hierarchy(cl): if cl.lookup_opts.admin.date_hierarchy: @@ -245,7 +245,7 @@ 'title': year.year } for year in years] } -date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) +date_hierarchy = register.inclusion_tag(date_hierarchy, 'admin/date_hierarchy.html') def search_form(cl): return { @@ -253,12 +253,12 @@ 'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field, 'search_var': SEARCH_VAR } -search_form = register.inclusion_tag('admin/search_form.html')(search_form) +search_form = register.inclusion_tag(search_form, 'admin/search_form.html') def filter(cl, spec): return {'title': spec.title(), 'choices' : list(spec.choices(cl))} -filter = register.inclusion_tag('admin/filter.html')(filter) +filter = register.inclusion_tag(filter, 'admin/filter.html') def filters(cl): return {'cl': cl} -filters = register.inclusion_tag('admin/filters.html')(filters) +filters = register.inclusion_tag(filters, 'admin/filters.html') === django/contrib/admin/templatetags/admin_modify.py ================================================================== --- django/contrib/admin/templatetags/admin_modify.py (revision 2929) +++ django/contrib/admin/templatetags/admin_modify.py (local) @@ -22,9 +22,9 @@ include_admin_script = register.simple_tag(include_admin_script) def submit_row(context): - opts = context['opts'] - change = context['change'] - is_popup = context['is_popup'] + opts = context.get('opts') + change = context.get('change') + is_popup = context.get('is_popup') return { 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), @@ -35,7 +35,7 @@ 'show_save_and_continue': not is_popup and context['has_change_permission'], 'show_save': True } -submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) +submit_row = register.inclusion_tag(submit_row, 'admin/submit_line.html', takes_context=True) def field_label(bound_field): class_names = [] @@ -229,4 +229,4 @@ 'bound_fields': bound_fields, 'class_names': " ".join(class_names), } -admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line) +admin_field_line = register.inclusion_tag(admin_field_line, 'admin/field_line.html', takes_context=True) === django/contrib/admin/templatetags/adminapplist.py ================================================================== --- django/contrib/admin/templatetags/adminapplist.py (revision 2929) +++ django/contrib/admin/templatetags/adminapplist.py (local) @@ -61,4 +61,4 @@ raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0] return AdminApplistNode(tokens[2]) -register.tag('get_admin_app_list', get_admin_app_list) +register.tag(get_admin_app_list, name='get_admin_app_list') === django/contrib/admin/templatetags/log.py ================================================================== --- django/contrib/admin/templatetags/log.py (revision 2929) +++ django/contrib/admin/templatetags/log.py (local) @@ -50,4 +50,4 @@ raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None)) -register.tag('get_admin_log', DoGetAdminLog('get_admin_log')) +register.tag(DoGetAdminLog('get_admin_log'), name='get_admin_log') === django/contrib/admin/views/template.py ================================================================== --- django/contrib/admin/views/template.py (revision 2929) +++ django/contrib/admin/views/template.py (local) @@ -56,7 +56,7 @@ node.template_dirs = settings_module.TEMPLATE_DIRS return node register = template.Library() - register.tag('extends', new_do_extends) + register.tag(new_do_extends, name='extends') template.builtins.append(register) # Now validate the template using the new template dirs === django/contrib/comments/templatetags/comments.py ================================================================== --- django/contrib/comments/templatetags/comments.py (revision 2929) +++ django/contrib/comments/templatetags/comments.py (local) @@ -313,10 +313,10 @@ return CommentListNode(package, module, var_name, obj_id, tokens[5], self.free, ordering) # registration comments -register.tag('get_comment_list', DoGetCommentList(False)) -register.tag('comment_form', DoCommentForm(False)) -register.tag('get_comment_count', DoCommentCount(False)) +register.tag(DoGetCommentList(False), name='get_comment_list') +register.tag(DoCommentForm(False), name='comment_form') +register.tag(DoCommentCount(False), name='get_comment_count') # free comments -register.tag('get_free_comment_list', DoGetCommentList(True)) -register.tag('free_comment_form', DoCommentForm(True)) -register.tag('get_free_comment_count', DoCommentCount(True)) +register.tag(DoGetCommentList(True), name='get_free_comment_list') +register.tag(DoCommentForm(True), name='free_comment_form') +register.tag(DoCommentCount(True), name='get_free_comment_count') === django/template/__init__.py ================================================================== --- django/template/__init__.py (revision 2929) +++ django/template/__init__.py (local) @@ -54,6 +54,7 @@ >>> t.render(c) '\n\n\n\n' """ +import keyword import re from inspect import getargspec from django.utils.functional import curry @@ -76,6 +77,7 @@ VARIABLE_TAG_END = '}}' ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' +BOOLEANS = ['False', 'True'] # what to report as the origin for templates that come from non-loader sources # (e.g. strings) @@ -223,7 +225,7 @@ elif token.token_type == TOKEN_VAR: if not token.contents: self.empty_variable(token) - filter_expression = self.compile_filter(token.contents) + filter_expression = self.compile_filter(token.contents, default=settings.TEMPLATE_STRING_IF_INVALID) var_node = self.create_variable_node(filter_expression) self.extend_nodelist(nodelist, var_node,token) elif token.token_type == TOKEN_BLOCK: @@ -305,9 +307,9 @@ self.tags.update(lib.tags) self.filters.update(lib.filters) - def compile_filter(self,token): + def compile_filter(self, token, default=None): "Convenient wrapper for FilterExpression" - return FilterExpression(token, self) + return FilterExpression(token, self, default) def find_filter(self, filter_name): if self.filters.has_key(filter_name): @@ -492,13 +494,13 @@ This class should never be instantiated outside of the get_filters_from_token helper function. """ - def __init__(self, token, parser): + def __init__(self, token, parser, default=None): self.token = token - matches = filter_re.finditer(token) + self.default = default var = None filters = [] upto = 0 - for match in matches: + for match in filter_re.finditer(token): start = match.start() if upto != start: raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \ @@ -530,13 +532,13 @@ upto = match.end() if upto != len(token): raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] - self.var , self.filters = var, filters + self.var, self.filters = var, filters def resolve(self, context): try: obj = resolve_variable(self.var, context) except VariableDoesNotExist: - obj = settings.TEMPLATE_STRING_IF_INVALID + return self.default for func, args in self.filters: arg_vals = [] for lookup, arg in args: @@ -584,7 +586,8 @@ """ Returns the resolved variable, which may contain attribute syntax, within the given context. The variable may be a hard-coded string (if it begins - and ends with single or double quote marks). + and ends with single or double quote marks), a boolean literal, or an + integer or float literal. >>> c = {'article': {'section':'News'}} >>> resolve_variable('article.section', c) @@ -600,51 +603,110 @@ (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - if path[0] in '0123456789': + if path[0].isdigit(): number_type = '.' in path and float or int - try: - current = number_type(path) - except ValueError: - current = settings.TEMPLATE_STRING_IF_INVALID - elif path[0] in ('"', "'") and path[0] == path[-1]: - current = path[1:-1] - else: - current = context - bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR) - while bits: - try: # dictionary lookup - current = current[bits[0]] - except (TypeError, AttributeError, KeyError): - try: # attribute lookup - current = getattr(current, bits[0]) - if callable(current): - if getattr(current, 'alters_data', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - try: # method call (assuming no args required) - current = current() - except TypeError: # arguments *were* required - # GOTCHA: This will also catch any TypeError - # raised in the function itself. - current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call - except Exception, e: - if getattr(e, 'silent_variable_failure', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - raise - except (TypeError, AttributeError): - try: # list-index lookup - current = current[int(bits[0])] - except (IndexError, ValueError, KeyError): - raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute - except Exception, e: - if getattr(e, 'silent_variable_failure', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - raise - del bits[0] - return current + return number_type(path) + if path in BOOLEANS: + return bool(BOOLEANS.index(path)) + if path[0] in ('"', "'"): + if path[0] == path[-1]: + return path[1:-1] + raise VariableDoesNotExist, "Missing quotes in string literal %s" % (path,) + for bit in path.split(VARIABLE_ATTRIBUTE_SEPARATOR): + try: # dictionary lookup + context = context[bit] + except (TypeError, AttributeError, KeyError): + try: # attribute lookup + context = getattr(context, bit) + except (TypeError, AttributeError): + try: # list-index lookup + context = context[int(bit)] + except (IndexError, ValueError, KeyError): + raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bit, context) # missing attribute + else: + if callable(context): + if getattr(context, 'alters_data', False): + raise VariableDoesNotExist("Failed lookup for key [%s] in %r" % (bit, context)) + try: # method call (assuming no args required) + context = context() + except Exception, e: + if getattr(e, 'silent_variable_failure', False): + raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bit, context) # missing attribute + raise + return context +tag_tokens_re = re.compile(r""" +(?:\s*) # ignore preceeding whitespace +( # token group + (?:[A-Za-z_][0-9A-Za-z_]*=)?" # optional name and open double-quote + [^"\\]* # normal characters + (?:\\. # chew up backslash and following char + (?:|[^"\\])* # normal characters + )* # repeat as needed to chew up the backslashes + " # close double-quote +| + (?:[A-Za-z_][0-9A-Za-z_]*=)?' # optional name and open single-quote + [^'\\]* # normal characters + (?:\\. # chew up backslash and following char + (?:|[^'\\])* # normal characters + )* # repeat as needed to chew up the backslashes + ' # close single-quote +| + (?:[A-Za-z_][0-9A-Za-z_]*=)? # optional name + [^"'\s][^\s]* # unquoted token +) +(?:\s+|$) # chew up following ws until next token +""", re.VERBOSE) + +def tokenize_tag_contents(contents): + + """ Parse tag contents into (tag_name, args_to_resolve, kwds_to_resolve). + If an argument name is a Python keyword append an underscore. + + >>> tokenize_tag_contents("An 'easy' one.") + ('An', ["'easy'", 'one.'], {}) + >>> tokenize_tag_contents(r'''Please don't try 'thi\\'s at' named="hom\\"e."''') + ('Please', ["don't", 'try', "'thi\'s at'"], {'named': '"hom\\\\"e."'}) + """ + + bits = [] + i, n = 0, len(contents) + while i < n: + m = tag_tokens_re.match(contents, i) + if not m: + raise ValueError("Can't parse string %s at index %s" %(repr(contents), i)) + bit = m.group(1) + if bit.startswith('"'): + bit = bit.replace('\\"', '"') + elif bit.startswith("'"): + bit = bit.replace("\\'", "'") + bits.append(bit) + i = m.end() + tag_name = bits[0] + args_to_resolve = [] + kwds_to_resolve = {} + for bit in bits[1:]: + if bit[0] in ("'", '"'): + if kwds_to_resolve: + raise SyntaxError("template tag syntax error: non-keyword arguments must come before keyword arguments") + args_to_resolve.append(bit) + else: + try: + name, value = bit.split('=', 1) + if keyword.iskeyword(name): + name += '_' + kwds_to_resolve[name] = value + except ValueError: + if kwds_to_resolve: + raise SyntaxError("template tag syntax error: non-keyword arguments must come before keyword arguments") + args_to_resolve.append(bit) + return tag_name, args_to_resolve, kwds_to_resolve + +def resolve_tag_arguments(context, args_to_resolve, kwds_to_resolve): + resolved_args = [resolve_variable(var, context) for var in args_to_resolve] + resolved_kwds = dict([(name, resolve_variable(var, context)) for name, var in kwds_to_resolve.items()]) + return resolved_args, resolved_kwds + class Node: def render(self, context): "Return the node rendered as a string" @@ -738,122 +800,105 @@ raise return self.encode_output(output) -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) +class SimpleNode(Node): + def __init__(self, func, takes_context, block_nodelist, args_to_resolve, kwds_to_resolve): + self.func = func + self.takes_context = takes_context + self.block_nodelist = block_nodelist + self.args_to_resolve = args_to_resolve + self.kwds_to_resolve = kwds_to_resolve -class Library(object): - def __init__(self): - self.filters = {} - self.tags = {} + def render(self, context): + resolved_args, resolved_kwds = resolve_tag_arguments(context, self.args_to_resolve, self.kwds_to_resolve) + if self.block_nodelist: + resolved_args.insert(0, self.block_nodelist) + if self.takes_context: + resolved_args.insert(0, context) + return self.func(*resolved_args, **resolved_kwds) or '' - 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) +class SimpleNodeFactory(object): + def __init__(self, func, takes_context=False, takes_block=False): + self.func = func + self.__doc__ = func.__doc__ + self.takes_context = takes_context + self.takes_block = takes_block - def tag_function(self,func): - self.tags[func.__name__] = func - return func + def __call__(self, parser, token): + tag_name, args_to_resolve, kwds_to_resolve = tokenize_tag_contents(token.contents) + block_nodelist = None + if self.takes_block: + block_nodelist = parser.parse(('end' + tag_name,)) + parser.delete_first_token() + return SimpleNode(self.func, self.takes_context, block_nodelist, args_to_resolve, kwds_to_resolve) - 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 - return filter_func - else: - raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg) +class InclusionNode(Node): + def __init__(self, func, template, context_class, takes_context, args_to_resolve, kwds_to_resolve): + self.func = func + self.template = template + self.context_class = context_class + self.takes_context = takes_context + self.args_to_resolve = args_to_resolve + self.kwds_to_resolve = kwds_to_resolve - def filter_function(self, func): - self.filters[func.__name__] = func - return func + def render(self, context): + resolved_args, resolved_kwds = resolve_tag_arguments(context, self.args_to_resolve, self.kwds_to_resolve) + if self.takes_context: + resolved_args.insert(0, context) + if not getattr(self, 'nodelist', None): + from django.template.loader import get_template + t = get_template(self.template) + self.nodelist = t.nodelist + context = self.context_class(self.func(*resolved_args, **resolved_kwds)) + return self.nodelist.render(context) - def simple_tag(self,func): - (params, xx, xxx, defaults) = getargspec(func) +class InclusionNodeFactory(object): + def __init__(self, func, template, context_class=Context, takes_context=False): + self.func = func + self.__doc__ = func.__doc__ + self.template = template + self.context_class = context_class + self.takes_context = takes_context - class SimpleNode(Node): - def __init__(self, vars_to_resolve): - self.vars_to_resolve = vars_to_resolve + def __call__(self, parser, token): + tag_name, args_to_resolve, kwds_to_resolve = tokenize_tag_contents(token.contents) + return InclusionNode(self.func, self.template, self.context_class, self.takes_context, args_to_resolve, kwds_to_resolve) - def render(self, context): - resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] - return func(*resolved_vars) +class Library(object): + def __init__(self): + self.filters = {} + self.tags = {} - 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 tag(self, func=None, name=None): + if callable(func): + self.tags[name or func.__name__] = func + return func + elif func is None: + return curry(self.tag, name=name) + raise InvalidTemplateLibrary("Unsupported argument to Library.tag: (%r)" % (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'" + def filter(self, func=None, name=None): + if callable(func): + self.filters[name or func.__name__] = func + return func + elif func is None: + return curry(self.filter, name=name) + raise InvalidTemplateLibrary("Unsupported argument to Library.filter: (%r)" % (func,)) - class InclusionNode(Node): - def __init__(self, vars_to_resolve): - self.vars_to_resolve = vars_to_resolve + def simple_tag(self, func=None, takes_context=False, takes_block=False, name=None): + if callable(func): + self.tags[name or func.__name__] = SimpleNodeFactory(func, takes_context, takes_block) + return func + elif func is None: + return curry(self.simple_tag, takes_context=takes_context, takes_block=takes_block, name=name) + raise InvalidTemplateLibrary("Unsupported argument to Library.simple_tag: (%r)" % (func,)) - 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.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) + def inclusion_tag(self, func=None, template=None, context_class=Context, takes_context=False, name=None): + if callable(func): + self.tags[name or func.__name__] = InclusionNodeFactory(func, template, context_class, takes_context) return func - return dec + elif func is None: + return curry(self.inclusion_tag, template=template, context_class=context_class, takes_context=takes_context, name=name) + raise InvalidTemplateLibrary("Unsupported argument to Library.inclusion_tag: (%r)" % (func,)) def get_library(module_name): lib = libraries.get(module_name, None) === django/template/context.py ================================================================== --- django/template/context.py (revision 2929) +++ django/template/context.py (local) @@ -37,7 +37,7 @@ for d in self.dicts: if d.has_key(key): return d[key] - return settings.TEMPLATE_STRING_IF_INVALID + raise KeyError("Context key not found: %s" % key) def __delitem__(self, key): "Delete a variable from the current context" @@ -49,7 +49,7 @@ return True return False - def get(self, key, otherwise): + def get(self, key, otherwise=None): for d in self.dicts: if d.has_key(key): return d[key] === django/template/defaultfilters.py ================================================================== --- django/template/defaultfilters.py (revision 2929) +++ django/template/defaultfilters.py (local) @@ -469,7 +469,7 @@ register.filter(removetags) register.filter(random) register.filter(rjust) -register.filter('slice', slice_) +register.filter(slice_, name='slice') register.filter(slugify) register.filter(stringformat) register.filter(striptags) === django/template/defaulttags.py ================================================================== --- django/template/defaulttags.py (revision 2929) +++ django/template/defaulttags.py (local) @@ -45,7 +45,10 @@ def render(self, context): for var in self.vars: - value = resolve_variable(var, context) + try: + value = resolve_variable(var, context) + except VariableDoesNotExist: + continue if value: return str(value) return '' @@ -142,8 +145,14 @@ return "" def render(self, context): - val1 = resolve_variable(self.var1, context) - val2 = resolve_variable(self.var2, context) + try: + val1 = resolve_variable(self.var1, context) + except VariableDoesNotExist: + val1 = None + try: + val2 = resolve_variable(self.var2, context) + except VariableDoesNotExist: + val2 = None if (self.negate and val1 != val2) or (not self.negate and val1 == val2): return self.nodelist_true.render(context) return self.nodelist_false.render(context) @@ -187,7 +196,7 @@ def render(self, context): obj_list = self.target.resolve(context) - if obj_list == '': # target_var wasn't found in context; fail silently + if obj_list is None: context[self.var_name] = [] return '' output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]} @@ -391,7 +400,7 @@ nodelist = parser.parse(('endfilter',)) parser.delete_first_token() return FilterNode(filter_expr, nodelist) -filter = register.tag("filter", do_filter) +filter = register.tag(do_filter, name='filter') #@register.tag def firstof(parser, token): @@ -469,7 +478,7 @@ nodelist_loop = parser.parse(('endfor',)) parser.delete_first_token() return ForNode(loopvar, sequence, reversed, nodelist_loop) -do_for = register.tag("for", do_for) +do_for = register.tag(do_for, name='for') def do_ifequal(parser, token, negate): """ @@ -579,7 +588,7 @@ else: nodelist_false = NodeList() return IfNode(boolvars, nodelist_true, nodelist_false) -do_if = register.tag("if", do_if) +do_if = register.tag(do_if, name='if') #@register.tag def ifchanged(parser, token): === django/template/loader_tags.py ================================================================== --- django/template/loader_tags.py (revision 2929) +++ django/template/loader_tags.py (local) @@ -169,6 +169,6 @@ return ConstantIncludeNode(path[1:-1]) return IncludeNode(bits[1]) -register.tag('block', do_block) -register.tag('extends', do_extends) -register.tag('include', do_include) +register.tag(do_block, name='block') +register.tag(do_extends, name='extends') +register.tag(do_include, name='include') === django/templatetags/i18n.py ================================================================== --- django/templatetags/i18n.py (revision 2929) +++ django/templatetags/i18n.py (local) @@ -215,7 +215,7 @@ 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(do_get_available_languages, name='get_available_languages') +register.tag(do_get_current_language, name='get_current_language') +register.tag(do_translate, name='trans') +register.tag(do_block_translate, name='blocktrans') === docs/templates_python.txt ================================================================== --- docs/templates_python.txt (revision 2929) +++ docs/templates_python.txt (local) @@ -561,35 +561,36 @@ Most filters don't take arguments. In this case, just leave the argument out of your function. Example:: - def lower(value): # Only one argument. + def do_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 with your ``Library`` instance, to make it available to Django's template language:: - register.filter('cut', cut) - register.filter('lower', lower) + register.filter(cut) + register.filter(do_lower, name='lower') -The ``Library.filter()`` method takes two arguments: +The ``Library.filter()`` method takes the following arguments: - 1. The name of the filter -- a string. - 2. The compilation function -- a Python function (not the name of the + 1. The filter function -- a Python function (not the name of the function as a string). + 2. An optional alternative name for the filter -- a string). By default + it is the same as the function name. If you're using Python 2.4 or above, you can use ``register.filter()`` as a decorator instead:: - @register.filter(name='cut') + @register.filter def cut(value, arg): return value.replace(arg, '') - @register.filter - def lower(value): + @register.filter(name=lower) + def do_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. +If you use the filter decorator without the ``name`` argument, as in the first +example above, Django will use the function's name as the filter name. Writing custom template tags ---------------------------- @@ -709,14 +710,14 @@ Finally, register the tag with your module's ``Library`` instance, as explained in "Writing custom template filters" above. Example:: - register.tag('current_time', do_current_time) + register.tag(do_current_time, name='current_time') The ``tag()`` method takes two arguments: - 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 + 1. The tag function -- a Python function (not the name of the function as a string). + 2. An optional alternative name for the template tag -- a string. If this + is left out, the name of the tag function will be used. As with filter registration, it is also possible to use this as a decorator, in Python 2.4 and above:: === tests/othertests/templates.py ================================================================== --- tests/othertests/templates.py (revision 2929) +++ tests/othertests/templates.py (local) @@ -1,6 +1,6 @@ from django.conf import settings +settings.TEMPLATE_STRING_IF_INVALID = "[undefined]" - from django import template from django.template import loader from django.utils.translation import activate, deactivate, install @@ -23,7 +23,7 @@ def do_echo(parser, token): return EchoNode(token.contents.split()[1:]) -register.tag("echo", do_echo) +register.tag(do_echo, name='echo') template.libraries['django.templatetags.testtags'] = register @@ -73,7 +73,7 @@ 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"), # Fail silently when a variable is not found in the current context - 'basic-syntax04': ("as{{ missing }}df", {}, "asdf"), + 'basic-syntax04': ("as{{ missing }}df", {}, "as%sdf" % settings.TEMPLATE_STRING_IF_INVALID), # A variable may not contain more than one word 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError), @@ -89,7 +89,7 @@ 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"), # Fail silently when a variable's attribute isn't found - 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ""), + 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "%s" % settings.TEMPLATE_STRING_IF_INVALID), # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError), @@ -105,10 +105,10 @@ 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"), # Fail silently when a variable's dictionary key isn't found - 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ""), + 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "%s" % settings.TEMPLATE_STRING_IF_INVALID), # Fail silently when accessing a non-simple method - 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ""), + 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "%s" % settings.TEMPLATE_STRING_IF_INVALID), # Basic filter usage 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), @@ -147,7 +147,7 @@ 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute - 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"), + 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1%s2" % settings.TEMPLATE_STRING_IF_INVALID), # In methods that raise an exception without a "silent_variable_attribute" set to True, # the exception propogates === tests/othertests/tokenize_tag_contents.py ================================================================== --- tests/othertests/tokenize_tag_contents.py (revision 2929) +++ tests/othertests/tokenize_tag_contents.py (local) @@ -0,0 +1,62 @@ +""" +# positional argument +>>> tok('tag arg1') +('tag', ['arg1'], {}) + +# single-quoted positional argument +>>> tok("tag 'arg1 with spaces'") +('tag', ["'arg1 with spaces'"], {}) + +# double-quoted positional argument +>>> tok('tag "arg1 with spaces"') +('tag', ['"arg1 with spaces"'], {}) + +# named argument +>>> tok('tag name1=arg1') +('tag', [], {'name1': 'arg1'}) + +# single-quoted named argument +>>> tok("tag name1='arg1 with spaces'") +('tag', [], {'name1': "'arg1 with spaces'"}) + +# double-quoted named argument +>>> tok('tag name1="arg1 with spaces"') +('tag', [], {'name1': '"arg1 with spaces"'}) + +# positional argument and named argument +>>> tok('tag arg1 name1=arg2') +('tag', ['arg1'], {'name1': 'arg2'}) + +# mixed arguments +>>> tok('tag arg1 "arg2 with spaces" name1=arg3 name2="arg4 with spaces"') +('tag', ['arg1', '"arg2 with spaces"'], {'name2': '"arg4 with spaces"', 'name1': 'arg3'}) + +# double quotes in single-quoted string +>>> tok("tag 'arg1 \\"with quotes\\"'") +('tag', ['\\'arg1 "with quotes"\\''], {}) + +# single quotes in double-quoted string +>>> tok('tag "arg1 \\'with quotes\\'"') +('tag', ['"arg1 \\'with quotes\\'"'], {}) + +# single quotes in single-quoted string +>>> tok("tag 'arg1 \\\\'with quotes\\\\''") +('tag', ["'arg1 'with quotes''"], {}) + +# double quotes in double-quoted string +>>> tok('tag "arg1 \\\\"with quotes\\\\""') +('tag', ['"arg1 "with quotes""'], {}) + +# positional argument after named argument not allowed +>>> tok('tag name1=arg1 arg2') +Traceback (most recent call last): +... +SyntaxError: template tag syntax error: non-keyword arguments must come before keyword arguments + +""" + +from django.template import tokenize_tag_contents as tok + +if __name__ == "__main__": + import doctest + doctest.testmod()