Ticket #3453: resolve-variable-v3.diff

File resolve-variable-v3.diff, 22.2 KB (added by jacob, 8 years ago)

An updated and somewhat improved version of the patch in which Variable becomes an actual class.

  • django/templatetags/i18n.py

     
    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
    44from django.utils import translation
     
    3232
    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
    4242        else:
  • django/contrib/comments/templatetags/comments.py

     
    1919        ratings_optional=False, ratings_required=False, rating_options='',
    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
    2426        self.ratings_optional, self.ratings_required = ratings_optional, ratings_required
     
    3234        context.push()
    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 ''
    3840            # Validate that this object ID is valid for this content-type.
     
    7577class CommentCountNode(template.Node):
    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
    8084
     
    8286        from django.conf import settings
    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,
    8892            content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count()
     
    9296class CommentListNode(template.Node):
    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
    97103        self.ordering = ordering
     
    102108        get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma
    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 ''
    108114        kwargs = {
  • django/contrib/admin/templatetags/admin_modify.py

     
    7272    default = None
    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):
    7878        if klass not in cls.nodelists:
     
    9696    get_nodelist = classmethod(get_nodelist)
    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()
    102102        context['bound_field'] = bound_field
     
    156156
    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:
    165165            bound_related_object_class = TabularBoundRelatedObject
  • django/template/defaultfilters.py

     
    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
    66from django.utils.encoding import force_unicode, smart_str, iri_to_uri
     
    297297    Takes a list of dicts, returns that list sorted by the property given in
    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]
    303304
     
    306307    Takes a list of dicts, returns that list sorted in reverse order by the
    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()
    312314    return [item[1] for item in decorated]
  • django/template/__init__.py

     
    8888tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    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
    9593libraries = {}
     
    564562                elif constant_arg is not None:
    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)
    570568                filters.append( (filter_func,args))
    571569                upto = match.end()
    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:
    581580                obj = None
     
    595594                if not lookup:
    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
    601600
     
    637636def resolve_variable(path, context):
    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).
     639    the given context.
     640   
     641    Deprecated; use the Variable class instead.
     642    """
     643    return Variable(path).resolve(context)
    642644
    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'
     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):
    673733                            current = settings.TEMPLATE_STRING_IF_INVALID
     
    685745                                    raise
    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):
    697757                        current = settings.TEMPLATE_STRING_IF_INVALID
    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):
    711771    def render(self, context):
     
    861921
    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
    870930        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
     
    883943
    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
    892952                    else:
  • django/template/defaulttags.py

     
    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
    66from django.conf import settings
     
    3030    def render(self, context):
    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
    3636        return value
     
    5757
    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
    6868            if value:
     
    147147    def __init__(self, nodelist, *varlist):
    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):
    153153        if 'forloop' in context and context['forloop']['first']:
     
    156156            if self._varlist:
    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)
    162162        except VariableDoesNotExist:
     
    175175
    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
    181181
     
    184184
    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
    194194        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
  • django/template/loader_tags.py

     
    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
    44from django.conf import settings
     
    9999
    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)
    109109        except TemplateSyntaxError, e:
  • docs/templates_python.txt

     
    928928``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
    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:
    935932
     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.
     961
    936962Shortcut for simple tags
    937963~~~~~~~~~~~~~~~~~~~~~~~~
    938964
Back to Top