Django

Code

Changeset 6399

Show
Ignore:
Timestamp:
09/20/07 23:00:32 (1 year ago)
Author:
jacob
Message:

Fixed #3453: introduced a new template variable resolution system by Brian Harring (thanks!). The upshot is that variable resolution is about 25% faster, and you should see a measurable performance increase any time you've got long or deeply nested loops.

Variable resolution has changed behind the scenes -- see the note in templates_python.txt -- but template.resolve_variable() still exists. This should be fully backwards-compatible.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/templatetags/admin_modify.py

    r5803 r6399  
    7373 
    7474    def __init__(self, bound_field_var): 
    75         self.bound_field_var = bound_field_var 
     75        self.bound_field_var = template.Variable(bound_field_var) 
    7676 
    7777    def get_nodelist(cls, klass): 
     
    9797 
    9898    def render(self, context): 
    99         bound_field = template.resolve_variable(self.bound_field_var, context) 
     99        bound_field = self.bound_field_var.resolve(context) 
    100100 
    101101        context.push() 
     
    157157class EditInlineNode(template.Node): 
    158158    def __init__(self, rel_var): 
    159         self.rel_var = rel_var 
     159        self.rel_var = template.Variable(rel_var) 
    160160 
    161161    def render(self, context): 
    162         relation = template.resolve_variable(self.rel_var, context) 
     162        relation = self.rel_var.resolve(context) 
    163163        context.push() 
    164164        if relation.field.rel.edit_inline == models.TABULAR: 
  • django/trunk/django/contrib/comments/templatetags/comments.py

    r5848 r6399  
    2020        is_public=True): 
    2121        self.content_type = content_type 
     22        if obj_id_lookup_var is not None: 
     23            obj_id_lookup_var = template.Variable(obj_id_lookup_var) 
    2224        self.obj_id_lookup_var, self.obj_id, self.free = obj_id_lookup_var, obj_id, free 
    2325        self.photos_optional, self.photos_required = photos_optional, photos_required 
     
    3335        if self.obj_id_lookup_var is not None: 
    3436            try: 
    35                 self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context) 
     37                self.obj_id = self.obj_id_lookup_var.resolve(context) 
    3638            except template.VariableDoesNotExist: 
    3739                return '' 
     
    7678    def __init__(self, package, module, context_var_name, obj_id, var_name, free): 
    7779        self.package, self.module = package, module 
     80        if context_var_name is not None: 
     81            context_var_name = template.Variable(context_var_name) 
    7882        self.context_var_name, self.obj_id = context_var_name, obj_id 
    7983        self.var_name, self.free = var_name, free 
     
    8387        manager = self.free and FreeComment.objects or Comment.objects 
    8488        if self.context_var_name is not None: 
    85             self.obj_id = template.resolve_variable(self.context_var_name, context) 
     89            self.obj_id = self.context_var_name.resolve(context) 
    8690        comment_count = manager.filter(object_id__exact=self.obj_id, 
    8791            content_type__app_label__exact=self.package, 
     
    9397    def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None): 
    9498        self.package, self.module = package, module 
     99        if context_var_name is not None: 
     100            context_var_name = template.Variable(context_var_name) 
    95101        self.context_var_name, self.obj_id = context_var_name, obj_id 
    96102        self.var_name, self.free = var_name, free 
     
    103109        if self.context_var_name is not None: 
    104110            try: 
    105                 self.obj_id = template.resolve_variable(self.context_var_name, context) 
     111                self.obj_id = self.context_var_name.resolve(context) 
    106112            except template.VariableDoesNotExist: 
    107113                return '' 
  • django/trunk/django/template/defaultfilters.py

    r6223 r6399  
    11"Default variable filters" 
    22 
    3 from django.template import resolve_variable, Library 
     3from django.template import Variable, Library 
    44from django.conf import settings 
    55from django.utils.translation import ugettext, ungettext 
     
    298298    the argument. 
    299299    """ 
    300     decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value] 
     300    var_resolve = Variable(arg).resolve 
     301    decorated = [(var_resolve(item), item) for item in value] 
    301302    decorated.sort() 
    302303    return [item[1] for item in decorated] 
     
    307308    property given in the argument. 
    308309    """ 
    309     decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value] 
     310    var_resolve = Variable(arg).resolve 
     311    decorated = [(var_resolve(item), item) for item in value] 
    310312    decorated.sort() 
    311313    decorated.reverse() 
  • django/trunk/django/template/defaulttags.py

    r6289 r6399  
    11"Default tags used by the template system, available to all templates." 
    22 
    3 from django.template import Node, NodeList, Template, Context, resolve_variable 
     3from django.template import Node, NodeList, Template, Context, Variable 
    44from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 
    55from django.template import get_library, Library, InvalidTemplateLibrary 
     
    3131        self.counter += 1 
    3232        value = self.cyclevars[self.counter % self.cyclevars_len] 
    33         value = resolve_variable(value, context) 
     33        value = Variable(value).resolve(context) 
    3434        if self.variable_name: 
    3535            context[self.variable_name] = value 
     
    5858class FirstOfNode(Node): 
    5959    def __init__(self, vars): 
    60         self.vars = vars 
     60        self.vars = map(Variable, vars) 
    6161 
    6262    def render(self, context): 
    6363        for var in self.vars: 
    6464            try: 
    65                 value = resolve_variable(var, context) 
     65                value = var.resolve(context) 
    6666            except VariableDoesNotExist: 
    6767                continue 
     
    148148        self.nodelist = nodelist 
    149149        self._last_seen = None 
    150         self._varlist = varlist 
     150        self._varlist = map(Variable, varlist) 
    151151 
    152152    def render(self, context): 
     
    157157                # Consider multiple parameters. 
    158158                # This automatically behaves like a OR evaluation of the multiple variables. 
    159                 compare_to = [resolve_variable(var, context) for var in self._varlist] 
     159                compare_to = [var.resolve(context) for var in self._varlist] 
    160160            else: 
    161161                compare_to = self.nodelist.render(context) 
     
    176176class IfEqualNode(Node): 
    177177    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 
    178         self.var1, self.var2 = var1, var2 
     178        self.var1, self.var2 = Variable(var1), Variable(var2) 
    179179        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 
    180180        self.negate = negate 
     
    185185    def render(self, context): 
    186186        try: 
    187             val1 = resolve_variable(self.var1, context) 
     187            val1 = self.var1.resolve(context) 
    188188        except VariableDoesNotExist: 
    189189            val1 = None 
    190190        try: 
    191             val2 = resolve_variable(self.var2, context) 
     191            val2 = self.var2.resolve(context) 
    192192        except VariableDoesNotExist: 
    193193            val2 = None 
  • django/trunk/django/template/__init__.py

    r6268 r6399  
    8989                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 
    9090                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) 
    91 # matches if the string is valid number 
    92 number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$') 
    9391 
    9492# global dictionary of libraries that have been loaded using get_library 
     
    565563                    args.append((False, constant_arg.replace(r'\"', '"'))) 
    566564                elif var_arg: 
    567                     args.append((True, var_arg)) 
     565                    args.append((True, Variable(var_arg))) 
    568566                filter_func = parser.find_filter(filter_name) 
    569567                self.args_check(filter_name,filter_func, args) 
     
    572570        if upto != len(token): 
    573571            raise TemplateSyntaxError, "Could not parse the remainder: '%s' from '%s'" % (token[upto:], token) 
    574         self.var, self.filters = var, filters 
     572        self.filters = filters 
     573        self.var = Variable(var) 
    575574 
    576575    def resolve(self, context, ignore_failures=False): 
    577576        try: 
    578             obj = resolve_variable(self.var, context) 
     577            obj = self.var.resolve(context) 
    579578        except VariableDoesNotExist: 
    580579            if ignore_failures: 
     
    596595                    arg_vals.append(arg) 
    597596                else: 
    598                     arg_vals.append(resolve_variable(arg, context)) 
     597                    arg_vals.append(arg.resolve(context)) 
    599598            obj = func(obj, *arg_vals) 
    600599        return obj 
     
    638637    """ 
    639638    Returns the resolved variable, which may contain attribute syntax, within 
    640     the given context. The variable may be a hard-coded string (if it begins 
    641     and ends with single or double quote marks). 
    642  
    643     >>> c = {'article': {'section':'News'}} 
    644     >>> resolve_variable('article.section', c) 
    645     u'News' 
    646     >>> resolve_variable('article', c) 
    647     {'section': 'News'} 
    648     >>> class AClass: pass 
    649     >>> c = AClass() 
    650     >>> c.article = AClass() 
    651     >>> c.article.section = 'News' 
    652     >>> resolve_variable('article.section', c) 
    653     u'News' 
     639    the given context. 
     640     
     641    Deprecated; use the Variable class instead. 
     642    """ 
     643    return Variable(path).resolve(context) 
     644 
     645class Variable(object): 
     646    """ 
     647    A template variable, resolvable against a given context. The variable may be 
     648    a hard-coded string (if it begins and ends with single or double quote 
     649    marks):: 
     650     
     651        >>> c = {'article': {'section':'News'}} 
     652        >>> Variable('article.section').resolve(c) 
     653        u'News' 
     654        >>> Variable('article').resolve(c) 
     655        {'section': 'News'} 
     656        >>> class AClass: pass 
     657        >>> c = AClass() 
     658        >>> c.article = AClass() 
     659        >>> c.article.section = 'News' 
     660        >>> Variable('article.section').resolve(c) 
     661        u'News' 
    654662 
    655663    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 
    656664    """ 
    657     if number_re.match(path): 
    658         number_type = '.' in path and float or int 
    659         current = number_type(path) 
    660     elif path[0] in ('"', "'") and path[0] == path[-1]: 
    661         current = path[1:-1] 
    662     else: 
     665     
     666    def __init__(self, var): 
     667        self.var = var 
     668        self.literal = None 
     669        self.lookups = None 
     670         
     671        try: 
     672            # First try to treat this variable as a number. 
     673            # 
     674            # Note that this could cause an OverflowError here that we're not  
     675            # catching. Since this should only happen at compile time, that's 
     676            # probably OK. 
     677            self.literal = float(var) 
     678         
     679            # So it's a float... is it an int? If the original value contained a 
     680            # dot or an "e" then it was a float, not an int. 
     681            if '.' not in var and 'e' not in var.lower(): 
     682                self.literal = int(self.literal) 
     683                 
     684            # "2." is invalid 
     685            if var.endswith('.'): 
     686                raise ValueError 
     687 
     688        except ValueError: 
     689            # A ValueError means that the variable isn't a number. 
     690            # If it's wrapped with quotes (single or double), then 
     691            # we're also dealing with a literal. 
     692            if var[0] in "\"'" and var[0] == var[-1]: 
     693                self.literal = var[1:-1] 
     694             
     695            else: 
     696                # Otherwise we'll set self.lookups so that resolve() knows we're 
     697                # dealing with a bonafide variable 
     698                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) 
     699     
     700    def resolve(self, context): 
     701        """Resolve this variable against a given context.""" 
     702        if self.lookups is not None: 
     703            # We're dealing with a variable that needs to be resolved 
     704            return self._resolve_lookup(context) 
     705        else: 
     706            # We're dealing with a literal, so it's already been "resolved" 
     707            return self.literal 
     708             
     709    def __repr__(self): 
     710        return "<%s: %r>" % (self.__class__.__name__, self.var) 
     711     
     712    def __str__(self): 
     713        return self.var 
     714 
     715    def _resolve_lookup(self, context): 
     716        """ 
     717        Performs resolution of a real variable (i.e. not a literal) against the 
     718        given context.  
     719         
     720        As indicated by the method's name, this method is an implementation 
     721        detail and shouldn't be called by external code. Use Variable.resolve() 
     722        instead. 
     723        """ 
    663724        current = context 
    664         bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR) 
    665         while bits: 
     725        for bit in self.lookups: 
    666726            try: # dictionary lookup 
    667                 current = current[bits[0]
     727                current = current[bit
    668728            except (TypeError, AttributeError, KeyError): 
    669729                try: # attribute lookup 
    670                     current = getattr(current, bits[0]
     730                    current = getattr(current, bit
    671731                    if callable(current): 
    672732                        if getattr(current, 'alters_data', False): 
     
    686746                except (TypeError, AttributeError): 
    687747                    try: # list-index lookup 
    688                         current = current[int(bits[0])] 
     748                        current = current[int(bit)] 
    689749                    except (IndexError, # list index out of range 
    690750                            ValueError, # invalid literal for int() 
    691                             KeyError,   # current is a dict without `int(bits[0])` key 
     751                            KeyError,   # current is a dict without `int(bit)` key 
    692752                            TypeError,  # unsubscriptable object 
    693753                            ): 
    694                         raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute 
     754                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute 
    695755                except Exception, e: 
    696756                    if getattr(e, 'silent_variable_failure', False): 
     
    698758                    else: 
    699759                        raise 
    700             del bits[0] 
    701     if isinstance(current, (basestring, Promise)): 
    702         try: 
    703             current = force_unicode(current) 
    704         except UnicodeDecodeError: 
    705             # Failing to convert to unicode can happen sometimes (e.g. debug 
    706             # tracebacks). So we allow it in this particular instance. 
    707             pass 
    708     return current 
     760     
     761        if isinstance(current, (basestring, Promise)): 
     762            try: 
     763                current = force_unicode(current) 
     764            except UnicodeDecodeError: 
     765                # Failing to convert to unicode can happen sometimes (e.g. debug 
     766                # tracebacks). So we allow it in this particular instance. 
     767                pass 
     768        return current 
    709769 
    710770class Node(object): 
     
    862922        class SimpleNode(Node): 
    863923            def __init__(self, vars_to_resolve): 
    864                 self.vars_to_resolve = vars_to_resolve 
     924                self.vars_to_resolve = map(Variable, vars_to_resolve) 
    865925 
    866926            def render(self, context): 
    867                 resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] 
     927                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] 
    868928                return func(*resolved_vars) 
    869929 
     
    884944            class InclusionNode(Node): 
    885945                def __init__(self, vars_to_resolve): 
    886                     self.vars_to_resolve = vars_to_resolve 
     946                    self.vars_to_resolve = map(Variable, vars_to_resolve) 
    887947 
    888948                def render(self, context): 
    889                     resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] 
     949                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] 
    890950                    if takes_context: 
    891951                        args = [context] + resolved_vars 
  • django/trunk/django/template/loader_tags.py

    r5927 r6399  
    1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable 
     1from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable 
    22from django.template import Library, Node 
    33from django.template.loader import get_template, get_template_from_string, find_template_source 
     
    100100class IncludeNode(Node): 
    101101    def __init__(self, template_name): 
    102         self.template_name = template_name 
     102        self.template_name = Variable(template_name) 
    103103 
    104104    def render(self, context): 
    105105        try: 
    106             template_name = resolve_variable(self.template_name, context) 
     106            template_name = self.template_name.resolve(context) 
    107107            t = get_template(template_name) 
    108108            return t.render(context) 
  • django/trunk/django/templatetags/i18n.py

    r5609 r6399  
    1 from django.template import Node, resolve_variable 
     1from django.template import Node, Variable 
    22from django.template import TemplateSyntaxError, TokenParser, Library 
    33from django.template import TOKEN_TEXT, TOKEN_VAR 
     
    3333class TranslateNode(Node): 
    3434    def __init__(self, value, noop): 
    35         self.value = value 
     35        self.value = Variable(value) 
    3636        self.noop = noop 
    3737 
    3838    def render(self, context): 
    39         value = resolve_variable(self.value, context) 
     39        value = self.value.resolve(context) 
    4040        if self.noop: 
    4141            return value 
  • django/trunk/docs/templates_python.txt

    r6307 r6399  
    929929format it accordingly. 
    930930 
    931 .. note:: 
    932     The ``resolve_variable()`` function will throw a ``VariableDoesNotExist`` 
    933     exception if it cannot resolve the string passed to it in the current 
    934     context of the page. 
     931.. admonition:: New in development version: 
     932 
     933    Variable resolution has changed in the development version of Django. 
     934    ``template.resolve_variable()`` is still available, but has been deprecated 
     935    in favor of a new ``template.Variable`` class. Using this class will usually 
     936    be more efficient than calling ``template.resolve_variable`` 
     937     
     938    To use the ``Variable`` class, simply instantiate it with the name of the 
     939    variable to be resolved, and then call ``variable.resolve(context)``. So, 
     940    in the development version, the above example would be more correctly 
     941    written as: 
     942     
     943    .. parsed-literal:: 
     944     
     945        class FormatTimeNode(template.Node): 
     946            def __init__(self, date_to_be_formatted, format_string): 
     947                self.date_to_be_formatted = **Variable(date_to_be_formatted)** 
     948                self.format_string = format_string 
     949         
     950            def render(self, context): 
     951                try: 
     952                    actual_date = **self.date_to_be_formatted.resolve(context)** 
     953                    return actual_date.strftime(self.format_string) 
     954                except template.VariableDoesNotExist: 
     955                    return '' 
     956     
     957    Changes are highlighted in bold. 
     958 
     959Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot 
     960resolve the string passed to it in the current context of the page. 
    935961 
    936962Shortcut for simple tags