Search code examples
pythondjangodictionarydjango-templates

.items not working on defaultdict in Django template


I can't get .items to work in my Django template:

copy and paste from my CBV's get_context_data:

    context['data'] = assertion_dict
    context['dataitems'] = assertion_dict.items()

    return context

copy and paste from my template:

  <h3>data dump</h3>
  {{data}}

  <h3>dataitems</h3>
  {% for key, value in dataitems %}
     {{ key }}: {{ value }} <br/>
  {% endfor %}

  <h3>data.items</h3>
  {% for key, value in data.items %}
     {{ key }}: {{ value }} <br/>
  {% endfor %}

  <h3>Not found test</h3>
  {{ i_dont_exist }}

output:

**data dump**
defaultdict(<class 'list'>, {<BadgeType: Talent>: [<BadgeAssertion: Blender Blue Belt>], <BadgeType: Achievement>: [<BadgeAssertion: MyBadge#1>, <BadgeAssertion: MyBadge#1>, <BadgeAssertion: MyBadge#2>], <BadgeType: Award>: [<BadgeAssertion: Copy of Copy of Blenbade>]})

**dataitems**
Talent: [<BadgeAssertion: Blender Blue Belt>]
Achievement: [<BadgeAssertion: MyBadge#1>, <BadgeAssertion: MyBadge#1>, <BadgeAssertion: MyBadge#2>]
Award: [<BadgeAssertion: Copy of Copy of Blenbade>]

**data.items**

**Not found test**
DEBUG WARNING: undefined template variable [i_dont_exist] not found 

Why isn't the second version working, where I use data.items in my template?


Solution

  • This is a known issue in Django: you cannot iterate over a defaultdict in a template. The docs suggest that the best way to handle this is to convert your defaultdict to a dict before passing it to the template:

    context['data'] = dict(assertion_dict)
    

    The reason it doesn't work, by the way, is that when you call {{ data.items }} in your template, Django will first attempt to find data['items'], and then data.items. The defaultdict will return a default value for the former, so Django will not attempt the latter, and you end up trying to loop over the default value instead of the dict.