106👍
You can avoid the copy to a new dict by disabling the defaulting feature of defaultdict once you are done inserting new values:
new_data.default_factory = None
Explanation
The template variable resolution algorithm in Django will attempt to resolve new_data.items
as new_data['items']
first, which resolves to an empty list when using defaultdict(list).
To disable the defaulting to an empty list and have Django fail on new_data['items']
then continue the resolution attempts until calling new_data.items()
, the default_factory attribute of defaultdict can be set to None.
- [Django]-Django Admin – Disable the 'Add' action for a specific model
- [Django]-Favorite Django Tips & Features?
- [Django]-Django: Get list of model fields?
8👍
Since the "problem" still exist years later and is inherint to the way Django templates work, I prefer writing a new answer giving the full details of why this behaviour is kept as-is.
How-to fix the bug
First, the solution is to cast the defaultdict
into a dict
before passing it to the template context:
context = {
'data': dict(new_data)
}
You should not use defaultdict
objects in template context in Django.
But why?
The reason behind this "bug" is detailed in the following Django issue #16335:
Indeed, it boils down to the fact that the template language uses the same syntax for dictionary and attribute lookups.
… and from the docs:
Dictionary lookup, attribute lookup and list-index lookups are implemented with a dot notation. […] If a variable resolves to a callable, the template system will call it with no arguments and use its result instead of the callable.
When Django resolve your template expression it will try first data['items']
. BUT, this is a valid expression, which will automatically creates a new entry items
in your defaultdict data
, initialized with an empty list (in the original author case) and returns the list created (empty).
The intented action would be to call the method items
with no arguments of the instance data
(in short: data.items()
), but since data['items']
was a valid expression, Django stop there and gets the empty list just created.
If you try the same code but with data = defaultdict(int)
, you would get a TypeError: 'int' object is not iterable
, because Django won’t be able to iterate over the "0" value returned by the creation of the new entry of the defaultdict
.
- [Django]-How to squash recent Django migrations?
- [Django]-Django ignores router when running tests?
- [Django]-Django migration strategy for renaming a model and relationship fields