Electron + Django(第1部分),桌面应用程序集成JavaScript和Python
#python #django #typescript #electron

简介和概念

通过Electron和Django构建桌面应用程序实际上是在构建Web应用程序,该应用程序使用电子作为前端由本地浏览器和Django作为后端。

chart.drawio.png

那么,为什么我们需要构建桌面应用程序而不是Web应用程序?

因为:

  • 桌面应用程序可以离线运行
  • 桌面应用程序可以访问客户端PC低级API(例如文件系统ð)

POC(概念验证)

我们将使用电子,打字稿,webpack和django构建一个简单的桌子应用程序。

output.gif

如上图所示,

  • 用户首先输入文本。
  • 输入传递给Django Web服务器并返回输出,该输出组合了输入,日期和时间以及其他文本。
  • 最后,输出将显示在窗口中

0.先决条件

假设您已经安装了:

  • 节点JS,v14.19.1
  • python,3.9.10
  • Virtualenv,20.13.1

1.设置电子

我们将使用电子伪造TypeScript + Webpack模板创建我们的桌面应用程序。

  • 在命令窗口中运行以下命令:
npx create-electron-app edtwExample --template=typescript-webpack

我们命名了示例edtw,Whick代表 e lectron, d jango, t ypscript, w ebpack

  • 运行命令后,您应该看到命令窗口的输出:

image.png

文件结构:

image.png

  • run npm run start内部insives edtwExample文件夹,应弹出以下窗口 image.png

2.设置Django

  • edtwExample文件夹中创建一个名为python的文件夹
mkdir python
cd python

image.png

  • 创建虚拟环境并激活它
virtualenv edtwExampleEnv
edtwExampleEnv\Scripts\activate
  • 安装Django和Django Rest框架(具有版本)
pip install django==4.0.3 djangorestframework==3.13.1
  • 发起django项目
django-admin startproject edtwExample

这是结果文件结构:
image.png

  • 通过以下命令运行django应用程序
python manage.py runserver
  • 在浏览器中打开127.0.0.1:8000,您应该看到以下内容: image.png

3.电子启动时启动Django应用(使用Spawn)

为了这样做,我们在index.ts中创建了一个使用spawn运行django runserver命令
startDjangoServer方法

import { spawn } from 'child_process';

const startDjangoServer = () =>
{
    const djangoBackend = spawn(`python\\edtwExampleEnv\\Scripts\\python.exe`,
        ['python\\edtwExample\\manage.py', 'runserver', '--noreload']);
    djangoBackend.stdout.on('data', data =>
    {
        console.log(`stdout:\n${data}`);
    });
    djangoBackend.stderr.on('data', data =>
    {
        console.log(`stderr: ${data}`);
    });
    djangoBackend.on('error', (error) =>
    {
        console.log(`error: ${error.message}`);
    });
    djangoBackend.on('close', (code) =>
    {
        console.log(`child process exited with code ${code}`);
    });
    djangoBackend.on('message', (message) =>
    {
        console.log(`message:\n${message}`);
    });
    return djangoBackend;
}

以下脚本调用cmd以使用comguments ['python\\edtwExample\\manage.py', 'runserver', '--noreload']的命令python\edtwExampleEnv\Scripts\python.exe运行新过程。

const djangoBackend = spawn(`python\\edtwExampleEnv\\Scripts\\python.exe`,
        ['python\\edtwExample\\manage.py', 'runserver', '--noreload']);

以下脚本记录django流程的输出

djangoBackend.stdout.on('data', data =>
{
    log.info(`stdout:\n${data}`);
});
djangoBackend.stderr.on('data', data =>
{
    log.error(`stderr: ${data}`);
});
djangoBackend.on('error', (error) =>
{
    log.error(`error: ${error.message}`);
});
djangoBackend.on('close', (code) =>
{
    log.info(`child process exited with code ${code}`);
});
djangoBackend.on('message', (message) =>
{
    log.info(`stdout:\n${message}`);
});

我们在createWindow方法中调用startDjangoServer方法。

const createWindow = (): void => {

    startDjangoServer();

  // Create the browser window.
  const mainWindow = new BrowserWindow({
    height: 600,
    width: 800,
  });

  // and load the index.html of the app.
  mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);

  // Open the DevTools.
  mainWindow.webContents.openDevTools();
};
  • 运行npm run start并打开任务管理器,您应该能够看到Python流程
    image.png

  • 如果关闭应用程序窗口,则Python进程将停止

笔记

必须包括['python\\edtwExample\\manage.py', 'runserver', '--noreload']中的参数--noreload,以防止Django应用程序启动两次。

如果省略了--noreload,您将在后台运行4个Python实例。
image.png

即使您关闭了应用程序窗口,还有2个Python实例,您仍然可以访问Django网站。
image.png

4.在Django中构造API方法

  • INSTALLED_APPS中添加rest_framework in settings.py

image.png

  • 在命令窗口中运行以下命令以创建一个名为edtwExampleAPI的应用程序
python manage.py startapp edtwExampleAPI

您应该看到以下文件结构:

image.png

  • edtwExample\urls.py中添加path('', include('edtwExampleAPI.urls')),

image.png

  • 在文件夹edtwExampleAPI下创建urls.py并在那里粘贴以下内容
from django.urls import include, path
from rest_framework import routers
from . import views

router = routers.DefaultRouter()
router.register( 'edtwExampleAPI', views.EdtwViewSet, basename='edtwExampleAPI' )

## Wire up our API using automatic URL routing.
## Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('', include(router.urls)),
]
  • views.py中,复制并粘贴以下内容
from datetime import datetime
from rest_framework import viewsets
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response

class EdtwViewSet(viewsets.ViewSet):
    ## Create your views here.
    @action(methods=['GET'],  detail=False, name='Get Value from input' )
    def get_val_from( self, request ):

        input = request.GET[ 'input' ]

        return Response( status=status.HTTP_200_OK,
                data=f"[{ datetime.now() }] input= { input }, value from Django" )
  • 重新启动Django Web服务器,然后转到http://127.0.0.1:8000/edtwExampleAPI/get_val_from/?input=This+is+an+input+text

将显示以下内容:
image.png

5.从电子致电Django API

  • 复制并将以下代码粘贴到index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World!</title>
  </head>
  <body>
    <h1>💖 Hello World!</h1>
    <p>Welcome to your Electron application.</p>
    <input id="input_text" type="text"></body>
    <button id="btn_get_val_from_django" >Get Value From Django</button>
    <h2>Output</h2>
    <p id="p_output"></p>
  </body>
</html>
  • 复制并将以下代码粘贴到renderer.ts
import axios from 'axios';
import './index.css';

const btnGetValFromDjango = document.getElementById('btn_get_val_from_django');
btnGetValFromDjango.onclick = async () => {

    const res = await axios.get("http://127.0.0.1:8000/edtwExampleAPI/get_val_from/", { params: {
        input: ( document.getElementById('input_text') as HTMLInputElement ).value
    } });

    const result = res.data;
    document.getElementById('p_output').innerHTML = result;
};

console.log('👋 This message is being logged by "renderer.js", included via webpack');
  • 我们的逻辑完成了。在这里,将会出现2个错误。不用担心,我会让您知道如何解决它。 ð

如果您测试了应用程序,它将显示以下错误。

image.png

上述错误是由于内容安全策略造成的。我们将通过在package.json中添加devContentSecurityPolicy并重新启动应用程序来修复它。 (有关更多信息,请参见this。)

"@electron-forge/plugin-webpack",
{
    "mainConfig": "./webpack.main.config.js",
    "devContentSecurityPolicy": "connect-src 'self' http://127.0.0.1:8000 'unsafe-eval'",
    "renderer": {
        "config": "./webpack.renderer.config.js",
        "entryPoints": [
            {
                "html": "./src/index.html",
                "js": "./src/renderer.ts",
                "name": "main_window"
            }
        ]
    }
}
  • 之后,如果您再次尝试该应用程序,将会有另一个错误。

image.png

这是由于常见的CORS政策。我们选择here中引入的修复程序。

这个概念是在浏览器检查原点之前替换标题。

index.ts中添加以下方法

const UpsertKeyValue = (obj : any, keyToChange : string, value : string[]) => {
    const keyToChangeLower = keyToChange.toLowerCase();
    for (const key of Object.keys(obj)) {
        if (key.toLowerCase() === keyToChangeLower) {
        // Reassign old key
        obj[key] = value;
        // Done
        return;
        }
    }
    // Insert at end instead
    obj[keyToChange] = value;
}

更改createWindow方法如下所示6

const createWindow = (): void => {

    startDjangoServer();

    // Create the browser window.
    const mainWindow = new BrowserWindow({
        height: 600,
        width: 800,
    });

    mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
        (details, callback) => {
            const { requestHeaders } = details;
            UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*']);
            callback({ requestHeaders });
        },
    );

    mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
        const { responseHeaders } = details;
        UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*']);
        UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*']);
        callback({
            responseHeaders,
        });
    });

    // and load the index.html of the app.
    mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);

    // Open the DevTools.
    mainWindow.webContents.openDevTools();
};
  • 重新启动应用程序并完成! ðð

源代码

您可以检查source code以获取更多信息。

使用电子和Django构建桌面应用程序的原因

最初,我想构建一个桌面应用程序,并专注于使用Python来构建后端或业务逻辑,因此我首先搜索Python的桌面框架。但是,他们要么是

  • 不是很用户友好
  • 缺乏漂亮的UI框架
  • 不免费

由于以上三个原因,我希望如果选择它们,我可能需要花费大量时间来开发。 (这是一个很好的ref。)

当我是一个网络开发人员时,我问自己,我可以使用我已经知道的(例如JavaScript&Python)来构建桌面应用程序吗?

这就是为什么电子进入我的视力。

  • 电子 + django是一个很好的方法,如果

    • 您已经拥有一个使用Django作为后端的Web应用程序,并且您希望将其转换为桌面应用程序
    • 您想在Django上更出色 并使用您喜欢的前端库(例如React,Angular或Vue E.T.C)开发前端
  • 如果

    ,这可能不是一个不错的方法
    • 您一无所有地构建一个桌面应用程序。 (Electron + Django需要更多的时间来设置,您需要维护2种编程语言)。

      对于这种情况,我建议使用电子本身与打字稿结合使用,因为已经存在现有模板来处理此情况。此外,电子本身可以访问PC低级API以满足您的需求