简介和概念
通过Electron和Django构建桌面应用程序实际上是在构建Web应用程序,该应用程序使用电子作为前端由本地浏览器和Django作为后端。
那么,为什么我们需要构建桌面应用程序而不是Web应用程序?
因为:
- 桌面应用程序可以离线运行
- 桌面应用程序可以访问客户端PC低级API(例如文件系统ð)
POC(概念验证)
我们将使用电子,打字稿,webpack和django构建一个简单的桌子应用程序。
如上图所示,
- 用户首先输入文本。
- 输入传递给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
- 运行命令后,您应该看到命令窗口的输出:
文件结构:
- run
npm run start
内部insivesedtwExample
文件夹,应弹出以下窗口
2.设置Django
- 在
edtwExample
文件夹中创建一个名为python
的文件夹
mkdir python
cd python
- 创建虚拟环境并激活它
virtualenv edtwExampleEnv
edtwExampleEnv\Scripts\activate
- 安装Django和Django Rest框架(具有版本)
pip install django==4.0.3 djangorestframework==3.13.1
- 发起django项目
django-admin startproject edtwExample
- 通过以下命令运行django应用程序
python manage.py runserver
- 在浏览器中打开127.0.0.1:8000,您应该看到以下内容:
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();
};
笔记
必须包括['python\\edtwExample\\manage.py', 'runserver', '--noreload']
中的参数--noreload
,以防止Django应用程序启动两次。
如果省略了--noreload
,您将在后台运行4个Python实例。
即使您关闭了应用程序窗口,还有2个Python实例,您仍然可以访问Django网站。
4.在Django中构造API方法
- 在
INSTALLED_APPS
中添加rest_framework
insettings.py
- 在命令窗口中运行以下命令以创建一个名为
edtwExampleAPI
的应用程序
python manage.py startapp edtwExampleAPI
您应该看到以下文件结构:
- 在
edtwExample\urls.py
中添加path('', include('edtwExampleAPI.urls')),
- 在文件夹
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
。
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个错误。不用担心,我会让您知道如何解决它。 ð
如果您测试了应用程序,它将显示以下错误。
上述错误是由于内容安全策略造成的。我们将通过在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"
}
]
}
}
- 之后,如果您再次尝试该应用程序,将会有另一个错误。
这是由于常见的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以满足您的需求
-