>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:
- create a hefty installer that installs python, a database, puts files where they need to be, etc
- pyinstaller
- py2exe
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 \ " % 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.pyIt 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)