Home  >  Article  >  Backend Development  >  How to exchange data between Python and JavaScript

How to exchange data between Python and JavaScript

WBOY
WBOYforward
2023-05-11 23:04:041339browse

telepath is a Django library for exchanging data between Python and JavaScript, allowing you to build applications with rich client-side interfaces while keeping business logic in server-side code.

What does it do?

It provides a mechanism to package structured data, including Python objects, into a JSON serializable format. This mechanism can be extended to support any Python class by registering it with the corresponding JavaScript implementation. The packed data can then be included in the HTTP response and decompressed in JavaScript to obtain a data structure equivalent to the original data.

Installation method

pip install telepath

And add 'telepath' to the project's INSTALLED_APPS.

Introduction

Suppose we are building a Django application for playing checkers. We have spent days or weeks building a Python implementation of the game rules and providing classes that represent the current game state and various parts. However, we also want to provide the player with a properly user-friendly interface, which means it's time for us to write a JavaScript front-end. Our UI code will inevitably have its own objects representing different roles, mirroring the data structures we are tracking on the server - but we cannot send Python objects, so sending this data to the client will usually That means designing a JSON representation of the game state, and designing a lot of styling code on both ends, traversing the data structure to convert back and forth between native objects. Let's see how telepath simplifies the process.

A complete checkers game is a bit too much for this tutorial, so we'll just opt ​​for the rendering step...

In the Python environment, create a new Django project:

pip install "Django>=3.1,<3.2" django-admin startproject draughts cd draughts ./manage.py startapp games

Add 'games' to the INSTALLED_APPS list in drafts/settings.py.

For simplicity, in this example we will not involve a database and represent the game state as a normal Python class instead of a Django model. Modify games/views.py as follows:

from django.shortcuts import render   class Piece:     def __init__(self, color, position):         self.color = color         self.position = position   class GameState:     def __init__(self, pieces):         self.pieces = pieces      @staticmethod     def new_game():         black_pieces = [             Piece(&#39;black&#39;, (x, y))             for y in range(0, 3)             for x in range((y + 1) % 2, 8, 2)         ]         white_pieces = [             Piece(&#39;white&#39;, (x, y))             for y in range(5, 8)             for x in range((y + 1) % 2, 8, 2)         ]         return GameState(black_pieces + white_pieces)   def game(request):     game_state = GameState.new_game()      return render(request, &#39;game.html&#39;, {})

Create games/templates/game.html as follows:

<!doctype html> <html>     <head>         <title>Draughts</title>         <script>             document.addEventListener('DOMContentLoaded', event => {                 const gameElement = document.getElementById('game');                 gameElement.innerHTML = 'TODO: render the board here'             });         </script>     </head>     <body>         <h2>Draughts</h2>         <div id="game">         </div>     </body> </html>

Add new views to drafts/urls.py:

from django.contrib import admin from django.urls import path  from games.views import game  urlpatterns = [     path('', game),     path('admin/', admin.site.urls), ]

Now, start the server using ./manage.py runserver and visit http://localhost:8000/.

So far we have created a GameState object that represents the new game - now it's time to introduce telepath so that we can transfer that object to the client. Execute the following command:

pip install telepath

and add 'telepath' to the INSTALLED_APPS list in drafts/settings.py. Now edit the games/views.py file:

import json from django.shortcuts import render from telepath import JSContext  # ...  def game(request):     game_state = GameState.new_game()      js_context = JSContext()     packed_game_state = js_context.pack(game_state)     game_state_json = json.dumps(packed_game_state)      return render(request, 'game.html', {         'game_state_json': game_state_json,     })

Here JSContext is a helper tool used to manage the conversion of game state objects into a representation that we can use in Javascript. js_context.pack takes that object and converts it into a value that can be JSON serialized and passed to our template. However, now reloading the page fails with an error of the form: don't know how to pack object:

This is because GameState is a custom Python type that Telepath doesn't know how to handle yet. Any custom types passed to pack must be linked to the corresponding JavaScript implementation; this is done by defining an Adapter object and registering it with telepath. Update game/views.py as follows:

import json from django.shortcuts import render from telepath import Adapter, JSContext, register  # ...  class GameState:     # keep definition as before   class GameStateAdapter(Adapter):     js_constructor = 'draughts.GameState'      def js_args(self, game_state):         return [game_state.pieces]      class Media:         js = ['draughts.js']   register(GameStateAdapter(), GameState)

Here js_constructor is the identifier of the JavaScript constructor that will be used to build the GameState instance on the client, and the js_args definition will be passed to this constructor List of arguments to recreate the JavaScript counterpart of the given game_state object. The Media class indicates a file that follows Django's conventions for formatting media, and is where GameState's JavaScript implementation can be found. We will see what this JavaScript implementation looks like later, for now we need to define a similar adapter for the Piece class, since our definition of GameStateAdapter depends on being able to package Piece instances. Add the following definition to games/views.py:

class Piece:     # keep definition as before   class PieceAdapter(Adapter):     js_constructor = 'draughts.Piece'      def js_args(self, piece):         return [piece.color, piece.position]      class Media:         js = ['draughts.js']   register(PieceAdapter(), Piece)

Reload the page and you will see the error disappear, indicating that we have successfully serialized the GameState object to JSON and passed it to the template. Now we can include it in the template - edit games/templates/game.html:

<body>        <h2>Draughts</h2>        <div id="game" data-game-state="{{ game_state_json }}">        </div>    </body>

Reload the page again and inspect the game element in your browser's developer tools (in Chrome and Firefox , right-click the TODO comment and select Inspect or Inspect Element), you will see a JSON representation of the GameState object, ready to be unpacked into a complete JavaScript object.

In addition to packaging the data into a JSON-serializable format, the JSContext object also tracks the JavaScript media definitions required to decompress the data as its media properties. Let's update the game view to pass it to the template as well - in games/views.py:

def game(request):     game_state = GameState.new_game()      js_context = JSContext()     packed_game_state = js_context.pack(game_state)     game_state_json = json.dumps(packed_game_state)      return render(request, 'game.html', {         'game_state_json': game_state_json,         'media': js_context.media,     })

Add the following code to the HTML header file in games/templates/game.html:

<head>         <title>Draughts</title>         {{ media }}         <script>             document.addEventListener('DOMContentLoaded', event => {                 const gameElement = document.getElementById('game');                 gameElement.innerHTML = 'TODO: render the board here'             });         </script>     </head>

重新加载页面并查看源代码,您将看到这带来了两个JavaScript包括 ——  telepath.js(客户端telepath库,提供解包机制)和我们在适配器定义中指定的draughts.js文件。后者尚不存在,所以让我们在games /  static / draughts.js中创建它:

class Piece {     constructor(color, position) {         this.color = color;         this.position = position;     } } window.telepath.register('draughts.Piece', Piece);   class GameState {     constructor(pieces) {         this.pieces = pieces;     } } window.telepath.register('draughts.GameState', GameState);

这两个类定义实现了我们先前在适配器对象中声明的构造函数-构造函数接收的参数是js_args定义的参数。window.telepath.register行将这些类定义附加到通过js_constructor指定的相应标识符。现在,这为我们提供了解压缩JSON所需的一切-回到games  / templates / game.html中,更新JS代码,如下所示:

<script>            document.addEventListener('DOMContentLoaded', event => {                const gameElement = document.getElementById('game');                const gameStateJson = gameElement.dataset.gameState;                const packedGameState = JSON.parse(gameStateJson);                const gameState = window.telepath.unpack(packedGameState);                console.log(gameState);            })        </script>

您可能需要重新启动服务器以获取新的games/static文件夹。重新加载页面,然后在浏览器控制台中,您现在应该看到填充了Piece对象的GameState对象。现在,我们可以继续在games/static/draughts.js中填写渲染代码:

class Piece {     constructor(color, position) {         this.color = color;         this.position = position;     }      render(container) {         const element = document.createElement('div');         container.appendChild(element);         element.style.width = element.style.height = '24px';         element.style.border = '2px solid grey';         element.style.borderRadius = '14px';         element.style.backgroundColor = this.color;     } } window.telepath.register('draughts.Piece', Piece)   class GameState {     constructor(pieces) {         this.pieces = pieces;     }      render(container) {         const table = document.createElement('table');         container.appendChild(table);         const cells = [];         for (let y = 0; y < 8; y++) {             let row = document.createElement(&#39;tr&#39;);             table.appendChild(row);             cells[y] = [];             for (let x = 0; x < 8; x++) {                 let cell = document.createElement(&#39;td&#39;);                 row.appendChild(cell);                 cells[y][x] = cell;                 cell.style.width = cell.style.height = &#39;32px&#39;;                 cell.style.backgroundColor = (x + y) % 2 ? &#39;silver&#39;: &#39;white&#39;;             }         }          this.pieces.forEach(piece => {             const [x, y] = piece.position;             const cell = cells[y][x];             piece.render(cell);         });     } } window.telepath.register('draughts.GameState', GameState)

在games/templates/game.html中添加对render方法的调用:

<script>             document.addEventListener('DOMContentLoaded', event => {                 const gameElement = document.getElementById('game');                 const gameStateJson = gameElement.dataset.gameState;                 const packedGameState = JSON.parse(gameStateJson);                 const gameState = window.telepath.unpack(packedGameState);                 gameState.render(gameElement);             })         </script>

重新加载页面,您将看到我们的跳棋程序已准备就绪,可以开始游戏了。

  • 让我们快速回顾一下我们已经取得的成果:

  • 我们已经打包和解包了自定义Python /  JavaScript类型的数据结构,而无需编写代码来递归该结构。如果我们的GameState对象变得更复杂(例如,“棋子”列表可能变成棋子和国王对象的混合列表,或者状态可能包括游戏历史),则无需重构任何数据打包/拆包逻辑,除了为每个使用的类提供一个适配器对象。

  • 仅提供了解压缩页面数据所需的JS文件-如果我们的游戏应用程序扩展到包括Chess,Go和Othello,并且所有生成的类都已通过Telepath注册,则我们仍然只需要提供与跳棋知识相关的代码。

即使我们使用任意对象,也不需要动态内联JavaScript ——  所有动态数据都以JSON形式传递,并且所有JavaScript代码在部署时都是固定的(如果我们的网站强制执行CSP,这一点很重要)。

The above is the detailed content of How to exchange data between Python and JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete