Reputation: 38255
mydict = {"key1":"value1", "key2":"value2"}
The regular way to lookup a dictionary value in a Django template is {{ mydict.key1 }}
, {{ mydict.key2 }}
. What if the key is a loop variable? ie:
{% for item in list %} # where item has an attribute NAME
{{ mydict.item.NAME }} # I want to look up mydict[item.NAME]
{% endfor %}
mydict.item.NAME
fails. How to fix this?
Upvotes: 321
Views: 238802
Reputation: 11891
If your dictionary has a fixed set of keys, then Pythons relatively new dataclasses offers an elegant way to do this.
Create a dataclass for your dictionary
@dataclass
class MyObject:
key_1: str
key_2: str
Then warp your dictionary in it before passing it to the template. So probably in get_conetxt_data().
context.update({"mydict" :MyObject(**mydict)})
Then you can access the values with the dot notaion in the template.
{{ mydict.key_1 }}
Upvotes: 0
Reputation: 476604
Please don't. Arbitrary dictionary lookups have not been implemented by Django, but for good reasons.
The main reason not to do this, is that logic in the template that needs this, is often business logic. Such logic does not belong to the template, but should be moved to the view.
Another problem is that Django's template engine is not very efficient: it requires a lot of context resolutions, and rendering logic, meaning that such dictionary lookups are thus not "for free", these will slow down rendering, especially if it is done in a loop.
Often it will also require resolving the variable that is the key, and this can make debugging more tricky, since it is possible that the result of the context resolution is not what one would expect. For example if the variable is not found, it will use the string_if_invalid
value [Django-doc], which is not per se what one might intend, and thus the behavior could produce a result, but a wrong result.
Finally if an error occurs, it makes debugging harder, and if something goes wrong, it is possible that this is passed silently, whereas it is often better that in case something fails, it is at least logged somewhere.
If you need to perform arbitrary subscripting or attribute lookups, that means you should move the logic from the template to the view. Indeed, in that case you prepare the data in the view, for example here with:
def my_view(request):
my_list = ['key2']
my_dict = {'key1': 'value1', 'key2': 'value2'}
result = [my_dict[k] for k in my_list]
return render(request, 'name-of-template.html', {'result': result})
and thus then process result
in the template, not the logic with my_dict
and my_list
.
Upvotes: 0
Reputation: 11
After the 11 years later.
You can use this doc for 4.2 https://docs.djangoproject.com/en/4.2/howto/custom-template-tags/
views.py
from django.template.defaultfilters import register
@register.simple_tag
def example_tag(var_1, var_2, *args, **kwargs):
args_1 = kwargs["args_1"]
args_2 = kwargs["args_2"]
return args_1+" "+ args_2
example.html
{% example_tag variable_1 variable_2 args_1="hi" args_2="hello" %}
output
hi hello
Upvotes: 1
Reputation: 5858
The accepted answer works fine, but I just want to add how to drill down nested value using filter chaining
mydict = {"USD": { "amount": 30 }, "JPY": { "amount": 3000 }}
currency = "JPY"
{{ mydict|get_item:currency|get_item:"amount" }}
The output will be 3000
Upvotes: 0
Reputation: 142
Since I can't comment, let me do this in the form of an answer:
to build on culebrón's answer or Yuji 'Tomita' Tomita's answer, the dictionary passed into the function is in the form of a string, so perhaps use ast.literal_eval to convert the string to a dictionary first, like in this example.
With this edit, the code should look like this:
# code for custom template tag
@register.filter(name='lookup')
def lookup(value, arg):
value_dict = ast.literal_eval(value)
return value_dict.get(arg)
<!--template tag (in the template)-->
{{ mydict|lookup:item.name }}
Upvotes: 1
Reputation: 36473
Write a custom template filter:
from django.template.defaulttags import register
...
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
(I use .get
so that if the key is absent, it returns none. If you do dictionary[key]
it will raise a KeyError
then.)
usage:
{{ mydict|get_item:item.NAME }}
Upvotes: 476
Reputation: 43
Environment: Django 2.2
from django.template.defaulttags import register
@register.filter(name='lookup')
def lookup(value, arg):
return value.get(arg)
I put this code in a file named template_filters.py in my project folder named portfoliomgr
No matter where you put your filter code, make sure you have __init__.py in that folder
Add that file to libraries section in templates section in your projectfolder/settings.py file. For me, it is portfoliomgr/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'libraries':{
'template_filters': 'portfoliomgr.template_filters',
}
},
},
]
In your html code load the library
{% load template_filters %}
Upvotes: 4
Reputation: 359
env: django 2.1.7
view:
dict_objs[query_obj.id] = {'obj': query_obj, 'tag': str_tag}
return render(request, 'obj.html', {'dict_objs': dict_objs})
template:
{% for obj_id,dict_obj in dict_objs.items %}
<td>{{ dict_obj.obj.obj_name }}</td>
<td style="display:none">{{ obj_id }}</td>
<td>{{ forloop.counter }}</td>
<td>{{ dict_obj.obj.update_timestamp|date:"Y-m-d H:i:s"}}</td>
Upvotes: -2
Reputation: 118458
You can't by default. The dot is the separator / trigger for attribute lookup / key lookup / slice.
Dots have a special meaning in template rendering. A dot in a variable name signifies a lookup. Specifically, when the template system encounters a dot in a variable name, it tries the following lookups, in this order:
- Dictionary lookup. Example: foo["bar"]
- Attribute lookup. Example: foo.bar
- List-index lookup. Example: foo[bar]
But you can make a filter which lets you pass in an argument:
https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-filters
@register.filter(name='lookup')
def lookup(value, arg):
return value[arg]
{{ mydict|lookup:item.name }}
Upvotes: 50
Reputation: 5715
For me creating a python file named template_filters.py
in my App with below content did the job
# coding=utf-8
from django.template.base import Library
register = Library()
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
usage is like what culebrón said :
{{ mydict|get_item:item.NAME }}
Upvotes: 8
Reputation: 21
I had a similar situation. However I used a different solution.
In my model I create a property that does the dictionary lookup. In the template I then use the property.
In my model: -
@property
def state_(self):
""" Return the text of the state rather than an integer """
return self.STATE[self.state]
In my template: -
The state is: {{ item.state_ }}
Upvotes: 2
Reputation: 16521
Fetch both the key and the value from the dictionary in the loop:
{% for key, value in mydict.items %}
{{ value }}
{% endfor %}
I find this easier to read and it avoids the need for special coding. I usually need the key and the value inside the loop anyway.
Upvotes: 99