Django's template language can not safely process dict - python

In django's template language, if a variable called a.x, then django will try in this order to get the right value:
a['x'], dictionary lookup
a.x or a.x(), attribute or method lookup
a[x], list indexing
The result is: any variable that support var['str'] becomes a time bomb. Imagine a is a dict, with a key 'items' whoes value is 'hello world', then a.items will result in 'hello world', not ditc.items(). Here is the code:
from django.template import Engine, Context
template = """
{% for k, v in a.items %}
{{ k }} = {{ v }}
{% endfor %}
"""
e = Engine()
t = e.from_string(template)
a = {'items': 'hello world'}
print(t.render(Context({'a': a})))
While I expect items = hello world as the output, the real output is:
h =
e =
l =
...
For a dict variable, if it has a key with the same name of a dict method, then you will never be able to call that method in the template. It becomes a time bomb because you never know if in the future you will add a key called 'items' into a dict variable.
Such risk is not only for dict but any type which accept var['key'].
So my question is, why the order is designed like this, and how to make it safe.

There is a simple workaround (besides using a better name for your keys):
from django.template import Engine, Context
template = """
{% for k, v in items_func %}
{{ k }} = {{ v }}
{% endfor %}
"""
e = Engine()
t = e.from_string(template)
a = {'items': 'hello world'}
print(t.render(Context({'a': a, 'items_func': a.items})))
>> items = hello world

why the order is designed like this
It's designed like this because the template syntax is supposed to be simple and doing so encourages you to put more logic into the view rather than in the template.
How to make it safe?
Give your keys a descriptive name
"items" doesn't do anything to actually describe what should be inside of the key, so not only will it give django a headache, it will give any other developer that has to debug an issue with this dictionary a headache too.

By using the . for three different lookups, there's always the potential for a problem like this. If the order was changed to try a.x first, then it would cause problems for other users that wanted to access a['x'].
I don't think there's a general way to protect against this behaviour. Once you're burned by it once, you'll remember not to put an items key in your dictionaries in future.
As a last resort, you could switch to Jinja, which supports subscript syntax, {{ foo['bar'] }}. That means that {{ foo.bar }} checks for the attribute foo.bar first. See the Jinja docs for more information.

Related

Getting list of context variables out of string

I have the following string in my Django app: "Hello, {{ name }}. I will arrive at {{ time }}". My goal is to come up with a list of variables that is used in that string:
def my_func():
# What should be done here ?
my_str = "Hello, {{ name }}. I will arrive at {{ time }}"
print(my_func(my_str)) # Desired output ["name", "time"]
The desired output here should be ["name", "time"].
Currently, I am going to implement that invoking a regexp, but I feel like there must be some in-built way to achieve that. Any hints ?
You can use jinja2schema.infer to get the list of variables used in template:
import jinja2schema
def my_func(my_str):
return list(jinja2schema.infer(my_str).keys())
my_str = "Hello, {{ name }}. I will arrive at {{ time }}"
variables = my_func(my_str)
variables should look like:
['time', 'name']

Jinja2 failsafe template rendering

I'm using Jinja2 to create email notification messages, mostly error notifications.
Sometimes I get invalid or incomplete data about the error details. After all, it is an error condition when this happens. E.g. a dict item might be missing or value type could be incorrect. In such case the rendering fails, but the error notification has to be sent.
I can do following in the exception handler:
To send the template as-is. At least the type of error event will be known to the recipient, but important data will be missing
To send a backup template with just raw data. That would mean to maintain two versions of each message.
What I really want is to render the template on a "best-effort" basis skipping all errors. Is there a way to do that in Jinja2?
Use defaults everywhere it's necessary. Also handles undefined variables:
default(value, default_value=u'', boolean=False):
If the value is undefined it will return the passed default value, otherwise the value of the variable:
{{ my_variable|default('my_variable is not defined') }}
This will output the value of my_variable if the variable was defined, otherwise 'my_variable is not defined'. If you want to use default with variables that evaluate to false you have to set the second parameter to true:
{{ ''|default('the string was empty', true) }}
Or you could use none if you're creating more complex structures based on the data:
{% if p is not none %}
{{ p.User['first_name'] }}
{% else %}
NONE
{%endif %}

Django ManyToMany realtionship doesn't work

I'm trying to do this in my models.py:
class Tag(models.Model):
''' snip '''
name = models.CharField(max_length=30)
class Stuff(models.Model):
kind = models.CharField(max_length=30)
tag = models.ManyToManyField(Tag)
But when I make query from Stuff in shell the relationship fields return 'None' like this:
>>> q = Stuff.objects.all()
>>> p = q.tag.name
>>> print q.tag.name
None
I can't use this keys in my template too.
Database backend is mysql.
What is the problem?
You can call many to many relation like this,
q = Stuff.objects.all()
for p in q.tag.all():
print p.name
In HTML
{%for tag in stuff.tag.all %}
{{ tag.name }}
{% endfor %}
It's unclear how exactly you want your models to work. Do you want Stuff to have a single Tag or several Tags?
Solutions for both case uses follow.
The case where a Stuff object has many Tags
There are some mistakes here.
In your example the variable q isn't a Stuff object, so you can't ask for it's attributes. It's a queryset. Like the other answer exemplifies you have to iterate through it, like a list.
A ManyToMany relationship means that Stuff is going to have many Tags, but you're using it as if Stuff had only one Tag.
An example (I'm going to replace Stuff.tag by Stuff.tags, since it's misleading to call it a single tag):
# Get the first stuff
>>> stuff = Stuff.objects.first()
# Access the attribute `tag`. Notice it's NOT a Tag, but a `RelatedManager`.
>>> stuff.tag
<django.db.models.fields.related.ManyRelatedManager object at 0x7fe2a3e5cc10>
# which you can use as a queryset!!
>>> stuff.tag.all()
[<Tag: tag1>, <Tag: tag2>, ...]
# Then you can iterate through it, filter or whatever
>>> stuff.tag[0]
<Tag: tag1>
>>> stuff.tag[0].name
u'tag1'
The case where a Stuff object has a single Tag
If you want Stuff to have only ONE Tag object you have to declare it like this.
tag = models.OneToOneField(Tag)
NOW you can do stuff.tag.name and use it like that in the template. If you want many Tags you'll have to iterate through the Stuff's Tags like this (again, I'm using tags instead of tag for this example):
{% for tag in stuff.tags %}
{{ tag.name }}
{% endfor %}
There are of course other ways, like using the filter join
{# Print the tag names joined by ', ' #}
{{ stuff.tags.all|join:', ' }}

Weird runtime error when looping over DefaultDict in django template

Using Django 1.5.1
I have a simple code here -
count_by_media_type = defaultdict(int)
for user_media in user_media_data:
count_by_media_type[user_media['media_type']] += 1
This is a part of a view, which is rendered in the template using a for iteration loop -
{% for media_type in count_by_media_type %}
..........
{% endfor %}
The value of count_by_media_type just before the render_to_response method in the loop is
defaultdict(<type 'int'>, {u'photo': 1, u'audio': 4, u'video': 3})
But somehow, on rendering , there is a runtime error in iteration
dictionary changed size during iteration
And the value of count_by_media_type as seen in the template context becomes
defaultdict(<type 'int'>, {u'photo': 1, u'audio': 4, u'video': 3, u'media_type': 0})
This is quite weird, as how does a new key media_type is coming in the variable?
Now , when I switch the above code, mentioned in the view with
count_by_media_type = Counter([user_media['media_type'] for user_media in user_media_data])
Everything works fine.
Any clues anyone?
Using defaultdicts causes weird behaviour in Django templates, because of the way template variable lookups work. See the Behind the scenes box of the Django docs.
The Django docs suggest converting the defaultdict to a regular dict before passing to the template.
count_by_media_type = defaultdict(int)
for user_media in user_media_data:
count_by_media_type[user_media['media_type']] += 1
count_by_media_type = dict(count_by_media_type)
Or, as this answer suggests, you can disable the defaulting feature after you have finished inserting values.
count_by_media_type.default_factory = None

Print None object properties as empty string

I am passing an object to my Jinja2 template to be used as form values.
If a property doesn't exist, it is printing None as a string, where I would like it to just print an empty string (aka, nothing).
As there are a lot of properties for the object, I wish to avoid string coercion at the Controller level.
My current Jinja code looks like:
value="{{ my_object.my_property }}"
Try:
{{ my_object.my_property if my_object.my_property != None }}
I was able to utilize the following, which is not too long:
{{ my_object.my_property or '' }}

Categories