48

I'm trying to pass a Query Set from Django to a template with javascript.

I've tried different approaches to solve this:

1. Normal Approach - Javascript gets all messed up with trying to parse the object because of the nomenclature [ &gt Object:ID &lt, &gt Object:ID &lt,... ]

Django View

django_list = list(Some_Object.objects.all())

Template HTML + JS

<script type="text/javascript" >
    var js_list = {{django_list}};
</script>

2. JSON Approach - Django fails on converting the object list to a json string is not JSON serializable

Django View

django_list = list(Some_Object.objects.all())
json_list = simplejson.dumps(django_list)

Template HTML + JS

<script type="text/javascript" >
    var js_list = {{json_list}};
</script>

So, I need some help here :)

Any one has any suggestion / solution?

Thanks!

Mc-
  • 3,968
  • 10
  • 38
  • 61

14 Answers14

35

Same Question, "Better"(more recent) answer: Django Queryset to dict for use in json

Answer by vashishtha-jogi:

A better approach is to use DjangoJSONEncoder. It has support for Decimal.

import json
from django.core.serializers.json import DjangoJSONEncoder

prices = Price.objects.filter(product=product).values_list('price','valid_from')

prices_json = json.dumps(list(prices), cls=DjangoJSONEncoder)

Very easy to use. No jumping through hoops for converting individual fields to float.

Update : Changed the answer to use builtin json instead of simplejson.

This is answer came up so often in my google searches and has so many views, that it seems like a good idea to update it and save anyone else from digging through SO. Assumes Django 1.5.

Community
  • 1
  • 1
agconti
  • 17,780
  • 15
  • 80
  • 114
  • Instead of the `.values_list()`, one can also just use `.values()` to get everything – Advena Jan 20 '21 at 10:34
  • After prices_json is created and passed to DOM, you can also give its content to a Javascript variable by ```var prices_data= {{ prices_json|safe }};``` – Yi Zong Kuang Jan 19 '22 at 17:02
32

Ok, I found the solution!

Mostly it was because of not quoting the results. When Javascript was trying to parse the object this wasn't recognized as string.

So, first step is:

var js_list = {{django_list}}; 

changed to:

var js_list = "{{django_list}}";

After this I realized that Django was escaping characters so I had to replace them like this:

 var myJSONList = (("{{json_list}}").replace(/&(l|g|quo)t;/g, function(a,b){
                return {
                    l   : '<',
                    g   : '>',
                    quo : '"'
                }[b];
            }));

 myData = JSON.parse( myJSONList );

Note: I tried to avoid escaping characters from Django using this:

var js_list = "{{json_list|safe}}"; 

But this doesn't work because it gets confused with the quotes.

Finally I found a way to avoid the logic on the backend of converting to JSON before sending it to Javascript:

var myDjangoList = (("{{django_list |safe}}").replace(/&(l|g|quo)t;/g, function(a,b){
            return {
                l   : '<',
                g   : '>',
                quo : '"'
            }[b];
        }));

myDjangoList = myDjangoList.replace(/u'/g, '\'')
myDjangoList = myDjangoList.replace(/'/g, '\"')

myData = JSON.parse( myDjangoList );

I'm sure this can be improved, I let this to you ;)

Thanks for your answers

Hope it helps to someone else!

Mc-
  • 3,968
  • 10
  • 38
  • 61
  • 3
    This answer is not an elegant approach. Try http://stackoverflow.com/questions/10502135/django-queryset-to-dict-for-use-in-json – user Jan 26 '14 at 08:55
  • As it turns out, the answer on that linked question isn't so good either. Json.dumps doesn't work anymore, serializers need to be used. Now, that's a good approach but with a simple values_list function for a model passed into the template, the approach in this answer works. – Roast Biter May 08 '21 at 09:50
10

Django querysets are serializable by JSON. Some field types (such as date, apparently), can't be serialized at is. A workaround for date objects is posted in another question on JSON and Python.

I would recommend creating dictionaries directly in the JavaScript itself. Given models like this:

class Article(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    content = models.TextField()

class Author(models.Model):
    article = models.ForeignKey("Article", related_name="authors")
    first_name=models.CharField(max_length=100)
    last_name=models.CharField(max_length=100)

I'd do something like this in the template:

<script type="text/javascript">
    var articles = [
    {% for article in article_list %}
        {% if not forloop.first %},{% endif %}
        {
            title: "{{ article.title }}",
            slug: "{{ article.slug }}",
            content: "{{ article.content }}",
            authors: [
            {% for author in article.authors.all %}
                {% if not forloop.first %},{% endif %}
                {
                    first_name: "{{ author.first_name }}",
                    last_name: "{{ author.last_name }}",
                }
            {% endfor %}
            ]
        }
    {% endfor %}
    ]
</script>

If you maybe worded the question a little poorly and aren't planning on inserting code in a <script> tag and actually need JSON for some reason, I'd simply do a loop in the view and create a list of dicts, which JSON has no problem serializing, and JavaScript no problem in understanding.

Community
  • 1
  • 1
Jordan Reiter
  • 20,467
  • 11
  • 95
  • 161
8

EDIT: please don't use this method, see @agconti's answer.

Use the escapejs filter: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#escapejs

Example of dumping a list:

var foo = [{% for x in y %}'{{ x|escapejs }}',{% endfor %}]
boxed
  • 3,895
  • 2
  • 24
  • 26
  • What if I have a nested list? How do I dump that then? With the above code, I am getting only the items on the first level. Any help would be appreciated. – Harsh Mehta Jul 10 '17 at 08:01
  • @HarshMehta see the answer below mine: use DjangoJSONEncoder. – boxed Jul 11 '17 at 09:31
7

Since Django 2.1 there is the json-script template tag. From the docs:

json_script

Safely outputs a Python object as JSON, wrapped in a tag, ready for use with JavaScript.

Argument: HTML “id” of the tag.

For example:

{{ value|json_script:"hello-data" }} 

If value is the dictionary {'hello': 'world'}, the output will be:

<script id="hello-data" type="application/json">
{"hello": "world"}
</script>

The resulting data can be accessed in JavaScript like this:

var value = JSON.parse(document.getElementById('hello-data').textContent); 

XSS attacks are mitigated by escaping the characters “<”, “>” and “&”. For example if value is {'hello': 'world</script>&amp;'}, the output is:

<script id="hello-data" type="application/json">
    {"hello": "world\\u003C/script\\u003E\\u0026amp;"}
</script> 

This is compatible with a strict Content Security Policy that prohibits in-page script execution. It also maintains a clean separation between passive data and executable code.

Mathieu Dhondt
  • 8,405
  • 5
  • 37
  • 58
6

You can use the combination of safe and escapejs built-in filter in Django.

var json_string = unescape({{json_list | safe | escapejs}});
var json_data = JSON.parse(json_string);
lguiel
  • 328
  • 7
  • 15
4

Your problem is that, as so often, your requirements are under-specified. What exactly do you want the JSON to look like? You say you want to "serialize the queryset", but in what format? Do you want all the fields from each model instance, a selection, or just the unicode representation? When you've answered that question, you'll know how to solve your problem.

One approach, for example, might be to use the values queryset method to output a dictionary of fields for each instance, and serialize that (you need to convert it to a list first):

data = SomeObject.objects.values('field1', 'field2', 'field3')
serialized_data = simplejson.dumps(list(data))
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
4

You have to mark the string as safe to be sure it's not escaped.

in one of my project I use it like this:

# app/templatetag/jsonify.py
from django import template
from django.utils.safestring import mark_safe
import json

register = template.Library()

@register.filter
def jsonify(list):
    return mark_safe(json.dumps(list))

and in the template

{% load jsonify %}
<script type="text/javascript" >
    var js_list = {{ python_list|jsonify|escapejs }};
</script>

but you may prefer to just add mark_safe or use |safe in the template to avoid all &gt; stuff

If the problem is for handling complex python object you may have to do your handler like this: JSON datetime between Python and JavaScript

christophe31
  • 6,359
  • 4
  • 34
  • 46
  • 1
    This is vulnerable to XSS. For example, try the following Python list: `[' – Blender May 31 '17 at 03:02
  • you are right, in this case, unless a template rendered .js file, we should add "|escapejs" which I forgot, I edit, thx Blender – christophe31 May 31 '17 at 14:42
3

Django offers in-built help for the very scenario you are trying to do here. It goes something like this:

You have a python sequence, list, dictionary, etc in your view, let's call it py_object. One approach is to jsonify it before passing it to the rendering engine.

from django.shortcuts import render_to_response
import json  

Then later on use like this...

render_to_response('mypage.html',{'js_object':json.dumps(py_object)})

In you template, then use the safe filter to import the already jsonized object from python into javascript, like this...

data = {{ js_object|safe }}

That should solve your problem i hope.

JWL
  • 13,591
  • 7
  • 57
  • 63
1

either;

read object using {{ django_list }} and then remove unwanted characters

or do;

{{ django_list | safe}}
Tek Nath Acharya
  • 1,676
  • 2
  • 20
  • 35
0

Consolidated answer (my env: Django 2.0)

In views.py

import json
data= []
// fil the list
context['mydata'] = json.dumps({'data':data})

In template

  <script type="text/javascript">
      var mydataString = "{{mydata|escapejs}}";
      console.log(JSON.parse(mydataString));
  </script>
vamsi
  • 2,809
  • 2
  • 15
  • 18
0

For me to send the whole QuerySet (while preserving the fields names; sending object not list). I used the following

    # views.py        
    units = Unit.objects.all()
    units_serialized = serializers.serialize('json', units)
    context['units'] = units_serialized

and just use safe tag in the template

    # template.html
    <script>
            console.log({{units|safe}});
    </script>
Rami Alloush
  • 2,308
  • 2
  • 27
  • 33
0

NOTE for django 2.1

i found this a little confusing on django documentation so simply explaining a little bit easy way.

we would normally use this like

{{ my_info }}

or loop over it depending on what we needed. But if we use the following filter,

json_script 

we can safely output this value as JSON

{{ my_info|json_script:"my-info" }}

Our data has been added as JSON, wrapped in a script tag, and we can see the data. We can now use this value by looking it up in JavaScript like so:

info = JSON.parse(document.getElementById('my-info').textContent);
jahmed31
  • 3,456
  • 3
  • 23
  • 23
0

Be careful on also making sure that you output JSON data correctly from Django, otherwise all trials on the frontend side will be a waste of time. In my case I could not use JsonResponse as part of the render function so I did the following:

    def view(self, request):

        data = []
        machine_data = list(Machine.objects.filter(location__isnull=False).values_list('serial', 'location', 'customer__name'))
        data.append({
            "locations": machine_data,
        })

        return render(request, 'admin/company/device/map.html', {
            "machines": data
        })

And on the frontend:

{% block content %}

    {{ machines_with_location|json_script:"machineLocationData" }}

    <div id="content-main">

        <h1>Title</h1>

        <script type="text/javascript">

            const locationDataFromJson = JSON.parse(document.getElementById('machineLocationData').textContent);

        </script>

    </div>

{% endblock %}
Greg Holst
  • 874
  • 10
  • 23