Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Query a template for the variables it needs?

I'd like to be able to instantiate a template from a file (presumably using the django.template.loader.get_template(filename) ), and then determine the set of variables that should be defined in whatever context it is passed.

I thought there would be a method on the Template object, but there doesn't seem to be.

I read the docs, and the closest I found was this:

http://docs.djangoproject.com/en/1.0/topics/templates/#using-the-built-in-reference

which suggests going to the admin interface to see all the variables associated with a given view.

I don't want to go through the admin interface because I want to do this programmatically -- I'm trying to write tests.

I'm using Django version (1, 0, 2, 'final', 0)

Updated:

I tried synack's answer and found that (with the replacement of filter_expression.token by filter_expression.var, to get the actual name of the variable without tags and so on) it returned the variables that are defined locally in the template, but did not work for variables that are defined in the parent that it extends.

So for example, suppose I have templates in two files:

toyparent.html:

{%block base_results%}
Django is {{adjective}}
{%endblock base_results%}

toychild.html:

{% extends "toyparent.html" %}

{%block base_results%}
    {{block.super}}
    I {{verb}} it.
{%endblock base_results %}

And I load the child template:

>>> toy=django.template.loader.get_template('toychild.html')

This renders properly:

>>> toy.render(django.template.Context(dict(adjective='cool',verb='heart')))
u'\n    \nDjango is cool\n\n    I heart it.\n\n'

But I can't get the two variables from it:

>>> v=toy.nodelist.get_nodes_by_type(VariableNode)
>>> for k in v: print k.filter_expression.var
...
block.super
verb
like image 758
Vicki Laidler Avatar asked Dec 03 '09 22:12

Vicki Laidler


1 Answers

You are able to visually inspect a template and observe the presence of any "Variable Node" objects in that template's nodelist:

>>> from django.template import Template, Context
>>> t = Template("Django is {{ adjective }} and I {{ verb }} it.")
>>> t.nodelist
[<Text Node: 'Django is '>, <Variable Node: adjective>, <Text Node: ' and I '>, <Variable Node: verb>, <Text Node: ' it.'>]

These are of the type VariableNode, which is a class that can be directly imported for use in comparisons. Any Node instance has a get_nodes_by_type() method that can be called against a nodelist, which return all nodes of that type for the template. Example:

>>> from django.template import VariableNode
>>> varnodes = t.nodelist.get_nodes_by_type(VariableNode)
>>> varnodes
[<Variable Node: adjective>, <Variable Node: verb>]

So now you have a list of the variables for the template. This will need to be taken a step further to extract the actual name of each variable without peforming stupid string-slicing tricks on their repr names.

The variable name itself is stored in filter_expression.token for each VariableNode:

>>> varnodes[0].filter_expression.token
u'adjective'

And so a simple list comprehension gets us all of the variable names for the template:

>>> template_vars = [x.filter_expression.token for x in varnodes]
>>> template_vars
[u'adjective', u'verb']

So, not the simplest solution, but if there is a better way I don't know about it.

Bonus: A function!!

from django.template import VariableNode
def get_template_vars(t):
   varnodes = t.nodelist.get_nodes_by_type(VariableNode)
   return [x.filter_expression.token for x in varnodes]

Ok, it's not so complex after all!

Follow-up Edit: Getting variables from parent templates

(This follow-up is using the information from the updated question).

This is where it does actually get complex because the nodelist of the toy template is a single ExtendsNode (in this case).

>>> toy.nodelist
[<ExtendsNode: extends "mysite/toyparent.html">]

I would imagine that in larger templates there could be multiple ExtendsNode objects. Anyhow, if you inspect the ExtendsNode, and extract the parent template from it, you are able to treat the parent the same as my original example:

>>> enode = toy.nodelist[0]
>>> enode.parent_name
u'mysite/toyparent.html'
>>> parent = enode.get_parent(enode.parent_name)
>>> parent
<django.template.Template object at 0x101c43790>
>>> parent.nodelist.get_nodes_by_type(VariableNode)
[<Variable Node: adjective>]

And there is your adjective variable extracted from the parent template. To perform a test against an ExtendsNode you can import the class from django.template.loader_tags:

>>> from django.template.loader_tags import ExtendsNode
>>> ext = toy.nodelist.get_nodes_by_type(ExtendsNode)
>>> ext
[<ExtendsNode: extends "mysite/toyparent.html">]

So, you could do some tests against templates for the presence of an ExtendsNode and walk backwards to the parent template and individually get those variable names. However, this is starting to seem like a can of worms.

For example, if you were to do this:

>>> toy.nodelist.get_nodes_by_type((ExtendsNode, VariableNode))
[<ExtendsNode: extends "mysite/toyparent.html">, <Variable Node: block.super>, <Variable Node: verb>]

Now you've got the ExtendsNode and VariableNode objects and it just starts to get confusing. What do we do then? Do we attempt to ignore any block variables returned from such tests? I don't know!!

In any case, this is the information you wanted, but I don't think that this is a practical solution. I insist that there is still probably a better way. It might be worth looking into what you are trying to solve and see if there is another approach you can take.

like image 196
jathanism Avatar answered Nov 15 '22 23:11

jathanism