Django

Code

Changeset 1443

Show
Ignore:
Timestamp:
11/26/05 16:46:31 (4 years ago)
Author:
adrian
Message:

Fixed #911 -- Made template system scoped to the parser instead of the template module. Also changed the way tags/filters are registered and added support for multiple arguments to {% load %} tag. Thanks, rjwittams. This is a backwards-incompatible change for people who've created custom template tags or filters. See http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges for upgrade instructions.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/templates/admin/change_form.html

    r1434 r1443  
    11{% extends "admin/base_site" %} 
    2 {% load i18n %} 
    3 {% load admin_modify %} 
    4 {% load adminmedia %} 
     2{% load i18n admin_modify adminmedia %} 
    53{% block extrahead %} 
    64{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %} 
  • django/trunk/django/contrib/admin/templates/admin/change_list.html

    r1434 r1443  
    1 {% load admin_list %} 
    2 {% load i18n %} 
     1{% load adminmedia admin_list i18n %} 
    32{% extends "admin/base_site" %} 
    43{% block bodyclass %}change-list{% endblock %} 
  • django/trunk/django/contrib/admin/templates/admin/edit_inline_stacked.html

    r1434 r1443  
     1{% load admin_modify %} 
    12<fieldset class="module aligned"> 
    23   {% for fcw in bound_related_object.form_field_collection_wrappers %} 
  • django/trunk/django/contrib/admin/templates/admin/edit_inline_tabular.html

    r1434 r1443  
     1{% load admin_modify %} 
    12<fieldset class="module"> 
    23   <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table> 
  • django/trunk/django/contrib/admin/templates/admin/field_line.html

    r1434 r1443  
     1{% load admin_modify %} 
    12<div class="{{ class_names }}" > 
    23{% for bound_field in bound_fields %}{{ bound_field.html_error_list }}{% endfor %} 
  • django/trunk/django/contrib/admin/templates/admin/filter.html

    r1434 r1443  
     1{% load i18n %} 
    12<h3>{% blocktrans %} By {{ title }} {% endblocktrans %}</h3> 
    23<ul> 
  • django/trunk/django/contrib/admin/templates/admin/filters.html

    r1434 r1443  
     1{% load admin_list %} 
    12{% if cl.has_filters %}<div id="changelist-filter"> 
    23<h2>Filter</h2> 
  • django/trunk/django/contrib/admin/templates/admin/pagination.html

    r1434 r1443  
     1{% load admin_list %} 
    12<p class="paginator"> 
    23{% if pagination_required %} 
  • django/trunk/django/contrib/admin/templates/admin/search_form.html

    r1434 r1443  
     1{% load adminmedia %} 
    12{% if cl.lookup_opts.admin.search_fields %} 
    23<div id="toolbar"><form id="changelist-search" action="" method="get"> 
  • django/trunk/django/contrib/admin/templates/widget/default.html

    r1434 r1443  
    1 {% output_all bound_field.form_fields %} 
     1{% load admin_modify %}{% output_all bound_field.form_fields %} 
  • django/trunk/django/contrib/admin/templates/widget/file.html

    r1434 r1443  
    1 {% if bound_field.original_value %} 
     1{% load admin_modify %}{% if bound_field.original_value %} 
    22Currently: <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value }} </a><br /> 
    33Change: {% output_all bound_field.form_fields %} 
  • django/trunk/django/contrib/admin/templates/widget/foreign.html

    r1434 r1443  
     1{% load admin_modify adminmedia %} 
    12{% output_all bound_field.form_fields %} 
    23{% if bound_field.raw_id_admin %} 
     
    56{% if bound_field.needs_add_label %} 
    67            <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id}}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a> 
    7 {% endif %} {% endif %} 
     8{% endif %}{% endif %} 
  • django/trunk/django/contrib/admin/templatetags/adminapplist.py

    r948 r1443  
    11from django.core import template 
     2 
     3register = template.Library() 
    24 
    35class AdminApplistNode(template.Node): 
     
    5557    return AdminApplistNode(tokens[2]) 
    5658 
    57 template.register_tag('get_admin_app_list', get_admin_app_list) 
     59register.tag('get_admin_app_list', get_admin_app_list) 
  • django/trunk/django/contrib/admin/templatetags/admin_list.py

    r1434 r1443  
    44from django.core import meta, template 
    55from django.core.exceptions import ObjectDoesNotExist 
    6 from django.core.template.decorators import simple_tag, inclusion_tag 
    76from django.utils import dateformat 
    87from django.utils.html import strip_tags, escape 
     
    109from django.utils.translation import get_date_formats 
    1110from django.conf.settings import ADMIN_MEDIA_PREFIX 
     11from django.core.template import Library 
     12 
     13register = Library() 
    1214 
    1315DOT = '.' 
    1416 
    15 #@simple_tag 
     17#@register.simple_tag 
    1618def paginator_number(cl,i): 
    1719    if i == DOT: 
     
    2123    else: 
    2224       return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1) 
    23 paginator_number = simple_tag(paginator_number) 
    24  
    25 #@inclusion_tag('admin/pagination') 
     25paginator_number = register.simple_tag(paginator_number) 
     26 
     27#@register.inclusion_tag('admin/pagination') 
    2628def pagination(cl): 
    2729    paginator, page_num = cl.paginator, cl.page_num 
     
    6567        '1': 1, 
    6668    } 
    67 pagination = inclusion_tag('admin/pagination')(pagination) 
     69pagination = register.inclusion_tag('admin/pagination')(pagination) 
    6870 
    6971def result_headers(cl): 
     
    178180        yield list(items_for_result(cl,res)) 
    179181 
    180 #@inclusion_tag("admin/change_list_results") 
     182#@register.inclusion_tag("admin/change_list_results") 
    181183def result_list(cl): 
    182184    res = list(results(cl)) 
     
    184186            'result_headers': list(result_headers(cl)), 
    185187            'results': list(results(cl))} 
    186 result_list = inclusion_tag("admin/change_list_results")(result_list) 
    187  
    188 #@inclusion_tag("admin/date_hierarchy") 
     188result_list = register.inclusion_tag("admin/change_list_results")(result_list) 
     189 
     190#@register.inclusion_tag("admin/date_hierarchy") 
    189191def date_hierarchy(cl): 
    190192    lookup_opts, params, lookup_params, lookup_mod = \ 
     
    257259                } for year in years ] 
    258260            } 
    259 date_hierarchy = inclusion_tag('admin/date_hierarchy')(date_hierarchy) 
    260  
    261 #@inclusion_tag('admin/search_form') 
     261date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy) 
     262 
     263#@register.inclusion_tag('admin/search_form') 
    262264def search_form(cl): 
    263265    return { 
     
    266268        'search_var': SEARCH_VAR 
    267269    } 
    268 search_form = inclusion_tag('admin/search_form')(search_form) 
    269  
    270 #@inclusion_tag('admin/filter') 
     270search_form = register.inclusion_tag('admin/search_form')(search_form) 
     271 
     272#@register.inclusion_tag('admin/filter') 
    271273def filter(cl, spec): 
    272274    return {'title': spec.title(), 'choices' : list(spec.choices(cl))} 
    273 filter = inclusion_tag('admin/filter')(filter) 
    274  
    275 #@inclusion_tag('admin/filters') 
     275filter = register.inclusion_tag('admin/filter')(filter) 
     276 
     277#@register.inclusion_tag('admin/filters') 
    276278def filters(cl): 
    277279    return {'cl': cl} 
    278 filters = inclusion_tag('admin/filters')(filters) 
     280filters = register.inclusion_tag('admin/filters')(filters) 
  • django/trunk/django/contrib/admin/templatetags/adminmedia.py

    r1411 r1443  
    1 from django.core.template.decorators import simple_tag 
     1from django.core.template import Library 
     2register = Library() 
    23 
    34def admin_media_prefix(): 
     
    78        return '' 
    89    return ADMIN_MEDIA_PREFIX 
    9 admin_media_prefix = simple_tag(admin_media_prefix) 
     10admin_media_prefix = register.simple_tag(admin_media_prefix) 
  • django/trunk/django/contrib/admin/templatetags/admin_modify.py

    r1434 r1443  
    33from django.utils.text import capfirst 
    44from django.utils.functional import curry 
    5 from django.core.template.decorators import simple_tag, inclusion_tag 
    65from django.contrib.admin.views.main import AdminBoundField 
    76from django.core.meta.fields import BoundField, Field 
     
    109import re 
    1110 
     11register = template.Library() 
     12 
    1213word_re = re.compile('[A-Z][a-z]+') 
    1314 
     
    1516    return '_'.join([s.lower() for s in word_re.findall(name)[:-1]]) 
    1617 
    17 #@simple_tag 
     18#@register.simple_tag 
    1819def include_admin_script(script_path): 
    1920    return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path) 
    20 include_admin_script = simple_tag(include_admin_script) 
    21  
    22 #@inclusion_tag('admin/submit_line', takes_context=True) 
     21include_admin_script = register.simple_tag(include_admin_script) 
     22 
     23#@register.inclusion_tag('admin/submit_line', takes_context=True) 
    2324def submit_row(context, bound_manipulator): 
    2425    change = context['change'] 
     
    3738        'show_save': True 
    3839    } 
    39 submit_row = inclusion_tag('admin/submit_line', takes_context=True)(submit_row) 
    40  
    41 #@simple_tag 
     40submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row) 
     41 
     42#@register.simple_tag 
    4243def field_label(bound_field): 
    4344    class_names = [] 
     
    5455    return '<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \ 
    5556        capfirst(bound_field.field.verbose_name), colon) 
    56 field_label = simple_tag(field_label) 
     57field_label = register.simple_tag(field_label) 
    5758 
    5859class FieldWidgetNode(template.Node): 
     
    171172        return output 
    172173 
    173 #@simple_tag 
     174#@register.simple_tag 
    174175def output_all(form_fields): 
    175176    return ''.join([str(f) for f in form_fields]) 
    176 output_all = simple_tag(output_all) 
    177  
    178 #@simple_tag 
     177output_all = register.simple_tag(output_all) 
     178 
     179#@register.simple_tag 
    179180def auto_populated_field_script(auto_pop_fields, change = False): 
    180181    for field in auto_pop_fields: 
     
    192193                     f, field.name, add_values, field.maxlength)) 
    193194    return ''.join(t) 
    194 auto_populated_field_script = simple_tag(auto_populated_field_script) 
    195  
    196 #@simple_tag 
     195auto_populated_field_script = register.simple_tag(auto_populated_field_script) 
     196 
     197#@register.simple_tag 
    197198def filter_interface_script_maybe(bound_field): 
    198199    f = bound_field.field 
     
    203204    else: 
    204205        return '' 
    205 filter_interface_script_maybe = simple_tag(filter_interface_script_maybe) 
     206filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe) 
    206207 
    207208def do_one_arg_tag(node_factory, parser,token): 
     
    214215    tag_name = class_name_to_underscored(node.__name__) 
    215216    parse_func = curry(do_one_arg_tag, node) 
    216     template.register_tag(tag_name, parse_func) 
     217    register.tag(tag_name, parse_func) 
    217218 
    218219one_arg_tag_nodes = ( 
     
    224225    register_one_arg_tag(node) 
    225226 
    226 #@inclusion_tag('admin/field_line', takes_context=True) 
     227#@register.inclusion_tag('admin/field_line', takes_context=True) 
    227228def admin_field_line(context, argument_val): 
    228229    if (isinstance(argument_val, BoundField)): 
     
    250251        'class_names': " ".join(class_names), 
    251252    } 
    252 admin_field_line = inclusion_tag('admin/field_line', takes_context=True)(admin_field_line) 
    253  
    254 #@simple_tag 
     253admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line) 
     254 
     255#@register.simple_tag 
    255256def object_pk(bound_manip, ordered_obj): 
    256257    return bound_manip.get_ordered_object_pk(ordered_obj) 
    257258 
    258 object_pk = simple_tag(object_pk) 
     259object_pk = register.simple_tag(object_pk) 
  • django/trunk/django/contrib/admin/templatetags/log.py

    r948 r1443  
    11from django.models.admin import log 
    22from django.core import template 
     3 
     4register = template.Library() 
    35 
    46class AdminLogNode(template.Node): 
     
    4951        return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None)) 
    5052 
    51 template.register_tag('get_admin_log', DoGetAdminLog('get_admin_log')) 
     53register.tag('get_admin_log', DoGetAdminLog('get_admin_log')) 
  • django/trunk/django/contrib/admin/views/template.py

    r948 r1443  
    5151 
    5252        # so that inheritance works in the site's context, register a new function 
    53         # for "extends" that uses the site's TEMPLATE_DIR instead 
     53        # for "extends" that uses the site's TEMPLATE_DIRS instead. 
    5454        def new_do_extends(parser, token): 
    5555            node = loader.do_extends(parser, token) 
    5656            node.template_dirs = settings_module.TEMPLATE_DIRS 
    5757            return node 
    58         template.register_tag('extends', new_do_extends) 
     58        register = template.Library() 
     59        register.tag('extends', new_do_extends) 
     60        template.builtins.append(register) 
    5961 
    60         # now validate the template using the new template dirs 
    61         # making sure to reset the extends function in any case 
     62        # Now validate the template using the new template dirs 
     63        # making sure to reset the extends function in any case. 
    6264        error = None 
    6365        try: 
     
    6668        except template.TemplateSyntaxError, e: 
    6769            error = e 
    68         template.register_tag('extends', loader.do_extends
     70        template.builtins.remove(register
    6971        if error: 
    7072            raise validators.ValidationError, e.args 
  • django/trunk/django/contrib/comments/templatetags/comments.py

    r1378 r1443  
    66from django.models.core import contenttypes 
    77import re 
     8 
     9register = template.Library() 
    810 
    911COMMENT_FORM = ''' 
     
    361363 
    362364# registration comments 
    363 template.register_tag('get_comment_list', DoGetCommentList(False)) 
    364 template.register_tag('comment_form', DoCommentForm(False)) 
    365 template.register_tag('get_comment_count', DoCommentCount(False)) 
     365register.tag('get_comment_list', DoGetCommentList(False)) 
     366register.tag('comment_form', DoCommentForm(False)) 
     367register.tag('get_comment_count', DoCommentCount(False)) 
    366368# free comments 
    367 template.register_tag('get_free_comment_list', DoGetCommentList(True)) 
    368 template.register_tag('free_comment_form', DoCommentForm(True)) 
    369 template.register_tag('get_free_comment_count', DoCommentCount(True)) 
     369register.tag('get_free_comment_list', DoGetCommentList(True)) 
     370register.tag('free_comment_form', DoCommentForm(True)) 
     371register.tag('get_free_comment_count', DoCommentCount(True)) 
  • django/trunk/django/contrib/markup/templatetags/markup.py

    r467 r1443  
    55    * Textile, which requires the PyTextile library available at 
    66      http://dealmeida.net/projects/textile/ 
    7        
     7 
    88    * Markdown, which requires the Python-markdown library from 
    99      http://www.freewisdom.org/projects/python-markdown 
    10        
     10 
    1111    * ReStructuredText, which requires docutils from http://docutils.sf.net/ 
    12      
     12 
    1313In each case, if the required library is not installed, the filter will 
    1414silently fail and return the un-marked-up text. 
     
    1717from django.core import template 
    1818 
    19 def textile(value, _): 
     19register = template.Library() 
     20 
     21def textile(value): 
    2022    try: 
    2123        import textile 
     
    2426    else: 
    2527        return textile.textile(value) 
    26          
    27 def markdown(value, _): 
     28 
     29def markdown(value): 
    2830    try: 
    2931        import markdown 
     
    3234    else: 
    3335        return markdown.markdown(value) 
    34          
    35 def restructuredtext(value, _): 
     36 
     37def restructuredtext(value): 
    3638    try: 
    3739        from docutils.core import publish_parts 
     
    4143        parts = publish_parts(source=value, writer_name="html4css1") 
    4244        return parts["fragment"] 
    43          
    44 template.register_filter("textile", textile, False) 
    45 template.register_filter("markdown", markdown, False
    46 template.register_filter("restructuredtext", restructuredtext, False
     45 
     46register.filter(textile) 
     47register.filter(markdown
     48register.filter(restructuredtext
  • django/trunk/django/core/template/defaultfilters.py

    r1050 r1443  
    11"Default variable filters" 
    22 
    3 from django.core.template import register_filter, resolve_variable 
     3from django.core.template import resolve_variable, Library 
     4from django.conf.settings import DATE_FORMAT, TIME_FORMAT 
    45import re 
    56import random as random_module 
    67 
     8register = Library() 
     9 
    710################### 
    811# STRINGS         # 
    912################### 
    1013 
    11 def addslashes(value, _): 
     14 
     15def addslashes(value): 
    1216    "Adds slashes - useful for passing strings to JavaScript, for example." 
    1317    return value.replace('"', '\\"').replace("'", "\\'") 
    1418 
    15 def capfirst(value, _): 
     19def capfirst(value): 
    1620    "Capitalizes the first character of the value" 
    1721    value = str(value) 
    1822    return value and value[0].upper() + value[1:] 
    1923 
    20 def fix_ampersands(value, _): 
     24def fix_ampersands(value): 
    2125    "Replaces ampersands with ``&amp;`` entities" 
    2226    from django.utils.html import fix_ampersands 
    2327    return fix_ampersands(value) 
    2428 
    25 def floatformat(text, _): 
     29def floatformat(text): 
    2630    """ 
    2731    Displays a floating point number as 34.2 (with one decimal place) -- but 
     
    3842        return '%d' % int(f) 
    3943 
    40 def linenumbers(value, _): 
     44def linenumbers(value): 
    4145    "Displays text with line numbers" 
    4246    from django.utils.html import escape 
     
    4852    return '\n'.join(lines) 
    4953 
    50 def lower(value, _): 
     54def lower(value): 
    5155    "Converts a string into all lowercase" 
    5256    return value.lower() 
    5357 
    54 def make_list(value, _): 
     58def make_list(value): 
    5559    """ 
    5660    Returns the value turned into a list. For an integer, it's a list of 
     
    5963    return list(str(value)) 
    6064 
    61 def slugify(value, _): 
     65def slugify(value): 
    6266    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens" 
    6367    value = re.sub('[^\w\s-]', '', value).strip().lower() 
     
    7882        return "" 
    7983 
    80 def title(value, _): 
     84def title(value): 
    8185    "Converts a string into titlecase" 
    8286    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 
     
    97101    return truncate_words(value, length) 
    98102 
    99 def upper(value, _): 
     103def upper(value): 
    100104    "Converts a string into all uppercase" 
    101105    return value.upper() 
    102106 
    103 def urlencode(value, _): 
     107def urlencode(value): 
    104108    "Escapes a value for use in a URL" 
    105109    import urllib 
    106110    return urllib.quote(value) 
    107111 
    108 def urlize(value, _): 
     112def urlize(value): 
    109113    "Converts URLs in plain text into clickable links" 
    110114    from django.utils.html import urlize 
     
    120124    return urlize(value, trim_url_limit=int(limit), nofollow=True) 
    121125 
    122 def wordcount(value, _): 
     126def wordcount(value): 
    123127    "Returns the number of words" 
    124128    return len(value.split()) 
     
    161165################### 
    162166 
    163 def escape(value, _): 
     167def escape(value): 
    164168    "Escapes a string's HTML" 
    165169    from django.utils.html import escape 
    166170    return escape(value) 
    167171 
    168 def linebreaks(value, _): 
     172def linebreaks(value): 
    169173    "Converts newlines into <p> and <br />s" 
    170174    from django.utils.html import linebreaks 
    171175    return linebreaks(value) 
    172176 
    173 def linebreaksbr(value, _): 
     177def linebreaksbr(value): 
    174178    "Converts newlines into <br />s" 
    175179    return value.replace('\n', '<br />') 
     
    185189    return value 
    186190 
    187 def striptags(value, _): 
     191def striptags(value): 
    188192    "Strips all [X]HTML tags" 
    189193    from django.utils.html import strip_tags 
     
    215219    return [item[1] for item in decorated] 
    216220 
    217 def first(value, _): 
     221def first(value): 
    218222    "Returns the first item in a list" 
    219223    try: 
     
    229233        return value 
    230234 
    231 def length(value, _): 
     235def length(value): 
    232236    "Returns the length of the value - useful for lists" 
    233237    return len(value) 
     
    237241    return len(value) == int(arg) 
    238242 
    239 def random(value, _): 
     243def random(value): 
    240244    "Returns a random item from the list" 
    241245    return random_module.choice(value) 
     
    254258        return value # Fail silently. 
    255259 
    256 def unordered_list(value, _): 
     260def unordered_list(value): 
    257261    """ 
    258262    Recursively takes a self-nested list and returns an HTML unordered list -- 
     
    315319################### 
    316320 
    317 def date(value, arg): 
     321def date(value, arg=DATE_FORMAT): 
    318322    "Formats a date according to the given format" 
    319323    from django.utils.dateformat import format 
    320324    return format(value, arg) 
    321325 
    322 def time(value, arg): 
     326def time(value, arg=TIME_FORMAT): 
    323327    "Formats a time according to the given format" 
    324328    from django.utils.dateformat import time_format 
    325329    return time_format(value, arg) 
    326330 
    327 def timesince(value, _): 
     331def timesince(value): 
    328332    'Formats a date as the time since that date (i.e. "4 days, 6 hours")' 
    329333    from django.utils.timesince import timesince 
     
    348352    return int(value) % int(arg) == 0 
    349353 
    350 def yesno(value, arg): 
     354def yesno(value, arg=_("yes,no,maybe")): 
    351355    """ 
    352356    Given a string mapping values for true, false and (optionally) None, 
     
    380384################### 
    381385 
    382 def filesizeformat(bytes, _): 
     386def filesizeformat(bytes): 
    383387    """ 
    384388    Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 
     
    394398    return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) 
    395399 
    396 def pluralize(value, _): 
     400def pluralize(value): 
    397401    "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'" 
    398402    try: 
     
    409413    return '' 
    410414 
    411 def phone2numeric(value, _): 
     415def phone2numeric(value): 
    412416    "Takes a phone number and converts it in to its numerical equivalent" 
    413417    from django.utils.text import phone2numeric 
    414418    return phone2numeric(value) 
    415419 
    416 def pprint(value, _): 
     420def pprint(value): 
    417421    "A wrapper around pprint.pprint -- for debugging, really" 
    418422    from pprint import pformat 
    419423    return pformat(value) 
    420424 
    421 # Syntax: register_filter(name of filter, callback, has_argument
    422 register_filter('add', add, True
    423 register_filter('addslashes', addslashes, False
    424 register_filter('capfirst', capfirst, False
    425 register_filter('center', center, True
    426 register_filter('cut', cut, True
    427 register_filter('date', date, True) 
    428 register_filter('default', default, True
    429 register_filter('default_if_none', default_if_none, True) 
    430 register_filter('dictsort', dictsort, True
    431 register_filter('dictsortreversed', dictsortreversed, True
    432 register_filter('divisibleby', divisibleby, True
    433 register_filter('escape', escape, False) 
    434 register_filter('filesizeformat', filesizeformat, False
    435 register_filter('first', first, False
    436 register_filter('fix_ampersands', fix_ampersands, False
    437 register_filter('floatformat', floatformat, False
    438 register_filter('get_digit', get_digit, True
    439 register_filter('join', join, True
    440 register_filter('length', length, False
    441 register_filter('length_is', length_is, True
    442 register_filter('linebreaks', linebreaks, False
    443 register_filter('linebreaksbr', linebreaksbr, False
    444 register_filter('linenumbers', linenumbers, False
    445 register_filter('ljust', ljust, True
    446 register_filter('lower', lower, False
    447 register_filter('make_list', make_list, False
    448 register_filter('phone2numeric', phone2numeric, False
    449 register_filter('pluralize', pluralize, False) 
    450 register_filter('pprint', pprint, False
    451 register_filter('removetags', removetags, True
    452 register_filter('random', random, False
    453 register_filter('rjust', rjust, True
    454 register_filter('slice', slice_, True
    455 register_filter('slugify', slugify, False
    456 register_filter('stringformat', stringformat, True
    457 register_filter('striptags', striptags, False
    458 register_filter('time', time, True) 
    459 register_filter('timesince', timesince, False) 
    460 register_filter('title', title, False) 
    461 register_filter('truncatewords', truncatewords, True
    462 register_filter('unordered_list', unordered_list, False
    463 register_filter('upper', upper, False
    464 register_filter('urlencode', urlencode, False) 
    465 register_filter('urlize', urlize, False) 
    466 register_filter('urlizetrunc', urlizetrunc, True
    467 register_filter('wordcount', wordcount, False
    468 register_filter('wordwrap', wordwrap, True
    469 register_filter('yesno', yesno, True
     425# Syntax: register.filter(name of filter, callback
     426register.filter(add
     427register.filter(addslashes
     428register.filter(capfirst
     429register.filter(center
     430register.filter(cut
     431register.filter(date) 
     432register.filter(default
     433register.filter(default_if_none) 
     434register.filter(dictsort
     435register.filter(dictsortreversed
     436register.filter(divisibleby
     437register.filter(escape) 
     438register.filter(filesizeformat
     439register.filter(first
     440register.filter(fix_ampersands
     441register.filter(floatformat
     442register.filter(get_digit
     443register.filter(join
     444register.filter(length
     445register.filter(length_is
     446register.filter(linebreaks
     447register.filter(linebreaksbr
     448register.filter(linenumbers
     449register.filter(ljust
     450register.filter(lower
     451register.filter(make_list
     452register.filter(phone2numeric
     453register.filter(pluralize) 
     454register.filter(pprint
     455register.filter(removetags
     456register.filter(random
     457register.filter(rjust
     458register.filter(slice_
     459register.filter(slugify
     460register.filter(stringformat
     461register.filter(striptags
     462register.filter(time) 
     463register.filter(timesince) 
     464register.filter(title) 
     465register.filter(truncatewords
     466register.filter(unordered_list
     467register.filter(upper
     468register.filter(urlencode) 
     469register.filter(urlize) 
     470register.filter(urlizetrunc
     471register.filter(wordcount
     472register.filter(wordwrap
     473register.filter(yesno
  • django/trunk/django/core/template/defaulttags.py

    r1379 r1443  
    11"Default tags used by the template system, available to all templates." 
    22 
    3 from django.core.template import Node, NodeList, Template, Context, resolve_variable, resolve_variable_with_filters, get_filters_from_token, registered_filters 
    4 from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, register_tag 
     3from django.core.template import Node, NodeList, Template, Context, resolve_variable 
     4from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END 
     5from django.core.template import get_library, Library, InvalidTemplateLibrary 
    56import sys 
     7 
     8register = Library() 
    69 
    710class CommentNode(Node): 
     
    2831 
    2932class FilterNode(Node): 
    30     def __init__(self, filters, nodelist): 
    31         self.filters, self.nodelist = filters, nodelist 
     33    def __init__(self, filter_expr, nodelist): 
     34        self.filter_expr, self.nodelist = filter_expr, nodelist 
    3235 
    3336    def render(self, context): 
    3437        output = self.nodelist.render(context) 
    3538        # apply filters 
    36         for f in self.filters: 
    37             output = registered_filters[f[0]][0](output, f[1]) 
    38         return output 
     39        return self.filter_expr.resolve(Context({'var': output})) 
    3940 
    4041class FirstOfNode(Node): 
     
    8283        context.push() 
    8384        try: 
    84             values = resolve_variable_with_filters(self.sequence, context) 
     85            values = self.sequence.resolve(context) 
    8586        except VariableDoesNotExist: 
    8687            values = [] 
     
    148149 
    149150class IfNode(Node): 
    150     def __init__(self, boolvars, nodelist_true, nodelist_false): 
    151         self.boolvars = boolvars 
     151    def __init__(self, bool_exprs, nodelist_true, nodelist_false): 
     152        self.bool_exprs = bool_exprs 
    152153        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 
    153154 
     
    170171 
    171172    def render(self, context): 
    172         for ifnot, boolvar in self.boolvars: 
     173        for ifnot, bool_expr in self.bool_exprs: 
    173174            try: 
    174                 value = resolve_variable_with_filters(boolvar, context) 
     175                value = bool_expr.resolve(context) 
    175176            except VariableDoesNotExist: 
    176177                value = None 
     
    180181 
    181182class RegroupNode(Node): 
    182     def __init__(self, target_var, expression, var_name): 
    183         self.target_var, self.expression = target_var, expression 
     183    def __init__(self, target, expression, var_name): 
     184        self.target, self.expression = target, expression 
    184185        self.var_name = var_name 
    185186 
    186187    def render(self, context): 
    187         obj_list = resolve_variable_with_filters(self.target_var, context) 
     188        obj_list = self.target.resolve(context) 
    188189        if obj_list == '': # target_var wasn't found in context; fail silently 
    189190            context[self.var_name] = [] 
     
    191192        output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]} 
    192193        for obj in obj_list: 
    193             grouper = resolve_variable_with_filters('var.%s' % self.expression, \ 
    194                 Context({'var': obj})) 
     194            grouper = self.expression.resolve(Context({'var': obj})) 
    195195            # TODO: Is this a sensible way to determine equality? 
    196196            if output and repr(output[-1]['grouper']) == repr(grouper): 
     
    237237 
    238238class LoadNode(Node): 
    239     def __init__(self, taglib): 
    240         self.taglib = taglib 
    241  
    242     def load_taglib(taglib): 
    243         mod = __import__("django.templatetags.%s" % taglib.split('.')[-1], '', '', ['']) 
    244         reload(mod) 
    245         return mod 
    246     load_taglib = staticmethod(load_taglib) 
    247  
    248     def render(self, context): 
    249         "Import the relevant module" 
    250         try: 
    251             self.__class__.load_taglib(self.taglib) 
    252         except ImportError: 
    253             pass # Fail silently for invalid loads. 
     239    def render(self, context): 
    254240        return '' 
    255241 
     
    277263 
    278264class WidthRatioNode(Node): 
    279     def __init__(self, val_var, max_var, max_width): 
    280         self.val_var = val_va
    281         self.max_var = max_va
     265    def __init__(self, val_expr, max_expr, max_width): 
     266        self.val_expr = val_exp
     267        self.max_expr = max_exp
    282268        self.max_width = max_width 
    283269 
    284270    def render(self, context): 
    285271        try: 
    286             value = resolve_variable_with_filters(self.val_var, context) 
    287             maxvalue = resolve_variable_with_filters(self.max_var, context) 
     272            value = self.val_expr.resolve(context) 
     273            maxvalue = self.max_expr.resolve(context) 
    288274        except VariableDoesNotExist: 
    289275            return '' 
     
    296282        return str(int(round(ratio))) 
    297283 
    298 def do_comment(parser, token): 
     284#@register.tag 
     285def comment(parser, token): 
    299286    """ 
    300287    Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` 
     
    303290    parser.delete_first_token() 
    304291    return CommentNode() 
    305  
    306 def do_cycle(parser, token): 
     292comment = register.tag(comment) 
     293 
     294#@register.tag 
     295def cycle(parser, token): 
    307296    """ 
    308297    Cycle among the given strings each time this tag is encountered 
     
    370359    else: 
    371360        raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args) 
    372  
    373 def do_debug(parser, token): 
    374     "Print a whole load of debugging information, including the context and imported modules" 
    375     return DebugNode() 
    376  
     361cycle = register.tag(cycle) 
     362 
     363#@register.tag(name="filter") 
    377364def do_filter(parser, token): 
    378365    """ 
     
    389376    """ 
    390377    _, rest = token.contents.split(None, 1) 
    391     _, filters = get_filters_from_token('var|%s' % rest
     378    filter_expr = parser.compile_filter("var|%s" % (rest)
    392379    nodelist = parser.parse(('endfilter',)) 
    393380    parser.delete_first_token() 
    394     return FilterNode(filters, nodelist) 
    395  
    396 def do_firstof(parser, token): 
     381    return FilterNode(filter_expr, nodelist) 
     382filter = register.tag("filter", do_filter) 
     383 
     384#@register.tag 
     385def firstof(parser, token): 
    397386    """ 
    398387    Outputs the first variable passed that is not False. 
     
    420409        raise TemplateSyntaxError, "'firstof' statement requires at least one argument" 
    421410    return FirstOfNode(bits) 
    422  
    423  
     411firstof = register.tag(firstof) 
     412 
     413#@register.tag(name="for") 
    424414def do_for(parser, token): 
    425415    """ 
     
    463453        raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents 
    464454    loopvar = bits[1] 
    465     sequence = bits[3] 
     455    sequence = parser.compile_filter(bits[3]) 
    466456    reversed = (len(bits) == 5) 
    467457    nodelist_loop = parser.parse(('endfor',)) 
    468458    parser.delete_first_token() 
    469459    return ForNode(loopvar, sequence, reversed, nodelist_loop) 
     460do_for = register.tag("for", do_for) 
    470461 
    471462def do_ifequal(parser, token, negate): 
     
    498489    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) 
    499490 
     491#@register.tag 
     492def ifequal(parser, token): 
     493    return do_ifequal(parser, token, False) 
     494ifequal = register.tag(ifequal) 
     495 
     496#@register.tag 
     497def ifnotequal(parser, token): 
     498    return do_ifequal(parser, token, True) 
     499ifnotequal = register.tag(ifnotequal) 
     500 
     501#@register.tag(name="if") 
    500502def do_if(parser, token): 
    501503    """ 
     
    555557            if not_ != 'not': 
    556558                raise TemplateSyntaxError, "Expected 'not' in if statement" 
    557             boolvars.append((True, boolvar)) 
     559            boolvars.append((True, parser.compile_filter(boolvar))) 
    558560        else: 
    559             boolvars.append((False, boolpair)) 
     561            boolvars.append((False, parser.compile_filter(boolpair))) 
    560562    nodelist_true = parser.parse(('else', 'endif')) 
    561563    token = parser.next_token() 
     
    566568        nodelist_false = NodeList() 
    567569    return IfNode(boolvars, nodelist_true, nodelist_false) 
    568  
    569 def do_ifchanged(parser, token): 
     570do_if = register.tag("if", do_if) 
     571 
     572#@register.tag 
     573def ifchanged(parser, token): 
    570574    """ 
    571575    Check if a value has changed from the last iteration of a loop. 
     
    588592    parser.delete_first_token() 
    589593    return IfChangedNode(nodelist) 
    590  
    591 def do_ssi(parser, token): 
     594ifchanged = register.tag(ifchanged) 
     595 
     596#@register.tag 
     597def ssi(parser, token): 
    592598    """ 
    593599    Output the contents of a given file into the page. 
     
    614620            raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0] 
    615621    return SsiNode(bits[1], parsed) 
    616  
    617 def do_load(parser, token): 
     622ssi = register.tag(ssi) 
     623 
     624#@register.tag 
     625def load(parser, token): 
    618626    """ 
    619627    Load a custom template tag set. 
     
    624632    """ 
    625633    bits = token.contents.split() 
    626     if len(bits) != 2: 
    627         raise TemplateSyntaxError, "'load' statement takes one argument" 
    628     taglib = bits[1] 
    629     # check at compile time that the module can be imported 
    630     try: 
    631         LoadNode.load_taglib(taglib) 
    632     except ImportError, e: 
    633         raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e) 
    634     return LoadNode(taglib) 
    635  
    636 def do_now(parser, token): 
     634    for taglib in bits[1:]: 
     635        # add the library to the parser 
     636        try: 
     637            lib = get_library("django.templatetags.%s" % taglib.split('.')[-1]) 
     638            parser.add_library(lib) 
     639        except InvalidTemplateLibrary, e: 
     640            raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e) 
     641    return LoadNode() 
     642load = register.tag(load) 
     643 
     644#@register.tag 
     645def now(parser, token): 
    637646    """ 
    638647    Display the date, formatted according to the given string. 
     
    650659    format_string = bits[1] 
    651660    return NowNode(format_string) 
    652  
    653 def do_regroup(parser, token): 
     661now = register.tag(now) 
     662 
     663#@register.tag 
     664def regroup(parser, token): 
    654665    """ 
    655666    Regroup a list of alike objects by a common attribute. 
     
    700711    if len(firstbits) != 4: 
    701712        raise TemplateSyntaxError, "'regroup' tag takes five arguments" 
    702     target_var = firstbits[1] 
     713    target = parser.compile_filter(firstbits[1]) 
    703714    if firstbits[2] != 'by': 
    704715        raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'" 
     
    706717    if lastbits_reversed[1][::-1] != 'as': 
    707718        raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'" 
    708     expression = lastbits_reversed[2][::-1] 
     719 
     720    expression = parser.compile_filters('var.%s' % lastbits_reversed[2][::-1]) 
     721 
    709722    var_name = lastbits_reversed[0][::-1] 
    710     return RegroupNode(target_var, expression, var_name) 
    711  
    712 def do_templatetag(parser, token): 
     723    return RegroupNode(target, expression, var_name) 
     724regroup = register.tag(regroup) 
     725 
     726#@register.tag 
     727def templatetag(parser, token): 
    713728    """ 
    714729    Output one of the bits used to compose template tags. 
     
    736751            (tag, TemplateTagNode.mapping.keys()) 
    737752    return TemplateTagNode(tag) 
    738  
    739 def do_widthratio(parser, token): 
     753templatetag = register.tag(templatetag) 
     754 
     755@register.tag 
     756def widthratio(parser, token): 
    740757    """ 
    741758    For creating bar charts and such, this tag calculates the ratio of a given 
     
    753770    if len(bits) != 4: 
    754771        raise TemplateSyntaxError("widthratio takes three arguments") 
    755     tag, this_value_var, max_value_var, max_width = bits 
     772    tag, this_value_expr, max_value_expr, max_width = bits 
    756773    try: 
    757774        max_width = int(max_width) 
    758775    except ValueError: 
    759776        raise TemplateSyntaxError("widthratio final argument must be an integer") 
    760     return WidthRatioNode(this_value_var, max_value_var, max_width) 
    761  
    762 register_tag('comment', do_comment) 
    763 register_tag('cycle', do_cycle) 
    764 register_tag('debug', do_debug) 
    765 register_tag('filter', do_filter) 
    766 register_tag('firstof', do_firstof) 
    767 register_tag('for', do_for) 
    768 register_tag('ifequal', lambda parser, token: do_ifequal(parser, token, False)) 
    769 register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True)) 
    770 register_tag('if', do_if) 
    771 register_tag('ifchanged', do_ifchanged) 
    772 register_tag('regroup', do_regroup) 
    773 register_tag('ssi', do_ssi) 
    774 register_tag('load', do_load) 
    775 register_tag('now', do_now) 
    776 register_tag('templatetag', do_templatetag) 
    777 register_tag('widthratio', do_widthratio) 
     777    return WidthRatioNode(parser.compile_filter(this_value_expr), 
     778                          parser.compile_filter(max_value_expr), max_width) 
     779widthratio = register.tag(widthratio) 
  • django/trunk/django/core/template/__init__.py

    r1379 r1443  
    44How it works: 
    55 
    6 The tokenize() function converts a template string (i.e., a string containing 
     6The Lexer.tokenize() function converts a template string (i.e., a string containing 
    77markup with custom template tags) to tokens, which can be either plain text 
    88(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK). 
     
    5656""" 
    5757import re 
     58from inspect import getargspec 
     59from django.utils.functional import curry 
    5860from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG 
    5961 
     
    8385                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 
    8486 
    85 # global dict used by register_tag; maps custom tags to callback functions 
    86 registered_tags = {} 
    87  
    88 # global dict used by register_filter; maps custom filters to callback functions 
    89 registered_filters = {} 
     87# global dictionary of libraries that have been loaded using get_library 
     88libraries = {} 
     89# global list of libraries to load by default for a new parser 
     90builtins = [] 
    9091 
    9192class TemplateSyntaxError(Exception): 
     
    106107    pass 
    107108 
     109class InvalidTemplateLibrary(Exception): 
     110    pass 
     111 
    108112class Origin(object): 
    109113    def __init__(self, name): 
     
    111115 
    112116    def reload(self): 
    113         raise NotImplementedException 
     117        raise NotImplementedError 
    114118 
    115119    def __str__(self): 
     
    265269    def __init__(self, tokens): 
    266270        self.tokens = tokens 
     271        self.tags = {} 
     272        self.filters = {} 
     273        for lib in builtins: 
     274            self.add_library(lib) 
    267275 
    268276    def parse(self, parse_until=[]): 
     
    275283                if not token.contents: 
    276284                    self.empty_variable(token) 
    277                 var_node = self.create_variable_node(token.contents) 
     285                filter_expression = self.compile_filter(token.contents) 
     286                var_node = self.create_variable_node(filter_expression) 
    278287                self.extend_nodelist(nodelist, var_node,token) 
    279288            elif token.token_type == TOKEN_BLOCK: 
     
    289298                self.enter_command(command, token) 
    290299                try: 
    291                     compile_func = registered_tags[command] 
     300                    compile_func = self.tags[command] 
    292301                except KeyError: 
    293302                    self.invalid_block_tag(token, command) 
     
    303312        return nodelist 
    304313 
    305     def create_variable_node(self, contents): 
    306         return VariableNode(contents
     314    def create_variable_node(self, filter_expression): 
     315        return VariableNode(filter_expression
    307316 
    308317    def create_nodelist(self): 
     
    344353    def delete_first_token(self): 
    345354        del self.tokens[0] 
     355 
     356    def add_library(self, lib): 
     357        self.tags.update(lib.tags) 
     358        self.filters.update(lib.filters) 
     359 
     360    def compile_filter(self,token): 
     361        "Convenient wrapper for FilterExpression" 
     362        return FilterExpression(token, self) 
     363 
     364    def find_filter(self, filter_name): 
     365        if self.filters.has_key(filter_name): 
     366            return self.filters[filter_name] 
     367        else: 
     368            raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name 
    346369 
    347370class DebugParser(Parser): 
     
    484507             (?: 
    485508              %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 
    486               "(?P<arg>%(str)s)" 
     509              "(?P<constant_arg>%(str)s)"| 
     510              (?P<var_arg>[%(var_chars)s]+) 
    487511             ) 
    488512         )? 
     
    499523filter_re = re.compile(filter_raw_string) 
    500524 
    501 class FilterParser(object): 
     525class FilterExpression(object): 
    502526    """ 
    503527    Parses a variable token and its optional filters (all as a single string), 
     
    514538    get_filters_from_token helper function. 
    515539    """ 
    516     def __init__(self, token): 
     540    def __init__(self, token, parser): 
     541        self.token = token 
    517542        matches = filter_re.finditer(token) 
    518543        var = None 
     
    537562            else: 
    538563                filter_name = match.group("filter_name") 
    539                 arg, i18n_arg = match.group("arg","i18n_arg") 
     564                args = [] 
     565                constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg") 
    540566                if i18n_arg: 
    541                     arg =_(i18n_arg.replace('\\', '')) 
    542                 if arg: 
    543                     arg = arg.replace('\\', '') 
    544                 if not registered_filters.has_key(filter_name): 
    545                     raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name 
    546                 if registered_filters[filter_name][1] == True and arg is None: 
    547                     raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name 
    548                 if registered_filters[filter_name][1] == False and arg is not None: 
    549                     raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg) 
    550                 filters.append( (filter_name,arg) ) 
     567                    args.append((False, _(i18n_arg.replace('\\', '')))) 
     568                elif constant_arg: 
     569                    args.append((False, constant_arg.replace('\\', ''))) 
     570                elif var_arg: 
     571                    args.append((True, var_arg)) 
     572                filter_func = parser.find_filter(filter_name) 
     573                self.args_check(filter_name,filter_func, args) 
     574                filters.append( (filter_func,args)) 
    551575                upto = match.end() 
    552576        if upto != len(token): 
     
    554578        self.var , self.filters = var, filters 
    555579 
    556 def get_filters_from_token(token): 
    557     "Convenient wrapper for FilterParser" 
    558     p = FilterParser(token) 
    559     return (p.var, p.filters) 
     580    def resolve(self, context): 
     581        try: 
     582            obj = resolve_variable(self.var, context) 
     583        except VariableDoesNotExist: 
     584            obj = '' 
     585        for func, args in self.filters: 
     586            arg_vals = [] 
     587            for lookup, arg in args: 
     588                if not lookup: 
     589                    arg_vals.append(arg) 
     590                else: 
     591                    arg_vals.append(resolve_variable(arg, context)) 
     592            obj = func(obj, *arg_vals) 
     593        return obj 
     594 
     595    def args_check(name, func, provided): 
     596        provided = list(provided) 
     597        plen = len(provided) 
     598        (args, varargs, varkw, defaults) = getargspec(func) 
     599        # First argument is filter input. 
     600        args.pop(0) 
     601        if defaults: 
     602            nondefs = args[:-len(defaults)] 
     603        else: 
     604            nondefs = args 
     605        # Args without defaults must be provided. 
     606        try: 
     607            for arg in nondefs: 
     608                provided.pop(0) 
     609        except IndexError: 
     610            # Not enough 
     611            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 
     612 
     613        # Defaults can be overridden. 
     614        defaults = defaults and list(defaults) or [] 
     615        try: 
     616            for parg in provided: 
     617                defaults.pop(0) 
     618        except IndexError: 
     619            # Too many. 
     620            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 
     621 
     622        return True 
     623    args_check = staticmethod(args_check) 
     624 
     625    def __str__(self): 
     626        return self.token 
    560627 
    561628def resolve_variable(path, context): 
     
    608675    return current 
    609676 
    610 def resolve_variable_with_filters(var_string, context): 
    611     """ 
    612     var_string is a full variable expression with optional filters, like: 
    613         a.b.c|lower|date:"y/m/d" 
    614     This function resolves the variable in the context, applies all filters and 
    615     returns the object. 
    616     """ 
    617     var, filters = get_filters_from_token(var_string) 
    618     try: 
    619         obj = resolve_variable(var, context) 
    620     except VariableDoesNotExist: 
    621         obj = '' 
    622     for name, arg in filters: 
    623         obj = registered_filters[name][0](obj, arg) 
    624     return obj 
    625  
    626677class Node: 
    627678    def render(self, context): 
     
    688739 
    689740class VariableNode(Node): 
    690     def __init__(self, var_string): 
    691         self.var_string = var_string 
     741    def __init__(self, filter_expression): 
     742        self.filter_expression = filter_expression 
    692743 
    693744    def __repr__(self): 
    694         return "<Variable Node: %s>" % self.var_string 
     745        return "<Variable Node: %s>" % self.filter_expression 
    695746 
    696747    def encode_output(self, output): 
     
    704755 
    705756    def render(self, context): 
    706         output = resolve_variable_with_filters(self.var_string, context) 
     757        output = self.filter_expression.resolve(context) 
    707758        return self.encode_output(output) 
    708759 
     
    710761    def render(self, context): 
    711762        try: 
    712              output = resolve_variable_with_filters(self.var_string, context) 
     763             output = self.filter_expression.resolve(context) 
    713764        except TemplateSyntaxError, e: 
    714765            if not hasattr(e, 'source'): 
     
    717768        return self.encode_output(output) 
    718769 
    719 def register_tag(token_command, callback_function): 
    720     registered_tags[token_command] = callback_function 
    721  
    722 def unregister_tag(token_command): 
    723     del registered_tags[token_command] 
    724  
    725 def register_filter(filter_name, callback_function, has_arg): 
    726     registered_filters[filter_name] = (callback_function, has_arg) 
    727  
    728 def unregister_filter(filter_name): 
    729     del registered_filters[filter_name] 
    730  
    731 import defaulttags 
    732 import defaultfilters 
     770def generic_tag_compiler(params, defaults, name, node_class, parser, token): 
     771    "Returns a template.Node subclass." 
     772    bits = token.contents.split()[1:] 
     773    bmax = len(params) 
     774    def_len = defaults and len(defaults) or 0 
     775    bmin = bmax - def_len 
     776    if(len(bits) < bmin or len(bits) > bmax): 
     777        if bmin == bmax: 
     778            message = "%s takes %s arguments" % (name, bmin) 
     779        else: 
     780            message = "%s takes between %s and %s arguments" % (name, bmin, bmax) 
     781        raise TemplateSyntaxError, message 
     782    return node_class(bits) 
     783 
     784class Library(object): 
     785    def __init__(self): 
     786        self.filters = {} 
     787        self.tags = {} 
     788 
     789    def tag(self, name = None, compile_function = None): 
     790        if name == None and compile_function == None: 
     791            # @register.tag() 
     792            return self.tag_function 
     793        elif name != None and compile_function == None: 
     794            if(callable(name)): 
     795                # @register.tag 
     796                return self.tag_function(name) 
     797            else: 
     798                # @register.tag('somename') or @register.tag(name='somename') 
     799                def dec(func): 
     800                    return self.tag(name, func) 
     801                return dec 
     802        elif name != None and compile_function != None: 
     803            # register.tag('somename', somefunc) 
     804            self.tags[name] = compile_function 
     805            return compile_function 
     806        else: 
     807            raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function) 
     808 
     809    def tag_function(self,func): 
     810        self.tags[func.__name__] = func 
     811        return func 
     812 
     813    def filter(self, name = None, filter_func = None): 
     814        if name == None and filter_func == None: 
     815            # @register.filter() 
     816            return self.filter_function 
     817        elif filter_func == None: 
     818            if(callable(name)): 
     819                # @register.filter 
     820                return self.filter_function(name) 
     821            else: 
     822                # @register.filter('somename') or @register.filter(name='somename') 
     823                def dec(func): 
     824                    return self.filter(name, func) 
     825                return dec 
     826        elif name != None and filter_func != None: 
     827            # register.filter('somename', somefunc) 
     828            self.filters[name] = filter_func 
     829        else: 
     830            raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg) 
     831 
     832    def filter_function(self, func): 
     833        self.filters[func.__name__] = func 
     834        return func 
     835 
     836    def simple_tag(self,func): 
     837        (params, xx, xxx, defaults) = getargspec(func) 
     838 
     839        class SimpleNode(Node): 
     840            def __init__(self, vars_to_resolve): 
     841                self.vars_to_resolve = vars_to_resolve 
     842 
     843            def render(self, context): 
     844                resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] 
     845                return func(*resolved_vars) 
     846 
     847        compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode) 
     848        compile_func.__doc__ = func.__doc__ 
     849        self.tag(func.__name__, compile_func) 
     850        return func 
     851 
     852    def inclusion_tag(self, file_name, context_class=Context, takes_context=False): 
     853        def dec(func): 
     854            (params, xx, xxx, defaults) = getargspec(func) 
     855            if takes_context: 
     856                if params[0] == 'context': 
     857                    params = params[1:] 
     858                else: 
     859                    raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'" 
     860 
     861            class InclusionNode(Node): 
     862                def __init__(self, vars_to_resolve): 
     863                    self.vars_to_resolve = vars_to_resolve 
     864 
     865                def render(self, context): 
     866                    resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] 
     867                    if takes_context: 
     868                        args = [context] + resolved_vars 
     869                    else: 
     870                        args = resolved_vars 
     871 
     872                    dict = func(*args) 
     873 
     874                    if not getattr(self, 'nodelist', False): 
     875                        from django.core.template_loader import get_template 
     876                        t = get_template(file_name) 
     877                        self.nodelist = t.nodelist 
     878                    return self.nodelist.render(context_class(dict)) 
     879 
     880            compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode) 
     881            compile_func.__doc__ = func.__doc__ 
     882            self.tag(func.__name__, compile_func) 
     883            return func 
     884        return dec 
     885 
     886def get_library(module_name): 
     887    lib = libraries.get(module_name, None) 
     888    if not lib: 
     889        try: 
     890            mod = __import__(module_name, '', '', ['']) 
     891        except ImportError, e: 
     892            raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e) 
     893        for k, v in mod.__dict__.items(): 
     894            if isinstance(v, Library): 
     895                lib = v 
     896                libraries[module_name] = lib 
     897                break 
     898    if not lib: 
     899        raise InvalidTemplateLibrary, "Template library %s does not have a Library member" % module_name 
     900    return lib 
     901 
     902def add_to_builtins(module_name): 
     903    builtins.append(get_library(module_name)) 
     904 
     905add_to_builtins('django.core.template.defaulttags') 
     906add_to_builtins('django.core.template.defaultfilters') 
  • django/trunk/django/core/template/loader.py

    r1399 r1443  
    2222 
    2323from django.core.exceptions import ImproperlyConfigured 
    24 from django.core.template import Origin, StringOrigin, Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, resolve_variable, register_tag 
     24from django.core.template import Origin, StringOrigin, Template, TemplateDoesNotExist, add_to_builtins 
    2525from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG 
    2626 
     
    6969    find_template_source(name, dirs)[0] 
    7070 
    71 class ExtendsError(Exception): 
    72     pass 
    73  
    7471def get_template(template_name): 
    7572    """ 
     
    114111    raise TemplateDoesNotExist, ', '.join(template_name_list) 
    115112 
    116 class BlockNode(Node): 
    117     def __init__(self, name, nodelist, parent=None): 
    118         self.name, self.nodelist, self.parent = name, nodelist, parent 
    119  
    120     def __repr__(self): 
    121         return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist) 
    122  
    123     def render(self, context): 
    124         context.push() 
    125         # Save context in case of block.super(). 
    126         self.context = context 
    127         context['block'] = self 
    128         result = self.nodelist.render(context) 
    129         context.pop() 
    130         return result 
    131  
    132     def super(self): 
    133         if self.parent: 
    134             return self.parent.render(self.context) 
    135         return '' 
    136  
    137     def add_parent(self, nodelist): 
    138         if self.parent: 
    139             self.parent.add_parent(nodelist) 
    140         else: 
    141             self.parent = BlockNode(self.name, nodelist) 
    142  
    143 class ExtendsNode(Node): 
    144     def __init__(self, nodelist, parent_name, parent_name_var, template_dirs=None): 
    145         self.nodelist = nodelist 
    146         self.parent_name, self.parent_name_var = parent_name, parent_name_var 
    147         self.template_dirs = template_dirs 
    148  
    149     def get_parent(self, context): 
    150         if self.parent_name_var: 
    151             self.parent_name = resolve_variable_with_filters(self.parent_name_var, context) 
    152         parent = self.parent_name 
    153         if not parent: 
    154             error_msg = "Invalid template name in 'extends' tag: %r." % parent 
    155             if self.parent_name_var: 
    156                 error_msg += " Got this from the %r variable." % self.parent_name_var 
    157             raise TemplateSyntaxError, error_msg 
    158         try: 
    159             return get_template_from_string(*find_template_source(parent, self.template_dirs)) 
    160         except TemplateDoesNotExist: 
    161             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent 
    162  
    163     def render(self, context): 
    164         compiled_parent = self.get_parent(context) 
    165         parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode) 
    166         parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)]) 
    167         for block_node in self.nodelist.get_nodes_by_type(BlockNode): 
    168             # Check for a BlockNode with this node's name, and replace it if found. 
    169             try: 
    170                 parent_block = parent_blocks[block_node.name] 
    171             except KeyError: 
    172                 # This BlockNode wasn't found in the parent template, but the 
    173                 # parent block might be defined in the parent's *parent*, so we 
    174                 # add this BlockNode to the parent's ExtendsNode nodelist, so 
    175                 # it'll be checked when the parent node's render() is called. 
    176                 if parent_is_child: 
    177                     compiled_parent.nodelist[0].nodelist.append(block_node) 
    178             else: 
    179                 # Keep any existing parents and add a new one. Used by BlockNode. 
    180                 parent_block.parent = block_node.parent 
    181                 parent_block.add_parent(parent_block.nodelist) 
    182                 parent_block.nodelist = block_node.nodelist 
    183         return compiled_parent.render(context) 
    184  
    185 class ConstantIncludeNode(Node): 
    186     def __init__(self, template_path): 
    187         try: 
    188             t = get_template(template_path) 
    189             self.template = t 
    190         except Exception, e: 
    191             if TEMPLATE_DEBUG: 
    192                 raise 
    193             self.template = None 
    194  
    195     def render(self, context): 
    196         if self.template: 
    197             return self.template.render(context) 
    198         else: 
    199             return '' 
    200  
    201 class IncludeNode(Node): 
    202     def __init__(self, template_name): 
    203         self.template_name = template_name 
    204  
    205     def render(self, context): 
    206          try: 
    207              template_name = resolve_variable(self.template_name, context) 
    208              t = get_template(template_name) 
    209              return t.render(context) 
    210          except TemplateSyntaxError, e: 
    211              if TEMPLATE_DEBUG: 
    212                  raise 
    213              return '' 
    214          except: 
    215              return '' # Fail silently for invalid included templates. 
    216  
    217 def do_block(parser, token): 
    218     """ 
    219     Define a block that can be overridden by child templates. 
    220     """ 
    221     bits = token.contents.split() 
    222     if len(bits) != 2: 
    223         raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0] 
    224     block_name = bits[1] 
    225     # Keep track of the names of BlockNodes found in this template, so we can 
    226     # check for duplication. 
    227     try: 
    228         if block_name in parser.__loaded_blocks: 
    229             raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name) 
    230         parser.__loaded_blocks.append(block_name) 
    231     except AttributeError: # parser._loaded_blocks isn't a list yet 
    232         parser.__loaded_blocks = [block_name] 
    233     nodelist = parser.parse(('endblock',)) 
    234     parser.delete_first_token() 
    235     return BlockNode(block_name, nodelist) 
    236  
    237 def do_extends(parser, token): 
    238     """ 
    239     Signal that this template extends a parent template. 
    240  
    241     This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) 
    242     uses the literal value "base" as the name of the parent template to extend, 
    243     or ``{% extends variable %}`` uses the value of ``variable`` as the name 
    244     of the parent template to extend. 
    245     """ 
    246     bits = token.contents.split() 
    247     if len(bits) != 2: 
    248         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0] 
    249     parent_name, parent_name_var = None, None 
    250     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]: 
    251         parent_name = bits[1][1:-1] 
    252     else: 
    253         parent_name_var = bits[1] 
    254     nodelist = parser.parse() 
    255     if nodelist.get_nodes_by_type(ExtendsNode): 
    256         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] 
    257     return ExtendsNode(nodelist, parent_name, parent_name_var) 
    258  
    259 def do_include(parser, token): 
    260     """ 
    261     Loads a template and renders it with the current context. 
    262  
    263     Example:: 
    264  
    265         {% include "foo/some_include" %} 
    266     """ 
    267  
    268     bits = token.contents.split() 
    269     if len(bits) != 2: 
    270         raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0] 
    271     path = bits[1] 
    272     if path[0] in ('"', "'") and path[-1] == path[0]: 
    273         return ConstantIncludeNode(path[1:-1]) 
    274     return IncludeNode(bits[1]) 
    275  
    276 register_tag('block', do_block) 
    277 register_tag('extends', do_extends) 
    278 register_tag('include', do_include) 
     113add_to_builtins('django.core.template.loader_tags') 
  • django/trunk/django/templatetags/i18n.py

    r1412 r1443  
    1 from django.core.template import Node, NodeList, Template, Context, resolve_variable, resolve_variable_with_filters, registered_filters 
    2 from django.core.template import TemplateSyntaxError, register_tag, TokenParser 
     1from django.core.template import Node, NodeList, Template, Context, resolve_variable 
     2from django.core.template import TemplateSyntaxError, TokenParser, Library 
    33from django.core.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR 
    44from django.utils import translation 
    55import re, sys 
     6 
     7register = Library() 
    68 
    79class GetAvailableLanguagesNode(Node): 
     
    5456        context.push() 
    5557        for var,val in self.extra_context.items(): 
    56             context[var] = resolve_variable_with_filters(val, context) 
     58            context[var] = val.resolve(context) 
    5759        singular = self.render_token_list(self.singular) 
    5860        if self.plural and self.countervar and self.counter: 
    59             count = resolve_variable_with_filters(self.counter, context) 
     61            count = self.counter.resolve(context) 
    6062            context[self.countervar] = count 
    6163            plural = self.render_token_list(self.plural) 
     
    180182                    if self.tag() != 'as': 
    181183                        raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'" 
    182                     extra_context[self.tag()] = value 
     184                    extra_context[self.tag()] = parser.compile_filter(value) 
    183185                elif tag == 'count': 
    184                     counter = self.value(
     186                    counter = parser.compile_filter(self.value()
    185187                    if self.tag() != 'as': 
    186188                        raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'" 
     
    214216    return BlockTranslateNode(extra_context, singular, plural, countervar, counter) 
    215217 
    216 register_tag('get_available_languages', do_get_available_languages) 
    217 register_tag('get_current_language', do_get_current_language) 
    218 register_tag('trans', do_translate) 
    219 register_tag('blocktrans', do_block_translate) 
     218register.tag('get_available_languages', do_get_available_languages) 
     219register.tag('get_current_language', do_get_current_language) 
     220register.tag('trans', do_translate) 
     221register.tag('blocktrans', do_block_translate) 
  • django/trunk/docs/templates_python.txt

    r1261 r1443  
    445445Python code, depending on whether you're writing filters or tags. 
    446446 
     447To be a valid tag library, the module contain a module-level variable that is a 
     448``template.Library`` instance, in which all the tags and filters are 
     449registered. So, near the top of your module, put the following:: 
     450 
     451    from django.core import template 
     452    register = template.Library() 
     453 
     454Convention is to call this instance ``register``. 
     455 
    447456.. admonition:: Behind the scenes 
    448457 
     
    454463------------------------------- 
    455464 
    456 Custom filters are just Python functions that take two arguments: 
    457  
    458     * The value of the variable (input) -- not necessarily a string 
    459     * The value of the argument -- always a string 
     465**This section applies to the Django development version.** 
     466 
     467Custom filters are just Python functions that take one or two arguments: 
     468 
     469    * The value of the variable (input) -- not necessarily a string. 
     470    * The value of the argument -- this can have a default value, or be left 
     471      out altogether. 
     472 
     473For example, in the filter ``{{ var|foo:"bar" }}``, the filter ``foo`` would be 
     474passed the variable ``var`` and the argument ``"bar"``. 
    460475 
    461476Filter functions should always return something. They shouldn't raise 
     
    469484        return value.replace(arg, '') 
    470485 
    471 Most filters don't take arguments. For filters that don't take arguments, the 
    472 convention is to use a single underscore as the second argument to the filter 
    473 definition. Example:: 
    474  
    475     def lower(value, _): 
     486And here's an example of how that filter would be used:: 
     487 
     488    {{ somevariable|cut:"0" }} 
     489 
     490Most filters don't take arguments. In this case, just leave the argument out of 
     491your function. Example:: 
     492 
     493    def lower(value): # Only one argument. 
    476494        "Converts a string into all lowercase" 
    477495        return value.lower() 
    478496 
    479 When you've written your filter definition, you need to register it, to make it 
    480 available to Django's template language:: 
    481  
    482     from django.core import template 
    483     template.register_filter('cut', cut, True) 
    484     template.register_filter('lower', lower, False) 
    485  
    486 ``register_filter`` takes three arguments: 
     497When you've written your filter definition, you need to register it with 
     498your ``Library`` instance, to make it available to Django's template language:: 
     499 
     500    register.filter('cut', cut) 
     501    register.filter('lower', lower) 
     502 
     503The ``Library.filter()`` method takes two arguments: 
    487504 
    488505    1. The name of the filter -- a string. 
    489506    2. The compilation function -- a Python function (not the name of the 
    490507       function as a string). 
    491     3. A boolean, designating whether the filter requires an argument. This 
    492        tells Django's template parser whether to throw ``TemplateSyntaxError`` 
    493        when filter arguments are given (or missing). 
    494  
    495 The convention is to put all ``register_filter`` calls at the bottom of your 
    496 template-library module. 
     508 
     509If you're using Python 2.4 or above, you can use ``register.filter()`` as a 
     510decorator instead:: 
     511 
     512    @register.filter(name='cut') 
     513    def cut(value, arg): 
     514        return value.replace(arg, '') 
     515 
     516    @register.filter 
     517    def lower(value): 
     518        return value.lower() 
     519 
     520If you leave off the ``name`` argument, as in the second example above, Django 
     521will use the function's name as the filter name. 
    497522 
    498523Writing custom template tags 
    499524---------------------------- 
     525 
     526**This section applies to the Django development version.** 
    500527 
    501528Tags are more complex than filters, because tags can do anything. 
     
    525552function with the tag contents and the parser object itself. This function is 
    526553responsible for returning a ``Node`` instance based on the contents of the tag. 
    527  
    528 By convention, the name of each compilation function should start with ``do_``. 
    529554 
    530555For example, let's write a template tag, ``{% current_time %}``, that displays 
     
    613638~~~~~~~~~~~~~~~~~~~ 
    614639 
    615 Finally, use a ``register_tag`` call, as in ``register_filter`` above. Example:: 
    616  
    617     from django.core import template 
    618     template.register_tag('current_time', do_current_time) 
    619  
    620 ``register_tag`` takes two arguments: 
    621  
    622     1. The name of the template tag -- a string. 
     640Finally, register the tag with your module's ``Library`` instance, as explained 
     641in "Writing custom template filters" above. Example:: 
     642 
     643    register.tag('current_time', do_current_time) 
     644 
     645The ``tag()`` method takes two arguments: 
     646 
     647    1. The name of the template tag -- a string. If this is left out, the 
     648       name of the compilation function will be used. 
    623649    2. The compilation function -- a Python function (not the name of the 
    624650       function as a string). 
     651 
     652As with filter registration, it is also possible to use this as a decorator, in 
     653Python 2.4 and above: 
     654 
     655    @register.tag(name="current_time") 
     656    def do_current_time(parser, token): 
     657        # ... 
     658 
     659    @register.tag 
     660    def shout(parser, token): 
     661        # ... 
     662 
     663If you leave off the ``name`` argument, as in the second example above, Django 
     664will use the function's name as the tag name. 
    625665 
    626666Setting a variable in the context 
  • django/trunk/docs/templates.txt

    r1349 r1443  
    278278makes the ``comment_form`` tag available for use. Consult the documentation 
    279279area in your admin to find the list of custom libraries in your installation. 
     280 
     281**New in Django development version:** The ``{% load %}`` tag can take multiple 
     282library names, separated by spaces. Example:: 
     283 
     284    {% load comments i18n %} 
    280285 
    281286Built-in tag and filter reference 
  • django/trunk/tests/othertests/defaultfilters.py

    r1007 r1443  
    11""" 
    2 >>> floatformat(7.7, None
     2>>> floatformat(7.7
    33'7.7' 
    4 >>> floatformat(7.0, None
     4>>> floatformat(7.0
    55'7' 
    6 >>> floatformat(0.7, None
     6>>> floatformat(0.7
    77'0.7' 
    8 >>> floatformat(0.07, None
     8>>> floatformat(0.07
    99'0.1' 
    10 >>> floatformat(0.007, None
     10>>> floatformat(0.007
    1111'0.0' 
    12 >>> floatformat(0.0, None
     12>>> floatformat(0.0
    1313'0' 
    1414""" 
  • django/trunk/tests/othertests/markup.py

    r483 r1443  
    11# Quick tests for the markup templatetags (django.contrib.markup) 
    22 
    3 from django.core.template import Template, Context 
    4 import django.contrib.markup.templatetags.markup # this registers the filters 
     3from django.core.template import Template, Context, add_to_builtins 
     4 
     5add_to_builtins('django.contrib.markup.templatetags.markup') 
    56 
    67# find out if markup modules are installed and tailor the test appropriately 
     
    910except ImportError: 
    1011    textile = None 
    11      
     12 
    1213try: 
    1314    import markdown 
    1415except ImportError: 
    1516    markdown = None 
    16      
     17 
    1718try: 
    1819    import docutils 
     
    3738else: 
    3839    assert rendered == textile_content 
    39      
     40 
    4041### test markdown 
    4142 
  • django/trunk/tests/othertests/templates.py

    r1416 r1443  
    1 import traceback 
     1from django.conf import settings 
     2 
     3# Turn TEMPLATE_DEBUG off, because tests assume that. 
     4settings.TEMPLATE_DEBUG = False 
    25 
    36from django.core import template 
    47from django.core.template import loader 
    58from django.utils.translation import activate, deactivate, install 
     9import traceback 
    610 
    711# Helper objects for template tests 
     
    100104    'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), 
    101105 
    102     #Escaped string as argument 
    103     'basic-syntax30': (r"""{{ var|default_if_none:" endquote\" hah" }}""", {"var": None}, ' endquote" hah'), 
     106    # Escaped string as argument 
     107    'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), 
     108 
     109    # Variable as argument 
     110    'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'), 
     111 
     112    # Default argument testing 
     113    'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), 
    104114 
    105115    ### IF TAG ################################################################ 
     
    290300    "A custom template loader that loads the unit-test templates." 
    291301    try: 
    292         return ( TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name
     302        return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name
    293303    except KeyError: 
    294304        raise template.TemplateDoesNotExist, template_name 
  • django/trunk/tests/testapp/templatetags/testtags.py

    r367 r1443  
    22 
    33from django.core import template 
     4 
     5register = template.Library() 
    46 
    57class EchoNode(template.Node): 
     
    1214def do_echo(parser, token): 
    1315    return EchoNode(token.contents.split()[1:]) 
    14      
    15 template.register_tag("echo", do_echo) 
     16 
     17register.tag("echo", do_echo)