Author: jacob
Date: 2007-09-20 23:00:32 -0500 (Thu, 20 Sep 2007)
New Revision: 6399

Modified:
   django/trunk/django/contrib/admin/templatetags/admin_modify.py
   django/trunk/django/contrib/comments/templatetags/comments.py
   django/trunk/django/template/__init__.py
   django/trunk/django/template/defaultfilters.py
   django/trunk/django/template/defaulttags.py
   django/trunk/django/template/loader_tags.py
   django/trunk/django/templatetags/i18n.py
   django/trunk/docs/templates_python.txt
Log:
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.


Modified: django/trunk/django/contrib/admin/templatetags/admin_modify.py
===================================================================
--- django/trunk/django/contrib/admin/templatetags/admin_modify.py      
2007-09-20 23:56:54 UTC (rev 6398)
+++ django/trunk/django/contrib/admin/templatetags/admin_modify.py      
2007-09-21 04:00:32 UTC (rev 6399)
@@ -72,7 +72,7 @@
     default = None
 
     def __init__(self, bound_field_var):
-        self.bound_field_var = bound_field_var
+        self.bound_field_var = template.Variable(bound_field_var)
 
     def get_nodelist(cls, klass):
         if klass not in cls.nodelists:
@@ -96,7 +96,7 @@
     get_nodelist = classmethod(get_nodelist)
 
     def render(self, context):
-        bound_field = template.resolve_variable(self.bound_field_var, context)
+        bound_field = self.bound_field_var.resolve(context)
 
         context.push()
         context['bound_field'] = bound_field
@@ -156,10 +156,10 @@
 
 class EditInlineNode(template.Node):
     def __init__(self, rel_var):
-        self.rel_var = rel_var
+        self.rel_var = template.Variable(rel_var)
 
     def render(self, context):
-        relation = template.resolve_variable(self.rel_var, context)
+        relation = self.rel_var.resolve(context)
         context.push()
         if relation.field.rel.edit_inline == models.TABULAR:
             bound_related_object_class = TabularBoundRelatedObject

Modified: django/trunk/django/contrib/comments/templatetags/comments.py
===================================================================
--- django/trunk/django/contrib/comments/templatetags/comments.py       
2007-09-20 23:56:54 UTC (rev 6398)
+++ django/trunk/django/contrib/comments/templatetags/comments.py       
2007-09-21 04:00:32 UTC (rev 6399)
@@ -19,6 +19,8 @@
         ratings_optional=False, ratings_required=False, rating_options='',
         is_public=True):
         self.content_type = content_type
+        if obj_id_lookup_var is not None:
+            obj_id_lookup_var = template.Variable(obj_id_lookup_var)
         self.obj_id_lookup_var, self.obj_id, self.free = obj_id_lookup_var, 
obj_id, free
         self.photos_optional, self.photos_required = photos_optional, 
photos_required
         self.ratings_optional, self.ratings_required = ratings_optional, 
ratings_required
@@ -32,7 +34,7 @@
         context.push()
         if self.obj_id_lookup_var is not None:
             try:
-                self.obj_id = 
template.resolve_variable(self.obj_id_lookup_var, context)
+                self.obj_id = self.obj_id_lookup_var.resolve(context)
             except template.VariableDoesNotExist:
                 return ''
             # Validate that this object ID is valid for this content-type.
@@ -75,6 +77,8 @@
 class CommentCountNode(template.Node):
     def __init__(self, package, module, context_var_name, obj_id, var_name, 
free):
         self.package, self.module = package, module
+        if context_var_name is not None:
+            context_var_name = template.Variable(context_var_name)
         self.context_var_name, self.obj_id = context_var_name, obj_id
         self.var_name, self.free = var_name, free
 
@@ -82,7 +86,7 @@
         from django.conf import settings
         manager = self.free and FreeComment.objects or Comment.objects
         if self.context_var_name is not None:
-            self.obj_id = template.resolve_variable(self.context_var_name, 
context)
+            self.obj_id = self.context_var_name.resolve(context)
         comment_count = manager.filter(object_id__exact=self.obj_id,
             content_type__app_label__exact=self.package,
             content_type__model__exact=self.module, 
site__id__exact=settings.SITE_ID).count()
@@ -92,6 +96,8 @@
 class CommentListNode(template.Node):
     def __init__(self, package, module, context_var_name, obj_id, var_name, 
free, ordering, extra_kwargs=None):
         self.package, self.module = package, module
+        if context_var_name is not None:
+            context_var_name = template.Variable(context_var_name)
         self.context_var_name, self.obj_id = context_var_name, obj_id
         self.var_name, self.free = var_name, free
         self.ordering = ordering
@@ -102,7 +108,7 @@
         get_list_function = self.free and FreeComment.objects.filter or 
Comment.objects.get_list_with_karma
         if self.context_var_name is not None:
             try:
-                self.obj_id = template.resolve_variable(self.context_var_name, 
context)
+                self.obj_id = self.context_var_name.resolve(context)
             except template.VariableDoesNotExist:
                 return ''
         kwargs = {

Modified: django/trunk/django/template/__init__.py
===================================================================
--- django/trunk/django/template/__init__.py    2007-09-20 23:56:54 UTC (rev 
6398)
+++ django/trunk/django/template/__init__.py    2007-09-21 04:00:32 UTC (rev 
6399)
@@ -88,8 +88,6 @@
 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), 
re.escape(BLOCK_TAG_END),
                                           re.escape(VARIABLE_TAG_START), 
re.escape(VARIABLE_TAG_END),
                                           re.escape(COMMENT_TAG_START), 
re.escape(COMMENT_TAG_END)))
-# matches if the string is valid number
-number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$')
 
 # global dictionary of libraries that have been loaded using get_library
 libraries = {}
@@ -564,18 +562,19 @@
                 elif constant_arg is not None:
                     args.append((False, constant_arg.replace(r'\"', '"')))
                 elif var_arg:
-                    args.append((True, var_arg))
+                    args.append((True, Variable(var_arg)))
                 filter_func = parser.find_filter(filter_name)
                 self.args_check(filter_name,filter_func, args)
                 filters.append( (filter_func,args))
                 upto = match.end()
         if upto != len(token):
             raise TemplateSyntaxError, "Could not parse the remainder: '%s' 
from '%s'" % (token[upto:], token)
-        self.var, self.filters = var, filters
+        self.filters = filters
+        self.var = Variable(var)
 
     def resolve(self, context, ignore_failures=False):
         try:
-            obj = resolve_variable(self.var, context)
+            obj = self.var.resolve(context)
         except VariableDoesNotExist:
             if ignore_failures:
                 obj = None
@@ -595,7 +594,7 @@
                 if not lookup:
                     arg_vals.append(arg)
                 else:
-                    arg_vals.append(resolve_variable(arg, context))
+                    arg_vals.append(arg.resolve(context))
             obj = func(obj, *arg_vals)
         return obj
 
@@ -637,37 +636,98 @@
 def resolve_variable(path, context):
     """
     Returns the resolved variable, which may contain attribute syntax, within
-    the given context. The variable may be a hard-coded string (if it begins
-    and ends with single or double quote marks).
+    the given context.
+    
+    Deprecated; use the Variable class instead.
+    """
+    return Variable(path).resolve(context)
 
-    >>> c = {'article': {'section':'News'}}
-    >>> resolve_variable('article.section', c)
-    u'News'
-    >>> resolve_variable('article', c)
-    {'section': 'News'}
-    >>> class AClass: pass
-    >>> c = AClass()
-    >>> c.article = AClass()
-    >>> c.article.section = 'News'
-    >>> resolve_variable('article.section', c)
-    u'News'
+class Variable(object):
+    """
+    A template variable, resolvable against a given context. The variable may 
be
+    a hard-coded string (if it begins and ends with single or double quote
+    marks)::
+    
+        >>> c = {'article': {'section':'News'}}
+        >>> Variable('article.section').resolve(c)
+        u'News'
+        >>> Variable('article').resolve(c)
+        {'section': 'News'}
+        >>> class AClass: pass
+        >>> c = AClass()
+        >>> c.article = AClass()
+        >>> c.article.section = 'News'
+        >>> Variable('article.section').resolve(c)
+        u'News'
 
     (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
     """
-    if number_re.match(path):
-        number_type = '.' in path and float or int
-        current = number_type(path)
-    elif path[0] in ('"', "'") and path[0] == path[-1]:
-        current = path[1:-1]
-    else:
+    
+    def __init__(self, var):
+        self.var = var
+        self.literal = None
+        self.lookups = None
+        
+        try:
+            # First try to treat this variable as a number.
+            #
+            # Note that this could cause an OverflowError here that we're not 
+            # catching. Since this should only happen at compile time, that's
+            # probably OK.
+            self.literal = float(var)
+        
+            # So it's a float... is it an int? If the original value contained 
a
+            # dot or an "e" then it was a float, not an int.
+            if '.' not in var and 'e' not in var.lower():
+                self.literal = int(self.literal)
+                
+            # "2." is invalid
+            if var.endswith('.'):
+                raise ValueError
+
+        except ValueError:
+            # A ValueError means that the variable isn't a number.
+            # If it's wrapped with quotes (single or double), then
+            # we're also dealing with a literal.
+            if var[0] in "\"'" and var[0] == var[-1]:
+                self.literal = var[1:-1]
+            
+            else:
+                # Otherwise we'll set self.lookups so that resolve() knows 
we're
+                # dealing with a bonafide variable
+                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
+    
+    def resolve(self, context):
+        """Resolve this variable against a given context."""
+        if self.lookups is not None:
+            # We're dealing with a variable that needs to be resolved
+            return self._resolve_lookup(context)
+        else:
+            # We're dealing with a literal, so it's already been "resolved"
+            return self.literal
+            
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, self.var)
+    
+    def __str__(self):
+        return self.var
+
+    def _resolve_lookup(self, context):
+        """
+        Performs resolution of a real variable (i.e. not a literal) against the
+        given context. 
+        
+        As indicated by the method's name, this method is an implementation
+        detail and shouldn't be called by external code. Use Variable.resolve()
+        instead.
+        """
         current = context
-        bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
-        while bits:
+        for bit in self.lookups:
             try: # dictionary lookup
-                current = current[bits[0]]
+                current = current[bit]
             except (TypeError, AttributeError, KeyError):
                 try: # attribute lookup
-                    current = getattr(current, bits[0])
+                    current = getattr(current, bit)
                     if callable(current):
                         if getattr(current, 'alters_data', False):
                             current = settings.TEMPLATE_STRING_IF_INVALID
@@ -685,27 +745,27 @@
                                     raise
                 except (TypeError, AttributeError):
                     try: # list-index lookup
-                        current = current[int(bits[0])]
+                        current = current[int(bit)]
                     except (IndexError, # list index out of range
                             ValueError, # invalid literal for int()
-                            KeyError,   # current is a dict without 
`int(bits[0])` key
+                            KeyError,   # current is a dict without `int(bit)` 
key
                             TypeError,  # unsubscriptable object
                             ):
-                        raise VariableDoesNotExist("Failed lookup for key [%s] 
in %r", (bits[0], current)) # missing attribute
+                        raise VariableDoesNotExist("Failed lookup for key [%s] 
in %r", (bit, current)) # missing attribute
                 except Exception, e:
                     if getattr(e, 'silent_variable_failure', False):
                         current = settings.TEMPLATE_STRING_IF_INVALID
                     else:
                         raise
-            del bits[0]
-    if isinstance(current, (basestring, Promise)):
-        try:
-            current = force_unicode(current)
-        except UnicodeDecodeError:
-            # Failing to convert to unicode can happen sometimes (e.g. debug
-            # tracebacks). So we allow it in this particular instance.
-            pass
-    return current
+    
+        if isinstance(current, (basestring, Promise)):
+            try:
+                current = force_unicode(current)
+            except UnicodeDecodeError:
+                # Failing to convert to unicode can happen sometimes (e.g. 
debug
+                # tracebacks). So we allow it in this particular instance.
+                pass
+        return current
 
 class Node(object):
     def render(self, context):
@@ -861,10 +921,10 @@
 
         class SimpleNode(Node):
             def __init__(self, vars_to_resolve):
-                self.vars_to_resolve = vars_to_resolve
+                self.vars_to_resolve = map(Variable, vars_to_resolve)
 
             def render(self, context):
-                resolved_vars = [resolve_variable(var, context) for var in 
self.vars_to_resolve]
+                resolved_vars = [var.resolve(context) for var in 
self.vars_to_resolve]
                 return func(*resolved_vars)
 
         compile_func = curry(generic_tag_compiler, params, defaults, 
getattr(func, "_decorated_function", func).__name__, SimpleNode)
@@ -883,10 +943,10 @@
 
             class InclusionNode(Node):
                 def __init__(self, vars_to_resolve):
-                    self.vars_to_resolve = vars_to_resolve
+                    self.vars_to_resolve = map(Variable, vars_to_resolve)
 
                 def render(self, context):
-                    resolved_vars = [resolve_variable(var, context) for var in 
self.vars_to_resolve]
+                    resolved_vars = [var.resolve(context) for var in 
self.vars_to_resolve]
                     if takes_context:
                         args = [context] + resolved_vars
                     else:

Modified: django/trunk/django/template/defaultfilters.py
===================================================================
--- django/trunk/django/template/defaultfilters.py      2007-09-20 23:56:54 UTC 
(rev 6398)
+++ django/trunk/django/template/defaultfilters.py      2007-09-21 04:00:32 UTC 
(rev 6399)
@@ -1,6 +1,6 @@
 "Default variable filters"
 
-from django.template import resolve_variable, Library
+from django.template import Variable, Library
 from django.conf import settings
 from django.utils.translation import ugettext, ungettext
 from django.utils.encoding import force_unicode, smart_str, iri_to_uri
@@ -297,7 +297,8 @@
     Takes a list of dicts, returns that list sorted by the property given in
     the argument.
     """
-    decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for 
item in value]
+    var_resolve = Variable(arg).resolve
+    decorated = [(var_resolve(item), item) for item in value]
     decorated.sort()
     return [item[1] for item in decorated]
 
@@ -306,7 +307,8 @@
     Takes a list of dicts, returns that list sorted in reverse order by the
     property given in the argument.
     """
-    decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for 
item in value]
+    var_resolve = Variable(arg).resolve
+    decorated = [(var_resolve(item), item) for item in value]
     decorated.sort()
     decorated.reverse()
     return [item[1] for item in decorated]

Modified: django/trunk/django/template/defaulttags.py
===================================================================
--- django/trunk/django/template/defaulttags.py 2007-09-20 23:56:54 UTC (rev 
6398)
+++ django/trunk/django/template/defaulttags.py 2007-09-21 04:00:32 UTC (rev 
6399)
@@ -1,6 +1,6 @@
 "Default tags used by the template system, available to all templates."
 
-from django.template import Node, NodeList, Template, Context, resolve_variable
+from django.template import Node, NodeList, Template, Context, Variable
 from 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
 from django.template import get_library, Library, InvalidTemplateLibrary
 from django.conf import settings
@@ -30,7 +30,7 @@
     def render(self, context):
         self.counter += 1
         value = self.cyclevars[self.counter % self.cyclevars_len]
-        value = resolve_variable(value, context)
+        value = Variable(value).resolve(context)
         if self.variable_name:
             context[self.variable_name] = value
         return value
@@ -57,12 +57,12 @@
 
 class FirstOfNode(Node):
     def __init__(self, vars):
-        self.vars = vars
+        self.vars = map(Variable, vars)
 
     def render(self, context):
         for var in self.vars:
             try:
-                value = resolve_variable(var, context)
+                value = var.resolve(context)
             except VariableDoesNotExist:
                 continue
             if value:
@@ -147,7 +147,7 @@
     def __init__(self, nodelist, *varlist):
         self.nodelist = nodelist
         self._last_seen = None
-        self._varlist = varlist
+        self._varlist = map(Variable, varlist)
 
     def render(self, context):
         if 'forloop' in context and context['forloop']['first']:
@@ -156,7 +156,7 @@
             if self._varlist:
                 # Consider multiple parameters.
                 # This automatically behaves like a OR evaluation of the 
multiple variables.
-                compare_to = [resolve_variable(var, context) for var in 
self._varlist]
+                compare_to = [var.resolve(context) for var in self._varlist]
             else:
                 compare_to = self.nodelist.render(context)
         except VariableDoesNotExist:
@@ -175,7 +175,7 @@
 
 class IfEqualNode(Node):
     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
-        self.var1, self.var2 = var1, var2
+        self.var1, self.var2 = Variable(var1), Variable(var2)
         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
         self.negate = negate
 
@@ -184,11 +184,11 @@
 
     def render(self, context):
         try:
-            val1 = resolve_variable(self.var1, context)
+            val1 = self.var1.resolve(context)
         except VariableDoesNotExist:
             val1 = None
         try:
-            val2 = resolve_variable(self.var2, context)
+            val2 = self.var2.resolve(context)
         except VariableDoesNotExist:
             val2 = None
         if (self.negate and val1 != val2) or (not self.negate and val1 == 
val2):

Modified: django/trunk/django/template/loader_tags.py
===================================================================
--- django/trunk/django/template/loader_tags.py 2007-09-20 23:56:54 UTC (rev 
6398)
+++ django/trunk/django/template/loader_tags.py 2007-09-21 04:00:32 UTC (rev 
6399)
@@ -1,4 +1,4 @@
-from django.template import TemplateSyntaxError, TemplateDoesNotExist, 
resolve_variable
+from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
 from django.template import Library, Node
 from django.template.loader import get_template, get_template_from_string, 
find_template_source
 from django.conf import settings
@@ -99,11 +99,11 @@
 
 class IncludeNode(Node):
     def __init__(self, template_name):
-        self.template_name = template_name
+        self.template_name = Variable(template_name)
 
     def render(self, context):
         try:
-            template_name = resolve_variable(self.template_name, context)
+            template_name = self.template_name.resolve(context)
             t = get_template(template_name)
             return t.render(context)
         except TemplateSyntaxError, e:

Modified: django/trunk/django/templatetags/i18n.py
===================================================================
--- django/trunk/django/templatetags/i18n.py    2007-09-20 23:56:54 UTC (rev 
6398)
+++ django/trunk/django/templatetags/i18n.py    2007-09-21 04:00:32 UTC (rev 
6399)
@@ -1,4 +1,4 @@
-from django.template import Node, resolve_variable
+from django.template import Node, Variable
 from django.template import TemplateSyntaxError, TokenParser, Library
 from django.template import TOKEN_TEXT, TOKEN_VAR
 from django.utils import translation
@@ -32,11 +32,11 @@
 
 class TranslateNode(Node):
     def __init__(self, value, noop):
-        self.value = value
+        self.value = Variable(value)
         self.noop = noop
 
     def render(self, context):
-        value = resolve_variable(self.value, context)
+        value = self.value.resolve(context)
         if self.noop:
             return value
         else:

Modified: django/trunk/docs/templates_python.txt
===================================================================
--- django/trunk/docs/templates_python.txt      2007-09-20 23:56:54 UTC (rev 
6398)
+++ django/trunk/docs/templates_python.txt      2007-09-21 04:00:32 UTC (rev 
6399)
@@ -928,11 +928,37 @@
 ``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
 format it accordingly.
 
-.. note::
-    The ``resolve_variable()`` function will throw a ``VariableDoesNotExist``
-    exception if it cannot resolve the string passed to it in the current
-    context of the page.
+.. admonition:: New in development version:
 
+    Variable resolution has changed in the development version of Django.
+    ``template.resolve_variable()`` is still available, but has been deprecated
+    in favor of a new ``template.Variable`` class. Using this class will 
usually
+    be more efficient than calling ``template.resolve_variable``
+    
+    To use the ``Variable`` class, simply instantiate it with the name of the
+    variable to be resolved, and then call ``variable.resolve(context)``. So,
+    in the development version, the above example would be more correctly
+    written as:
+    
+    .. parsed-literal::
+    
+        class FormatTimeNode(template.Node):
+            def __init__(self, date_to_be_formatted, format_string):
+                self.date_to_be_formatted = **Variable(date_to_be_formatted)**
+                self.format_string = format_string
+        
+            def render(self, context):
+                try:
+                    actual_date = 
**self.date_to_be_formatted.resolve(context)**
+                    return actual_date.strftime(self.format_string)
+                except template.VariableDoesNotExist:
+                    return ''
+    
+    Changes are highlighted in bold.
+
+Variable resolution will throw a ``VariableDoesNotExist`` exception if it 
cannot
+resolve the string passed to it in the current context of the page.
+
 Shortcut for simple tags
 ~~~~~~~~~~~~~~~~~~~~~~~~
 


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to