Django

Code

Ticket #3453: resolve-variable-v3.diff

File resolve-variable-v3.diff, 22.2 kB (added by jacob, 1 year ago)

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

  • django/templatetags/i18n.py

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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