首页 > django > django打包成客户端软件

django打包成客户端软件

2009年11月23日 roboter 发表评论 阅读评论

用django做了一个网站,希望断网的时候,也能够在本地使用。google的gears功能很弱,需要一大堆js来操作sqlite数据库,感觉很麻烦。因此还是需要自己弄一个客户端软件。

py2exe可以提供打包功能,将python打包成一个可执行的exe文件。google了一下“django py2exe” ,已经有了很多详细的文档。这个老外的博客比较详细,按照这个流程下来就基本搞定了。

首先确定要打的包:

  1. django
  2. cherrypy,是多线程的http服务器,替换django的http测试服务器
  3. 自己的app
  4. python的email

编写一个用于启动的python文件,从原作者处拷贝过来,稍微做了一些修改,命名为startdesktop.py

import os
import sys

os.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 AdminMediaHandler

if __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_PREFIX

    cherrypy.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 os

def 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_files

def 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多,有点大。

在这个过程中,还会碰到其他问题:

  1. template文件太多,不能暴露出去,这个时候可以预先装载template文件到一个py文件中,再编写一个load_template_source函数,放到settings中的TEMPLATE_LOADERS,用于模板查找就可以了。
  2. admin界面不能
    使用,因为django使用imp来查找app的admin.py文件,而imp不能搜索.zip文件。可以使用woobiz的方法解决,这个暂时还没有试验。
分类: django 标签:
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.