django打包成客户端软件
用django做了一个网站,希望断网的时候,也能够在本地使用。google的gears功能很弱,需要一大堆js来操作sqlite数据库,感觉很麻烦。因此还是需要自己弄一个客户端软件。
py2exe可以提供打包功能,将python打包成一个可执行的exe文件。google了一下“django py2exe” ,已经有了很多详细的文档。这个老外的博客比较详细,按照这个流程下来就基本搞定了。
首先确定要打的包:
- django
- cherrypy,是多线程的http服务器,替换django的http测试服务器
- 自己的app
- python的email
编写一个用于启动的python文件,从原作者处拷贝过来,稍微做了一些修改,命名为startdesktop.py
import os
import sysos.environ[ 'DJANGO_SETTINGS_MODULE' ] = "settings"
import settings
from cherrypy import wsgiserver
import cherrypy
import webbrowser
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandlerif __name__ == "__main__":
print ‘To exit DemoSite close this window.’# Set up site-wide config first so we get a log if errors occur.
cherrypy.config.update({
‘environment’: ‘production’,
‘log.error_file’: ’site.log’,
‘log.screen’: True,
})# run CherryPy and open browser
try:
full_media_path = os.path.dirname( os.path.abspath( sys.argv[ 0 ])) + settings.ADMIN_MEDIA_PREFIXcherrypy.tree.graft(
AdminMediaHandler(
WSGIHandler(),
media_dir=full_media_path
),
‘/’
)
cherrypy.server.socket_port = settings.WEB_SERVER_PORT
cherrypy.server.start()
cherrypy.engine.start_with_callback( webbrowser.open, ( settings.START_URL,), )
cherrypy.engine.block()
except KeyboardInterrupt:
cherrypy.server.stop()
再然后就是py2exe的setup.py文件的编写了,基本上照抄下来,原作者使用的django版本很老,有一些模块已经没有了,同时又增加了很多新模块。同时目前的网站代码还是比较多,要手工一个个写是在太麻烦,就写一个函数搜一下,自动把当前的app文件添加进去。同时用正则表达式查找导入的django模块,发现一个就自动添加一个。修改后的setup.py文件如下:
# -*- coding: utf-8 -*-
from distutils.core import setup
import py2exe
import osdef add_path_tree( base_path, path, skip_dirs=[ '.svn', '.git' ]):
path = os.path.join( base_path, path )
partial_data_files = []
for root, dirs, files in os.walk( os.path.join( path )):
sample_list = []
for skip_dir in skip_dirs:
if skip_dir in dirs:
dirs.remove( skip_dir )
if files:
for filename in files:
sample_list.append( os.path.join( root, filename ))
if sample_list:
partial_data_files.append((
root.replace(
base_path + os.sep if base_path else ”,
”,
1
),
sample_list
))
return partial_data_filesdef add_python_file(includes,curpath,prefix=”):
files = os.listdir(curpath)
for f in files:
fpath = os.path.join(curpath, f)
if os.path.isdir(fpath):
if f == ‘.svn’:
continue
add_python_file(includes,fpath,prefix+’%s.’ % f)
else:
name,ext = os.path.splitext(f)
if ext == ‘.py’:
includes.append(’%s%s’ % (prefix,name))
add_django_import(includes,fpath)import re
pat = r’from +django\.(.*?) +import +(.*?)( +as +|\n)’
def add_django_import(includes,filepath):
#print filepath
f = open(filepath,’r')
data = f.read()
f.close()
models = re.findall(pat,data)
for m in models:
prefix = ‘django.%s.’ % m[0]
pys = m[1].split(’,')
for p in pys:
p = p.lower().strip()
dmodel = ‘%s%s’ % (prefix,p)
add_django_file(includes,dmodel)
prefix = ‘django.%s’ % m[0]
add_django_file(includes,prefix)def add_django_file(includes,dmodel):
if dmodel in includes:
return
item = dmodel.replace(’.',’/')
mpath = os.path.join(django_path,item)
if os.path.exists(mpath):
print ‘1′,dmodel
includes.append(dmodel)
#如果是django的目录,则继续寻找
mpath = os.path.join(mpath,’__init__.py’)
#print mpath
if os.path.exists(mpath):
add_django_import(includes,mpath)
else:
mpath = os.path.join(dja
ngo_path,’%s.py’ % item)
if os.path.exists(mpath):
#如果是django的文件,则继续寻找
print ‘2′,dmodel
#print mpath
includes.append(dmodel)
add_django_import(includes,mpath)py2exe_options = {
‘py2exe’: {
‘compressed’: 1,
‘optimize’: 2,
‘ascii’: 1,
‘bundle_files’: 1,
‘dist_dir’: ‘../setup-client’,
‘packages’: [ 'encodings' ],
‘excludes’ : [
'pywin',
'pywin.debugger',
'pywin.debugger.dbgcon',
'pywin.dialogs',
'pywin.dialogs.list',
'Tkconstants',
'Tkinter',
'tcl',
],
‘dll_excludes’: [ 'w9xpopen.exe', 'MSVCR71.dll' ],
‘includes’: [
'django.template.loaders.filesystem',
'django.template.loaders.app_directories',
'django.middleware.common',
'django.contrib.auth.backends',
'django.contrib.sessions.middleware',
'django.middleware.doc',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sessions.backends.db',
'django.contrib.sites',
'django.contrib.admin',
'django.core.cache.backends',
'django.db.backends.sqlite3.base',
'django.db.backends.sqlite3.introspection',
'django.db.backends.sqlite3.creation',
'django.db.backends.sqlite3.client',
'django.template.defaulttags',
'django.template.defaultfilters',
'django.template.loader_tags',
'django.conf.urls.defaults',
'django.contrib.admin.views.main',
'django.core.context_processors',
'django.contrib.auth.management',
'django.contrib.auth.management.commands',
'django.contrib.auth.handlers',
'django.contrib.auth',
'django.contrib.auth.middleware',
'django.views.static',
'django.contrib.admin.templatetags.adminmedia',
'django.contrib.admin.templatetags.admin_list',
'django.contrib.admin.templatetags.admin_modify',
'django.contrib.admin.templatetags.log',
'django.conf.urls.shortcut',
'django.views.defaults',
'django.core.cache.backends.locmem',
'django.templatetags.i18n',
'django.views.i18n',
# also used by django?
'email.mime.audio',
'email.mime.base',
'email.mime.image',
'email.mime.message',
'email.mime.multipart',
'email.mime.nonmultipart',
'email.mime.text',
'email.charset',
'email.encoders',
'email.errors',
'email.feedparser',
'email.generator',
'email.header',
'email.iterators',
'email.message',
'email.parser',
'email.utils',
'email.base64mime',
'email.quoprimime',
'traceback',
],
}
}# Take the first value from the environment variable PYTHON_PATH
python_path = os.environ[ 'PYTHONPATH' ].split( ‘;’ )[ 0 ]django_path = os.path.normpath( python_path + ‘/lib/site-packages/’)
django_admin_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/admin’ )
py2exe_data_files = []lc_path = os.path.normpath( python_path + ‘/lib/site-packages/django/conf/locale’)
# django admin files
py2exe_data_files += add_path_tree( django_admin_path, ‘templates’ )
py2exe_data_files += add_path_tree( django_admin_path, ‘media’ )
# project files
py2exe_data_files += add_path_tree( ”, ‘newmedia’ )
py2exe_data_files += add_path_tree( ”, ‘db’ )exedict = py2exe_options['py2exe']
includes = exedict['includes']
curpath = os.path.dirname(__file__)
add_python_file(includes,curpath)django_auth_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/auth’ )
add_python_file(includes,django_auth_path,prefix=’django.contrib.auth.’)
add_python_file(includes,django_admin_path,prefix=’django.contrib.admin.’)print includes
setup(
options=py2exe_options,
data_files=py2exe_data_files,
zipfile = None,
console=[ 'startdesktop.py' ],
)
最后再执行setup.py py2exe就开始打包了,最后得到的exe为6M多,有点大。
在这个过程中,还会碰到其他问题:
- template文件太多,不能暴露出去,这个时候可以预先装载template文件到一个py文件中,再编写一个load_template_source函数,放到settings中的TEMPLATE_LOADERS,用于模板查找就可以了。
- admin界面不能
使用,因为django使用imp来查找app的admin.py文件,而imp不能搜索.zip文件。可以使用woobiz的方法解决,这个暂时还没有试验。