因为项目是给不懂Python或者不会安装Python环境的人用的,不可能给每个人都安装环境,所以选择用Pyinstaller把django打包成exe文件,然后在电脑上直接运行的办法。
调试环境:Windows10,Python3.5,django1.11.5,pyinstaller3.3.
以下是遇到的各种问题,凭记忆写的,可能不是很详细:
tips:每次打包完成之后如果还是和之前一样的错误,建议先删除掉build和dist文件夹(如果删除失败很可能是你命令行在那个文件夹里面,因为你刚刚运行了manage.exe),然后再用 pyinstaller manage.py 打包,否则有时候你改了一些其他的文件,但是他不会识别到(因为manage.py文件并没有发生变化),而是使用之前的文件直接生成,所以会产生和之前同样的错误。
我开始时候的目录结构:
/web_manage
/image # (存放用户上传目录文件,本应叫media,但是没来得及改)
/opeartion # (唯一的一个app,名字就是operation)
/static # (存放js,css,image等静态文件目录)
/templates # (模板文件目录)
/utils # (存放一些额外的自己写的函数之类的文件目录)
/web_manage (存放settings文件目录)
settings.py
urls.py
wsgi.py
db.sqlite3 # (数据库文件)
manage.py # (这就不说了吧)
1.ImportError:web_manage.settings is not a module (也许是package?)。
web_manage是我的project的名字,但是也是settings父文件夹的名字(pycharm新建项目适合没有设置好的锅)。
综合之前的情况考虑,这个可能是我的文件夹重名了,所以无法找到settings文件。
所以解决方案:
改文件夹的名字,不能一样。因为settings父文件夹会有很多引用,所以不能乱改,就改 project 文件夹的name。然后担心还是有问题,所以在manage.py文件里面import sys 之后加了两行代码:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
NEW_DIR = os.path.join(BASE_DIR, 'web_manage')
这段代码的作用是,把和该py文件同目录下的web_manage文件夹加到Python的搜索路径下去。
再次pyinstaller,就不是这个错误了,下一个。
2.ImportError: no module named django.contrib.admin.apps
这个问题很重要,因为是大部分阻挡我前进的原因。和这个一起遇到的还有
- ImportError: no module named django.contrib.auth.apps
- ImportError: no module named django.contrib.contenttypes.apps
- ImportError: no module named django.contrib.sessions.apps
- ImportError: no module named django.contrib.messages.apps
- ImportError: no module named django.contrib.staticfiles.apps
这是一连串的错误,但是运行的时候只会一个一个报错。这几个contrib后面的,都是django自带的app,因为在运行的时候才会用到,而pyinstaller没有发现它们,也就不会去把它们放在程序文件里面,所以就报错了。这个是百度了很久都没找到我想要的,然后在神站stackoverflow上面一篇只有2个人赞的文章找了解决办法。
那个回答主要是说:
pyinstaller不会去引用这些隐式调用的module,所以需要自己去添加,他在Python文件夹下面的\Lib\site-packages\PyInstaller\hooks文件夹里面新添加了一个hook文件hook-django.contrib.py,文件内容如下:
# Copyright (c) 2005-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('django.contrib')
我照着他的做了,但是问题依然没有解决,还是那样报错,所以就开始联想了,我猜这语句的意思就是增加django.contrib包到exe文件去。回答者说他成功了,但是不知道为什么我这里没效果,于是试着改了一下,加了个.admin,文件名也改成hook-django.contrib.admin.py。
# Copyright (c) 2005-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('django.contrib.admin')
当我抱着试一试的心态去再删除build和dist文件夹的时候。
居然成功了,突然开心。
所以果然成功道路上布满了荆棘,程序照样停止运行了。所以下一个问题变成了django.contrib.auth.apps的问题了。
我就突然发现,其实这些都是app,所以估计会一起报错,于是想着把另外几个app对应的名字和文件也加到hook文件夹里面。
但是作为一个程序员,能够去做这么麻烦的事情吗?当然不可能了,于是看看其他的hook文件,有没有一下子添加好几个module的操作。
哇哈哈哈,还真让我找到了一个hook-django.core.mail.py~
找到的文件,长这样
#-----------------------------------------------------------------------------
# Copyright (c) 2013-2017, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
"""
django.core.mail uses part of the email package.
Problem is: when using runserver with autoreload mode, the thread that
checks fore changed files unwillingly trigger further imports within
the email package because of the LazyImporter in email (used in 2.5 for
backward compatibility).
We then need to name those modules as hidden imports, otherwise at
runtime the autoreload thread will complain with a traceback.
"""
hiddenimports = [
'email.mime.message',
'email.mime.image',
'email.mime.text',
'email.mime.multipart',
'email.mime.audio'
]
于是照着之前的操作,新建个类似文件名的文件hook-django.contrib.py ,Ctrl+C再Ctrl+V,改一下里面的包名,然后,失败了。
此时此刻,我开始怀疑自己爆炸的操作了。
没办法,不屈的程序猿也要屈服于bug了,我还是老老实实去把那几个文件加上吧。
至此,大部分问题都解决了,因为可以运行manage.exe不报错了。
然而,事情果然不简单。又报错了,下面又开始了填坑之路。
ImportError: no module named django.template.loader
这个时候,我已经成了一个老司机了,看到这个问题,就照着之前的样子去又加了一个hook文件,果然就没问题了,继续操作。
TemplateSyntaxError: 'static' is not a registered tag library. Must be one of
这个问题不是hook的问题了,是另外的,好在也在stackoverflow找到了解决办法。照做了,就解决了。这是因为模板文件头加了个{% load staticfiles %}然后文件夹引用了static文件夹的原因,然而文件没有引入解析static的功能,所以需要引入。
原文django.template.exceptions.TemplateSyntaxError: 'static' is not a registered tag library. Must be one of:
具体做法是:
在settings文件里面,在TEMPLATES设置里面OPTIONS后面加一个libraries的信息就好了
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.media',
],
'libraries': { # Adding this section should work around the issue.
'staticfiles': 'django.templatetags.static',
},
},
},
]
下一个就是TemplateNotFound的error了。
这个简单,是没有找到templates文件,直接就把image,static,templates这几个文件夹拷进去了manage.exe的同一个文件夹内。解决。
最后一个问题,static文件夹无法识别
打开网页之后,发现有网页内容、图片,但是css、js全都没有。这很明显是static的锅,肯定是没有找到。然而又发现在image文件夹下的图片显示出来了。于是走了个投机取巧的办法,把static文件夹复制进去了image文件夹。然后把static的路径改成了MEDIA_URL + STATIC_URL的样子。于是一切就正常了。。。
STATIC_URL = '/image/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
MEDIA_URL = '/image/'
MEDIA_ROOT = os.path.join(BASE_DIR, "image")
然而问题又来了,bootstrap图标好像不能正常显示了,所以感觉还是把MEDIA_URL改成'/static/media/',然后把media文件夹里面的内容放到static文件夹下面的media文件夹里面,这样应该可以解决这个问题了(我还没去试,感觉行得通)。
或者试试把 STATICFILES_DIRS 改成 STATICFILES_DIR = os.path.join(BASE_DIR, "static")?(这个骚操作,可以试一试,不保证成功率的哈)
弄了一大下午,终于解决了这一大堆的麻烦,接下来终于可以继续去完善这个项目了。完成之后再发一篇文章。