简介与概念

从 Electron 和 Django 构建桌面应用程序实际上是构建一个 Web 应用程序,它使用 Electron 作为本地浏览器的前端,Django 作为后端。

chart.drawio.png

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

因为:

  • 桌面应用可以离线运行

  • 桌面应用可以访问客户端PC底层API(例如文件系统📁)

POC(概念验证)

我们将使用 Electron、TypeScript、Webpack 和 Django 构建一个简单的桌面应用程序。

输出.gif

如上图所示,

  • 用户首先输入一个文本。

  • 输入被传递到 Django Web 服务器并返回输出,输出结合了输入、日期和时间以及其他一些文本。

  • 最后会在窗口中显示输出

0.先决条件

假设您已经安装:

  • 节点js,v14.19.1

  • 蟒蛇,3.9.10

  • 虚拟环境,20.13.1

1.设置电子

我们将使用 Electron ForgeTypeScript + Webpack模板来创建我们的桌面应用程序。

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

我们将示例命名为edtw,whick 代表 Electron、Django、TypScript、Webpack

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

image.png

文件结构:

image.png

  • edtwExample文件夹内运行npm run start,应该会弹出如下窗口image.png

2.设置 Django

  • edtwExample文件夹中创建一个名为python的文件夹
mkdir python
光盘蟒蛇

image.png

  • 创建虚拟环境并激活
virtualenv edtwExampleEnv
edtwExampleEnv\Scripts 激活
  • 安装Django和Django REST框架(附版本)
pip install djangou003du003d4.0.3 djangorestframeworku003du003d3.13.1
  • 启动Django项目
django-admin startproject edtw示例

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

  • 通过以下命令运行 Django 应用
python manage.py 运行服务器
  • 在浏览器中打开 127.0.0.1:8000 应该会看到以下内容: image.png

3.电子启动时启动 Django 应用程序(使用 spawn)

为此,我们在index.ts中创建了一个startDjangoServer方法,该方法使用spawn运行 djangorunserver命令

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 以使用带有参数['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

  • 如果关闭app窗口,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_frameworksettings.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.从 Electron 调用 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并重新启动应用程序来修复它。 (有关更多信息,请参阅这个。)

"@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 政策。我们在这里选择中引入的修复。

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

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

index.ts中改变createWindow方法如下

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();
};
  • 重新启动应用程序并完成! 🎉👏

源码

您可以查看源代码以获取更多信息。

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

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

  • 不是很人性化

  • 缺少好看的UI框架

  • 不是免费的

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

作为一名网络开发人员,我问自己,我可以使用我已经知道的东西(例如 JavaScript 和 Python)来构建桌面应用程序吗?

这就是电子进入我视线的原因。

  • Electron + Django 是一个很好的方法,如果

  • 您已经有一个使用 Django 作为后端并使用 JavaScript 作为前端的 Web 应用程序,并且您希望将其转换为桌面应用程序

  • 你想在 Django 上更出色,并使用你最喜欢的前端库(例如 React、Angular 或 vue 等)开发前端

  • 如果

  • 您从无到有构建桌面应用程序。 (Electron + Django 需要相对更多的时间来设置,并且您需要维护 2 种编程语言)。

对于这种情况,我建议将 Electron 本身与 TypeScript 结合使用,因为已经存在模板来处理这个问题。此外,Electron 本身可以访问 PC 低级 API 以满足您的需求

下一篇

Electron + Django(第 2 部分),打包到生产环境

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐