Stan
Stan

Reputation: 38255

Django template how to look up a dictionary value with a variable

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

Answers (12)

wobbily_col
wobbily_col

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

willeM_ Van Onsem
willeM_ Van Onsem

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

msp
msp

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

cyberfly
cyberfly

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

NJHJ
NJHJ

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

culebr&#243;n
culebr&#243;n

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

Krishna
Krishna

Reputation: 43

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 %}
    

Upvotes: 4

Yi Yang Apollo
Yi Yang Apollo

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

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

AmiNadimi
AmiNadimi

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

sexybear2
sexybear2

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

Paul Whipp
Paul Whipp

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

Related Questions