Django 项目打包exe本地运行
记一次离谱的需求
其实本来觉得Django项目架构比较清晰,代码逻辑也简单,没打算记笔记,结果遇到离谱需求折腾了很久
开发了一个Django项目,到交付的时候了,客户说自己没有服务器…
没服务器还要登录功能😓
没办法,甲方最大,整吧
第一步,迁移数据库
项目数据库是基于服务器上的pgsql,先迁移到本地sqlite
先把数据导出到本地文件
python manage.py dumpdata
理论上是用这个命令,但是我好像报了编码错误,所以编写了下边这个python脚本
import osfrom django.core import serializers
from django.apps import apps
import json
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'efplctp.settings')import subprocess
import sys
import os# 获取虚拟环境中 Python 解释器的完整路径
# 如果你使用的是 Windows,路径可能是 venv\Scripts\python.exe
# 如果你使用的是 Unix/Linux/macOS,路径可能是 venv/bin/python
python_executable = os.path.join(os.path.dirname(sys.executable), 'python')# 构造完整的命令
command = [python_executable, "manage.py", "dumpdata"]# 运行命令并捕获输出
try:result = subprocess.run(command, capture_output=True, text=True, encoding="utf-8")if result.returncode == 0:# 将输出保存到文件with open("data.json", "w", encoding="utf-8") as f:f.write(result.stdout)print("数据已成功导出到 data.json")else:print("导出数据时出错:", result.stderr)
except Exception as e:print("运行命令时发生错误:", e)
修改项目数据库配置
导出了数据库文件,就可以修改setting.py里的数据库配置了,改成
DATABASES = {"default": {"ENGINE": 'django.db.backends.sqlite3',"NAME": BASE_DIR / 'db.sqlite3',}
}
创建数据库
python manage.py makemigrations
这个命令是基于模型类代码来检查模型的变化,并生成相应的迁移文件
python manage.py migrate
在数据库中创建相应的表和字段
导入数据
执行以下命令即可
python manage.py loaddata data.json
第二步,修改项目配置
修改静态资源文件和模板文件访问路径
大概修改这几个就够了
STATIC_URL = "static/"STATIC_ROOT = os.path.join(BASE_DIR, "static/")TEMPLATE_DIRS = [os.path.join(BASE_DIR, "templates")
]DEBUG = False
静态资源文件访问路径重定向
在urls.py
文件末尾添加代码
urlpatterns += re_path(r'static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT})
这样就可以在http请求静态资源文件时重定向到本地文件了~
这里要注意,修改的urls.py文件位置!!应该是和setting.py同级,这里有同事给我挖坑,害我折腾好久调不出来
第三步,编写运行脚本
先让项目跑起来
根目录下编写脚本 run.py
def main():os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'efplctp.settings')# 手动设置 sys.argvsys.argv = ['manage.py', 'runserver', '0.0.0.0:8000', '--noreload']with open('log.txt', 'w') as f:# 将标准输出重定向到文件对象,# 这一步是为了在打包后讲打印输出到这里#(其实输出也没什么用,但是不这么写的话,# 在打包成运行无命令行窗口的模式#( pyi-makespec --onefile --windowed run.py )时会报错)sys.stdout = f# 初始化 Djangodjango.setup()# 执行命令execute_from_command_line(sys.argv)if __name__ == '__main__':main()
执行命令打包运行脚本
pyi-makespec --onefile --windowed run.py
会生成一个spec文件,然后手动修改datas,用于目录映射,修改name指定输出文件名称
运行pyinstaller.exe .\run.spec
以生成exe文件,可能会有import报错,哪个模块报错就把哪个模块加到hiddenimports
里,最后得到以下run.spec
文件
# -*- mode: python ; coding: utf-8 -*-block_cipher = Nonea = Analysis(['run.py'],pathex=[],binaries=[],datas=[('static', './static'),('biz/templates', './templates'),('efplctp/templates', './templates'),('db.sqlite3', './')],hiddenimports=["common.middlewares","django.contrib.sessions","biz","django.contrib.staticfiles","django.contrib.contenttypes","common.middlewares","django.contrib.sessions.templatetags",'django.templatetags.future','django.templatetags.i18n','django.templatetags.l10n','django.contrib.admin.templatetags.log','django.contrib.admin.templatetags.admin_urls','django.contrib.admin.templatetags.admin_modify',],hookspath=[],hooksconfig={},runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)exe = EXE(pyz,a.scripts,a.binaries,a.zipfiles,a.datas,[],name='efplctp',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,upx_exclude=[],runtime_tmpdir=None,console=False,disable_windowed_traceback=False,argv_emulation=False,target_arch=None,codesign_identity=None,entitlements_file=None,
)
至此打包初步完成
双击运行后自动打开浏览器
在run.py
文件main
方法之前添加以下代码
url = 'http://localhost:8000'
webbrowser.open_new(url)
防止重复运行导致端口占用
在run.py
文件main
方法之前添加以下代码
def kill_process(*pids):for pid in pids:a = os.kill(pid, signal.SIGILL)print('已杀死pid为%s的进程, 返回值是:%s' % (pid, a))def get_pid(*ports):# 其中\"为转义"pids = []for port in ports:str = os.popen("netstat -ano | findstr :%s" % (port)).read()tmp = [i for i in str.split(' ') if i != '']if len(tmp) > 4:pid = tmp[4]pids.append(int(pid))print(str)print(pids)return pids# 杀死占用端口号的ps进程
ps_ports = ["8000"]
kill_process(*get_pid(*ps_ports))