From e3f3d4e86f984e747b18012b4587e48c27005a76 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Wed, 5 Oct 2005 21:41:41 +0000 Subject: [PATCH] Merged to r778. Added template tag decorators: * simple_tag : transforms a function outputting a string into a tag. arguments are converted into tag arguments, and defaults are supported. * inclusion_tag : transforms a function outputting a dictionary into a tag which renders an included template. The name of the template is given as an argument to the decorator, and the dictionary returnd by the function is used to populate the context to render the included template. The template is loaded at compile time, and the nodelist cached. Also made the {%include %} tag load the template at compile time if the argument is a constant string. git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@780 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin_templates/admin_change_form.html | 2 +- django/conf/global_settings.py | 2 +- django/core/defaulttags.py | 17 +- django/core/template_decorators.py | 70 +++++ django/templatetags/admin_modify.py | 243 ++++++++---------- 5 files changed, 196 insertions(+), 138 deletions(-) create mode 100644 django/core/template_decorators.py diff --git a/django/conf/admin_templates/admin_change_form.html b/django/conf/admin_templates/admin_change_form.html index 2beac80272..692de223af 100644 --- a/django/conf/admin_templates/admin_change_form.html +++ b/django/conf/admin_templates/admin_change_form.html @@ -87,7 +87,7 @@ {% if auto_populated_fields %} {% endif %} diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 449d25d01e..c5e560c9e1 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -110,7 +110,7 @@ IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'fav SECRET_KEY = '' # Path to the "jing" executable -- needed to validate XMLFields -JING_PATH = "/usr/bin/jng" +JING_PATH = "/usr/bin/jing" ############## # MIDDLEWARE # diff --git a/django/core/defaulttags.py b/django/core/defaulttags.py index d5bfec7cf0..41595fcee0 100644 --- a/django/core/defaulttags.py +++ b/django/core/defaulttags.py @@ -227,6 +227,15 @@ class SsiNode(template.Node): return '' # Fail silently for invalid included templates. return output + +class ConstantIncludeNode(template.Node): + def __init__(self, template_path): + t = template_loader.get_template(template_path) + self.nodelist = t.nodelist + + def render(self, context): + return self.nodelist.render(context) + class IncludeNode(template.Node): def __init__(self, template_path_var): self.template_path_var = template_path_var @@ -631,6 +640,10 @@ def do_include(parser, token): parsed = False if len(bits) != 2: raise template.TemplateSyntaxError, "'include' tag takes one argument: the path to the template to be included" + + path = bits[1] + if path[0] in ('"', "'") and path[-1] == path[0]: + return ConstantIncludeNode(path[1:-1]) return IncludeNode(bits[1]) def do_load(parser, token): @@ -648,8 +661,8 @@ def do_load(parser, token): # check at compile time that the module can be imported try: LoadNode.load_taglib(taglib) - except ImportError: - raise template.TemplateSyntaxError, "'%s' is not a valid tag library" % taglib + except ImportError, e: + raise template.TemplateSyntaxError, "'%s' is not a valid tag library, %s" % (taglib, e) return LoadNode(taglib) def do_now(parser, token): diff --git a/django/core/template_decorators.py b/django/core/template_decorators.py new file mode 100644 index 0000000000..3b32e694e7 --- /dev/null +++ b/django/core/template_decorators.py @@ -0,0 +1,70 @@ +from inspect import getargspec +from django.core import template +from django.core.template_loader import render_to_string, get_template +from django.utils.functional import curry + +def gen_compile_func(params, defaults, name, node_class, parser, token): + #look in tags for + bits = token.contents.split()[1:] + bmax = len(params) + def_len = defaults and len(defaults) or 0 + bmin = bmax - def_len + if( len(bits) < bmin or len(bits) > bmax ): + if bmin == bmax: + message = "%s takes %s arguments" % (name, bmin) + else: + message = "%s takes between %s and %s arguments" % (name, bmin, bmax) + raise template.TemplateSyntaxError(message) + return node_class(bits) + + +def simple_tag(func): + (params,_, _, defaults) = getargspec(func) + class TNode(template.Node): + def __init__(self, vars_to_resolve): + #get the vars to resolve + self.vars_to_resolve = vars_to_resolve + + def render(self, context): + resolved_vars = [template.resolve_variable(var, context) + for var in self.vars_to_resolve] + return func(*resolved_vars) + compile_func = curry(gen_compile_func, params, defaults, func.__name__, TNode) + compile_func.__doc__ = func.__doc__ + template.register_tag(func.__name__, compile_func) + return func + + +def inclusion_tag(file_name, context_class=template.Context, takes_context=False): + def dec(func): + (params,_, _, defaults) = getargspec(func) + if takes_context: + if params[0] == 'context': + params = params[1:] + else: + raise template.TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'" ) + class TNode(template.Node): + def __init__(self, vars_to_resolve): + self.vars_to_resolve = vars_to_resolve + + def render(self, context): + resolved_vars = [template.resolve_variable(var, context) + for var in self.vars_to_resolve] + if takes_context: + args = [context] + resolved_vars + else: + args = resolved_vars + + dict = func(*args) + + if not getattr(self, 'nodelist', False): + t = get_template(file_name) + self.nodelist = t.nodelist + return self.nodelist.render(context_class(dict)) + + compile_func = curry(gen_compile_func, params, defaults, func.__name__, TNode) + compile_func.__doc__ = func.__doc__ + template.register_tag(func.__name__, compile_func) + return func + return dec + diff --git a/django/templatetags/admin_modify.py b/django/templatetags/admin_modify.py index c52505e044..2938f47a58 100644 --- a/django/templatetags/admin_modify.py +++ b/django/templatetags/admin_modify.py @@ -5,6 +5,8 @@ from django.utils.text import capfirst from django.utils.html import escape from django.utils.functional import curry +from django.core.template_decorators import simple_tag, inclusion_tag + from django.views.admin.main import AdminBoundField import re @@ -13,21 +15,15 @@ word_re = re.compile('[A-Z][a-z]+') def class_name_to_underscored(name): return '_'.join([ s.lower() for s in word_re.findall(name)[:-1] ]) - -class IncludeAdminScriptNode(template.Node): - def __init__(self, var): - self.var = var - - def render(self, context): - resolved = template.resolve_variable(self.var, context) +#@simple_tag +def include_admin_script(script_path): return '' % \ - (ADMIN_MEDIA_PREFIX, resolved) - -class SubmitRowNode(template.Node): - def __init__(self): - pass + (ADMIN_MEDIA_PREFIX, script_path) +include_admin_script = simple_tag(include_admin_script) - def render(self, context): + +#@inclusion_tag('admin_submit_line', takes_context=True) +def submit_row(context): change = context['change'] add = context['add'] show_delete = context['show_delete'] @@ -36,8 +32,7 @@ class SubmitRowNode(template.Node): has_delete_permission = context['has_delete_permission'] is_popup = context['is_popup'] - - output = render_to_string('admin_submit_line', { + return { 'onclick_attrib' : (ordered_objects and change and 'onclick="submitOrderForm();"' or ''), 'show_delete_link' : (not is_popup and has_delete_permission @@ -46,50 +41,35 @@ class SubmitRowNode(template.Node): 'show_save_and_add_another': not is_popup and (not save_as or add), 'show_save_and_continue': not is_popup, 'show_save': True - }, context); - context.pop() - return output; + } -class AdminFieldBoundNode(template.Node): - def __init__(self, argument): - self.argument = argument +srdec = inclusion_tag('admin_submit_line', takes_context=True) +submit_row = srdec(submit_row) + +#@simple_tag +def field_label(bound_field): + class_names = [] + if isinstance(bound_field.field, meta.BooleanField): + class_names.append("vCheckboxLabel") + else: + if not bound_field.field.blank: + class_names.append('required') + if not bound_field.first: + class_names.append('inline') - def render(self, context): - argument_val = template.resolve_variable(self.argument, context) - if (isinstance(argument_val, list)): - bound_fields = argument_val - else: - bound_fields = [argument_val] - add = context['add'] - change = context['change'] - - context.push() - context['bound_fields'] = bound_fields - context['class_names'] = " ".join(self.get_class_names(bound_fields)) - t = template_loader.get_template("admin_field") - output = t.render(context) - context.pop() - - return output + class_str = class_names and ' class="%s"' % ' '.join(class_names) or '' + return ' ' % \ + (bound_field.element_id, class_str, + capfirst(bound_field.field.verbose_name) ) +field_label = simple_tag(field_label) - def get_class_names(self, bound_fields): - class_names = ['form-row'] - for bound_field in bound_fields: - for f in bound_field.form_fields: - if f.errors(): - class_names.append('errors') - break - - # Assumes BooleanFields won't be stacked next to each other! - if isinstance(bound_fields[0].field, meta.BooleanField): - class_names.append('checkbox-row') - - return class_names - class FieldWidgetNode(template.Node): def __init__(self, bound_field_var): self.bound_field_var = bound_field_var + self.nodelists = {} + t = template_loader.get_template("widget/default") + self.default = t.nodelist def render(self, context): @@ -100,22 +80,26 @@ class FieldWidgetNode(template.Node): context.push() context['bound_field'] = bound_field klass = bound_field.field.__class__ - t = None - while klass: - try: - field_class_name = klass.__name__ - template_name = "widget/%s" % \ - class_name_to_underscored(field_class_name) - - t = template_loader.get_template(template_name) - break - except template.TemplateDoesNotExist: - klass = (len(klass.__bases__) > 0) and klass.__bases__[0] or None - - if t == None: - t = template_loader.get_template("widget/default") - - output = t.render(context) + if not self.nodelists.has_key(klass): + t = None + while klass: + try: + field_class_name = klass.__name__ + template_name = "widget/%s" % \ + class_name_to_underscored(field_class_name) + t = template_loader.get_template(template_name) + break + except template.TemplateDoesNotExist: + klass = bool(klass.__bases__) and klass.__bases__[0] or None + + if t == None: + nodelist = self.default + else: + nodelist = t.nodelist + + self.nodelists[klass] = nodelist + + output = self.nodelists[klass].render(context) context.pop() return output @@ -177,71 +161,38 @@ class EditInlineNode(template.Node): context['num_headers'] = len(field_wrapper_list) context['original_row_needed'] = max([fw.use_raw_id_admin() for fw in field_wrapper_list]) # context['name_prefix'] = "%s." % (var_name,) - -class FieldLabelNode(template.Node): - def __init__(self, bound_field_var): - self.bound_field_var = bound_field_var - - def render(self, context): - bound_field = template.resolve_variable(self.bound_field_var, context) - class_names = [] - if isinstance(bound_field.field, meta.BooleanField): - class_names.append("vCheckboxLabel") - else: - if not bound_field.field.blank: - class_names.append('required') - if not bound_field.first: - class_names.append('inline') - - class_str = class_names and ' class="%s"' % ' '.join(class_names) or '' - return ' ' % (bound_field.element_id, class_str, capfirst(bound_field.field.verbose_name) ) -class OutputAllNode(template.Node): - def __init__(self, form_fields_var): - self.form_fields_var = form_fields_var - - def render(self, context): - form_fields = template.resolve_variable(self.form_fields_var, context) - return ''.join([str(f) for f in form_fields]) -class AutoPopulatedFieldScriptNode(template.Node): - def __init__(self, auto_pop_var): - self.auto_pop_var = auto_pop_var +#@simple_tag +def output_all(form_fields): + return ''.join([str(f) for f in form_fields]) +output_all = simple_tag(output_all) - def render(self,context): - auto_pop_fields = template.resolve_variable(self.auto_pop_var, context) - change = context['change'] - for field in auto_pop_fields: - t = [] - if change: - t.append('document.getElementById("id_%s")._changed = true;' % field.name ) - else: - t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) - add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]) - for f in field.prepopulate_from: - t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if(e._changed) { e.value = URLify(%s, %s);} } ' % (f, field.name, add_values, field.maxlength) ) - - return ''.join(t) - -class FilterInterfaceScriptMaybeNode(template.Node): - def __init__(self, bound_field_var): - self.bound_field_var = bound_field_var - - def render(self, context): - bound_field = template.resolve_variable(self.bound_field_var, context) - f = bound_field.field - if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface: - return '\n' % (f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX) +#@simple_tag +def auto_populated_field_script(auto_pop_fields, change = False): + for field in auto_pop_fields: + t = [] + if change: + t.append('document.getElementById("id_%s")._changed = true;' % field.name ) else: - return '' + t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) - + add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]) + for f in field.prepopulate_from: + t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if(e._changed) { e.value = URLify(%s, %s);} } ' % (f, field.name, add_values, field.maxlength) ) + return ''.join(t) +auto_populated_field_script = simple_tag(auto_populated_field_script) -def do_submit_row(parser, token): - return SubmitRowNode() - +#@simple_tag +def filter_interface_script_maybe(bound_field): + f = bound_field.field + if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface: + return '\n' % (f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX) + else: + return '' +filter_interface_script_maybe = simple_tag(filter_interface_script_maybe) def do_one_arg_tag(node_factory, parser,token): tokens = token.contents.split() @@ -251,14 +202,8 @@ def do_one_arg_tag(node_factory, parser,token): one_arg_tag_nodes = [ - IncludeAdminScriptNode, - AdminFieldBoundNode, - FieldLabelNode, FieldWidgetNode, - OutputAllNode, EditInlineNode, - AutoPopulatedFieldScriptNode, - FilterInterfaceScriptMaybeNode, ] @@ -267,9 +212,39 @@ def register_one_arg_tag(node): parse_func = curry(do_one_arg_tag, node) template.register_tag(tag_name, parse_func) - - for node in one_arg_tag_nodes: register_one_arg_tag(node) -template.register_tag('submit_row', do_submit_row ) + +#@inclusion_tag('admin_field', takes_context=True) +def admin_field_bound(context, argument_val): + if (isinstance(argument_val, list)): + bound_fields = argument_val + else: + bound_fields = [argument_val] + add = context['add'] + change = context['change'] + + class_names = ['form-row'] + for bound_field in bound_fields: + for f in bound_field.form_fields: + if f.errors(): + class_names.append('errors') + break + + # Assumes BooleanFields won't be stacked next to each other! + if isinstance(bound_fields[0].field, meta.BooleanField): + class_names.append('checkbox-row') + + return { + 'add' : context['add'], + 'change' : context['change'], + 'bound_fields' : bound_fields, + 'class_names' : " ".join(class_names) + } + + +afbdec = inclusion_tag('admin_field', takes_context=True) +admin_field_bound = afbdec(admin_field_bound) + +