In most cases, if you have context data in your view like the following:
Then all you have to do to make it work in a template is to use the safe template filter and then it just works. Example:
But what if your data is more complex like the following example?
safe template filter no longer works because your booleans aren’t
types like the QuerySet, the Python datetime object, and your HTML string.
You might try using something like the escapejs hoping
to find some easy template filter that just works. However, I’ll save you the
trouble; it doesn’t work and the escapejs filter is only meant for
use on a single variable and not a list of dicts. You also might try using
json.dumps() with Django’s serialize() function, but as
I discovered this doesn’t work so well for most of my use cases because the
serialize() function doesn’t serialize the model instances in a flattened
structure and serializes the data in a more nested structure. Example:
The Magic Solution
So if you’re wanting a Django template filter that just works, you’ll have to create your own. The following is the solution I came up with that just works and extends Django’s DjangoJSONEncoder encoder class which adds the functionality to serialize QuerySets.
With the previous
to_json template filter created, then using it in a template
like the following just works!
So why not use Django’s json_script template tag?
The short answer is you can use the json_script template tag, but there are some things to consider.
First, if you look at the actual code you’ll see that the filter wraps the code in a script tag
which I’ve found to not be necessary. Secondly, there isn’t a great way to use a subclassed
DjangoJSONEncoder that we
need if there are custom data types that the default
DjangoJSONEncoder doesn’t handle.
What about security?
You should always be mindful of security and test your application to make sure that it hasn’t created a security
exploit. I also recommend reading Django’s excellent security documentation. Having said that, in our
to_json() I’m using mark_safe, which I’m fine with because I’ve read and know the
precautions to take. First, under no circumstances pass user-submitted data to the
to_json() filter unless you know
the data has been sanitized with something like bleach. Secondly, write some tests and test your application
manually for XSS exploits.
I should also mention that in our example data we’re using
User.objects.all() for illustrative purposes only. However,
those with a keen eye might recognize that this would send the hashed password to the template, which I don’t
recommend. Instead, to avoid this I recommend you something like
User.objects.values_list('first_name', 'last_name', 'email') or something equivalent.
Hopefully, this post will end up saving engineers a lot of time and prevent a lot of bugs from being created. I also
hope that maybe Django could either include some of this information in the documentation or add a template filter
to_json to Django that could be easily extended with your project’s own default encoder.