>Py2exe And Django 

Last Edit: Dec. 21, 2010, 10:10 p.m.

Portable django apps using py2exe

While webapps probably won't ever replace native programs, they do have their place. Their user interfaces are easy to design, and with django, setting them up is a breeze. If you create a django web app and would like to be able to distribute it, you've got a few options:

The first is probably not worth the trouble compared to the other options. I didn't have much luck with pyinstaller even though they claim to have the necessary introspection to work with django.

There are quite a few trixy things about compiling a django app with py2exe, and there aren't many helpful sites on the web on how to do it. This [lost the link in the server crash... sorry :( ] is one of the best, but I had to do a few more things to get it working.

This file was mostly from the afformentioned site. klap3 is the name of the app

import django
import klap3.settings
import webbrowser, time, sys, os
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
from django.core.handlers.wsgi import WSGIHandler

class DummyFile(object):
    def write(*a, **kw): pass

if __name__ == "__main__":
    os.environ['DJANGO_SETTINGS_MODULE'] = 'klap3.settings'
    port = 8000
    out = sys.stdout
    import klap3.admin

    from django.conf import settings
    try:
        path = 'adminmedia/'
        handler = AdminMediaHandler(WSGIHandler(), path)

        #sys.stderr = sys.stdout = DummyFile()
        webbrowser.open('http://localhost:%s' % port) #mmm
        run('0.0.0.0', port, handler)
    except WSGIServerException, e:
        # Use helpful error messages instead of ugly tracebacks.
        ERRORS = {
            13: "You don't have permission to access that port.",
            98: "That port is already in use.",
            99: "That IP address can't be assigned-to.",
        }
        try:
            error_text = ERRORS[e.args[0].args[0]]
            sys.stderr.write("Error: %s \n" % error_text)
        except (AttributeError, KeyError):
            error_text = str(e)

This is the bootstrapping script that you put in the external directory to your app

    * /bootstrap.py
    * /klap3/
          o settings.py
It takes care of alot of the magic involved. If you look at the pyinstaller django information, they require you to have a bootstrap script also, but their example doesn't work. Whether this would work with pyinstaller, I do not know.

The next important step is modifying your settings file to work from the zip file. There are two important changes here: the CURRENT_DIR and OUTSIDE_DIR. OUTSIDE_DIR is the directory the .zip file (and also the .exe generated) lives in. This is where you have to copy your template directory and sqlite database because they dont' like to read from the zip file.

# Django settings for klap3 project.
import os
CURRENT_DIR = os.path.dirname(__file__)
OUTSIDE_DIR = CURRENT_DIR.split('library.zip')[0]

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
    ('Your Name', 'your_email@domain.com'),

)

MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3'  

         
DATABASE_NAME = OUTSIDE_DIR + '\\test1.db'


DATABASE_USER = ''             # Not used with sqlite3.
DATABASE_PASSWORD = ''      # Not used with sqlite3. 
DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.

# Local time zone for this installation. All choices can be found here:
# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
# http://blogs.law.harvard.edu/tech/stories/storyReader$15
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT.
# Example: "http://media.lawrence.com"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
ADMIN_MEDIA_ROOT = OUTSIDE_DIR + '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = ''

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
    #'django.template.loaders.eggs.load_template_source',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.middleware.doc.XViewMiddleware',
)

ROOT_URLCONF = 'klap3.urls'
TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates".
    # Always use forward slashes, even on Windows.
		#C:\Documents and Settings\Sean.TOSHIBA-USER\Desktop\myKLAP3\klap3\templates
    os.path.join(OUTSIDE_DIR,'templates'),
)

#TEMPLATE_CONTEXT_PROCESSORS = (
	#'django.core.context_processors.auth',
#)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'django.contrib.humanize',
    #'django.contrib.databrowse',
    'klap3.search',
    'klap3.librarian',
    'klap3.admin',
    #'klap3.databrowse',
)


You also have to copy the django/contrib/admin/templates folder into your templates/admin directory as well as copy all the django/contrib/admin/media to the file specified in the bootstrap script.

There was one other change; to the url file:

import os
CURRENT_DIR = os.path.dirname(__file__)
OUTSIDE_DIR = CURRENT_DIR.split('library.zip')[0]
print "SITE_MEDIA"
print CURRENT_DIR
print OUTSIDE_DIR

urlpatterns += patterns('',
  # Media
  (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': OUTSIDE_DIR + '\\sitemedia'}),
	
  # Uncomment this for admin. This should be disabled for the production site
  (r'^admin/(.*)', admin.site.root),
  (r'^databrowse/(.*)', databrowse.site.root),
  
  #(r'^accounts/login/$', 'django.contrib.auth.views.login'),
  (r'^accounts/login/$', 'klap3.librarian.views.login_view'),
)
because i was using django.views.static.serve to deal out some static media, that also had to be set up to read from outside the .zip file. The directory had to be moved accordingly.

Ok, you finally create a setup script

from distutils.core import setup

import py2exe

setup(console=[{'script':'bootstrap.py','icon_resources':[(0x0001,'kath.ico')]}],
         options={'py2exe':{'packages':['django','email',]}})

I tried to get it to use an icon, but it wasn't working. the options line forces py2exe to include the entirety of django. You need this because django is sneaky about loading modules.

I was now ready to get compilin' I ran setup.py py2ex and got a build directory. I move all my folders as mentioned before to their required locations, and for good measure, copied the apps directory into library.zip/klap3/ Voila! a working distributable django app

Some clarification on what files needed to be moved:

Here is the directory structure of the app running in development mode. That entire file hierarchy was copied into library.zip/klap3/.

From the original directory structure, templates and media were copied to the top level output directory (with the .exe in it) as templates and sitemedia respectively (as listed in the urls file). The admin templates were copied from the site-packages/django/contrib/admin/templates/* to templates/admin/. Site-packages/django/contrib/admin/media was copied to the top level output directory and named adminmedia (in accordance to the bootstrap script)

Her words are bolts of lightning.