通过 Django 将 Python 数据传递给 JavaScript

发布于 2024-08-04 17:28:26 字数 458 浏览 10 评论 0原文

我使用 Django 和 Apache 来提供网页服务。我的 JavaScript 代码当前包含一个数据对象,其值将根据用户从选项菜单中的选择显示在各种 HTML 小部件中。我想从 Python 字典中获取这些数据。我想我知道如何将 JavaScript 代码嵌入到 HTML 中,但是如何将数据对象嵌入到该脚本中(即时)以便脚本的函数可以使用它?

换句话说,我想从 Python 字典创建一个 JavaScript 对象或数组,然后将该对象插入到 JavaScript 代码中,然后将该 JavaScript 代码插入到 HTML 中。

我认为这种结构(例如,嵌入 JavaScript 代码中的变量中的数据)不是最理想的,但作为新手,我不知道替代方案。我已经看过 Django 序列化函数的文章,但这些文章对我没有帮助,直到我能够首先将数据放入我的 JavaScript 代码中。

我(还)没有使用像 jQuery 这样的 JavaScript 库。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

笑红尘 2024-08-11 17:28:26

请注意,请参阅底部的 2018 年更新,

我建议不要在 Django 模板中放入太多 JavaScript - 它往往很难编写和调试,特别是当您的项目扩展时。相反,尝试将所有 JavaScript 编写在模板加载的单独脚本文件中,并在模板中仅包含 JSON 数据对象。这允许您通过 JSLint 之类的东西运行整个 JavaScript 应用程序,缩小它等等,您可以使用静态 HTML 文件对其进行测试,而不依赖于 Django 应用程序。使用 simplejson 这样的库还可以节省您编写繁琐的序列化代码所花费的时间。

如果您不假设您正在构建 AJAX 应用程序,则可以简单地这样做:

在视图中:

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, …})

在模板中:

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

请注意,数据类型很重要:如果 my_data 是一个简单的数字或来自不包含 HTML 的受控源的字符串(例如格式化日期),不需要特殊处理。如果用户可能提供不受信任的数据,则需要使用类似 转义escapejs过滤器并确保您的 JavaScript 安全地处理数据,以避免跨站点脚本攻击。

就日期而言,您可能还需要考虑如何传递日期。我几乎总是发现将它们作为 Unix 时间戳传递是最简单的:

在 Django 中:

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

在 JavaScript 中,假设您已经使用上面代码片段的结果执行了类似 time_t = {{ time_t }} 的操作:

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

最后,请注意 UTC - 您需要让 Python 和 Django 日期函数以 UTC 格式交换数据,以避免与用户当地时间发生令人尴尬的变化。

编辑:请注意,javascript 中的 setTime 以毫秒为单位,而 time.mktime 的输出以秒为单位。这就是为什么我们需要乘以 1000

2018 年更新:我仍然喜欢使用 JSON 来处理复杂的值,但在接下来的十年中 HTML5 数据 API 已获得近乎通用的浏览器支持并且它对于传递简单(非列表/字典)值非常方便,特别是如果您可能希望基于这些值应用 CSS 规则并且您不关心不受支持的 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";
});

如果您想在 Django 模板中设置初始视图状态并在 JavaScript 更新 data- 属性时自动更新,那么这是向 CSS 公开数据的巧妙方法。我用它来隐藏进度小部件,直到用户选择要处理的内容,或者根据获取结果有条件地显示/隐藏错误,甚至使用诸如 #some-element::after 之类的 CSS 显示活动记录计数之类的事情{ 内容:attr(数据活动传输); }

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); }.

才能让你更想念 2024-08-11 17:28:26

对于任何可能遇到此问题的人,请确保在模板中的安全模式下渲染 json 对象。您可以像这样手动设置

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

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>
野却迷人 2024-08-11 17:28:26

截至 2018 年中期,最简单的方法是使用 Python 的 JSON 模块,simplejson 现已弃用。请注意,正如 @wilblack 提到的,您需要使用 safe 过滤器或带有 off 选项的 autoescape 标签来防止 Django 自动转义。在这两种情况下,您都可以在视图中将字典的内容添加到上下文

viewset.py

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

中,然后在模板中按照 @wilblack 建议添加:

template.html

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

安全警告
json.dumps 不会转义正斜杠:攻击是 {'': ''}.与其他答案中的问题相同。添加了另一个答案,希望能修复它。

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.

忘羡 2024-08-11 17:28:26

您可以在 .html 模板中包含

Paul 是对的:最好使用 json 模块创建 JSON 字符串,然后将该字符串插入模板中。这将最好地处理引用问题,并轻松处理深层结构。

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.

┼── 2024-08-11 17:28:26

这是次优的。您是否考虑过使用 django 内置的序列化器将数据作为 JSON 传递?

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

Spring初心 2024-08-11 17:28:26

请参阅此问题的相关回复。一种选择是使用 jsonpickle 在 Python 对象和 JSON/Javascript 对象之间进行序列化。它包装 simplejson 并处理 simplejson 通常不接受的事情。

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.

走过海棠暮 2024-08-11 17:28:26

修复@willblack 和@Daniel_Kislyuk 答案中的安全漏洞。

如果数据不受信任,您不能只执行

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>

因为数据可能类似于
{"":""}
默认情况下,正斜杠不会被转义。显然,json.dumps 的转义可能与 Javascript 中的转义不 100% 匹配,这就是问题的根源。

已修复的解决方案

据我所知,以下内容解决了该问题:

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

如果仍然存在问题,请在评论中发表。

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.

迷爱 2024-08-11 17:28:26

将 Java 脚本嵌入到 Django 模板中相当总是坏主意。

相反,因为这条规则有一些例外。

一切都取决于您的 Java 脚本代码站点和功能。

最好有单独的静态文件,例如 JS,但问题是每个单独的文件都需要另一个连接/GET/请求/响应机制。有时,对于小的一、两行代码,os JS 将其放入模板中,然后使用 django templatetags 机制 - 您可以在其他模板中使用 is ;)

关于对象 - 相同。如果你的网站有AJAX架构/web2.0之类的青睐——你可以在客户端放置一些计数/数学运算来达到很好的效果。如果对象很小 - 嵌入到模板中,如果对象很大 - 在另一个连接中响应它们以避免用户挂起页面。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文