Django template how to look up a dictionary value with a variable
Asked Answered
M

11

316
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?

Mammoth answered 3/11, 2011 at 18:26 Comment(1)
https://mcmap.net/q/99016/-django-template-how-to-look-up-a-dictionary-value-with-a-variable return dictionary.get(key) if dictionary else return NoneCavazos
W
469

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 }}
Wehrmacht answered 3/11, 2011 at 18:31 Comment(13)
Django Custom Template Tag documentation, for those finding this in the future.Fenland
Why is this not built in by default? :-(Recurvate
I think @Fenland meant Django Custom Template Filter documentationElsyelton
in Jinja2 {{ mydict[key] }}Baksheesh
Great solution. @BerislavLopac Sadly it's a wontfix with Django core developers : code.djangoproject.com/ticket/3371Islas
Does the filter go in views.py, some extra filters.py, or what file?Unscathed
Is there a possibility to save the tag result in a variable?Vaccine
+alanse I added it as a sub-function INSIDE the views.py function in question... but see docs.djangoproject.com/en/1.10/howto/custom-template-tags/…Rumney
@brunodesthuilliers I mean in terms of the conceptual design of JSX for rendering HTML content as compared to Django templates. There's no reason something much more React-like isn't possible in Python.Amary
I was only able to get this to work after adding a name argument based on @Yuji 'Tomita' Tomita answer: @register.filter(name='get_item')Cession
@BerislavLopac because the Django template language sucked when it was created and it still sucks today. Friends don't let friends use Django templates.Cecilycecity
@MatheusJardimB Is it possible to assign the return data of tag filter to a variable or to check the return data is not empty. like, variable={{ mydict|get_item:item.NAME }}Stacte
This worked for me. How would I pass two arguments in this way? {{ mydict_get_item:'attribute', 'second_attribute' }} does not seem to work.Kamchatka
A
96

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.

Abwatt answered 12/7, 2015 at 1:13 Comment(7)
He did not ask to enumerate a dict (as you show) - he asked to get the dict's value given a variable key. Your proposal does not provide solution.Needlefish
It is a solution (just very inefficient) since you can enumerate the items of the dict and then match with the key from the list.Prague
Note that this does not work if the dictionary you are trying to access contains another dictionary inside.Widgeon
If your values are dicts, you can include another for loop to process their keys and values but is likely that the complexity is taking you towards it being worth using a custom filter as described in @culebron's answer.Abwatt
@PaulWhipp i have the same problem but the key has multi values and when im tring your answer it shows only first value .Sideshow
@M.Mevlevi I'm not sure what you mean by a key having multi values but I'm guessing that you have something like a dict with {a: [1, 2, 3]}. value would be bound to [1, 2, 3] in that case so you'd need to handle that appropriately rather than just presenting the value directly (which assumes it has a string representation).Abwatt
I think this solution is still good for someone although it can't work if the value is another dictionary.Rubric
S
50

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 }}
Sabbatarian answered 3/11, 2011 at 18:31 Comment(2)
I would still use return value.get(arg) because that would not throw a KeyError exception if the key is not present.Tonsillotomy
return value.get(arg, None)Thalamus
C
8

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 }}
Cytolysis answered 29/6, 2017 at 19:12 Comment(2)
Why register = Library() ? What does it do ?Newsletter
If you want all your templates to know about your new filter, then you have to register it under django.template.base.Library class. by register = Library() we instantiate that class and use filter function annotator inside it to reach our need.Cytolysis
F
3

Environment: Django 2.2

  1. Example code:


    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

  1. No matter where you put your filter code, make sure you have __init__.py in that folder

  2. 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',
                }
            },
        },
    ]

  1. In your html code load the library

    
    {% load template_filters %}
    
Fugato answered 7/6, 2020 at 15:15 Comment(0)
A
2

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_ }}
Aeolian answered 7/10, 2016 at 0:39 Comment(0)
N
1

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 }}
Negligence answered 31/10, 2019 at 4:54 Comment(3)
Is it possible to assign the value returned({{ mydict|lookup:item.name }}) to a variableStacte
@Stacte I am not sure what you mean by your question. Perhaps my code was confusing; I have corrected it and added comments since then.Negligence
@Stacte coming from grails/gsp and other template languages I had the same question - but one needs to think different in django: you can do that before you render the template. When you create the context for the template in the view you can just add transient properties and (I believe) even methods to your model objects and access those from the template - great for all adhoc stuff you need just in that template and gives very readable template code.Siphonostele
R
1

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.

Ralphralston answered 9/2 at 14:7 Comment(0)
S
0

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

Steele answered 9/3, 2023 at 1:57 Comment(0)
L
0

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
Luteal answered 4/5, 2023 at 17:35 Comment(0)
N
-2

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>
Nasturtium answered 6/3, 2019 at 2:42 Comment(2)
The template code {{ dict_obj.obj.obj_name }} is in this case equivalent to Python code dict_obj["obj"]["obj_name"], however, the question is about the equivalent of dict_obj[obj][obj_name].Acid
How is the answer used, inside a template?Lem

© 2022 - 2024 — McMap. All rights reserved.