59

I'm using Django and Apache to serve webpages. My JavaScript code currently includes a data object with values to be displayed in various HTML widgets based on the user's selection from a menu of choices. I want to derive these data from a Python dictionary. I think I know how to embed the JavaScript code in the HTML, but how do I embed the data object in that script (on the fly) so the script's functions can use it?

Put another way, I want to create a JavaScript object or array from a Python dictionary, then insert that object into the JavaScript code, and then insert that JavaScript code into the HTML.

I suppose this structure (e.g., data embedded in variables in the JavaScript code) is suboptimal, but as a newbie I don't know the alternatives. I've seen write-ups of Django serialization functions, but these don't help me until I can get the data into my JavaScript code in the first place.

I'm not (yet) using a JavaScript library like jQuery.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
chernevik
  • 3,922
  • 9
  • 41
  • 55

8 Answers8

82

n.b. see 2018 update at the bottom

I recommend against putting much JavaScript in your Django templates - it tends to be hard to write and debug, particularly as your project expands. Instead, try writing all of your JavaScript in a separate script file which your template loads and simply including just a JSON data object in the template. This allows you to do things like run your entire JavaScript app through something like JSLint, minify it, etc. and you can test it with a static HTML file without any dependencies on your Django app. Using a library like simplejson also saves you the time spent writing tedious serialization code.

If you aren't assuming that you're building an AJAX app this might simply be done like this:

In the view:

from django.utils import simplejson


def view(request, …):
    js_data = simplejson.dumps(my_dict)
    …
    render_template_to_response("my_template.html", {"my_data": js_data, …})

In the template:

<script type="text/javascript">
    data_from_django = {{ my_data }};
    widget.init(data_from_django);
</script>

Note that the type of data matters: if my_data is a simple number or a string from a controlled source which doesn't contain HTML, such as a formatted date, no special handling is required. If it's possible to have untrusted data provided by a user you will need to sanitize it using something like the escape or escapejs filters and ensure that your JavaScript handles the data safely to avoid cross-site scripting attacks.

As far as dates go, you might also want to think about how you pass dates around. I've almost always found it easiest to pass them as Unix timestamps:

In Django:

time_t = time.mktime(my_date.timetuple())

In JavaScript, assuming you've done something like time_t = {{ time_t }} with the results of the snippet above:

my_date = new Date();
my_date.setTime(time_t*1000);

Finally, pay attention to UTC - you'll want to have the Python and Django date functions exchange data in UTC to avoid embarrassing shifts from the user's local time.

EDIT : Note that the setTime in javascript is in millisecond whereas the output of time.mktime is seconds. That's why we need to multiply by 1000

2018 Update: I still like JSON for complex values but in the intervening decade the HTML5 data API has attained near universal browser support and it's very convenient for passing simple (non-list/dict) values around, especially if you might want to have CSS rules apply based on those values and you don't care about unsupported versions of Internet Explorer.

<div id="my-widget" data-view-mode="tabular">…</div>

let myWidget = document.getElementById("my-widget");
console.log(myWidget.dataset.viewMode); // Prints tabular
somethingElse.addEventListener('click', evt => {
    myWidget.dataset.viewMode = "list";
});

This is a neat way to expose data to CSS if you want to set the initial view state in your Django template and have it automatically update when JavaScript updates the data- attribute. I use this for things like hiding a progress widget until the user selects something to process or to conditionally show/hide errors based on fetch outcomes or even something like displaying an active record count using CSS like #some-element::after { content: attr(data-active-transfers); }.

Chris Adams
  • 4,966
  • 1
  • 30
  • 28
  • Thanks. I planned to serve the javascript as a media file, similar to a css stylesheet. This seems like a better solution than embedding the data in the js, though I'll have to learn some JSON, and write some server-side code to handle data requests. – chernevik Sep 18 '09 at 19:11
  • Sounds like a good idea - one thing which I really like about that approach is that it's trivial to write a Django view which returns JSON (if you do this often, look at django-piston for creating REST APIs) and it's very easy to test isolated parts that way. – Chris Adams Sep 18 '09 at 21:19
  • I'm not ignoring this! I'm just building up my knowledge of writing and serving javascript to the point where I actually use it. I'm now planning to use JSON to pass in metadata to drive which widgets are affected by the menu choice, but this has required me learning more about creating and accessing javascript data structures. – chernevik Sep 22 '09 at 21:47
  • I got this approach to work, and tried to select it as the answer -- and was told it was too old to be changed, unless it was edited! Mea culpa. If you edit this, I'll select it as the answer. – chernevik Sep 28 '09 at 15:47
  • Note for people searching the archives: if you need to pass dates directly to a JSON consumer which uses strict ISO8601 dates, you might find this bit of code from a recent YUI app helpful: http://www.djangosnippets.org/snippets/1841/ – Chris Adams Dec 22 '09 at 20:30
  • 4
    I think {{ my_data|safe }} is correct instead of {{ my_data }} ? – Julian Popov Aug 27 '12 at 12:56
  • Ju: quite possibly, although it depends on the data (e.g. if it's a simple `{"date": 1234567}` timestamp, there won't be anything which needs to be escaped). Lately, I've started using an `as_json` template filter (https://github.com/acdha/django-sugar/blob/master/sugar/templatetags/sugar_template_utils.py#L100-107) so I can handle the escaping more obviously in the template (e.g. `{{ my_data|as_json }}` and my implementation calls `mark_safe` on its output – Chris Adams Aug 28 '12 at 11:51
  • 2
    No, no, no, NO! Browsers parse HTML before Javascript. If `my_dict` has a string containing ``, it will break out of the script tag. – Chris Martin Jun 09 '14 at 07:22
  • See https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet - particularly Rule #3.1 – Chris Martin Jun 09 '14 at 07:34
  • @ChrisMartin: the original question appeared to be using safe values (selections from a menu). If you're displaying untrusted HTML content from users, you definitely need to sanitize it – and that would strongly encourage creating a separate JSON view as discussed above and AJAX loading it to avoid this. – Chris Adams Jun 09 '14 at 14:40
  • Can there be safety issues of parsing your json straight into your html? For instance, 'data_from_django = {{ my_data }};' will render all the key-value pairs in the html. Is this good practice? – macrocosme Jul 06 '15 at 01:54
  • 1
    @macrocosme: yes – see my previous comment. Unless you're using data from a known-safe source or which is validated to purely safe types like integers, dates, etc. you need to have a strategy for sanitizing data anywhere it's rendered with `|safe`. – Chris Adams Jul 06 '15 at 16:09
44

For anyone who might be having a problems with this, be sure you are rendering your json object under safe mode in the template. You can manually set this like this

<script type="text/javascript">
    data_from_django = {{ my_data|safe }};
    widget.init(data_from_django);
</script>
wilblack
  • 1,077
  • 10
  • 12
6

As of mid-2018 the simplest approach is to use Python's JSON module, simplejson is now deprecated. Beware, that as @wilblack mentions you need to prevent Django's autoescaping either using safe filter or autoescape tag with an off option. In both cases in the view you add the contents of the dictionary to the context

viewset.py

import json
 def get_context_data(self, **kwargs):
    context['my_dictionary'] = json.dumps(self.object.mydict)

and then in the template you add as @wilblack suggested:

template.html

<script>
    my_data = {{ my_dictionary|safe }};
</script>

Security warning: json.dumps does not escape forward slashes: an attack is {'</script><script>alert(123);</script>': ''}. Same issue as in other answers. Added another answer hopefully fixing it.

Sergey Orshanskiy
  • 6,794
  • 1
  • 46
  • 50
Daniel Kislyuk
  • 956
  • 10
  • 11
3

You can include <script> tags inside your .html templates, and then build your data structures however is convenient for you. The template language isn't only for HTML, it can also do Javascript object literals.

And Paul is right: it might be best to use a json module to create a JSON string, then insert that string into the template. That will handle the quoting issues best, and deal with deep structures with ease.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
2

It is suboptimal. Have you considered passing your data as JSON using django's built in serializer for that?

Paul McMillan
  • 19,693
  • 9
  • 57
  • 71
  • I thought I'd learn some basics before wading into the acronyms. But it seems I can either learn some JSON, or build a solution from basic elements, which I'll scrap once I've learned some JSON. – chernevik Sep 18 '09 at 19:08
1

See the related response to this question. One option is to use jsonpickle to serialize between Python objects and JSON/Javascript objects. It wraps simplejson and handles things that are typically not accepted by simplejson.

Community
  • 1
  • 1
John Paulett
  • 15,596
  • 4
  • 45
  • 38
1

Putting Java Script embedded into Django template is rather always bad idea.

Rather, because there are some exceptions from this rule.

Everything depends on the your Java Script code site and functionality.

It is better to have seperately static files, like JS, but the problem is that every seperate file needs another connect/GET/request/response mechanism. Sometimes for small one, two liners code os JS to put this into template, bun then use django templatetags mechanism - you can use is in other templates ;)

About objects - the same. If your site has AJAX construction/web2.0 like favour - you can achieve very good effect putting some count/math operation onto client side. If objects are small - embedded into template, if large - response them in another connection to avoid hangind page for user.

bluszcz
  • 4,054
  • 4
  • 33
  • 52
0

Fixing the security hole in the answers by @willblack and @Daniel_Kislyuk.

If the data is untrusted, you cannot just do

viewset.py

 def get_context_data(self, **kwargs):
    context['my_dictionary'] = json.dumps(self.object.mydict)

template.html

<script>
    my_data = {{ my_dictionary|safe }};
</script>

because the data could be something like {"</script><script>alert(123);</script>":""} and forward slashes aren't escaped by default. Clearly the escaping by json.dumps may not 100% match the escaping in Javascript, which is where the problems come from.

Fixed solution

As far as I can tell, the following fixes the problem:

<script>
   my_data = JSON.parse("{{ my_dictionary|escapejs }}");
</script>

If there are still issues, please post in the comments.

Sergey Orshanskiy
  • 6,794
  • 1
  • 46
  • 50