Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #911 -- Made template system scoped to the parser instead of th…

…e 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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@1443 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 3ede006fc98f7e96ae9fb997872f78635576d5f8 1 parent 5676d5b
Adrian Holovaty authored November 26, 2005

Showing 33 changed files with 785 additions and 586 deletions. Show diff stats Hide diff stats

  1. 4  django/contrib/admin/templates/admin/change_form.html
  2. 3  django/contrib/admin/templates/admin/change_list.html
  3. 1  django/contrib/admin/templates/admin/edit_inline_stacked.html
  4. 1  django/contrib/admin/templates/admin/edit_inline_tabular.html
  5. 1  django/contrib/admin/templates/admin/field_line.html
  6. 1  django/contrib/admin/templates/admin/filter.html
  7. 1  django/contrib/admin/templates/admin/filters.html
  8. 1  django/contrib/admin/templates/admin/pagination.html
  9. 1  django/contrib/admin/templates/admin/search_form.html
  10. 2  django/contrib/admin/templates/widget/default.html
  11. 2  django/contrib/admin/templates/widget/file.html
  12. 3  django/contrib/admin/templates/widget/foreign.html
  13. 32  django/contrib/admin/templatetags/admin_list.py
  14. 37  django/contrib/admin/templatetags/admin_modify.py
  15. 4  django/contrib/admin/templatetags/adminapplist.py
  16. 5  django/contrib/admin/templatetags/adminmedia.py
  17. 4  django/contrib/admin/templatetags/log.py
  18. 12  django/contrib/admin/views/template.py
  19. 14  django/contrib/comments/templatetags/comments.py
  20. 26  django/contrib/markup/templatetags/markup.py
  21. 302  django/core/template/__init__.py
  22. 67  django/core/template/decorators.py
  23. 162  django/core/template/defaultfilters.py
  24. 178  django/core/template/defaulttags.py
  25. 169  django/core/template/loader.py
  26. 172  django/core/template/loader_tags.py
  27. 22  django/templatetags/i18n.py
  28. 5  docs/templates.txt
  29. 90  docs/templates_python.txt
  30. 12  tests/othertests/defaultfilters.py
  31. 13  tests/othertests/markup.py
  32. 18  tests/othertests/templates.py
  33. 6  tests/testapp/templatetags/testtags.py
4  django/contrib/admin/templates/admin/change_form.html
... ...
@@ -1,7 +1,5 @@
1 1
 {% extends "admin/base_site" %}
2  
-{% load i18n %}
3  
-{% load admin_modify %}
4  
-{% load adminmedia %}
  2
+{% load i18n admin_modify adminmedia %}
5 3
 {% block extrahead %}
6 4
 {% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
7 5
 {% endblock %}
3  django/contrib/admin/templates/admin/change_list.html
... ...
@@ -1,5 +1,4 @@
1  
-{% load admin_list %}
2  
-{% load i18n %}
  1
+{% load adminmedia admin_list i18n %}
3 2
 {% extends "admin/base_site" %}
4 3
 {% block bodyclass %}change-list{% endblock %}
5 4
 {% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %}
1  django/contrib/admin/templates/admin/edit_inline_stacked.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load admin_modify %}
1 2
 <fieldset class="module aligned">
2 3
    {% for fcw in bound_related_object.form_field_collection_wrappers %}
3 4
       <h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
1  django/contrib/admin/templates/admin/edit_inline_tabular.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load admin_modify %}
1 2
 <fieldset class="module">
2 3
    <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
3 4
    <thead><tr>
1  django/contrib/admin/templates/admin/field_line.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load admin_modify %}
1 2
 <div class="{{ class_names }}" >
2 3
 {% for bound_field in bound_fields %}{{ bound_field.html_error_list }}{% endfor %}
3 4
 {% for bound_field in bound_fields %}
1  django/contrib/admin/templates/admin/filter.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load i18n %}
1 2
 <h3>{% blocktrans %} By {{ title }} {% endblocktrans %}</h3>
2 3
 <ul>
3 4
 {% for choice in choices %}
1  django/contrib/admin/templates/admin/filters.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load admin_list %}
1 2
 {% if cl.has_filters %}<div id="changelist-filter">
2 3
 <h2>Filter</h2>
3 4
 {% for spec in cl.filter_specs %}
1  django/contrib/admin/templates/admin/pagination.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load admin_list %}
1 2
 <p class="paginator">
2 3
 {% if pagination_required %}
3 4
 {% for i in page_range %}
1  django/contrib/admin/templates/admin/search_form.html
... ...
@@ -1,3 +1,4 @@
  1
+{% load adminmedia %}
1 2
 {% if cl.lookup_opts.admin.search_fields %}
2 3
 <div id="toolbar"><form id="changelist-search" action="" method="get">
3 4
 <label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" /></label>
2  django/contrib/admin/templates/widget/default.html
... ...
@@ -1 +1 @@
1  
-{% output_all bound_field.form_fields %}
  1
+{% load admin_modify %}{% output_all bound_field.form_fields %}
2  django/contrib/admin/templates/widget/file.html
... ...
@@ -1,4 +1,4 @@
1  
-{% if bound_field.original_value %}
  1
+{% load admin_modify %}{% if bound_field.original_value %}
2 2
 Currently: <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value }} </a><br />
3 3
 Change: {% output_all bound_field.form_fields %}
4 4
 {% else %} {% output_all bound_field.form_fields %} {% endif %}
3  django/contrib/admin/templates/widget/foreign.html
... ...
@@ -1,7 +1,8 @@
  1
+{% load admin_modify adminmedia %}
1 2
 {% output_all bound_field.form_fields %}
2 3
 {% if bound_field.raw_id_admin %}
3 4
             <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" class="related-lookup" id="lookup_{{bound_field.element_id}}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
4 5
 {% else %}
5 6
 {% if bound_field.needs_add_label %}
6 7
             <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 %}
32  django/contrib/admin/templatetags/admin_list.py
@@ -3,16 +3,18 @@
3 3
 from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
4 4
 from django.core import meta, template
5 5
 from django.core.exceptions import ObjectDoesNotExist
6  
-from django.core.template.decorators import simple_tag, inclusion_tag
7 6
 from django.utils import dateformat
8 7
 from django.utils.html import strip_tags, escape
9 8
 from django.utils.text import capfirst
10 9
 from django.utils.translation import get_date_formats
11 10
 from django.conf.settings import ADMIN_MEDIA_PREFIX
  11
+from django.core.template import Library
  12
+
  13
+register = Library()
12 14
 
13 15
 DOT = '.'
14 16
 
15  
-#@simple_tag
  17
+#@register.simple_tag
16 18
 def paginator_number(cl,i):
17 19
     if i == DOT:
18 20
        return '... '
@@ -20,9 +22,9 @@ def paginator_number(cl,i):
20 22
        return '<span class="this-page">%d</span> ' % (i+1)
21 23
     else:
22 24
        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)
  25
+paginator_number = register.simple_tag(paginator_number)
24 26
 
25  
-#@inclusion_tag('admin/pagination')
  27
+#@register.inclusion_tag('admin/pagination')
26 28
 def pagination(cl):
27 29
     paginator, page_num = cl.paginator, cl.page_num
28 30
 
@@ -64,7 +66,7 @@ def pagination(cl):
64 66
         'ALL_VAR': ALL_VAR,
65 67
         '1': 1,
66 68
     }
67  
-pagination = inclusion_tag('admin/pagination')(pagination)
  69
+pagination = register.inclusion_tag('admin/pagination')(pagination)
68 70
 
69 71
 def result_headers(cl):
70 72
     lookup_opts = cl.lookup_opts
@@ -177,15 +179,15 @@ def results(cl):
177 179
     for res in cl.result_list:
178 180
         yield list(items_for_result(cl,res))
179 181
 
180  
-#@inclusion_tag("admin/change_list_results")
  182
+#@register.inclusion_tag("admin/change_list_results")
181 183
 def result_list(cl):
182 184
     res = list(results(cl))
183 185
     return {'cl': cl,
184 186
             'result_headers': list(result_headers(cl)),
185 187
             'results': list(results(cl))}
186  
-result_list = inclusion_tag("admin/change_list_results")(result_list)
  188
+result_list = register.inclusion_tag("admin/change_list_results")(result_list)
187 189
 
188  
-#@inclusion_tag("admin/date_hierarchy")
  190
+#@register.inclusion_tag("admin/date_hierarchy")
189 191
 def date_hierarchy(cl):
190 192
     lookup_opts, params, lookup_params, lookup_mod = \
191 193
       cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
@@ -256,23 +258,23 @@ def get_dates(unit, params):
256 258
                     'title': year.year
257 259
                 } for year in years ]
258 260
             }
259  
-date_hierarchy = inclusion_tag('admin/date_hierarchy')(date_hierarchy)
  261
+date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy)
260 262
 
261  
-#@inclusion_tag('admin/search_form')
  263
+#@register.inclusion_tag('admin/search_form')
262 264
 def search_form(cl):
263 265
     return {
264 266
         'cl': cl,
265 267
         'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
266 268
         'search_var': SEARCH_VAR
267 269
     }
268  
-search_form = inclusion_tag('admin/search_form')(search_form)
  270
+search_form = register.inclusion_tag('admin/search_form')(search_form)
269 271
 
270  
-#@inclusion_tag('admin/filter')
  272
+#@register.inclusion_tag('admin/filter')
271 273
 def filter(cl, spec):
272 274
     return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
273  
-filter = inclusion_tag('admin/filter')(filter)
  275
+filter = register.inclusion_tag('admin/filter')(filter)
274 276
 
275  
-#@inclusion_tag('admin/filters')
  277
+#@register.inclusion_tag('admin/filters')
276 278
 def filters(cl):
277 279
     return {'cl': cl}
278  
-filters = inclusion_tag('admin/filters')(filters)
  280
+filters = register.inclusion_tag('admin/filters')(filters)
37  django/contrib/admin/templatetags/admin_modify.py
@@ -2,24 +2,25 @@
2 2
 from django.utils.html import escape
3 3
 from django.utils.text import capfirst
4 4
 from django.utils.functional import curry
5  
-from django.core.template.decorators import simple_tag, inclusion_tag
6 5
 from django.contrib.admin.views.main import AdminBoundField
7 6
 from django.core.meta.fields import BoundField, Field
8 7
 from django.core.meta import BoundRelatedObject, TABULAR, STACKED
9 8
 from django.conf.settings import ADMIN_MEDIA_PREFIX
10 9
 import re
11 10
 
  11
+register = template.Library()
  12
+
12 13
 word_re = re.compile('[A-Z][a-z]+')
13 14
 
14 15
 def class_name_to_underscored(name):
15 16
     return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
16 17
 
17  
-#@simple_tag
  18
+#@register.simple_tag
18 19
 def include_admin_script(script_path):
19 20
     return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path)
20  
-include_admin_script = simple_tag(include_admin_script)
  21
+include_admin_script = register.simple_tag(include_admin_script)
21 22
 
22  
-#@inclusion_tag('admin/submit_line', takes_context=True)
  23
+#@register.inclusion_tag('admin/submit_line', takes_context=True)
23 24
 def submit_row(context, bound_manipulator):
24 25
     change = context['change']
25 26
     add = context['add']
@@ -36,9 +37,9 @@ def submit_row(context, bound_manipulator):
36 37
         'show_save_and_continue': not is_popup,
37 38
         'show_save': True
38 39
     }
39  
-submit_row = inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
  40
+submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
40 41
 
41  
-#@simple_tag
  42
+#@register.simple_tag
42 43
 def field_label(bound_field):
43 44
     class_names = []
44 45
     if isinstance(bound_field.field, meta.BooleanField):
@@ -53,7 +54,7 @@ def field_label(bound_field):
53 54
     class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
54 55
     return '<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
55 56
         capfirst(bound_field.field.verbose_name), colon)
56  
-field_label = simple_tag(field_label)
  57
+field_label = register.simple_tag(field_label)
57 58
 
58 59
 class FieldWidgetNode(template.Node):
59 60
     nodelists = {}
@@ -170,12 +171,12 @@ def render(self, context):
170 171
         context.pop()
171 172
         return output
172 173
 
173  
-#@simple_tag
  174
+#@register.simple_tag
174 175
 def output_all(form_fields):
175 176
     return ''.join([str(f) for f in form_fields])
176  
-output_all = simple_tag(output_all)
  177
+output_all = register.simple_tag(output_all)
177 178
 
178  
-#@simple_tag
  179
+#@register.simple_tag
179 180
 def auto_populated_field_script(auto_pop_fields, change = False):
180 181
     for field in auto_pop_fields:
181 182
         t = []
@@ -191,9 +192,9 @@ def auto_populated_field_script(auto_pop_fields, change = False):
191 192
                      ' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
192 193
                      f, field.name, add_values, field.maxlength))
193 194
     return ''.join(t)
194  
-auto_populated_field_script = simple_tag(auto_populated_field_script)
  195
+auto_populated_field_script = register.simple_tag(auto_populated_field_script)
195 196
 
196  
-#@simple_tag
  197
+#@register.simple_tag
197 198
 def filter_interface_script_maybe(bound_field):
198 199
     f = bound_field.field
199 200
     if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface:
@@ -202,7 +203,7 @@ def filter_interface_script_maybe(bound_field):
202 203
               f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)
203 204
     else:
204 205
         return ''
205  
-filter_interface_script_maybe = simple_tag(filter_interface_script_maybe)
  206
+filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
206 207
 
207 208
 def do_one_arg_tag(node_factory, parser,token):
208 209
     tokens = token.contents.split()
@@ -213,7 +214,7 @@ def do_one_arg_tag(node_factory, parser,token):
213 214
 def register_one_arg_tag(node):
214 215
     tag_name = class_name_to_underscored(node.__name__)
215 216
     parse_func = curry(do_one_arg_tag, node)
216  
-    template.register_tag(tag_name, parse_func)
  217
+    register.tag(tag_name, parse_func)
217 218
 
218 219
 one_arg_tag_nodes = (
219 220
     FieldWidgetNode,
@@ -223,7 +224,7 @@ def register_one_arg_tag(node):
223 224
 for node in one_arg_tag_nodes:
224 225
     register_one_arg_tag(node)
225 226
 
226  
-#@inclusion_tag('admin/field_line', takes_context=True)
  227
+#@register.inclusion_tag('admin/field_line', takes_context=True)
227 228
 def admin_field_line(context, argument_val):
228 229
     if (isinstance(argument_val, BoundField)):
229 230
         bound_fields = [argument_val]
@@ -249,10 +250,10 @@ def admin_field_line(context, argument_val):
249 250
         'bound_fields':  bound_fields,
250 251
         'class_names': " ".join(class_names),
251 252
     }
252  
-admin_field_line = inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
  253
+admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
253 254
 
254  
-#@simple_tag
  255
+#@register.simple_tag
255 256
 def object_pk(bound_manip, ordered_obj):
256 257
     return bound_manip.get_ordered_object_pk(ordered_obj)
257 258
 
258  
-object_pk = simple_tag(object_pk)
  259
+object_pk = register.simple_tag(object_pk)
4  django/contrib/admin/templatetags/adminapplist.py
... ...
@@ -1,5 +1,7 @@
1 1
 from django.core import template
2 2
 
  3
+register = template.Library()
  4
+
3 5
 class AdminApplistNode(template.Node):
4 6
     def __init__(self, varname):
5 7
         self.varname = varname
@@ -54,4 +56,4 @@ def get_admin_app_list(parser, token):
54 56
         raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0]
55 57
     return AdminApplistNode(tokens[2])
56 58
 
57  
-template.register_tag('get_admin_app_list', get_admin_app_list)
  59
+register.tag('get_admin_app_list', get_admin_app_list)
5  django/contrib/admin/templatetags/adminmedia.py
... ...
@@ -1,4 +1,5 @@
1  
-from django.core.template.decorators import simple_tag
  1
+from django.core.template import Library
  2
+register = Library()
2 3
 
3 4
 def admin_media_prefix():
4 5
     try:
@@ -6,4 +7,4 @@ def admin_media_prefix():
6 7
     except ImportError:
7 8
         return ''
8 9
     return ADMIN_MEDIA_PREFIX
9  
-admin_media_prefix = simple_tag(admin_media_prefix)
  10
+admin_media_prefix = register.simple_tag(admin_media_prefix)
4  django/contrib/admin/templatetags/log.py
... ...
@@ -1,6 +1,8 @@
1 1
 from django.models.admin import log
2 2
 from django.core import template
3 3
 
  4
+register = template.Library()
  5
+
4 6
 class AdminLogNode(template.Node):
5 7
     def __init__(self, limit, varname, user):
6 8
         self.limit, self.varname, self.user = limit, varname, user
@@ -48,4 +50,4 @@ def __call__(self, parser, token):
48 50
                 raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name
49 51
         return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
50 52
 
51  
-template.register_tag('get_admin_log', DoGetAdminLog('get_admin_log'))
  53
+register.tag('get_admin_log', DoGetAdminLog('get_admin_log'))
12  django/contrib/admin/views/template.py
@@ -50,21 +50,23 @@ def isValidTemplate(self, field_data, all_data):
50 50
             return
51 51
 
52 52
         # 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.
54 54
         def new_do_extends(parser, token):
55 55
             node = loader.do_extends(parser, token)
56 56
             node.template_dirs = settings_module.TEMPLATE_DIRS
57 57
             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)
59 61
 
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.
62 64
         error = None
63 65
         try:
64 66
             tmpl = loader.get_template_from_string(field_data)
65 67
             tmpl.render(template.Context({}))
66 68
         except template.TemplateSyntaxError, e:
67 69
             error = e
68  
-        template.register_tag('extends', loader.do_extends)
  70
+        template.builtins.remove(register)
69 71
         if error:
70 72
             raise validators.ValidationError, e.args
14  django/contrib/comments/templatetags/comments.py
@@ -6,6 +6,8 @@
6 6
 from django.models.core import contenttypes
7 7
 import re
8 8
 
  9
+register = template.Library()
  10
+
9 11
 COMMENT_FORM = '''
10 12
 {% load i18n %}
11 13
 {% if display_form %}
@@ -360,10 +362,10 @@ def __call__(self, parser, token):
360 362
         return CommentListNode(package, module, var_name, obj_id, tokens[5], self.free, ordering)
361 363
 
362 364
 # 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))
  365
+register.tag('get_comment_list', DoGetCommentList(False))
  366
+register.tag('comment_form', DoCommentForm(False))
  367
+register.tag('get_comment_count', DoCommentCount(False))
366 368
 # 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))
  369
+register.tag('get_free_comment_list', DoGetCommentList(True))
  370
+register.tag('free_comment_form', DoCommentForm(True))
  371
+register.tag('get_free_comment_count', DoCommentCount(True))
26  django/contrib/markup/templatetags/markup.py
@@ -4,35 +4,37 @@
4 4
 
5 5
     * Textile, which requires the PyTextile library available at
6 6
       http://dealmeida.net/projects/textile/
7  
-      
  7
+
8 8
     * Markdown, which requires the Python-markdown library from
9 9
       http://www.freewisdom.org/projects/python-markdown
10  
-      
  10
+
11 11
     * ReStructuredText, which requires docutils from http://docutils.sf.net/
12  
-    
  12
+
13 13
 In each case, if the required library is not installed, the filter will
14 14
 silently fail and return the un-marked-up text.
15 15
 """
16 16
 
17 17
 from django.core import template
18 18
 
19  
-def textile(value, _):
  19
+register = template.Library()
  20
+
  21
+def textile(value):
20 22
     try:
21 23
         import textile
22 24
     except ImportError:
23 25
         return value
24 26
     else:
25 27
         return textile.textile(value)
26  
-        
27  
-def markdown(value, _):
  28
+
  29
+def markdown(value):
28 30
     try:
29 31
         import markdown
30 32
     except ImportError:
31 33
         return value
32 34
     else:
33 35
         return markdown.markdown(value)
34  
-        
35  
-def restructuredtext(value, _):
  36
+
  37
+def restructuredtext(value):
36 38
     try:
37 39
         from docutils.core import publish_parts
38 40
     except ImportError:
@@ -40,7 +42,7 @@ def restructuredtext(value, _):
40 42
     else:
41 43
         parts = publish_parts(source=value, writer_name="html4css1")
42 44
         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
+
  46
+register.filter(textile)
  47
+register.filter(markdown)
  48
+register.filter(restructuredtext)
302  django/core/template/__init__.py
@@ -3,7 +3,7 @@
3 3
 
4 4
 How it works:
5 5
 
6  
-The tokenize() function converts a template string (i.e., a string containing
  6
+The Lexer.tokenize() function converts a template string (i.e., a string containing
7 7
 markup with custom template tags) to tokens, which can be either plain text
8 8
 (TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
9 9
 
@@ -55,6 +55,8 @@
55 55
 '\n<html>\n\n</html>\n'
56 56
 """
57 57
 import re
  58
+from inspect import getargspec
  59
+from django.utils.functional import curry
58 60
 from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG
59 61
 
60 62
 __all__ = ('Template','Context','compile_string')
@@ -82,11 +84,10 @@
82 84
 tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
83 85
                                           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
84 86
 
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
  88
+libraries = {}
  89
+# global list of libraries to load by default for a new parser
  90
+builtins = []
90 91
 
91 92
 class TemplateSyntaxError(Exception):
92 93
     pass
@@ -105,12 +106,15 @@ class SilentVariableFailure(Exception):
105 106
     "Any function raising this exception will be ignored by resolve_variable"
106 107
     pass
107 108
 
  109
+class InvalidTemplateLibrary(Exception):
  110
+    pass
  111
+
108 112
 class Origin(object):
109 113
     def __init__(self, name):
110 114
         self.name = name
111 115
 
112 116
     def reload(self):
113  
-        raise NotImplementedException
  117
+        raise NotImplementedError
114 118
 
115 119
     def __str__(self):
116 120
         return self.name
@@ -264,6 +268,10 @@ def create_token(self, token_string, source):
264 268
 class Parser(object):
265 269
     def __init__(self, tokens):
266 270
         self.tokens = tokens
  271
+        self.tags = {}
  272
+        self.filters = {}
  273
+        for lib in builtins:
  274
+            self.add_library(lib)
267 275
 
268 276
     def parse(self, parse_until=[]):
269 277
         nodelist = self.create_nodelist()
@@ -274,7 +282,8 @@ def parse(self, parse_until=[]):
274 282
             elif token.token_type == TOKEN_VAR:
275 283
                 if not token.contents:
276 284
                     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)
278 287
                 self.extend_nodelist(nodelist, var_node,token)
279 288
             elif token.token_type == TOKEN_BLOCK:
280 289
                 if token.contents in parse_until:
@@ -288,7 +297,7 @@ def parse(self, parse_until=[]):
288 297
                 # execute callback function for this tag and append resulting node
289 298
                 self.enter_command(command, token)
290 299
                 try:
291  
-                    compile_func = registered_tags[command]
  300
+                    compile_func = self.tags[command]
292 301
                 except KeyError:
293 302
                     self.invalid_block_tag(token, command)
294 303
                 try:
@@ -302,8 +311,8 @@ def parse(self, parse_until=[]):
302 311
             self.unclosed_block_tag(parse_until)
303 312
         return nodelist
304 313
 
305  
-    def create_variable_node(self, contents):
306  
-        return VariableNode(contents)
  314
+    def create_variable_node(self, filter_expression):
  315
+        return VariableNode(filter_expression)
307 316
 
308 317
     def create_nodelist(self):
309 318
         return NodeList()
@@ -344,6 +353,20 @@ def prepend_token(self, token):
344 353
     def delete_first_token(self):
345 354
         del self.tokens[0]
346 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
  369
+
347 370
 class DebugParser(Parser):
348 371
     def __init__(self, lexer):
349 372
         super(DebugParser, self).__init__(lexer)
@@ -483,7 +506,8 @@ def value(self):
483 506
          (?:%(arg_sep)s
484 507
              (?:
485 508
               %(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]+)
487 511
              )
488 512
          )?
489 513
  )""" % {
@@ -498,7 +522,7 @@ def value(self):
498 522
 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
499 523
 filter_re = re.compile(filter_raw_string)
500 524
 
501  
-class FilterParser(object):
  525
+class FilterExpression(object):
502 526
     """
503 527
     Parses a variable token and its optional filters (all as a single string),
504 528
     and return a list of tuples of the filter name and arguments.
@@ -513,7 +537,8 @@ class FilterParser(object):
513 537
     This class should never be instantiated outside of the
514 538
     get_filters_from_token helper function.
515 539
     """
516  
-    def __init__(self, token):
  540
+    def __init__(self, token, parser):
  541
+        self.token = token
517 542
         matches = filter_re.finditer(token)
518 543
         var = None
519 544
         filters = []
@@ -536,27 +561,69 @@ def __init__(self, token):
536 561
                     raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
537 562
             else:
538 563
                 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")
540 566
                 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))
551 575
                 upto = match.end()
552 576
         if upto != len(token):
553 577
             raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
554 578
         self.var , self.filters = var, filters
555 579
 
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
560 627
 
561 628
 def resolve_variable(path, context):
562 629
     """
@@ -607,22 +674,6 @@ def resolve_variable(path, context):
607 674
             del bits[0]
608 675
     return current
609 676
 
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  
-
626 677
 class Node:
627 678
     def render(self, context):
628 679
         "Return the node rendered as a string"
@@ -687,11 +738,11 @@ def render(self, context):
687 738
         return self.s
688 739
 
689 740
 class 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
692 743
 
693 744
     def __repr__(self):
694  
-        return "<Variable Node: %s>" % self.var_string
  745
+        return "<Variable Node: %s>" % self.filter_expression
695 746
 
696 747
     def encode_output(self, output):
697 748
         # Check type so that we don't run str() on a Unicode object
@@ -703,30 +754,153 @@ def encode_output(self, output):
703 754
             return output
704 755
 
705 756
     def render(self, context):
706  
-        output = resolve_variable_with_filters(self.var_string, context)
  757
+        output = self.filter_expression.resolve(context)
707 758
         return self.encode_output(output)
708 759
 
709 760
 class DebugVariableNode(VariableNode):
710 761
     def render(self, context):
711 762
         try:
712  
-             output = resolve_variable_with_filters(self.var_string, context)
  763
+             output = self.filter_expression.resolve(context)
713 764
         except TemplateSyntaxError, e:
714 765
             if not hasattr(e, 'source'):
715 766
                 e.source = self.source
716 767
             raise
717 768
         return self.encode_output(output)
718 769
 
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
  770
+def 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
+
  784
+class 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
+
  886
+def 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
+
  902
+def add_to_builtins(module_name):
  903
+    builtins.append(get_library(module_name))
  904
+
  905
+add_to_builtins('django.core.template.defaulttags')
  906
+add_to_builtins('django.core.template.defaultfilters')
67  django/core/template/decorators.py
... ...
@@ -1,67 +0,0 @@
1  
-from django.core.template import Context, Node, TemplateSyntaxError, register_tag, resolve_variable
2  
-from django.core.template_loader import get_template
3  
-from django.utils.functional import curry
4  
-from inspect import getargspec
5  
-
6  
-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
7  
-    "Returns a template.Node subclass."
8  
-    bits = token.contents.split()[1:]
9  
-    bmax = len(params)
10  
-    def_len = defaults and len(defaults) or 0
11  
-    bmin = bmax - def_len
12  
-    if(len(bits) < bmin or len(bits) > bmax):
13  
-        if bmin == bmax:
14  
-            message = "%s takes %s arguments" % (name, bmin)
15  
-        else:
16  
-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
17  
-        raise TemplateSyntaxError, message
18  
-    return node_class(bits)
19  
-
20  
-def simple_tag(func):
21  
-    (params, xx, xxx, defaults) = getargspec(func)
22  
-
23  
-    class SimpleNode(Node):
24  
-        def __init__(self, vars_to_resolve):
25  
-            self.vars_to_resolve = vars_to_resolve
26  
-
27  
-        def render(self, context):
28  
-            resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
29  
-            return func(*resolved_vars)
30  
-
31  
-    compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
32  
-    compile_func.__doc__ = func.__doc__
33  
-    register_tag(func.__name__, compile_func)
34  
-    return func
35  
-
36  
-def inclusion_tag(file_name, context_class=Context, takes_context=False):
37  
-    def dec(func):
38  
-        (params, xx, xxx, defaults) = getargspec(func)
39  
-        if takes_context:
40  
-            if params[0] == 'context':
41  
-                params = params[1:]
42  
-            else:
43  
-                raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
44  
-
45  
-        class InclusionNode(Node):
46  
-            def __init__(self, vars_to_resolve):
47  
-                self.vars_to_resolve = vars_to_resolve
48  
-
49  
-            def render(self, context):
50  
-                resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
51  
-                if takes_context:
52  
-                    args = [context] + resolved_vars
53  
-                else:
54  
-                    args = resolved_vars
55  
-
56  
-                dict = func(*args)
57  
-
58  
-                if not getattr(self, 'nodelist', False):
59  
-                    t = get_template(file_name)
60  
-                    self.nodelist = t.nodelist
61  
-                return self.nodelist.render(context_class(dict))
62  
-
63  
-        compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
64  
-        compile_func.__doc__ = func.__doc__
65  
-        register_tag(func.__name__, compile_func)
66  
-        return func
67  
-    return dec
162  django/core/template/defaultfilters.py
... ...
@@ -1,28 +1,32 @@
1 1
 "Default variable filters"
2 2
 
3  
-from django.core.template import register_filter, resolve_variable
  3
+from django.core.template import resolve_variable, Library
  4
+from django.conf.settings import DATE_FORMAT, TIME_FORMAT
4 5
 import re
5 6
 import random as random_module
6 7
 
  8
+register = Library()
  9
+
7 10
 ###################
8 11
 # STRINGS         #
9 12
 ###################
10 13
 
11  
-def addslashes(value, _):
  14
+
  15
+def addslashes(value):
12 16
     "Adds slashes - useful for passing strings to JavaScript, for example."
13 17
     return value.replace('"', '\\"').replace("'", "\\'")
14 18
 
15  
-def capfirst(value, _):
  19
+def capfirst(value):
16 20
     "Capitalizes the first character of the value"
17 21
     value = str(value)
18 22
     return value and value[0].upper() + value[1:]
19 23
 
20  
-def fix_ampersands(value, _):
  24
+def fix_ampersands(value):
21 25
     "Replaces ampersands with ``&amp;`` entities"
22 26
     from django.utils.html import fix_ampersands
23 27
     return fix_ampersands(value)
24 28
 
25  
-def floatformat(text, _):
  29
+def floatformat(text):
26 30
     """
27 31
     Displays a floating point number as 34.2 (with one decimal place) -- but
28 32
     only if there's a point to be displayed
@@ -37,7 +41,7 @@ def floatformat(text, _):
37 41
     else:
38 42
         return '%d' % int(f)
39 43
 
40  
-def linenumbers(value, _):
  44
+def linenumbers(value):
41 45
     "Displays text with line numbers"
42 46
     from django.utils.html import escape
43 47
     lines = value.split('\n')
@@ -47,18 +51,18 @@ def linenumbers(value, _):
47 51
         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
48 52
     return '\n'.join(lines)
49 53
 
50  
-def lower(value, _):
  54
+def lower(value):
51 55
     "Converts a string into all lowercase"
52 56
     return value.lower()
53 57
 
54  
-def make_list(value, _):
  58
+def make_list(value):
55 59
     """
56 60
     Returns the value turned into a list. For an integer, it's a list of
57 61
     digits. For a string, it's a list of characters.
58 62
     """
59 63
     return list(str(value))
60 64
 
61  
-def slugify(value, _):
  65
+def slugify(value):